Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
# OpenSpec Change: drive-port
- **Change ID**: drive-port
- **Package**: `@suaveplan/drive` (new)
- **Status**: PROPOSED
- **Date**: 2026-04-28
## Summary
Port `@suaveplan/drive` from `../suaveplan/packages/universal/drive` into genesis as a **file-VFS-only** core. Drive in genesis exposes paths and bytes — `read`, `write`, `delete`, `stat`, `list`, `createReadStream`, `createWriteStream` — backed by `AbstractStorage` from `@suaveplan/storage`. Chunking, P2P mesh sync, transports, and `transfer()`-style cross-drive copy live in **separate packages** that consume drive via its plugin API. There is no `StorageAdapter` shim — drive sits directly on `AbstractStorage`. Constructor accepts a storage instance directly; `Drive.initialize()` calls `storage.connect()` for storages drive owns; `Drive.dispose()` closes only those it opened.
## Why this is file-only
The previous draft of this proposal carried chunks, `transfer()` / manager, and capability-aware streaming all in one package. Discussion this session split those into:
| Concern | Package | Layer |
| --- | --- | --- |
| File VFS (paths, bytes, metadata, plugin slot) | `@suaveplan/drive` | this change |
| Random-access offset writes on storage | `@suaveplan/storage` (`setPartial`) | `storage-set-partial` |
| Chunk format, manifest, packer, sig-verify gate | `@suaveplan/drive-plugin-chunks` | `drive-plugin-chunks-port` |
| Peer index, availability gossip, parallel pull | `@suaveplan/drive-mesh` | `drive-mesh-port` |
| Wire — HTTPS / WebRTC / libp2p / SMB-as-storage | `@suaveplan/drive-transport-*` | per transport |
Drive proper does **not** know about chunks, peers, signatures, or wires. It is a thin file abstraction over storage with a plugin slot.
## Current state
- Source `@suaveplan/drive` v1.1.1 (`../suaveplan/packages/universal/drive`): ~7 300 LOC, 127 files, ships only `MemoryStorage`, has its own incompatible `StorageAdapter` interface.
- Genesis `AbstractStorage` (`packages/storage/storage/src/abstract/abstract.ts:56`) already declares `getPartial`, `getItemSize`, `createReadStream`, `createWriteStream` as abstract — the `MIGRATION.md` §6.3 storage-bridge work is already done. No `StorageProviderAdapter` shim is created.
## What changes
### 1. Storage contract — `DriveStorage` is structural
`Drive` consumes `AbstractStorage` directly. `DriveStorage` is a structural view that adds an `id: string`, optional `capabilities`, and optional `connect`/`dispose`/`isConnected` lifecycle hooks. See `storage-adapter-sketch.md` in this folder for the full TypeScript sketch.
### 2. Constructor overloads
```ts
new Drive() // empty, attach storages later
new Drive(storage) // bare AbstractStorage, registered as id "default"
new Drive(storage, options) // storage + options
new Drive({ defaultStorage, plugins, ... }) // options-only form
```
`new Drive(storage)` is sugar for `new Drive({ defaultStorage: storage })` — observably equivalent.
### 3. Lifecycle ownership
`Drive.initialize()` walks `this.storages` and calls `storage.connect?.()` only when `storage.isConnected?.() !== true`. Storages drive itself connects are tracked in `connectedByMe: WeakSet<DriveStorage>`. `Drive.dispose()` closes only those — pre-connected storages passed in by the caller are left open. Both `initialize` and `dispose` are idempotent.
### 4. Plugin slot
```ts
interface DrivePlugin<THandle = void> {
readonly name: string;
install(drive: Drive): THandle | Promise<THandle>;
dispose?(): void | Promise<void>;
}
const chunks = drive.use(chunksPlugin({ packer, serverPubKey }));
// drive.* — file-level
// chunks.* — chunk-level (via the plugin's returned handle)
```
`drive.use(plugin)` returns the plugin's typed handle. Drive's own type surface stays file-level; plugins extend functionality through their handles, not through module augmentation.
Plugins also register middleware on drive's file ops (`read`, `write`, `delete`, `stat`, `list`) via `drive.useMiddleware(...)`. Middleware execute outer-first on request, outer-last on response; `plugin.dispose()` runs in reverse-registration order on `Drive.dispose()`.
### 5. Generics
```ts
class Drive<TStorageMap extends Record<string, DriveStorage> = Record<string, DriveStorage>> {
use<K extends string, S extends DriveStorage>(id: K, storage: S):
Drive<TStorageMap & Record<K, S>>;
getStorage<K extends keyof TStorageMap & string>(id: K): TStorageMap[K];
}
```
Storage IDs are typed end-to-end. Calls with an unknown id are a TypeScript error.
### 6. Module-augmentation factories
`StorageFactory.register('file', FileSystem)` (already in `@suaveplan/storage`) is the single registration point. `Drive.fromConfig({ storage: { type: 'file', config: { ... } } })` resolves through `StorageFactory`. Drive does not maintain a separate adapter registry.
### 7. Error codes
`DRIVE_CODES = defineCodes("DRIVE", [NOT_INITIALIZED, DISPOSED, STORAGE_CONNECT_FAILED, STORAGE_NOT_FOUND, IO_FAILED, INVALID_PATH, PLUGIN_FAILED] as const)`. `DriveError extends SuaveplanError<DriveErrorCode>`. Frozen registry.
### 8. Error handling boundaries
- `storage.connect()` errors → `DriveError(DRIVE.STORAGE_CONNECT_FAILED, { cause })`
- ops on disposed drive → `DriveError(DRIVE.DISPOSED)`
- ops before `initialize()` → `DriveError(DRIVE.NOT_INITIALIZED)`
- `getStorage("nope")` → `DriveError(DRIVE.STORAGE_NOT_FOUND)`
- plugin install/dispose throw → `DriveError(DRIVE.PLUGIN_FAILED, { cause })`
## Impact
- New package `packages/drive/drive/` at `0.1.0`.
- New OpenSpec capability `drive` at `openspec/specs/drive/spec.md`.
- No changes to `@suaveplan/storage` (the `setPartial` addition is `storage-set-partial`, a separate change).
## Out of scope (covered by sibling changes)
- Chunk format, manifest, packer integration → `drive-plugin-chunks-port`
- Sparse-file storage layout → requires `storage-set-partial`
- Mesh peer index / parallel pull → `drive-mesh-port`
- Wire transports → `drive-transport-https-port`, `drive-transport-webrtc-port`
- `transfer()` / `DriveManager` cross-drive copy → deferred; mesh subsumes most of it
- `@suaveplan/drive-sharing` port → separate change
- `@suaveplan/vault` orchestration layer → separate change after drive lands