Files
sodalive-ios/docs/20261029_이벤트_상세_딥링크_연동/plan-task.md
T

31 KiB

이벤트 상세 딥링크 연동 실행 계획

문서 항목 내용
상태 클라이언트 구현·전체 로컬 검증·리뷰 승인 완료
작성일 2026-10-29
요구사항 기준 prd.md
API 기준 api-contract.md
현재 Phase Phase 3 로컬 검증 및 완료 Gate
현재 활성 Goal 없음

목표와 현재 상태

OneLink 및 기존 앱 인입점으로 이벤트 상세에 안전하게 진입한다. P1의 실제 SDK 공존 증거와 단축 URL 없이 진행하라는 사용자 후속 결정으로 구현 진입 조건을 충족했다. 과거 gate 차단 기록은 아래에 보존한다.

Phase 상태 완료 Task 활성/다음 Goal 차단 조건
1. SDK feasibility 완료 / 제한 기록 2/2 구현 진행 승인 실제 short 양성 검증은 비차단 후속 항목
2. 인입 구현 및 리뷰 수정 완료 3/3 없음 없음
3. 로컬 검증 완료 1/1 없음 실제 short 양성 검증은 비차단 후속 항목

범위와 제약

기존 AppStep.eventDetail(eventId:), 공통 pending queue, SDK 초기화/Notifly bridge/GCD 경로를 재사용한다. 신규 alias, push payload 가공, SDK 업그레이드, router 재작성, 새 화면/API, 실백엔드 및 실제 공급자/AASA/push 전달 검증은 제외한다. 현재 branch에서 수행하며 commit/worktree 생성은 승인되지 않았다.

Phase 결과: URL-only HTTPS short OneLink가 지원되는 실제 SDK 경로로 이벤트 값을 제공할 수 있는지 증거로 판정한다. 선행조건: 없음. offline probe는 sibling이 수행 중이며 결과가 아직 없다.

Task 1.1 P1-T1 SDK URL resolution 근거 확인

Goal: 실제 설치 SDK에서 short HTTPS OneLink의 URL-only 해석 경로와 callback/API 계약을 확인한다. Files: 소스 변경 없음. 확인 대상은 기존 AppsFlyer SDK 문서/설치 SDK metadata 및 sibling probe 결과다. 앱 코드·테스트·project config는 수정하지 않는다. TDD 예외 사유: 읽기 전용 외부/SDK feasibility 확인이다. 대체 검증: 근거 URL/API 이름, SDK 버전, 입력 형태, 관찰된 callback과 해석된 deep_link_value/deep_link_sub5를 결과에 기록한다. 실제 SDK와 독립된 mock callback이나 합성 activity 생성만으로는 통과하지 않는다.

  • SDK 전문 검증 결과 및 실제 설치 아티팩트/헤더 증거를 확인했다.
  • URL-only HTTPS 해석과 UDL 공존 콜백이 입증되지 않아 PASS를 부여하지 않았다.
  • P1-GATE를 BLOCKED로 판정하고 공급자 지원 확인 또는 별도 승인된 실 SDK 테스트가 필요한 상태로 중단했다. 임의 SDK API는 구현하지 않았다.

Phase 1 Gate P1-GATE

선행조건: P1-T1 완료 증거. 통과: 실제 지원 경로로 HTTPS shortlink를 처리하고 이벤트 키 값을 얻는 증거가 명확하다. 이때만 Phase 2를 시작한다. 실패/불명확: Phase 2와 3을 모두 차단한다. 안전한 offline 증거만으로 확인되지 않는 실제 SDK 동작은 공급자 확인 또는 별도 승인으로 해결한다.

  • 근거 및 BLOCKED 판정을 이 문서 Progress에 기록했다. 이 체크는 gate 통과가 아니라 판정 기록 완료다.

Phase 2: 조건부 TDD 인입 구현

선행조건: 실제 SDK 공존 증거와 PRD DEC-004의 사용자 후속 승인. 정상 short URL fixture가 없다는 이유로 이 Phase를 다시 차단하지 않는다.

Task 2.1 P2-T1 공통 URL parse, queue, 중복 및 취소

