# Adapter Correctness and Refactoring Remediation Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Close every confirmed adapter correctness/lifecycle gap, preserve versioned compatibility, and only then extract cohesive state-machine/Saga boundaries or promote optional capabilities. **Architecture:** Native/browser/provider effects stay behind adapter-owned typed results. Logical effect certainty is monotonic, non-cooperative work remains tracked as `DRAINING`, external bindings are installed as exact immutable snapshots, and multi-store changes use versioned Saga/migration protocols rather than simulated cross-API transactions. The five detailed reviews under `docs/reviews/adapters/` are the finding-level source of truth; this plan fixes their execution order, interfaces, tests, deployment, and rollback. **Tech Stack:** TypeScript 7, Vitest 4, Fetch API, Cache Storage, IndexedDB, OPFS, Service Worker API, Web Locks, WebSocket/SSE, React 19, TanStack Query 5, Playwright. ## Global Constraints - Production source changes are TDD: introduce a focused failing test, observe the intended failure, implement the minimum behavior, then run focused and static gates. - Preserve application-facing native-free ports and closed Result/failure vocabularies. Raw URL, header, token, path, `File`, `Response`, `IDB*`, `Cache`, stream, endpoint, and provider error do not cross inward. - A dispatched mutation cannot later be reported as `NOT_STARTED`. Unknown native effects require read-back/reconcile, never optimistic rollback or automatic replay. - Abort/deadline fences late commits immediately; they do not justify dropping a still-running resource. Retain work until actual settlement and expose `DRAINING` where applicable. - Keep `AVAILABLE_NOT_COMPOSED`, `DESIGNED_NOT_IMPLEMENTED`, and `NOT_SELECTED` unchanged until the task explicitly requires promotion and all named browser/provider gates pass. - Preserve current public facades and persisted/wire V1 readers through the rollback window. New writes may move to V2 only through expand → dual-read/emit → old-writer drain → contract. - Never perform broad prefix/root/database cleanup. Parse exact ownership and delete only exact owned resources under bounded count/time/cursor limits. - Do not add generic middleware/interceptor, generic repository, automatic protocol downgrade, persistent telemetry queue, or unsafe in-memory substitute for Web Locks. - Refactor after characterization. File length alone is not a split criterion, and fixture expectations may not be changed merely because implementation moved. - Every task’s commit is optional for the executor but, when commits are requested, use only the files listed in that task and do not combine independent subsystems. --- ## Source documents and precedence 1. [Adapter review index](../../reviews/adapters/README.md) fixes cross-cutting decisions and subsystem order. 2. [Network/state](../../reviews/adapters/01-network-and-state.md), [realtime/RPC](../../reviews/adapters/02-realtime-and-browser-rpc.md), [storage/files](../../reviews/adapters/03-storage-and-browser-files.md), [browser transfer](../../reviews/adapters/04-browser-transfer.md), and [worker/push](../../reviews/adapters/05-service-worker-and-web-push.md) fix finding-specific behavior, signatures, tests, migration, and rollback. 3. [2026-08-01 HTTP/worker plan](./2026-08-01-http-worker-adapter-remediation.md) and [runtime-correctness plan](./2026-08-01-runtime-correctness-remediation.md) remain active. This plan adds gates; it does not mark their tasks complete. 4. If instructions appear to conflict, apply this order: current verified fail-close correction → existing contract-authority consolidation → versioned migration → behavior-preserving extraction → optional capability promotion. ## Finding-to-task coverage | Task | Finding IDs | | --- | --- | | 1 | containment and baseline only | | 2 | `N-01` | | 3 | `N-02` | | 4 | `N-03` | | 5 | `N-04`, `N-11` | | 6 | `STO-01` | | 7 | `STO-02`, browser proof for `STO-08` | | 8 | `STO-03`, `STO-04`, `STO-05` | | 9 | `N-05`, `N-09`, `N-10` | | 10 | `N-06`, `N-07`, `N-08` legacy compatibility path | | 11 | `R-02`, `R-03` | | 12 | `R-01`, `R-04`, `R-05`, `R-06`, promotion gate `R-07` | | 13 | `BT-PRE-01`~`05`, `BT-UP-01`~`04`, `BT-IMG-01`~`02`, `BT-X-01` | | 14 | `SW-URL-01`, `SW-01`~`SW-09` | | 15 | `WP-01`~`WP-07` | | 16 | `STO-06`, `STO-07`, `SW-10`, wire/data migrations from Tasks 6/13/15 | | 17 | `BT-UP-05`~`07`, `BT-IMG-03`~`04`, `GAP-01`~`03` | | 18 | all final evidence and rollback fixtures | --- ### Task 1: Establish containment, clean baseline, and red-test ledger **Files:** - Create: `docs/operations/adapter-remediation-ledger.md` - Read only: `src/bootstrap/runtime-adapters.ts` - Read only: `src/bootstrap/optional-runtime-host.ts` - Read only: product-specific composition roots outside the template default, if present **Interfaces:** - Consumes: the current availability/selection state and the review finding IDs. - Produces: a checked ledger with owner, activation state, red test, PR, rollout state, rollback trigger, and evidence link for every confirmed finding. - [ ] **Step 1: Record active capability exposure without changing composition** Run: ```bash rg -n "createBrowserOpfsRuntime|createBrowserFileRuntime|createPublicResponseCache|createBrowserRpcRuntime|createWebPush|createServiceWorker|createResumableUpload|createImageCdn" src recipes tests rg -n "AVAILABLE_NOT_COMPOSED|DESIGNED_NOT_IMPLEMENTED|NOT_SELECTED" docs/architecture src/bootstrap ``` Expected: template defaults remain uncomposed for optional storage/realtime/RPC/push/image capabilities. If a product-specific OPFS V1 writer is discovered, stop its **new write admission** through that product’s existing kill switch; keep read/export/reconcile available. Do not invent a template kill switch when no writer is composed. - [ ] **Step 2: Create the ledger with a fixed schema** Use this exact column set for every confirmed ID: ```markdown | ID | Activation | Red test command | Fix commit/PR | Rollout state | Rollback trigger | Evidence | | --- | --- | --- | --- | --- | --- | --- | ``` Initial rollout state is `NOT_STARTED`; planned gaps use `PROMOTION_BLOCKED`, not `DEFECT`. - [ ] **Step 3: Capture a fresh baseline** Run: ```bash corepack pnpm check:types corepack pnpm lint corepack pnpm check:architecture corepack pnpm test:unit ``` Expected: record exact exit codes and counts. Environment-level child-process failures must be copied verbatim into the ledger and must not be converted to adapter failures or ignored as green. - [ ] **Step 4: Commit documentation only** ```bash git add docs/operations/adapter-remediation-ledger.md git commit -m "docs: establish adapter remediation ledger" ``` --- ### Task 2: Restore V3 HTTP diagnostics and terminal failure telemetry (`N-01`) **Files:** - Modify: `src/adapters/http/http-execution-v3.ts` - Modify: `src/bootstrap/runtime-adapters.ts` - Modify: `src/features/reference-feature/adapters/create-reference-feature-input.ts` - Test: `tests/unit/http-execution-v3.test.ts` - Test: `tests/unit/runtime-adapters.test.ts` - Create: `tests/integration/http-execution-v3-observability.test.ts` - Modify: `docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md` **Interfaces:** - Consumes: installed operation ID, route ID, attempt count, duration, status, cancellation owner, and effect certainty from one logical V3 execution. - Produces: exactly one accepted diagnostic for every terminal outcome and exactly one `api.request.failed` telemetry event for terminal non-abort failures. ```ts export type HttpExecutionObservation = Readonly<{ routeId: string; operationId: string; diagnosticsOperation: string; outcome: HttpExecutionOutcome["kind"]; errorKind: string; status?: number; attemptCount: number; durationMs: number; effect: HttpEffectCertainty; cancellationOwner?: CancellationOwner; }>; export type HttpExecutionContext = Readonly<{ routeId: string; signal?: AbortSignal; scope: CacheScopeSnapshot; intent?: MutationIntent; }>; ``` - [ ] **Step 1: Add red integration cases** Add these exact cases to `http-execution-v3-observability.test.ts`: ```ts it("projects every V3 terminal outcome through the closed diagnostics allowlist", async () => {}); it("emits one failure telemetry event for a non-abort terminal failure", async () => {}); it("does not emit failure telemetry for caller cancellation or scope fencing", async () => {}); it("preserves the feature route id through the installed operation executor", async () => {}); it("cannot change the HTTP result when diagnostics or telemetry throws", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/integration/http-execution-v3-observability.test.ts tests/unit/http-execution-v3.test.ts tests/unit/runtime-adapters.test.ts ``` Expected: diagnostic projection fails on current `attempts`/`certainty`, route ID is absent, and terminal failure telemetry count is zero. - [ ] **Step 3: Implement the closed projection** Use only registered keys: `route_id`, `operation_id`, `operation`, `outcome`, `error_kind`, `http_status_group`, `attempt_count_bucket`, and `duration_bucket`. Do not forward raw attempt count, status, duration, URL, intent, key, input identity, or free-form certainty. If effect certainty is operationally required, add `effect_certainty` simultaneously to the contract allowlist, closed value validator, fixture, and ADR; otherwise omit it. - [ ] **Step 4: Run green and producer gates** ```bash corepack pnpm exec vitest run tests/integration/http-execution-v3-observability.test.ts tests/unit/http-execution-v3.test.ts tests/unit/runtime-adapters.test.ts corepack pnpm check:diagnostics corepack pnpm check:types:app corepack pnpm check:types:test ``` - [ ] **Step 5: Commit** ```bash git add src/adapters/http/http-execution-v3.ts src/bootstrap/runtime-adapters.ts src/features/reference-feature/adapters/create-reference-feature-input.ts tests/integration/http-execution-v3-observability.test.ts tests/unit/http-execution-v3.test.ts tests/unit/runtime-adapters.test.ts docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md git commit -m "fix: restore V3 HTTP observability" ``` --- ### Task 3: Make auth profiles authoritative and cancellation-cooperative (`N-02`) **Files:** - Modify: `src/contracts/rest-profiles.ts` - Modify: `src/contracts/external-contract-runtime.ts` - Modify: `src/application/ports/auth-session-port.ts` - Modify: `src/adapters/auth/external-session-adapter.ts` - Modify: `src/adapters/http/http-contract-bridge.ts` - Modify: `src/adapters/http/http-execution-v3.ts` - Modify: `src/bootstrap/runtime-adapters.ts` - Test: `tests/unit/rest-profile-contract.test.ts` - Test: `tests/unit/auth-session-adapter.test.ts` - Test: `tests/unit/http-execution-v3.test.ts` - Create: `tests/integration/http-execution-v3-auth-profile.test.ts` - Modify: `docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md` **Interfaces:** ```ts export type CredentialHeaderName = | "authorization" | "x-csrf-token" | "x-tenant-context"; export type RestAuthProfile = Readonly<{ authProfileId: string; transport: "ANONYMOUS" | "BEARER_HEADER" | "SAME_ORIGIN_COOKIE"; credentials: "omit" | "same-origin" | "include"; allowedCredentialHeaders: readonly CredentialHeaderName[]; requiredCredentialHeaders: readonly CredentialHeaderName[]; }>; export type AuthOperationContext = Readonly<{ signal: AbortSignal; deadlineAtMonotonicMs: number; }>; ``` - [ ] **Step 1: Add red tests for transport ownership** ```ts it("rejects an unknown auth profile during composition", () => {}); it("rejects credential attempts to replace Accept Content-Type or credentials", async () => {}); it("requires authorization for a bearer profile before fetch", async () => {}); it("forbids credential headers for an anonymous profile", async () => {}); it("bounds a non-cooperative credential owner by the operation lifetime", async () => {}); it("ignores a late credential completion after caller abort or scope fence", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/integration/http-execution-v3-auth-profile.test.ts tests/unit/rest-profile-contract.test.ts tests/unit/auth-session-adapter.test.ts tests/unit/http-execution-v3.test.ts ``` - [ ] **Step 3: Install profiles and restrict patches** Install a frozen exact auth-profile registry during composition. The profile owns Fetch `credentials` and required/allowed credential-header sets. Credential collaborators return proof headers only and receive `AuthOperationContext`. Missing/extra/transport-owned fields return `AUTH_INTEGRATION_FAILURE` with command effect `NOT_STARTED` and fetch count zero. For demo mode, inject a fixed non-secret demo authorization marker into `createDemoSessionAdapter`; do not weaken `REFERENCE_EXTERNAL_BEARER`. A truly anonymous backend requires a distinct anonymous installed contract/profile. - [ ] **Step 4: Run green and architecture gates** ```bash corepack pnpm exec vitest run tests/integration/http-execution-v3-auth-profile.test.ts tests/unit/rest-profile-contract.test.ts tests/unit/auth-session-adapter.test.ts tests/unit/http-execution-v3.test.ts tests/unit/runtime-adapters.test.ts corepack pnpm check:types corepack pnpm check:architecture ``` - [ ] **Step 5: Commit** ```bash git add src/contracts/rest-profiles.ts src/contracts/external-contract-runtime.ts src/application/ports/auth-session-port.ts src/adapters/auth/external-session-adapter.ts src/adapters/http/http-contract-bridge.ts src/adapters/http/http-execution-v3.ts src/bootstrap/runtime-adapters.ts tests/unit/rest-profile-contract.test.ts tests/unit/auth-session-adapter.test.ts tests/unit/http-execution-v3.test.ts tests/integration/http-execution-v3-auth-profile.test.ts docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md git commit -m "fix: enforce installed HTTP auth profiles" ``` --- ### Task 4: Preserve monotonic command-effect certainty across retries (`N-03`) **Files:** - Modify: `src/adapters/http/http-effect-certainty.ts` - Modify: `src/adapters/http/http-execution-v3.ts` - Test: `tests/unit/http-execution-v3.test.ts` - Test: `tests/integration/http-execution-contract.test.ts` - Modify: `docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md` **Interfaces:** ```ts export function joinMutationEffectCertainty( current: MutationEffectCertainty, observed: MutationEffectCertainty, ): MutationEffectCertainty; ``` Join order is conservative: `MAYBE_APPLIED` dominates `NOT_APPLIED` and `NOT_STARTED`; `NOT_APPLIED` dominates `NOT_STARTED`; `APPLIED_CONFIRMED` is terminal and cannot enter automatic retry. Queries remain `NOT_APPLICABLE`. - [ ] **Step 1: Add the exact red interleaving** Attempt 1 dispatches an idempotent command and receives 429, retry sleep resolves, the loop-entry scope check is current, then the pre-dispatch final-invariant scope check becomes false. Assert final `SCOPE_FENCED` with `MAYBE_APPLIED`, not `NOT_STARTED`. - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts ``` - [ ] **Step 3: Separate physical attempt state from logical execution history** Keep per-attempt state for local cleanup, but update one logical certainty accumulator at each dispatch/response/error boundary. Every final-invariant, cancellation, timeout, and scope-fence return reads the accumulator. Never reset it when beginning a retry. - [ ] **Step 4: Run green** ```bash corepack pnpm exec vitest run tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts corepack pnpm check:types:app corepack pnpm check:types:test ``` - [ ] **Step 5: Commit** ```bash git add src/adapters/http/http-effect-certainty.ts src/adapters/http/http-execution-v3.ts tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md git commit -m "fix: preserve command effect certainty across retries" ``` --- ### Task 5: Make telemetry disposal terminal (`N-04`, `N-11`) **Files:** - Modify: `src/adapters/telemetry/best-effort-telemetry.ts` - Modify: `src/adapters/diagnostics/bounded-diagnostics.ts` - Modify: `src/bootstrap/runtime-adapters.ts` - Test: `tests/unit/telemetry.test.ts` - Test: `tests/unit/diagnostics.test.ts` - Test: `tests/unit/runtime-adapters.test.ts` - Modify: `docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md` **Interfaces:** ```ts type TelemetryLifecycle = "ACTIVE" | "DISPOSED"; export interface BestEffortTelemetry { emit(event: TelemetryEvent): void; flush(): Promise; dispose(): void; } ``` - [ ] **Step 1: Add red lifecycle and capacity tests** ```ts it("drops queued events and invalidates scheduled callbacks on dispose", async () => {}); it("ignores emit after dispose", async () => {}); it("aborts an in-flight sink and prevents post-dispose rescheduling", async () => {}); it("joins an already active flush", async () => {}); it.each([Number.NaN, Number.POSITIVE_INFINITY, 0, -1, 1.5])("rejects invalid telemetry and diagnostics capacity %s", value => {}); it("runtime infrastructure disposal disposes telemetry first", () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/telemetry.test.ts tests/unit/diagnostics.test.ts tests/unit/runtime-adapters.test.ts ``` - [ ] **Step 3: Implement terminal disposal** Validate capacity as a safe integer within the documented absolute ceiling. `dispose()` changes lifecycle once, removes `pagehide`, clears queue, invalidates scheduled generations, aborts the current sink controller, and prevents late sink completion from scheduling more work. `flush()` returns the same active promise. Disposal does not emit recursive drop telemetry. - [ ] **Step 4: Run green and commit** ```bash corepack pnpm exec vitest run tests/unit/telemetry.test.ts tests/unit/diagnostics.test.ts tests/unit/runtime-adapters.test.ts corepack pnpm check:diagnostics git add src/adapters/telemetry/best-effort-telemetry.ts src/adapters/diagnostics/bounded-diagnostics.ts src/bootstrap/runtime-adapters.ts tests/unit/telemetry.test.ts tests/unit/diagnostics.test.ts tests/unit/runtime-adapters.test.ts docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md git commit -m "fix: terminate telemetry work on disposal" ``` --- ### Task 6: Repair the OPFS compensation Saga before new writes (`STO-01`) **Files:** - Modify: `src/application/ports/browser-file-storage/opfs-ports.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-protocol.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-client.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-runtime.ts` - Modify: `src/adapters/storage/opfs/opfs-byte-store-adapter.ts` - Modify: `src/adapters/storage/opfs/indexeddb-opfs-journal.ts` - Test: `tests/unit/opfs-byte-store.test.ts` - Test: `tests/unit/opfs-worker-runtime.test.ts` - Test: `tests/unit/indexeddb-opfs-journal.test.ts` - Modify: `docs/architecture/browser-file-and-origin-storage.md` - Modify: `docs/operations/browser-file-storage-recovery.md` **Interfaces:** ```ts declare const opfsPhysicalGenerationBrand: unique symbol; export type OpfsPhysicalGenerationId = string & { readonly [opfsPhysicalGenerationBrand]: "OpfsPhysicalGenerationId"; }; export type OpfsCleanupEffect = | Readonly<{ kind: "CLEANED" | "ALREADY_CLEAN" }> | Readonly<{ kind: "EFFECT_UNKNOWN" }>; export type OpfsPreparedObjectV2 = Readonly<{ physicalSchemaVersion: 2; physicalGenerationId: OpfsPhysicalGenerationId; descriptor: DurableObjectDescriptor; chunks: readonly OpfsChunkReference[]; }>; ``` - [ ] **Step 1: Add red crash/race tests** ```ts it("keeps PREPARING journal when compensating cleanup is aborted or unavailable", async () => {}); it("does not roll back journal after an unknown worker mutation effect", async () => {}); it("delayed stale cleanup cannot delete a reused logical generation", async () => {}); it("holds the OPFS mutation lease until exact physical cleanup completes", async () => {}); ``` The delayed cleanup test must gate T1 abort, allow T2 to create the same logical generation with a different physical token, resume T1, and prove T2 open/verify bytes still succeed. - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/opfs-byte-store.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/indexeddb-opfs-journal.test.ts ``` - [ ] **Step 3: Correct compensation ownership** Remove the client’s duplicate fire-and-forget abort. The coordinator issues one `abortPreparedPut` using a composition-owned bounded cleanup signal, not the already-aborted caller signal. Roll back journal/budget only after `CLEANED` or `ALREADY_CLEAN`; retain `PREPARING/FILES_READY` plus reservation on timeout, crash, malformed response, or `EFFECT_UNKNOWN`, and return `OBJECT_RECONCILE`. - [ ] **Step 4: Fence physical paths and cleanup** Write new staging/manifest paths with `physicalGenerationId`; v1 readers remain. Cleanup targets the exact transaction/token path and holds the origin mutation Web Lock through physical deletion. Releasing the lease before delete is forbidden. - [ ] **Step 5: Run green and storage gates** ```bash corepack pnpm exec vitest run tests/unit/opfs-byte-store.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/indexeddb-opfs-journal.test.ts corepack pnpm check:types:web-worker corepack pnpm check:types:app corepack pnpm check:browser-file-storage-boundaries ``` - [ ] **Step 6: Commit** ```bash git add src/application/ports/browser-file-storage/opfs-ports.ts src/adapters/storage/opfs/opfs-worker-protocol.ts src/adapters/storage/opfs/opfs-worker-client.ts src/adapters/storage/opfs/opfs-worker-runtime.ts src/adapters/storage/opfs/opfs-byte-store-adapter.ts src/adapters/storage/opfs/indexeddb-opfs-journal.ts tests/unit/opfs-byte-store.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/indexeddb-opfs-journal.test.ts docs/architecture/browser-file-and-origin-storage.md docs/operations/browser-file-storage-recovery.md git commit -m "fix: preserve OPFS recovery authority during cleanup" ``` Rollback: disable new V2 writes, retain v1+v2 readers and incomplete journals, reconcile exact tokens, and never downgrade/delete the journal database or OPFS root. --- ### Task 7: Execute only the canonical download target (`STO-02`, `STO-08` proof) **Files:** - Modify: `src/adapters/browser-files/download-delivery-adapter.ts` - Modify only if browser proof fails: `src/adapters/browser-files/browser-file-picker.ts` - Test: `tests/unit/browser-file-download.test.ts` - Test: `tests/unit/browser-file-picker.test.ts` - Test: `tests/browser-capabilities/browser-files.spec.ts` **Interfaces:** ```ts type ResolvedBrowserManagedTarget = Readonly<{ absoluteHref: string }>; function resolveBrowserManagedTarget( href: string, baseOrigin: string, policy: Readonly<{ allowCrossOrigin: boolean; allowQuery: boolean }>, ): BrowserDataResult; ``` - [ ] **Step 1: Add the hostile-base red test** Use `href="downloads/a"`, `baseOrigin="https://app.example"`, and a host/document base of `https://evil.example/`. Assert the handoff receives exactly `https://app.example/downloads/a` and the evil origin is never assigned. - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/browser-file-download.test.ts ``` - [ ] **Step 3: Return and execute the parsed URL** Replace the boolean validator with `resolveBrowserManagedTarget`; apply scheme/origin/query/hash/credential rules once, then pass `target.value.absoluteHref` to the host. Do not re-use the raw string after validation. - [ ] **Step 4: Characterize system-picker receiver binding in a real browser** Run: ```bash corepack pnpm exec playwright test --config playwright.capabilities.config.ts tests/browser-capabilities/browser-files.spec.ts ``` If the real `Window.showOpenFilePicker/showSaveFilePicker` throws `Illegal invocation`, introduce `SystemPickerHost { open; save? }` captured/bound to `window` at composition. If it does not reproduce in supported engines, leave source unchanged and record `STO-08` as rejected hypothesis with browser versions/evidence. - [ ] **Step 5: Run green and commit** ```bash corepack pnpm exec vitest run tests/unit/browser-file-download.test.ts tests/unit/browser-file-picker.test.ts git add src/adapters/browser-files/download-delivery-adapter.ts tests/unit/browser-file-download.test.ts tests/browser-capabilities/browser-files.spec.ts git commit -m "fix: execute canonical browser download targets" ``` Include picker files in the commit only if the browser red/green cycle required the receiver fix. --- ### Task 8: Restore public-cache policy and idempotent repair (`STO-03`, `STO-04`, `STO-05`) **Files:** - Modify: `src/adapters/cache-storage/public-cache-policy.ts` - Modify: `src/adapters/cache-storage/public-response-cache-adapter.ts` - Test: `tests/unit/public-response-cache.test.ts` - Modify: `docs/operations/client-cache-and-storage-recovery.md` **Interfaces:** ```ts function stageAvailability(dependencies: PublicCacheDependencies): BrowserDataFailure | null; function localMutationAvailability( dependencies: PublicCacheDependencies, operation: "CACHE_ACTIVATE" | "CACHE_DELETE", ): BrowserDataFailure | null; ``` - [ ] **Step 1: Add red policy, repair, and availability cases** ```ts it("rejects a policy that enables variants but strips Vary", () => {}); it("preserves Vary for every stored custom variant", async () => {}); it("restages an evicted entry even when the release marker remains", async () => {}); it("activates a verified prestaged release without a fetcher", async () => {}); it("cleans exact owned caches without a fetcher", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts ``` - [ ] **Step 3: Implement cross-field policy and shared verification** Reject composition when `allowedVaryHeaderNames.length > 0` and response headers omit `vary`. Preserve a validated `Vary` through sanitization. Extract one `verifyReleaseCandidate` used by stage fast-path and activation. A matching marker is a claim; missing/mismatched response deletes only that owned candidate and triggers network restage. Abort/unknown verification never returns stage success and never changes the active pointer. - [ ] **Step 4: Segregate dependencies** Stage requires cache storage, mutation lock, and fetcher. Activate/cleanup require cache storage and mutation lock only. Keep current+verified previous retention and exact ownership parsing. - [ ] **Step 5: Run green and commit** ```bash corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts corepack pnpm check:browser-file-storage-boundaries corepack pnpm check:types:app git add src/adapters/cache-storage/public-cache-policy.ts src/adapters/cache-storage/public-response-cache-adapter.ts tests/unit/public-response-cache.test.ts docs/operations/client-cache-and-storage-recovery.md git commit -m "fix: make public cache staging repairable" ``` --- ### Task 9: Harden state-sidecar keys, storage-event admission, and pagination (`N-05`, `N-09`, `N-10`) **Files:** - Modify: `src/adapters/query-cache/conditional-validator-store.ts` - Modify: `src/adapters/cross-context-invalidation/browser-cross-context-host.ts` - Modify: `src/adapters/cross-context-invalidation/browser-cross-context-invalidation.ts` - Modify: `src/contracts/storage-keys.ts` - Modify: `src/adapters/query-cache/cursor-pagination-runtime.ts` - Test: `tests/unit/conditional-validator-store.test.ts` - Test: `tests/unit/cross-tab-invalidation.test.ts` - Test: `tests/unit/cursor-pagination-runtime.test.ts` **Interfaces:** ```ts type ConditionalValidatorKeyTuple = readonly [ scopeFingerprint: string, definitionId: string, identityToken: string, representationVersion: number, ]; type StoragePulseEvent = Readonly<{ key: string | null; newValue: string | null; storageArea: "EXPECTED_LOCAL_STORAGE" | "OTHER_OR_UNKNOWN"; }>; ``` - [ ] **Step 1: Add four red groups** ```ts it("keeps colon-bearing validator tuples injective", () => {}); it("rejects storage pulses from another or unknown storage area", () => {}); it("returns PAGINATION_ABORTED when a non-cooperative page resolves after abort", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/conditional-validator-store.test.ts tests/unit/cross-tab-invalidation.test.ts tests/unit/cursor-pagination-runtime.test.ts ``` - [ ] **Step 3: Implement bounded exact admission** Encode the validated fixed tuple with `JSON.stringify`, not delimiter join. Capture native localStorage once and compare `StorageEvent.storageArea` by object identity; register the opaque pulse key policy in `storage-keys.ts`. Race `loadPage` against abort and recheck before page observation/accumulation; ignore late completion. - [ ] **Step 4: Run green and commit** ```bash corepack pnpm exec vitest run tests/unit/conditional-validator-store.test.ts tests/unit/cross-tab-invalidation.test.ts tests/unit/cursor-pagination-runtime.test.ts corepack pnpm check:architecture git add src/adapters/query-cache/conditional-validator-store.ts src/adapters/cross-context-invalidation/browser-cross-context-host.ts src/adapters/cross-context-invalidation/browser-cross-context-invalidation.ts src/contracts/storage-keys.ts src/adapters/query-cache/cursor-pagination-runtime.ts tests/unit/conditional-validator-store.test.ts tests/unit/cross-tab-invalidation.test.ts tests/unit/cursor-pagination-runtime.test.ts git commit -m "fix: harden bounded state sidecars" ``` --- ### Task 10: Harden the exported legacy HTTP rollback path (`N-06`, `N-07`, `N-08`) **Files:** - Modify: `src/contracts/mutation-intent.ts` - Modify: `src/application/ports/auth-session-port.ts` - Modify: `src/adapters/auth/external-session-adapter.ts` - Modify: `src/adapters/http/client.ts` - Modify: `src/adapters/http/bounded-json.ts` - Test: `tests/integration/http-client.test.ts` - Test: `tests/integration/auth-recovery.test.ts` - Create: `tests/unit/bounded-json-compatibility.test.ts` - Test: `tests/unit/bounded-body-reader.test.ts` **Interfaces:** - Consumes: the auth context from Task 3 and one exported `defineIdempotencyKey` validator shared by V2/V3. - Produces: a compatibility client that cannot replay a keyed command without a validated non-empty key and whose credential/body waits are lifetime-bounded. - [ ] **Step 1: Add red compatibility cases** ```ts it.each(["", " ", "bad\u0000key", "x".repeat(513)])("rejects invalid keyed command key %j before credentials and fetch", async key => {}); it("bounds a non-cooperative legacy credential owner by total deadline", async () => {}); it("ignores late auth recovery after the lifetime ends", async () => {}); it("keeps a closed result when reader cancel or releaseLock throws", async () => {}); it("cancels the body on legacy content-type mismatch", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/integration/http-client.test.ts tests/integration/auth-recovery.test.ts tests/unit/bounded-json-compatibility.test.ts tests/unit/bounded-body-reader.test.ts ``` - [ ] **Step 3: Reuse shared authorities** Reject a caller-supplied invalid key without trimming/regenerating, before credentials/timer/fetch, and preserve one key across physical retries. Pass/race the lifetime signal through credential and recovery. Delegate `readBoundedJson` to `bounded-body-reader` and map V3 reader codes to the existing V2 codes; isolate cancel/release errors and cancel on content-type mismatch. - [ ] **Step 4: Run green and commit** ```bash corepack pnpm exec vitest run tests/integration/http-client.test.ts tests/integration/auth-recovery.test.ts tests/unit/bounded-json-compatibility.test.ts tests/unit/bounded-body-reader.test.ts corepack pnpm check:types git add src/contracts/mutation-intent.ts src/application/ports/auth-session-port.ts src/adapters/auth/external-session-adapter.ts src/adapters/http/client.ts src/adapters/http/bounded-json.ts tests/integration/http-client.test.ts tests/integration/auth-recovery.test.ts tests/unit/bounded-json-compatibility.test.ts tests/unit/bounded-body-reader.test.ts git commit -m "fix: harden the legacy HTTP rollback path" ``` Do not delete the V2 client in this task. Deletion requires zero production callers, equivalent V3 evidence, and an expired rollback window. --- ### Task 11: Retain realtime work through actual settlement (`R-02`, `R-03`) **Files:** - Modify: `src/application/ports/realtime/event-authority.ts` - Modify: `src/application/ports/realtime/index.ts` - Modify: `src/adapters/realtime/stream-coordinator.ts` - Modify: `src/adapters/realtime/live-poll-handoff-coordinator.ts` - Modify: `src/adapters/realtime/index.ts` - Test: `tests/unit/realtime/stream-coordinator.test.ts` - Test: `tests/unit/realtime/live-poll-handoff-coordinator.test.ts` - Modify: `docs/architecture/realtime-events-web-push-and-bounded-polling.md` - Modify: `docs/architecture/decisions/VD-28-realtime-events-web-push-and-bounded-polling.md` **Interfaces:** ```ts export type RealtimeStreamLifecycle = "OPEN" | "DRAINING" | "CLOSED"; export type RealtimeStreamTaskLimits = Readonly<{ effectTimeoutMs: number; recoveryTimeoutMs: number; drainTimeoutMs: number; }>; export type RealtimeStreamCoordinator = Readonly<{ // existing methods remain close(): Promise>; }>; ``` - [ ] **Step 1: Add red never-settling cases** ```ts it("keeps the stream DRAINING until a non-cooperative effect settles", async () => {}); it("bounds non-cooperative recovery and rejects its late checkpoint", async () => {}); it("close returns a bounded timeout while tracked tasks remain DRAINING", async () => {}); it("tracks a retired active writer after handoff queue overflow", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/realtime/stream-coordinator.test.ts tests/unit/realtime/live-poll-handoff-coordinator.test.ts ``` - [ ] **Step 3: Add orthogonal lifecycle and retained registries** Keep freshness `UNKNOWN/CURRENT/STALE/RESYNCING` separate from lifecycle. On effect/recovery deadline, revoke commit capability and abort immediately, return bounded `IDLE_TIMEOUT`, retain the underlying task, and reject new work while DRAINING. On handoff fail-close, move active/probe/quiescing candidates into a deduplicated retired-writer set before clearing active references. Only actual settlement transitions DRAINING to STALE or CLOSED. - [ ] **Step 4: Run green and commit** ```bash corepack pnpm exec vitest run tests/unit/realtime/stream-coordinator.test.ts tests/unit/realtime/live-poll-handoff-coordinator.test.ts tests/unit/realtime/websocket-connection.test.ts tests/unit/realtime/fetch-sse-connection.test.ts corepack pnpm check:realtime-boundaries corepack pnpm check:types:app git add src/application/ports/realtime/event-authority.ts src/application/ports/realtime/index.ts src/adapters/realtime/stream-coordinator.ts src/adapters/realtime/live-poll-handoff-coordinator.ts src/adapters/realtime/index.ts tests/unit/realtime/stream-coordinator.test.ts tests/unit/realtime/live-poll-handoff-coordinator.test.ts docs/architecture/realtime-events-web-push-and-bounded-polling.md docs/architecture/decisions/VD-28-realtime-events-web-push-and-bounded-polling.md git commit -m "fix: retain realtime work through draining" ``` --- ### Task 12: Install Browser RPC bindings and explicit stream leases (`R-01`, `R-04`, `R-05`, `R-06`, `R-07` gate) **Files:** - Modify: `src/contracts/browser-rpc.ts` - Modify: `src/adapters/browser-rpc/transport.ts` - Modify: `src/adapters/browser-rpc/browser-rpc-runtime.ts` - Modify: `src/adapters/browser-rpc/unavailable-browser-rpc-transport.ts` - Modify: `src/adapters/browser-rpc/index.ts` - Modify: `src/adapters/realtime/websocket/websocket-protocol.ts` - Test: `tests/unit/browser-rpc/browser-rpc-contract.test.ts` - Test: `tests/unit/browser-rpc/browser-rpc-runtime.test.ts` - Test: `tests/unit/realtime/websocket-protocol.test.ts` - Modify: `docs/architecture/protobuf-browser-transport-and-rest-gateway.md` **Interfaces:** ```ts export type BrowserRpcTransportStream = Readonly<{ frames: AsyncIterable; cancel(reason: BrowserRpcStreamCancelReason): void; waitClosed(): Promise; }>; export function installBrowserRpcContractBindings( bindings: BrowserRpcContractBindings, ): InstalledBrowserRpcContractBindings; ``` - [ ] **Step 1: Add red lease, installer, exception, and byte-cap cases** ```ts it("bounds non-cooperative stream cancellation and completes the consumer", async () => {}); it("rejects a second stream while the prior lease is DRAINING", async () => {}); it("snapshots installed bindings before later source mutation", async () => {}); it("rejects extra accessor and symbol keys without invoking getters", () => {}); it("returns a closed failure and releases listeners when clock or fence throws", async () => {}); it("rejects oversized text before allocating a full UTF-8 copy", () => {}); it("counts multibyte and lone-surrogate bytes like TextEncoder", () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/browser-rpc/browser-rpc-contract.test.ts tests/unit/browser-rpc/browser-rpc-runtime.test.ts tests/unit/realtime/websocket-protocol.test.ts ``` - [ ] **Step 3: Install exact snapshots and structured leases** Capture registry own descriptors once into null-prototype frozen maps. Reject getter/accessor, symbol, extra key, malformed descriptor, and revoked proxy at composition. Runtime and transport calls use installed snapshots only. Replace bare stream iterable with cancel/closed receipt; caller completion is bounded while unresolved transport cleanup remains tracked as DRAINING. Wrap clock/fence/collaborator access in the closed Result boundary and release all listeners/timers in one outer `finally`. - [ ] **Step 4: Count WebSocket UTF-8 without full allocation** Pre-reject when UTF-16 code-unit length already exceeds the byte cap, then count code points incrementally with early exit. Count a valid surrogate pair as four bytes and each lone surrogate as the three-byte replacement sequence. - [ ] **Step 5: Run green and retain promotion block** ```bash corepack pnpm exec vitest run tests/unit/browser-rpc tests/unit/realtime --reporter=dot --maxWorkers=4 corepack pnpm check:realtime-boundaries corepack pnpm check:types ``` Browser RPC remains `AVAILABLE_NOT_COMPOSED`. A selected Connect/gRPC-Web transport must separately prove enqueue-time `maxBufferedBytes`, raw/decompressed ceilings, cancel/closed receipts, terminal framing, target browsers, and load behavior before composition. - [ ] **Step 6: Commit** ```bash git add src/contracts/browser-rpc.ts src/adapters/browser-rpc/transport.ts src/adapters/browser-rpc/browser-rpc-runtime.ts src/adapters/browser-rpc/unavailable-browser-rpc-transport.ts src/adapters/browser-rpc/index.ts src/adapters/realtime/websocket/websocket-protocol.ts tests/unit/browser-rpc/browser-rpc-contract.test.ts tests/unit/browser-rpc/browser-rpc-runtime.test.ts tests/unit/realtime/websocket-protocol.test.ts docs/architecture/protobuf-browser-transport-and-rest-gateway.md git commit -m "fix: install bounded Browser RPC stream leases" ``` --- ### Task 13: Correct browser transfer leases, versioning, delete certainty, and probe parsing (`BT-*` correctness) **Files:** - Create: `src/adapters/platform/abortable-operation.ts` - Create: `tests/unit/abortable-operation.test.ts` - Modify: `src/application/ports/browser-transfer/presigned-transfer.ts` - Modify: `src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts` - Modify: `src/adapters/browser-transfer/presigned/presigned-capability-vault.ts` - Modify: `src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts` - Modify: `src/adapters/browser-files/download-delivery-adapter.ts` - Modify: `src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts` - Modify: `src/adapters/browser-transfer/resumable-upload/indexeddb-checkpoint-store.ts` - Modify: `src/adapters/browser-transfer/resumable-upload/presigned-upload-part-executor.ts` - Modify: `src/adapters/browser-transfer/image-cdn/browser-image-probe.ts` - Modify: `src/adapters/browser-transfer/image-cdn/image-cdn-runtime.ts` - Test: `tests/unit/presigned-transfer.test.ts` - Test: `tests/unit/browser-file-download.test.ts` - Test: `tests/unit/resumable-upload-checkpoint.test.ts` - Test: `tests/unit/resumable-upload-fetch-transport.test.ts` - Test: `tests/unit/image-cdn-runtime.test.ts` **Interfaces:** ```ts type PresignedDownloadByteSource = Readonly<{ byteLength: number; capability: PresignedDownloadCapability; integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION"; stream(signal: AbortSignal): AsyncIterable>; close(): void; }>; type PartitionDeleteOutcome = | Readonly<{ state: "DELETED"; effect: "APPLIED" }> | Readonly<{ state: "PENDING"; effect: "UNKNOWN"; reason: "BLOCKED_DEADLINE" }>; type AbortRace = | Readonly<{ kind: "VALUE"; value: T }> | Readonly<{ kind: "TERMINAL"; terminal: "CALLER_ABORT" | "DEADLINE" | "CLOSED" }>; ``` - [ ] **Step 1: Add the shared abort mechanics golden tests** Prove first terminal owner, idempotent close, listener/timer cleanup under throwing scheduler, observed late rejection, and compensation of a late `Response`. `race` returns `AbortRace` and `terminal()` is a live accessor; it cannot return bare `T` on a terminal race. The utility imports no subsystem result taxonomy. - [ ] **Step 2: Add focused red cases** ```ts it("does not fetch a presigned download until stream consumption", async () => {}); it("closes an unused download source without network I/O", async () => {}); it("requires PRESIGNED_TRANSFER_V1 in request and response", async () => {}); it("returns PENDING UNKNOWN when deleteDatabase is still blocked", async () => {}); it("keeps the checkpoint store closed until a pending delete is resolved externally", async () => {}); it.each([Number.NaN, Number.POSITIVE_INFINITY, -1])("rejects invalid upload clock %s", async now => {}); it("rejects an AbortSignal facade without removeEventListener", async () => {}); it("rejects unmatched quotes in Cache-Control numeric directives", async () => {}); ``` - [ ] **Step 3: Run red** ```bash corepack pnpm exec vitest run tests/unit/abortable-operation.test.ts tests/unit/presigned-transfer.test.ts tests/unit/resumable-upload-checkpoint.test.ts tests/unit/resumable-upload-fetch-transport.test.ts tests/unit/image-cdn-runtime.test.ts ``` - [ ] **Step 4: Implement fixed semantics** Use a lazy single-start download lease with `READY/STREAMING/CLOSED`, rechecking expiry/minimum remaining life at first stream. Every consumer closes it in `finally`, including pre-stream prompt/size failures. Add mandatory protocol through request, response, capability, registration, vault snapshot, and executor common binding; map mismatch to `POLICY_REJECTED`. Server negotiates by request shape (`legacy→legacy`, `V1→V1`) before client rollout; never dual-emit unknown fields. Split vault factory output into issuer/consumer capabilities and revalidate runtime invariants. Reject encoded separator/backslash/NUL/dot/double-encoding using strict segment decoding and literal percent-hex rejection. After native `deleteDatabase` dispatch, report `PENDING/effect UNKNOWN`; in one realm an `(IDBFactory identity, databaseName)` registry prevents recreation until late settlement. Validate finite non-negative safe clocks. Validate `removeEventListener` and isolate cleanup throws. Tokenize Cache-Control with quote/escape awareness, accept only complete quoted numbers, and enforce the exact private directive deny set. Treat mandatory image resolve signal as a separate P3 type-contract change with a real typecheck fixture, not a runtime defect. - [ ] **Step 5: Run green and commit as independent subsystem commits** ```bash corepack pnpm exec vitest run tests/unit/abortable-operation.test.ts tests/unit/presigned-transfer.test.ts tests/unit/resumable-upload-checkpoint.test.ts tests/unit/resumable-upload-fetch-transport.test.ts tests/unit/resumable-upload-http-control-plane.test.ts tests/unit/resumable-upload-runtime.test.ts tests/unit/image-cdn-runtime.test.ts corepack pnpm check:types corepack pnpm check:architecture ``` Commit presigned, resumable, and image changes separately. Do not combine them merely because they share abort mechanics. --- ### Task 14: Make Service Worker cache/removal outcomes truthful (`SW-URL-01`, `SW-01`~`SW-09`) **Files:** - Modify: `src/adapters/service-worker/service-worker-lifecycle.ts` - Modify: `src/adapters/service-worker/service-worker-page-controller.ts` - Modify: `src/adapters/service-worker/service-worker-removal.ts` - Modify: `src/adapters/service-worker/service-worker-static-assets.ts` - Create: `src/contracts/service-worker-static-manifest.ts` - Modify: `src/contracts/service-worker.ts` - Modify: `scripts/lib/service-worker-build-input.ts` - Modify: `scripts/generate-service-worker-assets.ts` - Test: `tests/unit/service-worker-runtime.test.ts` - Test: `tests/unit/service-worker-build-input.test.ts` **Interfaces:** - Consumes: exact current cache name, exact ownership parser, expected worker source/nonce/target identity, generated manifest canonical digest. - Produces: no stale cross-cache response, no foreign-cache deletion, truthful removal result, strict build admission, bounded late work. - [ ] **Step 1: Add red P1 table** ```ts it("classifies a generated root-relative asset against an absolute Request URL", async () => {}); it("matches static responses only in the current release cache", async () => {}); it("deletes only exact owned static cache names", async () => {}); it("reports unregister false as FAILED", async () => {}); it("does not hide ownership mismatch or cleanup failure as DISABLED", async () => {}); it("rejects asset row or canonical set-digest tampering at build input", () => {}); ``` - [ ] **Step 2: Add lifecycle hardening red cases** ```ts it("accepts replies only from the captured waiting or controller source", async () => {}); it("coalesces concurrent activation and reset commands", async () => {}); it("treats zero in-scope clients as drained", async () => {}); it("isolates per-client postMessage failures according to commit phase", async () => {}); it("observes and cleans non-cooperative late install work", async () => {}); ``` - [ ] **Step 3: Run red** ```bash corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts tests/unit/service-worker-build-input.test.ts ``` - [ ] **Step 4: Implement exact cache and lifecycle authority** Canonicalize each generated root-relative manifest URL with the registration scope into a frozen same-origin absolute set and use it consistently for install cache keys/fetch classification. Open/match/delete only the current cache. Use `isOwnedStaticCacheName`, never raw prefix. Map `unregister() === false` to `FAILED`; map cleanup `ABSENT/UNREGISTERED/PURGED → DISABLED`, `OWNERSHIP_MISMATCH → INCOMPATIBLE`, and `FAILED → FAILED`. The runtime-neutral `service-worker-static-manifest.ts` owns exact row keys, type/extension and root-relative URL rules, and length-prefixed canonical bytes. Generator/build gate hash those bytes with Node SHA-256; worker hashes the same bytes with injected WebCrypto. Do not import `node:crypto` in worker code or reimplement the digest in Task 16. Activation/reset/drain replies require captured source identity + nonce where present + current target identity immediately before admission and are single-flight. Empty client set is drained. Drain delivery failure rejects activation; `skipWaiting()` success is the commit; accepted/reload notifications occur afterward and are best effort. Public install closes at 60 seconds, fences new candidate work, cancels late bodies, and registers a second exact-delete after non-cancellable `cache.put` settles; it never awaits extra cleanup beyond 60 seconds. - [ ] **Step 5: Run green and commit P1 separately from lifecycle hardening** ```bash corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts tests/unit/service-worker-build-input.test.ts corepack pnpm check:types:service-worker corepack pnpm check:architecture ``` Do not implement protocol V2 in this task; Task 16 merges `SW-10` with the existing 2026-08-01 plan. --- ### Task 15: Bind Web Push mutations and receipts to exact authority (`WP-01`~`WP-07`) **Files:** - Modify: `src/adapters/web-push/push-association-fence-store.ts` - Modify: `src/adapters/web-push/runtime-support.ts` - Modify: `src/adapters/web-push/push-registration-gateway.ts` - Modify: `src/adapters/web-push/push-subscription-adapter.ts` - Modify: `src/adapters/web-push/service-worker-runtime.ts` - Modify: `src/adapters/web-push/inbound/push-event-adapter.ts` - Modify: `src/adapters/web-push/inbound/notification-click-adapter.ts` - Modify: `src/contracts/web-push.ts` - Test: `tests/unit/web-push-fence-store.test.ts` - Test: `tests/unit/web-push-runtime-support.test.ts` - Test: `tests/unit/web-push-subscription-adapter.test.ts` - Test: `tests/unit/web-push-worker-runtime.test.ts` **Interfaces:** ```ts type WebPushRegisterCommitV2 = Readonly<{ protocol: "WEB_PUSH_REGISTRATION_RECEIPT_V2"; associationEpoch: string; fenceGeneration: string; sessionBindingEpoch: string; releaseEpoch: string; requestBindingSha256: string; replacedAssociationEpoch: string | null; }>; type NativeEffectCertainty = "CONFIRMED" | "NOT_APPLIED" | "MAYBE_APPLIED"; type WebPushMutationLifecycle = "OPEN" | "RECONCILIATION_REQUIRED" | "CLOSED"; ``` - [ ] **Step 1: Add red authority/effect cases** ```ts it("rejects a CAS receipt that is not the exact next revision", async () => {}); it("reports mutation outcome unknown when deadline races repository commit", async () => {}); it("rejects a backend receipt with any mismatched authority tuple field", async () => {}); it("reconciles an existing ACTIVE association before registering again", async () => {}); it("records the requested operation for every pre-aborted command", async () => {}); it("reports bounded client and notification truncation as degraded", async () => {}); it("reports late notification focus or open effects as MAYBE_APPLIED", async () => {}); ``` - [ ] **Step 2: Run red** ```bash corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts tests/unit/web-push-runtime-support.test.ts tests/unit/web-push-subscription-adapter.test.ts tests/unit/web-push-worker-runtime.test.ts ``` - [ ] **Step 3: Implement exact mutation truth** Write/remove CAS receipts require expected key and `(expectedRevision ?? 0) + 1`. Deadline or caller abort racing a post-dispatch repository mutation returns the new closed `MUTATION_OUTCOME_UNKNOWN`, moves lifecycle to `RECONCILIATION_REQUIRED`, and blocks mutation until bounded exact read-back. Use separate exact V2 register and reconcile response unions; reconcile includes `ACTIVE | ABSENT` and omits idempotency key from its documented binding. Register binds operation, authority tuple, subscription fingerprint, idempotency key, and `expectedPreviousAssociationEpoch`. Server negotiates V1/V2 by request protocol and never dual-emits fields into a strict V1 response. Compare decoded fixed-length digest bytes before local fence CAS. Repeated enable uses a private `reconcilePrepared()` inside the existing exclusive section; it does not call the public guarded `reconcile()`. Register only after authoritative absence, and receipt `replacedAssociationEpoch` must equal the request’s expected previous epoch. Pre-abort uses the requested operation. Limit observation uses the bounded bucket `0|1_8|9_32|33_64|GT_64` plus `truncated`; incomplete notification cleanup returns `{ complete: false }` separately from revoke authority. Native-effect phase tracking reports `NOT_APPLIED` before invocation, `MAYBE_APPLIED` while pending, and `CONFIRMED` on fulfillment, including one safe late observation; it never authorizes retry. - [ ] **Step 4: Run green and retain unselected status** ```bash corepack pnpm exec vitest run tests/unit/web-push-codec.test.ts tests/unit/web-push-fence-store.test.ts tests/unit/web-push-runtime-support.test.ts tests/unit/web-push-subscription-adapter.test.ts tests/unit/web-push-worker-runtime.test.ts corepack pnpm check:types:service-worker corepack pnpm check:architecture ``` Keep `WEB_PUSH` `NOT_SELECTED`/`AVAILABLE_NOT_COMPOSED`; do not add worker handlers or default consent flow in this task. --- ### Task 16: Execute bounded readers and versioned storage/worker migrations (`STO-06`, `STO-07`, `SW-10`) **Files:** - Create: `src/adapters/service-worker/bounded-worker-response.ts` - Modify: `src/adapters/service-worker/service-worker-lifecycle.ts` - Modify: `src/contracts/service-worker-static-manifest.ts` - Modify: `src/contracts/service-worker.ts` - Modify: `src/adapters/service-worker/service-worker-protocol.ts` - Modify: `src/adapters/service-worker/service-worker-entry.ts` - Modify: `src/adapters/service-worker/service-worker-page-controller.ts` - Modify: `src/bootstrap/register-service-worker.ts` - Modify: `src/adapters/storage/indexeddb/indexeddb-types.ts` - Modify: `src/adapters/storage/indexeddb/indexeddb-maintenance.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-protocol.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-client.ts` - Modify: `src/adapters/storage/opfs/opfs-worker-runtime.ts` - Test: `tests/unit/service-worker-runtime.test.ts` - Test: `tests/unit/service-worker-build-input.test.ts` - Test: `tests/unit/indexeddb-maintenance.test.ts` - Test: `tests/unit/opfs-worker-runtime.test.ts` - Test: `tests/browser-capabilities/indexeddb-runtime.spec.ts` - Test: `tests/browser-capabilities/opfs-runtime.spec.ts` **Interfaces:** ```ts export interface OldWriterDrainLease { readonly leaseId: string; readonly validUntilEpochMs: number; assertValid(signal?: AbortSignal): Promise>; release(): Promise; } export const OPFS_WORKER_PROTOCOL_VERSION = 2 as const; export const SERVICE_WORKER_PROTOCOL_VERSION = 2 as const; ``` - [ ] **Step 1: Finish the pre-existing bounded Service Worker marker task** Add red cases for declared oversize, headerless oversize, invalid UTF-8, malformed JSON, never-ending stream, reader cancel, and release isolation. Implement realm-safe reads of at most `maxBytes + 1`, fatal `TextDecoder`, explicit cancellation, and strict marker parsing. Never use `Response.text()` for protocol data. Run: ```bash corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts ``` - [ ] **Step 2: Add IndexedDB commit-phase deadline and lease red cases** ```ts it("stops codec migration commit at the cooperative deadline", async () => {}); it("keeps each row sidecar budget and checkpoint atomically aligned", async () => {}); it("requires an old-writer drain lease to remain valid before batch commit", async () => {}); ``` Before transaction start require a minimum commit reserve. Within the transaction, check monotonic budget before starting the next untouched record; finish the already-started row atomically or abort the transaction. Revalidate the temporal drain lease immediately before batch commit. Clock/lease failure aborts with no checkpoint advance. - [ ] **Step 3: Integrate Service Worker manifest identity and protocol V2 once** Use the shared canonical bytes from Task 14. Add tuple-mutation tests over protocol/cache schema/build/release/contract/static set and prove every mutation changes the digest/rejects activation. Replace the optional field bag with exact kind-discriminated schemas; every message, including `SYNC_WAKE_OBSERVED`, uses the constructor. V1/V2 mismatch fails closed and never forces `skipWaiting`. - [ ] **Step 4: Version OPFS page-worker protocol and handshake** Every request/response includes protocol version, request ID, and echoed kind. Client pending state stores expected kind and uses strict per-kind value/closed failure-code decoders. `HELLO/CAPABILITIES` binds protocol and physical schema before read/write. Wrong version/schema maps to `INCOMPATIBLE` and admits no mutation. Keep v1 data readers through the rollback window; new writes use V2 token paths from Task 6. - [ ] **Step 5: Run unit, worker type, and real-browser gates** ```bash corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts tests/unit/service-worker-build-input.test.ts tests/unit/indexeddb-maintenance.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/opfs-byte-store.test.ts corepack pnpm check:types:web-worker corepack pnpm check:types:service-worker corepack pnpm check:browser-file-storage-boundaries corepack pnpm test:browser-capabilities -- tests/browser-capabilities/indexeddb-runtime.spec.ts tests/browser-capabilities/opfs-runtime.spec.ts ``` - [ ] **Step 6: Deploy expand/drain/contract without schema downgrade** Release N adds V2 schemas and v1+v2 readers. Release N+1 proves old writer drain and enables V2 writes/copy-on-write migration. Keep V1 reader/cache prefix for the documented rollback/grace window. Only after active/rollback clients drain may a bounded exact-owner cleanup remove V1 physical generations. Rollback disables new V2 admission and uses compatible readers; it never lowers DB/worker schema or deletes roots. Commit bounded marker, IndexedDB deadline, Service Worker V2, and OPFS V2 as four independently reviewable commits. --- ### Task 17: Perform characterization-preserving extraction and keep promotion gaps gated **Files — resumable upload extraction:** - Create: `src/adapters/browser-transfer/resumable-upload/upload-session-state-machine.ts` - Create: `src/adapters/browser-transfer/resumable-upload/upload-session-reconciler.ts` - Create: `src/adapters/browser-transfer/resumable-upload/upload-part-scheduler.ts` - Create: `src/adapters/browser-transfer/resumable-upload/upload-retry-executor.ts` - Create: `src/adapters/browser-transfer/resumable-upload/upload-abort-saga.ts` - Modify: `src/adapters/browser-transfer/resumable-upload/resumable-upload-runtime.ts` - Test: `tests/unit/resumable-upload-runtime.test.ts` **Files — image extraction:** - Create: `src/adapters/browser-transfer/image-cdn/image-asset-decoder.ts` - Create: `src/adapters/browser-transfer/image-cdn/image-capability-verification.ts` - Create: `src/adapters/browser-transfer/image-cdn/image-presentation-projector.ts` - Modify: `src/adapters/browser-transfer/image-cdn/image-cdn-runtime.ts` - Test: `tests/unit/image-cdn-runtime.test.ts` **Files — storage/download extraction inherited from the existing plan:** - Create: `src/adapters/storage/opfs/opfs-worker-bootstrap.ts` - Create: `src/adapters/storage/opfs/opfs-worker-message-host.ts` - Create: `src/adapters/storage/opfs/opfs-worker-core.ts` - Create: `src/adapters/storage/opfs/opfs-worker-lock.ts` - Create: `src/adapters/storage/opfs/opfs-physical-io.ts` - Create: `src/adapters/cache-storage/public-cache-manifest.ts` - Create: `src/adapters/cache-storage/cache-lock.ts` - Create: `src/adapters/browser-files/download-browser-managed.ts` - Create: `src/adapters/browser-files/download-picker-stream.ts` - Create: `src/adapters/browser-files/download-object-url.ts` - Modify: the three existing facades in their current files - Test: `tests/unit/opfs-worker-runtime.test.ts` - Test: `tests/unit/public-response-cache.test.ts` - Test: `tests/unit/browser-file-download.test.ts` **Promotion gaps in this task:** preview decode probe (`GAP-01`), bounded cache/origin lifecycle (`GAP-02/03`), image descriptor provider (`BT-IMG-04`), and Web Locks capability selection (`BT-UP-07`). Concrete Browser RPC transport evidence remains exclusively in Task 12 (`R-07`). - [ ] **Step 1: Freeze facade characterization before moving code** For every facade, snapshot success/failure/caller abort/deadline/lock loss/late provider/cleanup order and public failure kind. Run all relevant existing suites green before extraction. A refactor that needs fixture semantic changes is rejected and returned to the preceding correctness task. - [ ] **Step 2: Extract one cohesive owner per commit** Resumable order: pure transition table → retry executor → reconciler → bounded scheduler → abort Saga. Image order: exact decoder → capability verification → presentation projection. OPFS order: bootstrap/host → lock/physical I/O → core state machine. Cache and download strategies follow the existing 2026-08-01 plan. Facades keep public exports and capability identity. - [ ] **Step 3: Add async runtime lifecycle without changing application port semantics** For resumable upload, adapter runtime `dispose(): Promise` shares one `OPEN→CLOSING→CLOSED` drain. Existing `close(): void` closes admission and starts that same drain. No current bootstrap consumer is assumed; a future composition owner must await dispose. Late results remain fenced. - [ ] **Step 4: Keep known gaps at their declared status** Do not create product policy/consent/registry or change bootstrap selection in a refactor PR. A later promotion PR must include: ```text Preview: header parser + native decode probe + pixel/decoded/frame/deadline bounds + bitmap close. Origin lifecycle: leader lease + count/time/cursor pages + pressure hysteresis + productive-GC retry proof. Image CDN: V1 descriptor provider + exact request binding + minimum TTL + single-flight refresh. Resumable: Web Locks supported matrix; unsupported returns UNSUPPORTED with no unsafe fallback. ``` - [ ] **Step 5: Verify each extraction independently** ```bash corepack pnpm exec vitest run tests/unit/resumable-upload-runtime.test.ts tests/unit/image-cdn-runtime.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/public-response-cache.test.ts tests/unit/browser-file-download.test.ts corepack pnpm check:types corepack pnpm check:architecture corepack pnpm lint git diff --check ``` --- ### Task 18: Run the final evidence, migration, and rollback gates **Files:** - Modify: `docs/operations/adapter-remediation-ledger.md` - Modify only after evidence: availability/readiness sections in the affected architecture docs - No production behavior change in this task **Interfaces:** - Consumes: all task commits and product-specific provider/browser fixtures. - Produces: exact PASS/FAIL/UNVERIFIED evidence, no unsupported completion claims, and a release/rollback decision per capability. - [ ] **Step 1: Run focused subsystem suites in a fresh process** ```bash corepack pnpm exec vitest run tests/unit/browser-rpc tests/unit/realtime --reporter=dot --maxWorkers=4 corepack pnpm exec vitest run tests/unit/opfs-byte-store.test.ts tests/unit/opfs-worker-runtime.test.ts tests/unit/indexeddb-opfs-journal.test.ts tests/unit/indexeddb-maintenance.test.ts tests/unit/public-response-cache.test.ts tests/unit/browser-file-download.test.ts corepack pnpm exec vitest run tests/unit/presigned-transfer.test.ts tests/unit/resumable-upload-checkpoint.test.ts tests/unit/resumable-upload-fetch-transport.test.ts tests/unit/resumable-upload-http-control-plane.test.ts tests/unit/resumable-upload-runtime.test.ts tests/unit/image-cdn-runtime.test.ts corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts tests/unit/service-worker-build-input.test.ts tests/unit/web-push-codec.test.ts tests/unit/web-push-fence-store.test.ts tests/unit/web-push-store-port-compatibility.test.ts tests/unit/web-push-runtime-support.test.ts tests/unit/web-push-subscription-adapter.test.ts tests/unit/web-push-worker-runtime.test.ts ``` - [ ] **Step 2: Run repository static and full tests** ```bash corepack pnpm check:types corepack pnpm lint corepack pnpm check:architecture corepack pnpm check:diagnostics corepack pnpm check:browser-file-storage-boundaries corepack pnpm check:realtime-boundaries corepack pnpm test:all corepack pnpm verify:documentation git diff --check ``` Record exact failures. Do not mark a task complete because a different gate passed or because a failure is assumed environmental. - [ ] **Step 3: Run real capability and compatibility fixtures** ```bash corepack pnpm test:browser-capabilities -- \ tests/browser-capabilities/browser-files.spec.ts \ tests/browser-capabilities/indexeddb-runtime.spec.ts \ tests/browser-capabilities/opfs-runtime.spec.ts \ tests/browser-capabilities/public-cache-storage.spec.ts \ tests/browser-capabilities/presigned-streaming.spec.ts \ tests/browser-capabilities/resumable-upload.spec.ts \ tests/browser-capabilities/image-cdn.spec.ts corepack pnpm test:browser-file-storage-removal corepack pnpm test:realtime-removal ``` Also run server/provider compatibility matrices for presigned V1, Web Push V1/V2, Service Worker V1/V2, and OPFS V1/V2. Missing real provider/browser infrastructure is `UNVERIFIED`, not PASS. - [ ] **Step 4: Exercise rollback in staging** For each versioned capability: stop new admission, drain/retain active leases, deploy compatible reader, activate verified previous cache/release, and reconcile unknown effects. Prove no DB/root/schema downgrade, no broad delete, no synthesized cursor/checkpoint, and no automatic transport downgrade. - [ ] **Step 5: Close the ledger and only then update readiness** Every confirmed ID requires linked red/green evidence, exact command output, rollout state, and rollback trigger. Planned gaps remain `PROMOTION_BLOCKED` until their separate evidence exists. Update `AVAILABLE_NOT_COMPOSED` to a higher status only in an explicitly authorized product-selection change. --- ## Plan self-review checklist - [ ] Every confirmed finding ID in the five review files maps to exactly one task above. - [ ] `STO-08` and other hypotheses have characterization gates and are not silently implemented as defects. - [ ] `DESIGNED_NOT_IMPLEMENTED`/`NOT_SELECTED` items remain promotion gates, not regressions. - [ ] Existing 2026-08-01 Tasks 1–6 remain active and are not duplicated or marked complete. - [ ] Service Worker bounded marker Task 4 precedes full-identity V2; the shared manifest digest is implemented once. - [ ] All new wire/persisted shapes retain old readers during expand/drain/contract migration. - [ ] No placeholder marker, cross-task shorthand, invented failure code, or unspecified cleanup outcome remains. - [ ] Type names used by later tasks match their defining task.