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

9.4 KiB

Proposal: complete-drive — fill VFS-core gaps in @suaveplan/drive

  • Change ID: complete-drive
  • Capability: drive
  • Package: @suaveplan/drive
  • Status: PROPOSED
  • Design: ./design.md
  • Spec delta: ./specs/drive/spec.md
  • Companions (parallel, share surface, scope-disjoint): complete-drive-uri, complete-types-drive-promotion

Why

The current genesis @suaveplan/drive ships the VFS spine (constructor overloads, init/dispose FSM, plugin slot, middleware chain for read/write/delete/stat/list, template hydration via hydrateFrom/getKeyStatus/getPresence, DRIVE_* codes) but several capabilities implied by the package's identity as a file-VFS core are either partial or missing. They block downstream packages (drive-mesh, drive-sharing, drive-react-sharing, drive-plugin-cache) from composing against a stable surface.

What

Audit findings (genesis vs. upstream + CLAUDE.md identity)

# Gap Today Required
1 createReadStream signature (path, signal?) positional overload (path, options?: { offset?; length?; signal? }) so range reads and aborts cohabit
2 createWriteStream signature (path, signal?) positional overload (path, options?: { signal? }) for parity, no behaviour drift
3 Drive.copy(src, dst, opts?) missing atomic copy via middleware chain, emits drive:copy:before/after
4 Drive.move(src, dst, opts?) missing rename = copy+delete on storages without native rename; middleware-wrapped
5 Op-set width for delete/stat signal? positional accept { signal? } options bundle (matches read/write)
6 Drive.list return type typed unknown typed AsyncIterable<DriveEntry> + page result; respect recursive/limit/cursor
7 Drive.watch(path, opts?) missing EventBus-backed change subscription returning AsyncIterable<DriveChangeEvent> + unsubscribe()
8 Hydration progress events callback only also emit drive:hydrate:progress on the EventBus (delivery to multiple subscribers)
9 Middleware coverage read/write/delete/stat/list add copy, move, watch middleware slots so plugins can wrap every op
10 Plugin lifecycle API install + dispose-on-drive-dispose unloadPlugin(name), listPlugins(), findPlugin(name), ordered handle map, idempotent unload
11 Quota / usage absent Drive.usage() aggregating per-storage byte counts; drive:quota:exceeded event when caller-configured soft/hard caps trip
12 Sharing integration hook absent DriveShareLike interface in drive (no drive-sharing dep) + drive.shareHook?: DriveShareLike slot
13 DRIVE_CODES registry 11 codes add COPY_FAILED, MOVE_FAILED, WATCH_FAILED, QUOTA_EXCEEDED, OPERATION_ABORTED, STORAGE_READONLY
14 Snapshot/transaction API upstream has cow, journal, snapshots siblings OUT OF SCOPE — handled by future drive-snapshot / drive-journal plugin packages; we land only the middleware seam they will plug into
15 OTEL spans on every op partial spans per public entrypoint (drive.read, drive.write, drive.copy, …) named drive.<op> with drive.storage.id attribute
16 100% coverage currently below 100% hard gate at 100/100/100/100 (statements/branches/functions/lines), istanbul

Out of scope

  • Sharing-specific UX — owned by @suaveplan/drive-sharing and @suaveplan/drive-react-sharing. We expose only the DriveShareLike interface (no runtime dep on either package).
  • Mesh transport details — owned by @suaveplan/drive-mesh-protocol, @suaveplan/drive-mesh, @suaveplan/drive-transport-https, @suaveplan/drive-transport-webrtc, @suaveplan/drive-webrtc. We expose only the plugin slot + middleware seam they consume.
  • Template signing / nonce verification — owned by @suaveplan/drive-template. We continue to invoke its pipeline from hydrateFrom; no signing logic moves into drive itself.
  • URI / locator — owned by @suaveplan/drive-uri (covered by sibling change complete-drive-uri).
  • Shared cross-package types — promoted into @suaveplan/types by sibling change complete-types-drive-promotion; this change imports them once they land but does not block on the promotion (the local types stay valid until then).
  • Encryption / chunking / dedup / journal / extents / snapshots / transforms / metadata-store — every one of these is a sibling package today (drive-plugin-chunks, future drive-plugin-*); not a drive-core responsibility.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/drive to enumerate. For each dependent:

  • @suaveplan/drive-template — [x] tests run / [x] docs reviewed / [ ] no action needed — consumes hydrateFrom + nonce LRU; new progress events are additive, no breakage.
  • @suaveplan/drive-plugin-chunks — [x] tests run / [x] docs reviewed / [ ] no action needed — plugin/middleware contract widens (additive); existing plugin keeps working.
  • @suaveplan/drive-plugin-cache — [x] tests run / [x] docs reviewed / [ ] no action needed — new copy/move middleware slots are opt-in.
  • @suaveplan/drive-mesh-protocol — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: pure wire-protocol, does not touch Drive surface).
  • @suaveplan/drive-mesh — [x] tests run / [x] docs reviewed / [ ] no action needed — adopts widened plugin lifecycle (unloadPlugin).
  • @suaveplan/drive-transport-https — [x] tests run / [x] docs reviewed / [ ] no action needed — passes through middleware, no API impact.
  • @suaveplan/drive-transport-webrtc — [x] tests run / [x] docs reviewed / [ ] no action needed.
  • @suaveplan/drive-webrtc — [x] tests run / [x] docs reviewed / [ ] no action needed.
  • @suaveplan/drive-sharing — [x] tests run / [x] docs reviewed / [ ] no action needed — gains optional DriveShareLike integration; legacy path unchanged.
  • @suaveplan/drive-react-sharing — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: re-exports drive-sharing types only).

