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>
1279 lines
66 KiB
Markdown
1279 lines
66 KiB
Markdown
# 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<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`:
|
||
|
||
```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<void>;
|
||
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<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**
|
||
|
||
```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<RealtimeResult<void>>;
|
||
}>;
|
||
```
|
||
|
||
- [ ] **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<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**
|
||
|
||
```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<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**
|
||
|
||
```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<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:
|
||
|
||
```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<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:
|
||
|
||
```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.
|