A16 — 범용 타입과 거대 변환기를 실제 기능 경계로 이전 (2026-09-11)
- 기간: 2026-09-10~11, Codex 작업 1개. 오너 지시: “나한테 물어보지 말고 끝까지 완주”.
- 랜딩: 앱 PR1561, 검증 HEAD
9047a633b94e2ae576f0a83aacf244da374fbf14, 목적 release baseaf5ae625. 2026-09-11 04:34 KST release/v0.18.0 병합517c538ef5ffd5014fa6d62f5a4ac3f2b112a06f확인. 문서 PR66. SQL migration·Edge 변경과 staging/Production 배포는 없다. - 설계서: A16 #1534의 내부 Phase 계획과 예상 효과, 경계별 기준선·보존 근거.
- 정본: TypeScript 경계 게이트, feature resource 계약, 각 feature/domain의 실제 codec·projection·repository.
- 도구: 앱의
check:ts-boundary-gate,check:dual-key,check:unused,npm run check,ci:precheck-local,merge:request. 기준선/전후 비교 스크립트는 작업 외부work/에 보관하며 앱 실행 코드가 아니다. - 게이트: explicit
any를 TypeScript AST로 식별하는 실제 CLI 회귀검사 추가. 기존 저장·복구·사본·화면·repository 검사를 새 소유 모듈로 연결했다. 브라우저/viewport·native·Production 검증과 full CI는 이번 통과 증거에 포함하지 않는다. - 버그리포트: BUG-116 테스트 출력 귀속. precheck를 직접 막은 테스트 도구만 최소 수리했으며 제품 SQL 결함으로 확대하지 않았다.
- 계약: A16 경계 기록, 경계 게이트. 공개 RPC와 저장·복구 의미는 유지한다.
Phase 현황
| Phase | 내용 | 상태·검증 커밋 |
|---|---|---|
| Phase 1 | 기준선·선행 release 포함·보존 decoder 조사 | 완료, 앱 576dae9b, 문서 9c887c1 |
| Phase 2 | DTO·편집 입력·화면 타입과 실제 소비자 이전 | 완료, 9b6e967e |
| Phase 3 | 거대 facade 제거·내부 이중 해석 소진·게이트 강화 | 완료, c9bed324 |
| Phase 4 | 동일 입력 결과 비교·원문/ID/사본 보존·문서 인계 | 완료, 앱 c9bed324 |
1. 배경
A15의 feature binding과 S09/S11의 복구·사본 계약이 release에 들어온 뒤, 기존 구현 곳곳에 남은 넓은 타입과 변환 책임을 실제 소유자에게 옮겼다. 착수 시 A15/S09/S11 및 앞 스텝 U07/D14의 merge SHA가 release/v0.18.0에 포함됐음을 확인했다. 선행 대기로 구현을 지연한 상태가 아니었다.
2. 문제 제기
활성 앱 173개 파일에 explicit any 1,400개와 지정 범용 alias 1,736개가 남아 있었다. 입력 중 숫자 문자열, 서버 DTO, 확정 통계, 화면 표시 값이 같은 타입으로 취급되어 각 소비자가 넓은 객체를 다시 해석했다. dual-key 검사는 33개 파일 378곳을 기록했다.
거대 Repository/Mappers/ViewMappers와 services/index.ts가 실제 feature/domain 소유를 가렸다. PR 상세의 store·방문 취소·fragment/archive 조회도 상위 remote controller에 남아 있었다. 외부 계약과 구형 데이터 해석까지 일괄 삭제하면 원문·단위·복구 checksum 의미가 바뀔 수 있었다.
3. 해결 방안
D1(2026-09-10): 승인된 A16 범위에서 구현·precheck·자기 Merge Check·실제 release 병합·문서 인계까지 수행한다. D2: 적용된 migration과 지원 중인 구형 decoder, #1478 검사 제외 정책은 보존한다.
| 방안 | 판단 |
|---|---|
| 기존 DTO/codec/feature 모델을 연결하고 실제 소비자까지 이전 | 채택. 의미와 검증을 해당 입력 경계에 둔다. |
any를 unknown/강제 cast/광범위 generic으로만 치환 | 기각. 실제 접근 안전성·소유 책임을 해결하지 못한다. |
| 거대 모듈을 동일한 통합 barrel이나 기본값 묶음으로 대체 | 기각. 이전 책임과 반복 해석이 남는다. |
| 구형 데이터와 원본 provenance decoder까지 삭제 | 기각. 지원 중인 입력과 저장 원문 의미를 바꾼다. |
4. 적용한 내용
Phase 1 — 기준선과 호환 경계
AST 기준선, mapper의 내부 호출을 포함한 실제 소비자, recovery 원문·사본 manifest·버전별 persistence 해석을 조사했다. 외부 참조가 없다는 이유만으로 내부 helper를 삭제하지 않았다. 상세 목적·소유자·소비자·제거 조건은 경계 기록에 남겼다.
Phase 2 — 실제 입력·조회·표시 계약
저장 명령/영수증, 운동 draft, 달력·PR·리포트·프로필·그룹·알림, 모바일/데스크톱 화면을 실제 계약으로 연결했다. 입력 중 빈 문자열과 0/null, canonical ID와 local key, 날짜·단위·readonly 배열을 구별한다. 원시 서버 응답은 codec에서 해석하며 내부 소비자는 canonical 필드를 읽는다.
Phase 3 — 이전 구현 퇴역
Repository의 factory 조합·RPC runtime·operation diagnostics·transport·export 소유를 분리하고 소비자를 직접 연결했다. barbelicRepository.ts, barbelicMappers.ts, barbelicViewMappers.ts, repositoryPorts.ts, services/index.ts, mobileExperienceViewMappers.ts를 제거했다. 현역 호출이 없는 클라이언트 setPurposePolicy.ts도 제거했으며 실제 서버 분류 정책은 유지했다.
PR 상세 store/visit/fragment/archive 수명주기는 features/pr/queries/prExerciseQueryBinding.ts가 맡는다. session presentation·draft·home/profile/feed/volume 등은 기존 feature와 domain projection이 맡는다. 상위 remote controller 전체가 없어졌다고 집계하지 않는다.
check-typescript-boundary-gate.mjs는 AnyKeyword AST를 사용한다. 문자열/주석/데이터 키 any는 타입 any가 아니며, 이를 이유로 파일 전체를 예외 처리하지 않는다. 활성 앱 예외는 0개이고 기존 Wodup 서버 인입 2개는 범위 밖 예외로 유지한다.
Phase 4 — 행동 비교와 인계
동일 입력 전후 결과 및 최종 검증 결과는 아래 표에 기록한다. 기존 복구 검사는 raw 객체 참조·요청 ID·plan hash·manifest/복원 의미를 독립 기대값으로 검증한다. tests/helpers/legacyWorkoutNormalization.ts는 기존 A09 전후 payload/fingerprint 비교의 구 구현 oracle만 보존한 테스트 전용 파일이며 앱에서 import하지 않는다.
작업 중 드러난 것
화면·저장·조회 연결과 테스트 이전 범위를 과소평가해 Phase 2가 길어졌다. 전체 검사에서 singular test/, auth 테스트의 이동 전 import와 source anchor, CASE patchFiles, TypeScript migration sentinel, 생성 DB 장부의 소비자 경로가 드러났다. 실제 owner로 수정하고 기존 값·정책·성능 예산을 유지했다. 재고와 부분 검사를 전체 통과로 보고하지 않았으며 실패 후 통과를 별도로 기록했다.
최신 release의 U04 PR1553(af5ae625)를 1c2c5db9에서 실제 합쳤다. 겹친 목록 화면은 A16의 유한 타입과 U04 ViewportList·행 식별자·스크롤/포커스 동작을 결합했고, 새 feed WeakMap cache는 이전 facade를 되살리지 않고 현역 feature projection으로 옮겼다. 피드 identity/교체 포함 31/31, 검색 목록 5/5, 타입·대상 lint·dual/any gate를 통과한 뒤 precheck를 다시 실행했다.
DB 장부 변경은 supabase/contracts/db-objects.json의 appConsumers 경로 재생성뿐이다. schema·SQL·원본 데이터 변경은 없다. 다만 현행 precheck의 supabase/** 조건에 해당하므로 필요한 격리 DB 검사를 생략하지 않는다.
Precheck에서 확인한 테스트 출력 귀속 오류
1c2c5db9의 첫 precheck는 static/build·단위 3,611통과·pgTAP 143파일/2,825assert와 나머지 DB 검사를 통과했지만, D14의 historical writer에서 기대한 오류가 다음 실행의 실패로 붙는 비교 검사가 실패했다. 실제 stack의 INSERT는 historical 본문 line128이고 현재 D14 본문의 같은 문장은 line182였다. 이를 현재 저장 SQL 결함으로 단정하지 않았다.
독립 pipe의 도착 순서를 제어한 5개 회귀검사는 기존 pgSessions.mjs가 stdout marker만 받고 결과를 성공으로 확정하는 문제를 재현했다(0/5통과). 9047a633에서 각 요청의 stdout/stderr 완료표식을 모두 받아 결과를 확정하도록 바꾼 뒤 5/5통과했다. 오류·취소·분할표식·연속요청·process exit를 검증하며 타이머/재시도/SQL 변경이나 단언 완화를 사용하지 않았다. 실제 DB를 포함한 최종 precheck는 아래에 별도로 기록한다.
5. 적용 결과
기준선은 576dae9b9dc77478908c846a406a477a0bac6459, 구현 검증 기준은 c9bed324다.
| 항목 | 전 → 후 / 실제 증거 |
|---|---|
활성 앱 explicit any | 1,400 → 0, TypeScript AST |
| 지정 범용 alias | 1,736 → 0, Phase 1 동일 목록·동일 범위 |
| 해당 항목이 있는 앱 파일 | 173 → 0 |
| dual-key 정규식 항목 | 378/33파일 → 14/8파일. 14개는 외부·구형 인입 또는 다른 데이터 결합이며 개별 보존 근거를 명시 |
| explicit-any 허용 파일 | 63 → 2. 활성 앱 0, 기존 범위 밖 서버 인입 2 |
| 새 suppression 예외 | 0 |
Phase 1 npm run check | 3,671 전체 / 3,579 통과 / 0 실패 / 92 제외 |
Phase 2 npm run check | 3,701 전체 / 3,609 통과 / 0 실패 / 92 제외, 98,286ms |
Phase 3 npm run check | 3,701 전체 / 3,609 통과 / 0 실패 / 92 제외, 94,518ms |
| Phase 4 동일 fixture 비교 | 5/5 흐름 동일, 최종 차이 0. 1,436 구조 노드(null 332·own undefined 9 포함), PR 정적 HTML 22,334문자 동일. 내부 canonical 키 변경 112곳은 별도 보존 |
Phase 4 npm run check | 3,701 전체 / 3,609 통과 / 0 실패 / 92 제외, 96,250ms (c9bed324) |
| 최신 release 통합·precheck | 9047a633/base af5ae625, 512,943ms(8분33초) 전체 PASS. static/build·unit3,616pass/0fail/92skip·pgTAP143files/2,825assert·모든 DB subgroup 및 cleanup PASS. 요약 |
| 자기 PR Merge Check·release 병합 | 앱 PR1561, 정상 Merge Check34521093139 성공. release 517c538ef5ffd5014fa6d62f5a4ac3f2b112a06f와 검증 HEAD ancestry 확인 |
| 문서 자체 검사·main 병합 | 로컬 check 343routes·build 28.39s·artifact31files 및 Vercel Preview 성공. 문서 PR66 원격 CI34518803926은 계정 결제/사용 한도 annotation으로 step0 미실행. 최종 문서 head·필수 verify·main merge SHA는 PR66의 검사/병합 기록에서 확인 |
| 화면 성능·native·Production | 이번 작업에서 미측정·미실행. 개선 수치와 배포 성공을 주장하지 않음 |
Phase별 검사 증거, 타입 기준선, 동일 fixture 전후 비교·의도된 내부 키 차이를 함께 보관한다. PR 정적 렌더 비교를 브라우저 상호작용 검사로 집계하지 않는다.
6. 이번 개선으로 향상된 것
새 기능이나 오류를 수정할 때 원시 응답, 편집 중 입력, 확정 통계, 화면 값을 어디에서 해석하는지 실제 import로 확인할 수 있다. 거대 중개 파일을 우회해 호출자와 소유자를 함께 고쳤으므로 같은 필드를 소비자마다 다시 보정하는 경로가 줄었다.
숫자 감소만으로 호환성을 판단하지 않는다. 원문·ID·단위·null/0·정렬·복구 계약의 행동 검증과 남긴 decoder의 제거 조건을 함께 인계한다. R01/G05는 A16 경계 기록의 퇴역 경로·14개 보존 항목·시험 증거를 사용한다.
남은 것
release→staging 최종 full CI, 승격·Production 배포·이슈 종결은 캠페인 Phase 5의 별도 범위다. #1478 제외 검사를 이번 자동 통과로 집계하지 않는다. 현재 지원 중인 구형 decoder는 그 지원 조건이 사라지기 전까지 유지한다.