Goal: 허용 URI/OneLink만 기존 AppStep.eventDetail(eventId:)로 전달하며 잘못된 입력과 중복으로 route를 손상하지 않는다. Files, gate 통과 뒤 실제 필요를 확인: SodaLive/Sources/App/AppDeepLinkHandler.swift, SodaLive/Sources/App/SodaLiveApp.swift, SodaLive/Sources/App/AppState.swift(필요한 경우만), work/tests/AppDeepLinkGiftDetailCheck.swift, work/tests/의 신규 집중 Swift check. 기존 라우트 정의와 파일 구조를 먼저 확인한다. Interfaces: consumes AppStep.eventDetail(eventId:) 및 기존 공통 pending queue. Produces 기존 deep link ingress로 전달되는 양수 Int 목적지만.

  • RED: URI 2종, 허용 HTTPS host 및 ID 경계/overflow, invalid 입력이 current/pending을 보존하는 최소 fixture를 추가한다.
  • RED 확인: 저장소의 실제 Swift check 실행으로 요구 assertion 실패를 확인한다.
  • GREEN: 최소 parser/기존 queue 연결과 identical pending/current/delivery dedupe를 구현한다. 후속 새 탭은 억제하지 않는다.
  • GREEN 확인: 재탭·cold start·로그인 전 pending·live confirmation 취소 테스트 통과.
  • REFACTOR: 이번 변경에서 생긴 중복만 정리하고 집중 검사 및 기존 route 회귀를 기록한다.

Task 2.2 P2-T2 push, Notifly, notification-list ingress 소유권

Goal: 각 기존 입력이 계약대로 하나의 owner만 거쳐 공통 route queue에 도달한다. Files, gate 통과 뒤 확인: SodaLive/Sources/App/AppDelegate.swift, SodaLive/Sources/Notification/List/PushNotificationListViewModel.swift, 필요한 최소 V2 SDK bridge, 관련 work/tests/ Swift check. Interfaces: consumes Task 2.1의 기존 deep link ingress. Produces SDK 시작 전 push path 등록과 실제 userInfo 처리 연결, 중복 없는 Notifly/알림목록 전달.

  • RED: SDK 경계 fixture에서 HTTPS OneLink handlePushNotification(userInfo:), owner 단일성 및 목록 pending gate를 확인하는 실패 검사를 추가했다. pre-start path 등록은 AppDelegate 실제 빌드/리뷰에서 확인한다.
  • RED 확인: focused production-source Swift check에서 미연결 호출/중복 owner 실패를 확인한다.
  • GREEN: 실제 SDK API와 P1-GATE에서 증명된 입력 경로만 연결한다. custom URI는 기존 앱 경로, HTTPS OneLink는 SDK 경로를 따른다.
  • GREEN 확인: 일반 push, Notifly marker+URL, 알림 목록 URL이 한 번만 queue에 들어가는지 검사한다. in-app fake push는 만들지 않는다.
  • REFACTOR: production 코드만 최소 정리하고 focused 및 직접 영향 회귀 결과를 기록한다.

Phase 3: 로컬 검증 및 완료 Gate

선행조건: Phase 2 구현 완료.

Gate P3-GATE

Files: 앞서 명시한 변경 파일 및 work/tests/ 검사만.

  • 이벤트 전용 검사와 push ownership, callback pending/dedup/cancel 회귀를 실행한다.
  • 기존 14개와 신규 이벤트 딥링크 검사를 포함한 work/tests 15개를 모두 실행했다.
  • SodaLive 및 SodaLive-dev Debug iOS Simulator 빌드를 수행했다.
  • 단일 독립 리뷰와 지적 수정의 delta 승인을 같은 작업 디렉터리 reviews/에 기록했다.
  • P1의 제한된 실제 SDK 콜백 검증과 제품 코드의 로컬 대역 검사를 구분했다. 정상 short 실서버 성공·Notifly/AASA/실푸시·backend 검증은 미실행 후속 항목이다.

의존성 및 Progress

P1-T1 → P1-GATE(PASS 필수) → P2-T1 → P2-T2 → P3-GATE

P1-T1 및 P1-GATE 초기 기록: 2026-10-29

  • 상태: 대기. 지원 여부 판정 전이며 구현 완료가 아니다.
  • 관찰: performOnAppAttribution은 legacy callback으로 알려져 있으며 URL-only HTTPS OneLink resolution을 입증하지 않는다. synthetic NSUserActivity도 지원 근거가 아니다.
  • 진행: sibling이 안전한 offline probe를 수행 중이다. 결과는 아직 확인되지 않았다.
  • 다음 행동: 결과와 SDK 근거를 대조해 gate 판정. 실패/불명확 시 공급자 지원 확인 또는 별도 승인된 real SDK test 요청 후 정지한다.

