Skip to content

A16 타입 경계 재고와 이식 근거

대상: 앱 #1534, release/v0.18.0. Phase 1·2는 당시 스냅샷이며, 현재 구현 상태는 아래 Phase 3 기록을 따른다. 당시 로컬 구현·검사와 아래 최종 release 반영을 구분한다. Production 배포 증거는 아니다.

Phase 1 기준선

2026-09-10 앱 576dae9b9dc77478908c846a406a477a0bac6459, clean clone에서 조사했다. S09/S11/A15/U07/D14 최종 앱 merge 다섯 개의 ancestry를 확인했다. A15 문서 PR57과 D14 문서 PR59 인계를 사용한다. S09/S11 계약 문서는 아직 열린 docs PR31/28의 실제 파일을 읽었으며 main 게시 완료로 집계하지 않는다.

TypeScript AST의 AnyKeyword만 세면 src/react 명시 any는 1,400개다. 범용 별칭 참조는 1,736개, 두 집합의 파일은 173개다. 이 수치는 아직 호출 여부/보존 자격을 나누기 전 전체 재고이며 문자열의 any 논리식 키는 제외한다. 별칭만 사용하는 파일은 156개다. dual-key gate는 33파일/378곳을 기록한다. 항목 제거는 실제 소비자와 동작 증거를 함께 남긴다.

소유 영역확인한 기준선소비자·처리 원칙
services/barbelicMappers.ts3,129줄, any 401, dual 227홈/PR/volume/피드/로그/도메인 조립의 실제 호출을 확인하고 DTO와 기능별 투영으로 옮김
services/barbelicViewMappers.ts1,891줄, any 233, dual 51날짜/스크롤/편집 진입/프로필/홈 표시가 섞임. 현재 editor와 canonical 모델의 소비자로 좁힘
services/barbelicRepository.ts1,278줄, any 90공용 전송/부팅과 잔여 도메인 책임 구분. 공개 RPC와 원본 데이터를 변경하지 않음
types/controllers.ts, mobileUi.ts, desktopUi.ts범용 레코드·함수·props 기본값모델 종류별 명시 타입, 실제 콜백 인자, React DOM/SVG/CSS 계약으로 이전
A15 UI bridge 13파일ComponentType<any> 등의 기존 허용실제 컴포넌트 props와 feature 계약을 연결. 더 넓은 별칭으로 재포장하지 않음
완료/계획 쓰기·상세 codecA02에서 위치만 이전한 anywire DTO/검증 입력과 편집 입력·확정 사본을 구분; local key/UUID·null/0·원문 보존

내부 함수를 외부에서 직접 부르지 않는다는 이유만으로 삭제하지 않는다. 두 mapper의 외부 src 참조 없는 함수 153개에는 내부 조립 함수가 포함되며 dead-code 수치가 아니다.

Phase 2 이식 완료

2026-09-11 03:08 KST 측정은 명시 any 33개, 범용 별칭 14개, 두 집합 5파일이며 dual-key는 21파일/62곳이다. 남은 any는 소비자 없는 공용 별칭·구형 편집기 helper·PR 카드 localStorage helper에 한정된다. 활성 화면·도메인·저장 명령의 범용 별칭 연결과 전체 검사를 마치고 앱 9b6e967e6b876bf4dc90183001c65c3715f0bc2f에 커밋했다. 남은 미사용 helper·별칭과 구 mapper의 퇴역은 Phase 3에서 진행한다.

이식한 경계실제 계약·소비자보존 증거
서버 세션/계획 읽기 → 표시·편집 입력sessionReadModel, planReadModel, sessionPresentation, 달력·상세·홈·편집 화면같은 ID·날짜·null·0·미완료 입력과 source 참조를 유지하는 기존 mapper/editor 검사
완료 저장·영수증·기기 초안완료 쓰기 codec, workoutDraftInput, draft store/cache, 모바일 flow와 desktop 저장 경로완료 쓰기/영수증 229건, 초안 저장·복귀·owner·quarantine 및 controller 131건의 선택 검사 통과. 서로 겹치는 검사 수는 합산하지 않음
PR·리포트·친구 조회prDetailPresentation, prPresentation, prCardPresentation, reportPresentation, feature resource와 모바일 화면조회 fragment의 순서·cursor·세대, 실측 기록·히스토그램·출석·즐겨찾기·친구 owner 격리 검사
Repository·인증·진단SDK의 실제 요청/응답 계약, provider JSON 검증 경계, 유한 callback05b726c2에서 npm run check: 3,588 통과/0 실패/92 제외. check:unused 기존 2건 유지
계약과 구현의 의존 방향types/gymData, profileReadModel, catalogReadModel, compactPrReadModel, prDetailPresentation의 명시 모델계약 층이 service/feature 구현을 import하지 않게 하고 실제 projection 반환값을 모델에 대조. 신규 broad cast/suppression 없이 타입 검사

