Files
suaveplan/openspec/changes/complete-drive-uri/tasks.md
2026-05-27 23:29:53 +01:00

136 lines
14 KiB
Markdown

# Tasks — `complete-drive-uri`
> Every checkbox MUST reflect work actually done in this change. Walk every `### Requirement` and `#### Scenario` in `specs/drive-uri/spec.md` AND `specs/drive/spec.md` before declaring done (§12a).
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Spec authoring
- [ ] 1.1 Read `openspec/AGENTS.md` §0c, §2b, §2c, §6a, §7, §11, §13, §13a, §14
- [ ] 1.2 Read `openspec/archive/2026-05-16-add-drive-uri/proposal.md`, `_superseded-narrow-deferrals-2026-05-17/{add-drive-uri-writes,add-drive-range-reads}/{proposal,design}.md`
- [ ] 1.3 Read `openspec/specs/drive-uri/spec.md` and `openspec/specs/drive/spec.md`
- [ ] 1.4 Confirm `Drive.{read,write,delete,stat,list,createReadStream,createWriteStream,hydrateFrom,getKeyStatus,getPresence}` surface; `DriveStorage.capabilities` flag pattern; `StreamingStorageExtensions` shape; `MeshDrive.{advertise,unadvertise}` shape; `ChunkRequest` union; `parseUri` query/fragment shape
- [ ] 1.5 Draft `proposal.md` + `design.md` + `tasks.md` + spec deltas
- [ ] 1.6 Build the numbered Requirement / Scenario implementation checklist (per §0c step 6)
- [ ] 1.7 `openspec validate complete-drive-uri --strict` exits 0
## 2. `@suaveplan/drive` — range overload + new codes
- [ ] 2.1 `src/types/drive.types.ts` — add `RangeReadOptions { offset?, length?, signal? }`; extend `DriveStorage` with `capabilities.nativeRange?: boolean` and an extended `createReadStream?(path, options?: { offset?, length?, signal? })` signature
- [ ] 2.2 `src/error-codes.ts` — add `RANGE_INVALID`, `RANGE_OUT_OF_BOUNDS` to `DRIVE_CODES` (registry grows 11 → 13)
- [ ] 2.3 `src/error-codes.spec-conformance.test.ts` — extend frozen-registry and every-code-starts-with-`DRIVE.` checks
- [ ] 2.4 `src/file-operations/streams.range.ts` — pure slicing transform `sliceStream(stream, offset, length)` honouring `signal`
- [ ] 2.5 `src/file-operations/streams.range.md` — sibling doc
- [ ] 2.6 `src/file-operations/streams.ts` — overload `createReadStream(storage, path, signal?)` AND `createReadStream(storage, path, options: RangeReadOptions)`; runtime `instanceof AbortSignal` discriminator; synchronous validation → `DRIVE.RANGE_INVALID`; `nativeRange` → forward `{ offset, length }`; otherwise wrap in `sliceStream`
- [ ] 2.7 `src/file-operations/streams.md` — update docs
- [ ] 2.8 `src/file-operations/streams.range.test.ts` — boundary table: fully inside one chunk, spans boundary, aligned multi-chunk, tail-to-EOF, zero-length, whole-file, abort mid-stream
- [ ] 2.9 `src/drive/drive.ts` — `createReadStream` overload signatures forwarded to `streams.ts`; middleware context carries `range?: { offset, length? }`
- [ ] 2.10 `src/drive/drive.test.ts` — overload-compat (bare signal still works), options form, `DRIVE.RANGE_INVALID` synchronous throw, `DRIVE.RANGE_OUT_OF_BOUNDS` (size-known) synchronous throw
## 3. `@suaveplan/drive-uri` — error registry + types
- [ ] 3.1 `src/error-codes/error-codes.ts` — extend `DRIVE_URI_CODES` to 14 codes: `TEMPLATE_READ_ONLY`, `RANGE_INVALID`, `RANGE_OUT_OF_BOUNDS`, `MESH_PROPAGATION_TIMEOUT`, `STAT_UNAVAILABLE`, `LIST_UNSUPPORTED`, `INVALID_QUERY` added to the existing 7
- [ ] 3.2 `src/error-codes/error-codes.ts` — extend `DriveUriErrorOptions` with `range?: { offset: number; length?: number }`, `fileId?: string`, `key?: string`, `value?: string`
- [ ] 3.3 `src/error-codes/error-codes.md` — describe every code, its call site, and the narrowed role of `WRITE_UNSUPPORTED`
- [ ] 3.4 `src/error-codes/error-codes.test.ts` — exhaustive registry shape (14 codes, every code starts with `DRIVE_URI.`, `Object.isFrozen` holds), `DriveUriError` discriminants all surface, `cause` preserved across re-wraps
- [ ] 3.5 `src/types/plugin-options.types.ts` — add `MeshAdvertiseHook { advertise(fileId, bitset, totalChunks, signal?), unadvertise(fileId, signal?) }`; widen `TemplateProvider.fetchTemplate(templateId, options?: { nonce?, signal? })` (preserve back-compat by making the second arg optional); add `?advertise?: MeshAdvertiseHook` to `DriveMeshUriPluginOptions`
- [ ] 3.6 `src/types/plugin-options.types.md` — update
## 4. `@suaveplan/drive-uri` — query parsing
- [ ] 4.1 `src/query/parse-query.ts` — `parseQuery(query: string | undefined, scheme: string): ParsedQuery` returning `{ signalTimeoutMs?, advertise?, nonce? }`; unknown keys throw `DRIVE_URI.INVALID_QUERY`
- [ ] 4.2 `src/query/parse-query.md` — sibling doc
- [ ] 4.3 `src/query/parse-query.test.ts` — per-scheme allow-list, unknown keys, malformed values (`?advertise=maybe`, `?signal-timeout-ms=-1`), empty/missing query
## 5. `@suaveplan/drive-uri` — events
- [ ] 5.1 `src/events/events.ts` — typed topic constants `DRIVE_URI_TOPICS = { write: "drive_uri:write", delete: "drive_uri:delete", meshAdvertised: "drive_uri:mesh_advertised" } as const`; `emit(events, topic, payload)` no-op when `events` is undefined
- [ ] 5.2 `src/events/events.md` — sibling doc
- [ ] 5.3 `src/events/events.test.ts` — emission against a real `EventBusLike` fixture; no-op when bus absent
## 6. `@suaveplan/drive-uri` — adapter widening
- [ ] 6.1 `src/adapter/drive-storage-like.ts` — widen `get`/`set`/`delete`/`exists`/`stat`/`list` to accept and forward `AbortSignal`; add `getMetadata(key, signal?): Promise<StatResult | undefined>` mapping `Drive.stat → StatResult`; add `createWriteStream(key, body, signal?): Promise<void>` consuming `ReadableStream<Uint8Array>` and piping into `Drive.createWriteStream`; widen `createReadStream` to accept `{ offset?, length?, signal? }`; advertise `capabilities = { nativeRange: true, nativeStreams: true }`
- [ ] 6.2 `src/adapter/drive-storage-like.md` — update for every new method and signature
- [ ] 6.3 `src/adapter/drive-storage-like.test.ts` — `get`/`set`/`delete`/`exists`/`stat`/`list` all propagate `signal`; pre-aborted signal short-circuits; `getMetadata` returns `StatResult` shape; `createWriteStream` round-trips; `createReadStream({ offset, length })` forwards; `clear()` still rejects `WRITE_UNSUPPORTED`; `list` returns the expected re-serialisable keys
## 7. `@suaveplan/drive-uri` — InstrumentedDriveAdapter per-scheme dispatch
- [ ] 7.1 `src/plugins/drive/drive.streams.ts` — replace unconditional `set` rejection with per-scheme dispatch: `drive`/`drive+mesh` → `super.set(key, value, signal)`; `drive+template` → throw `DRIVE_URI.TEMPLATE_READ_ONLY`. Same for `delete` and `createWriteStream`
- [ ] 7.2 `src/plugins/drive/drive.streams.ts` — pre-aborted signal on every op → throw `DRIVE_URI.ABORTED`
- [ ] 7.3 `src/plugins/drive/drive.streams.ts` — range pass-through on `createReadStream`; OTel `drive.uri.range = "{offset}-{end}"` span attribute when present
- [ ] 7.4 `src/plugins/drive/drive.streams.ts` — emit `drive_uri:write` / `drive_uri:delete` events via `events/events.ts` on every success and every failure
- [ ] 7.5 `src/plugins/drive/drive.streams.ts` — narrow `WRITE_UNSUPPORTED` to `DriveStorageLikeAdapter.clear()` only (preserve constant for `clear` call site)
- [ ] 7.6 `src/plugins/drive/drive.streams.md` — update
- [ ] 7.7 `src/plugins/drive/drive.streams.test.ts` — pre-aborted abort short-circuits per op; range round-trip; write-stream round-trip; event emission asserted via real bus fixture
## 8. `@suaveplan/drive-uri` — drive:// plugin
- [ ] 8.1 `src/plugins/drive/drive.ts` — invoke `parseQuery(parts.query, "drive")` and propagate `signal-timeout-ms`; reject `?advertise` and `?nonce` with `DRIVE_URI.INVALID_QUERY`
- [ ] 8.2 `src/plugins/drive/drive.test.ts` — full op matrix: read/write/delete/stat/list/exists; AbortSignal threading; pre-aborted abort; `?signal-timeout-ms` honoured; `?advertise` rejected; `DRIVE_ID_MISMATCH` preserved; `INVALID_URI` for malformed inputs
## 9. `@suaveplan/drive-uri` — drive+mesh:// plugin
- [ ] 9.1 `src/plugins/drive-mesh/drive-mesh.ts` — invoke `parseQuery(parts.query, "drive+mesh")`; honour `?advertise=true|false`; on `true` requires `options.advertise` hook else throws `DRIVE_URI.CAPABILITY_MISSING` (reason: `advertise`) synchronously at `parse`
- [ ] 9.2 `src/plugins/drive-mesh/drive-mesh.ts` — after a successful write under `?advertise=true`, invoke `options.advertise.advertise(fileId, bitset, totalChunks, signal)`; rejection → wrap as `DRIVE_URI.MESH_PROPAGATION_TIMEOUT`; on success emit `drive_uri:mesh_advertised`
- [ ] 9.3 `src/plugins/drive-mesh/drive-mesh.ts` — without `?advertise`, emit `drive_uri.mesh_write_local_only` span event on every successful write
- [ ] 9.4 `src/plugins/drive-mesh/drive-mesh.md` — update for opt-in flow + redaction policy
- [ ] 9.5 `src/plugins/drive-mesh/drive-mesh.test.ts` — default = no advertise (spy asserts zero calls + local-only span event); `?advertise=true` → exactly one advertise call; `MeshAdvertiseHook` rejection → `MESH_PROPAGATION_TIMEOUT`; absence of hook with `?advertise=true` → `CAPABILITY_MISSING`; full op matrix
## 10. `@suaveplan/drive-uri` — drive+template:// plugin
- [ ] 10.1 `src/plugins/drive-template/drive-template.ts` — invoke `parseQuery(parts.query, "drive+template")`; forward `?nonce=…` to `templateProvider.fetchTemplate(templateId, { nonce, signal })`
- [ ] 10.2 `src/plugins/drive-template/drive-template.ts` — `set`/`delete`/`createWriteStream` throw `DRIVE_URI.TEMPLATE_READ_ONLY` synchronously, carrying `{ scheme, path: key, templateId }`
- [ ] 10.3 `src/plugins/drive-template/drive-template.md` — update
- [ ] 10.4 `src/plugins/drive-template/drive-template.test.ts` — full read-side op matrix post-hydrate; nonce forwarding; write/delete/writeStream all reject `TEMPLATE_READ_ONLY`; existing hydration behaviour preserved (memoised, concurrent-share, NOT_AVAILABLE, INVALID)
## 11. `@suaveplan/drive-uri` — OTel
- [ ] 11.1 `src/otel/otel.ts` — add `drive_uri.stream_chunks_fetched` histogram; setter for `drive.uri.range` and `drive.uri.has_advertise` span attributes; extend `redactUri` to also strip the fragment
- [ ] 11.2 `src/otel/otel.md` — update
- [ ] 11.3 `src/otel/otel.test.ts` — every counter/histogram fires per op; range attribute present iff range supplied; advertise attribute on mesh writes; redaction holds for `?` and `#`
## 12. `@suaveplan/drive-uri` — conformance
- [ ] 12.1 `src/conformance/conformance.test.ts` — re-run `runUriPluginConformance` with full **read/write/delete/stat/list/exists/range** matrices for all three plugins. Fixtures: 3 valid URIs + 6 invalid + build round-trip + range round-trip per scheme; `drive`/`drive+mesh` pass full cycle; `drive+template` rejects writes with `TEMPLATE_READ_ONLY`
## 13. Integration tests (no mocks per CLAUDE.md rule 2)
- [ ] 13.1 `tests/integration/drive-roundtrip-write.test.ts` — real local-storage `Drive`; `resolver.write` then `resolver.read`; assert equality
- [ ] 13.2 `tests/integration/drive-mesh-roundtrip-write.test.ts` — mesh-configured `Drive` with a real advertise hook implementation; default = no advertise; `?advertise=true` → exactly one advertise observed
- [ ] 13.3 `tests/integration/template-write-rejection.test.ts` — wired template-backed `Drive`; `resolver.write` and `resolver.remove` both reject `TEMPLATE_READ_ONLY`
- [ ] 13.4 `tests/integration/drive-range-roundtrip.test.ts` — write 200 KiB file via local Drive; `resolver.open("drive://…", { offset: 60_000, length: 10_000, signal })` returns exactly bytes [60 000, 70 000)
- [ ] 13.5 `tests/integration/drive-stat-list-exists.test.ts` — full read-side matrix on a real Drive: `resolver.stat` → `StatResult`, `resolver.exists` → boolean, `resolver.list` → URIs that round-trip through `resolver.read`
## 14. Migration audit
- [ ] 14.1 `grep -rln "DRIVE_URI.WRITE_UNSUPPORTED" packages/ | grep -v "/dist/\|/coverage/\|/drive-uri/"` — every hit reviewed; non-matching consumers re-routed onto `TEMPLATE_READ_ONLY` / `RANGE_*` / `MESH_PROPAGATION_TIMEOUT` where appropriate
- [ ] 14.2 Update `README.md` Features + Modules sections to reflect full surface
## 15. Pre-publish gate (§13)
- [ ] 15.1 Spec reconciliation — walk every `### Requirement` and `#### Scenario` in `specs/drive-uri/spec.md` AND `specs/drive/spec.md`; confirm every `**Verified by:**` test name exists verbatim
- [ ] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/drive --filter=@suaveplan/drive-uri` — green
- [ ] 15.3 `bunx turbo test:coverage --filter=@suaveplan/drive --filter=@suaveplan/drive-uri` — 100/100/100/100 on touched modules
- [ ] 15.4 `bunx turbo build --filter=@suaveplan/drive --filter=@suaveplan/drive-uri` — green
- [ ] 15.5 Banned-pattern, barrel, test-runner-import, missing-`.md`, README, bare-version, subpath-export, security scans — all clean
- [ ] 15.6 Dependent sweep: `bunx turbo test --filter=...@suaveplan/drive` — green
## 16. Root-doc refresh (§13b)
- [ ] 16.1 `bun scripts/folder-reorg/generate-manifest.ts` — refresh `MANIFEST.md`
- [ ] 16.2 `bun scripts/generate-missing.ts` — refresh `MISSING.yml`
- [ ] 16.3 Update `STATUS.md` / `SUMMARY.md` paragraphs for `drive-uri` and `drive` to reflect full surface
- [ ] 16.4 `bunx markdownlint-cli2 --fix` over touched docs
## 17. Archival (§14)
- [ ] 17.1 Fold `specs/drive-uri/spec.md` delta into `openspec/specs/drive-uri/spec.md`
- [ ] 17.2 Fold `specs/drive/spec.md` delta into `openspec/specs/drive/spec.md`
- [ ] 17.3 Move `openspec/changes/complete-drive-uri/` → `openspec/archive/<yyyy-mm-dd>-complete-drive-uri/`
- [ ] 17.4 Delete `openspec/changes/_superseded-narrow-deferrals-2026-05-17/` (research already folded into this change's archive)