Skip to content

v0.18.0 Phase 2 종료 보완 — receipt 복구와 resource 갱신 연결

기준: 2026-09-08, main 84c9e226a931bdab12d4e2a7d43b967f1490eb70, Production ba10ad96faa0d3f05a713e98bef9414b1696b8c9. 사용자 요청으로 Phase 2 진행 점검에서 찾은 두 연결 지점을 구현한다. 총괄은 62개 로드맵. 범위는 S04/S05/S07/A09 기존 완료 조건이며 새 계획 ID를 만들지 않는다.

통합 기준은 작업 중 병합된 내비게이션 #1395 및 문서 #1399/#1400을 포함한 main ed3d6db73eebd8ade80042da848ef61e6279414f다. 구현 커밋 80f2ac5f3, 통합 커밋 37a0306d6codex/phase2-closeout에 있다. 이 기준 뒤의 새 main 변경까지 검증했다는 뜻은 아니다.

상태와 배포 경계

Phase 2 원 구현 PR 18/18 병합, Phase 1 포함 29/62다. A09 최종 staging run 34208080762의 DB·함수·frontend·smoke 성공을 확인했다. Phase 2 17개는 이미 v0.17.1/v0.17.2로 Production에 포함됐다(실제 v0.17.2 배포). 원래 v0.18.0에 모으려던 계획과 실제 반영 버전의 차이를 보존하며, milestone을 바꿔 과거 계획을 지우지 않는다.

출시 대상은 사용자 결정에 따라 v0.17.3이며 main 통합을 진행한다. 이 문서의 v0.18.0 명칭은 원래 62개 계획의 Phase 2 범위를 뜻한다. 보완 구현은 별도 브랜치에서 로컬 검증했으며 실제 병합 SHA·staging 성공은 통합 PR에서 확인한다. 원 구현의 성공 기록은 이번 보완 커밋의 검증을 대신하지 않는다. Production 릴리스는 별도다.

1. 저장 여부가 불명확한 요청의 서버 확인

옛 탭이 생성 요청을 서버에 반영한 뒤 기기 행을 지우면 새 탭의 후속 편집·삭제는 서버 ID를 알지 못한다. 기존에는 findCreateReceipt 형태만 있고 제품 주입이 없어 격리 후 자동 복구가 끝나지 않았다. 미러 v1/v2 근거가 어긋난 행도 원문은 보존했지만 receipt 확인 경로가 없었다.

  • get_workout_recovery_receipt_v1(p_client_mutation_id)는 로그인 owner의 immutable receipt 장부 한 건만 읽는다. caller가 owner를 지정하지 못하며, 타인/부재는 null, 인증 없음은 거부한다. 현재 session/tree/통계를 읽거나 생성·변경하지 않는다. 기존 service-role 전용 함수는 공개하지 않는다. DB 계약.
  • repository codec → domain의 현재 사용자 확인·timeout → feature query의 owner epoch 확인 → dispatcher의 identity·revision·generation 대조로 연결한다. 출처만으로 생성 ID를 추측하지 않고 정확한 선행 mutation ID와 sourceRef를 대조한다.
  • 이미 LG_PREDECESSOR_RECEIPT_UNKNOWN으로 격리된 후속도 영수증을 찾으면 기존 S03 조립기와 원자 교체를 거쳐 이어서 처리한다. 네트워크/인증 오류는 영수증 부재와 구분해 원문을 유지하고 다음 연결 시 재시도한다.
  • 미확인 미러는 일치하는 영수증이 있을 때만 원자 ACK·uploaded tombstone으로 정리한다. 부재는 과거 로컬 취소/교체일 수도 있으므로 자동 재전송하지 않는다. 불일치·지원하지 못하는 옛 원문은 보존한다. 서버 ID가 없는 미확인 미러의 intent를 선행 영수증만으로 실행하지 않는다.
  • 옛 계획은 큐 ID와 wire ID가 다를 수 있다. 현재 canonical 값으로 보정하지 않고 당시 원문의 DTO/codec·동일 deterministic identity 함수로만 조회 키를 복원한다. 필수 값이 없으면 추측하지 않는다.
  • 조회 도중 다른 탭이 행을 고쳤으면 원문 관찰 비교에서 ACK를 거부한다. ACK가 커밋된 뒤에만 채택 중복 장부를 갱신하므로 실패한 정리가 다음 성공의 화면 갱신을 삼키지 않는다.
  • 과거 영수증은 현재 상세가 아니다. 복구 결과는 recovered로 전달하고 원문으로 최신 확정 카드/삭제를 합성하지 않는다. canonical resource 재조회·명시 missing으로 화면을 수렴시킨다.

2. 저장 직후 조회도 같은 owner resource로

