인입·관리자 서버 호출을 자기 저장소·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, head903fbbd6, base99669e3e, 큐 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, head410c87eb) 130파일·2,328 assert 통과. release 큐 실행 기록은 PR 본문. - 버그리포트: 없음(수리 건 아님).
- 계약:
ImportPort결과 타입 고정(writes.ts), 관리자 port 신설(adminPorts.ts), G03 §16 정정 문단, A01 지도 §3-2 신설·§4 두 행 갱신.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | release/v0.18.0 생성(origin/main 2ecd3430)·워크트리·착수 댓글·진입점 대조표 | ✅ |
| Phase 2 | 인입 codec 2·importRepository 4함수·importDto·ImportPort 결과 고정·컨트롤러/러너/앱 셸 잡 DTO 연결·행동 검사 14 | ✅ 2f639bed |
| Phase 3 | 관리자 codec·adminRepository 6함수·조립 분리·관리자 함수를 일반 파사드에서 제거·본인 내보내기 분리·관리자 port·행동 검사 7·번들 경계 검사 3 | ✅ f0005382 (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. 관리자 조립을 옛 파일에 남김 | createAdminRepository 를 barbelicRepository 의 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_check | importRepository.uploadWodupJsonlOriginal → WodupImportJobDto(pending) |
| Wodup 시작 | 같은 배치 | 본인 / 관리자 | 엣지 wodup-start-import(→ enqueue_wodup_import_batch → 워커 kick) | 위와 같음 | 엣지가 is_lift_guild_admin 재검사, 남의 배치는 404 | startWodupImportBatch → WodupImportStartResult(accepted / already_running / already_terminal) |
| Wodup 조회 | 배치 행 1건 | 본인 / 관리자(대상 계정으로 좁힘) | wodup_import_batches select(1.5초 폴링 ≤180회는 호출자) | 위와 같음 | RLS | loadWodupImportBatch → WodupImportJobDto(phase·terminal·outcome) |
| Motra 기입 | motra · 원문 = 브라우저 정규화 페이로드(파일 해시 file_sha256) | 본인 / 관리자 → 대상 계정 | import_motra_workouts_v1 동기 1회(one-shot 게이트는 서버) | 관리자 Motra 탭 | 서버 is_lift_guild_admin·42501 | importMotraWorkouts → MotraImportResultDto(kind: "completed") |
| InBody | inbody · 원문 = 버킷 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 탭·커스텀 종목 탭 | 서버 42501 | adminRepository.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결과unknown5 → 0.importDomain은 인스턴스를 직접 받고repositoryPorts의 import 행 제거.- 컨트롤러(
wodupImportController)·프로필 러너(wodupImportRunner)·앱 셸(appController.profileWodupImport)이 잡 DTO 의phase/terminal로 완료를 판정.motraImportController는 camelCase DTO 를 읽는다. 관리자 feature 의 Motra 완료 판정은result.kind === "completed". - 테스트:
importRepositoryDestinations14건 신설, 옛 저장소 테스트의 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곳, 검증 뒤).- 테스트:
adminRepositoryDestinations7건,adminBundleBoundary3건 신설.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(legacyBundleIsolation30초 예산 초과 — 다른 세션 부하 중, 단독 재실행 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 build는BARBELIC_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줄) |
| 목적지 factory | importRepository 1함수·adminRepository 1함수 → 4함수·6함수, codec 3파일 신설 |
ImportPort 결과 unknown | 5 → 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 / Production | release/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 은 WodupImportJobQuery → WodupImportJobDto 를 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 묶음
repositoryRuntime과userDataExportRepositoryPort는 옛 composition root 해체 때 정본을 따라 옮긴다. 이중 키 허용 1곳(엣지 배포 응답 옛/새 키)은 엣지 함수의 구 헤더 폐기 때 제거. - staging 승격(full CI 1회)·QA·Production 은 릴리스 담당 절차(v0.18.0)로 진행하며 이 문서 §5 의 상태 표를 그때 갱신한다.