Files
2026-05-27 23:29:53 +01:00

8.7 KiB

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<FallbackUriErrorCode> 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/<yyyy-mm-dd>-add-storage-uri-plugin-fallback/