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,255 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: drive-uri (add-drive-uri)
**Package**: `@suaveplan/drive-uri`
**Status**: PROPOSED
> Every requirement carries a `**Verified by:**` line at authoring time.
> **Note — buffered-read signal threading is revisited in `add-signal-to-storage-like`.** `StorageUriResolver.read()` does not currently thread `AbortSignal` through `adapter.get()`, only through `adapter.createReadStream()` via `open()`. Requirement R7 below therefore verifies signal propagation through the streaming path. A future cross-cutting change against `@suaveplan/types`' `StorageLike` interface will extend `get/set/exists/...` to accept an optional `AbortSignal`; once it lands, R7 can be extended to cover the buffered path as well.
## ADDED Requirements
### Requirement: `drive://` plugin parses authority as driveId and delegates to `Drive.read`
The `driveUriPlugin(options)` factory SHALL return a `UriPlugin` with `scheme === "drive"`. Its `parse` SHALL extract the URI authority as `driveId` and the remainder as a slash-prefix-trimmed `key`. The returned plugin SHALL forward `key` to the consumer-supplied `Drive` via `Drive.read(key, { signal })` (or `Drive.createReadStream` on `open()`). The plugin SHALL throw `DRIVE_URI.DRIVE_ID_MISMATCH` when the URI's driveId does not equal the `driveId` declared in `options`.
#### Scenario: URI dispatches to Drive.read
- **WHEN** `resolver.read("drive://primary/folder/file.bin")` is called and the resolver holds `driveUriPlugin({ drive, driveId: "primary" })`
- **THEN** `drive.read("folder/file.bin", { signal })` is invoked exactly once and its return value is the resolver's result
#### Scenario: driveId mismatch surfaces DRIVE_URI.DRIVE_ID_MISMATCH
- **WHEN** `resolver.read("drive://other/x")` is called and the plugin is configured with `driveId: "primary"`
- **THEN** the call rejects with an error whose `cause` is a `DriveUriError` having `code === "DRIVE_URI.DRIVE_ID_MISMATCH"`, `uri === "drive://other/x"`, `expected === "primary"`, `actual === "other"` (the `StorageUriResolver` wraps the plugin's `parse` throw as `STORAGE_URI.ADAPTER_ERROR` per its own contract, preserving the original on `cause`). `plugin.parse(uri)` called outside the resolver throws the `DriveUriError` directly.
#### Scenario: build round-trips driveId and key
- **WHEN** `plugin.build("folder/file.bin", { driveId: "primary" })` is called
- **THEN** the returned string equals `"drive://primary/folder/file.bin"`
**Verified by:** `src/plugins/drive/drive.test.ts::"dispatches a drive:// URI to Drive.read"` AND `src/plugins/drive/drive.test.ts::"throws DRIVE_ID_MISMATCH when URI driveId differs from configured driveId"` AND `src/plugins/drive/drive.test.ts::"build round-trips driveId and key into a drive:// URI"`
---
### Requirement: `drive+mesh://` plugin captures swarmId and delegates to Drive without dispatch-on-swarm
The `driveMeshUriPlugin(options)` factory SHALL return a `UriPlugin` with `scheme === "drive+mesh"`. Its `parse` SHALL extract the URI authority as `meta.swarmId` and the remainder as `key`. The plugin SHALL forward `key` to the consumer-supplied `Drive` exactly like the `drive` plugin; it SHALL NOT use `swarmId` to choose a `Drive` or transport — peer routing is the `Drive`'s and its transports' responsibility. The swarmId SHALL be surfaced as the `drive.uri.swarm_id` span attribute on every emitted span.
#### Scenario: URI dispatches to the configured Drive regardless of swarmId
- **WHEN** `resolver.read("drive+mesh://swarmA/raw/clip.mov")` is called and the resolver holds `driveMeshUriPlugin({ drive: meshDrive })`
- **THEN** `meshDrive.read("raw/clip.mov", { signal })` is invoked exactly once
#### Scenario: swarmId appears on the emitted span
- **WHEN** `resolver.read("drive+mesh://swarmA/raw/clip.mov")` is called
- **THEN** the emitted span has attribute `drive.uri.swarm_id === "swarmA"`
#### Scenario: build round-trips swarmId and key
- **WHEN** `plugin.build("raw/clip.mov", { swarmId: "swarmA" })` is called
- **THEN** the returned string equals `"drive+mesh://swarmA/raw/clip.mov"`
**Verified by:** `src/plugins/drive-mesh/drive-mesh.test.ts::"dispatches a drive+mesh:// URI to the configured Drive"` AND `src/plugins/drive-mesh/drive-mesh.test.ts::"surfaces swarmId on the emitted span"` AND `src/plugins/drive-mesh/drive-mesh.test.ts::"build round-trips swarmId and key into a drive+mesh:// URI"`
---
### Requirement: `drive+template://` plugin fetches the template, hydrates the Drive, then reads
The `driveTemplateUriPlugin(options)` factory SHALL return a `UriPlugin` with `scheme === "drive+template"`. On the first `get` for a given `templateId`, the plugin SHALL invoke `options.templateProvider.fetchTemplate(templateId, signal)`, then `options.drive.hydrateFrom(template, { signal })`, before delegating to `options.drive.read(key, { signal })`. Concurrent reads against the same `templateId` SHALL await a single in-flight hydration. Subsequent reads after a successful hydration SHALL skip both calls.
#### Scenario: first read hydrates exactly once
- **WHEN** `resolver.read("drive+template://templA/folder/file.bin")` is called for the first time
- **THEN** `templateProvider.fetchTemplate("templA", signal)` is invoked exactly once AND `drive.hydrateFrom(template, { signal })` is invoked exactly once AND `drive.read("folder/file.bin", { signal })` is invoked exactly once
#### Scenario: concurrent reads against the same templateId share one hydration
- **WHEN** two `resolver.read("drive+template://templA/x")` calls are issued before the first hydration resolves
- **THEN** `templateProvider.fetchTemplate` is invoked exactly once AND `drive.hydrateFrom` is invoked exactly once AND both `drive.read` calls run after hydration resolves
#### Scenario: second read after successful hydration skips fetch and hydrate
- **WHEN** a second `resolver.read("drive+template://templA/y")` is issued after the first read returned successfully
- **THEN** `templateProvider.fetchTemplate` is NOT invoked again AND `drive.hydrateFrom` is NOT invoked again AND `drive.read("y", { signal })` is invoked
**Verified by:** `src/plugins/drive-template/drive-template.test.ts::"hydrates exactly once on the first read"` AND `src/plugins/drive-template/drive-template.test.ts::"shares a single hydration across concurrent reads against the same templateId"` AND `src/plugins/drive-template/drive-template.test.ts::"skips fetch and hydrate on subsequent reads against the same templateId"`
---
### Requirement: `templateProvider` is required for `drive+template://`
The `driveTemplateUriPlugin(options)` factory SHALL throw `DRIVE_URI.CAPABILITY_MISSING` synchronously at construction time when `options.templateProvider` is missing or is not a function-typed `fetchTemplate` carrier. The factory MUST NOT defer this check to first use — the plugin would otherwise silently accept registration and fail at the first `read`.
#### Scenario: missing templateProvider throws at construction
- **WHEN** `driveTemplateUriPlugin({ drive })` is called without a `templateProvider`
- **THEN** the call throws `DriveUriError` with `code === "DRIVE_URI.CAPABILITY_MISSING"` and a `reason` field naming `templateProvider`
#### Scenario: non-function templateProvider.fetchTemplate is rejected
- **WHEN** `driveTemplateUriPlugin({ drive, templateProvider: { fetchTemplate: "not a function" as unknown as TemplateProvider["fetchTemplate"] } })` is called
- **THEN** the call throws `DriveUriError` with `code === "DRIVE_URI.CAPABILITY_MISSING"`
**Verified by:** `src/plugins/drive-template/drive-template.test.ts::"throws CAPABILITY_MISSING when templateProvider is absent"` AND `src/plugins/drive-template/drive-template.test.ts::"throws CAPABILITY_MISSING when templateProvider.fetchTemplate is not a function"`
---
### Requirement: Template fetch and hydration failures map to distinct DRIVE_URI codes
When `options.templateProvider.fetchTemplate` rejects, the plugin SHALL re-wrap the rejection as `DRIVE_URI.TEMPLATE_NOT_AVAILABLE` with the original error preserved on `cause`. When `Drive.hydrateFrom` rejects (any `DTP_*` code), the plugin SHALL re-wrap the rejection as `DRIVE_URI.TEMPLATE_INVALID` with the original on `cause`. The two failures MUST NOT be conflated — operator response differs (network/availability vs. integrity).
> Note: when `resolver.read(...)` raises an async failure produced by the drive-template adapter (a `DriveUriError`), the `StorageUriResolver` wraps it as `STORAGE_URI.ADAPTER_ERROR` per its own spec, preserving the original `DriveUriError` on `cause`. The scenarios below therefore assert on `(err as { cause }).cause` — that is the original drive-uri throwable.
#### Scenario: fetchTemplate rejection becomes TEMPLATE_NOT_AVAILABLE
- **WHEN** `resolver.read("drive+template://templA/x")` is called and `templateProvider.fetchTemplate` rejects with `new Error("404")`
- **THEN** the outer call rejects with an error whose `cause` is a `DriveUriError` having `code === "DRIVE_URI.TEMPLATE_NOT_AVAILABLE"` AND `cause.cause.message === "404"`
#### Scenario: hydrateFrom rejection becomes TEMPLATE_INVALID
- **WHEN** `resolver.read("drive+template://templA/x")` is called and `drive.hydrateFrom` rejects with a `SuaveplanError` carrying `code === "DTP.TEMPLATE_EXPIRED"`
- **THEN** the outer call rejects with an error whose `cause` is a `DriveUriError` having `code === "DRIVE_URI.TEMPLATE_INVALID"` AND `cause.cause.code === "DTP.TEMPLATE_EXPIRED"`
**Verified by:** `src/plugins/drive-template/drive-template.test.ts::"wraps fetchTemplate rejection as TEMPLATE_NOT_AVAILABLE"` AND `src/plugins/drive-template/drive-template.test.ts::"wraps hydrateFrom rejection as TEMPLATE_INVALID"`
---
### Requirement: `DriveStorageLikeAdapter` translates Drive missing-file errors into `undefined`
The internal `DriveStorageLikeAdapter` class SHALL implement `StorageLike` and SHALL translate `Drive.read` rejections whose `cause` chain indicates a not-found condition into `undefined` (matching the `StorageLike.get` contract). Every other failure SHALL be re-thrown unchanged so the upstream `StorageUriResolver` can wrap it as `STORAGE_URI.ADAPTER_ERROR`.
#### Scenario: not-found Drive error returns undefined
- **WHEN** `adapter.get("missing.bin")` is called and `Drive.read` rejects with a `DRIVE.IO_FAILED` whose cause is a `NOT_FOUND`-flavoured marker
- **THEN** the call resolves to `undefined`
#### Scenario: arbitrary Drive failure is re-thrown
- **WHEN** `adapter.get("x")` is called and `Drive.read` rejects with `new SuaveplanError("DRIVE.PLUGIN_FAILED", { message: "plugin crashed" })`
- **THEN** the call rejects with the same error reference; the adapter does NOT translate it to `undefined`
#### Scenario: clear() is unsupported
- **WHEN** `adapter.clear()` is called
- **THEN** the call rejects with a `SuaveplanError` whose message names `Drive.delete(path)` as the supported alternative
**Verified by:** `src/adapter/drive-storage-like.test.ts::"returns undefined for Drive not-found errors"` AND `src/adapter/drive-storage-like.test.ts::"re-throws non-not-found Drive errors unchanged"` AND `src/adapter/drive-storage-like.test.ts::"clear is unsupported and names the alternative"`
---
### Requirement: AbortSignal propagation across every plugin op
The adapter shim `createReadStream(key, signal)` SHALL forward the `AbortSignal` argument unchanged to `Drive.createReadStream(key, signal)` so that `StorageUriResolver.open()` threads the caller's signal into the drive's streaming path. A signal that is already aborted at call time SHALL cause the adapter shim to reject with `DRIVE_URI.ABORTED` without invoking the `Drive`. The `drive+template` plugin SHALL forward the signal to `templateProvider.fetchTemplate` and `Drive.hydrateFrom` on the first hydration. `StorageUriResolver`'s own pre-flight abort check (which raises `STORAGE_URI.ABORTED` before any plugin is consulted) is preserved as-is and is not modified by drive-uri.
#### Scenario: pre-aborted signal short-circuits the adapter stream
- **WHEN** `adapter.createReadStream("x", AbortSignal.abort())` is called on the adapter returned by `driveUriPlugin`
- **THEN** the call throws `DriveUriError` with `code === "DRIVE_URI.ABORTED"` AND `Drive.createReadStream` is NOT invoked
#### Scenario: live signal reaches Drive.createReadStream unchanged
- **WHEN** `adapter.createReadStream("x", liveSignal)` is called
- **THEN** the `Drive.createReadStream` invocation receives the same `liveSignal` reference
**Verified by:** `src/plugins/drive/drive.test.ts::"short-circuits with DRIVE_URI.ABORTED on a pre-aborted signal"` AND `src/plugins/drive/drive.test.ts::"forwards a live AbortSignal unchanged to Drive.createReadStream"`
---
### Requirement: Streaming reads forward Drive.createReadStream when present
The `DriveStorageLikeAdapter` SHALL expose a `createReadStream(key, signal)` method that forwards directly to `Drive.createReadStream(key, signal)`. When `resolver.open("drive://…")` is invoked, the resolver SHALL use this method (no buffered fallback) and the `storage_uri.stream_fallback_total` counter SHALL NOT be incremented.
#### Scenario: open returns Drive.createReadStream output
- **WHEN** `resolver.open("drive://primary/clip.mov")` is called
- **THEN** the returned `ReadableStream` is the same reference returned by `Drive.createReadStream("clip.mov", signal)` AND `storage_uri.stream_fallback_total` does NOT increment
#### Scenario: mid-stream abort cancels the Drive stream
- **WHEN** `resolver.open("drive://primary/clip.mov", controller.signal)` is called and `controller.abort()` is invoked after the first chunk
- **THEN** the underlying `Drive.createReadStream`'s signal observes the abort
**Verified by:** `src/plugins/drive/drive.streams.test.ts::"open forwards Drive.createReadStream without buffered fallback"` AND `src/plugins/drive/drive.streams.test.ts::"mid-stream abort propagates to Drive.createReadStream"`
---
### Requirement: Writes via drive:// schemes are rejected
The three plugins SHALL reject `set` (write) calls with `DRIVE_URI.WRITE_UNSUPPORTED`. The rejection SHALL carry the original URI and scheme on the error so operators can see misuse rate via `drive_uri.writes_total{scheme, outcome: "error"}`.
#### Scenario: write against drive:// rejects with DRIVE_URI.WRITE_UNSUPPORTED on the cause chain
- **WHEN** `resolver.write("drive://primary/x", new Uint8Array([1, 2, 3]))` is called
- **THEN** the call rejects with an error whose outer `code === "STORAGE_URI.WRITE_UNSUPPORTED"` (the resolver's normalised write-failure code) AND whose `cause.code === "DRIVE_URI.WRITE_UNSUPPORTED"` (the original drive-uri rejection) AND whose `cause` carries `uri === "drive://primary/x"`, `scheme === "drive"`
#### Scenario: writes_total counter increments on the rejection
- **WHEN** `adapter.set("x", bytes)` is called on the adapter returned by `driveMeshUriPlugin`
- **THEN** the call rejects with `code === "DRIVE_URI.WRITE_UNSUPPORTED"` AND `drive_uri.writes_total{scheme: "drive+mesh", outcome: "error"}` is incremented by 1
**Verified by:** `src/plugins/drive/drive.test.ts::"rejects writes with DRIVE_URI.WRITE_UNSUPPORTED"` AND `src/otel/otel.test.ts::"increments writes_total on rejected writes"`
---
### Requirement: OpenTelemetry instrumentation
Every plugin operation SHALL emit a span on the `@suaveplan/drive-uri` tracer and SHALL record `drive_uri.op_duration_ms{op, scheme, outcome}` on its meter. Errors SHALL increment `drive_uri.errors_total{scheme, code}`. The `drive.uri` span attribute SHALL exclude any URI query string. The `drive+template` plugin SHALL increment `drive_uri.template_hydrations_total{outcome}` exactly once per first-use template; cache-hit reads SHALL NOT increment it.
#### Scenario: successful read records duration and reads counter
- **WHEN** `resolver.read("drive://primary/x")` succeeds
- **THEN** `drive_uri.op_duration_ms{op:"read", scheme:"drive", outcome:"success"}` records one observation AND `drive_uri.reads_total{scheme:"drive", outcome:"success"}` increments by 1
#### Scenario: error increments errors_total with the resolved code
- **WHEN** `resolver.read("drive+template://templA/x")` rejects with `TEMPLATE_NOT_AVAILABLE`
- **THEN** `drive_uri.errors_total{scheme:"drive+template", code:"DRIVE_URI.TEMPLATE_NOT_AVAILABLE"}` increments by 1
#### Scenario: query strings are stripped from span attributes
- **WHEN** `resolver.read("drive+mesh://swarmA/path?peers=abcdef")` is called
- **THEN** the emitted span's `drive.uri` attribute equals `"drive+mesh://swarmA/path"` (no `?peers=…`)
#### Scenario: template_hydrations_total increments once per first-use template
- **WHEN** `resolver.read("drive+template://templA/x")` is called then `resolver.read("drive+template://templA/y")` is called
- **THEN** `drive_uri.template_hydrations_total{outcome:"success"}` is exactly 1
**Verified by:** `src/otel/otel.test.ts::"records duration histogram and reads counter on success"` AND `src/otel/otel.test.ts::"increments errors_total with the resolved DRIVE_URI code"` AND `src/otel/otel.test.ts::"strips query strings from the drive.uri span attribute"` AND `src/otel/otel.test.ts::"increments template_hydrations_total exactly once per first-use template"`
---
### Requirement: Conformance harness reuse via `runUriPluginConformance`
The package SHALL re-export `runUriPluginConformance` from `@suaveplan/storage-uri` and SHALL include fixture-driven conformance tests for each of the three plugins. The fixtures SHALL cover three valid URI shapes, four invalid URI shapes, and a `build` round-trip for each valid fixture. The package SHALL NOT author its own parallel harness.
#### Scenario: drive plugin passes the conformance harness
- **WHEN** `runUriPluginConformance(driveUriPlugin({ drive, driveId: "primary" }), driveFixtures)` is invoked
- **THEN** every assertion passes without throwing
#### Scenario: drive+template plugin passes the conformance harness
- **WHEN** `runUriPluginConformance(driveTemplateUriPlugin({ drive, templateProvider }), templateFixtures)` is invoked
- **THEN** every assertion passes without throwing
**Verified by:** `src/conformance/conformance.test.ts::"drive plugin satisfies runUriPluginConformance"` AND `src/conformance/conformance.test.ts::"drive+template plugin satisfies runUriPluginConformance"`
---
### Requirement: Cross-scheme composition with `storage-uri`
A single `StorageUriResolver` MUST accept a mix of `storage-uri`'s plugins and `drive-uri`'s plugins and dispatch each URI to the correct plugin without code-path divergence. Registration of one drive plugin SHALL NOT prevent registration of any number of `storage-uri` plugins, and vice versa.
#### Scenario: file:// and drive:// resolve through the same resolver
- **WHEN** `createStorageUriResolver([fileUriPlugin({ rootDir }), driveUriPlugin({ drive, driveId: "primary" })])` is constructed and both `resolver.read("file:///tmp/a")` and `resolver.read("drive://primary/b")` are invoked
- **THEN** the file:// call routes to the file plugin's adapter AND the drive:// call routes to the drive plugin's adapter; neither call observes the other's state
**Verified by:** `tests/integration/compose-with-storage-uri.test.ts::"file:// and drive:// resolve through one StorageUriResolver"`
<!-- markdownlint-enable MD024 -->