변경 금지 및 완료 판정

  • 제품 구현은 P1 검증 결과와 사용자 후속 승인 범위를 따른다. 실제 short URL 성공 증거가 없음을 숨기지 않는다.
  • 지원되지 않은 SDK API나 query-only shortlink 해석을 만들어내지 않는다.
  • 실제 공급자/백엔드 전달 검증을 수행하거나 통과했다고 주장하지 않는다.
  • Phase 2/3 미완료 상태를 완료로 표시하지 않는다. 커밋은 별도 명시 요청 전까지 하지 않는다.

P1-T1 결과 및 P1-GATE 판정 - 2026-10-29

  • 실제 설치 artifact CFBundleVersion과 정적 바이너리 문자열은 6.17.5다. workspace lock은 6.17.5, project lock은 6.18.1이며 이번 작업에서 변경하지 않았다.
  • 설치 헤더의 performOnAppAttribution(with:)는 onAppOpenAttribution을 강제 호출하며 re-engagement/session/launch를 집계하지 않는 URL resolver로 문서화돼 있다. URL-only API가 없다는 뜻은 아니다. 기존 deepLinkDelegate 설정과 공존할 때의 callback 선택은 미입증이다.
  • sandbox-exec deny-network 프로필 아래 /usr/bin/true는 exit 0이었지만 실제 iOS SDK와 위임된 네트워크 작업의 격리를 증명하지 않는다. SDK 로드/실행, 실제 OneLink 해석, 시뮬레이터 생성은 하지 않았다.
  • 결과: SDK 검증 기록, BLOCKED. 승인된 게이트 정책에 따라 Phase 2/3 제품 구현 및 완료 검증은 미착수다.
  • baseline: bash work/tests/check-app-deep-link-gift-detail.sh PASS assertions=279; bash work/tests/check-event-detail.sh 전체 그룹 PASS. 기존 production handler/API/VM 검사이며 새 딥링크 기능 검증이 아니다.
  • 문서 점검: 초안의 프로덕션/개발 scheme 표기를 사용자 확정 계약인 voiceon/voiceon-test로 교정했다. 앱 소스는 변경하지 않았다.
  • 작업 중 project.pbxproj에 참조 블록 순서 이동이 관찰됐다. 이번 실행에서는 해당 파일을 쓰거나 Xcode 빌드를 실행하지 않았으므로 외부 변경으로 보존하고 되돌리지 않았다.
  • 정리: 근거를 reviews 문서에 보존한 뒤 이번 실행 소유의 두 임시 디렉터리 및 notepad를 제거했고 test ! -e를 통과했다. SDK 인스턴스·앱·시뮬레이터·잔존 테스트 프로세스는 생성하지 않았다. git diff --exit-code -- SodaLive/Sources work/tests와 git diff --check는 exit 0이다.

Task 1.2 P1-T2 제한적으로 승인된 실제 SDK 검증 - 2026-10-29

  • 사용자 승인: 이벤트 API 호출과 푸시 발송을 제외하고 AppsFlyer 테스트 OneLink의 실제 SDK 해석만 허용했다.
  • 범위: 현재 설치 SDK 6.17.5를 독립 임시 harness에서 실행하여 UDL delegate와 legacy delegate가 같이 있을 때 manual URL resolver의 실제 callback/이벤트 파라미터를 관찰한다. 제품 앱은 실행하지 않는다.
  • 금지: 앱 백엔드 요청, 이벤트 상세 API, 푸시 발송, 운영 앱 실행, AppsFlyer 설정 변경/단축 링크 생성, SDK 업그레이드, 자격증명·전체 payload 로그 노출.
  • TDD 예외: 제품 구현 전 SDK 외부 계약 검증이며 임시 실행 harness만 사용한다. 실제 SDK callback과 네트워크 결과를 기록하고 mock callback을 실증으로 취급하지 않는다.
  • long/short URL 입력의 실제 callback과 성공/실패를 확인했다. long은 event-detail/123 반환, 공식 short 예시는 실패하여 정상 short fixture 미확보 제한을 기록했다.
  • 실제 관찰에 따라 P1-GATE를 부분 입증/차단 유지로 재판정했다. 콜백 공존 문제는 확인됐지만 정상 short 응답을 추정하지 않았다.
  • 소유한 앱/시뮬레이터/프로세스/임시 자원을 정리하고 증거를 reviews에 보존했다.

