# Complete `@suaveplan/drive-uri` — full per-scheme write + range-read parity, every operation, every option **Package**: `@suaveplan/drive-uri` (+ MODIFIED surface on `@suaveplan/drive`) **Status**: PROPOSED **Type**: capability-completion (single change supersedes the two narrow follow-ups) **Supersedes**: `openspec/changes/_superseded-narrow-deferrals-2026-05-17/add-drive-uri-writes/` AND `…/add-drive-range-reads/` — both deferrals land in this single change. **Depends on**: `add-drive-uri` (archived `2026-05-16`), `add-storage-uri` (archived). - **Design**: ./design.md - **Spec deltas**: - ./specs/drive-uri/spec.md (primary) - ./specs/drive/spec.md (MODIFIED — `createReadStream` range overload) ## Why `add-drive-uri` shipped read-only with two explicit deferrals — **writes** and **range reads** — plus a documented gap on **buffered-read AbortSignal threading** (carried as a footnote in the canonical spec). Two narrow follow-ups (`add-drive-uri-writes`, `add-drive-range-reads`) were drafted, then superseded so the package can land at functional completeness in a single coherent change. This change closes every gap and brings `drive-uri` to full parity with what `Drive` itself exposes, so every URI operation is supported with first-class semantics. Concretely, the gaps closed: | # | Gap | Source of deferral | |---|--------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| | 1 | `drive://` writes / removes / write-streams | `add-drive-uri` proposal § Out of scope (line 32) | | 2 | `drive+mesh://` writes / removes / write-streams | `add-drive-uri` proposal § Out of scope (line 32) | | 3 | `drive+template://` writes permanently locked-down with own code | `add-drive-uri-writes` proposal — `DRIVE_URI.TEMPLATE_READ_ONLY` rather than the temporary `WRITE_UNSUPPORTED` | | 4 | Range reads `{ offset, length }` on all three schemes | `add-drive-uri` proposal § Out of scope (line 35); `add-drive-range-reads` | | 5 | Chunk-aware mesh range optimisation (`specific` ↔ `bitset` pivot) | `add-drive-range-reads` proposal | | 6 | `AbortSignal` threading through buffered `get`/`set`/`delete`/`stat`/`exists`/`list` paths | Existing canonical spec footnote (lines 7-9 of `openspec/specs/drive-uri/spec.md`) | | 7 | `createWriteStream(key, body, signal?)` adapter surface | Not surfaced today — `StorageUriResolver.write` falls back to buffered drain | | 8 | First-class `stat` returning `StatResult` (size / lastModified) | `getMetadata` hook on the adapter — never wired | | 9 | First-class `list` with prefix + pagination via `Drive.list` | `DriveStorageLikeAdapter.list` exists but lacks resolver re-serialisation through `UriPlugin.build` | | 10 | Cheap `exists` via `Drive.getPresence` / `getKeyStatus` (no buffered `read`) | Today's `exists` calls `Drive.stat` — fine for local, expensive for template-backed | | 11 | Per-scheme query-string options (`?nonce=…` for templates, `?advertise=true|false` for mesh) | Never surfaced; need redaction policy | | 12 | Mesh re-advertise opt-in via `?advertise=true` query param + EventBus emission | `add-drive-uri-writes` left this consumer-controlled by default; this change keeps the default but adds an opt-in | | 13 | OTel span attributes per op with backend identification + range tag | `add-drive-range-reads` proposal | | 14 | EventBus emission on every write/delete for downstream sync | `add-drive-uri-writes` design tangentially | | 15 | Exhaustive `DRIVE_URI.*` error registry (read-side, write-side, template-read-only, range, mesh-propagation, capability) | Today's registry is 7 codes; ships at 14 | | 16 | `runUriPluginConformance` re-run against full read/write/delete/stat/list/exists matrix on all three plugins | `add-drive-uri-writes` proposal | The composition model is unchanged: three `UriPlugin` factories plug into the existing `StorageUriResolver`. No new resolver class. No re-implementation of any chunk codec, mesh transport, or template-signing logic — those stay inside `Drive` and its installed plugins. ## What changes ### `@suaveplan/drive` (MODIFIED) - MODIFIED: `Drive.createReadStream(path, signalOrOptions?)` — overloaded. Bare `AbortSignal` preserved; new `RangeReadOptions` form accepts `{ offset?, length?, signal? }`. `instanceof AbortSignal` discriminator at runtime. Synchronous validation: `Number.isInteger(x) && x >= 0 && Number.isFinite(x)`; failures throw `DRIVE.RANGE_INVALID` BEFORE stream construction. - MODIFIED: `Drive.createWriteStream(path, signal?)` — unchanged signature; reused by adapter's `createWriteStream` wrapper. - ADDED: `RangeReadOptions` interface on `src/types/drive.types.ts`. - ADDED: `DRIVE.RANGE_INVALID`, `DRIVE.RANGE_OUT_OF_BOUNDS` codes in `DRIVE_CODES` registry (11 → 13 codes). - MODIFIED: `DriveStorage.capabilities?.nativeRange?: boolean` flag. `defaultCreateReadStream` honours range via slicing transform; native-range storages forward `{ offset, length }` to `storage.createReadStream`. - MODIFIED: `DriveMiddleware` context gains `range?: { offset: number; length?: number }`. Chunks-plugin reassemble path (out of scope for THIS package; covered in design.md as a follow-up to land within the same wave) honours it. ### `@suaveplan/drive-uri` (the bulk of the change) - ADDED — per-scheme per-op matrix (full): | Operation | `drive://` | `drive+mesh://` | `drive+template://` | |-------------------|---------------------------------------------|--------------------------------------------------------------------------|--------------------------------------| | `get` | `Drive.read(key, { signal })` | `Drive.read(key, { signal })` | hydrate-then-read | | `set` (write) | `Drive.write(key, bytes, { signal })` | `Drive.write` + optional `mesh.advertise(…)` via `?advertise=true` opt-in | `DRIVE_URI.TEMPLATE_READ_ONLY` | | `delete` | `Drive.delete(key, signal)` | `Drive.delete` + optional `mesh.unadvertise(fileId)` on opt-in | `DRIVE_URI.TEMPLATE_READ_ONLY` | | `stat` | `Drive.stat(key, signal)` → `StatResult` | same; cache-aware on template hits | same (post-hydrate) | | `exists` | `Drive.getPresence(key)` cheap path → fallback `stat` | same | same (post-hydrate) | | `list` | `Drive.list(prefix, { signal, limit, cursor })` → keys re-serialised via `UriPlugin.build` | same | same | | `createReadStream`| `Drive.createReadStream(key, { offset, length, signal })` | same; chunk-aware mesh request via `ChunkRequest.specific` ↔ `bitset` pivot | same | | `createWriteStream`| `Drive.createWriteStream(key, signal)` | same | `DRIVE_URI.TEMPLATE_READ_ONLY` | | `clear` | `DRIVE_URI.WRITE_UNSUPPORTED` (footgun) | same | same | - ADDED — `DRIVE_URI_CODES` registry grows 7 → **14 codes** via `defineCodes("DRIVE_URI", […] as const)`. New codes: - `TEMPLATE_READ_ONLY` — permanent template-mutation rejection. - `RANGE_INVALID` — invalid `{ offset, length }` per-scheme echo of `DRIVE.RANGE_INVALID`. - `RANGE_OUT_OF_BOUNDS` — per-scheme echo of `DRIVE.RANGE_OUT_OF_BOUNDS` / `CHUNKS.RANGE_OUT_OF_BOUNDS`. - `MESH_PROPAGATION_TIMEOUT` — explicit `?advertise=true` write where the consumer-supplied advertise hook rejects or times out. - `STAT_UNAVAILABLE` — underlying storage cannot answer `stat` and the URI surface needs a positive answer. - `LIST_UNSUPPORTED` — the bound `Drive` does not expose `list` (the AbstractStorage backing it lacks the method). - `INVALID_QUERY` — query-string option is malformed (e.g. `?advertise=maybe`). - **Retained**: `INVALID_URI`, `DRIVE_ID_MISMATCH`, `TEMPLATE_NOT_AVAILABLE`, `TEMPLATE_INVALID`, `WRITE_UNSUPPORTED` (now narrowed to `clear()` call site only), `CAPABILITY_MISSING`, `ABORTED`. - ADDED — `DriveUriError` discriminants extend with `range?: { offset: number; length?: number }`, `fileId?: string`. - ADDED — `AbortSignal` threading on every buffered op: `get`/`set`/`delete`/`stat`/`exists`/`list` accept and forward `AbortSignal` to the underlying `Drive` method. Pre-aborted signal → `DRIVE_URI.ABORTED` short-circuit before any `Drive` call. - ADDED — `InstrumentedDriveAdapter.createWriteStream(key, body, signal?)`: per-scheme dispatch (`drive`/`drive+mesh` → forward; `drive+template` → throw `TEMPLATE_READ_ONLY`). - ADDED — `InstrumentedDriveAdapter.getMetadata(key, signal?): Promise`: translates `Drive.stat` into `StatResult { size, lastModified, etag?, contentType? }` using `DriveStats.checksum` (etag) and `Drive.stat().modifiedAt` (lastModified). `StorageUriResolver.stat()` picks this up automatically via the existing `StreamingStorageExtensions.getMetadata` duck-type. - ADDED — Per-scheme `UriPlugin.build` round-trips `meta` (`driveId`/`swarmId`/`templateId`) **and** preserves any incoming query string when supplied via `meta.query`. The resolver's `list` re-serialiser receives prefix-listed keys and produces fully-qualified URIs that round-trip through `parse`. - ADDED — Query-string parsing on every plugin: - `drive+mesh://swarm/path?advertise=true|false` → opt-in mesh re-advertise on write/delete. - `drive+template://template/path?nonce=…` → forwards `nonce` to `templateProvider.fetchTemplate(templateId, { nonce, signal })` (the provider is widened with an optional second `options` bag — back-compat preserved). - `?signal-timeout-ms=` (any scheme) → upper bound on a single-op latency before raising `DRIVE_URI.ABORTED`. - Unknown query keys → `DRIVE_URI.INVALID_QUERY` with the offending key. - ADDED — Query-string redaction policy: every `drive.uri` span attribute, every error's `.uri`, and every emitted EventBus event strips the query string (`redactUri` already exists; extended to also strip the fragment). Sensitive keys (`nonce`, `peers`, future `auth=`) are never logged or surfaced. - ADDED — Mesh re-advertise via `?advertise=true`: when set on a `drive+mesh://` write, the plugin invokes the consumer-supplied `MeshAdvertiseHook` from `DriveMeshUriPluginOptions.advertise?`. When the hook is absent, the plugin raises `DRIVE_URI.CAPABILITY_MISSING` with `reason: "advertise"` synchronously at construction time *only if* the consumer opts in at the URI level — wiring stays purely additive. The hook is invoked **after** local persistence is durable; failure is wrapped as `DRIVE_URI.MESH_PROPAGATION_TIMEOUT`. - ADDED — `MeshAdvertiseHook` interface (universal-tier, lives in `types/plugin-options.types.ts`): `advertise(fileId, bitset, totalChunks, signal?): Promise` + `unadvertise(fileId, signal?): Promise`. The hook wraps the consumer's `mesh.advertise(...)` / `mesh.unadvertise(...)` calls; `drive-uri` never depends on `@suaveplan/drive-mesh` directly. - ADDED — EventBus emission on every write/delete success/failure when the wrapped `Drive` exposes `events`: topics `drive_uri:write` / `drive_uri:delete` carrying `{ scheme, key, uri (redacted), outcome, durationMs, fileId?, range? }`. Used by downstream sync engines (`@suaveplan/vault-sync`, future `drive-sharing` flows) to drive incremental updates. EventBus is read off the `Drive` reference passed at construction; absence is a no-op. - ADDED — Span event `drive_uri.mesh_write_local_only` on every `drive+mesh://` write where `?advertise=true` is NOT set, so operators can audit that the consumer-controlled gossip contract holds. - MODIFIED — `DRIVE_URI.WRITE_UNSUPPORTED` retained but narrowed to `DriveStorageLikeAdapter.clear()` only. Every other call site that previously raised it now succeeds or raises a more specific code per the matrix above. - MODIFIED — `DriveStorageLikeAdapter`: - `get(key, signal?)`, `set(key, value, signal?)`, `delete(key, signal?)`, `exists(key, signal?)`, `list(options & { signal? })`, `stat(key, signal?)` all accept and forward `AbortSignal`. - `createReadStream(key, options?: { offset?, length?, signal? })` forwards range to `Drive.createReadStream(key, options)`. - `createWriteStream(key, body, signal?)`: consumes a `ReadableStream`, pipes into `Drive.createWriteStream(key, signal)`. - `getMetadata(key, signal?)` → `StatResult | undefined` per `StreamingStorageExtensions`. - `capabilities.nativeRange === true` advertised so `StorageUriResolver` uses the range-aware `createReadStream` directly. - MODIFIED — Conformance: `runUriPluginConformance` re-run with **full read/write/delete/stat/list/exists/range matrices** for all three plugins. Fixtures: - `drive://` — full cycle passes. - `drive+mesh://` — full cycle passes; mesh-advertise spy asserts zero calls when `?advertise` is omitted, exactly one call when `?advertise=true`. - `drive+template://` — read/stat/exists/list pass; `set`/`delete`/`createWriteStream` reject with `DRIVE_URI.TEMPLATE_READ_ONLY`. - ADDED — Per-package OTel surface extensions: - `drive_uri.stream_chunks_fetched` histogram tagged `{scheme}` — chunk count per `createReadStream` call (range narrows this). - `drive.uri.range = "{offset}-{end}"` span attribute, omitted when no range. - `drive.uri.has_advertise = "true"|"false"` span attribute on `drive+mesh://` write spans. - All existing counters preserved. ## Out of scope Per user instruction, only two carve-outs: - **NEW URI schemes.** Adding a fourth scheme (e.g. `drive+cdn://`) is a separate change. The three existing schemes ship at full functional completeness here. - **Templating policy.** Whether templates are mutable (re-issue, multi-version, key rotation policy) lives in `@suaveplan/drive-template`. This change codifies the URI surface's read-only treatment via `TEMPLATE_READ_ONLY` but does not alter template semantics themselves. ## Source mapping This is a **capability-completion** change for a genesis-original package. Surface anchors: - `packages/drive/drive-uri/src/plugins/drive/drive.streams.ts:69-84` — current unconditional `set` rejection (replaced). - `packages/drive/drive-uri/src/adapter/drive-storage-like.ts:75-86` — `set`/`delete` already pass-through internally; the instrumented override now per-scheme dispatches. - `packages/drive/drive-uri/src/adapter/drive-storage-like.ts:88-119` — `list` exists; resolver re-serialisation via `UriPlugin.build` newly required. - `packages/drive/drive/src/drive/drive.ts:180-240` — `Drive.write` / `delete` / `stat` / `list` / `createReadStream` / `createWriteStream` consumed verbatim. - `packages/drive/drive/src/drive/drive.ts:222-230` — `createReadStream` overload landing zone. - `packages/drive/drive/src/file-operations/streams.ts:50-70` — `nativeRange` slicing-transform insertion point; mirrors existing `nativeStreams` capability flag pattern. - `packages/drive/drive-mesh/src/mesh/drive-mesh/drive-mesh.ts:176-193` — `advertise(fileId, bitset, totalChunks)` / `unadvertise(fileId)` shape consumed structurally by `MeshAdvertiseHook` (no direct dep). - `packages/drive/drive-mesh-protocol/src/types/protocol.types.ts:38-42` — `ChunkRequest.specific | bitset` shapes; pivot at 64 chunks per `MAX_CHUNKS_DEFAULT`. - `packages/storage/storage-uri/src/resolver/resolver.writes.ts:30-75` — confirms `SuaveplanError` subclasses pass through `writeViaPlugin` unchanged, so every new `DRIVE_URI.*` code reaches the caller without a `STORAGE_URI.*` envelope. - `packages/storage/storage-uri/src/resolver/resolver.streams.ts:24-54` — `openViaPlugin` already forwards to `adapter.createReadStream` (no fallback) when present; range bag passes through via the existing options-args shape. - `packages/storage/storage-uri/src/types/plugin.types.ts:41-67` — `StreamingStorageExtensions.getMetadata` / `createReadStream` / `createWriteStream` duck-type — `drive-uri` adapter implements all three. - `openspec/archive/2026-05-16-add-drive-uri/proposal.md` lines 30-38 — original deferral text. - `openspec/changes/_superseded-narrow-deferrals-2026-05-17/add-drive-uri-writes/proposal.md` — superseded research, reused. - `openspec/changes/_superseded-narrow-deferrals-2026-05-17/add-drive-range-reads/proposal.md` — superseded research, reused. ### Requirements with no direct source line | Requirement | Justification | |----------------------------------------------------------------------------|---------------| | Single coherent change instead of two narrow follow-ups | The deferrals were carved fine-grained on the assumption write semantics were "non-trivial"; revisiting per-scheme showed they are not. Landing both together avoids two passes through the conformance harness, two rounds of doc churn, and two dependent-sweep cycles. The supersede notice (`_superseded-narrow-deferrals-2026-05-17/`) records the consolidation. | | `?advertise=true` query opt-in rather than always-advertise on mesh write | Direct `Drive.write` does not auto-advertise — the URI surface MUST preserve that contract by default. The opt-in gives the URI an explicit knob without bending the default. | | `MeshAdvertiseHook` lives in `drive-uri` not `drive-mesh` | `drive-uri` MUST NOT depend on `drive-mesh` (it would couple the URI tier to a concrete transport). The structural hook lets consumers wire their `mesh.advertise(...)` without `drive-uri` ever importing `@suaveplan/drive-mesh`. | | EventBus emission off the wrapped `Drive`'s `events` rather than a separate bus | The `Drive` already carries `events: EventBusLike | undefined`(`drive.types.ts:73`); reusing it keeps consumer wiring minimal and matches the precedent set by`key:grant`(`Drive.hydrateFrom`). | | `TEMPLATE_READ_ONLY` distinct from `WRITE_UNSUPPORTED` rather than reusing | `WRITE_UNSUPPORTED` was a *temporary* state code from `add-drive-uri`; templates are *permanently* read-only via an architectural constraint (authority re-signing). Distinct codes let consumers branch correctly. | | Range overload via `instanceof AbortSignal` discriminator | The only other shape that satisfies the position-1 type is `RangeReadOptions`; `AbortSignal.prototype` is a stable cross-runtime brand. Duck-typing on `signal?.aborted` would falsely match `RangeReadOptions`. | | Range registry mirrored on `DRIVE_URI` rather than only `DRIVE` | Consumers branching on the URI surface should not need to know which underlying error class fired; the URI-tier echoes the canonical drive codes via wrapping while preserving `cause`. | | Cheap `exists` via `Drive.getPresence` over `Drive.stat` | `getPresence` returns a synchronous `ChunkPresence` from the in-memory `FileLocationIndex` for hydrated files (template path) and falls back to `stat` for raw paths — strictly cheaper for `drive+template://` reads. | | Query-string redaction always-on | A future opt-in `?auth=…` is plausible; codifying redaction now prevents a leak when that opt-in lands. | ## Verification approach Every requirement in the spec deltas carries a `**Verified by:**` line referencing concrete `it(...)` titles in planned test files. The pre-archive gate (`§13` of `AGENTS.md`) re-runs the full conformance matrix against all three plugins. Integration tests (no mocks per CLAUDE.md rule 2) cover real-Drive round-trips for read, write, delete, stat, list, exists, range, and template hydration paths. ## Migration | Old | New | |------------------------------------------------------------------|--------------------------------------------------------------| | `resolver.write("drive://…", bytes)` → `DRIVE_URI.WRITE_UNSUPPORTED` | succeeds; returns `void` | | `resolver.write("drive+mesh://…", bytes)` → `DRIVE_URI.WRITE_UNSUPPORTED` | succeeds; mesh advertise opt-in via `?advertise=true` | | `resolver.write("drive+template://…", bytes)` → `DRIVE_URI.WRITE_UNSUPPORTED` | `DRIVE_URI.TEMPLATE_READ_ONLY` | | `resolver.remove("drive+template://…")` → silent footgun (forwarded to `Drive.delete`) | `DRIVE_URI.TEMPLATE_READ_ONLY` | | `resolver.stat("drive://…")` → `undefined` (no `getMetadata`) | `StatResult { size, lastModified, etag? }` | | `resolver.open("drive+mesh://…/clip.mov", signal)` → whole-file fetch | same; `open(..., { offset, length, signal })` fetches only in-range chunks | | `DRIVE_URI.WRITE_UNSUPPORTED` raised for `set`/`delete` | only raised for `DriveStorageLikeAdapter.clear()` | Consumer search: `grep -rln "DRIVE_URI.WRITE_UNSUPPORTED" packages/ | grep -v "/dist/\|/coverage/\|/drive-uri/"`. Branch new code on `err.code === "DRIVE_URI.TEMPLATE_READ_ONLY"` for the immutable template surface; on `DRIVE_URI.RANGE_INVALID` / `RANGE_OUT_OF_BOUNDS` for range errors; on `DRIVE_URI.MESH_PROPAGATION_TIMEOUT` for opt-in advertise failures. ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/drive-uri` to enumerate. The package's downstream surface is currently empty (`drive-uri` is consumed only via integration tests at the time of writing); the broader `...@suaveplan/drive` dependents drive verification: - `@suaveplan/drive-uri` — [x] tests run / [x] docs reviewed / [ ] no action needed - `@suaveplan/drive-plugin-chunks` — [x] tests run / [x] docs reviewed / [ ] no action needed (middleware honours `ctx.range`; `#reassemble` extended) - `@suaveplan/drive-mesh` — [x] tests run / [x] docs reviewed / [ ] no action needed (pull path constructs `ChunkRequest.specific` / `bitset` from range; `advertise` consumed via structural hook) - `@suaveplan/drive-mesh-protocol` — [ ] tests run / [x] docs reviewed / [x] no action needed (no protocol wire change; existing union shapes used) - `@suaveplan/drive-transport-https` — [ ] tests run / [x] docs reviewed / [x] no action needed (transport carries envelopes opaquely) - `@suaveplan/drive-transport-webrtc` — [ ] tests run / [x] docs reviewed / [x] no action needed (same) - `@suaveplan/drive-template` — [ ] tests run / [x] docs reviewed / [x] no action needed (hydration unchanged) - `@suaveplan/drive-sharing` — [ ] tests run / [x] docs reviewed / [x] no action needed (calls `Drive.read`) - `@suaveplan/storage-uri` — [x] tests run / [x] docs reviewed / [ ] no action needed (`writeViaPlugin` already passes through `SuaveplanError` subclasses; `openViaPlugin` already forwards `createReadStream`) - `@suaveplan/drive` — [x] tests run / [x] docs reviewed / [ ] no action needed (range overload is purely additive) ### Affected upstreams (none breaking) | Package | Action | Breaking? | |-----------------------------------|-----------------------------------------------------------------------------------------|-----------| | `@suaveplan/drive` | ADD `RangeReadOptions`, `createReadStream` overload, `RANGE_*` codes, `nativeRange` cap | no | | `@suaveplan/storage-uri` | None — `StreamingStorageExtensions` already specifies `createWriteStream` + `getMetadata`| no | | `@suaveplan/drive-mesh` | None — `advertise`/`unadvertise` consumed via structural hook only | no | | `@suaveplan/drive-template` | None — only `DriveTemplate` type consumed | no | | `@suaveplan/drive-plugin-chunks` | Honour `ctx.range` (delivered as a sibling change inside the same wave) | no | | `@suaveplan/types` | None — `DriveLike` hoist tracked separately | no | ### Ordering Unblocked. All packages exist on genesis today; the change is additive at every boundary.