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

25 KiB

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)
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<StatResult | undefined>: 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=<int> (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<void> + unadvertise(fileId, signal?): Promise<void>. 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<Uint8Array>, 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
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.