Files
clean-architecture-frontend…/docs/architecture/2026-07-30-frontend-runtime-capability-repository-aligned-implementation-closed-deep-design.md
2026-08-01 19:39:59 +09:00

154 KiB

프론트엔드 런타임 Capability 저장소 정합형 구현 결정 폐쇄 상세 설계

  • 작성일: 2026-07-30
  • 상태: 구현 결정 정본 v1
  • 대상 저장소: clean-architecture-frontend-template
  • 기준 저장소 스냅샷: 제공된 Repomix 전체 저장소 표현
  • 상위 설계:
    • 2026-07-30-frontend-production-capability-repository-aligned-deep-design.md
  • 계약 정본:
    • 별도 API·이벤트 계약 관리 저장소에서 발행한 immutable TypeScript contract package
  • 기준 기술:
    • Node.js 24 major
    • pnpm 11
    • TypeScript strict / allowJs: false
    • React 19
    • React Router 7 data mode
    • TanStack Query 5
    • Zod 4
    • Vite 8
  • 문서 규칙:
    • MUST: 구현자가 변경할 수 없는 기본 결정
    • MUST NOT: 존재하면 설계 위반
    • MAY: 명시적으로 선택된 capability에만 허용
    • 본 문서의 exact literal, 상태, 상한, 순서와 source path는 예시가 아니라 구현 정본이다.

목차


0. 문서 권한과 제외 범위

0.1 목적

이 문서는 다음 프론트엔드 런타임 capability를 현재 저장소 구조에 맞춰 구현할 때 개발자가 다시 선택하거나 질문할 지점을 제거한다.

Fetch HTTP Client
Runtime Config
Router
Query / Mutation
Connection / Reconnect / Resume
Event Validation
Subscription Cleanup
Web Worker
Service Worker
Background Sync

상위 저장소 정합형 설계가 방향과 책임을 고정했다면, 본 문서는 다음을 추가로 고정한다.

  • exact source path;
  • exact public type shape;
  • exact lifecycle state;
  • exact startup/shutdown order;
  • exact timeout, queue, payload와 retry ceiling;
  • exact optional capability selection과 removal 절차;
  • external contract package를 저장소 runtime에 연결하는 단일 경로;
  • browser lifecycle과 session/account transition ordering;
  • failure union과 mutation uncertainty 처리;
  • 구현 중 허용되지 않는 fallback;
  • 구현 완료를 판정할 deterministic behavior case.

0.2 본 문서가 대체하는 범위

상위 설계의 다음 절은 본 문서가 더 구체적인 정본이다.

상위 범위 본 문서 정본
Runtime Config와 boot coherence §5–§6
Fetch HTTP Client §4, §7–§8
Router §9
Query와 Mutation §10–§12
Realtime 공통 경계 §13–§15
Web Worker §16
Service Worker §17–§18
Offline Command와 Background Sync §19
optional capability 조립 §3, §17.3, §24
lifecycle·security·observability §20–§22
source 배치와 구현 순서 §24–§25

상위 설계와 본 문서가 충돌하면 위 범위에서는 본 문서를 따른다.

0.3 API·이벤트 계약 관련 명시적 제외

다음은 별도 API·이벤트 계약 관리 저장소가 소유한다. 본 문서에서 다시 정의하거나 frontend local schema로 복제하지 않는다.

OpenAPI / AsyncAPI 원문
operation method/path/status/media semantics
Problem Details type/code/extensions
pagination cursor wire shape
idempotency server semantics와 retention
event type/envelope/payload schema
replay/cursor/snapshot server protocol
provider compatibility와 deprecation/sunset
backend controller/event publisher conformance
contract package 생성·발행·서명·provenance

본 저장소가 소유하는 것은 다음뿐이다.

immutable TypeScript contract package pin
package manifest/digest 검증
frontend-owned normalized descriptor interface
runtime validator 호출
wire DTO -> application value mapper
HTTP/Query/Realtime runtime behavior
contractSet와 frontend release coherence
unknown/future contract input의 safe failure

외부 package의 의미가 부족하거나 서로 모순되면 frontend가 임의 의미를 보충하지 않는다. 해당 feature contribution을 composition하지 않고 CONTRACT_CONTRIBUTION_INVALID로 종료한다.

0.4 CI 명시적 비범위

다음은 별도 CI/CD 플랫폼이 소유하며 본 문서에서 설계하지 않는다.

  • workflow YAML;
  • merge/release stage 구성;
  • gate ID와 promotion formula;
  • artifact retention service;
  • package publication identity;
  • vulnerability/signing/SBOM pipeline;
  • deployment/canary controller;
  • CI evidence aggregation.

§23은 코드가 만족해야 할 행동 검증 케이스만 정의한다. 어느 pipeline에서 실행할지는 정의하지 않는다.

0.5 정본 우선순위

충돌 시 다음 순서를 사용한다.

  1. 현재 저장소 source와 실행 가능한 test가 증명하는 사실;
  2. 별도 계약 저장소에서 발행한 exact immutable package;
  3. 본 문서의 MUST/MUST NOT와 decision ledger;
  4. 상위 저장소 정합형 설계;
  5. 기존 일반 greenfield 설계;
  6. 예시 코드와 설명 문구.

예시가 public type과 다르면 public type이 정본이다. 두 설정이 같은 의미를 서로 다르게 지정하면 precedence를 만들지 않고 startup을 실패시킨다.


1. 현재 저장소 기준선

1.1 물리 구조

현재 저장소의 canonical source root는 다음과 같다.

src/
  contracts/       # 저장소 내부 registry와 provider-neutral runtime contract
  application/     # input/output port, use case, policy, view model
  adapters/        # HTTP, query cache, storage, realtime, web-push 등 concrete adapter
  presentation/    # React inbound adapter, Router, Query hook, UI
  bootstrap/       # 유일한 composition root와 boot
  features/        # 제거 가능한 vertical feature

다음 신규 parallel root는 만들지 않는다.

src/platform
src/service-worker
src/workers
src/infrastructure
src/api
src/shared/api

1.2 계층 방향

현재 config/architecture/layers.json의 방향을 유지한다.

domain       -> domain only
application  -> application, domain, contracts
presentation -> presentation, application, domain, contracts
adapters     -> adapters, application, domain, contracts
bootstrap    -> src 전체

추가 불변조건:

presentation MUST NOT import adapters
application MUST NOT import React, Router, TanStack Query, browser API
adapters MUST NOT import presentation/bootstrap
feature presentation MUST call application input or approved inbound bridge
bootstrap alone selects concrete runtime

1.3 현재 구현을 재사용하는 항목

다음 source를 제거하거나 동일 기능으로 다시 만들지 않는다.

