Skip to content

인입·관리자 서버 호출을 자기 저장소·codec 으로, 잡 상태는 단계 DTO 로, 관리자 코드는 일반 앱 파사드 밖으로 — v0.18.0 A05 (2026-09-09)

  • 기간: 2026-09-09 ~ 2026-09-10 02:47 KST (세션 1개, 오너 지시 "#1406 진행해줘 나한테 물어보지 말고 phase 끝까지 완주" → "main 업데이트된거 확인하고 반영한다음 이어서 진행"). 계획 ID A05 / Phase 3 Step 1([리팩터링 3-1]).
  • 랜딩: PR #1443 (Phase 1~5 한 PR, base release/v0.18.0, 릴리스 큐 merge:request 로 통합) — release/v0.18.0 병합 4f6d193b(2026-09-10 02:47 KST, head 903fbbd6, base 99669e3e, 큐 8차 run 34384981686). 마이그레이션·엣지 함수 없음, DB·서버 함수·UI 표현 변경 없음. release 통합 / staging 확인 / Production 배포는 서로 다른 상태: release 통합까지 완료, staging·Production 은 미반영(§5). 적용 릴리스 v0.18.0 예정. 이 문서는 앱 저장소가 아니라 Barbelic-docs 에 있다(v0.17.7 #1463 문서 분리 — §4-5).
  • 설계서: 없음 — 분석·Phase 계획·"예상 효과·개선사항" 표는 이슈 #1406 댓글.
  • 정본: src/react/services/domains/repositories/importRepository.ts·adminRepository.ts, domains/codecs/wodupImportCodec.ts·motraImportCodec.ts·adminModerationCodec.ts, domains/adminComposition.ts(앱 진입점 그래프 밖), 본인 데이터 내보내기는 domains/adminDomain.ts(내보내기만 남음), 계약 src/react/contracts/ports/importDto.ts·adminDto.ts·adminPorts.ts; 문서 A01 추출 지도 §3-2·§4, G03 §16, G05 장부(import·admin 행).
  • 도구: 옛 파일에서 함수 블록을 표식 줄 기준으로 잘라내는 일회성 Python 스크립트 — 작업 폴더 밖 scratch, 제품에 없음. 장부는 node scripts/architecture/check-coverage-inventory.mjs --render, DB 모델 장부는 npm run db:model 로 재생성.
  • 게이트: tests/react/importRepositoryDestinations.test.mjs(14)·adminRepositoryDestinations.test.mjs(7)·adminBundleBoundary.test.mjs(3, 앱 진입점 모듈 그래프에 관리자 모듈 0), domainPortWiring(factory 런타임 import 허용 목록 + A05 codec 순수성), 기존 인입·관리자 테스트 40여 파일, npm run check 전체(§5), 앱 빌드 산출물 grep. 로컬 pgTAP: ci:precheck-local(2026-09-10 02:23 KST, head 410c87eb) 130파일·2,328 assert 통과. release 큐 실행 기록은 PR 본문.
  • 버그리포트: 없음(수리 건 아님).
  • 계약: ImportPort 결과 타입 고정(writes.ts), 관리자 port 신설(adminPorts.ts), G03 §16 정정 문단, A01 지도 §3-2 신설·§4 두 행 갱신.

Phase 현황

Phase내용상태
Phase 1release/v0.18.0 생성(origin/main 2ecd3430)·워크트리·착수 댓글·진입점 대조표
Phase 2인입 codec 2·importRepository 4함수·importDto·ImportPort 결과 고정·컨트롤러/러너/앱 셸 잡 DTO 연결·행동 검사 142f639bed
Phase 3관리자 codec·adminRepository 6함수·조립 분리·관리자 함수를 일반 파사드에서 제거·본인 내보내기 분리·관리자 port·행동 검사 7·번들 경계 검사 3f0005382 (main 반영 뒤 정리 169a9e2a)
Phase 4옛 경로 제거 확인·G05 장부·A01 지도·G03 §16·작업 기록·등록 2곳✅ 이 문서
Phase 5사전 검증 → PR → 릴리스 큐 → 통합 SHA 기록✅ release/v0.18.0 병합 4f6d193b (main v0.17.7 반영 169a9e2a, release 재반영 410c87eb·aa20aba7·903fbbd6, 큐 8차)