Phase 2 최종 npm run check는 3,701건 중 3,609 통과·0 실패·92 제외, 98,286ms다. 정적·타입·lint·manifest 검사를 포함한다. 앞 실행의 9개 실패는 이동 전 소스 앵커와 화면 계약의 의존 방향을 수정해 해결했다. 이 결과는 로컬 Phase 검사이며 release 병합이나 Production 증거가 아니다.

Phase 3 최종 상태

2026-09-11 앱 c9bed324에서 활성 소비자의 타입 연결과 미사용 facade 퇴역을 마쳤다. Phase 1과 같은 src/react 재고 기준으로 AST 명시 any1,400 → 0개, 조사 대상 범용 별칭 참조는 1,736 → 0개, 두 집합의 파일은 173 → 0개다. dual-key 정규식은 33파일/378곳 → 8파일/14곳이다. 이 수치는 모든 unknown·단언·외부 호환 경계를 제거했다는 뜻이 아니다.

명시 any 허용 목록은 63 → 2파일이며 활성 앱 예외는 0파일이다. 남은 두 파일은 서버의 기존 와드업 worker/normalizer 입력 경계다. 파일 수를 any 두 개로 해석하지 않는다. TypeScript boundary gate는 문자열 검색 대신 TypeScript AST의 AnyKeyword를 검사하며 suppression 예외는 두 서버 파일에도 없다.

Phase 3 최종 npm run check3,701건 중 3,609 통과·0 실패·92 제외, 94,518ms다. 정적·타입·lint·manifest와 단위 검사를 포함하며 #1478에서 자동 제외한 관리자·와드업 검사를 통과로 집계하지 않았다. Phase 2와 통과 건수가 같다는 사실만으로 각 CASE가 동일하다고 보지 않는다. 이 결과는 로컬 Phase 검사이며 브라우저/native·실서비스 인증·Production 데이터 또는 배포를 검증한 결과가 아니다.

최종 책임과 import 소유자

아래 경로는 앱 src/react/ 기준이다. 실제 조립 코드는 기존 factory에 전송·시계·캐시 함수를 주입한다. 기존 거대 객체의 모든 메서드를 같은 모양으로 재수출하지 않는다.