P1-T2 실제 SDK 실행 결과 - 2026-10-29

  • 실제 SDK 검증 기록: SDK 6.17.5의 iOS Simulator slice를 링크한 독립 임시 앱으로 실행했다. parent가 최종 Swift 소스, build/run 스크립트, callback JSONL, SDK enum, 버전/서명/정리 영수증을 직접 읽었다.
  • 긴 URL: UDL+legacy delegate가 실제 등록된 상태에서 performOnAppAttribution(with:) 호출 후 didResolveDeepLink(found)로 event-detail/123 반환. UDL 없는 대조군은 onAppOpenAttribution으로 같은 값 반환. 무설정 및 합성 설정에서도 같은 callback 선택을 관찰했다.
  • 단축 URL: 공식 문서의 과거 예시만 확보됐다. 실제 SDK는 UDL 활성 시 didResolveDeepLink(failure), legacy-only 시 onAppOpenAttributionFailure를 반환했다. 오류 domain com.appsflyer.sdk.deeplink, code 0. 원인을 링크 상태/설정/네트워크 중 하나로 단정하지 않는다.
  • 긴 URL의 성공은 SDK 로컬 해석과 일치하며 서버의 template 검증이나 정상 short resolution 성공을 뜻하지 않는다. 따라서 이전의 callback 공존 불확실성은 해소됐지만 정상 short 양성 증거가 필요한 기존 gate는 유지한다.
  • 기존 저장소에는 유효한 이벤트용 단축 URL fixture가 없었다. 다음 재개 조건은 정상 이벤트용 단축 OneLink URL 제공이다. 새 hosted link를 만들거나 실제 개발키/SDK start로 범위를 확대하지 않았다.
  • 제품 소스/API/UI/프로젝트 설정은 이번 검증에서 변경하지 않았다. 이벤트 API·앱 서버·푸시 발송·운영 앱 실행은 없다. 기존 프로젝트 파일의 외부 재정렬은 그대로 보존한다.
  • 정리: 소유 앱 uninstall 및 simulator shutdown/delete 영수증을 확인하고 parent도 UUID 부재와 앱 프로세스 부재를 검증했다. 결과를 reviews에 보존한 뒤 소유 임시 소스·바이너리·캐시·원시 로그가 있던 두 run root와 notepad를 삭제했다. 두 root 부재 검사 PASS. 공유 SDK/Xcode 캐시 및 사용자 기기는 변경하지 않았다.

Phase 2 착수 결정 - 2026-10-29

  • 사용자 명시 지시로 short 실연결 양성 검증을 비차단 후속 항목으로 전환했다. 추가 fixture 확보 요청 및 SDK 가능성 재조사를 반복하지 않는다.
  • SDK 6.17.5에서 직접 관찰한 performOnAppAttribution(with:) → didResolveDeepLink를 앱 내 OneLink resolver로 사용한다. push의 실제 userInfo 경로와 Notifly 단일 owner는 기존 승인 계약을 따른다.
  • TDD는 실제 production handler/ingress를 컴파일하는 기존 work/tests 패턴을 사용한다. 네트워크/SDK 경계만 대역으로 대체하고 실제 앱 parser, 상태 전이, route 횟수를 검증한다. 기존 279개 deep-link 회귀는 약화하지 않는다.
  • 코드 변경은 현재 브랜치에서 수행하며 기존 project.pbxproj 재정렬을 외부 변경으로 보존한다. commit/push/worktree/SDK 업그레이드/이벤트 API 변경은 하지 않는다.

