The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 KiB
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
DRAININGwhere applicable. - Keep
AVAILABLE_NOT_COMPOSED,DESIGNED_NOT_IMPLEMENTED, andNOT_SELECTEDunchanged 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
- Adapter review index fixes cross-cutting decisions and subsystem order.
- Network/state, realtime/RPC, storage/files, browser transfer, and worker/push fix finding-specific behavior, signatures, tests, migration, and rollback.
- 2026-08-01 HTTP/worker plan and runtime-correctness plan remain active. This plan adds gates; it does not mark their tasks complete.
- 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-0105, BT-UP-0104, 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-0507, BT-IMG-0304, 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:
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:
| 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:
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
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.failedtelemetry event for terminal non-abort failures.
export type HttpExecutionObservation = Readonly<{
routeId: string;
operationId: string;
diagnosticsOperation: string;
outcome: HttpExecutionOutcome<unknown, unknown>["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:
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
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
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
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:
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
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
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
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
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:
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
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
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
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:
type TelemetryLifecycle = "ACTIVE" | "DISPOSED";
export interface BestEffortTelemetry {
emit(event: TelemetryEvent): void;
flush(): Promise<void>;
dispose(): void;
}
- Step 1: Add red lifecycle and capacity tests
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
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
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:
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
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
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
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
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:
type ResolvedBrowserManagedTarget = Readonly<{ absoluteHref: string }>;
function resolveBrowserManagedTarget(
href: string,
baseOrigin: string,
policy: Readonly<{ allowCrossOrigin: boolean; allowQuery: boolean }>,
): BrowserDataResult<ResolvedBrowserManagedTarget>;
- 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
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:
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
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:
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
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
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
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:
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
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
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
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
defineIdempotencyKeyvalidator 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
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
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
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:
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<RealtimeResult<void>>;
}>;
- Step 1: Add red never-settling cases
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
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
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:
export type BrowserRpcTransportStream = Readonly<{
frames: AsyncIterable<BrowserRpcStreamFrame>;
cancel(reason: BrowserRpcStreamCancelReason): void;
waitClosed(): Promise<void>;
}>;
export function installBrowserRpcContractBindings(
bindings: BrowserRpcContractBindings,
): InstalledBrowserRpcContractBindings;
- Step 1: Add red lease, installer, exception, and byte-cap cases
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
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
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
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:
type PresignedDownloadByteSource = Readonly<{
byteLength: number;
capability: PresignedDownloadCapability;
integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION";
stream(signal: AbortSignal): AsyncIterable<BrowserDataResult<Uint8Array>>;
close(): void;
}>;
type PartitionDeleteOutcome =
| Readonly<{ state: "DELETED"; effect: "APPLIED" }>
| Readonly<{ state: "PENDING"; effect: "UNKNOWN"; reason: "BLOCKED_DEADLINE" }>;
type AbortRace<T> =
| 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<T> returns AbortRace<T> 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
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
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
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
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
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
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
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:
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
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
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
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:
export interface OldWriterDrainLease {
readonly leaseId: string;
readonly validUntilEpochMs: number;
assertValid(signal?: AbortSignal): Promise<BrowserDataResult<void>>;
release(): Promise<void>;
}
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:
corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts
- Step 2: Add IndexedDB commit-phase deadline and lease red cases
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
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<void> 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:
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
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
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
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
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-08and other hypotheses have characterization gates and are not silently implemented as defects.DESIGNED_NOT_IMPLEMENTED/NOT_SELECTEDitems 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.