일반 상세 열기는 resource를 쓰지만 영수증 뒤 갱신은 API를 직접 호출하던 경로를 제거했다. pending controller가 remote controller의 완료/계획 상세 loader를 주입받아 같은 cache·dedup·취소·owner epoch를 사용한다. receipt revision 하한은 loader 안과 소비자 반환 경계에서 검사한다. fresh cache를 반환하거나 낮은 하한으로 시작한 요청에 합류하더라도 오래된 응답은 거부한다. 해당 응답이 여전히 현재 cache일 때만 무효화해 이후 도착한 더 새로운 값을 보호한다.

새 pending 입력·더 높은 revision·owner 이탈을 보호하고, 조회 대체/취소로 나온 null을 삭제로 취급하지 않는다. 서버가 명시적으로 missing을 확인했을 때만 제거한다. 계획 카드 변환은 feature selector에 한 번 두고 상세와 요약 카드가 같은 canonical revision으로 수렴한다. 셸의 preservePlans:true는 다른 낙관 입력을 보존한다.

삭제된 운동과 만료된 인증 세션 구분

최신 통합본의 CASE-045에서 다른 탭이 운동을 삭제한 뒤 오래된 수정 receipt를 받은 탭이 상세를 조회하면, 서버가 P0002 / Session not found를 반환했다. resource가 이 값을 정규화하지 않아 remote controller의 인증 오류 분류가 로그인 만료로 오인했고, owner를 해제하면서 진행 중인 Home 조회를 취소했다. 이후 인증을 다시 채택해 화면이 회복되더라도 브라우저의 엄격한 오류 검증에는 net::ERR_ABORTED가 남았다.

loadSessionDetailResource에서 해당 RPC의 P0002session_detail_missing으로 정규화하고 원 오류를 cause로 보존한다. 실제 인증 오류 session_not_found / 401은 그대로 전달한다. 문구 전체를 무시하거나 인증 검사를 완화하지 않는다. 이 구분을 실제 resource 호출과 인증 분류를 함께 실행하는 회귀 검사로 고정했다. 원래 CASE-045는 수정하지 않았으며 진단용 계측과 재진입 대기 시도는 제거했다.

검증

검증실제 결과 / 기준
전체 DB 재생·스냅샷 대조180개 migration 재생 성공, schema.sql --check 일치
pgTAP / 동시 저장120파일·2,085 assert / 동시 저장 세대·영수증 13건 통과. 새 owner receipt 계약 26개 포함
CRUD / 빈 계정 / 유산소12/12 · 8/8 · 6/6 통과
정책·정적·unused·앱/관리자 빌드·산출물통합 코드의 ci:local verify 단계에서 모두 통과
전체 단위 검사통합 후 npm run test: 3,327 pass, 0 fail, DB 연결이 필요한 27 skip. skip은 통과 수에 포함하지 않는다. 이 중 동시 저장 13건은 위 DB 단계에서 별도 실행했다
저장·복구 실제 브라우저통합 후 npm run test:persistence-browser: 48/48, skip 0. 새 controller/cache 8건·실제 IDB/mirror 복구 2건 포함
화면 여정 / viewport통합 전·후 각각 57/57 · 14/14 및 evidence 통과, flaky·skip 0. 통합 후 앱 코드는 37a0306d6이며 이후 변경은 문서·장부·단위 검사뿐이다
영역 장부check-coverage-inventory.mjs --strict 통과, 미분류 0. 통합된 내비게이션 변경의 누락 6파일도 기존 shell 영역에 등록
문서npm --prefix docs run build:site 통과

실행 명령은 npm run ci:local -- --full --sandbox <전용 sandbox>와 통합 후 npm run ci:local -- --full --only verify,persistence,browser,viewport --base ed3d6db73eebd8ade80042da848ef61e6279414f --sandbox <동일 sandbox>다. 첫 실행은 잘못 추가한 미등재 테스트 신고 때문에 static 단계에서 실패했고, 신고를 제거한 뒤 통과했다. 통합 실행의 단위 검사에서는 옛 변수명 creates.map에 의존한 정규식 1건이 실패해, 일반·복구 생성의 날짜 포함/다른 owner 제외/역사 카드 합성 금지를 확인하는 행동 단언으로 교체했다. 전체 단위 검사와 manifest 검사를 다시 통과했으며 실패 기록을 성공으로 덮어 표기하지 않는다.

domainPortWiring.test.mjs는 새 순수 codec import 한 파일만 허용하며 현재 manifest 미등재라 신고 대상이 아니다. manifest 등재 파일인 calendarReadModelMapper.test.mjs의 위 행동 검사 변경은 기존 pending-changes.json 항목에 이유를 추가했다. 기존 canonical ID·binding 단언은 유지한다.