Phase 2 구현 경로 확정 - 2026-10-29

  • P2-T1/P2-T2 공유 인터페이스: AppDeepLinkHandler.handle(url:source:)와 단일 pendingDeepLinkAction을 그대로 사용한다. AppDeepLinkAction.eventDetail(eventId:), handleEventDeepLink(result:), handlePushDeepLink(userInfo:)를 기존 handler에 추가한다. 신규 production 파일/모듈/프로젝트 membership 변경은 없다.
  • Ruling: handler에서 AppsFlyerLib를 직접 사용한다. short/long 원본 URL을 performOnAppAttribution(with:)에 전달하고 UDL 결과만 이벤트 action으로 바꾼다. 테스트는 기존 Moya 패턴처럼 SDK 경계 대역 모듈을 컴파일하며 기존 gift/community 279 assertion을 유지한다.
  • Ruling: AppState.setAppStep(step:completion:)에 기본값 있는 완료 closure만 추가한다. 이벤트 한 건의 in-flight ID를 실제 비동기 route 적용 완료 또는 live 취소에서 해제한다. current ID 비교만 하거나 영구 last-ID/TTL/SDK 요청 상관관계를 만드는 대안은 사용하지 않는다.
  • Ruling: push 소유권 정책은 기존 handler의 실행 가능한 함수로 둔다. Notifly marker와 비어 있지 않은 String/URL(NSURL bridge 포함)이 있으면 SDK 소유로 종료하며 malformed nonempty String도 legacy로 흘리지 않는다. Firebase/Notifly hook은 AppDelegate에서 먼저 실행한다. 그 밖의 nonempty deep_link는 URI 공통 경로 또는 검증된 OneLink의 실제 userInfo SDK 경로로 처리하며 실패해도 legacy로 fallback하지 않는다.
  • 수정 대상 확정: App/AppDeepLinkHandler.swift, App/AppDelegate.swift, App/AppState.swift, work/tests/AppDeepLinkGiftDetailCheck.swift, work/tests/check-app-deep-link-gift-detail.sh, 신규 work/tests/EventDeepLinkCheck.swift, work/tests/check-event-deep-link.sh. SodaLiveApp.swift, SplashView.swift, 알림 목록 ViewModel, UI/API/SDK 설정값은 변경하지 않는다.
  • 검증: 실제 production handler와 AppState를 함께 컴파일해 async navigation, single-slot pending, live confirm/cancel을 실행한다. SDK 대역은 URL 전달/decoded UDL/push 경계만 담당하며 실제 SDK 네트워크 성공 증거로 취급하지 않는다. 양 scheme 빌드, 전체 회귀 및 독립 리뷰는 parent가 수행한다.
  • P2-T1 RED: production handler/AppState + 새 Swift harness로 bash work/tests/check-event-deep-link.sh 실행, exit 1 및 FAIL: event URI accepted: voiceon, 1 확인. 컴파일 오류가 아니라 미구현 URI 수락 assertion이 실패했다. 변경 전 gift/community baseline은 PASS AppDeepLinkGiftDetailCheck assertions=279다.
  • URI/HTTPS 최소 구현 후 추가 RED: 테스트를 먼저 확장하고 callback/push 경계를 false로 둔 상태에서 bash work/tests/check-event-deep-link.sh callback → exit 1 FAIL: decoded UDL event accepted, bash work/tests/check-event-deep-link.sh push → exit 1 FAIL: HTTPS push consumed. 같은 실제 소스의 uri onelink는 assertions=188 PASS, 기존 gift/community는 assertions=279 PASS다.
  • 상태 교체 회귀 RED: bash work/tests/check-event-deep-link.sh lifecycle → exit 1 FAIL: superseded live event releases in-flight guard for deliberate reopen. live 대기 이벤트를 기존 다른 외부 navigation이 대체하면 이전 cancel closure가 호출되지 않아 이벤트 guard가 남는 근본 원인을 확인했다.
  • Ruling: AppState.requestExternalNavigation에서 대기 요청을 교체할 때 이전 cancel closure를 호출한다. 기존 single-slot replacement를 명시적 취소로 마무리하는 한 줄 변경이며, 오래된 이벤트 guard를 추측해서 해제하거나 영구 기록을 만드는 대안은 배제한다. 기존 외부 탐색도 교체되면 자신의 취소 정리를 실행한다.