Migration

  • Old: drive.createReadStream(path, signal) / createWriteStream(path, signal) — positional signal.

  • New: drive.createReadStream(path, { offset?, length?, signal? }) / createWriteStream(path, { signal? }) — options bundle.

  • Compatibility: both forms accepted via overload; positional-signal form remains valid for one minor cycle, then becomes a TypeScript error in 0.1.0.

  • Search command for consumers: grep -rn "createReadStream\|createWriteStream" packages/ | grep -v "/dist/\|/coverage/"

  • Recommended replacement: always pass the options bundle.

  • Old: drive.delete(path, signal) / drive.stat(path, signal) positional.

  • New: drive.delete(path, { signal? }) / drive.stat(path, { signal? }) options bundle (positional kept as overload for one cycle).

  • Search: grep -rn "drive\.\(delete\|stat\)(" packages/ | grep -v "/dist/".

Parity Checklist

Capability Upstream (main) Genesis after this change Notes
POSIX open/close/read/write/lseek/fsync file-descriptor model ✅ 🚫 Scoped out: genesis Drive is a high-level VFS, not POSIX FD emulation. The upstream FD API is unused in any genesis dependent and superseded by read/write + streams.
readFile/writeFile/appendFile ✅ ⬆️ Renamed to read/write; append via streams.
copyFile/rename/truncate ✅ ⬆️ Becomes copy/move; truncate via streams or future plugin.
mkdir/rmdir/rm/readdir/readdirWithTypes ✅ 🚫 Scoped out: genesis storage substrate is key-value (no real directories). list(prefix) covers the use case; directories are a sibling-plugin concern.
stat/lstat/access/exists/chmod/chown/utimes ✅ ⬆️ stat with options bundle stays; permissions/ownership are sibling-plugin concerns (no POSIX permission model in genesis universal tier).
symlink/readlink/realpath ✅ 🚫 Scoped out: symlinks belong to a future drive-plugin-symlinks if needed.
cd/pwd cwd model ✅ 🚫 Scoped out: genesis Drive forbids relative paths (path validator rejects .. and leading /); cwd has no meaning.
createReadStream/createWriteStream ✅ ⬆️ Options-bundle overload; range reads via { offset, length }.
Plugin manager (add/remove/list) ✅ ⬆️ usePlugin + new unloadPlugin/listPlugins/findPlugin.
Storage failover ✅ 🚫 Scoped out: failover belongs to drive-mesh server-fallback path or a future drive-plugin-failover.
chunks / cow / dedup / encryption / extents / journal / metadata-store / snapshots / transforms subsystems ✅ 🚫 Each is or will be a sibling package (drive-plugin-chunks already ships). VFS core only exposes the plugin/middleware seam.
Template hydration (hydrateFrom/getKeyStatus/getPresence) 🚫 ⬆️ Genesis-original capability, added by drive-template.
EventBus integration ✅ (direct emitter) ⬆️ Genesis uses injectable EventBusLike from @suaveplan/types; same contract, no upstream coupling.
Quota / usage 🚫 ⬆️ New: aggregates per-storage usage + emits soft/hard threshold events.
Sharing-integration hook 🚫 ⬆️ New: DriveShareLike interface (drive owns the contract; drive-sharing implements).