ed3d6db73까지의 통합은 SQL을 바꾸지 않았고 당시 로컬 preflight 내용 해시는 0d1d03e99460dbc3b219583caf6b907f93f57be3f091ae55c0f3e06b9dfa454f였다. 이후 #1401이 main 365f16d456de10a4a84a068260456da903e67145에 병합되어 세트 스코어 변경을 추가 통합한다. receipt migration은 이미 main에서 사용한 번호를 피하도록 20260913070100_workout_recovery_receipt_v1.sql로 옮겼다. 따라서 이전 해시를 새 통합본의 DB 검증으로 재사용하지 않으며 새 replay·생성물·관련 검증 결과를 아래와 통합 PR에 기록한다.

추가 통합 검증: 181 migration replay·snapshot 일치, pgTAP 121파일/2,111 assert, 실제 동시 저장 13건, CRUD 12/12·빈 계정 8/8·유산소 6/6, viewport 14/14가 통과했다. 새 preflight 서버 내용 해시는 3ca230b10a563b08ecbe7b04a80b6daed9c3feb5a723cb5a650109f056c107b1이다. 전체 browser는 56/57 clean·CASE-045 1 flaky여서 첫 통합 실행과 evidence는 실패로 남긴다. 계측으로 위 인증 오분류를 확인해 수정한 뒤 변경하지 않은 CASE-045를 retries=0·repeat-each=3으로 3/3 통과, 전체 npm run check 3,338 pass·0 fail·DB 환경 27 skip, 저장 복구 실제 browser 48/48를 다시 확인했다. 이 선택 재검사를 전체 57개 재실행으로 표시하지 않는다. 최종 원격 CI는 모든 필수 browser·viewport·evidence를 실행한다.

2026-09-08 후속 사용자 요청으로 이 보완은 main 대상 PR·자동 랜딩·staging 확인을 진행하며 v0.17.3에 포함한다. Phase 3의 21개 이슈는 별도 release/v0.18.0 브랜치로 커밋을 통합한다. 이번 PR에 함께 들어가는 Phase 3 변경은 게시 번호·실행 순서·브랜치 지시를 기록한 총괄 문서뿐이며 Phase 3 구현 코드는 포함하지 않는다.

같은 날 사용자가 D12 #1328의 추가 운영 성능 측정을 생략하고 닫도록 지시해 이슈를 종료했다. 측정 성공이나 운영 효과 입증으로 기록하지 않으며 R04/R05의 전체 검증 범위는 유지한다. A09 #1342는 원 구현의 staging 성공과 release/v0.17.3 포함을 확인해 [v0.17.3 스테이징]·v0.17.3 milestone으로 갱신했다. 이 보완의 검증·병합 상태와 구분한다.

공유 sentinel package.json은 새 실제 controller 브라우저 검사를 기존 persistence 검사에 등재하는 범위만 변경한다. dependency graph·lockfile·앱 런타임 설정 변경은 없다. 담당은 Phase 2 종료 기능 통합이며 새 검사가 CI에서 누락되지 않게 하는 목적이다.

정상 후속 인계

  • D12: index/FK Production 적용 확인과 대표 트래픽 성능 검증은 다르다. 관측 구간에 save 호출이 증가하지 않아 운영 지연 개선은 미확정이다.
  • B01 → R02/R05: 실제 provider 로그인·연결·해제 검증.
  • B02/N01 → R05: iOS release build, Android 서명/설치, 실제 WebView IDB·quota·복구 검증. Chromium 통과로 대신하지 않는다.
  • S09/S10: 영수증이 없거나 원문 근거가 불충분한 격리행의 사용자 복구 선택·이력/사본 조회. 이번 자동 경로가 원문을 보존해 인계한다.
  • U02/U03 및 후속 APP/DATA: 모바일·데스크톱 editor 상태 전면 이식, 전체 feature resource·통계 worker/projection 구조는 원래 후속 범위다.

Phase 2 원 구현 대조표

작업 / 이슈원 구현 PR병합 SHA실제 반영
S02 #1325#13492801239a현재 Production 포함
S08 #1326#135006db380c현재 Production 포함
D02 #1327#13527db92fb5현재 Production 포함
D12 #1328#1358d1e77113현재 Production 포함
A02 #1329#1354af2a828b현재 Production 포함
A03 #1330#1351530bb4cf현재 Production 포함
B01 #1331#1346ba0f4035현재 Production 포함
B02 #1332#13567b44295e현재 Production 포함
S03 #1333#13631bba53eb현재 Production 포함
S04 #1334#1369f8334135현재 Production 포함
A07 #1335#13650775e6b2현재 Production 포함
S05 #1336#137473d6f84b현재 Production 포함
S07 #1337#137737066827현재 Production 포함
A08 #1338#13724fb2d26f현재 Production 포함
A12 #1339#13781514fc52현재 Production 포함
A13 #1340#1375d5e12e9d현재 Production 포함
S06 #1341#13851ebfda64현재 Production 포함
A09 #1342#1394ba3b77bfStaging, Production 미포함