Phase 2 완료 및 집중 GREEN - 2026-10-29

  • bash work/tests/check-event-deep-link.sh → exit 0, 5개 그룹 및 PASS EventDeepLinkCheck assertions=324. 실제 production AppDeepLinkHandler/AppState를 실행해 두 URI/hostless 기존 관례, 양수 64-bit/Int.max 및 invalid fallback 방지, short/long SDK 전달, decoded UDL/실패 보존, splash/latest pending, async burst/current/back/reopen, live confirm/cancel/다른 외부 요청으로 교체를 검증했다.
  • push 그룹은 production handlePushDeepLink(userInfo:)를 실행해 실제 입력 dictionary 보존, URI/HTTPS SDK 분기, unknown nonempty no-fallback, empty legacy 반환, Notifly String/NSURL/malformed 소유권 및 목록의 공통 pending 진입을 확인했다. Notifly SDK URL opening/cold replay/click tracking 자체는 실행하지 않았다.
  • bash work/tests/check-app-deep-link-gift-detail.sh → exit 0, 기존 assertions=279 그대로 PASS. SDK 모듈 경계와 추가 AppState signature 대역만 보완했으며 기존 assertion은 수정·삭제하지 않았다.
  • bash work/tests/check-event-detail.sh → exit 0, 실제 API/repository/DTO/VM 전체 그룹 및 기존 7개 ID-only navigation call-site 검사 PASS. 앱 서버 요청 없이 기존 transport 대역을 사용했다.
  • GIT_MASTER=1 git diff --check → exit 0. AppDelegate의 UDL은 기존 sub1~4/legacy 종류/logUtm 처리를 유지한 뒤 공통 이벤트 수신 함수를 호출한다. GCD에 이벤트를 추가하지 않았다. Firebase/Notifly callback 이후 push 정책을 호출하고 pre-start 초기화에서 ["deep_link"] 경로를 등록했다.
  • 단일 SDK URL 요청/콜백 상관관계는 만들지 않았다. notFound/failure는 pending을 지우지 않으며 같은 URL도 새 입력마다 SDK로 전달할 수 있다. navigation만 queued/live/current 범위에서 중복 억제하고 completion/cancel 시 guard를 해제한다.
  • 양 target 실제 SDK 링크 빌드, 전체 work/tests 및 독립 리뷰는 parent의 P3-GATE이며 아직 완료 표시하지 않는다. 실제 정상 short 성공은 비차단 후속 검증이고, 이번 검사의 synthetic short URL 및 decoded UDL은 로컬 경계 대역 데이터다.
  • runner 독립성: env -u TMPDIR bash work/tests/check-event-deep-link.sh → exit 0/assertions=324, env -u TMPDIR bash work/tests/check-app-deep-link-gift-detail.sh → exit 0/assertions=279. 두 runner는 자체 mktemp/trap 정리를 사용하며 TMPDIR 설정 유무에 의존하지 않는다.
  • 문법 검증: swiftc -frontend -parse SodaLive/Sources/App/AppDeepLinkHandler.swift SodaLive/Sources/App/AppDelegate.swift SodaLive/Sources/App/AppState.swift work/tests/AppDeepLinkGiftDetailCheck.swift work/tests/EventDeepLinkCheck.swift 및 bash -n work/tests/check-event-deep-link.sh work/tests/check-app-deep-link-gift-detail.sh 모두 exit 0. 이 세션에는 lsp_diagnostics 도구가 없으며 AppDelegate 실제 SDK 링크/type-check는 parent 빌드로 남긴다.
  • 변경 파일은 production 3개, 테스트/runner 4개 및 이 계획 문서뿐이다. SodaLive.xcodeproj/project.pbxproj의 기존 외부 재정렬은 쓰지 않았고, UI/알림 목록/Splash/SDK lockfile은 변경하지 않았다. 네트워크 요청, push 발송, 실제 SDK 실행, commit, reviewer/subagent 실행은 없다.
  • P2-T1 추가 상태 경계 RED: live에서 다른 이벤트를 기다리다가 이미 current인 이벤트를 최신 intent로 탭하는 fixture를 추가했다. bash work/tests/check-event-deep-link.sh lifecycle → exit 1 FAIL: latest intent for already-current event replaces live pending without stacking current event. 기존 대기 요청은 교체하되 실제 navigation 직전에 current 목적지를 다시 확인해 중복 stack 없이 완료하도록 수정한다.

P3-GATE 상위 실행자 검증 1차 - 2026-10-29

  • production handler/AppDelegate/AppState 및 변경/신규 테스트·runner를 읽고 기존 MainView의 외부 이동 호출도 대조했다. 신규 테스트는 실제 AppState의 비동기 navigationPath와 live 확인/취소를 실행한다.
  • 전체 work/tests/check-*.sh 15개 중 첫 13개 PASS 후 300초 실행 제한으로 received-gift 검사 도중 중단됐다. bash work/tests/check-received-gift-detail.sh && bash work/tests/check-user-creator-chat-date-presentation.sh를 별도로 재실행해 둘 다 exit 0, 합계 15개 모두 PASS를 확인했다.
  • 이벤트 딥링크 assertions=325, 기존 gift/community assertions=279 PASS. 실제 OneLink 서버/Notifly 전달은 이 로컬 검사로 입증되지 않으며 경계 대역 사용 사실을 유지한다.
  • xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS Simulator' -disableAutomaticPackageResolution CODE_SIGNING_ALLOWED=NO build: exit 0. 동일 명령의 SodaLive-dev도 exit 0. 구현자 완료 후 최종 snapshot에서 두 스킴을 다시 빌드하여 모두 BUILD SUCCEEDED를 확인했다.
  • 단일 Oracle 리뷰 진행 중. 리뷰 전 결과이며 차단 지적이 있으면 신규 회귀 Task를 기록하고 관련 검사·빌드를 다시 수행한다. LSP 도구는 제공되지 않아 실제 양 타깃 컴파일과 Swift parse로 대체한다.