책임현재 소유자·실제 소비자유지한 경계
도메인 조회·쓰기와 조립services/domains/repositories/*Repository.ts, repositoryComposition.ts; 각 *Domain.ts가 필요한 workoutRepository, planRepository, statsRepository, profileRepository 등의 인스턴스를 직접 import기존 factory/port 주입과 인스턴스 수명. 화면은 repository를 직접 import하지 않음
전송·부팅·본인 사본 fallbackservices/repositoryTransport.ts, screenRpcRuntime.ts; repository 조립에서 주입기존 RPC 오류·취소·문서 수명·논리 구독자·부팅 캐시·owner별 사본 fallback. rpcTransport.ts의 전송 구현 재사용
진단·저장 이름services/operationDiagnostics.ts, storageObjectName.ts; 일반·import·admin 조립operation ID, 진단 버퍼·저장 수명과 파일명 규칙. 내부 진단은 operationId 한 표기
lazy import와 관리자 조립services/domains/importComposition.tsimportDomain.ts; adminComposition.tsrepositories/adminRepository.tsimport는 해당 lazy 도메인에서 조립. 관리자 조립은 일반 앱 진입 그래프 밖에서 공용 전송·진단만 주입. #1478 자동 제외 유지
본인 원시 데이터 내보내기services/domains/repositories/userDataExportRepository.tsloadDebugSnapshotRowsadminDomain.ts이름에 admin이 있는 도메인의 본인 내보내기와 운영 관리자 조립을 구분. 원시 select 필드·순서·chunk·쿼리 의미 유지
wire → 명시 읽기·쓰기 모델services/domains/codecs/sessionDetailCodec, planDetailCodec, homeShellCodec, feedCardCodec, prDetailCodec, completedWorkoutWriteCodec 등; statsDomain.ts 등이 함수 직접 import서버 snake DTO를 소유 codec에서 읽고 내부 canonical 모델로 전달. 완료 쓰기·영수증·원본 provenance 책임을 합치지 않음
PR·volume 도메인 투영services/domains/projections/prOverviewProjection, prDetailProjection, prScreenProjection, prCardProjection, volumeOverviewProjection조회 fragment·메타데이터와 UI 표시 모델을 각 소비자에 연결
플랫폼 표시·편집 진입features/home/desktopHomeProjection.ts, calendar/desktopCalendarProjection.ts, volume/desktopVolumeProjection.ts, social/desktopFeedProjection.ts, log-table/desktopLogTableProjection.ts, profile/profileProjection.ts, workout/plan editor·query, services/localDateTime.ts실제 feature/공용 계약을 직접 import. 시간·정렬·0·null·입력 원문과 DOM/SVG callback 타입 유지

퇴역 파일은 services/barbelicRepository.ts, services/domains/repositoryPorts.ts, services/barbelicMappers.ts, services/barbelicViewMappers.ts, services/index.ts, services/mobileExperienceViewMappers.ts, setPurposePolicy.ts다. 이전 파일을 다시 만드는 호환 barrel은 없다. 제품의 barbelicApi.tsdomains/servicePorts.ts는 실제 API 조립 책임으로 남아 있으므로 모든 facade를 삭제했다고 표현하지 않는다. remoteDataController.ts2,627 → 2,291줄이며 조회 상태·resource 연결을 유지한다.

구형 운동 정규화 함수는 제품 소비자가 없어 퇴역했지만, 비교 기대값을 만드는 tests/helpers/legacyWorkoutNormalization.ts는 테스트 전용 oracle로 남겼다. 소비자는 a09EditorSaveParity, completedSessionRecoveryScenarios, mappers, recordingProfileRoundTrip의 네 테스트이며 src/react import는 없다. 기존 정규화 규칙의 회귀 비교용으로, 독립적인 서버 정답이나 제품 fallback으로 간주하지 않는다. 네 검사의 입력·완료 판정·복구 비교를 같은 강도로 대체한 뒤에만 이 helper를 제거한다.

남은 정규식 14곳의 보존 자격

아래 목록은 dual-key-baseline.json과 실제 표현식을 대조했다. gate는 객체의 동일성을 판정하지 않는 정규식이므로 14곳 모두가 같은 객체의 camel/snake 이중 해석은 아니다. 호출식인 S.dateKey()도 잡히며, 한 행에 두 매치가 있을 수 있다. field()·대괄호 조회·배열 후보 같은 모든 호환 표현을 전수 집계한 수치도 아니다. 아래 축약 파일명은 위 소유자 경로 및 표의 명시 경로를 따른다.

번호·소유 파일매치·실제 공급원/소비자보존 자격·제거 조건
1 · features/catalog/catalogBinding.tscreated.name_eninput.nameEn; 생성 응답과 입력 초안 → 방금 생성한 종목 선택값서로 다른 객체. 응답 영어명이 비어 있으면 입력을 유지한다. 생성 응답이 해당 표시값을 항상 공급하는 계약과 빈 문자열 행동을 검증한 뒤 입력 fallback 제거
2 · services/bodyweightTraining.tsuserLoadOverrideUnitrawPayloadraw_payloadcanonical 세트와 저장된 raw 세트의 단위 정정 이력을 읽는다. 지원 중인 raw 공급원 모두에서 동일 정정 정보를 선행 decode하고 재시작/복구 동작을 검증한 뒤 제거
3 · services/bodyweightTraining.tsuserLoadOverrideUnituser_load_override_unit명시 단위 정정이 _lift_guild.user_load_override.unit보다 우선한다. 위 공급원의 정정 단위를 canonical 필드로 손실 없이 확정한 뒤 제거; kg 정정 시 lb 통계 반올림을 중단하는 의미 유지
4 · services/bodyweightTraining.tsisPoundLoadOriginrawPayloadraw_payload저장 provenance의 normalized/raw 단위를 확인하는 별도 호출 지점. lb 계보와 사용자 kg 정정의 우선순위를 선행 decoder가 보존한 뒤 제거
5 · services/domains/codecs/feedCardCodec.tsmain.name_encatalogExercise.nameEn; RPC 카드와 카탈로그 → 피드 카드서로 다른 공급원. 서버 영어명 우선·부재 시 카탈로그 보완을 유지한다. 지원 카드가 표시 영어명을 확정 공급하거나 보완 정책이 종료된 뒤 제거
6 · services/domains/codecs/homeShellCodec.tshomeDashboard.as_ofS.dateKey(); 서버 기준일과 현재 날짜 함수 → 셸이중 키가 아닌 정규식 매치. dashboard가 없는 부팅/사본 경로도 기준일을 공급하도록 계약이 바뀌고 검증될 때만 현재 날짜 fallback 제거
7 · services/domains/projections/prOverviewProjection.tsexercise.track_prcatalog.trackPr; RPC 요약과 카탈로그 → 압축 PR 항목서로 다른 공급원. 명시 false를 보존하는 ?? 우선순위다. 모든 지원 요약이 해당 메타데이터를 공급한 뒤 제거
8 · services/domains/projections/prOverviewProjection.tsexercise.sort_ordercatalog.sortOrder; RPC 요약과 카탈로그 → PR 정렬서로 다른 공급원. 0을 보존하고 마지막 기본값 1000을 유지한다. 모든 지원 요약이 정렬값을 공급하고 결과 순서가 같음을 검증한 뒤 제거
9 · services/domains/projections/prScreenProjection.tsserverBoard.year_pr_countlocalBoard.yearPrCount서버 보드와 현재 PR 목록으로 만든 보드의 결합. 서버가 없을 때 표시를 유지하고 서버 0을 우선한다. lazy 개요 미도착을 포함한 모든 호출에서 서버값이 보장된 뒤 제거
10 · services/domains/projections/prScreenProjection.tsserverBoard.total_pr_countlocalBoard.tickerTotal서로 다른 의미 이름을 연결한 표시 fallback. 위 서버 공급 보장과 ticker 값 검증 뒤 제거
11 · services/domains/repositories/adminRepository.tsbody.deployment_versionbody.deploymentVersion외부 Edge 배포 probe 응답 호환. 뒤의 배포 버전 header fallback도 유지한다. 지원 배포 응답 계약이 하나로 확정되고 구 응답 지원이 끝난 뒤 제거. #1478 제외 검사를 통과 증거로 집계하지 않음
12 · services/wodupJsonlUpload.tsworkout.movementDataworkout.movement_data외부 JSONL 원문의 movement 목록 → 사전 종목/세트 집계. 지원 exporter/raw 형식에서 해당 표기를 제거하거나 선행 decoder로 옮겨 집계가 같음을 검증한 뒤 제거
13 · services/wodupJsonlUpload.tsrow.session_exercisesrow.sessionExercises동일 JSONL 행의 지원 표기 호환. 지원 형식의 종료 또는 동일 값을 보존하는 선행 decode 후 제거
14 · services/wodupJsonlUpload.tsrow.sessionExercisesoriginal.session_exercises같은 행이 아닌 바깥 행과 원본 사본의 fallback. 원본에만 목록이 있는 지원 입력을 종료하거나 decode로 보존한 뒤 제거. 13·14는 한 표현식의 두 매치이며 #1478 자동 제외 유지

bodyweightTraining.ts의 3곳은 제한된 저장 provenance 읽기 decoder다. 유한한 PoundLoadOriginInput 필드와 객체 여부를 확인한 raw/normalized/사용자 정정 하위 객체에서 단위만 읽으며 원본 JSON을 변경하지 않는다. sessionDetailCodec.ts·planDetailCodec.ts의 저장 행과 낙관적 세션/영수증 투영이 소비한다. 현재 UI의 contracts/workout/bodyweight.ts는 canonical 필드만 읽는다. 이 경계는 전체 raw JSON의 스키마 검증을 끝냈다는 뜻이 아니며, 버전별 persistence decoder를 대체하지도 않는다.

정규식 밖의 필요한 wire 해석도 그대로 남는다. 예를 들어 calendarReadModelMapper.tstop_setsscreenRpcAdapters.ts가 행/참조를 검증한 실제 snake 응답이며 set_intensity_percent를 읽는다. 공급되지 않는 camel 후보와 intensity_percent 선언만 제거했고 raw row 참조·필드·0·null을 보존했다. 반대로 mobileDayViewMappers.ts의 세션 입력은 canonical SessionPresentationInput이므로 stimulusClass 한 필드만 읽는다.

보존 경계와 제거 조건

경계목적·담당·소비자보존/제거 조건
RPC/JSON ingress의 unknown도메인 codec; repository가 받은 외부 값 검증검증 뒤 명시 DTO로 좁힌다. 기능 내부의 범용 모델로 확산하지 않는다
S09 recovery plan.raw/planRawrecovery codec/outbox; plan→apply/receipt서버 계획 원문과 planHash 보존. 필드 보정/이름 변환을 추가하지 않음
S11 manifest.txt 재구성scripts/data-copy; 구형 사본의 검증·격리 복원원본 전체 sha256/bytes를 대조하며 증거 수준을 valid_reconstructed로 구분. 구형 사본 지원 기간이 끝나기 전 제거하지 않음
버전 있는 기기 사본/대기열 decoderpersistence; 기존 설치·재시작·복원지원 버전 및 제거 조건이 충족되기 전 현재 모델의 정리 편의로 삭제하지 않음
알림 선택 payload·그룹 v2 저장 JSONgroup codec/알림 projection, desktopFavoriteGroups기존 응답의 선택 필드·추가 메타데이터를 보존하고 소비하는 값만 객체 검사·기존 String/Number 규칙으로 확정. 공개 응답/저장 버전 지원이 끝나거나 검증이 상위 codec으로 이전될 때만 제거
관리자·와드업 자동검사 제외#1478 정책타입/빌드는 유지. 제외된 검사를 이번 자동 통과 증거로 집계하거나 자동 대상에 복귀시키지 않음

보존해야 하는 의미

  • 입력 중 원문과 저장 숫자, canonical UUID와 local key, DTO와 화면 표현을 합치지 않는다.
  • null/빈 문자열/0과 날짜·단위 변환의 현재 행동을 유지한다.
  • D14 이후에도 같은 내용의 새 논리 저장은 revision·영수증을 만들고 완료 저장의 통계 세대는 증가한다. 물리 자식 UPDATE 감소를 저장 생략으로 해석하지 않는다.
  • 적용 migration·원본 등급 컬럼·복구 checksum을 타입 정리 때문에 변경하지 않는다.
  • 로컬 타입/fixture 결과와 Production/native/실서비스 인증부터 저장까지의 검증은 구분한다.

Phase 4와 최종 release 인계

동일 fixture의 실제 producer→최종 소비5흐름에서 최종 차이0을 확인했다. 1,436구조노드(null332·own undefined9 포함), PR 정적 HTML22,334문자가 동일하며 내부 canonical 키변경112곳은 원시 차이로 별도 보존했다. Phase4 check3,701전체/3,609통과/0실패/92제외 뒤 최신 U04 release를 실제 통합했다.

최종 9047a633는 precheck 8분33초 전체 통과(단위3,616pass/0fail/92skip, pgTAP143파일/2,825assert 및 전체 DB subgroup/cleanup) 후 PR1561정상 Merge Check를 거쳐 release/v0.18.0 517c538ef5ffd5014fa6d62f5a4ac3f2b112a06f에 병합됐다. pgSessions 출력 귀속 수리는 BUG-116에 기록한다.

R01/G05는 이 문서의 소유자·퇴역 경로·14개 보존 경계와 작업 기록의 전후/검증 증거를 사용한다. 코드 release 반영, 문서 main 게시, full CI·staging/Production 배포는 별도 상태다.