docs(hub): port 12개, adapter 책임 10행, capability boot 단계, 디렉터리 확장

- 멀티프로토콜은 기존 ResourceQueryPort/ResourceCommandPort 를 재사용해
  신규 port 0개. 프로토콜은 registry 데이터이지 타입이 아니다.
- Image CDN 은 I-O 가 없으므로 adapter 가 아니라 application 정책.
- boot 에 capability 해석 단계 삽입 — 실패하지 않고, 비활성 adapter 는
  정적 import 하지 않는다(FE-NFR-020 방어).
- worker/SW 엔트리는 별도 실행 컨텍스트라 상위 layer 를 import 하지 않는다.
This commit is contained in:
DongHyeonka
2026-07-28 14:14:45 +09:00
parent a8804cd9f2
commit 7a7d79b749
@@ -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/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/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` | | `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 ### 4.3 Dependency matrix
@@ -545,6 +555,10 @@ Normative dependency summary:
- adapter는 application port를 구현하지만 application은 adapter 이름을 알면 안 된다. - adapter는 application port를 구현하지만 application은 adapter 이름을 알면 안 된다.
- presentation은 application facade를 호출하며 raw backend envelope를 직접 다루면 안 된다. - presentation은 application facade를 호출하며 raw backend envelope를 직접 다루면 안 된다.
- bootstrap만 concrete adapter를 조립할 수 있다. - 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 ### 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 | | `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 | | `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` | | `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가 정한다. `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 ### 4.5 Composition root
Planned location: Planned location:
@@ -577,14 +607,21 @@ Boot order는 다음을 `MUST` 따른다.
3. config envelope·schema·compatibility 검증 3. config envelope·schema·compatibility 검증
4. release manifest 정합성 확인 4. release manifest 정합성 확인
5. registry snapshot load 5. registry snapshot load
6. auth integration adapter 주입 6. capability 해석 — flag × 브라우저 feature detection → 활성 capability 집합 확정
7. HTTP/storage/telemetry/query-cache adapter 생성 7. auth integration adapter 주입
8. application facade 생성 8. HTTP/storage/telemetry/query-cache adapter 생성
9. router 생성 9. 활성 capability 의 adapter 생성 (비활성 capability 의 adapter 는 생성하지 않음)
10. React root mount 10. application facade 생성
11. router 생성
12. React root mount
2~4단계가 실패하면 product route를 mount하지 않고 boot error shell만 렌더한다. telemetry adapter 생성 실패는 console-safe fallback으로 계속 진행할 수 있다. 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 ### 4.6 Planned directory blueprint
```text ```text
@@ -600,6 +637,7 @@ src/
ports/ ports/
use-cases/ use-cases/
view-models/ view-models/
policies/
presentation/ presentation/
app/ app/
routes/ routes/
@@ -613,6 +651,26 @@ src/
query-cache/ query-cache/
auth/ auth/
release/ 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/ contracts/
routes.js routes.js
api-operations.js api-operations.js
@@ -622,8 +680,12 @@ src/
query-keys.js query-keys.js
telemetry.js telemetry.js
release-tokens.js release-tokens.js
capabilities.js
workers/
sample/ sample/
contract-fixture/ contract-fixture/
public/
sw.js
tests/ tests/
unit/ unit/
component/ component/