Capability 현재 source 판정
Runtime Config schema/load src/bootstrap/runtime-config-schema.ts, load-runtime-config.ts 보강
Release manifest/load src/bootstrap/load-release-manifest.ts V2 migration
composition root src/bootstrap/create-runtime-composition.ts, composition-root.ts 확장
bounded HTTP response src/adapters/http/bounded-json.ts 공통 bounded body reader로 확장
Fetch client src/adapters/http/client.ts execution V3 보강
request builder src/adapters/http/request-builder.ts package descriptor bridge 연결
REST registry/profile src/contracts/api-operations.ts, rest-profiles.ts external contribution normalization으로 migration
Query adapter src/presentation/adapters/query/application-query.ts 유지·보강
Query cache coordinator src/adapters/query-cache/* scope/invalidation 보강
Router runtime src/presentation/routes/* 유지
realtime registry/runtime src/contracts/realtime-*, src/adapters/realtime/* 제품 조립만 추가
Web Push worker handler src/adapters/web-push/service-worker-runtime.ts 단일 SW entry handler로 재사용
IndexedDB reference runtime src/adapters/storage/indexeddb/* offline dataset backend로 재사용
OPFS Worker src/adapters/storage/opfs/* generic CPU Worker와 분리 유지
Cache Storage reference src/adapters/cache-storage/* static asset handler에서 재사용

1.4 현재 삭제하지 않는 compatibility surface

다음 public/runtime surface는 migration 동안 유지한다.

  • existing reference feature route와 local fixture;
  • current ApiOperationV2 reader;
  • current runtime config V1 reader;
  • current release manifest V1 parser;
  • current query key and invalidation contracts;
  • current RT-01~RT-04 common runtime;
  • current browser file/storage reference runtime.

새 writer가 V2를 사용하도록 먼저 전환한 뒤 V1 reader를 제거한다. 같은 release에서 source shape, manifest shape와 runtime behavior를 동시에 big-bang 교체하지 않는다.

1.5 현재 상태 taxonomy

이 문서는 저장소 기존 상태 literal을 유지한다.

COMPOSED
AVAILABLE_NOT_COMPOSED
DESIGNED_NOT_IMPLEMENTED
NOT_SELECTED
PLATFORM_LIMITED

현재 주요 판정:

Capability 현재 본 문서 목표
Runtime Config COMPOSED V1 V2 bounded boot
Release coherence COMPOSED scalar contract version contractSet V2
HTTP reference path COMPOSED execution V3
Router COMPOSED ownership closed
Query memory cache COMPOSED scope/result/invalidation closed
Query persistence NOT_SELECTED 계속 disabled
Realtime common runtime AVAILABLE_NOT_COMPOSED 그대로 재사용
Product realtime NOT_SELECTED explicit contribution 필요
Generic CPU Worker NOT_SELECTED opt-in runtime
OPFS Worker AVAILABLE_NOT_COMPOSED 별도 유지
Cache Storage AVAILABLE_NOT_COMPOSED static asset only
PWA Service Worker lifecycle DESIGNED_NOT_IMPLEMENTED reference runtime
Offline command queue NOT_SELECTED opt-in reference runtime
Background Sync guarantee PLATFORM_LIMITED wake-only enhancement

2. 구현 결정 폐쇄 표

다음 표의 선택은 구현 중 다시 묻지 않는다.

질문 결정
capability별 새 src/platform/*를 만드는가 아니다. 기존 contracts/application/adapters/presentation/bootstrap/features에 배치한다.
API·이벤트 schema를 frontend에 복사하는가 아니다. exact immutable contract package만 소비한다.
deployed OpenAPI/AsyncAPI를 runtime에 읽는가 아니다. build dependency만 사용한다.
API_CONTRACT_VERSION scalar를 유지하는가 제거한다. Release Manifest V2 contractSet으로 교체한다.
Runtime Config가 optional feature를 켤 수 있는가 아니다. static installed capability를 끌 수만 있다.
boot JSON을 response.json()으로 읽는가 아니다. streaming byte bound 후 UTF-8/JSON parse한다.
HTTP retry owner HTTP transport 하나다. Query/Mutation retry는 false다.
redirect 모든 API/boot request에서 reject한다.
mutation timeout/abort를 실패로 단정하는가 아니다. send 이후는 MAYBE_APPLIED다.
Router loader가 server fetch/cache를 소유하는가 아니다. URL parse/guard/lazy lifecycle만 소유한다.
QueryClient 수 application runtime당 하나다.
Query persistence 기본 비활성이다. Web Storage와 IndexedDB에 query payload를 넣지 않는다.
query result size 추정 모든 definition의 required measureResult만 사용한다. generic fallback은 없다.
cross-tab data 전송 금지한다. opaque invalidation topic만 전송한다.
realtime transport 자동 fallback 금지한다. registration이 explicit fallback을 선택한다.
connection open과 resume success 별도 상태다. OPEN_UNRESUMED에서 event를 적용하지 않는다.
event checkpoint commit application effect 성공 이후다.
component가 raw socket을 소유하는가 아니다. ref-counted subscription lease만 소유한다.
generic Worker 기본 설치 아니다. profiling과 static contribution이 있어야 한다.
Worker가 network/auth를 소유하는가 아니다. CPU task만 수행한다.
PWA와 Web Push worker registration을 나누는가 아니다. 동일 scope에 physical worker 하나다.
skipWaiting() client drain handshake 성공 후에만 호출한다.
clients.claim() baseline에서 사용하지 않는다.
navigation offline shell 제공하지 않는다. navigation은 network-only다.
API response Cache Storage 금지한다. verified static asset만 cache한다.
Service Worker 등록 시점 Runtime Config/release 검증과 첫 committed render 이후다.
Background Sync가 command를 전송하는가 baseline에서는 아니다. foreground drain wake-up만 기록한다.
offline sender 수 origin/account partition당 하나다.
SENDING 중 crash PENDING으로 되돌리지 않고 EFFECT_UNKNOWN이다.
일반 disabled capability listener/DB/worker/timer를 만들지 않는다.
persisted Service Worker disable 새 register 없이 exact-owned registration만 unregister하고 cache를 보존한다.
Service Worker source 제거 ACTIVE -> REMOVE_REGISTRATION -> PURGE_OWNED_RESOURCES -> null 순서다.
lifecycle correctness를 unload에 의존하는가 아니다. transaction, TTL, recovery로 닫는다.

3. 목표 런타임 구조와 lifecycle

3.1 전체 흐름

Vite embedded build identity
  + installed contract contributions
  + installed runtime capabilities
       |
       v
bounded Runtime Config V2
       |
       v
bounded Release Manifest V2 + contractSet check
       |
       v
base composition
  HTTP / auth bridge / application / QueryClient / Router
       |
       v
React first committed render
       |
       v
post-mount optional runtime start
  offline foreground observer
  realtime hosts
  lazy Web Worker registry
  Service Worker active/cleanup controller

3.2 boot state machine

UNINITIALIZED
  -> LOADING_RUNTIME_CONFIG
  -> VALIDATING_RUNTIME_CONFIG
  -> LOADING_RELEASE_MANIFEST
  -> VALIDATING_RELEASE_COHERENCE
  -> COMPOSING_BASE
  -> MOUNTING
  -> BASE_READY
  -> STARTING_OPTIONAL
  -> READY

terminal/degraded state:

BOOT_FAILED_SAFE
BASE_READY_OPTIONAL_DEGRADED
DISPOSED

BOOT_FAILED_SAFE에서는 product Router와 Query provider를 mount하지 않는다. safe boot shell만 render한다.

3.3 composition result

export interface RuntimeComposition {
  readonly application: ApplicationApi;
  readonly queryClient: QueryClient;
  readonly router: Router;
  readonly scopeRuntime: ServerStateScopeRuntime;
  readonly optional: OptionalRuntimeHost;
  dispose(): Promise<void>;
}

feature page에 다음을 노출하지 않는다.

  • raw Fetch client;
  • endpoint URL;
  • auth credential attacher;
  • browser storage;
  • QueryClient;
  • WebSocket/EventSource;
  • Worker;
  • ServiceWorkerRegistration;
  • IndexedDB transaction.

3.4 optional runtime host

export interface OptionalRuntimeHost {
  readonly realtime: RealtimeRuntimeHost | null;
  readonly webWorkers: WebWorkerRuntimeHost | null;
  readonly serviceWorker: ServiceWorkerRuntimeHost | null;
  readonly offlineCommands: OfflineCommandRuntimeHost | null;
  startAfterMount(): Promise<void>;
  stop(): Promise<void>;
}

start order:

1. offline foreground browser lifecycle observer
2. realtime runtime
3. Web Worker prewarm 없음
4. Service Worker active/cleanup controller

stop order는 역순이다.

3.5 static capability selection SSOT

src/features/installed-runtime-capabilities.ts
export interface InstalledRuntimeCapabilities {
  readonly realtime: readonly InstalledRealtimeContribution[];
  readonly webWorkers: readonly InstalledWebWorkerContribution[];
  readonly serviceWorker: InstalledServiceWorkerSelection | null;
  readonly offlineCommands: InstalledOfflineCommandContribution | null;
}

export type RuntimeCapabilityOverride = "DEFAULT" | "DISABLED";

export type InstalledServiceWorkerSelection =
  | Readonly<{
      mode: "ACTIVE";
      scriptPath: "service-worker.js";
      handlers: readonly ServiceWorkerHandlerId[];
    }>
  | Readonly<{
      mode: "REMOVE_REGISTRATION";
      scriptPath: "service-worker.js";
    }>
  | Readonly<{
      mode: "PURGE_OWNED_RESOURCES";
      scriptPath: "service-worker.js";
    }>;

export type ServiceWorkerHandlerId =
  | "WEB_PUSH"
  | "PWA_STATIC_ASSETS"
  | "OFFLINE_SYNC_WAKEUP";

규칙:

  • source contribution이 설치 capability를 결정한다;
  • Runtime Config는 DEFAULT | DISABLED만 제공한다;
  • config가 source에 없는 capability를 켤 수 없다;
  • dynamic import path를 config 문자열에서 만들지 않는다;
  • contribution array와 object는 composition 시 deep snapshot/freeze한다;
  • duplicate ID와 contradictory selection은 mount 전 실패한다.

3.6 zero-side-effect 원칙과 persistent 예외

realtime/Web Worker/offline command가 unselected 또는 disabled이면:

factory call           0
window listener        0
timer                   0
network                 0
IndexedDB open          0
Worker creation         0

Service Worker는 browser에 persistent registration이 남을 수 있으므로 다음 예외만 허용한다.

static ACTIVE + runtime DISABLED
  -> new register 0
  -> exact owned registration lookup 1
  -> owned registration unregister at most 1
  -> owned cache delete 0

이 예외를 다른 capability의 hidden cleanup side effect 근거로 확대하지 않는다.


4. 외부 계약 package 소비 경계

4.1 단일 import 위치

service별 generated package import는 다음 파일 한 곳에서만 허용한다.

src/features/<feature>/contracts/<service>-contract-contribution.ts

예:

src/features/worklog/
  contracts/
    worklog-service-contract-contribution.ts
    worklog-mapper.ts
  application/
  adapters/
    worklog-http-gateway.ts
  presentation/

금지 import:

src/domain/**       -> @org-contracts/*
src/application/**  -> @org-contracts/*
src/presentation/** -> @org-contracts/*
src/adapters/http/** -> service-specific package
src/bootstrap/**     -> service-specific package direct import

bootstrap은 normalized feature contribution만 수집한다.

4.2 normalized contribution

export interface InstalledContractContribution {
  readonly featureId: string;
  readonly source: ContractContributionSource;
  readonly http: readonly InstalledHttpContract<unknown, unknown, unknown>[];
  readonly events: readonly InstalledEventContract<unknown, unknown>[];
}

export type ContractContributionSource =
  | Readonly<{
      kind: "EXTERNAL_PACKAGE";
      package: InstalledContractPackageIdentity;
    }>
  | Readonly<{
      kind: "TEMPLATE_FIXTURE";
      fixtureId: "REFERENCE_FEATURE_V1";
      revision: 1;
    }>;

export interface InstalledContractPackageIdentity {
  readonly packageId: string;
  readonly version: string;
  readonly digest: `sha256:${string}`;
  readonly runtimeProtocolVersion: 1;
  readonly sourceRevision: string;
}

frontend interface는 package의 business model을 소유하지 않는다. descriptor와 runtime validator reference만 보관한다.

4.3 HTTP contribution

export interface InstalledHttpContract<Input, WireOutput, Problem> {
  readonly contract: Readonly<{
    operationId: string;
    method: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
    pathTemplate: string;
    inputValidator: RuntimeValidator<Input>;
    outputValidator: RuntimeValidator<WireOutput>;
    problemValidator: RuntimeValidator<Problem>;
    acceptedStatuses: readonly number[];
    emptyBodyStatuses: readonly number[];
    retrySemantics: "SAFE" | "IDEMPOTENT" | "KEYED" | "NEVER";
    requestBody: "NONE" | "JSON";
    responseBody: "REQUIRED_JSON" | "OPTIONAL_JSON" | "NONE";
    commandRecovery: CommandRecoveryDescriptor | null;
    commandEffect: CommandEffectDescriptor<Problem> | null;
  }>;
  readonly frontend: HttpExecutionPolicy;
}

export interface HttpExecutionPolicy {
  readonly policyId: string;
  readonly requestByteLimit: number;
  readonly responseByteLimit: number;
  readonly totalDeadlineMs: number;
  readonly retryBudget: 0 | 1 | 2;
  readonly authProfileId: string;
  readonly diagnosticsOperation: string;
}

contract는 package가 제공한다. frontend가 method/path/status/retry/effect 의미를 다시 적지 않는다.

frontend만 이 저장소가 소유한다. 값은 §7의 hard ceiling 안에 있어야 한다.

4.4 command recovery descriptor

frontend는 server semantics를 새로 설계하지 않고 package가 제공하는 recovery capability만 소비한다.

export type CommandRecoveryDescriptor = Readonly<{
  mode: "IDEMPOTENCY_REPLAY" | "INSPECT_OPERATION";
  operationIdentityField: string;
  inspectOperationId?: string;
}>;

export interface CommandEffectDescriptor<Problem> {
  readonly successEffect: "APPLIED_CONFIRMED";
  classifyProblem(input: Readonly<{
    status: number;
    problem: Problem;
  }>): "NOT_APPLIED" | "APPLIED_CONFIRMED" | "MAYBE_APPLIED";
}

규칙:

  • query/read operation은 commandRecovery=null, commandEffect=null이다;
  • command operation은 commandEffect가 필수다;
  • command operation에 retrySemantics=KEYED이면 commandRecovery도 필수다;
  • descriptor가 없거나 invalid이면 automatic retry와 durable queue가 모두 금지된다;
  • frontend는 inspect request/response wire shape나 problem effect meaning을 작성하지 않는다;
  • classifyProblem은 package가 제공한 pure bounded classifier다;
  • classifier가 throw/unknown을 반환하면 effect는 fail-safe MAYBE_APPLIED이고 contract runtime failure를 기록한다;
  • feature gateway가 package의 inspect operation을 application-safe outcome으로 mapping한다.

4.5 event contribution

export interface InstalledEventContract<Envelope, Payload> {
  readonly eventType: string;
  readonly envelopeValidator: RuntimeValidator<Envelope>;
  readonly payloadValidator: RuntimeValidator<Payload>;
}

package identity는 enclosing InstalledContractContribution.source가 단 한 번 소유한다. HTTP와 event가 서로 다른 package identity라면 contribution을 둘로 나눈다.

connection, queue, dedupe, scope, recovery와 application effect는 frontend realtime contribution이 소유한다. generated package는 socket을 열거나 QueryClient를 참조하지 않는다.

4.6 runtime validator protocol

export interface RuntimeValidator<T> {
  readonly schemaId: string;
  safeParse(value: unknown):
    | Readonly<{ success: true; data: T }>
    | Readonly<{
        success: false;
        issues: readonly RuntimeValidationIssue[];
      }>;
}

export interface RuntimeValidationIssue {
  readonly path: readonly (string | number)[];
  readonly code: string;
}

규칙:

  • external response/event는 항상 unknown에서 시작한다;
  • generated interface와 as T는 validator를 대체하지 않는다;
  • validator throw는 CONTRACT_RUNTIME_FAILURE다;
  • ordinary mismatch는 CONTRACT_VALUE_INVALID다;
  • raw value, server detail, validator internals를 diagnostics에 넣지 않는다;
  • 성공 값은 mapper 전에 caller-owned mutable reference로 재사용하지 않는다.

4.7 mapper ownership

wire DTO
  -> feature contract mapper
  -> domain factory/application value
  -> Query cache/UI

mapper MUST:

  • feature가 소유한다;
  • no-throw discriminated result를 반환한다;
  • date/number/enum/null을 application canonical value로 변환한다;
  • generated DTO를 Query cache에 직접 넣지 않는다;
  • package metadata/header를 application value에 포함하지 않는다;
  • collection count와 field bound를 mapper 단계에서도 확인한다.
export type MappingResult<T> =
  | Readonly<{ ok: true; value: T }>
  | Readonly<{
      ok: false;
      error: Readonly<{ kind: "MAPPING_CONTRACT_VIOLATION"; code: string }>;
    }>;

4.8 installed contribution composer

src/features/installed-contract-contributions.ts
export const INSTALLED_CONTRACT_CONTRIBUTIONS = Object.freeze([
  REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION,
] as const);

reference fixture 규칙:

featureId   = reference-feature
source.kind = TEMPLATE_FIXTURE
fixtureId   = REFERENCE_FEATURE_V1
revision    = 1
events      = []

TEMPLATE_FIXTURE는 current reference HTTP vertical을 deterministic test data로 유지하기 위한 유일한 예외다. product feature는 반드시 EXTERNAL_PACKAGE다. fixture는 contractSet에 포함하지 않는다.

4.9 composer exact validation

composition 전에 다음을 모두 검사한다.

featureId:
  ^[a-z][a-z0-9-]{0,63}$

packageId:
  ^@[a-z0-9][a-z0-9._-]{0,62}/[a-z0-9][a-z0-9._-]{0,62}$

version:
  exact SemVer
  leading v 금지
  range/tag/whitespace 금지

digest:
  ^sha256:[0-9a-f]{64}$

sourceRevision:
  ^[0-9a-f]{7,64}$

operationId:
  ^[A-Za-z][A-Za-z0-9_.-]{0,127}$

eventType:
  UTF-8 <= 160 bytes
  whitespace/control character 금지

pathTemplate:
  startsWith /
  UTF-8 <= 512 bytes
  query/hash 금지

추가 검증:

  • external runtime protocol version은 정확히 1;
  • package ID별 정확히 한 version/digest;
  • duplicate operation ID 없음;
  • duplicate event type 없음;
  • status는 100..599, 1..32개, sorted unique;
  • empty-body status는 accepted status의 subset;
  • validator와 non-empty schemaId 필수;
  • request/response limit은 §7 hard ceiling 이하;
  • same operation method/path/status projection drift 없음;
  • fixture 외 local product contract 없음.

package identity 검증은 EXTERNAL_PACKAGE에만 적용한다. fixture에 가짜 digest를 부여하지 않는다.

4.10 package dependency 규칙

  • package.json은 exact version만 사용한다;
  • latest, caret, tilde, tag, Git URL, workspace link 금지;
  • lockfile resolved integrity와 package export manifest가 존재해야 한다;
  • runtime registry/Git/deployed docs에서 package를 보충하지 않는다;
  • package root barrel 전체 import 금지;
  • 사용 operation/event subpath만 정적 import한다;
  • package import는 top-level network/DOM/listener side effect가 없어야 한다;
  • package source와 generated output을 frontend에서 수정하지 않는다.

4.11 reference feature에서 product feature로의 migration

금지:

copy reference-schemas.ts
edit URL/status/type
ship as product local contract

필수:

external package pin
-> feature contribution adapter
-> feature mapper
-> feature gateway
-> application input
-> query/mutation/realtime bridge

reference fixture는 template 자체 제거 가능성 검증용이며 서비스 계약 authoring template가 아니다.


5. Contract Set과 release coherence

5.1 scalar 계약 version 제거

현재 다음 scalar는 여러 package를 표현할 수 없다.

RuntimeConfig.API_CONTRACT_VERSION
ReleaseManifest.apiContractVersion

V2 전환 결과:

Runtime Config
  = deployment/browser operational setting

Release Manifest
  = build/release/assets/compiled external contract package set

RuntimeConfig에서 API contract version을 제거한다.

5.2 Release Manifest V2

export interface ReleaseManifestV2 {
  readonly schemaVersion: 2;
  readonly appVersion: string;
  readonly buildId: string;
  readonly commitSha: string;
  readonly configSchemaVersion: string;
  readonly assetManifestHash: string;
  readonly releaseId: string;
  readonly builtAt: string;
  readonly routeChunks: Readonly<Record<string, string>>;
  readonly contractSet: Readonly<{
    setAlgorithm: "CA_CONTRACT_SET_V1";
    setDigest: `sha256:${string}`;
    packages: readonly ContractSetPackage[];
  }>;
}

export interface ContractSetPackage {
  readonly packageId: string;
  readonly version: string;
  readonly digest: `sha256:${string}`;
  readonly runtimeProtocolVersion: 1;
  readonly sourceRevision: string;
}

fixture contribution은 packages에 들어가지 않는다.

5.3 canonicalization

단일 구현:

src/contracts/contract-set-canonical.ts
export function canonicalizeContractSet(
  packages: readonly ContractSetPackage[],
): Uint8Array;

algorithm:

header = UTF-8("CA_FRONTEND_CONTRACT_SET_V1\0")
entries = packages sorted by UTF-8 byte lexicographic packageId
for each entry:
  append u32be(length(packageId)) + packageId bytes
  append u32be(length(version)) + version bytes
  append u32be(length(digest)) + digest bytes
  append u32be(runtimeProtocolVersion)
  append u32be(length(sourceRevision)) + sourceRevision bytes
setDigest = lower-hex SHA-256(canonical bytes)

중복 package ID는 hash 전에 실패한다. JSON property order나 locale sort에 의존하지 않는다.

Node build script와 browser runtime은 canonical byte 생성 함수를 공유한다. hash adapter만 Node crypto와 Web Crypto로 분리한다.

5.4 build-time expected set

INSTALLED_CONTRACT_CONTRIBUTIONS
  -> EXTERNAL_PACKAGE identities only
  -> canonicalize
  -> setDigest
  -> build manifest embed

TEMPLATE_FIXTURE는 filtering 후 set에 포함하지 않는다.

5.5 runtime check

boot에서 다음을 비교한다.

embedded expected package list
manifest package list
embedded expected setDigest
manifest setDigest
recomputed manifest setDigest
runtime protocol version

모두 일치해야 한다. package range negotiation, deployed server docs와 latest lookup을 하지 않는다.

5.6 failure code

CONTRACT_SET_SCHEMA_INVALID
CONTRACT_SET_ENTRY_INVALID
CONTRACT_SET_DUPLICATE_PACKAGE
CONTRACT_SET_DIGEST_INVALID
CONTRACT_SET_DIGEST_MISMATCH
CONTRACT_SET_PACKAGE_MISSING
CONTRACT_SET_PACKAGE_UNEXPECTED
CONTRACT_SET_VERSION_MISMATCH
CONTRACT_RUNTIME_PROTOCOL_UNSUPPORTED

이 failure는 product route mount 전 safe boot error다.

5.7 independent deployment 의미

frontend manifest는 frontend build에 compile된 package set을 검증한다. provider runtime version을 추측하거나 provider와 exact equality를 요구하지 않는다.

provider compatibility와 server support range는 별도 계약 시스템의 release responsibility다. frontend runtime은 build package set과 자신의 artifact coherence만 검증한다.

5.8 V1 migration

Release 1
  reader: V1 + V2
  writer: V1
  API_CONTRACT_VERSION 유지

Release 2
  reader: V1 + V2
  writer: V2
  contractSet 생성
  V1 input parser만 scalar를 읽음
  V2 input에 scalar가 있으면 strict reject

Release 3
  reader: V2
  writer: V2
  RuntimeConfig scalar reject as unknown field
  V1 fixtures 제거

V1과 V2 값을 동시에 비교해 precedence를 만들지 않는다. V2 manifest에서 scalar가 추가로 존재하면 strict parser가 거절한다.


6. Runtime Config와 boot

6.1 Runtime Config V2

export const runtimeConfigV2Schema = z
  .object({
    APP_ENV: z.enum(["local", "development", "staging", "production"]),
    API_BASE_URL: z.url(),
    REQUEST_TIMEOUT_MS: z.int().min(100).max(60_000).default(10_000),
    MAX_RETRY_ATTEMPTS: z.int().min(0).max(2).default(2),
    TELEMETRY_ENABLED: z.boolean(),
    TELEMETRY_ENDPOINT: z.url().optional(),
    AUTH_MODE: z.enum(["external", "demo"]),
    CONFIG_SCHEMA_VERSION: z.literal("2.0"),
    RELEASE_MANIFEST_URL: z.string().min(1).default("/release-manifest.json"),
    RELEASE_ID: z.string().min(1).optional(),
    BUILD_ID: z.string().min(1).optional(),
    CAPABILITY_OVERRIDES: z
      .object({
        REALTIME: z.enum(["DEFAULT", "DISABLED"]).default("DEFAULT"),
        WEB_WORKER: z.enum(["DEFAULT", "DISABLED"]).default("DEFAULT"),
        SERVICE_WORKER: z.enum(["DEFAULT", "DISABLED"]).default("DEFAULT"),
        OFFLINE_COMMANDS: z.enum(["DEFAULT", "DISABLED"]).default("DEFAULT"),
      })
      .strict()
      .default({}),
  })
  .strict();

API_CONTRACT_VERSION은 V2에 없다.

6.2 URL validation

API_BASE_URL:

  • absolute URL;
  • username/password/hash 없음;
  • pathname은 /로 끝남;
  • local/development 외 https:;
  • query는 없음;
  • canonical new URL(value).href를 runtime snapshot에 저장.

TELEMETRY_ENDPOINT:

  • enabled일 때 필수;
  • absolute URL;
  • username/password/hash 없음;
  • local/development 외 https:;
  • API base와 같을 필요 없음.

RELEASE_MANIFEST_URL:

  • same-origin absolute path;
  • /로 시작;
  • query/hash 없음;
  • //, backslash, percent-encoded slash, dot segment 거절;
  • maximum UTF-8 256 bytes.

6.3 config secret-name scan

기존 assertSafeConfigNames를 유지하고 다음 key fragment를 case-insensitive로 거절한다.

PASSWORD
SECRET
TOKEN
PRIVATE_KEY
CLIENT_SECRET
ACCESS_KEY
REFRESH_TOKEN
COOKIE
AUTHORIZATION

AUTH_MODE, TELEMETRY_ENABLED처럼 fragment가 semantic enum인 known allowlist는 exact key allowlist로만 허용한다. arbitrary nested key에는 allowlist를 적용하지 않는다.

6.4 bounded boot body reader

공통 source:

src/bootstrap/read-bounded-boot-json.ts
export interface BootJsonPolicy {
  readonly operation: "RUNTIME_CONFIG" | "RELEASE_MANIFEST";
  readonly maximumBytes: number;
  readonly totalDeadlineMs: 5_000;
}

numeric policy:

Runtime Config maximum = 65,536 bytes
Release Manifest maximum = 1,048,576 bytes
total deadline = 5,000 ms each

request options:

{
  method: "GET",
  cache: "no-store",
  credentials: "same-origin",
  redirect: "error",
  referrerPolicy: "no-referrer",
  headers: { Accept: "application/json" },
  signal,
}

6.5 response admission

순서:

fetch settled
-> status 200 only
-> content-type parse
-> application/json or +json only
-> Content-Length precheck when finite
-> ReadableStream bounded read
-> actual bytes <= policy maximum
-> UTF-8 fatal decode
-> JSON.parse
-> object shape check
-> Zod strict parse
-> deep snapshot

금지:

  • response.json();
  • unbounded response.text();
  • redirect follow;
  • HTML error page를 JSON으로 parse;
  • Content-Length만 신뢰;
  • timeout 후 body read continuation.

body overflow 시 reader를 cancel하고 AbortController를 abort한다.

6.6 boot failure union

export type BootLoadFailure =
  | "FETCH_FAILED"
  | "TIMEOUT"
  | "HTTP_STATUS_INVALID"
  | "CONTENT_TYPE_INVALID"
  | "BODY_TOO_LARGE"
  | "UTF8_INVALID"
  | "JSON_INVALID"
  | "SHAPE_INVALID"
  | "SECRET_NAME_REJECTED"
  | "SCHEMA_INVALID"
  | "BUILD_MISMATCH"
  | "RELEASE_MISMATCH"
  | "ASSET_MISMATCH"
  | "CONTRACT_SET_MISMATCH";

safe error는 다음만 포함한다.

failure kind/code
buildId
releaseId if already validated
config schema version if safe
support reference = buildId + bounded failure code

URL, response body, validation value, endpoint hostname, stack trace를 포함하지 않는다.

6.7 boot order

1. read embedded build config
2. load/validate Runtime Config V2
3. compare optional BUILD_ID
4. load/validate Release Manifest V2
5. verify build/config/release/assets
6. verify contractSet
7. compile static optional selection + runtime overrides
8. create diagnostics/telemetry no-op or selected sink
9. create auth/session adapter
10. create HTTP runtime
11. create application inputs/gateways
12. create QueryClient/scope/invalidation
13. create Router
14. create optional host objects without start side effect
15. mount React tree
16. committed effect starts optional runtime

6.8 partial composition rollback

각 step은 cleanup stack에 disposer를 push한다.

create A -> push dispose A
create B -> push dispose B
create C fails
-> dispose B
-> dispose A
-> render safe boot error

cleanup은 reverse order이며 하나의 cleanup failure가 다음 cleanup을 막지 않는다. 모든 cleanup failure를 bounded count와 kind로 합산한다.

6.9 boot retry

safe boot shell의 retry action:

  • 사용자의 explicit action만;
  • 동일 page session에서 maximum 1회;
  • 이전 attempt의 controller/resource settle 후 실행;
  • 1초 minimum interval;
  • retry 실패 후 automatic loop 없음;
  • retry success 시 full product root를 새로 mount;
  • partial composition object 재사용 금지.

6.10 Runtime Config immutability

validated config는 deep snapshot/freeze한다.

  • runtime 중 config endpoint polling 없음;
  • dynamic mutation 없음;
  • kill switch 변경은 page reload/new boot에서만 적용;
  • config object를 context로 노출하지 않음;
  • application은 typed port를 통해 필요한 value만 본다.

7. Fetch HTTP execution runtime

7.1 source ownership

유지/수정:

src/adapters/http/client.ts
src/adapters/http/request-builder.ts
src/adapters/http/bounded-json.ts
src/adapters/http/retry-policy.ts

신규:

src/adapters/http/bounded-body-reader.ts
src/adapters/http/http-execution-v3.ts
src/adapters/http/http-effect-certainty.ts
src/adapters/http/http-contract-bridge.ts

범용 Axios-like API를 만들지 않는다. caller는 InstalledHttpContract와 typed input을 전달한다.

7.2 execution interface

export interface ContractHttpExecutor {
  execute<Input, WireOutput, Problem>(
    operation: InstalledHttpContract<Input, WireOutput, Problem>,
    input: Input,
    context: HttpExecutionContext,
  ): Promise<HttpExecutionOutcome<WireOutput, Problem>>;
}

export interface HttpExecutionContext {
  readonly signal?: AbortSignal;
  readonly scope: CacheScopeSnapshot;
  readonly intent?: MutationIntentContext;
}

MutationIntentContext는 command에만 사용한다.

export interface MutationIntentContext {
  readonly intentId: string;
  readonly idempotencyKey?: string;
  readonly startedBy: "USER" | "FOREGROUND_RETRY" | "OUTBOX_REPLAY";
}

7.3 hard ceilings

default request body       262,144 bytes
hard request body        1,048,576 bytes
default success response 1,048,576 bytes
hard response body       8,388,608 bytes
problem response maximum    65,536 bytes
path template maximum           512 bytes
encoded query maximum          8,192 bytes
header value examined maximum  8,192 bytes per allowlisted header
total deadline default        10,000 ms
total deadline hard           60,000 ms
retry count hard                   2

external contribution의 frontend policy는 default보다 작거나 같게 줄일 수 있다. hard ceiling을 초과할 수 없다.

7.4 input validation과 request projection

순서:

capture scope
-> verify scope current
-> package input validator
-> feature/application input already canonical
-> request projection
-> encoded path/query/body byte check
-> credential patch
-> final invariant recheck
-> dispatch

final invariant recheck 항목:

  • method unchanged;
  • URL origin/path allowlist;
  • redirect mode error;
  • credentials mode expected;
  • credential patch header allowlist;
  • forbidden header 없음;
  • body/request byte bound;
  • abort/deadline not expired;
  • scope still current.

final invariant failure면 fetch 호출은 0회다.

7.5 URL construction

  • base는 validated API_BASE_URL;
  • path placeholder는 descriptor의 named input projection만 사용;
  • segment는 encodeURIComponent semantics로 한 번만 encode;
  • raw slash를 path value에 허용하지 않음;
  • query key order는 descriptor-generated order;
  • array encoding은 package descriptor가 제공한 exact projection;
  • undefined omit, null은 descriptor가 허용한 representation만;
  • URL string concatenation 금지;
  • absolute path override, origin override 금지;
  • final URL maximum 16,384 bytes.

7.6 request body

requestBody=NONE이면 body와 Content-Type 모두 없다.

requestBody=JSON이면:

package request projection
-> deterministic JSON encoder
-> UTF-8 bytes
-> requestByteLimit check
-> Content-Type application/json

JSON.stringify의 default property enumeration에 semantic digest를 의존하지 않는다. idempotency request digest가 필요하면 package가 canonical request digest projection을 제공한다.

7.7 credential bridge

export type CredentialPatchOutcome =
  | Readonly<{
      kind: "READY";
      headers: Readonly<Record<string, string>>;
      credentials: RequestCredentials;
    }>
  | Readonly<{ kind: "UNAUTHENTICATED" }>
  | Readonly<{ kind: "UNAVAILABLE" }>
  | Readonly<{ kind: "SCOPE_FENCED" }>;

허용 header:

Authorization
X-CSRF-Token
X-Tenant-Context

실제 profile이 쓰는 subset만 등록한다. caller가 arbitrary header를 추가하지 않는다.

credential unavailable에서 anonymous request로 downgrade하지 않는다.

7.8 physical attempt state

export type PhysicalAttemptState =
  | "PREPARING"
  | "READY_TO_SEND"
  | "DISPATCHED"
  | "RESPONSE_HEADERS"
  | "READING_BODY"
  | "VALIDATING"
  | "MAPPING_READY"
  | "SETTLED";

fetch() invocation 직전에 DISPATCHED로 바꾸지 않는다. invocation expression에 들어가기 직전 local state를 READY_TO_SEND, promise가 반환된 직후 DISPATCHED로 기록한다. synchronous throw는 NOT_STARTED다.

7.9 response admission

순서:

redirected=false
-> status package allowlist
-> content-type policy
-> Content-Length precheck
-> bounded stream read
-> empty-body policy
-> UTF-8/JSON parse when required
-> problem or success validator
-> scope current fence
-> typed outcome

response.ok만 사용하지 않는다.

7.10 body policy

REQUIRED_JSON:

  • allowed success status에서 non-empty body 필수;
  • JSON media 필수;
  • empty/whitespace는 contract violation.

OPTIONAL_JSON:

  • empty-body status 또는 zero body 허용;
  • body가 있으면 JSON media/validator 필수;
  • body 없는 값을 undefined로 명시적으로 mapping.

NONE:

  • body를 application value로 읽지 않음;
  • Content-Length > 0이면 즉시 violation + body cancel;
  • length가 없고 body stream이 있으면 최대 1 byte만 probe한다;
  • 첫 chunk가 있으면 violation + reader cancel;
  • immediately done이면 empty success;
  • descriptor가 unexpected body를 허용하지 않으므로 arbitrary drain하지 않는다.

7.11 typed outcome

export type HttpExecutionOutcome<Value, Problem> =
  | Readonly<{
      kind: "SUCCESS";
      value: Value;
      metadata: SafeResponseMetadata;
      effect: "NOT_APPLICABLE" | "APPLIED_CONFIRMED";
    }>
  | Readonly<{
      kind: "PROBLEM";
      problem: Problem;
      metadata: SafeResponseMetadata;
      effect: "NOT_APPLIED" | "APPLIED_CONFIRMED" | "MAYBE_APPLIED";
    }>
  | Readonly<{
      kind: "UNAUTHENTICATED";
      effect: "NOT_APPLICABLE" | "NOT_APPLIED" | "MAYBE_APPLIED";
    }>
  | Readonly<{
      kind: "FORBIDDEN";
      effect: "NOT_APPLICABLE" | "NOT_APPLIED" | "MAYBE_APPLIED";
    }>
  | Readonly<{
      kind: "RATE_LIMITED";
      retryAfterMs?: number;
      effect: "NOT_APPLICABLE" | "NOT_APPLIED" | "MAYBE_APPLIED";
    }>
  | Readonly<{
      kind: "CONTRACT_VIOLATION";
      violation: HttpContractViolation;
    }>
  | Readonly<{
      kind: "TRANSPORT_FAILURE";
      failure: HttpTransportFailure;
      effect: "NOT_STARTED" | "MAYBE_APPLIED";
    }>
  | Readonly<{
      kind: "CANCELLED";
      effect: "NOT_STARTED" | "MAYBE_APPLIED";
    }>;

7.12 safe response metadata

export interface SafeResponseMetadata {
  readonly status: number;
  readonly correlationReference?: string;
  readonly retryAfterMs?: number;
  readonly validatorToken?: string;
}

metadata에 raw header map, URL, server, cookie, traceparent, ETag raw value를 노출하지 않는다. validator token은 conditional validator store 내부 opaque binding이며 UI/application에 직접 전달하지 않는다.

7.13 contract violation

UNEXPECTED_STATUS
UNEXPECTED_EMPTY_BODY
UNEXPECTED_BODY
CONTENT_TYPE_MISMATCH
RESPONSE_TOO_LARGE
UTF8_INVALID
JSON_INVALID
SUCCESS_SCHEMA_INVALID
PROBLEM_SCHEMA_INVALID
VALIDATOR_RUNTIME_FAILURE
MAPPING_CONTRACT_VIOLATION
FINAL_REQUEST_INVARIANT_FAILED
SCOPE_FENCED
export type HttpContractViolation = Readonly<{
  kind:
    | "UNEXPECTED_STATUS"
    | "UNEXPECTED_EMPTY_BODY"
    | "UNEXPECTED_BODY"
    | "CONTENT_TYPE_MISMATCH"
    | "RESPONSE_TOO_LARGE"
    | "UTF8_INVALID"
    | "JSON_INVALID"
    | "SUCCESS_SCHEMA_INVALID"
    | "PROBLEM_SCHEMA_INVALID"
    | "VALIDATOR_RUNTIME_FAILURE"
    | "MAPPING_CONTRACT_VIOLATION"
    | "FINAL_REQUEST_INVARIANT_FAILED"
    | "SCOPE_FENCED";
  operation: "REQUEST" | "RESPONSE" | "VALIDATION" | "MAPPING";
}>;

contract violation은 ordinary network retry 대상이 아니다.

7.14 transport failure

DNS_OR_CONNECT_FAILURE
TLS_OR_NETWORK_FAILURE
TIMEOUT
RESPONSE_STREAM_FAILURE
ABORTED_BY_SCOPE
ABORTED_BY_CALLER
OVERLOADED_BEFORE_SEND
export type HttpTransportFailure = Readonly<{
  kind:
    | "DNS_OR_CONNECT_FAILURE"
    | "TLS_OR_NETWORK_FAILURE"
    | "NETWORK_FAILURE"
    | "TIMEOUT"
    | "RESPONSE_STREAM_FAILURE"
    | "ABORTED_BY_SCOPE"
    | "ABORTED_BY_CALLER"
    | "OVERLOADED_BEFORE_SEND";
  retryable: boolean;
}>;

browser가 세부 네트워크 원인을 제공하지 않으면 하나의 NETWORK_FAILURE bucket으로 축약한다. native exception message를 boundary 밖으로 내보내지 않는다.


8. Retry·cancellation·mutation effect certainty

8.1 retry owner

HTTP transport retry = enabled by exact operation policy
TanStack Query retry = false
TanStack Mutation retry = false
realtime reconnect = realtime coordinator
Background Sync retry = offline drain state machine

서로 다른 계층이 같은 physical operation을 중복 retry하지 않는다.

8.2 HTTP retry algorithm

base delay = 250 ms
max local delay = 2,000 ms
algorithm = full jitter
maximum retries = min(operation.retryBudget, RuntimeConfig.MAX_RETRY_ATTEMPTS, 2)

retry index 0의 ceiling:

min(2,000, 250 * 2^0) = 250 ms

실제 delay:

floor(random[0,1) * ceiling)

validated Retry-After가 있으면 local delay의 lower bound로 사용한다. 5,000ms를 넘거나 remaining total deadline을 넘으면 retry하지 않는다.

8.3 retry classification

SAFE:

  • GET/HEAD;
  • network failure after dispatch 허용;
  • 408, 425, 429, 502, 503, 504 중 accepted retry policy;
  • same frozen request 사용.

IDEMPOTENT:

  • package가 operation 자체 idempotent라고 선언;
  • same frozen request;
  • SAFE와 같은 failure/status subset.

KEYED:

  • same user intent의 동일 idempotency key 필수;
  • known response status가 retry 가능하다고 package가 명시한 경우만;
  • dispatch 후 response 없는 network/timeout/abort는 automatic retry 금지;
  • inspect/reconciliation path로 이동.

NEVER:

  • automatic retry 0.

8.4 auth retry

401 response 뒤 hidden refresh-and-replay를 하지 않는다.

  • credential owner는 send 전 READY/UNAUTHENTICATED/UNAVAILABLE을 결정한다;
  • response 401은 terminal UNAUTHENTICATED;
  • 사용자 re-auth 뒤 새 user intent 또는 explicit retry로 실행;
  • command를 새 credential로 자동 replay하지 않는다.

8.5 deadline

one logical operation deadline은 다음 전체를 포함한다.

credential resolution
request encode
retry backoff
all physical attempts
response read
runtime validation
mapper preparation

operation start에서 monotonic deadline을 capture한다. 각 sub-step이 remaining budget을 받는다. physical attempt별로 full timeout을 새로 부여하지 않는다.

8.6 cancellation sources

export type CancellationOwner =
  | "CALLER"
  | "ROUTE_TRANSITION"
  | "SCOPE_FENCE"
  | "APPLICATION_SHUTDOWN"
  | "DEADLINE";

linked AbortController는 first reason만 terminal reason으로 저장한다. later reason은 diagnostics count에만 반영한다.

8.7 mutation effect certainty

export type MutationEffectCertainty =
  | "NOT_STARTED"
  | "NOT_APPLIED"
  | "MAYBE_APPLIED"
  | "APPLIED_CONFIRMED";

판정:

관측 certainty
validation/config/auth/final invariant에서 종료 NOT_STARTED
fetch synchronous throw NOT_STARTED
package가 no-effect problem/status로 명시 NOT_APPLIED
success/committed receipt APPLIED_CONFIRMED
dispatch 후 timeout/network/abort/body loss MAYBE_APPLIED
unknown/unclassified problem MAYBE_APPLIED

frontend가 HTTP status만 보고 NOT_APPLIED를 추측하지 않는다.

8.8 mutation intent identity

export interface MutationIntent {
  readonly intentId: string;
  readonly operationId: string;
  readonly canonicalInputIdentity: string;
  readonly idempotencyKey?: string;
  readonly createdAtMonotonicMs: number;
}
  • intentId: crypto.randomUUID();
  • 같은 submit의 transport retry/reconcile에는 동일 intent/key;
  • form value가 변경되면 새 intent;
  • double click은 duplicate coordinator가 처리;
  • key/identity를 log/URL/query key에 넣지 않는다.

8.9 uncertain outcome handling

MAYBE_APPLIED면:

1. automatic resend 금지
2. optimistic layer를 arbitrary rollback하지 않음
3. mutation coordinator state = EFFECT_UNKNOWN
4. package recovery descriptor 확인
5. inspect operation이 있으면 bounded inspect
6. committed -> canonical result 적용
7. no effect -> explicit user retry 허용
8. unknown 유지 -> status UI/support path

inspect도 timeout이면 unknown을 유지한다.

8.10 UI projection

NOT_STARTED      -> ordinary retry 가능
NOT_APPLIED      -> policy상 retry 가능
MAYBE_APPLIED    -> "처리 상태 확인 필요"
APPLIED_CONFIRMED-> success

사용자 문구는 i18n catalog가 소유한다. native error와 server detail을 표시하지 않는다.


9. Router

9.1 ownership

Router owns:

route registry
path/search codec
navigation
redirect
lazy module lifecycle
route error boundary
focus/scroll/title
dirty navigation guard
session/access hint guard

Router does not own:

server data cache
HTTP retry
mutation lifecycle
realtime connection
business authorization
contract package loading

9.2 server-data loader/action 금지

baseline에서 loader/action은 server fetch를 수행하지 않는다.

허용 loader:

  • URL/path/search parse;
  • local route registration lookup;
  • synchronous redirect;
  • session snapshot 기반 access hint;
  • route-local static metadata.

금지:

  • direct fetch;
  • gateway call;
  • QueryClient prefetch/ensure;
  • IndexedDB read;
  • mutation action;
  • separate loader cache.

서버 data는 page의 feature query/mutation hook이 application input을 호출해 가져온다.

9.3 route registration

export interface InstalledRouteContribution {
  readonly routeId: string;
  readonly path: string;
  readonly ownerFeatureId: string;
  readonly load: () => Promise<RouteRuntimeModule>;
  readonly inputCodecId: string;
  readonly accessPolicyId: string;
}
  • route ID: existing registry format 유지;
  • duplicate route ID/path 거절;
  • feature page가 app-router를 import하지 않음;
  • lazy module은 route runtime module만 export;
  • Router가 feature gateway/adapter를 props로 전달하지 않음.

9.4 URL codec

path/search raw input은 unknown이다.

URLSearchParams
-> registered route codec
-> normalized route input
-> RouteInputProvider
-> page/controller
  • Object.fromEntries(searchParams) raw trust 금지;
  • duplicate query key policy를 codec이 명시;
  • unknown key는 default reject;
  • array/count/string byte bound;
  • route param은 domain ID factory 전까지 string;
  • malformed input은 safe bad-request route surface;
  • network/application gateway call 0.

9.5 route transition cleanup

feature page unmount 순서:

1. Query observer release
2. feature realtime subscription lease release
3. page browser listener cleanup
4. form/dirty guard cleanup
5. object URL/temporary file lease cleanup
6. page-owned abort controller abort

다음 global runtime은 유지한다.

QueryClient
scope runtime
cross-context coordinator
realtime host
Service Worker registration
offline repository

9.6 chunk recovery

chunk load failure
-> no-store Release Manifest V2 refresh
-> build/release/asset/contractSet compare
-> dirty state inspect
-> one guarded reload
-> second failure terminal safe surface

reload guard key:

CHUNK_RELOAD_GUARD = oldBuildId + newBuildId + failedRouteId

동일 pair/route당 한 번만 reload한다. localStorage record는 10분 TTL과 512-byte hard cap을 갖는다.

9.7 focus/scroll/title

  • successful route commit 후 main-content focus;
  • navigation마다 scroll top;
  • explicit registered hash-anchor route만 anchor focus;
  • document title은 typed message catalog;
  • background Query refresh는 focus/scroll을 변경하지 않음;
  • reduced motion 존중;
  • error route도 heading focus를 제공.

9.8 navigation blocker

blocking 조건:

dirty form
active non-cancellable local file workflow
mutation EFFECT_UNKNOWN recovery in progress
offline command conflict resolution draft

ordinary loading/query refresh는 navigation을 막지 않는다.

browser unload dialog는 browser standard prompt만 사용한다. custom string에 의존하지 않는다.


10. Query lifecycle과 cache

10.1 QueryClient 수와 기본 정책

application runtime당 QueryClient 하나다.

export const QUERY_CACHE_DEFAULTS = Object.freeze({
  staleTime: 30_000,
  gcTime: 300_000,
  refetchOnWindowFocus: true,
  retry: false,
  mutationRetry: false,
  persistence: false,
});

route/feature/session마다 새 QueryClient를 만들지 않는다.

10.2 fixed server-state profiles

export type ServerStateProfileId =
  | "DETAIL_STANDARD"
  | "LIST_STANDARD"
  | "LOOKUP_STABLE"
  | "VOLATILE_STATUS";

export interface ServerStateProfile {
  readonly profileId: ServerStateProfileId;
  readonly staleTimeMs: number;
  readonly gcTimeMs: number;
  readonly refetchOnWindowFocus: boolean;
  readonly maxResultItems: number;
  readonly maxEstimatedResultBytes: number;
}
DETAIL_STANDARD
  stale 30s, gc 5m, focus true, items 1, bytes 256KiB

LIST_STANDARD
  stale 15s, gc 5m, focus true, items 200, bytes 1MiB

LOOKUP_STABLE
  stale 5m, gc 30m, focus false, items 500, bytes 2MiB

VOLATILE_STATUS
  stale 0, gc 1m, focus true, items 1, bytes 64KiB

feature가 임의 숫자를 선언하지 않는다. 네 profile로 표현할 수 없으면 본 문서와 profile registry를 개정한다.

10.3 query definition

export interface QueryDefinition<Input, Value> {
  readonly definitionId: string;
  readonly definitionVersion: number;
  readonly owner: string;
  readonly namespace: string;
  readonly namespaceVersion: number;
  readonly operationId: string;
  readonly profileId: ServerStateProfileId;
  measureResult(value: Value): QueryResultMeasure;
  execute(
    input: Input,
    context: Readonly<{ signal: AbortSignal }>,
  ): Promise<Result<Value>>;
}

export interface QueryResultMeasure {
  readonly itemCount: number;
  readonly estimatedBytes: number;
}

10.4 result measurement

measureResult는 모든 definition에 필수다.

  • detail/scalar: item 1;
  • empty optional: item 0;
  • list/page: actual mapped items;
  • bytes: mapped application value의 bounded string UTF-8 bytes + primitive fixed width + collection overhead;
  • JSON.stringify fallback 금지;
  • wire DTO 재직렬화 금지;
  • generic recursive walker 금지;
  • throw/non-finite/negative/unsafe integer는 RESULT_MEASUREMENT_FAILED;
  • ceiling 초과는 RESULT_BUDGET_EXCEEDED;
  • measurement failure/overflow는 cache commit 0, retry 0.

10.5 client scope authority

현재 ServerStateScopeRuntime을 browser client scope authority로 확장한다.

export type ClientScopeLifecycleEvent =
  | Readonly<{ kind: "FENCED"; previousGeneration: number }>
  | Readonly<{ kind: "READY"; snapshot: CacheScopeSnapshot }>
  | Readonly<{ kind: "DISPOSED" }>;

export interface ServerStateScopeRuntime {
  getSnapshot(): CacheScopeSnapshot;
  subscribe(listener: () => void): () => void;
  subscribeLifecycle(
    listener: (event: ClientScopeLifecycleEvent) => void,
  ): () => void;
  dispose(): void;
}

10.6 scope transition exact order

identity-affecting session change:

1. generation increment
2. old snapshot isCurrent=false
3. FENCED synchronously publish
4. new HTTP/realtime/offline admission close
5. active Query cancel
6. QueryClient clear
7. conditional validator clear
8. mutation/invalidation pending lease clear
9. realtime stop/checkpoint detach
10. offline drain lease release and old partition hide
11. old identity registry close
12. new fingerprint/identity registry create
13. READY publish
14. React subscriber notify
15. new scope admission open

FENCEDREADY 사이 old value를 render하지 않는다. UI는 scope-transition state를 사용한다.

10.7 query key

[
  "query",
  1,
  scopeFingerprint,
  namespace,
  namespaceVersion,
  definitionVersion,
  runtimeIdentityToken
]

raw account/resource ID, URL, filter object, document, cursor, message를 key에 넣지 않는다.

runtime identity registry bounds:

max depth          12
max nodes          512
max string bytes   2,048
max encoded bytes 16,384
entries          4,096
canonical bytes   4 MiB

active reference가 있는 identity를 evict하지 않는다. capacity 부족은 network 전 local failure다.

10.8 execution commit fence

capture scope
-> acquire identity lease
-> execute application input with TanStack signal
-> scope current check
-> Result success check
-> measureResult
-> profile budget check
-> immutable value return
-> Query cache commit
-> finally identity release

scope가 stale면 successful response도 SCOPE_FENCED로 폐기한다.

10.9 UI states

initial-loading
success-fresh
success-refreshing
success-stale-degraded
empty
known-problem
transport-error
contract-error
scope-transition

background refresh에서 complete data를 full-page skeleton으로 숨기지 않는다.

10.10 persistence

baseline:

memory QueryClient = COMPOSED
query persistence = disabled
SSR hydration = NOT_SELECTED

Web Storage나 existing generic IndexedDB runtime에 Query payload를 자동 저장하지 않는다. persistence 요구는 VD-13 별도 selection을 통과해야 한다.

10.11 conditional validator

conditional validator store는 Query value와 분리한다.

  • exact operation/representation/scope/release binding;
  • max entries 1,024;
  • max token bytes 1,024;
  • LRU zero-ref eviction;
  • session transition clear;
  • 304는 current complete cached value가 있을 때만 success;
  • cache miss + 304는 contract/recovery failure;
  • raw ETag를 Query key/UI/log에 넣지 않음.

11. Mutation lifecycle

11.1 mutation definition

export interface MutationDefinition<Input, Value> {
  readonly definitionId: string;
  readonly operationId: string;
  readonly duplicatePolicy:
    | "JOIN_IDENTICAL"
    | "REJECT_WHILE_ACTIVE"
    | "ALLOW_PARALLEL";
  readonly optimisticPolicyId: "NONE" | string;
  readonly invalidationTopics: readonly string[];
  canonicalIdentity(input: Input): string;
  execute(
    input: Input,
    context: Readonly<{
      signal: AbortSignal;
      intent: MutationIntent;
    }>,
  ): Promise<MutationResult<Value>>;
}

export type MutationResult<Value> =
  | Readonly<{
      ok: true;
      value: Value;
      effect: "APPLIED_CONFIRMED";
    }>
  | Readonly<{
      ok: false;
      failure: AppFailure;
      effect: "NOT_STARTED" | "NOT_APPLIED" | "MAYBE_APPLIED";
    }>;

11.2 duplicate policy defaults

command default = REJECT_WHILE_ACTIVE
query-like idempotent action = JOIN_IDENTICAL only when exact semantic input identity is stable
ALLOW_PARALLEL = explicit feature decision only

JOIN_IDENTICAL은 같은 scope, definition, canonical input과 same user intent에서만 가능하다. 서로 다른 input을 logical key 하나로 합치지 않는다.

11.3 duplicate coordinator bounds

active definitions per runtime = 256
active intents total           = 1,024
canonical identity bytes       = 16,384
waiters per joined intent      = 32
settled retention              = 0 ms; immediate removal

coordinator map을 result cache로 사용하지 않는다.

11.4 optimistic default

optimisticPolicyId=NONE이 기본이다.

optimistic update 허용 조건:

  • deterministic local projection;
  • rollback snapshot bounded;
  • server revision/canonical response가 최종 authority;
  • list membership/sort/filter effect가 deterministic;
  • effect unknown recovery가 정의됨;
  • same query key layer order가 관리됨.

11.5 ordered optimistic layer

layers per query key      <= 8
single layer estimate     <= 262,144 bytes
all layers per query key  <= 2,097,152 bytes
layer age                 <= mutation total deadline + 60s

overflow면 pessimistic execution으로 전환한다. 기존 layer를 silent evict하지 않는다.

11.6 result ordering

success:

1. external wire validation
2. feature mapper
3. scope current check
4. mutation effect confirmed
5. exact detail seed if registered
6. optimistic layer commit/remove
7. registered invalidation topics emit
8. local active Query refetch schedule
9. success UI/navigation

list/aggregate는 default invalidate다. deterministic membership policy가 없으면 direct patch하지 않는다.

11.7 failure ordering

NOT_STARTED/NOT_APPLIED:

rollback optimistic layer
-> expose retryable failure

MAYBE_APPLIED:

optimistic layer를 committed/rolled-back으로 단정하지 않음
-> effect-unknown overlay
-> inspect/reconcile
-> confirmed outcome 후 canonical cache update

11.8 local invalidation failure

server command가 committed된 뒤 local invalidation이 실패해도 mutation을 failure로 바꾸지 않는다.

mutation result = success
cache health = degraded
bounded authoritative refetch schedule

command를 다시 보내지 않는다.

11.9 form submit

  • double-submit coordinator 사용;
  • dirty state는 APPLIED_CONFIRMED 뒤 clear;
  • effect unknown이면 dirty state를 clear하지 않음;
  • successful navigation은 invalidation scheduling 뒤;
  • route blocker는 active effect-unknown recovery를 고려;
  • browser abort가 server rollback 증거가 아님.

12. Cross-context invalidation

12.1 wire 의미

cross-tab wire는 invalidate-only hint다.

전송 금지:

query key
query data
input/filter
cursor
validator
optimistic layer
mutation body
account ID
resource ID

전송:

protocol version
release epoch
scope epoch
sequence
opaque topic ID
source instance ID digest

12.2 registry

export interface InvalidationRegistry {
  readonly topics: readonly string[];
  readonly namespaces: readonly string[];
  readonly edges: readonly Readonly<{
    topicId: string;
    namespace: string;
  }>[];
}

bounds:

topics              256
namespaces           256
edges              1,024
topic fan-out         64
namespace fan-in      64
ID bytes              80

duplicate edge와 orphan topic/namespace를 startup에서 거절한다.

12.3 primary/fallback transport

primary  = BroadcastChannel
fallback = localStorage pulse + storage event

channel/key는 registry-issued opaque token을 사용한다.

fallback pulse:

maximum 1,024 bytes
TTL 60 seconds
write then remove not used; latest bounded envelope overwrite
storageArea must equal localStorage
same-tab local dispatch is explicit, storage event를 기대하지 않음

12.4 ordering

source instance별 monotonic sequence를 사용한다.

  • duplicate sequence: ignore;
  • lower sequence: stale duplicate ignore;
  • exact next: topic apply;
  • gap: 모든 registered namespace stale mark;
  • release/scope epoch mismatch: event ignore + local full stale transition;
  • event는 authority가 아니라 refetch hint.

12.5 mutation coalescing

current local mutation lease 동안 같은 topic remote hint를 coalesce한다.

mutation settlement 후:

  • one invalidation apply;
  • active query bounded refetch;
  • inactive query는 next mount/focus revalidate;
  • remote hint 때문에 optimistic layer를 tab 간 복제하지 않는다.

12.6 lifecycle

  • application runtime start에서 one host;
  • session transition 시 scope epoch rotate;
  • logout에서 old event ignore;
  • pagehide bfcache는 host suspend, restore에서 duplicate listener 없이 resume;
  • dispose는 listener/channel/timer 제거;
  • hidden page도 invalidation hint 수신은 유지하되 refetch는 visibility policy를 따른다.

13. Realtime product composition

13.1 existing runtime reuse

다음 existing source를 common runtime으로 유지한다.

src/contracts/realtime-events.ts
src/contracts/realtime-streams.ts
src/application/ports/realtime/*
src/adapters/realtime/event-codec.ts
src/adapters/realtime/event-consumer.ts
src/adapters/realtime/reconnect-coordinator.ts
src/adapters/realtime/live-poll-handoff-coordinator.ts
src/adapters/realtime/stream-coordinator.ts
src/adapters/realtime/sse/*
src/adapters/realtime/websocket/*
src/adapters/realtime/polling/*
src/adapters/web-push/*

RT-01~RT-04를 새 protocol로 다시 구현하지 않는다.

13.2 product contribution source

src/features/<feature>/contracts/<feature>-realtime-contribution.ts
export interface InstalledRealtimeContribution {
  readonly contributionId: string;
  readonly featureId: string;
  readonly contractSourcePackageId: string;
  readonly stream: RealtimeStreamRegistration;
  readonly eventEffects: readonly InstalledRealtimeEventEffect[];
  readonly transport: InstalledRealtimeTransport;
}

export interface InstalledRealtimeEventEffect {
  readonly eventType: string;
  readonly mapperId: string;
  readonly effect:
    | "INVALIDATE_TOPICS"
    | "APPLY_AUTHORITATIVE_DELTA"
    | "EPHEMERAL_NOTIFICATION";
  readonly invalidationTopics: readonly string[];
}

export type InstalledRealtimeTransport =
  | Readonly<{ kind: "SSE"; endpointId: string }>
  | Readonly<{ kind: "WEBSOCKET"; endpointId: string }>
  | Readonly<{
      kind: "POLLING";
      operationId: string;
      intervalMs: number;
    }>;

endpoint/event/recovery meaning is imported from the external package/approved frontend contribution. Frontend does not invent a server path or event payload.

13.3 contribution validation

contributions max              64
streams max                   128
event types max               512
effects per event max           8
invalidation topics per event  32

validation:

  • contribution ID unique;
  • stream/event registration existing registry rules pass;
  • external event type exists in installed contract contribution;
  • event validator package belongs to contractSourcePackageId;
  • mapper exists and input schema matches payload validator;
  • invalidation topic exists;
  • transport endpoint/operation exists in installed package contribution;
  • polling interval 5,000..300,000ms;
  • transport exactly one;
  • fallback is explicit, not inferred from runtime support.

13.4 no mega port

SSE, WebSocket, Polling, Web Push는 다음 의미로 분리한다.

SSE        active page one-way stream
WebSocket  active page duplex protocol
Polling    visible finite HTTP scheduling
Web Push   inactive/background user notification hint

하나의 RealtimePort.connect(any)로 합치지 않는다.

13.5 transport selection

기본 선택 규칙:

  • one-way active stream: SSE;
  • duplex application protocol: WebSocket;
  • stream provider가 없고 bounded freshness로 충분: Polling;
  • inactive user notification: Web Push;
  • Web Push를 foreground state synchronization으로 사용 금지;
  • Polling을 connection failure 자동 downgrade로 사용 금지;
  • primary/fallback pair는 contribution의 stream.fallback이 명시한다.

13.6 product composition stop condition

다음 중 하나라도 없으면 product realtime을 composition하지 않는다.

exact external package event descriptor
exact endpoint/operation descriptor
feature mapper
feature effect mapping
scope mode
recovery mode
selected transport
kill switch contribution
browser lifecycle owner

common source가 존재한다는 이유로 production bootstrap에 연결하지 않는다.

13.7 host topology

application runtime당 RealtimeRuntimeHost 하나다.

export interface RealtimeRuntimeHost {
  acquire(subscription: RealtimeSubscriptionRequest): RealtimeSubscriptionLease;
  start(): Promise<void>;
  stop(reason: RuntimeStopReason): Promise<void>;
  dispose(): Promise<void>;
}

transport connection은 stream registration 단위로 공유한다. component/route마다 socket을 만들지 않는다.

13.8 single-writer rule

동일 stream/scope에는 active delivery writer가 하나다.

SSE or WebSocket writer
  XOR
bounded Polling writer

handoff:

old writer admission close
-> old in-flight settle/cancel
-> checkpoint snapshot
-> new writer recovery barrier
-> new writer OPEN

live와 polling이 동시에 same effect를 적용하지 않는다.


14. Connection·Reconnect·Resume

14.1 state machine

export type RealtimeConnectionState =
  | "IDLE"
  | "CONNECTING"
  | "AUTHENTICATING"
  | "OPEN_UNRESUMED"
  | "RECOVERING"
  | "OPEN"
  | "BACKING_OFF"
  | "PAUSED_OFFLINE"
  | "PAUSED_HIDDEN"
  | "DEGRADED"
  | "FATAL"
  | "STOPPED";

OPEN_UNRESUMED에서 application event effect를 실행하지 않는다.

14.2 connection success

connection success는 다음만 뜻한다.

transport handshake completed
selected protocol accepted
connection generation current

다음을 뜻하지 않는다.

authenticated subscription active
event gap recovered
server state latest
checkpoint durable
application effect applied

14.3 reconnect policy

exact default:

export const DEFAULT_REALTIME_RECONNECT_POLICY = Object.freeze({
  baseDelayMs: 500,
  maxDelayMs: 30_000,
  maxAttempts: 10,
  maxElapsedMs: 300_000,
  stableOpenMs: 60_000,
});
  • algorithm: full jitter;
  • server not-before hint: local delay lower bound;
  • hint valid range: 0..60,000ms;
  • remaining episode budget 초과 hint: reconnect 종료;
  • attempts/exhausted: DEGRADED, explicit recovery action;
  • automatic new episode cooldown: 300,000ms;
  • valid event/heartbeat 또는 60s stable open 후 attempt counter reset;
  • offline state에서는 timer 대신 online signal을 기다림.

14.4 error classification

reconnect candidate:

network close
provider unavailable
retryable server close
idle timeout
validated auth refresh-ready transition

fatal:

protocol mismatch
unknown required event version
schema/mapper runtime failure
forbidden
invalid endpoint
repeated recovery invariant failure

rate limited:

  • validated delay 사용;
  • maximum delay/episode budget 안에서만 retry;
  • no immediate retry storm.

14.5 visibility policy

VISIBLE -> normal connection/recovery
HIDDEN  -> 30,000ms grace
HIDDEN after grace -> close transport, PAUSED_HIDDEN
VISIBLE restore -> authoritative recovery before OPEN

hidden 상태에서 Polling fallback을 시작하지 않는다. Web Push는 별도 capability다.

14.6 network hint

navigator.onLine은 hint다.

  • offline event: new connection/retry 중지, active transport close;
  • online event: immediate recovery admission one time;
  • online인데 connect failure면 ordinary backoff;
  • offline인데 existing request가 성공해도 network authority를 재정의하지 않음.

14.7 resume authority

resume mode는 existing RealtimeRecoveryProfile literal을 유지한다.

CURSOR
SNAPSHOT_ONLY
SESSION_REBUILD

frontend owns:

  • checkpoint storage lifecycle;
  • connection generation;
  • duplicate/gap detection;
  • barrier ordering;
  • snapshot application ordering.

external package/provider owns server cursor/replay/snapshot semantics. Frontend는 cursor를 decode/modify하지 않는다.

14.8 recovery barrier

CURSOR:

open transport
-> send/attach opaque checkpoint through selected adapter
-> replay begins
-> validate/apply events sequentially
-> replay terminal barrier
-> OPEN

SNAPSHOT_ONLY:

open transport or hold/buffer
-> authoritative snapshot query
-> scope current check
-> apply snapshot
-> release buffered event barrier
-> OPEN

SESSION_REBUILD:

close old stream
-> rebuild feature input
-> reset feature state
-> fresh subscription
-> OPEN

barrier가 NONE인 snapshot profile도 connection open만으로 latest라고 표시하지 않는다. snapshot operation 성공이 필요하다.

14.9 checkpoint record

export interface RealtimeCheckpointRecord {
  readonly recordVersion: 1;
  readonly streamId: string;
  readonly scopeBinding: string;
  readonly connectionGeneration: number;
  readonly opaqueCheckpoint: string;
  readonly lastAppliedEventIdentity: string;
  readonly writtenAt: string;
  readonly expiresAt: string;
  readonly contractPackageDigest: `sha256:${string}`;
}

bounds:

checkpoint UTF-8 <= 4,096 bytes
record encoded <= 16,384 bytes
TTL <= 7 days
records per stream/scope = 1

credential, endpoint, raw account ID를 넣지 않는다.

14.10 gap handling

다음은 gap이다.

  • expected sequence보다 큼;
  • same sequence different fingerprint;
  • cursor expired;
  • dedupe window overflow;
  • queue overflow;
  • checkpoint version/package mismatch;
  • connection generation conflict.

처리:

new event admission stop
-> queued unapplied delta discard
-> state = RECOVERING
-> authoritative snapshot/session rebuild
-> checkpoint replace after effect
-> OPEN

gap 이후 delta를 계속 best-effort 적용하지 않는다.

14.11 SSE specifics

  • Fetch streaming adapter reuse;
  • response status/media validate;
  • maximum line/event bytes는 common event bound;
  • UTF-8 streaming decode;
  • incomplete terminal event discard;
  • heartbeat/comment는 payload event가 아님;
  • idle deadline 45s default;
  • total connection deadline 없음, lifecycle controls close;
  • server retry hint is lower bound, max 60s;
  • Last-Event-ID usage is adapter/profile-owned, opaque only.

14.12 WebSocket specifics

  • exact URL/profile from installed endpoint;
  • binary/text mode fixed per registration;
  • subprotocol allowlist exact;
  • outbound command disabled unless separate registered protocol;
  • max event bytes 64KiB baseline;
  • bufferedAmount high watermark 256KiB for outbound control messages;
  • heartbeat expected 30s, idle timeout 45s when protocol declares heartbeat;
  • browser WebSocket API가 backpressure를 보장한다고 가정하지 않음;
  • receive queue hard bounds are common runtime authority;
  • close code/reason raw text를 UI/log에 노출하지 않음.

14.13 bounded polling specifics

  • existing registered terminal query operation only;
  • visible page only;
  • default interval 30s;
  • minimum 5s, maximum 5m;
  • one in-flight request;
  • next schedule after previous settlement;
  • hidden/offline pause;
  • full jitter ±10% schedule spread;
  • Query retry disabled, HTTP transport owns attempt;
  • polling result enters same mapper/scope/result budget;
  • polling is freshness policy, not realtime delivery guarantee.

15. Event validation과 subscription cleanup

15.1 hard limits

existing constants are normative.

max event bytes       64 KiB
max payload depth     16
max payload nodes      4,096
max queue events         256
max queue bytes        4 MiB
max dedupe entries      2,048
max dedupe bytes        4 MiB
dedupe TTL             10 min

feature contribution can reduce but not raise them.

15.2 validation pipeline

raw frame
-> frame byte bound
-> protocol decode
-> common envelope validation
-> stream registration lookup
-> event type membership check
-> package payload validator
-> feature mapper
-> scope binding/generation check
-> duplicate/sequence/gap decision
-> queue admission
-> sequential effect
-> effect success
-> checkpoint transaction commit
-> subscriber notification

순서를 바꾸지 않는다.

15.3 envelope/payload failure

MALFORMED_FRAME
EVENT_TOO_LARGE
UNKNOWN_EVENT_TYPE
EVENT_NOT_ALLOWED_FOR_STREAM
ENVELOPE_SCHEMA_INVALID
PAYLOAD_SCHEMA_INVALID
VALIDATOR_RUNTIME_FAILURE
MAPPING_CONTRACT_VIOLATION
SCOPE_PROTOCOL_VIOLATION

unknown event를 generic object로 application에 전달하지 않는다.

state-bearing event failure:

admission stop -> recovery

ephemeral notification failure:

drop + bounded observation

15.4 duplicate/conflict

same event identity + same semantic fingerprint:

duplicate -> no effect, no checkpoint regression

same identity + different fingerprint:

EVENT_CONFLICT -> recovery

lower sequence but unknown dedupe entry:

STALE_EVENT -> drop only when registration semantics allow
otherwise recovery

15.5 queue

  • admission count/bytes atomic in host;
  • event processing sequential per stream;
  • different streams can progress independently;
  • queue overflow triggers recovery, not oldest-drop;
  • no unbounded Promise.then chain retained after settlement;
  • queued item owns immutable mapped event and byte estimate;
  • shutdown aborts admission and drains up to 2s, then drops unapplied items without checkpoint.

15.6 effect types

INVALIDATE_TOPICS:

registered opaque topics only
-> local invalidation coordinator
-> active Query bounded refetch

APPLY_AUTHORITATIVE_DELTA:

  • feature-owned reducer;
  • current immutable snapshot and revision required;
  • deterministic/no throw;
  • result size budget;
  • failure triggers recovery;
  • partial delta not stored as event history.

EPHEMERAL_NOTIFICATION:

  • no server-state authority;
  • bounded UI notification;
  • missed notification does not require state recovery unless separately marked state-bearing.

15.7 checkpoint commit ordering

금지:

checkpoint write
-> application effect

필수:

validate/map
-> effect commit
-> scope current recheck
-> checkpoint transaction commit
-> notify

checkpoint write failure after effect:

  • event effect success를 rollback할 수 없음;
  • state RECOVERING;
  • next connection uses authoritative snapshot;
  • same delta replay에 reducer idempotency를 가정하지 않음.

15.8 subscription lease

export interface RealtimeSubscriptionLease {
  readonly leaseId: string;
  readonly streamId: string;
  readonly state: "ACTIVE" | "RELEASED";
  release(): void;
}

key:

streamId + scope generation + subscription projection identity

ref-count behavior:

0 -> 1 : server/transport subscription admission
1 -> N : local subscriber only
N -> 1 : local release
1 -> 0 : 5s grace
0 after grace: unsubscribe/connection policy evaluation

15.9 cleanup triggers

immediate release:

  • explicit component cleanup;
  • route removal;
  • feature disable;
  • scope fence/logout;
  • application shutdown;
  • fatal protocol failure.

grace release:

  • ordinary React remount/navigation;
  • StrictMode setup-cleanup-setup;
  • short route transition.

scope fence는 grace를 사용하지 않는다.

15.10 React bridge

component uses feature hook only.

Component
-> useFeatureRealtime
-> acquire lease in effect
-> cleanup release
-> read external store via useSyncExternalStore

component does not:

  • open/close socket;
  • store cursor;
  • schedule reconnect;
  • parse event;
  • call QueryClient directly.

15.11 StrictMode

required behavior:

setup A acquire
cleanup A release -> grace starts
setup B acquire within grace -> same runtime retained
after final cleanup -> 5s -> actual unsubscribe

listener/ref count cannot go negative. release is idempotent.

15.12 bfcache

pagehide persisted=true:

  • new event effect admission close;
  • transport close;
  • checkpoint remains;
  • lease registry retained in suspended state.

pageshow persisted=true:

  • release manifest refresh;
  • scope current check;
  • authoritative resume;
  • leases reactivate after OPEN;
  • duplicate listeners/connections 없음.

16. Generic Web Worker

16.1 capability boundary

Generic CPU Worker is separate from:

OPFS dedicated worker
Service Worker
Web Push handler
SharedWorker
worklet

existing OPFS worker source is not moved into generic runtime.

16.2 selection condition

Generic Worker remains NOT_SELECTED until:

  • measured main-thread task exceeds named interaction budget;
  • task is CPU-bound;
  • input/output/cancellation owner exists;
  • safe main-thread fallback is defined or unsupported state is accepted;
  • static InstalledWebWorkerContribution exists.

network I/O task, ordinary JSON validation, small mapper work를 worker로 옮기지 않는다.

16.3 source layout

src/application/ports/web-worker/
  worker-task-port.ts
src/contracts/web-worker.ts
src/adapters/web-worker/
  web-worker-runtime.ts
  web-worker-client.ts
  web-worker-protocol.ts
  web-worker-entry.ts
  web-worker-task-registry.ts

application port is semantic task-specific, not generic arbitrary function execution.

16.4 installed contribution

export interface InstalledWebWorkerContribution {
  readonly taskGroupId: string;
  readonly tasks: readonly InstalledWorkerTask[];
  readonly fallback: "MAIN_THREAD_CHUNKED" | "UNSUPPORTED";
}

export interface InstalledWorkerTask {
  readonly taskId: string;
  readonly taskVersion: 1;
  readonly maximumInputBytes: number;
  readonly maximumOutputBytes: number;
  readonly deadlineMs: number;
}

bounds:

task groups max          16
tasks per group          32
input default         1 MiB
input hard            8 MiB
output default        1 MiB
output hard           8 MiB
deadline default      5 s
deadline hard        30 s

16.5 worker topology

  • task group당 worker 1개;
  • baseline pool size 1;
  • same group FIFO;
  • groups can run independently;
  • max active groups 4;
  • no shared global pool;
  • first task에서 lazy create;
  • idle 60s with no active/queue -> terminate;
  • next task creates new generation.

16.6 protocol V1

export type WorkerRequestMessage = Readonly<{
  protocol: "CA_WEB_WORKER_V1";
  kind: "EXECUTE";
  taskId: string;
  taskVersion: 1;
  requestId: string;
  workerGeneration: number;
  deadlineEpochMs: number;
  payload: unknown;
}>;

export type WorkerCancelMessage = Readonly<{
  protocol: "CA_WEB_WORKER_V1";
  kind: "CANCEL";
  requestId: string;
  workerGeneration: number;
}>;

export type WorkerResponseMessage =
  | Readonly<{
      protocol: "CA_WEB_WORKER_V1";
      kind: "SUCCESS";
      requestId: string;
      workerGeneration: number;
      payload: unknown;
    }>
  | Readonly<{
      protocol: "CA_WEB_WORKER_V1";
      kind: "FAILURE";
      requestId: string;
      workerGeneration: number;
      code: WorkerFailureCode;
    }>;

16.7 failure code

TASK_UNKNOWN
VERSION_UNSUPPORTED
INPUT_INVALID
INPUT_TOO_LARGE
OUTPUT_INVALID
OUTPUT_TOO_LARGE
QUEUE_FULL
DEADLINE_EXCEEDED
CANCELLED
CRASHED
TRANSFER_FAILED
STALE_RESULT
RUNTIME_PROTOCOL_FAILURE
export type WorkerFailureCode =
  | "TASK_UNKNOWN"
  | "VERSION_UNSUPPORTED"
  | "INPUT_INVALID"
  | "INPUT_TOO_LARGE"
  | "OUTPUT_INVALID"
  | "OUTPUT_TOO_LARGE"
  | "QUEUE_FULL"
  | "DEADLINE_EXCEEDED"
  | "CANCELLED"
  | "CRASHED"
  | "TRANSFER_FAILED"
  | "STALE_RESULT"
  | "RUNTIME_PROTOCOL_FAILURE";

native error message/stack is not posted to page.

16.8 queue bounds

queued tasks per group      32
queued encoded bytes      16 MiB
active tasks per group       1
waiters per request           1

queue full is a local typed failure. It does not create another worker.

16.9 cancellation

queued:

  • remove immediately;
  • settle CANCELLED;
  • no worker message.

active:

send CANCEL
-> cooperative task checks signal
-> 250ms grace
-> not settled: terminate worker
-> all active/queued in same generation settle CRASHED/CANCELLED
-> next task creates new generation subject to restart budget

16.10 crash/restart budget

maximum restarts = 3 per 5 minutes per task group
fourth crash -> group FATAL
manual retry -> new 5-minute window only after user action/page reload

crash does not retry completed/unknown task automatically.

16.11 structured clone/transfer

  • message schema validates cloned shape;
  • credentials, AbortSignal, class instance, function, DOM node prohibited;
  • ArrayBuffer transfer allowed only when task declares ownership transfer;
  • sender must treat transferred buffer as detached immediately;
  • output transfer is validated after receive;
  • SharedArrayBuffer baseline prohibited;
  • transferable list count max 16.

16.12 stale result fence

result is accepted only when all match.

requestId
workerGeneration
current task lease
caller scope generation
not cancelled
before deadline

stale result is dropped and observed. Query/UI is not updated.

16.13 worker network prohibition

worker entry cannot import HTTP adapter or use fetch, WebSocket, EventSource, credential/session adapter.

CPU task requiring remote input:

page/application HTTP
-> validate/map/bound payload
-> Worker task
-> validate result
-> application/UI

16.14 main-thread fallback

MAIN_THREAD_CHUNKED only:

  • same semantic task interface;
  • chunk budget max 8ms;
  • yield via scheduler/MessageChannel adapter;
  • same total deadline;
  • same cancellation/result validator;
  • input hard max reduced to 1MiB;
  • not presented as equivalent performance guarantee.

UNSUPPORTED returns typed unavailable; it does not execute synchronously.


17. Service Worker lifecycle

17.1 one physical registration

same scope has one worker entry.

PWA lifecycle
verified static asset fetch
Web Push event/click
optional sync wake-up

separate registration prohibited.

src/adapters/service-worker/service-worker-entry.ts

existing Web Push service-worker runtime becomes a handler factory inside this entry.

17.2 source layout

src/contracts/service-worker.ts
src/adapters/service-worker/
  service-worker-entry.ts
  service-worker-protocol.ts
  service-worker-page-controller.ts
  service-worker-lifecycle.ts
  service-worker-static-assets.ts
  service-worker-sync-handler.ts
  service-worker-removal.ts
src/bootstrap/register-service-worker.ts

17.2.1 TypeScript execution realms

main DOM, generic Web Worker와 Service Worker는 같은 TypeScript global lib를 공유하지 않는다.

tsconfig.app.json             lib = DOM + ES

tsconfig.web-worker.json      lib = WebWorker + ES
                              include generic worker entry/runtime only

tsconfig.service-worker.json  lib = WebWorker + ES
                              include service-worker entry/handlers only
  • worker tsconfig에서 DOM global type을 사용하지 않는다;
  • service-worker tsconfig에서 React/presentation/bootstrap page code를 include하지 않는다;
  • shared protocol file은 DOM/WebWorker global type 대신 structural types만 사용한다;
  • root tsconfig.json project references에 두 config를 추가한다;
  • skipLibCheck로 realm collision을 숨기지 않는다.

17.2.2 deterministic two-pass build

Service Worker가 exact hashed app asset 목록을 compile-time으로 가져야 하므로 build 순서를 다음으로 고정한다.

1. clean dist and .generated/frontend-runtime
2. generate contractSet + build-info source
3. Vite app build (emptyOutDir=true)
4. scan app dist and generate static asset source
5. ACTIVE mode일 때 Vite Service Worker build (emptyOutDir=false)
6. generate Release Manifest V2/build manifest

source:

scripts/build-frontend.ts
scripts/generate-contract-set.ts
scripts/generate-service-worker-assets.ts
vite.service-worker.config.ts
.generated/frontend-runtime/contract-set.ts
.generated/frontend-runtime/service-worker-build-info.ts
.generated/frontend-runtime/service-worker-assets.ts

Service Worker build rules:

  • input exactly src/adapters/service-worker/service-worker-entry.ts;
  • output exactly dist/service-worker.js relative to deployed app root;
  • ES module format;
  • inlineDynamicImports=true;
  • no additional worker chunk;
  • sourcemap off by default;
  • minification uses same pinned Vite toolchain;
  • REMOVE_REGISTRATION, PURGE_OWNED_RESOURCES, null mode does not run active worker build;
  • generated asset source excludes service-worker.js itself and index.html.

static delivery requirement:

service-worker.js Cache-Control = no-cache
runtime config/release manifest = no-store
hashed assets = immutable

runtime does not fetch hosting policy to prove it. Static delivery configuration owns this requirement.

17.3 static selection modes

ACTIVE

handlers contain one or more unique values.

WEB_PUSH
PWA_STATIC_ASSETS
OFFLINE_SYNC_WAKEUP

worker entry and register code are included.

REMOVE_REGISTRATION

  • worker entry not included;
  • cleanup controller included;
  • exact registration scope/script ownership check;
  • unregister only;
  • caches retained.

PURGE_OWNED_RESOURCES

  • repeat unregister check;
  • exact owned caches/metadata purge;
  • outbox/OPFS/file data untouched.

null

  • entry and cleanup code absent;
  • registration lookup 0;
  • Cache Storage access 0.

transition:

ACTIVE
-> REMOVE_REGISTRATION for at least one deployed release
-> PURGE_OWNED_RESOURCES after rollback retention
-> null

17.4 exact ownership check

expected:

scope = new URL(routerBasePath, location.origin).href
script = new URL("service-worker.js", scope).href

getRegistration(routerBasePath) result is owned only if:

  • registration.scope exact equals expected scope;
  • every present installing/waiting/active worker scriptURL is same-origin;
  • at least one present worker scriptURL exact equals expected script;
  • no present worker has different script URL.

ownership mismatch means no unregister/purge.

17.5 registration timing

Runtime Config valid
-> Release Manifest/contractSet valid
-> base composition
-> React first committed effect
-> PostMountRuntimeStarter.start
-> ACTIVE/cleanup mode action

ACTIVE registration call:

const expectedScope = new URL(routerBasePath, location.origin);
const scriptUrl = new URL("service-worker.js", expectedScope);

navigator.serviceWorker.register(scriptUrl, {
  scope: expectedScope.pathname,
  type: "module",
  updateViaCache: "none",
});

StrictMode repeat returns same in-flight promise.

17.6 runtime override DISABLED

static ACTIVE + config disabled:

new register                     0
worker message/update timer      0
sync register                    0
push handler activation          0
exact-owned registration lookup  1
owned registration unregister <= 1
owned cache delete               0

failure code DISABLE_CLEANUP_FAILED; capability remains inactive. next clean navigation may retry once, no loop.

17.7 protocol identity

serviceWorkerProtocolVersion = 1
cacheSchemaVersion = 1
buildId
releaseId
contractSetDigest
staticAssetSetDigest

page and worker read the same compile-time virtual build-info module.

17.8 message protocol

PAGE_HELLO
WORKER_HELLO_ACK
UPDATE_READY
ACTIVATE_REQUEST
ACTIVATE_ACCEPTED
ACTIVATE_REJECTED
CLIENT_DRAIN_REQUEST
CLIENT_DRAINED
ACTIVATED_RELOAD_REQUIRED
CACHE_RESET_REQUEST
CACHE_RESET_RESULT
SYNC_WAKE_OBSERVED

common fields:

protocolVersion = 1
messageId = UUID
sourceBuildId
targetBuildId optional
nonce optional

origin/source client ID, exact message keys, runtime schema and one-time nonce validation required.

17.9 install

without static asset handler:

  • Cache Storage open 0;
  • lifecycle/push/sync handlers register only.

with static assets:

1. embedded manifest validate
2. candidate cache open
3. fetch concurrency 4
4. status/content-type/bytes/integrity verify
5. candidate marker write
6. all success -> install resolve
7. any failure -> candidate cache delete and install reject

bounds:

assets count      256
single asset      2 MiB
asset set         5 MiB
concurrency       4
install deadline 60 s

partial candidate is never used.

17.10 waiting update

waiting worker sends UPDATE_READY to controlled clients.

it does not call skipWaiting() itself.

page blocks activation when:

  • dirty form;
  • active mutation;
  • effect unknown recovery;
  • offline command SENDING;
  • unresolved local file write;
  • incompatible protocol tuple.

17.11 client drain activation

page creates one-time nonce
-> ACTIVATE_REQUEST(expected tuple)
-> waiting worker validates
-> CLIENT_DRAIN_REQUEST to all controlled window clients
-> each client closes new mutation/realtime/offline admission
-> active effects settle
-> CLIENT_DRAINED(nonce)
-> all clients within 30s
-> ACTIVATE_ACCEPTED
-> waiting worker skipWaiting
-> activate event
-> no clients.claim
-> ACTIVATED_RELOAD_REQUIRED
-> clean page user/guarded reload once

one missing client rejects automatic activation.

17.12 no clients.claim

baseline does not call clients.claim().

  • first install page is not silently controlled;
  • update does not mix new worker with old page without reload;
  • current page becomes controlled after explicit clean reload;
  • future claim adoption requires separate compatibility decision.

17.13 first install

Service Worker reload guard is distinct from chunk recovery.

storage key = SERVICE_WORKER_RELOAD_GUARD
value schema = { version: 1, oldBuildId, newBuildId, writtenAt }
maximum bytes = 512
TTL = 10 minutes

CHUNK_RELOAD_GUARD를 재사용하지 않는다. Storage registry/codec이 exact key와 envelope를 소유한다.

if active tuple matches current build but navigator.serviceWorker.controller is null:

state = RELOAD_TO_ENABLE
  • no automatic reload;
  • user action only when page clean;
  • reload guard once per build;
  • after reload PAGE_HELLO handshake required;
  • handshake failure -> INCOMPATIBLE, capability inactive.

17.14 update checking

  • no immediate repeated registration.update();
  • visible page at most once per 6 hours;
  • manual action once independently rate-limited;
  • hidden page no scheduled update;
  • network failure does not fail product flow;
  • controllerchange reload guard (oldBuild,newBuild) once.

17.15 activate cleanup

  • candidate marker and tuple validate;
  • current revision active;
  • previous verified revision retain one;
  • older owned cache delete;
  • exact prefix + parser only;
  • config/release/API cache found -> security violation and delete;
  • unregister != cache cleanup.

17.16 page controller interface

export type ServiceWorkerStartOutcome =
  | Readonly<{ kind: "ACTIVE"; buildId: string }>
  | Readonly<{ kind: "RELOAD_TO_ENABLE" }>
  | Readonly<{ kind: "UPDATE_WAITING" }>
  | Readonly<{ kind: "DISABLED" }>
  | Readonly<{ kind: "INCOMPATIBLE" }>
  | Readonly<{ kind: "FAILED"; code: string }>;

export type ServiceWorkerActivationOutcome =
  | Readonly<{ kind: "ACTIVATED_RELOAD_REQUIRED" }>
  | Readonly<{ kind: "BLOCKED_DIRTY_CLIENT" }>
  | Readonly<{ kind: "CLIENT_DRAIN_TIMEOUT" }>
  | Readonly<{ kind: "NO_WAITING_WORKER" }>
  | Readonly<{ kind: "PROTOCOL_MISMATCH" }>
  | Readonly<{ kind: "FAILED"; code: string }>;

export type ServiceWorkerResetOutcome =
  | Readonly<{ kind: "RESET"; cachesDeleted: number }>
  | Readonly<{ kind: "NOT_CONTROLLED" }>
  | Readonly<{ kind: "PROTOCOL_MISMATCH" }>
  | Readonly<{ kind: "FAILED"; code: string }>;

export interface ServiceWorkerRuntimeHost {
  start(): Promise<ServiceWorkerStartOutcome>;
  requestActivation(): Promise<ServiceWorkerActivationOutcome>;
  resetOwnedCaches(): Promise<ServiceWorkerResetOutcome>;
  stop(): Promise<void>;
}

stop() removes page listener/update timer. ordinary shutdown does not unregister.

17.17 cleanup mode outcomes

export type ServiceWorkerRemovalOutcome =
  | Readonly<{ kind: "ABSENT" }>
  | Readonly<{ kind: "UNREGISTERED" }>
  | Readonly<{ kind: "PURGED"; cachesDeleted: number; metadataDeleted: number }>
  | Readonly<{ kind: "OWNERSHIP_MISMATCH" }>
  | Readonly<{ kind: "FAILED"; operation: "LOOKUP" | "UNREGISTER" | "PURGE" }>;

foreign resource count can be observed only as bounded count, no names.


18. Cache Storage와 fetch policy

18.1 existing runtime reuse

src/adapters/cache-storage/public-cache-policy.ts
src/adapters/cache-storage/public-response-cache-adapter.ts

no generic CacheService.

18.2 cache name

ca-static-v1-<first-16-lower-hex-of-staticAssetSetDigest>

parser requires exact prefix, schema literal and 16 hex chars.

18.3 embedded static asset manifest

Vite virtual modules:

virtual:ca-service-worker-assets
virtual:ca-service-worker-build-info
export interface StaticAssetManifestV1 {
  readonly schemaVersion: 1;
  readonly buildId: string;
  readonly releaseId: string;
  readonly setDigest: `sha256:${string}`;
  readonly assets: readonly Readonly<{
    url: string;
    sha256: `sha256:${string}`;
    bytes: number;
    contentType: string;
  }>[];
}

worker does not fetch a manifest endpoint.

18.4 manifest inclusion

include only:

  • Vite immutable hashed JS/CSS assets;
  • approved font/image assets with immutable hashed URL;
  • worker-owned static metadata.

exclude:

index.html
runtime config
release manifest
API response
user/account data
telemetry/auth endpoint
source map unless explicitly public
unhashed mutable asset
cross-origin opaque response

18.5 fetch classification order

non-GET                         -> network passthrough
navigation request              -> network-only
runtime config URL              -> network-only
release manifest URL            -> network-only
exact static asset manifest hit -> verified cache-first
all other requests              -> network passthrough

API base can be /; exact static hit is evaluated before generic network passthrough.

18.6 cache hit validation

  • exact URL;
  • method GET;
  • only build-generated query if manifest contains it;
  • cached marker matches set digest;
  • status 200;
  • content type exact normalized match;
  • no opaque response;
  • expected byte count;
  • install-time integrity verified.

invalid hit is deleted and treated as release mismatch/network fetch. It is not returned stale.

18.7 cache miss

runtime fetch success is returned but not written into active cache. Static cache population is install-time only.

missing immutable asset:

network failure/404
-> release mismatch classification
-> page chunk recovery path

18.8 navigation

baseline has no offline shell.

  • navigation network-only;
  • cached index.html fallback prohibited;
  • network error uses browser/app boot failure;
  • stale HTML and new assets/contractSet never intentionally mix;
  • installability/offline document is a separate product capability.

18.9 quota

  • asset set hard max 5MiB;
  • StorageManager.estimate() is advisory only;
  • actual QuotaExceededError is authoritative;
  • install quota failure deletes candidate;
  • existing current/previous cache retained;
  • no browser file/OPFS/outbox cleanup to make room;
  • user data has higher priority than recomputable static cache.

18.10 reset

CACHE_RESET_REQUEST requires:

  • controlled same-origin client;
  • current protocol;
  • one-time nonce;
  • user/incident action;
  • owned prefix parser.

reset deletes static caches only. registration remains. reload after reset is user/guarded action.


19. Offline Command와 Background Sync

19.1 capability status

baseline product selection remains NOT_SELECTED.

reference runtime can be implemented without composing a product operation. It must not create DB/listener when unselected.

19.2 scope

Offline Command is for durable user command retry after foreground interruption. It is not:

  • Query persistence;
  • arbitrary request cache;
  • background execution guarantee;
  • exactly-once guarantee;
  • event queue;
  • file upload checkpoint replacement.

19.3 external prerequisite consumption

an operation is queueable only when external package contribution provides:

retrySemantics = KEYED
commandRecovery non-null
stable input validator/projection
stable operation identity
runtime validator for terminal result/problem

frontend does not define server idempotency or inspect protocol.

19.4 source layout

src/application/ports/offline-command/
  offline-command-port.ts
src/contracts/offline-command.ts
src/adapters/offline-command/
  offline-command-codec.ts
  indexeddb-offline-command-repository.ts
  offline-command-drain.ts
  offline-command-lifecycle.ts
  offline-command-summary.ts

existing generic IndexedDB runtime is reused as dataset backend.

19.5 installed contribution

export interface InstalledOfflineCommandContribution {
  readonly datasetId: "OFFLINE_COMMANDS_V1";
  readonly operations: readonly InstalledOfflineOperation[];
}

export interface InstalledOfflineOperation {
  readonly operationId: string;
  readonly contractPackageId: string;
  readonly maximumRequestBytes: number;
  readonly retentionClass: "STANDARD_7D";
}

bounds:

operations                    64
single request             256 KiB
hard single request          1 MiB
records                    1,000
dataset bytes              50 MiB

19.6 record

export interface OfflineCommandRecordV1 {
  readonly recordVersion: 1;
  readonly commandId: string;
  readonly operationId: string;
  readonly contractPackageId: string;
  readonly contractPackageVersion: string;
  readonly contractPackageDigest: `sha256:${string}`;
  readonly scopePartition: string;
  readonly requestDigest: `sha256:${string}`;
  readonly requestPayload: Uint8Array;
  readonly idempotencyKey: string;
  readonly state: OfflineCommandState;
  readonly attempt: number;
  readonly createdAt: string;
  readonly updatedAt: string;
  readonly nextAttemptAt?: string;
  readonly leaseOwner?: string;
  readonly leaseExpiresAt?: string;
  readonly terminalCode?: string;
}
export type OfflineCommandState =
  | "PENDING"
  | "LEASED"
  | "FOREGROUND_REQUIRED"
  | "SENDING"
  | "RETRY_WAIT"
  | "ACKED"
  | "CONFLICT"
  | "EFFECT_UNKNOWN"
  | "EXPIRED";

19.7 physical storage

  • opaque DB/dataset name from existing governance;
  • account/session scope partition binding;
  • transaction-complete is write success;
  • record codec V1 strict;
  • future schema fail closed/read-only;
  • blocked/versionchange typed;
  • no credential/token except opaque idempotency key;
  • request payload minimized to exact package request projection;
  • no raw URL/header.

19.8 enqueue

scope current
-> operation allowlist
-> package identity current
-> package input validator
-> deterministic request encode
-> request bytes/digest
-> create idempotency key
-> dataset count/byte admission
-> one IDB transaction insert + counters
-> PENDING

same user intent duplicate uses feature/mutation coordinator before enqueue. Repository does not silently merge by payload digest.

19.9 sender topology

origin/account partition has one sender lease.

lease TTL     30s
renew every   10s
batch count   10
batch window  30s
parallel send 1

foreground tabs coordinate through existing cross-context hint and IndexedDB lease CAS. BroadcastChannel is hint, IDB lease is local authority.

19.10 state transitions

PENDING -> LEASED -> SENDING
SENDING -> ACKED
SENDING -> RETRY_WAIT
SENDING -> CONFLICT
SENDING -> EFFECT_UNKNOWN
PENDING/RETRY_WAIT -> FOREGROUND_REQUIRED
PENDING/RETRY_WAIT/FOREGROUND_REQUIRED -> EXPIRED

invalid transitions are corruption.

19.11 transaction boundaries

before network:

IDB transaction:
  verify current state
  verify lease owner/live
  increment attempt
  state=SENDING
  write operation attempt identity
commit

after terminal response:

validate/map/effect classify
-> IDB transaction terminal/retry state commit
-> lease release

19.12 crash recovery

expired LEASED before send:

safe -> PENDING

expired SENDING:

EFFECT_UNKNOWN

never reset SENDING to PENDING automatically.

19.13 foreground drain triggers

startup
online
visible
focus
manual

triggers coalesce into one scheduled drain. hidden page pauses new sends. active send result still settles.

19.14 auth and package fence

before each send:

  • scope current;
  • credential READY;
  • installed contract package identity exact matches record;
  • operation still installed;
  • request payload revalidated;
  • idempotency key present;
  • total deadline available.

failure:

auth unavailable -> FOREGROUND_REQUIRED
package missing/version/digest mismatch -> FOREGROUND_REQUIRED(CONTRACT_PACKAGE_UNAVAILABLE)
scope stale -> old partition hidden, no send
schema invalid -> CONFLICT/terminal corruption review

19.15 retry

retryable response/failure is package/HTTP runtime decision.

base delay 1s
max delay 5m
full jitter
attempt cap 10

Retry-After is lower bound up to 5m.

never retry:

  • contract/schema/mapper failure;
  • effect maybe applied;
  • auth unavailable;
  • conflict;
  • expired retention.

attempt cap -> FOREGROUND_REQUIRED(RETRY_EXHAUSTED), not silent drop.

19.16 effect unknown

automatic send stop
-> package recovery descriptor
-> foreground inspect when auth ready
-> committed: ACKED + canonical refresh
-> no effect: PENDING only after explicit transition
-> still unknown: EFFECT_UNKNOWN

manual retry button cannot resend EFFECT_UNKNOWN directly.

19.17 retention

PENDING/RETRY_WAIT/FOREGROUND_REQUIRED max age 7d
ACKED payload delete immediately; safe summary 24h
CONFLICT/EFFECT_UNKNOWN payload max 30d
EXPIRED payload delete; safe summary 7d

30d 이후 unresolved payload를 자동 send하지 않는다.

19.18 Background Sync baseline

Background Sync is optional wake-up.

sync tag = ca-outbox-v1

register only when:

  • service worker handler OFFLINE_SYNC_WAKEUP selected;
  • registration.sync exists;
  • pending/foreground-required record exists;
  • no in-flight same tag;
  • last registration >= 1 minute.

sync handler:

1. inspect bounded outbox metadata
2. update wakeRequestedAt
3. post SYNC_WAKE_OBSERVED to controlled clients
4. send authenticated command = 0
5. resolve handler

19.19 foreground fallback

Background Sync unsupported/fails/never fires is normal.

foreground triggers remain required. Queue semantics are identical with or without native Sync.

19.20 future worker send explicitly excluded

Service Worker authenticated send requires separate future design covering:

  • worker-safe credential owner;
  • no credential persistence;
  • refresh/revocation;
  • package validators in worker bundle;
  • effect reconciliation;
  • logout/version fence;
  • platform execution limitations;
  • user recovery.

until then worker send code is prohibited.

19.21 safe summary

export interface OfflineCommandSummary {
  readonly commandId: string;
  readonly operationLabelKey: string;
  readonly state: OfflineCommandState;
  readonly createdAt: string;
  readonly nextAction:
    | "WAIT"
    | "OPEN_APP"
    | "CHECK_STATUS"
    | "RESOLVE_CONFLICT"
    | "CONTACT_SUPPORT"
    | "DISMISS";
}

raw payload, idempotency key, package digest, account partition are not exposed to UI.

19.22 removal

1. new enqueue close
2. drain stop
3. active send settle/lease expire
4. unresolved count inspect
5. product recovery/export decision
6. selected dataset only delete when safe
7. sync registration stop
8. SW sync handler remove
9. composition remove

unresolved user command is not silently purged with source deletion.


20. 공통 start·stop·dispose

20.1 lifecycle interface

export interface RuntimeLifecycle {
  start(): void | Promise<void>;
  stop(reason: RuntimeStopReason): void | Promise<void>;
  dispose(): void | Promise<void>;
}

export type RuntimeStopReason =
  | "APPLICATION_SHUTDOWN"
  | "SCOPE_FENCED"
  | "FEATURE_DISABLED"
  | "HIDDEN_POLICY"
  | "INCIDENT_CONTAINMENT";

invariants:

  • start() idempotent;
  • concurrent start returns same promise;
  • start failure cleans partial resource and state FAILED;
  • stop() is restartable quiesce unless capability says terminal;
  • dispose() terminal;
  • dispose after dispose no-op;
  • start after dispose throws typed lifecycle error;
  • concurrent stop/dispose has one settlement;
  • cleanup error aggregation does not skip remaining cleanup.

20.2 runtime lifecycle states

NEW
STARTING
RUNNING
STOPPING
STOPPED
FAILED
DISPOSING
DISPOSED

allowed transitions:

NEW -> STARTING -> RUNNING
STARTING -> FAILED
RUNNING -> STOPPING -> STOPPED
STOPPED -> STARTING
NEW/STOPPED/FAILED/RUNNING -> DISPOSING -> DISPOSED

FAILED -> STARTING automatic transition is prohibited. Explicit owner recovery creates a new runtime instance or calls a named reset method.

20.3 application shutdown order

1. close new navigation/mutation/optional admission
2. stop Service Worker page controller/cleanup action
3. close Web Worker task admission
4. cooperative cancel/terminate Web Workers
5. stop realtime subscriptions/reconnect and drain
6. stop offline foreground drain and release lease
7. dispose client scope runtime
8. clear conditional validator store
9. dispose invalidation coordinator
10. cancel and clear QueryClient
11. close cross-context transport
12. dispose session subscription
13. telemetry flush up to deadline
14. diagnostics dispose

QueryClient를 realtime보다 먼저 clear하지 않는다. late realtime effect가 cleared cache를 다시 채우는 race를 막는다.

20.4 browser lifecycle owner

window listener owner:

src/adapters/platform/browser-lifecycle.ts

single listener set:

visibilitychange
online
offline
focus
pagehide
pageshow
beforeunload only while dirty state exists

capability별 direct window listener를 추가하지 않는다.

export interface BrowserLifecycleSnapshot {
  readonly visibility: "VISIBLE" | "HIDDEN";
  readonly connectivityHint: "ONLINE" | "OFFLINE";
  readonly pageState: "ACTIVE" | "PAGEHIDE" | "BFCACHE_RESTORED";
  readonly generation: number;
}

20.5 pagehide and bfcache

pagehide persisted=true:

  • terminal dispose하지 않음;
  • lifecycle state SUSPENDED projection;
  • realtime transport close;
  • offline new send pause;
  • active Worker follows task cancellation policy;
  • Query memory remains;
  • Service Worker registration remains;
  • no blocking telemetry flush.

pageshow persisted=true:

1. browser lifecycle generation increment
2. Release Manifest V2 no-store refresh once
3. scope current check
4. active Query refetch
5. realtime authoritative recovery
6. offline expired lease recovery
7. duplicate listener/runtime check

manifest incompatible면 product runtime을 resume하지 않고 safe reload/recovery surface로 이동한다.

20.6 unload non-guarantee

다음을 beforeunload, unload, sendBeacon correctness로 처리하지 않는다.

  • command complete;
  • checkpoint write;
  • subscription unsubscribe guarantee;
  • lease release guarantee;
  • Service Worker activation acknowledgement;
  • telemetry guaranteed delivery.

correctness is transaction/TTL/recovery based.

20.7 cleanup deadlines

realtime drain                 2,000ms
worker cooperative stop         250ms per active group
telemetry flush               1,000ms
offline local lease release   2,000ms
Service Worker page stop      no network wait
overall application stop      5,000ms

overall deadline expires:

  • remaining worker terminate;
  • transport close;
  • listener/timer remove;
  • unresolved durable state left for recovery;
  • no additional network retry.

20.8 scope fence vs shutdown

scope fence:

  • application remains alive;
  • global Router/QueryClient object remains;
  • account-bound data/listener/connection fenced;
  • new scope READY after reset.

shutdown:

  • all admission terminal close;
  • no new scope;
  • every runtime disposed.

두 lifecycle을 같은 clearEverything()로 합치지 않는다.


21. Security와 privacy

21.1 trust boundary

trusted build inputs:

exact installed contract package bytes
frontend source
embedded contractSet identity
embedded static asset manifest

untrusted runtime inputs:

Runtime Config bytes until validated
Release Manifest bytes until validated
HTTP response/problem
realtime frame/event
postMessage
IndexedDB historical record
Cache Storage response
URL/path/search/form input

trusted package does not make server response trusted. Runtime validation remains mandatory.

21.2 secret prohibition

must not contain credential/secret:

Runtime Config
Release Manifest
contractSet
Query key/value
Web Storage
IndexedDB query metadata
Cache Storage
Worker message
Service Worker message
realtime checkpoint diagnostics
URL
telemetry attributes
support reference

offline outbox may store opaque idempotency key but treats it as sensitive command identity:

  • no log;
  • no UI;
  • no cross-context message;
  • delete with payload retention.

21.3 generated package isolation

contract package must:

  • have no required postinstall script;
  • perform no network/DOM/listener side effect on import;
  • expose descriptor/validator/type only;
  • have no mutable singleton carrying user state;
  • not reference window, document, navigator, fetch, WebSocket at runtime;
  • not include credential helper;
  • not provide user-facing error copy.

violation blocks contribution composition.

21.4 boot request security

  • same-origin Runtime Config/release manifest;
  • redirect error;
  • no credentials beyond same-origin browser policy;
  • no-store;
  • strict content type;
  • byte bound;
  • secret-name scan;
  • safe error only;
  • no arbitrary manifest URL from query/local storage.

21.5 HTTP security

  • HTTPS outside local;
  • final URL allowlist;
  • redirect reject;
  • referrerPolicy=no-referrer;
  • credential patch allowlist;
  • raw URL/query/body/header no logs;
  • response byte bound;
  • duplicate JSON key semantics not relied upon;
  • prototype pollution keys rejected/stripped by validator/mapper;
  • problem detail rendered through safe text;
  • dangerouslySetInnerHTML forbidden.

21.6 Router security

  • open redirect forbidden;
  • return URL created only by same-origin route builder;
  • route param validation before domain ID;
  • frontend access guard not authorization proof;
  • chunk URL not user-generated;
  • search data not interpolated into raw HTML/style/script;
  • malformed route causes local failure before network.

21.7 Query/cache security

  • cache hit not authorization;
  • old scope value hidden immediately at FENCED;
  • raw IDs/URL/document/message absent from key;
  • credential/header/trace absent from value;
  • query persistence disabled;
  • production devtools governed by build profile;
  • optimistic layer excludes full command body/token.

21.8 Cross-context security

  • no data/query key;
  • opaque topic/source;
  • protocol/epoch validation;
  • storageArea exact check;
  • message bytes bound;
  • foreign/malformed event ignored;
  • channel name not based on account ID;
  • event is hint, not authority.

21.9 Realtime security

  • exact endpoint/profile allowlist;
  • frame/event bounds;
  • envelope/payload validation;
  • scope binding;
  • unknown event safe recovery;
  • cursor/endpoint/push subscription absent from diagnostics;
  • no arbitrary WebSocket outbound command;
  • open connection not auth success;
  • raw close reason not user/log.

21.10 Worker security

  • module worker static URL;
  • Blob/data URL worker forbidden;
  • eval, Function, dynamic importScripts forbidden;
  • no credential/network;
  • cloned payload validator;
  • input/output bounds;
  • generation/request fence;
  • SharedArrayBuffer disabled;
  • WASM only as separate exact-digest/memory-bound task capability.

21.11 Service Worker security

  • same-origin scope/script;
  • source client ID/origin/nonce validation;
  • no blind skipWaiting/clients.claim;
  • API/private response cache forbidden;
  • opaque cache forbidden;
  • owned prefix/parser cleanup only;
  • global variable not durable authority;
  • push payload validator and notification registry;
  • notification URL registered same-origin route only;
  • foreign registration never unregistered.

21.12 IndexedDB/outbox privacy

  • opaque physical DB name;
  • scope binding;
  • historical codec validation;
  • package digest recheck;
  • quota/corrupt/future schema separated;
  • logout old partition hidden immediately;
  • same-origin encryption not XSS authorization boundary;
  • payload minimization;
  • retention ceiling;
  • unresolved command export/recovery product-owned.

21.13 diagnostics redaction

prohibited values:

raw URL/query
request/response body
header/token/cookie
account/resource ID
event payload/cursor
idempotency key
contract package source path
cache key/value
worker payload
file name/path

allowed dimensions are closed enums and bounded IDs declared in §22.


22. Runtime observability

22.1 ownership

existing diagnostics/telemetry adapters are reused.

src/adapters/diagnostics/bounded-diagnostics.ts
src/adapters/telemetry/best-effort-telemetry.ts
src/contracts/diagnostics.ts
src/contracts/telemetry.ts

raw SDK is not exposed to feature/application.

22.2 closed event families

frontend_boot
frontend_http
frontend_query
frontend_mutation
frontend_scope
frontend_realtime
frontend_worker
frontend_service_worker
frontend_offline_command
frontend_drop

new arbitrary event name is prohibited. Add a typed registry entry first.

22.3 common attributes

allowlisted:

operationCategory
outcome
failureKind
appEnv
buildId bucket/release reference
featureId
operationId declared stable ID
profileId
transportKind
browserEngine bucket
queueSize bucket
payloadSize bucket
attempt bucket
lifecycleState

not allowlisted:

URL
user/account/resource ID
request ID from server
cursor/checkpoint
package digest full value
idempotency key
message/error string

contract package digest may be represented only as MATCH | MISMATCH | MISSING, not tag value.

22.4 HTTP observation

one logical operation terminal event plus bounded physical attempt counters.

logical operation ID
attempt count bucket
terminal outcome
certainty
request/response size bucket
latency bucket
retry reason category

physical attempts do not each emit high-cardinality traces by default.

22.5 Query observation

profileId
cache state: MISS | FRESH | STALE | REFRESH
result budget: ADMITTED | REJECTED
scope current: CURRENT | FENCED

query key/input/value absent.

22.6 Mutation observation

duplicate policy
duplicate outcome
optimistic used boolean
effect certainty
invalidation result
recovery state

intent/idempotency identity absent.

22.7 Realtime observation

streamId stable registry ID
eventType stable registry ID
transport
state transition
reconnect attempt bucket
queue bucket
recovery reason
validation outcome
effect outcome

cursor/payload/endpoint absent.

22.8 Worker observation

taskGroupId
taskId
queue bucket
input/output size bucket
latency bucket
cancel/crash/restart outcome
fallback mode

payload and native stack absent.

22.9 Service Worker observation

mode
handler set bitmask or closed enum
registration outcome
update state
client drain outcome
cache install/reset outcome
owned removal outcome

scope/script URL/cache name absent. ownership mismatch is a boolean/outcome only.

22.10 Offline observation

state transition
attempt bucket
age bucket
queue count/byte bucket
lease outcome
terminal/recovery category
sync wake observed boolean

operation label uses stable operation ID; no payload/key/partition.

22.11 bounded queue and failure

telemetry is best effort.

maximum queued events 500
maximum estimated bytes 1 MiB
flush batch 50
shutdown flush 1s
drop oldest non-critical observation when full
never block product flow
never recursively report sink failure

security/contract fatal events can be mirrored to bounded local diagnostics but do not gain infinite retention.

22.12 health semantics

frontend does not expose one global healthy boolean.

export type RuntimeHealth =
  | "AVAILABLE"
  | "DEGRADED"
  | "UNAVAILABLE"
  | "INCOMPATIBLE"
  | "DISABLED";

capability health is independent:

  • HTTP unavailable does not mark Router unavailable;
  • realtime fatal can fall back to explicit stale/polling policy;
  • Service Worker disabled does not fail product;
  • offline outbox conflict does not mean IndexedDB runtime globally corrupt;
  • contractSet mismatch is boot incompatible.

23. 구현 검증

23.1 범위

이 절은 local/unit/component/integration/real-browser behavior를 정의한다. CI stage, workflow, promotion formula는 정의하지 않는다.

23.2 Runtime Config/boot cases

valid V2
unknown field reject
API_CONTRACT_VERSION in V2 reject
secret-like key reject
URL credential/hash/query reject
HTTPS rule
config 64KiB exact and +1
manifest 1MiB exact and +1
Content-Length lie
chunked overflow
invalid UTF-8
invalid JSON/media/status
5s timeout
redirect reject
build/release/assets mismatch
contractSet canonical/digest mismatch
fixture excluded from contractSet
partial composition reverse cleanup
retry once/no loop

23.3 contract package consumer cases

only contribution file imports package
exact version/integrity
packageId/version/digest/source regex
runtime protocol mismatch
package duplicate/mismatch
duplicate operation/event
status sorted unique/subset
path/query/hash bound
validator missing/throw
fixture exact identity/events empty
product local fixture reject
package top-level side-effect fixture reject

23.4 HTTP cases

request validation before fetch
final invariant after auth patch
request 256KiB/default and hard +1
response/problem byte bounds
redirect/media/status/body policy
required/optional/none body
success/problem validator
scope fence before/after response
safe/idempotent retry
keyed dispatch-loss no retry
never retry
full jitter deterministic source
Retry-After ceiling/deadline
401 no hidden replay
caller/route/scope/deadline cancellation
NOT_STARTED vs MAYBE_APPLIED
inspect recovery projection

23.5 Router cases

static route registration
lazy module
path/search codec duplicate/unknown/bounds
no direct fetch/QueryClient/adapter import
loader/action server-data reject
focus/scroll/title
route unmount lease cleanup
chunk release/contractSet recovery
reload guard once
dirty navigation/effect-unknown blocker

23.6 Query/scope cases

one QueryClient
fixed profile registry only
required measureResult
measure throw/non-finite/negative/overflow
item/byte ceiling
scope FENCED synchronous
old snapshot immediately false
cancel/clear/validator/realtime/offline exact ordering
late success discarded
identity bounds/active lease eviction
retry false
memory-only/no persistence
304 with/without current cache

23.7 Mutation cases

three duplicate policies
same input different scope not joined
join waiter bound
optimistic default none
layer count/single/total bytes
out-of-order commit/rollback
effect unknown no arbitrary rollback
success seed/invalidation ordering
local invalidation failure does not resend
form dirty state clearing only confirmed

23.8 Cross-context cases

registry bounds/fanout/fanin
no key/data on wire
BroadcastChannel primary
localStorage fallback/storageArea
same-tab local dispatch
duplicate/lower/gap
release/scope epoch
mutation coalescing
overflow full stale
logout/dispose leak
two-page Chromium/Firefox/WebKit semantics

23.9 Realtime cases

zero contribution no connection/listener
one host/multiple leases
OPEN_UNRESUMED no effect
recovery barrier
500ms/30s/10/5m/60s reconnect policy
offline wait
hidden 30s/no hidden poll
visible recovery
unknown/malformed/schema/mapper/scope
64KiB event +1
queue/dedupe bounds
conflict/gap/expired cursor
single-writer live/poll handoff
effect before checkpoint
checkpoint failure after effect -> recovery
5s subscription grace
StrictMode acquire/release
logout/dispose/bfcache

23.10 Generic Worker cases

unselected no worker
protocol/version/generation
input/output bounds
queue count/bytes/FIFO
queued cancellation
active cooperative cancellation
250ms terminate
stale result fence
transfer ownership/detached buffer
crash 3/5m then fatal
idle 60s
fallback exact
no network/DOM/credential

23.11 Service Worker cases

null selection no lookup/register/cache
ACTIVE first-render idempotent registration
module/scope/updateViaCache
ACTIVE+DISABLED exact-owned unregister/cache retain
foreign registration ownership mismatch
ACTIVE -> REMOVE -> PURGE -> null
install all-or-nothing
asset count/item/set/integrity/media
waiting no blind skipWaiting
dirty/effect-unknown activation block
multi-client drain/30s missing client
no clients.claim
first install reload-to-enable
update 6h/manual rate
controllerchange guard
network-only navigation/config/manifest/API
exact static hit before API base root
unknown GET no runtime cache
current+previous retention
owned parser cleanup only
Web Push handler composition
future protocol mismatch

23.12 Offline/Sync cases

unselected no DB/listener
operation not KEYED/recovery missing reject
package identity/digest
request/count/dataset bounds
transaction complete
lease acquire/renew/release/takeover
LEASED expiry -> PENDING
SENDING expiry -> EFFECT_UNKNOWN
single sender/batch 10/30s
retry/backoff/attempt cap
auth/package/scope fence
ACKED payload delete
retention
unsupported Sync foreground works
Sync event sends zero command
wake message only
crash/reload restore
no credential persistence
removal unresolved command no silent purge

23.13 browser result vocabulary

SUPPORTED_AND_PASSED
SUPPORTED_AND_FAILED
UNSUPPORTED_WITH_FALLBACK_PASSED
UNSUPPORTED_WITHOUT_REQUIRED_CAPABILITY
ENVIRONMENT_UNAVAILABLE

unsupported is not ordinary pass. Background Sync requires foreground fallback pass.

23.14 leak inspection

lifecycle suite terminal checks:

active listeners = 0 except declared singleton
active timers = 0
open generic workers = 0
open realtime transports = 0
subscription refs = 0
queued worker/realtime task = 0
active IDB transaction = 0
owned abort controller settled
unhandled rejection = 0

persistent Service Worker registration is inspected according to selection mode, not counted as generic leak.


24. Source 배치와 migration

24.1 existing files to modify

src/bootstrap/runtime-config-schema.ts
src/bootstrap/load-runtime-config.ts
src/bootstrap/load-release-manifest.ts
src/bootstrap/create-runtime-composition.ts
src/bootstrap/composition-root.ts
src/bootstrap/runtime-adapters.ts
src/bootstrap/runtime-application.tsx
src/bootstrap/main.tsx
src/contracts/env.ts
src/contracts/release-tokens.ts
src/contracts/storage-keys.ts
src/contracts/api-operations.ts
src/contracts/rest-profiles.ts
src/contracts/server-state.ts
src/contracts/server-state-scope.ts
src/contracts/query-invalidation.ts
src/contracts/realtime-events.ts
src/contracts/realtime-streams.ts
src/adapters/http/bounded-json.ts
src/adapters/http/client.ts
src/adapters/http/request-builder.ts
src/adapters/http/retry-policy.ts
src/adapters/query-cache/server-state-scope-runtime.ts
src/adapters/query-cache/tanstack-cache-coordinator.ts
src/adapters/cross-context-invalidation/browser-cross-context-host.ts
src/adapters/cross-context-invalidation/browser-cross-context-invalidation.ts
src/adapters/cross-context-invalidation/index.ts
src/presentation/adapters/query/application-query.ts
src/presentation/adapters/query/optimistic-layer-runtime.ts
src/presentation/routes/app-router.tsx
src/presentation/routes/navigation-policy.ts
src/presentation/routes/platform-route-codecs.ts
src/presentation/routes/route-codecs.ts
src/presentation/routes/route-contract.ts
src/presentation/routes/route-input.tsx
src/presentation/routes/route-runtime.tsx
src/features/installed-feature-contracts.ts
src/features/installed-feature-adapters.ts
src/features/installed-feature-runtimes.tsx
scripts/generate-build-manifest.ts
vite.config.ts
tsconfig.json
tsconfig.app.json
package.json
.gitignore
eslint.config.ts
vitest.config.ts
playwright.capabilities.config.ts
config/hosting/cache-policy.json
public/config.json
public/release-manifest.json

24.2 new common files

src/contracts/contract-set.ts
src/contracts/contract-set-canonical.ts
src/contracts/external-contract-runtime.ts
src/contracts/runtime-capabilities.ts
src/contracts/service-worker.ts
src/contracts/web-worker.ts
src/contracts/offline-command.ts
src/bootstrap/read-bounded-boot-json.ts
src/bootstrap/register-service-worker.ts
src/features/installed-contract-contributions.ts
src/features/installed-runtime-capabilities.ts
src/adapters/http/bounded-body-reader.ts
src/adapters/http/http-execution-v3.ts
src/adapters/http/http-effect-certainty.ts
src/adapters/http/http-contract-bridge.ts
src/adapters/platform/browser-lifecycle.ts
scripts/build-frontend.ts
scripts/generate-contract-set.ts
scripts/generate-service-worker-assets.ts
vite.service-worker.config.ts
tsconfig.web-worker.json
tsconfig.service-worker.json
.generated/frontend-runtime/contract-set.ts
.generated/frontend-runtime/service-worker-build-info.ts
.generated/frontend-runtime/service-worker-assets.ts

.generated/frontend-runtime은 build-time derived source다. 사람이 수정하지 않고 build 시작 시 전체 삭제 후 재생성한다. Git에 commit하지 않는다.

24.3 optional source files

Generic Worker selected only:

src/application/ports/web-worker/*
src/adapters/web-worker/*

Service Worker active/removal selected:

src/adapters/service-worker/*

Offline Commands selected only:

src/application/ports/offline-command/*
src/adapters/offline-command/*

24.4 feature files

src/features/<feature>/contracts/<service>-contract-contribution.ts
src/features/<feature>/contracts/<feature>-mapper.ts
src/features/<feature>/contracts/<feature>-realtime-contribution.ts
src/features/<feature>/adapters/<feature>-http-gateway.ts
src/features/<feature>/application/<feature>-api.ts
src/features/<feature>/presentation/use-<feature>.ts

path token replacement is deterministic.

<feature> = InstalledContractContribution.featureId exactly
            lowercase kebab-case regex already validated

<service> = external package name의 slash 뒤 segment
            lowercase npm segment 그대로

팀이 임의 abbreviation/pluralization을 선택하지 않는다. 파일 충돌이 나면 package contribution을 한 feature 안의 named subdirectory로 분리하되 token 자체를 바꾸지 않는다.

24.5 architecture rule amendments

static checks must reject:

@org-contracts import outside feature contract contribution
presentation direct fetch
presentation direct adapters/QueryClient
application React/TanStack/browser API
generic Worker fetch/WebSocket/EventSource
separate Service Worker registration
Service Worker API response cache
runtime deployed spec/registry lookup
product TEMPLATE_FIXTURE
Router loader/action server-data call

24.6 Runtime Config/manifest migration

RC-1 add V2 schema/parser and bounded reader
RC-2 support V1/V2 reader, current writer V1
RC-3 generate contractSet and writer V2
RC-4 ignore accepted V1 scalar only in V1 input
RC-5 all fixtures/public files V2
RC-6 remove V1 parser/scalar after compatibility window

24.7 HTTP migration

HTTP-1 normalize current local reference fixture into contribution
HTTP-2 introduce external package contribution interface
HTTP-3 bridge current ApiOperationV2 reader
HTTP-4 add execution V3/outcome/certainty
HTTP-5 move feature gateway one operation at a time
HTTP-6 remove duplicate local path/status/type
HTTP-7 retire V2 writer after all installed operations V3

24.8 Query migration

Q-1 fixed profile registry
Q-2 required measureResult for current reference definitions
Q-3 scope lifecycle FENCED/READY
Q-4 commit fence/result budget
Q-5 many-to-many invalidation registry
Q-6 mutation duplicate/optimistic exact policies

24.9 Realtime migration

RT-1 keep common source unchanged where possible
RT-2 external event contract bridge
RT-3 feature effect contribution
RT-4 optional runtime host integration
RT-5 no product selection -> no bootstrap composition

24.10 Service Worker migration/removal

first adoption:

SW-1 protocol/build-info virtual module
SW-2 physical entry/handler composition
SW-3 static asset manifest/cache
SW-4 page controller/waiting/drain
SW-5 first-install/update lifecycle

removal:

Release N   mode ACTIVE
Release N+1 mode REMOVE_REGISTRATION
Release N+2 mode PURGE_OWNED_RESOURCES after rollback window
Release N+3 null

24.11 optional source zero-side-effect

optional source may exist as AVAILABLE_NOT_COMPOSED, but production bootstrap import is absent unless static selection references it.

Vite module inventory must show:

  • unselected worker entry absent;
  • unselected Service Worker active entry absent;
  • unselected offline adapter absent from production runtime graph where tree-shaking/static import boundary permits;
  • recipe/test source is not production import.

24.12 sample/reference removal

removing src/features/reference-feature must not remove common runtime.

removal sequence:

reference feature directory
-> reference template contribution
-> reference route/messages/runtime registration
-> reference tests
-> no remaining import/string in production bundle

common external contract consumer interfaces remain.


25. 구현 순서와 stop condition

25.1 principle

각 phase는 independently reviewable runtime increment다. API/backend contract authoring과 CI workflow는 이 순서에 포함하지 않는다.

Phase A — authority cleanup

  1. old generic design을 non-authoritative로 표시;
  2. this document reference add;
  3. external package ownership boundary document;
  4. current source path inventory freeze.

stop condition:

  • current repository snapshot/path가 문서와 다르면 후속 code 금지;
  • contract package format이 library design과 다르면 contribution interface부터 amend.

Phase B — contract package consumer foundation

  1. RuntimeValidator adapter;
  2. normalized contribution types;
  3. installed contribution composer;
  4. fixture vs external provenance;
  5. validation/duplicate checks;
  6. reference fixture normalization.

exit:

  • package import one path;
  • product fixture prohibited;
  • no runtime network/package lookup.

Phase C — contractSet and boot V2

  1. canonical bytes/digest;
  2. Release Manifest V2 schema;
  3. bounded boot reader;
  4. Runtime Config V2;
  5. contractSet boot check;
  6. partial cleanup stack;
  7. public fixture migration.

exit:

  • scalar API contract version no longer writer source;
  • boot body bounded/deadline;
  • product mount gated by contractSet.

Phase D — HTTP execution V3

  1. request/response hard ceilings;
  2. package descriptor bridge;
  3. final invariant after credential patch;
  4. outcome/failure union;
  5. total deadline/retry owner;
  6. effect certainty;
  7. current reference operation migration.

exit:

  • direct/duplicate path/status schema removed for migrated operation;
  • keyed uncertain command never automatic replayed.

Phase E — Router/Query/Mutation lifecycle

  1. Router no-server-data rule;
  2. fixed Query profiles;
  3. required measureResult;
  4. scope FENCED/READY exact ordering;
  5. commit fence;
  6. invalidation many-to-many;
  7. duplicate mutation coordinator;
  8. bounded optimistic layers.

exit:

  • old scope late result cannot commit;
  • Query/Mutation retry false;
  • local invalidation failure cannot resend command.

Phase F — Cross-context

  1. registry bounds;
  2. protocol/epoch envelope;
  3. BroadcastChannel;
  4. localStorage fallback;
  5. gap/full-stale;
  6. scope/logout lifecycle.

exit:

  • no data/key on wire;
  • two-page semantics;
  • disposal leak zero.

Phase G — Realtime product bridge

  1. external event validator bridge;
  2. installed realtime contribution;
  3. host lifecycle;
  4. subscription lease bridge;
  5. recovery/effect/checkpoint ordering;
  6. no-selection zero side effect.

stop condition:

  • no exact endpoint/event/recovery contribution -> remain NOT_SELECTED;
  • common runtime is not enough to compose.

Phase H — Generic Worker optional

execute only when a selected task exists.

  1. task port/protocol;
  2. group worker runtime;
  3. queue/cancel/crash;
  4. fallback;
  5. browser behavior.

stop condition:

  • no profiling/owner/task contribution -> do not implement production entry.

Phase I — Service Worker reference runtime

  1. static selection/removal mode;
  2. one physical entry;
  3. protocol/registration;
  4. static cache;
  5. waiting/client drain;
  6. first install/update;
  7. runtime disabled unregister;
  8. removal cleanup.

exit:

  • no API/navigation cache;
  • no blind activation/claim;
  • persistent removal path proven.

Phase J — Offline Command optional

execute only when product operation is selected.

  1. operation allowlist/record codec;
  2. IDB repository;
  3. foreground sender lease;
  4. state transitions;
  5. effect unknown recovery;
  6. retention/summary;
  7. optional Sync wake handler;
  8. removal.

stop condition:

  • no KEYED+recovery package descriptor -> remain NOT_SELECTED;
  • no product conflict/recovery UX -> do not compose.

Phase K — final integration review

  1. lifecycle shutdown/bfcache;
  2. security/redaction;
  3. all behavior cases;
  4. source/path/document consistency;
  5. old compatibility reader removal only after no caller.

26. 완료 기준

26.1 common

  • current repository path/architecture retained;
  • no parallel platform layer;
  • no backend/API/event source ownership duplication;
  • no CI design included;
  • optional unselected side effect zero;
  • exact lifecycle/disposal;
  • bounded input/output/queue/deadline;
  • typed failure and uncertainty;
  • current scope fence;
  • secret/privacy rules;
  • source/removal path.

26.2 external contract consumer

  • exact immutable package pin;
  • one import location;
  • fixture/product provenance separated;
  • normalized descriptor/validator;
  • package validation bounds;
  • local product schema duplication absent;
  • runtime registry/deployed docs lookup absent;
  • mapper boundary.

26.3 contractSet/boot

  • V2 manifest;
  • canonical length-prefixed digest;
  • fixture excluded;
  • Runtime Config scalar removed;
  • 64KiB/1MiB/5s bounded read;
  • strict URL/media/JSON/schema;
  • reverse partial cleanup;
  • safe retry once.

26.4 HTTP

  • package descriptor-driven;
  • request/response bounds;
  • final invariant after auth;
  • redirect reject;
  • total deadline;
  • transport-only retry;
  • body/status/media validation;
  • effect certainty;
  • keyed uncertainty no automatic resend;
  • scope fence.

26.5 Router

  • static route/codec;
  • no server-data loader/action;
  • no direct fetch/cache;
  • lazy/focus/scroll/error;
  • chunk contractSet recovery;
  • dirty/effect-unknown blocker.

26.6 Query/Mutation

  • one QueryClient;
  • fixed profiles;
  • required result measurement;
  • scope exact reset order;
  • late result discard;
  • memory-only;
  • bounded identity/invalidation;
  • duplicate coordinator;
  • optimistic default none/bounded layer;
  • success/failure ordering.

26.7 Realtime

  • existing common runtime reused;
  • product contribution exact;
  • open/resume separated;
  • reconnect policy exact;
  • hidden/offline lifecycle;
  • validation pipeline;
  • queue/dedupe bounds;
  • gap recovery;
  • effect-before-checkpoint;
  • ref-counted cleanup;
  • zero selection side effect.

26.8 Generic Worker

  • explicit selected task;
  • semantic port;
  • one worker per group;
  • protocol validation;
  • queue/input/output/deadline;
  • cancellation/terminate;
  • crash budget;
  • stale fence;
  • no network/credential;
  • fallback exact.

26.9 Service Worker

  • one physical registration;
  • post-mount module registration;
  • build/protocol handshake;
  • all-or-nothing static cache;
  • navigation/API/config/manifest network-only;
  • waiting/client drain;
  • no blind skipWaiting/clients.claim;
  • first install reload-to-enable;
  • runtime disabled exact unregister;
  • ACTIVE→REMOVE→PURGE→null;
  • foreign resource protection.

26.10 Offline/Background Sync

  • product operation selected;
  • external KEYED/recovery descriptor;
  • IDB transaction-complete;
  • exact bounds;
  • single sender lease;
  • SENDING crash -> EFFECT_UNKNOWN;
  • no automatic unknown resend;
  • foreground fallback;
  • Sync wake only;
  • retention/recovery/removal.

27. 위험 등록부

ID 위험 차단 결정 잔여 위험
FRT-R001 frontend local API schema가 두 번째 SSOT external package one import/provenance package publication quality는 external owner
FRT-R002 scalar contract version으로 multi-service mismatch contractSet V2 provider compatibility는 external release system
FRT-R003 boot body memory amplification streaming byte bound/deadline browser/network implementation variance
FRT-R004 auth patch가 URL/method/header 변조 final invariant recheck external auth adapter defect
FRT-R005 duplicate retry amplification HTTP-only retry owner user repeated intent remains product UX
FRT-R006 command timeout duplicate effect MAYBE_APPLIED/inspect/no resend server inspect may remain unavailable
FRT-R007 Router/Query dual cache loader server-data prohibition future SSR requires separate design
FRT-R008 old account late result leak synchronous scope FENCED/commit fence third-party widget outside scope
FRT-R009 result memory blowup required measure/fixed profiles feature estimator defect
FRT-R010 cross-tab stale data replication hint-only protocol hints can be lost; refetch recovery
FRT-R011 reconnect storm full jitter/attempt/episode bounds provider-wide outage
FRT-R012 connection open mistaken as recovered OPEN_UNRESUMED/barrier server recovery semantics external
FRT-R013 malformed event corrupts state validator/mapper/gap recovery malicious volume before edge controls
FRT-R014 subscription leak under StrictMode ref-count + 5s grace browser process crash
FRT-R015 Worker queue/crash heap amplification exact bounds/restart budget heavy task still impacts CPU/battery
FRT-R016 multiple Service Worker registrations one physical entry existing foreign registration conflict
FRT-R017 stale worker mixed with new page drain/no claim/reload handshake user may postpone update
FRT-R018 service worker removed from source but persists staged removal modes clients not revisiting app remain registered
FRT-R019 Cache Storage poisoning/stale HTML static manifest only/no navigation cache immutable asset host misconfiguration
FRT-R020 offline command duplicate after crash SENDING->EFFECT_UNKNOWN external reconciliation may need human
FRT-R021 Background Sync assumed reliable wake-only/foreground fallback browser may throttle background work
FRT-R022 logout leaves old durable data visible scope partition fence physical cleanup may be delayed
FRT-R023 diagnostics leak closed allowlist/redaction vendor SDK internal behavior
FRT-R024 bfcache resumes stale release manifest refresh/recovery offline manifest fetch unavailable
FRT-R025 over-design installs optional capability unused static selection/stop conditions future teams may bypass process

28. Normative decision ledger

Decision ID 결정 정본
FRT-D001 기존 repository layers 유지 §1.1–§1.2
FRT-D002 backend/API/event source design 제외 §0.3
FRT-D003 CI design 제외 §0.4
FRT-D004 immutable external package only §4
FRT-D005 package import one feature contribution path §4.1
FRT-D006 template fixture HTTP-only and contractSet excluded §4.8–§4.9
FRT-D007 scalar API contract version removed §5.1
FRT-D008 length-prefixed contractSet canonical digest §5.3
FRT-D009 Runtime Config may disable, never enable source-absent capability §3.5, §6.1
FRT-D010 boot 64KiB/1MiB/5s §6.4–§6.5
FRT-D011 boot reverse cleanup §6.8
FRT-D012 descriptor-driven HTTP execution §7.1–§7.2
FRT-D013 request/response hard bounds §7.3
FRT-D014 transport-only retry §8.1
FRT-D015 250ms/2s/full-jitter/max2 HTTP retry §8.2
FRT-D016 401 no hidden replay §8.4
FRT-D017 dispatch loss MAYBE_APPLIED §8.7–§8.9
FRT-D018 Router server-data loader/action prohibited §9.2
FRT-D019 one QueryClient §10.1
FRT-D020 four fixed Query profiles §10.2
FRT-D021 required feature-owned measureResult, no fallback §10.3–§10.4
FRT-D022 synchronous scope FENCED exact order §10.5–§10.6
FRT-D023 Query persistence disabled §10.10
FRT-D024 mutation duplicate default reject §11.2
FRT-D025 optimistic default none, layer bounds §11.4–§11.5
FRT-D026 cross-tab hint-only, many-to-many bounds §12
FRT-D027 existing realtime runtime reused §13.1
FRT-D028 product realtime explicit contribution §13.2–§13.6
FRT-D029 single writer live XOR polling §13.8
FRT-D030 OPEN_UNRESUMED separate §14.1–§14.2
FRT-D031 realtime reconnect 500ms/30s/10/5m/60s §14.3
FRT-D032 hidden 30s then close, no hidden polling §14.5
FRT-D033 event hard limits reuse current constants §15.1
FRT-D034 effect before checkpoint §15.7
FRT-D035 subscription 5s grace, scope immediate §15.8–§15.9
FRT-D036 generic Worker default not selected §16.2
FRT-D037 one Worker per group, lazy/60s idle §16.5
FRT-D038 Worker queue/cancel/crash exact policy §16.8–§16.10
FRT-D039 Worker no network/credential §16.13
FRT-D040 one physical Service Worker §17.1
FRT-D041 Service Worker after first committed render §17.5
FRT-D042 ACTIVE/REMOVE/PURGE/null lifecycle §17.3
FRT-D043 disabled exact-owned unregister, cache retained §17.6
FRT-D044 no blind skipWaiting, no clients.claim §17.10–§17.12
FRT-D045 static asset cache only §18
FRT-D046 navigation/API/config/manifest network-only §18.5–§18.8
FRT-D047 Offline queue selected only with KEYED recovery package §19.3
FRT-D048 offline records 1000/50MiB/256KiB §19.5
FRT-D049 sender lease 30s/renew10s/batch10/30s §19.9
FRT-D050 SENDING crash -> EFFECT_UNKNOWN §19.12
FRT-D051 Background Sync wake-only §19.18
FRT-D052 lifecycle shutdown exact order §20.3
FRT-D053 no unload correctness §20.6
FRT-D054 closed diagnostics allowlist §22
FRT-D055 behavior verification separate from CI §23
FRT-D056 implementation phase stop conditions §25

부록 A. Numeric constants

A.1 Boot

Constant Value
Runtime Config bytes 65,536
Release Manifest bytes 1,048,576
each boot request deadline 5,000ms
boot retry count 1
manifest URL bytes 256

A.2 HTTP

Constant Value
default request 262,144 bytes
hard request 1,048,576 bytes
default response 1,048,576 bytes
hard response 8,388,608 bytes
problem body 65,536 bytes
query string 8,192 bytes
final URL 16,384 bytes
default deadline 10,000ms
hard deadline 60,000ms
retry base 250ms
retry local max 2,000ms
Retry-After max 5,000ms
retries 2

A.3 Query

Profile stale gc items bytes
DETAIL_STANDARD 30s 5m 1 256KiB
LIST_STANDARD 15s 5m 200 1MiB
LOOKUP_STABLE 5m 30m 500 2MiB
VOLATILE_STATUS 0 1m 1 64KiB

identity registry:

Constant Value
depth 12
nodes 512
string bytes 2,048
identity bytes 16,384
entries 4,096
canonical memory 4MiB

A.4 Invalidation

Constant Value
topics 256
namespaces 256
edges 1,024
topic fan-out 64
namespace fan-in 64
pulse bytes 1,024
pulse TTL 60s

A.5 Realtime

Constant Value
event bytes 64KiB
payload depth 16
payload nodes 4,096
queue events 256
queue bytes 4MiB
dedupe entries 2,048
dedupe bytes 4MiB
dedupe TTL 10m
reconnect base 500ms
reconnect max 30s
attempts 10
episode 5m
stable reset 60s
hidden grace 30s
subscription grace 5s
stream drain 2s

A.6 Web Worker

Constant Value
groups 16
tasks/group 32
active groups 4
queued/group 32
queued bytes/group 16MiB
default input/output 1MiB
hard input/output 8MiB
default deadline 5s
hard deadline 30s
cancel grace 250ms
idle terminate 60s
restarts 3/5m
transferables 16

A.7 Service Worker/static cache

Constant Value
assets 256
single asset 2MiB
set 5MiB
fetch concurrency 4
install deadline 60s
client drain 30s
update check 6h
retained previous cache 1

A.8 Offline Commands

Constant Value
operations 64
default request 256KiB
hard request 1MiB
records 1,000
dataset 50MiB
sender lease 30s
renew 10s
batch 10
batch window 30s
parallel send 1
retry base 1s
retry max 5m
attempts 10
ordinary retention 7d
conflict/unknown 30d
Sync re-register minimum 1m

부록 B. 금지 구현 목록

create src/platform/* parallel runtime
copy OpenAPI/AsyncAPI/JSON Schema into product frontend
import backend Java DTO/module
fetch deployed /v3/api-docs during build/runtime
resolve Schema Registry latest at runtime
use mutable package tag/range
let component/hook call raw fetch/socket/Worker/IndexedDB
let Router loader create server cache or fetch
enable TanStack retry while HTTP transport retries
retry keyed command after dispatched response loss
interpret abort as server rollback
store generated wire DTO directly in Query cache
use JSON.stringify as generic Query result size fallback
persist Query payload in Web Storage by default
broadcast Query key/data/input/cursor
open one realtime connection per component
apply event before validation/scope/gap checks
write checkpoint before effect
continue applying deltas after gap/overflow
use hidden polling automatically
create generic Worker for network I/O
allow Worker credential/fetch/WebSocket
create Blob/data URL Worker
register PWA and Web Push workers separately for same scope
call skipWaiting without client drain
call clients.claim in baseline
cache index.html navigation fallback
cache API/private/auth responses in Cache Storage
treat unregister as cache deletion
switch Service Worker ACTIVE directly to null
unregister foreign registration by scope prefix guess
send authenticated command from baseline Background Sync
reset expired SENDING record to PENDING
manual resend EFFECT_UNKNOWN without inspect
store token/URL/header/cursor in diagnostics
use unload/sendBeacon as correctness boundary
claim unsupported browser capability as pass
claim COMPOSED means production/provider-ready

부록 C. 플랫폼 참고 기준

구현 시 pinned browser/library version의 공식 문서를 다시 확인하되 본 문서의 repository decision을 임의 변경하지 않는다.


최종 판정

본 문서는 프론트엔드 저장소가 소유하지 않는 backend/API/event 계약 authoring과 CI orchestration을 제거하고, 현재 코드 구조 안에서 contract package 소비부터 HTTP, Router, Query/Mutation, realtime, Worker, Service Worker와 offline lifecycle까지 구현 결정을 닫는다.

구현자는 다음을 다시 선택하지 않는다.

source root
contract import boundary
contractSet format/digest
Runtime Config V2 shape
boot byte/deadline
HTTP retry owner/limits
mutation uncertainty
Router data ownership
Query profiles/result measurement
scope reset order
cross-tab wire
realtime reconnect/resume/queue
subscription cleanup
Worker topology/bounds
Service Worker registration/update/removal/cache policy
offline sender/state/retention
Background Sync role
shutdown order

외부 contract package가 필요한 의미를 제공하지 않으면 frontend가 가정으로 채우지 않는다. 해당 product contribution은 NOT_SELECTED 또는 INCOMPATIBLE 상태로 남기며, common runtime 구현은 계속 독립적으로 검증 가능해야 한다.