Task 2.3 P2-R1 비동기 이벤트 이동 경합 회귀 수정

  • Review: REV-P2-001, 최초 REQUEST_CHANGES.
  • Goal: 현재 이벤트를 포함한 연속 혼합-ID 요청이 마지막 목적지를 보존하고, 실제 route 변경 시점에 중복/오래된 요청을 걸러낸다.
  • Files: SodaLive/Sources/App/AppDeepLinkHandler.swift, SodaLive/Sources/App/AppState.swift, work/tests/EventDeepLinkCheck.swift, 필요 시 기존 mock signature만 보완.
  • RED: current 45에서 중간 settle 없는 44 → 45, 44 → 45 → 44를 추가하고 의도한 assertion 실패를 확인한다.
  • GREEN: 최신 요청 유효성과 현재 목적지 검사를 실제 AppState route mutation과 동일한 main queue 블록에서 수행한다. 시간 지연/TTL이나 전역 router 재작성으로 우회하지 않는다.
  • GREEN 확인: 혼합 ID, 동일 ID burst, live 확인/취소/대체, back/reopen 및 기존 279개 회귀를 재실행한다. 현재 이벤트 뒤에 live가 표시된 경우에도 기존 종료 확인 정책을 지키는지 함께 확인한다.
  • REFACTOR: 관련 테스트/Swift parse/양 스킴 빌드 및 단일 reviewer의 delta 판정을 기록했다. 앱 API/UI/SDK 계약은 변경하지 않았다.
  • 최종 GREEN: 위 경계 수정 후 env -u TMPDIR bash work/tests/check-event-deep-link.sh → exit 0, 5개 그룹 및 assertions=325 PASS. env -u TMPDIR bash work/tests/check-app-deep-link-gift-detail.sh → exit 0/assertions=279, bash work/tests/check-event-detail.sh → exit 0/전 그룹 PASS. 변경 Swift 5개 parse와 GIT_MASTER=1 git diff --check를 다시 실행해 exit 0을 확인했다. parent 통합 gate 항목은 계속 미완료다.

P2-R1 RED 및 수정 인터페이스 확정 - 2026-10-29

  • 실제 production handler/AppState를 컴파일한 bash work/tests/check-event-deep-link.sh mixed-current → exit 1, FAIL: current 45 then queued 44->45 preserves 45 and original stack.
  • bash work/tests/check-event-deep-link.sh mixed-repeat → exit 1, FAIL: current 45 then queued 44->45->44 appends latest 44 exactly once. 두 fixture는 current 45/live OFF에서 입력 사이 settle 없이 실행하며 기존 325개 검사를 변경·삭제하지 않았다.
  • P2-R1 하위 발견: current event 45가 live player 뒤에 있으면 기존 early current 검사 때문에 승인된 live 종료 확인을 건너뛴다. bash work/tests/check-event-deep-link.sh current-live → exit 1, FAIL: current event behind live still requests leave confirmation. 이 Task 안에서 같은 current 판정 위치 수정으로 해결하며 UI/policy를 새로 만들지 않는다.
  • 수정 전 결정: AppState.setAppStep(step:ifCurrentStep:completion:)에 기본값 nil인 optional predicate를 추가한다. 기존 replaceCurrentAppStep 패턴과 같은 위치, 즉 기존 main async 블록 안에서 mutation 직전에 predicate를 실행한다. 완료 closure는 mutation 여부와 무관하게 한 번 호출해 오래된/no-op 이벤트 guard도 해제한다. 기존 step-only 및 completion caller 의미는 유지한다.
  • Handler의 current 목적지 early return은 제거하고 기존 live 요청 확인을 먼저 따른다. 최신 event ID와 current 목적지 비교는 위 predicate에서 원자적으로 처리한다. 추가 dispatch/delay, queue, TTL/history 또는 SDK 요청 계약은 추가하지 않는다. 기존 gift/community mock은 동일 predicate signature만 보완한다.

