diff --git a/raw/project-notes/ca-skeleton-frontend-operational-contract.md b/raw/project-notes/ca-skeleton-frontend-operational-contract.md index 5ec37f8..0753deb 100644 --- a/raw/project-notes/ca-skeleton-frontend-operational-contract.md +++ b/raw/project-notes/ca-skeleton-frontend-operational-contract.md @@ -521,7 +521,17 @@ Out of scope: | `adapters/storage` | storage port implementation, serialization, quota mapping | application port, Web Storage | token lifecycle, domain policy | `planned` | | `adapters/telemetry` | telemetry port implementation, queue, redaction, sink | application port, browser transport | UX decision, navigation | `planned` | | `adapters/query-cache` | application-owned `QueryCachePort` implementation, TanStack Query key/invalidation bridge | application port, TanStack Query | use-case policy, page-local query key | `planned` | -| `bootstrap` | config load, adapter construction, dependency injection, React mount | all runtime modules | business rule, page-specific orchestration | `planned` | +| `adapters/capability` | capability flag 해석, 브라우저 feature detection, 비활성 사유 | application port, runtime config, browser globals | 활성화 여부의 제품 판단 | `planned` | +| `adapters/file` | 파일 선택·저장 dialog, object URL 생성/해제 | application port, File System Access / input element | 파일 내용 해석, 도메인 검증 | `planned` | +| `adapters/blob-store` | IndexedDB·OPFS·Cache Storage 백엔드, quota 매핑, eviction | application port, browser storage API | 저장 대상의 의미, 도메인 정책 | `planned` | +| `adapters/cache-persistence` | 캐시 직렬화, version partition, 복원 거부 | application port, 자체 영속 백엔드 | 캐시 정책 결정(`QueryCachePort` 소유) | `planned` | +| `adapters/cross-tab` | BroadcastChannel·`storage` event 전송, 메시지 봉투 | application port, browser globals | 무효화 대상 결정 | `planned` | +| `adapters/transfer` | part 분할·병렬·재시도·무결성·진행 보고, credential-less 전송 | application port, fetch, Streams | presign 발급, 업로드 대상 도메인 규칙 | `planned` | +| `adapters/protocol` | GraphQL·gRPC-Web·Connect-Web codec 과 status 정규화 | application output port, fetch | operation 정의, use-case policy | `planned` | +| `adapters/realtime` | 연결 수명주기, 재연결 backoff, resume cursor, 프레임 디코드, 구독 해제 | application port, EventSource·WebSocket·fetch | 이벤트의 도메인 의미, 상태 병합 정책 | `planned` | +| `adapters/worker` | worker 생성·통신·timeout·terminate | application port, Worker API | 도메인 계산 규칙 | `planned` | +| `adapters/service-worker` | SW 등록·갱신 상태, background sync 큐 등록 | application port, ServiceWorker API | precache 정책 결정(`FE-D034` 소유), mutation 의미 | `planned` | +| `bootstrap` | config load, capability 해석, adapter construction, dependency injection, React mount | all runtime modules | business rule, page-specific orchestration | `planned` | ### 4.3 Dependency matrix @@ -545,6 +555,10 @@ Normative dependency summary: - adapter는 application port를 구현하지만 application은 adapter 이름을 알면 안 된다. - presentation은 application facade를 호출하며 raw backend envelope를 직접 다루면 안 된다. - bootstrap만 concrete adapter를 조립할 수 있다. +- `adapters/transfer`는 `UploadTransferPort` 입력으로 **바이너리 descriptor**를 받는다. `adapters/file`·`adapters/blob-store`의 concrete 모듈을 import하지 않으며, descriptor 조립은 `bootstrap` 또는 application orchestration이 한다 (`DELEG-FE-008`). +- `adapters/realtime/push`는 `ServiceWorkerHostPort` **인터페이스**를 통해 SW 등록 상태를 읽는다. `adapters/service-worker`의 concrete 모듈을 import하지 않는다 (`DELEG-FE-007`). +- `src/workers/*.worker.js`와 `public/sw.js`는 **별도 실행 컨텍스트**이므로 `presentation`·`application`·`domain`을 import하지 않는다. 공유가 필요하면 `domain`의 순수 모듈만 참조하고 그 사실을 `FE-REG-CAPABILITY` 행에 기록한다. +- worker 엔트리는 `adapters/worker`가 소유한다. `bootstrap`이 직접 `new Worker()`를 부르지 않는다. ### 4.4 Port ownership matrix @@ -558,9 +572,25 @@ Normative dependency summary: | `TelemetryPort` | `application` | `adapters/telemetry` | application + boundary | sanitized event → best-effort ack | `TelemetryDropped` internal only | | `ClockPort` | `application` | browser/system clock adapter | retry/release logic | now / monotonic duration | no user-facing error | | `ReleaseInfoPort` | `application` | runtime config/release adapter | boot + chunk recovery | release manifest → compatible release info | `RELEASE_MANIFEST_FAILURE`, `DEPLOY_MISMATCH` | +| `CapabilityPort` | `application` | `adapters/capability` | bootstrap + capability 소비 use case | capability ID → 활성 여부 + 사유 | `CAPABILITY_DISABLED`, `CAPABILITY_UNSUPPORTED` | +| `FileDialogPort` | `application` | `adapters/file` | 파일 입출력 use case | 선택 제약(accept·multiple·max) → 파일 descriptor 목록 / 저장 요청 → 저장 결과 | `FILE_PICKER_DISMISSED`, `FILE_REJECTED` | +| `BlobStorePort` | `application` | `adapters/blob-store` | 로컬 바이너리 보관 use case | 등록 key + 바이너리 descriptor → 저장/조회/삭제 결과 | `BLOB_STORE_UNAVAILABLE`, `BLOB_STORE_QUOTA_EXCEEDED` | +| `CachePersistencePort` | `application` | `adapters/cache-persistence` | `QueryCachePort` 구현 보조 | 캐시 스냅샷 + version tuple → 영속/복원 결과 | `CACHE_PERSISTENCE_FAILURE` | +| `CrossTabSyncPort` | `application` | `adapters/cross-tab` | 캐시 무효화 orchestration | 무효화 key 메시지 → 발행/수신 구독 | `CROSS_TAB_CHANNEL_UNAVAILABLE` | +| `UploadTransferPort` | `application` | `adapters/transfer/upload` | 업로드 use case | 전송 계획(source descriptor + presign 결과) → 진행 스트림 + 완료 결과 | `PRESIGN_EXPIRED`, `UPLOAD_PART_FAILED`, `TRANSFER_INTEGRITY_MISMATCH` | +| `StreamingDownloadPort` | `application` | `adapters/transfer/download` | 다운로드 use case | operation + range/resume 위치 → 진행 스트림 + 완료 결과 | `STREAM_INTERRUPTED` | +| `RealtimeSubscriptionPort` | `application` | `adapters/realtime/{sse,websocket,polling}` | 스트림 소비 use case | 구독 descriptor + resume cursor → 검증된 이벤트 스트림 + 해제 handle | `REALTIME_CONNECT_FAILED`, `REALTIME_DISCONNECTED`, `REALTIME_RESUME_GAP`, `EVENT_SCHEMA_MISMATCH` | +| `PushSubscriptionPort` | `application` | `adapters/realtime/push` | 알림 등록 use case | 권한 요청 + 공개키 → 구독 descriptor | `PUSH_PERMISSION_DENIED`, `PUSH_SUBSCRIPTION_EXPIRED` | +| `WorkerTaskPort` | `application` | `adapters/worker` | CPU 오프로드 use case | task 이름 + 직렬화 가능 입력 + timeout → 결과 또는 종료 | `WORKER_UNAVAILABLE`, `WORKER_TASK_TIMEOUT` | +| `ServiceWorkerHostPort` | `application` | `adapters/service-worker` | bootstrap + release 감시 | 등록 요청 → 등록/갱신 상태 스트림 | `SW_REGISTRATION_FAILED` | +| `BackgroundSyncPort` | `application` | `adapters/service-worker/sync` | 지연 mutation use case | keyed mutation descriptor → 큐 등록 결과 | `BACKGROUND_SYNC_UNSUPPORTED`, `BACKGROUND_SYNC_REPLAY_REJECTED` | `AuthSessionPort`는 token 문자열을 domain/application model로 반환하지 않는 형태를 우선한다. header supplier나 opaque credential attachment callback을 사용하고, 구현 세부는 auth owner가 정한다. +**멀티프로토콜(`FE-OC-030`)은 신규 port가 0개다.** GraphQL·gRPC-Web·Connect-Web은 기존 `ResourceQueryPort`/`ResourceCommandPort`의 다른 **구현체**다. 프로토콜이 application에 새 인터페이스로 새면 `FE-D010`의 dependency inversion이 무너지고, backend가 REST에서 gRPC로 옮겨갈 때 use case를 다시 써야 한다. 프로토콜 선택은 `FE-REG-API.protocol` 필드 = **데이터**이지 타입이 아니다. + +**Image CDN은 adapter가 아니라 `application`의 순수 정책이다.** URL 파생에는 I-O가 없다. `MediaUrlPolicy`를 `application/policies/`에 두고 CDN base·허용 transform은 `FE-REG-ENV` 행에서 읽는다. port로 만들면 테스트에 불필요한 test double만 늘어난다. + ### 4.5 Composition root Planned location: @@ -577,14 +607,21 @@ Boot order는 다음을 `MUST` 따른다. 3. config envelope·schema·compatibility 검증 4. release manifest 정합성 확인 5. registry snapshot load -6. auth integration adapter 주입 -7. HTTP/storage/telemetry/query-cache adapter 생성 -8. application facade 생성 -9. router 생성 -10. React root mount +6. capability 해석 — flag × 브라우저 feature detection → 활성 capability 집합 확정 +7. auth integration adapter 주입 +8. HTTP/storage/telemetry/query-cache adapter 생성 +9. 활성 capability 의 adapter 생성 (비활성 capability 의 adapter 는 생성하지 않음) +10. application facade 생성 +11. router 생성 +12. React root mount 2~4단계가 실패하면 product route를 mount하지 않고 boot error shell만 렌더한다. telemetry adapter 생성 실패는 console-safe fallback으로 계속 진행할 수 있다. +6단계와 9단계는 다음을 `MUST` 지킨다. + +- 6단계는 실패하지 않는다. flag가 ON인데 브라우저가 미지원이면 해당 capability를 비활성으로 확정하고 `capability.activation.rejected` telemetry를 남긴 뒤 boot를 계속한다. capability 부재가 boot를 막으면 skeleton이 특정 브라우저에 묶인다. +- 9단계는 비활성 capability의 adapter 모듈을 **정적 import하지 않는다**. 정적 import하면 `FE-NFR-020`(비활성 시 initial JS 증가 0)이 깨지고 `FE-GATE-033`이 실패한다. + ### 4.6 Planned directory blueprint ```text @@ -600,6 +637,7 @@ src/ ports/ use-cases/ view-models/ + policies/ presentation/ app/ routes/ @@ -613,6 +651,26 @@ src/ query-cache/ auth/ release/ + capability/ + file/ + blob-store/ + cache-persistence/ + cross-tab/ + transfer/ + upload/ + download/ + protocol/ + graphql/ + grpc-web/ + connect-web/ + realtime/ + sse/ + websocket/ + polling/ + push/ + worker/ + service-worker/ + sync/ contracts/ routes.js api-operations.js @@ -622,8 +680,12 @@ src/ query-keys.js telemetry.js release-tokens.js + capabilities.js + workers/ sample/ contract-fixture/ +public/ + sw.js tests/ unit/ component/