feat: 기능 추가 과정중

This commit is contained in:
donghyeon-ka
2026-07-30 15:58:20 +09:00
parent d3ef801fe6
commit 6c52cdb916
648 changed files with 126325 additions and 6680 deletions
+131 -22
View File
@@ -2,8 +2,13 @@
이 문서는 도메인과 무관한 선택형 frontend capability를 실제 프로젝트에
도입하는 실행 가이드다. 기본 스켈레톤에는 vendor runtime을 설치하지 않는다.
`RECIPE_AVAILABLE`계약·fake·failure policy가 준비됐다는 뜻이며 실제 provider,
runtime behavior 또는 production readiness를 뜻하지 않는다.
`RECIPE_AVAILABLE`catalog에 복사해 좁힐 recipe가 있다는 availability
표시다. runtime 구현이나 제품 선택·조립 상태가 아니다. 현재 catalog의 product
selection과 production composition은 별도로 `NOT_SELECTED`/미설치다.
일부 browser-native capability에는 dependency 없는
`referenceRuntime.status=AVAILABLE_NOT_COMPOSED` 구현이 함께 있지만, 이것도
제품 dataset·owner·policy가 정해져 bootstrap에 연결되기 전에는 설치된 기능이나
production readiness를 뜻하지 않는다.
## 1. 현재 상태와 파일 지도
@@ -14,13 +19,57 @@ runtime behavior 또는 production readiness를 뜻하지 않는다.
| TypeScript port | `recipes/frontend-capabilities/contracts.ts` | 아니오 |
| fake/unavailable | `recipes/frontend-capabilities/fake-adapters.ts` | 아니오 |
| contract test | `tests/recipes/optional-capability-contracts.test.ts` | 아니오 |
| 정적/번들 gate | `scripts/check-optional-recipes.mjs` | build 도구 |
| negative fixture | `scripts/check-optional-recipe-fixtures.mjs` | 아니오 |
| 완전 제거 gate | `scripts/test-optional-recipe-removal.mjs` | 아니오 |
| file/IndexedDB/OPFS/Cache 심층 계약 | `recipes/frontend-capabilities/browser-file-storage-contracts.ts` | 아니오 |
| 심층 deterministic fake | `recipes/frontend-capabilities/browser-file-storage-fakes.ts` | 아니오 |
| 심층 contract test | `tests/recipes/browser-file-storage-contracts.test.ts` | 아니오 |
| browser data current-status ledger | `docs/architecture/browser-data-capability-completion-ledger.md` | 문서만 |
| 심층 설계/ADR | `docs/architecture/browser-file-and-origin-storage.md`, `decisions/VD-11-browser-file-and-origin-storage.md` | 문서만 |
| client cache scope/persistence 설계 | `docs/architecture/client-cache-and-storage.md`, `decisions/VD-13-client-cache-scope-and-persistence.md` | 문서만 |
| realtime/Web Push/Polling 설계와 reference runtime | `docs/architecture/realtime-events-web-push-and-bounded-polling.md`, `decisions/VD-28-realtime-events-web-push-and-bounded-polling.md`, `src/adapters/realtime`, `src/adapters/web-push` | composition 전에는 tree-shaken |
| transfer/CDN 설계/ADR | `docs/architecture/presigned-transfer-and-image-cdn.md`, `decisions/VD-12-presigned-transfer-and-image-cdn.md` | 문서만 |
| Range/background 결정 | `docs/architecture/decisions/VD-14-resumable-download-and-background-transfer.md` | 문서만 |
| storage lifecycle/migration 결정 | `docs/architecture/decisions/VD-15-origin-storage-lifecycle-and-migration.md` | 문서만 |
| transfer composition/Image provider 결정 | `docs/architecture/decisions/VD-16-browser-transfer-composition-and-image-delivery.md` | 문서만 |
| REST/GraphQL/Connect/gRPC-Web·Protobuf/REST Gateway·Schema·Mapper·Server State 설계 | `docs/architecture/api-contract-schema-mapper-and-server-state.md`, `docs/architecture/protobuf-browser-transport-and-rest-gateway.md`, `decisions/VD-23-api-transport-selection-and-rest-execution.md`, `decisions/VD-24-runtime-schema-and-boundary-mapper.md`, `decisions/VD-25-server-state-cache-lifecycle.md`, `decisions/VD-26-persisted-graphql-operation.md`, `decisions/VD-27-grpc-web-unary-and-server-stream.md`, `decisions/VD-29-connect-web-and-browser-protobuf-runtime.md`, `decisions/VD-30-protobuf-contract-and-rest-gateway.md` | 문서만 |
| provider-neutral Browser RPC V3 계약/port/runtime | `src/contracts/browser-rpc.ts`, `src/application/ports/browser-rpc`, `src/adapters/browser-rpc`, `tests/unit/browser-rpc` | composition 전에는 tree-shaken |
| API contract/server-state 복구 runbook | `docs/operations/api-contract-and-server-state-recovery.md` | 문서만 |
| storage 복구 runbook template | `docs/operations/browser-file-storage-recovery.md` | 문서만 |
| transfer/CDN 복구 runbook template | `docs/operations/browser-transfer-recovery.md` | 문서만 |
| browser-native reference runtime | `src/adapters/browser-files`, `src/adapters/browser-transfer`, `src/adapters/storage/indexeddb`, `src/adapters/storage/opfs`, `src/adapters/cache-storage` | composition 전에는 tree-shaken |
| reference runtime browser evidence | `tests/browser-capabilities` | 아니오 |
| 정적/번들 gate | `scripts/check-optional-recipes.ts` | build 도구 |
| negative fixture | `scripts/check-optional-recipe-fixtures.ts` | 아니오 |
| 완전 제거 gate | `scripts/test-optional-recipe-removal.ts` | 아니오 |
| native reference runtime 제거 gate | `scripts/test-browser-file-storage-runtime-removal.ts` | 아니오 |
현재 `productionRuntimeDependencies`는 빈 배열이며 12개 recipe 모두 선택되지
않았다. TypeScript example은 product source가 import할 library가 아니라 선택
시 복사하고 좁힐 출발점이다.
않았다. `recipes/` TypeScript example은 선택 시 복사하고 좁힐 출발점이고,
`src/adapters`의 browser-native reference runtime은 공통 lifecycle·failure
mechanism을 재사용할 수 있는 실제 구현이다. 제품 feature는 이 runtime에
schema/codec/query와 dataset 정책을 주입하고 더 좁은 facade 뒤에서 조립한다.
### Reference runtime bundle budget
`referenceRuntime`이 있는 recipe는 catalog의 `sourceRoots`를 실제 budget entry로
사용한다. gate는 중첩 root의 실행 가능한 source를 중복 제거하고 경로순으로
정렬한 뒤, 모든 module을 하나의 synthetic entry에 포함한다. 이 entry는 Vite
production mode, ES2022/ES module, esbuild minify로 build하며 tree-shaking을
명시적으로 끈다. 따라서 synthetic consumer가 호출 여부를 알 수 없다는 이유로
validation, quota, integrity, cleanup 같은 fail-closed guard가 예산에서 빠지지
않는다. 생성된 모든 chunk/asset의 Node zlib gzip byte 합계를 catalog의
`bundleBudgetGzipBytes`와 비교하고 결과와 SHA-256을
`artifacts/quality/optional-recipes.json`에 기록한다.
이 synthetic build는 설치 크기 상한을 검증하기 위한 것이며 product bootstrap에
runtime을 compose하지 않는다. 별도의 production manifest/module-inventory
검사는 선택되지 않은 runtime source가 실제 `dist`에 없는지 계속 검증한다.
2026-07-28 기준 동일 설정의 `offline-indexeddb` 실측은 32,930 gzip bytes였다.
기존 8,000 bytes 값은 bundle 측정 없이 선언된 값으로 실제 reference runtime과
일치하지 않아 약 9% headroom을 둔 36,000 bytes로 교정했다.
`service-worker-pwa` 10,000 bytes와 `file-transfer` 52,000 bytes는 현재 실측을
수용하므로 유지한다. 이후 source가 예산을 넘으면 gate를 우회하거나 예산을
자동 인상하지 않고, output artifact와 변경 이유를 검토해야 한다.
## 2. 어느 경계에 두는가
@@ -40,10 +89,10 @@ type은 facade 밖으로 노출하지 않는다.
| recipe | 설치하는 경우 | 설치하면 안 되는 경우 | 핵심 fallback |
| --- | --- | --- | --- |
| realtime | ordered push/resume protocol이 확정됨 | polling이 충분하거나 ordering owner 없음 | bounded polling/stale UI |
| offline/IndexedDB | durable offline data/queue가 제품 요구 | credential 저장, DB 직접 연결, HTTP cache로 충분 | online-only + offline state |
| Service Worker/PWA | install/offline shell과 cache owner 승인 | update/rollback UX 없음 | hosting cache 기반 network app |
| file transfer | progress/cancel/size/type 정책 필요 | long-lived credential URL | bounded normal request |
| realtime | foreground ordered event 또는 duplex protocol이 확정됨 | focus refetch/manual refresh가 충분하거나 ordering·replay owner 없음 | bounded polling 또는 stale UI |
| offline/IndexedDB/OPFS | structured offline data/queue 또는 large local binary가 제품 요구 | credential, partition/retention/recovery 미정, HTTP cache로 충분 | read-only/online-only 또는 승인된 bounded Blob |
| Service Worker/Cache Storage | install/offline shell 또는 public HTTP representation cache owner 승인 | auth/private/opaque cache, update/rollback UX 없음 | hosting cache 기반 network app |
| file/picker/download | selection/preview/upload/download와 bounded memory/integrity 요구 | backend 재검증 없음, whole-buffer large file, long-lived credential URL | native input + authorized direct download |
| generated API | versioned source와 drift CI가 있음 | DTO가 domain/UI로 노출됨 | typed request builder + schema |
| feature flag | rollout/kill switch owner와 default 있음 | authorization에 사용 | typed local default |
| Web Worker | profiler가 main-thread 병목을 증명 | 단순 network I/O | chunked/deferred execution |
@@ -57,6 +106,13 @@ type은 facade 밖으로 노출하지 않는다.
SSOT다. 문서와 catalog가 다르면 gate가 검사하는 catalog를 우선 고치고 이 표도
같이 갱신한다.
Web Push target은 현재 13번째 설치 recipe가 아니다. 기존 `realtime`,
`service-worker-pwa`, `browser-permission`의 인접 경계를 조합해야 하는 별도
`NOT_SELECTED` capability다. 실제 선택 전 VD-10 amendment와 machine-readable
catalog에 permission, subscription/backend provider, worker handler, notification
policy와 removal source를 명시하며, foreground realtime이 선택됐다는 이유로
Web Push를 함께 설치하지 않는다.
## 4. 공통 구현 순서
1. 문제를 vendor 이름이 아닌 capability와 측정값으로 기록한다.
@@ -86,19 +142,67 @@ SSOT다. 문서와 catalog가 다르면 gate가 검사하는 catalog를 우선
resume token expiry를 정의한다.
- duplicate/out-of-order는 domain use case에 전달하기 전에 정책화한다.
- route unmount/logout에서 unsubscribe하고 heartbeat timer를 종료한다.
- SSE는 active document의 one-way stream, WebSocket은 duplex protocol,
Web Push는 Service Worker가 받는 background notification hint, Polling은
visible/finite HTTP scheduling policy로 분리한다.
- 기본 event 효과는 query namespace invalidation과 authoritative refetch다.
gap, cursor expiry와 queue overflow에서는 delta 적용을 중단하고 snapshot으로
복구한다.
- Web Push permission은 user action에서만 요청하고 subscription endpoint/key와
payload를 storage, URL, telemetry나 application state에 노출하지 않는다.
- detailed status, target contract와 구현 work package는
[Realtime events, Web Push, and bounded polling](./realtime-events-web-push-and-bounded-polling.md)을
따른다. 현재 concrete runtime과 product selection은 없다.
### Offline/Service Worker
### IndexedDB/OPFS
- store/cache 이름과 schema는 release와 독립적인 migration version을 가진다.
- quota, corrupt row, partial migration, downgrade/rollback을 fixture로 만든다.
- authenticated response와 credential은 기본 cache 대상이 아니다.
- stale worker loop를 막고 unregister 후 owned cache 삭제가 가능한지 검증한다.
- 기존 동기식 preference `StoragePort`에 넣지 않고 feature-specific async
repository와 large-object port를 사용한다.
- raw database/transaction/store/index/schema version은 adapter 밖으로 노출하지
않는다. request success가 아니라 transaction complete 이후에만 성공이다.
- DDL schema와 record codec version을 분리하고 schema upgrade는 additive,
data migration은 resumable bounded batch로 실행한다.
- versionchange/blocked/future schema를 read-only/online-only 상태로 드러내며
자동 reload와 database 삭제를 금지한다.
- OPFS는 immutable bytes만 소유하고 IndexedDB journal이
`PREPARING -> FILES_READY -> COMMITTED -> CLEANED` commit authority를 가진다.
- quota, corrupt row/manifest, migration checkpoint, worker crash, storage eviction,
N-1 rollback을 fixture와 실제 browser에서 검증한다.
### File/generated API
### Cache Storage/Service Worker
- upload는 client MIME을 신뢰하지 않고 size/type/server rejection을 모두 다룬다.
- Cache Storage는 public HTTP representation 전용이며 application repository나
query cache가 아니다.
- auth, cookie-dependent, private, personal, no-store, opaque, redirect, 206을
거절하고 query/Vary exact match를 보존한다.
- candidate 전체의 type/size/integrity가 검증된 뒤에만 release를 활성화하고
verified previous release를 rollback용으로 유지한다.
- stale worker loop를 막고 unregister와 parsed owned-cache cleanup을 별도 lifecycle로
검증한다.
### File/Blob/picker/download, presigned transfer와 generated API
- native File/Blob/handle은 transient adapter vault 안에 두고 application에는
opaque ref, untrusted metadata와 bounded range/chunk만 전달한다.
- native input을 baseline으로 두고 system picker는 user activation 안에서만
progressive enhancement한다. dismissal은 failure가 아니다.
- upload는 client MIME을 신뢰하지 않고 count/size/signature/server rejection,
resumable session, part checksum/idempotency, quarantine을 다룬다.
- presigned URL은 application에 raw URL로 노출하지 않고 in-memory identity
capability로 보관한다. method/resource 또는 session-part/offset/length/checksum,
expiry, origin/path/query/header를 정확히 묶고 data-plane fetch는 credential,
redirect, referrer와 cache를 fail-closed 정책으로 제한한다.
- resume는 IndexedDB checkpoint만 신뢰하지 않고 server status와 다시 선택한
source의 part digest를 대조한다. checkpoint에는 URL/query/signed header/token을
저장하지 않으며 explicit abort가 불명확하면 reconcile 전까지 유지한다.
- progress는 unknown total을 허용하며 navigation/unmount에서 AbortSignal로
취소한다.
취소한다. server upload session은 별도 abort/TTL cleanup이 필요하다.
- 큰 download는 single `Uint8Array`/Blob이 아니라 browser handoff 또는
backpressure stream을 사용하고 handoff와 confirmed save를 구분한다.
- Image CDN은 raw transform URL builder가 아니라 opaque asset과
composition-registered preset으로만 responsive descriptor를 만든다. immutable
revision, format/width/pixel/decode/cache/expiry와 CDN origin을 검증한다.
- object URL은 explicit lease로 만들고 replacement/unmount에서 revoke한다.
- generated code는 facade 뒤 DTO이며 runtime response schema와 contract drift
gate를 유지한다.
@@ -128,17 +232,22 @@ SSOT다. 문서와 catalog가 다르면 gate가 검사하는 catalog를 우선
```bash
corepack pnpm check:types:recipes
corepack pnpm test:recipes
corepack pnpm test:browser-capabilities
corepack pnpm build
corepack pnpm check:optional-recipes
corepack pnpm check:optional-recipe-fixtures
corepack pnpm test:optional-recipe-removal
corepack pnpm test:browser-file-storage-removal
```
negative gate는 cleanup 누락, unselected dependency, local adapter 밖 vendor
import, credential localStorage/URL/telemetry 경로, workflow store의 server-state
복제 production source의 recipe import를 거절한다. removal gate는 recipe와
recipe test를 삭제한 임시 사본에서 base typecheck, architecture, test와 build를
실행한다.
복제, production source의 recipe import와 선택 전 reference runtime composition을
거절한다. removal gate는 recipe와 recipe test를 삭제한 임시 사본에서 base
typecheck, architecture, test와 build를 실행한다.
별도 native-runtime removal gate는 browser file/storage source와 전용 test,
catalog metadata를 제거한 임시 사본에서 typecheck, architecture, 전체 base test,
build와 optional catalog 검사를 다시 실행한다.
## 7. 제거 체크리스트