P2-R1 집중 GREEN 및 parent delta 대기 - 2026-10-29

  • bash work/tests/check-event-deep-link.sh mixed-current mixed-repeat current-live → exit 0, 각 mixed 순서와 current-live confirm/cancel/reopen 그룹 PASS, assertions=21. 실제 production AppState mutation 직전에 최신 intent/current 비교를 수행해 44 → 45는 원래 45/stack을 보존하고 44 → 45 → 44는 44 한 건만 추가한다.
  • bash work/tests/check-event-deep-link.sh → exit 0/assertions=345. 기존 325개를 그대로 보존한 상태에서 신규 20개 assertion을 추가했다. obsolete/no-op completion 이후 back/reopen과 live 취소 뒤 같은 ID 재요청도 통과한다.
  • bash work/tests/check-app-deep-link-gift-detail.sh → exit 0, 기존 assertions=279 PASS. 기존 mock의 setAppStep optional predicate signature/skip completion만 보완했으며 assertion을 변경·삭제하지 않았다.
  • bash work/tests/check-event-detail.sh → exit 0, 실제 API/repository/DTO/VM 및 7개 기존 navigation call-site 검사 전체 PASS. 로컬 대역만 사용했으며 네트워크 요청은 없다.
  • swiftc -frontend -parse SodaLive/Sources/App/AppDeepLinkHandler.swift SodaLive/Sources/App/AppState.swift work/tests/AppDeepLinkGiftDetailCheck.swift work/tests/EventDeepLinkCheck.swift → exit 0. LSP 도구는 제공되지 않는다. cache/TMPDIR는 지정된 IMPL_TMP를 사용했고 runner의 자체 mktemp/trap 정리를 유지했다.
  • 변경 경계: handler 이벤트 case, AppState.setAppStep optional guard, 관련 기존 Swift check/mock 및 이 계획/리뷰 기록만 수정했다. AppDelegate/URL 계약/SDK calls/Notifly/마케팅/API/UI/runner는 이번 delta에서 변경하지 않았다. 빌드/전체 검사/reviewer/commit은 실행하지 않았으며, P2-R1의 마지막 체크는 parent 재빌드 및 동일 Oracle delta 판정 후 완료한다.

P2-R1 및 P3-GATE 최종 검증 - 2026-10-29

  • parent가 수정된 handler/AppState와 회귀 fixture를 직접 읽었다. latest/current predicate가 실제 mutation과 동일 main queue 블록에 있고, skip도 completion을 실행하며 live 확인을 먼저 거치는 것을 확인했다.
  • for script in work/tests/check-*.sh; do bash "$script"; done를 600초 제한으로 재실행: TOTAL_SCRIPTS=15, FAILED_SCRIPTS=0, exit 0. 이번 최종 실행은 timeout 없이 전체를 완료했다. 새 이벤트 assertion 345 및 기존 deep-link assertion 279 PASS.
  • 수정 snapshot에서 xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS Simulator' -disableAutomaticPackageResolution CODE_SIGNING_ALLOWED=NO build: BUILD SUCCEEDED, exit 0. 동일 명령의 SodaLive-dev도 BUILD SUCCEEDED, exit 0.
  • 동일 Oracle 리뷰 작업의 수정분 판정: REV-P2-001 ADDRESSED / APPROVE, 새 차단 사항 없음. 실제 source와 테스트 실행 경계를 독립 확인했으며 테스트·빌드 결과는 parent 실행 증거를 사용했다.
  • 실제 SDK 서버 호출/푸시 발송/앱 서버/운영 앱 실행은 구현·최종 검증 단계에서 하지 않았다. UI는 변경하지 않았으며 실제 iOS 전체 앱 조작을 검증했다고 주장하지 않는다. LSP 도구는 없어 Swift parse와 실제 양 타깃 컴파일로 대체했다.
  • 현재 브랜치에서 작업했고 commit/push/worktree/SDK 업그레이드는 수행하지 않았다. 기존 외부 project.pbxproj 재정렬을 보존했다.
  • 정리: 이번 구현의 xcodebuild/Swift 검사 프로세스가 남지 않았음을 확인하고 소유 run root 2개 및 notepad를 제거했다. 두 root 부재 검사 PASS. 원시 빌드 로그·컴파일 임시 산출물은 함께 제거했으며 실행 명령/결과/리뷰 판정은 이 문서에 보존했다. 공유 Xcode/SDK 캐시, 사용자 기기 및 외부 project.pbxproj 변경(기존 38행 이동)은 보존했다.