1. 배경

유저 A(관리자)가 관리자 페이지에서 Wodup 파일을 올리면 앱은 ① 배치 행을 만들고 ② 파일을 저장소에 올리고 ③ "업로드 완료" 상태 전이 RPC 를 부르고 ④ 엣지 함수로 서버 처리를 시작시키고 ⑤ 1.5초마다 배치 행을 다시 읽어 끝났는지 본다. 이 다섯 호출과 Motra 기입, 관리자 신고 처리·이용 정지·운영 점검표·엣지 배포 확인이 전부 5천 줄짜리 barbelicRepository.ts 안에 통계·소셜·그룹 코드와 섞여 있었다. A01(#1288)이 도메인별 목적지 파일을 만들었지만 인입·관리자 목적지에는 대표 함수 하나씩만 들어 있었고, Motra 함수는 옛 파일의 반환 타입을 타입 전용으로 참조하는 임시 경계를 남겼다.

Phase 3 의 I01(파서·identity)·D09(durable 잡 소비자)·A11(진행 상태 resource)은 인입 서버 호출과 잡 상태를 typed 계약으로 받아야 한다. 그 전에 소유 위치를 바꿔야 후속 담당이 5천 줄 파일을 통째로 가져오지 않고, 같은 완료 판정을 네 번째로 복제하지 않는다.

2. 문제 제기

결과 타입이 없어 컴파일이 아무것도 잡지 못했다

인입 port 5개(Wodup 3·Motra·InBody)와 관리자 함수 전부가 unknown/any 를 돌려줬다. 엣지 함수 응답({batch, job}), Motra 결과(session_count …), 신고 목록({items, counts}), 처리·정지 결과는 경계 검증 없이 화면까지 갔고, Motra 는 화면 컨트롤러가 서버 컬럼 이름(snake_case)을 직접 읽었다.

"접수됨"과 "끝남"을 같은 모양으로 돌려줬다

엣지 함수는 202(접수·처리 중)와 200(이미 끝남)을 구분해 답하지만 앱은 둘 다 배치 행 하나로 받았고, 관리자 컨트롤러·프로필 러너·앱 셸 세 곳이 각자 status 문자열 목록으로 "끝났는가"를 판정했다. 서버에 새 status 가 생기면 세 곳을 따로 고쳐야 했다.

관리자 전용 코드가 일반 앱 파사드에 있었다

BarbelicApi 에 관리자 전용 9개(신고 목록·처리·정지·운영 점검표·카탈로그 전량·매핑 페이지·유저 검색 등)가 들어 있어 일반 앱 빌드의 barbelicRepository 청크에 관리자 RPC 이름이 실렸다(빌드 grep 실측). 반대로 본인 데이터 내보내기(일반 사용자 기능)는 관리자 도메인 파일 안에 있었다.

3. 해결 방안

원칙

오너 결정 항목 없음(§26). 전제 3가지를 이슈 댓글에 적고 진행했다: ① release/v0.18.0 이 없어 최신 main(2ecd3430)에서 만들었다(보호 규칙은 갱신만 큐 전용, 생성 제한 없음). ② InBody 인입은 A03 이 profileRepository 로 옮긴 것을 유지하고 ImportPort 결과 타입만 고정한다. ③ 관리자 카탈로그·매핑 페이지의 직접 테이블 읽기(adminMappingPageRepository)는 검증 어댑터가 있어 옮기지 않고 관리자 전용으로 등재한다.

접근

내용판정
A. 근본 구조서버 호출을 자기 도메인 factory 로, 응답은 domains/codecs/ decoder 로, 잡 상태는 변환표 한 곳으로, 관리자는 전용 파사드로 분리채택
B. 땜질옛 파일에 두고 타입 주석만 추가, 컨트롤러의 문자열 비교 유지기각 — I01/D09/A11 이 다시 옛 파일을 가져와야 하고 완료 판정이 네 번째로 복제된다
C. 관리자 조립을 옛 파일에 남김createAdminRepositorybarbelicRepository 의 composition root 에서 조립기각(작업 중 실측) — 관리자 RPC 이름이 앱 청크에 그대로 실린다. 조립을 관리자 그래프 전용 모듈로 옮겨야 산출물 경계가 생긴다

4. 적용한 내용

Phase 1 — 준비·진입점 대조표

release/v0.18.0 을 origin/main 2ecd3430 에서 만들고(선행 A01 PR #1316 포함 확인) 작업 브랜치 feat/1406-a05-import-admin-repository 를 그 위에 두었다. G05 실행 목록과 실제 코드를 대조한 결과:

경로source·원문 위치행위자 → 대상 owner호출 순서소비 화면권한 경계A05 결과
Wodup 업로드wodup · 원문 = 저장소 버킷 wodup-imports/{owner}/{batch}/… + 배치 행(wodup_import_batches)본인 / 관리자 → 대상 계정(대리 인입, initiated_by_user_id)배치 insert → 파일 upload → mark_wodup_import_batch_uploaded_v1 (실패 시 …_failed_v1)관리자 인입 탭·프로필 화면(러너)RLS(wodup_import_batches_*_admin)·storage 정책·storage_ownership_checkimportRepository.uploadWodupJsonlOriginalWodupImportJobDto(pending)
Wodup 시작같은 배치본인 / 관리자엣지 wodup-start-import(→ enqueue_wodup_import_batch → 워커 kick)위와 같음엣지가 is_lift_guild_admin 재검사, 남의 배치는 404startWodupImportBatchWodupImportStartResult(accepted / already_running / already_terminal)
Wodup 조회배치 행 1건본인 / 관리자(대상 계정으로 좁힘)wodup_import_batches select(1.5초 폴링 ≤180회는 호출자)위와 같음RLSloadWodupImportBatchWodupImportJobDto(phase·terminal·outcome)
Motra 기입motra · 원문 = 브라우저 정규화 페이로드(파일 해시 file_sha256)본인 / 관리자 → 대상 계정import_motra_workouts_v1 동기 1회(one-shot 게이트는 서버)관리자 Motra 탭서버 is_lift_guild_admin·42501importMotraWorkoutsMotraImportResultDto(kind: "completed")
InBodyinbody · 원문 = 버킷 inbody-imports + inbody_import_batches본인A03 profileRepository.importInbodyBodyMetrics프로필 화면RLS유지(A03 소유), ImportPort 결과만 ProfileWorkspaceRecord 로 고정
신고 큐·처리·정지서버 표 content_reports·user_suspensions관리자 → 타 사용자get_admin_content_reports_v1·admin_resolve_content_report_v1·admin_set_user_suspension_v1관리자 신고 탭서버 is_lift_guild_admin(42501)adminRepository 3함수 + adminModerationCodec
운영 점검표RPC + 엣지 함수 GET 확인관리자get_admin_operations_checklist(앱 RPC 표본·엣지 배포 표본·기대 마이그레이션 인자)관리자 운영 탭서버adminRepository.loadAdminHealthReport(+ loadEdgeFunctionDeploymentSamples)
인입 대상 유저 검색auth.users+profiles관리자admin_search_users_v1인입·Motra 탭·커스텀 종목 탭서버 42501adminRepository.searchAdminImportTargets(옛 adminMappingPageRepository 에서 이동)
관리자 카탈로그·매핑exercises·exercise_synonyms·exercise_archetypes 직접 읽기, get_admin_mapping_page관리자adminMappingPageRepository(검증 어댑터 포함)카탈로그·매핑 탭RLS 의 is_lift_guild_admin 분기유지(관리자 전용, adminDomain 만 소비)
관리자 열람(impersonation)Vercel API POST /api/admin/impersonate·GET /api/admin/users관리자 → 대상 세션adminImpersonationClient(B01)일반 앱의 "유저로 보기" 런처서버 4관문(§2-3)유지(B01), 일반 파사드에 남김
본인 데이터 내보내기원시 표 select(본인 행만)본인loadCurrentProfile·loadDebugSnapshotRows프로필 화면RLS(본인)adminDomain(내보내기만) + userDataExportRepositoryPort 로 분리

없는 export·인입 기능을 새로 만들지 않았다.

Phase 2 — 인입 repository·codec (2f639bed)

  • domains/codecs/wodupImportCodec.ts: 배치 행 decoder 를 옛 services/wodupImportBatchAdapter.ts 에서 이동(사본 없음), 엣지 시작 응답 decoder({error, detail, current_status}WodupImportStartError, 문구는 종전과 같이 detail 우선), 서버 status → 단계 변환표(uploading·uploaded·queued → pending, normalizing·ready·importing → running, completed·completed_with_placeholders → succeeded, failed → failed; 모르는 status 는 거부).
  • domains/codecs/motraImportCodec.ts: 페이로드 모양 검사, 결과 decoder(status 가 completed 가 아니거나 개수·목록이 어긋나면 LG_SCHEMA_VALIDATION).
  • domains/repositories/importRepository.ts: 4함수 실제 소유. 진단 이벤트 이름·필드는 그대로, 전송 재시도 없음, 폴링 없음. 옛 파일의 Wodup 본문·wodupImportBatchPayload·상수 삭제, importRepository 인스턴스를 composition root 가 조립·재수출.
  • contracts/ports/importDto.ts 신설, ImportPort 결과 unknown 5 → 0. importDomain 은 인스턴스를 직접 받고 repositoryPorts 의 import 행 제거.
  • 컨트롤러(wodupImportController)·프로필 러너(wodupImportRunner)·앱 셸(appController.profileWodupImport)이 잡 DTO 의 phase/terminal 로 완료를 판정. motraImportController 는 camelCase DTO 를 읽는다. 관리자 feature 의 Motra 완료 판정은 result.kind === "completed".
  • 테스트: importRepositoryDestinations 14건 신설, 옛 저장소 테스트의 Wodup 5건 이전·삭제, readImportContractHardCut·wodupImportRunner·wodupImportPollCadence·adminImportOnBehalf·domainRepositoryDestinations·domainPortWiring 재조준.

Phase 3 — 관리자 repository·codec·파사드 (f0005382)

  • domains/codecs/adminModerationCodec.ts: 입력 검사(상태 필터·페이지 상한 1~200·조치 4종·메모 500자·uuid)와 응답 decoder(신고 큐 {items, counts}, 처리 {report_id, status, action, target_user_suspended}, 정지 {user_id, suspended}, 유저 검색 행). 신고 행은 화면(DesktopAdminReports)이 서버 컬럼 이름으로 읽으므로 필수 열만 고정한 열린 레코드.
  • domains/repositories/adminRepository.ts: 신고 3·유저 검색·운영 점검표·엣지 배포 확인. 점검표 검증기·배포 매니페스트·fetch 는 주입.
  • domains/adminComposition.ts: 옛 파일이 내주는 helper 묶음 repositoryRuntime(callRpc·callMutationRpc·진단 6개)으로 조립 — 관리자 셸 그래프에만 존재.
  • barbelicApi·lazyFeatureDomain·domains/index 에서 관리자 함수 8개 제거. (2026-09-09 시점에는 관리자 셸 전용 파사드 services/adminApi.ts 를 만들어 관리자 화면이 그것을 조립했으나, main 의 v0.17.7(#1463)이 관리자 브라우저 화면을 Barbelic-docs/admin 으로 옮기면서 이 저장소에 관리자 셸이 없어져 파사드도 함께 제거 — §4-5.)
  • 본인 데이터 내보내기 분리: adminDomain.ts 에는 내보내기 함수만 남고 옛 저장소의 원시 표 읽기 2개를 좁은 port(userDataExportRepositoryPort)로 받는다. contracts/ports/adminDto.ts·adminPorts.ts(관리자 port 3종·본인 내보내기 port·화면 경계 type guard 2개).
  • runtimeSchemaValidation.validateAdminHealthReport 의 반환을 검증한 AdminHealthReport 타입으로(캐스트 1곳, 검증 뒤).
  • 테스트: adminRepositoryDestinations 7건, adminBundleBoundary 3건 신설. adminHealthReport·lazyServiceBoundaries·adminMappingPageController·onDemandDataLoading·runtimeSchemaValidation·opsObservabilityContract·adminStandaloneShell·adminImportOnBehalf·CASE-018(e2e, 관리자 저장소를 조립 모듈에서 import) 재조준.

Phase 4 — 옛 경로 제거 확인·장부·문서

  • 옛 파일에 남은 인입·관리자 본문 0(readImportContractHardCut 부재 단언). services/wodupImportBatchAdapter.ts 삭제(이동), adminMappingPageRepository 의 유저 검색 삭제(이동), repositoryPorts 의 import/admin 행 삭제, lazyFeatureDomain 의 관리자 위임 8개 삭제.
  • G05 장부: 새 파일 9규칙(codec 3·조립·파사드·본인 내보내기·계약 3·테스트 3) 분류, import·admin 의 repository/service/types 행 증거 갱신, --render. 남은 미분류 24개는 선행 작업 파일(HQ 인계 그대로).
  • A01 지도 §3 두 행 완료 표기 + §3-2 신설, §4 import/admin port 행·helper 주입 행 갱신. G03 §16 표에 ImportPort 담당·결과 고정, 관리자 port 행 추가, 정정 문단. 총괄 A05 행·Phase 3 상태·장부 행, HQ 운영 문서 A05 게시 상태. 이 문서 + 사이드바·README 등록.

Phase 4-5 — main(v0.17.7) 반영 (2026-09-10, 169a9e2a)

오너 지시로 큐 요청을 보류하는 동안 main 에 v0.17.7(#1489)이 들어왔고, 그 안의 #1463 이 docs/ 전체와 관리자 브라우저 화면(독립 셸·feature·컨트롤러·adminMappingPageRepository)을 dekerd/Barbelic-docs 로 옮겼다. origin/main(937042d7)을 브랜치에 병합하며 충돌 28개를 정리했다.

  • main 대로 받은 것: 관리자 화면·컨트롤러·셸·관련 테스트 삭제, docs/ 삭제, e2e 업무 날짜 수정은 main 의 fixture(e2e/support/prOverviewFixture.mjs)로 대체(이 PR 의 임시 수정 2건 폐기), barbelicApi 의 카탈로그 관리자 쓰기 5개 제거(§남은 것의 R01 항목이 main 에서 해소됨).
  • 이 PR 에서 정리한 것: 관리자 셸이 없어졌으므로 관리자 셸 전용 파사드 services/adminApi.ts 와 중복 도메인 userDataExportDomain.ts 삭제. adminDomain.ts 는 main 의 내보내기 전용 모양을 유지하되 userDataExportRepositoryPort 만 받는다. adminPorts.ts 에서 카탈로그 읽기 port 제거. adminBundleBoundary 는 관리자 진입점 대신 adminComposition 그래프를 기준으로 하고 옛 저장소에 관리자 RPC 이름 0 단언을 추가. adminHealthReport 는 제거된 운영 콘솔 배선 단언 대신 저장소 위치 단언 1건. adminImportOnBehalf·opsObservabilityContract·onDemandDataLoading 의 삭제된 파일 앵커 제거. pending-changes.json 양쪽 사유 병합, dual-key 기준선 재생성, coverage-inventory 규칙 2개 제거.
  • 유지된 것: importRepository·wodupImportCodec·motraImportCodec·adminRepository·adminModerationCodec·adminComposition·port 계약 3파일·행동 검사 24건. 앱 쪽 관리자 저장소의 소비자는 e2e CASE-018 과 앞으로의 Barbelic-docs/admin 이다(연결은 후속).
  • 문서: 이 작업 기록과 A01 지도·G03 §16·G05 장부·총괄·HQ 운영 문서 편집은 앱 PR 에서 빠지고 Barbelic-docs PR 로 옮겼다(이 문서). G05 장부의 생성 절(--render)은 앱 저장소의 규칙 파일에서만 만들 수 있어 여기서는 import·admin 행만 손으로 갱신했다.
  • 검증(병합 뒤, 앱 저장소): tsc·check:test-manifest·check:unused 통과, npm run check 정적 검사 전부 통과 + 단위 3,477건 중 3,446 통과·실패 0·건너뜀 30·중단 1(legacyBundleIsolation 30초 예산 초과 — 다른 세션 부하 중, 단독 재실행 4.7초 통과). ci:local --full 은 이제 세션에서 직접 못 돌린다(큐 runner 전용).

Phase 5 — release 통합 (2026-09-10)

release 에 먼저 들어온 A06(#1453)·N01(#1441)·동기화(#1490)·A04(#1448)·S10(#1442)을 세 번에 걸쳐 합쳤다(410c87eb·aa20aba7·903fbbd6, 충돌 12곳). 옛 저장소에서 소셜 본문(A04)과 관리자 본문(A05)이 각각 빠지며 겹친 곳은 양쪽 모두 제거하고, composition root 는 A04 의 소셜·그룹 조립 + A05 의 인입 조립·repositoryRuntime 으로 합쳤다. 큐 실행: 6차(A06 선행 통합 충돌)·7차(A04·S10 선행 통합 충돌)는 Merge Check에서 떨어졌고 코드 결함은 0건, 8차 run 34384981686 통과 → 4f6d193b. #1491 정책(2026-09-10)대로 큐는 full CI 를 돌리지 않았고 full CI 는 release→staging 승격 후보에서 1회 돈다.

주요 결정과 그 근거

  • 완료 판정은 codec 의 변환표 한 곳이 한다. 컨트롤러·러너·앱 셸이 status 문자열 목록으로 판정하던 것을 잡 DTO 의 phase/terminal 로 바꿨다. 폴링 상한(180회)에 닿으면 마지막으로 읽은 비완료 잡을 그대로 돌려주고 화면은 완료로 그리지 않는다(종전 동작 유지, 이제 타입이 그것을 말한다).
  • Wodup 시작 결과와 Motra 완료는 다른 타입이다. WodupImportStartResult.kind(accepted / already_running / already_terminal)와 MotraImportResultDto.kind: "completed" — 같은 boolean 이 없다.
  • 관리자 조립은 관리자 그래프에서. 옛 파일 안에서 조립하면 관리자 RPC 이름이 앱 청크에 실린다(빌드 grep 실측). domains/adminComposition.ts 로 옮기고 옛 파일은 helper 묶음만 내준다.
  • 신고 행은 열린 레코드. 화면이 서버 컬럼 이름을 읽는 것(U03 소유 파일)을 바꾸지 않고 필수 열만 타입으로 고정했다. camelCase 전환은 U03 이 화면을 옮길 때.
  • 카탈로그 관리자 쓰기 5개는 옮기지 않았다. A03 의 catalogRepository 소유였고 2026-09-09 시점 일반 파사드에 남아 있었다. main 의 v0.17.7 이 barbelicApi 에서 이 5개를 제거해 병합 뒤에는 앱 파사드에 없다.

작업 중 드러난 것

  • 이슈 본문의 "landing:request 는 main/staging 전용" 문구는 구식이다 — 현재 스크립트는 base 가 release/* 인 PR 만 받는다.
  • 관리자 저장소를 옛 파일에서 조립하면 앱 청크에 관리자 RPC 이름이 남는다. 모듈 그래프 검사(adminBundleBoundary)와 빌드 grep 을 둘 다 남겨 재발을 막는다.
  • DB 모델 장부(supabase/contracts/db-objects.json·docs/data/db-model-ledger.md)는 앱 소비자 경로를 추적하므로 옛 저장소 → importRepository 이동으로 재생성이 필요했다(npm run db:model).
  • 이중 키 허용 기준선(dual-key-baseline.json)은 엣지 배포 응답의 옛/새 키 읽기 1곳이 파일을 옮긴 것만 반영했다(총 411 사이트 불변). 제거 조건 = 엣지 함수의 x-lift-guild-*·deploymentVersion 호환 폐기.
  • 명부 등재 테스트 9파일 재조준을 tests/audit/pending-changes.json 에 신고했다(readImportContractHardCut·repository·wodupImportRunner·adminHealthReport·lazyServiceBoundaries·adminMappingPageController·onDemandDataLoading·runtimeSchemaValidation + controllerBoundaries 는 무수정).
  • npm run buildBARBELIC_TARGET 없이는 거부한다(#1332) — 산출물 검사는 BARBELIC_TARGET=local npm run build 로 했다.
  • #1491 이후 큐는 2분 안에 끝나므로, 요청 직전에 origin/release/v0.18.0 을 다시 합쳐 두지 않으면 Merge Check의 충돌로 바로 떨어진다(7차 실패). 사전 검증 ci:precheck-local 의 통계 게시 성능 프로브(stats-isolated-convergence) 1건이 CPU 100% 부하(큐 runner full CI + 다른 세션 샌드박스 16개) 중 3초 제한 초과로 실패했다 — release 와 동일 SQL, A06 큐 실행에서 통과. 6시간 넘은 남의 샌드박스 3개를 내렸다.

5. 적용 결과

항목전 → 후
barbelicRepository.ts 의 인입·관리자 함수 본문Wodup 3·Motra 1·관리자 4·엣지 확인 3 = 11개(약 430줄) → 0 (파일 2,634 → 2,203줄)
목적지 factoryimportRepository 1함수·adminRepository 1함수 → 4함수·6함수, codec 3파일 신설
ImportPort 결과 unknown5 → 0 (importDto.ts)
완료 판정을 status 문자열로 하는 곳3(컨트롤러·러너·앱 셸) → 0 (codec 변환표 1곳)
경계 decoder 없는 응답5종(엣지 시작·Motra·신고 목록·처리·정지) → 0
일반 파사드의 관리자 전용 함수8 → 0 (관리자 브라우저 화면은 Barbelic-docs/admin, 앱 쪽 저장소는 adminComposition 이 앱 그래프 밖에서 조립)
앱 빌드(dist-vite, BARBELIC_TARGET=local) 청크의 관리자 RPC 이름barbelicRepository-*.js 1청크 → 0 (2026-09-09 실측; 관리자 셸 산출물은 v0.17.7 부터 이 저장소에 없음), service_role 문자열 0/0
A01 임시 경계Motra 의 옛 Repository 타입 참조 1 → 0, repositoryPorts import/admin 행 2 → 0(본인 내보내기 원시 표 읽기 2개만 좁은 port 로)
행동 검사신설 24건(인입 14·관리자 7·번들 경계 3)
로컬 게이트2026-09-09: npm run check 3,445 중 3,418 통과·0 실패·DB 조건 27 건너뜀 · check:lint·check:unused(기준선 14 불변)·check:ts-boundary-gate·check:test-manifest 통과 · 앱/관리자 빌드 성공. main 반영 뒤(2026-09-10): 3,477 중 3,446 통과·0 실패·30 건너뜀·1 중단(부하, 단독 통과)
화면 동작폴링 간격 1.5초·상한 180회·안내 문구·서버 재시도 정책(없음) 불변. 시각 변경 0
ci:local --full·release 큐PR 본문에 기록
release 통합 / staging / Productionrelease/v0.18.0 병합 4f6d193b(2026-09-10 02:47 KST) · staging 미반영 · Production 미반영

카탈로그 관리자 쓰기 5개(A03 소유)의 RPC 이름은 2026-09-09 시점 앱 청크에 남아 있었고, main 의 v0.17.7 이 파사드에서 제거했다(병합 뒤 재측정은 하지 않았다 — 큐의 full CI 가 빌드를 검증한다).

6. 이번 개선으로 향상된 것

후속 담당이 5천 줄 파일 없이 인입·관리자를 다룬다

I01 은 파서 결과를 WodupImportUploadInput.inspection·MotraImportInput.payload 로 넘기고, D09 는 WodupImportJobDto(서버 status 그대로 + 단계)를 durable 소비자의 관측 계약으로, A11 은 WodupImportJobQueryWodupImportJobDto 를 resource loader 로 그대로 쓴다. B01 의 관리자 권한은 adminRepository 가 42501 을 그대로 전파하는 것으로 소비한다.

화면이 서버 잡 상태를 추측하지 않는다

유저 A 가 파일을 올리고 서버가 "접수됨"만 답한 상태에서는 잡이 pending 이고, 워커가 잡으면 running, 끝나야 succeeded/failed 다. 폴링이 끊기거나 응답이 깨져도 어느 것도 완료가 되지 않는다.

관리자 코드가 일반 앱에 실리지 않는다

일반 앱 진입점의 모듈 그래프와 빌드 산출물에 관리자 저장소·codec·조립이 없다. 관리자 브라우저 화면은 Barbelic-docs/admin 이 소유하고, 앱 쪽 관리자 저장소는 domains/adminComposition.ts 가 앱 그래프 밖에서 조립한다(소비자: e2e CASE-018, 앞으로의 Barbelic-docs/admin).

구조적으로 남는 것

  • domainPortWiring: import/admin factory 의 런타임 import 허용 목록, A05 codec 3파일의 순수성(계약·타입만).
  • adminBundleBoundary: 앱 진입점 그래프에 관리자 모듈 0, adminComposition 그래프에는 존재(파사드·내보내기 도메인은 없음), 일반 파사드·옛 저장소에 관리자 바인딩·RPC 이름 0.
  • importRepositoryDestinations·adminRepositoryDestinations: 서버 상태별 단계, 불량 응답 거부, 42501 전파·재시도 없음, 재조회 identity.
  • 계약: ports/importDto.ts·adminDto.ts·adminPorts.ts, G03 §16 정정 문단.

남은 것

  • Barbelic-docs/admin 연결: 관리자 브라우저 화면은 자체 사본(admin/src/react/services/adminApi.ts 등)으로 서버를 부른다. 앱 저장소의 adminRepository·adminModerationCodec·port 계약을 그쪽이 소비하도록 연결하는 것은 후속(저장소 경계 문서 docs/process/repository-boundaries.md 의 "배포된 인증 API/RPC 계약으로 호출" 원칙 안에서 정한다). 카탈로그 관리자 쓰기 5개는 v0.17.7 이 앱 파사드에서 제거해 R01 항목이 해소됐다.
  • I01 인계: 파서(wodupJsonlUpload·motraNormalizedUpload·inbodyImport)와 identity 는 그대로다. I01 이 파서를 바꾸면 WodupImportUploadInput.inspection(schemaVersion·wodupUserId 필수)·MotraImportInput.payload(sessions·file_sha256) 모양만 지키면 된다. 부분 실패 목록은 WodupImportResult.errors/placeholders(배치 행 decoder)로 온다.
  • D09 인계: 잡 조회는 배치 행 1건 읽기이며 서버 status 9종 → 단계 4종 변환표는 wodupImportCodec.wodupImportJobPhase 하나다. 서버가 status 를 추가하면 codec 이 먼저 거부한다(모르는 상태는 어느 단계로도 읽지 않는다). 재시도·lease 회수는 서버 소유이며 앱은 새 write retry 를 추가하지 않았다.
  • A11 인계: loadWodupImportBatch(query) → WodupImportJobDto 를 owner 범위 resource 의 loader 로 쓰고 phase 로 구독 갱신을 끝낸다. 폴링(1.5초·180회)은 지금 컨트롤러·러너에 있으며 resource 로 옮기는 것은 A11 몫.
  • U03: 신고 행 AdminContentReportRecord 의 camelCase 전환은 화면을 옮길 때 adminDto.ts 에서 좁힌다.
  • R01/A16: 옛 파일이 내주는 helper 묶음 repositoryRuntimeuserDataExportRepositoryPort 는 옛 composition root 해체 때 정본을 따라 옮긴다. 이중 키 허용 1곳(엣지 배포 응답 옛/새 키)은 엣지 함수의 구 헤더 폐기 때 제거.
  • staging 승격(full CI 1회)·QA·Production 은 릴리스 담당 절차(v0.18.0)로 진행하며 이 문서 §5 의 상태 표를 그때 갱신한다.