# Tasks — add-storage-uri-plugin-fallback > Every checkbox MUST reflect work actually done in this change. ## 1. Spec authoring - [ ] 1.1 Read `openspec/AGENTS.md` §0, §0a, §0b, §0c, §13, §14 - [ ] 1.2 Read `openspec/project.md` - [ ] 1.3 Read `openspec/specs/storage-uri/spec.md` to confirm `UriPlugin` + `StorageUriResolver` contract - [ ] 1.4 Read `packages/storage/storage-uri/src/` (resolver, types, conformance) for recursive-dispatch surface - [ ] 1.5 Read `packages/flow/retry/src/` to confirm `RetryStrategy` surface for documentation cross-links - [ ] 1.6 Read `openspec/changes/complete-storage-mux/` for the multi-backend failover precedent (error classification, fan-out semantics, span hierarchy) - [ ] 1.7 Draft `proposal.md`, `design.md`, `specs/storage-uri-plugin-fallback/spec.md`, `tasks.md` - [ ] 1.8 Numbered Requirement/Scenario checklist per §0c step 6 — every requirement and every scenario in the delta tracked as an unchecked item until implementation lands ## 2. Scaffolding - [ ] 2.1 Create `packages/storage/storage-uri-plugin-fallback/` - [ ] 2.2 `package.json` v0.0.0, `"sideEffects": false`, subpath exports per feature directory - [ ] 2.3 Deps: `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/storage-uri`, `@suaveplan/types`, `@opentelemetry/api`, `zod`, `tslib` - [ ] 2.4 DevDeps: `@suaveplan/storage`, `@suaveplan/storage-file`, `@suaveplan/testing`, `@vitest/coverage-istanbul`, `fast-check`, `mitata`, `typescript`, `vite`, `vite-plugin-dts`, `vitest` - [ ] 2.5 `tsconfig.json`, `vite.config.ts`, `vitest.config.ts` via `createTestConfig` with `thresholds: 100/100/100/100` - [ ] 2.6 Root `README.md` with Modules section linking every co-located `.md` ## 3. Error registry - [ ] 3.1 `src/error-codes/error-codes.ts` — `defineCodes("FALLBACK_URI", [...])` with all 8 codes: `NO_CANDIDATES`, `INVALID_URI`, `ALL_CANDIDATES_FAILED`, `WRITE_MIRROR_FAILED`, `LIST_UNION_FAILED`, `ABORTED`, `MAX_DEPTH_EXCEEDED`, `CIRCULAR_REFERENCE` - [ ] 3.2 `FallbackUriError extends SuaveplanError` with `uri`, `candidateIndex`, `candidateUri`, `candidateErrors`, `mirrorOutcomes`, `cycleChain`, `depth` discriminants (only the ones relevant to each code are populated) - [ ] 3.3 `error-codes.md` + `error-codes.test.ts` — reachability sweep proving every code is thrown from a real call site ## 4. Types & schemas - [ ] 4.1 `src/types/policy.types.ts` — `FallbackPolicy` (`errorClass`, `maxParallel`, `stopOnAbort`, `writeMode`, `deleteMode`, `listMode`, `listFailureMode`, `maxDepth`, `maxCandidates`), `FallbackPluginOptions` - [ ] 4.2 `src/types/policy.schemas.ts` — Zod schemas: `maxParallel >= 1`, `maxDepth >= 1 && <= 8`, `maxCandidates >= 1 && <= 64`, mode enums - [ ] 4.3 `src/types/candidate.types.ts` — `CandidateError`, `CandidateAttempt`, `MirrorOutcome` ## 5. URI grammar - [ ] 5.1 `src/parse-uri/parse-fallback-uri.ts` — `parseFallbackUri(uri)` returning `{ candidates: readonly string[]; canonicalUri: string }`; `buildFallbackUri(uris)` with `URLSearchParams`; reject empty / oversized / non-`fallback` scheme / non-empty authority - [ ] 5.2 `src/parse-uri/parse-fallback-uri.md` + `.test.ts` — exhaustive validation matrix; fast-check round-trip property `buildFallbackUri ∘ parseFallbackUri = identity` on a small generator - [ ] 5.3 `src/parse-uri/abort-any.ts` — `AbortSignal.any` shim guarded by `typeof AbortSignal.any !== "function"` ## 6. Classification - [ ] 6.1 `src/classify/classify.ts` + `.md` + `.test.ts` — pure `classify(err, policy) => "transient" | "semantic" | "abort"` per design §7; consumes `@suaveplan/error#SuaveplanError` for code inspection ## 7. Recursion context - [ ] 7.1 `src/recursion/depth-context.ts` + `.md` + `.test.ts` — `DepthContext` value type, `withChildContext` helper that bumps depth + extends visited set, depth-cap enforcement throwing `MAX_DEPTH_EXCEEDED`, visited-set canonicalisation via `parseFallbackUri` reserialisation ## 8. Walk strategies - [ ] 8.1 `src/walk/sequential.ts` + `.md` + `.test.ts` — ordered fall-through; emits attempt-failed events; throws `ALL_CANDIDATES_FAILED` on exhaustion with `candidateErrors` populated - [ ] 8.2 `src/walk/parallel.ts` + `.md` + `.test.ts` — `Promise.any`-style race with concurrency cap (`maxParallel`); cancels pending candidates via `AbortSignal.any` once one succeeds ## 9. Operations (full UriPlugin surface) - [ ] 9.1 `src/ops/read.ts` + `.md` + `.test.ts` — `get(key)` + `createReadStream(key, signal)` + range `{ offset, length }` propagation - [ ] 9.2 `src/ops/write.ts` + `.md` + `.test.ts` — `set(key, bytes)` + `createWriteStream(key, body, signal)`; `writeMode: "all"` → `Promise.allSettled` + `WRITE_MIRROR_FAILED`; `writeMode: "first-success"` → walk + background settle - [ ] 9.3 `src/ops/metadata.ts` + `.md` + `.test.ts` — `exists` + `getMetadata` sequential walk - [ ] 9.4 `src/ops/delete.ts` + `.md` + `.test.ts` — mirror delete with `deleteMode: "any-success" | "all"` (default `"any-success"`) - [ ] 9.5 `src/ops/list.ts` + `.md` + `.test.ts` — union with dedup; `listMode: "union" | "concat"`; `listFailureMode: "throw" | "partial"` ## 10. Observability - [ ] 10.1 `src/observability/events.ts` + `.md` + `.test.ts` — topic constants (`storage-uri.fallback.attempt-failed`, `.attempt-succeeded`, `.write-mirror-partial`, `.cycle-detected`, `.depth-exceeded`); emit helpers consuming `EventBusLike` - [ ] 10.2 `src/observability/otel.ts` + `.md` + `.test.ts` — counter `storage_uri.fallback_total{candidate_index, scheme, outcome}`, histogram `storage_uri.fallback_walk_ms{outcome}`, parent + per-attempt child spans with attribute set ## 11. Plugin factory - [ ] 11.1 `src/plugin/plugin.ts` + `.md` — `fallbackUriPlugin(options)`; wires walk + ops + observability + recursion; binds to `fallback` scheme; produces `UriPlugin` whose `adapter` exposes the full streaming-extended `StorageLike` surface - [ ] 11.2 `src/plugin/plugin.test.ts` — construction validation (missing resolver, bad policy), abort short-circuit, sequential success, sequential exhaustion, parallel race, mirror write, cycle, depth cap - [ ] 11.3 `src/plugin/plugin.spec-conformance.test.ts` — `runUriPluginConformance(plugin, fixtures)` pass with `validUris` / `invalidUris` / `buildRoundTrip` populated from `parseFallbackUri` / `buildFallbackUri` ## 12. Fallback-specific conformance - [ ] 12.1 `src/conformance/fallback-conformance.ts` + `.md` — `runFallbackConformance(resolver, fixtures)` exporting the harness consumers (audio-IR loader, drive+CDN pairing) call from their own tests - [ ] 12.2 `src/conformance/fallback-conformance.test.ts` — self-test against the in-package plugin: K-fail-then-success, all-fail, abort, cycle, depth, mirror-write ## 13. Barrel + exports - [ ] 13.1 `src/index.ts` — explicit named re-exports per §6a (NO `export *`) - [ ] 13.2 `package.json` subpath exports — one entry per feature directory; verify count matches ## 14. Documentation - [ ] 14.1 Sibling `.md` for every implementation `.ts` (≥ 200 words, all required sections, real-import examples) - [ ] 14.2 `README.md` — install, URI grammar, simple example (`fallbackUriPlugin` registered alongside other plugins), advanced example (mirror writes + dedup list across CDN + drive), policy reference, complementarity note linking to `@suaveplan/storage-mux` ## 15. Gates - [ ] 15.1 `bun run check` - [ ] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/storage-uri-plugin-fallback` - [ ] 15.3 `bunx turbo test:coverage --filter=@suaveplan/storage-uri-plugin-fallback` (100/100/100/100) - [ ] 15.4 `bun scripts/lint/check-test-runner-imports.ts` → zero output for this package - [ ] 15.5 Tier-purity grep `grep -rnE "from ['\"](node:|bun:)" packages/storage/storage-uri-plugin-fallback/src/` → empty - [ ] 15.6 Banned-pattern scan `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/storage/storage-uri-plugin-fallback/src/ --include="*.ts"` → empty - [ ] 15.7 Barrel-shape scan per §6a → empty - [ ] 15.8 Bare-version scan → empty - [ ] 15.9 Missing `.md` scan → empty - [ ] 15.10 README present - [ ] 15.11 Subpath export count matches feature module count - [ ] 15.12 `bun scripts/openspec-conformance.ts @suaveplan/storage-uri-plugin-fallback` - [ ] 15.13 Dependent sweep `bunx turbo test --filter=...@suaveplan/storage-uri-plugin-fallback` ## 16. Manifest & archival - [ ] 16.1 `bun scripts/folder-reorg/generate-manifest.ts` - [ ] 16.2 `bun scripts/generate-missing.ts` - [ ] 16.3 Update `STATUS.md` and `SUMMARY.md` - [ ] 16.4 `bunx markdownlint-cli2 --fix` - [ ] 16.5 Fold `specs/storage-uri-plugin-fallback/spec.md` → `openspec/specs/storage-uri-plugin-fallback/spec.md` - [ ] 16.6 Move change folder → `openspec/archive/-add-storage-uri-plugin-fallback/`