Files
suaveplan/openspec/specs/drive/spec.md
2026-05-27 23:29:53 +01:00

4.7 KiB

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"