Initial Commit
This commit is contained in:
112
openspec/changes/complete-drive/proposal.md
Normal file
112
openspec/changes/complete-drive/proposal.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 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). |
|
||||
Reference in New Issue
Block a user