Initial Commit
This commit is contained in:
55
openspec/specs/drive/spec.md
Normal file
55
openspec/specs/drive/spec.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Drive — Capability Spec
|
||||
|
||||
## DRIVE-01 — Drive consumes `AbstractStorage` directly
|
||||
|
||||
`Drive` MUST accept any concrete `AbstractStorage` from `@suaveplan/storage` as its storage substrate. There MUST NOT be a separate `StorageAdapter` shim. Drive MUST narrow capabilities through a `DriveStorage` view that extends `AbstractStorage` with `id: string` and optional lifecycle hooks.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"accepts MemoryStorage as DriveStorage"
|
||||
|
||||
## DRIVE-02 — `initialize()` is idempotent and ownership-tracking
|
||||
|
||||
`Drive.initialize()` MUST be idempotent. For each registered storage, drive MUST call `storage.connect?.()` ONLY when `storage.isConnected?.() !== true`, and MUST record those storages in an internal `connectedByMe: WeakSet<DriveStorage>`. Connection failures MUST surface as `DriveError(DRIVE.STORAGE_CONNECT_FAILED, { cause })`.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"calls connect only when not already connected" AND `src/drive/drive.spec-conformance.test.ts`::"surfaces connect failure as STORAGE_CONNECT_FAILED"
|
||||
|
||||
## DRIVE-03 — `dispose()` is idempotent and ownership-respecting
|
||||
|
||||
`Drive.dispose()` MUST be idempotent. Drive MUST only close storages in `connectedByMe`; storages pre-connected by the caller MUST be left open. After `dispose()`, the drive transitions to a terminal disposed state.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"closes only storages connected by drive" AND `src/drive/drive.spec-conformance.test.ts`::"does not close pre-connected storage"
|
||||
|
||||
## DRIVE-04 — Operations after `dispose()` throw
|
||||
|
||||
Any file operation invoked on a disposed drive (`read`, `write`, `delete`, `stat`, `list`, `createReadStream`, `createWriteStream`) MUST throw `DriveError(DRIVE.DISPOSED)`. Operations invoked before `initialize()` MUST throw `DriveError(DRIVE.NOT_INITIALIZED)`.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"read throws DISPOSED after dispose" AND `src/drive/drive.spec-conformance.test.ts`::"read throws NOT_INITIALIZED before initialize"
|
||||
|
||||
## DRIVE-05 — Error-code registry is namespaced and frozen
|
||||
|
||||
`DRIVE_CODES` MUST be declared via `defineCodes("DRIVE", [...])` from `@suaveplan/error`. `Object.isFrozen(DRIVE_CODES)` MUST be `true`. Every code value MUST start with `DRIVE.`. `DriveError` MUST extend `SuaveplanError<DriveErrorCode>`.
|
||||
|
||||
**Verified by:** `src/error-codes.spec-conformance.test.ts`::"Object.isFrozen(DRIVE_CODES) is true" AND `src/error-codes.spec-conformance.test.ts`::"every code value starts with DRIVE."
|
||||
|
||||
## DRIVE-06 — Plugin middleware order
|
||||
|
||||
`drive.use(plugin)` and `drive.useMiddleware(mw)` MUST install in registration order. Middleware chains for `read`, `write`, `delete`, `stat`, `list` MUST execute outer-first on the request path and outer-last on the response path. `plugin.dispose()` MUST be called in reverse-registration order on `Drive.dispose()`. Plugin install/dispose throws MUST surface as `DriveError(DRIVE.PLUGIN_FAILED, { cause })`.
|
||||
|
||||
**Verified by:** `src/plugins/registry.spec-conformance.test.ts`::"middleware executes outer-first request, outer-last response" AND `src/plugins/registry.spec-conformance.test.ts`::"dispose calls plugins in reverse registration order" AND `src/plugins/registry.spec-conformance.test.ts`::"plugin install throw surfaces as PLUGIN_FAILED"
|
||||
|
||||
## DRIVE-07 — Capability fallbacks for streams
|
||||
|
||||
`drive.createReadStream(path)` MUST use `storage.createReadStream` when `storage.capabilities?.nativeStreams === true`, otherwise MUST fall back to the inherited `AbstractStorage.defaultCreateReadStream`. Same rule for `createWriteStream`.
|
||||
|
||||
**Verified by:** `src/file-operations/streams.spec-conformance.test.ts`::"createReadStream uses native stream when nativeStreams=true" AND `src/file-operations/streams.spec-conformance.test.ts`::"createReadStream falls back when nativeStreams=false"
|
||||
|
||||
## DRIVE-08 — Generic storage typing
|
||||
|
||||
`Drive<TStorageMap>` MUST narrow `TStorageMap` through `drive.use<K, S>(id, storage)` so that `drive.getStorage(id)` returns `TStorageMap[K]` exactly (no widening to the base `DriveStorage`). Calls with an unknown id MUST be a TypeScript error.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"getStorage returns narrowed type" (type-level)
|
||||
|
||||
## DRIVE-09 — Constructor overloads
|
||||
|
||||
`new Drive()`, `new Drive(storage)`, `new Drive(storage, options)`, and `new Drive({ defaultStorage, plugins, ... })` MUST all be valid. The bare-storage form MUST be observably equivalent to passing `{ defaultStorage: storage }` and MUST register the storage under id `"default"`.
|
||||
|
||||
**Verified by:** `src/drive/drive.spec-conformance.test.ts`::"new Drive() is valid" AND `src/drive/drive.spec-conformance.test.ts`::"new Drive(storage) registers as default"
|
||||
Reference in New Issue
Block a user