16 KiB
OpenSpec Change: drive-template-v1
- Change ID: drive-template-v1
- Package:
@suaveplan/drive-template(new — types + codecs + validators), with surface additions to@suaveplan/drive(hydrateFrom,getKeyStatus,getPresence) - Capability:
drive-template(new) - Status: PROPOSED
- Date: 2026-05-02
Summary
Define DriveTemplate — a server-signed configuration that pre-establishes a drive's folder structure, file metadata, encryption keys, and policies before any bytes are present locally. The drive lazily hydrates from peers (via drive-mesh-protocol) and falls back to a CDN with a reversed-ID URL convention. Adds per-file policy flags (autoDownload, meshAllowed, cdnAllowed, retention), inline and retroactive key distribution paths, lock-icon UX (getKeyStatus), progress UX (getPresence), and replay-protected template signature verification.
This is the "ship a 2 GB game asset bundle to N players, see lock icons that flip when DLC unlocks, watch progress bars fill as peers seed" feature — the production scenario the user described.
Why
The current drive design assumes drives are populated by writes. It has no concept of "the structure is known up front; the bytes arrive later." Concrete gaps that this proposal closes:
- No dehydrated drive concept. A drive issuer cannot publish a description of what will exist in a drive without already having the bytes locally and writing them through. (See
docs/system-review.md§3 — Blocker.) - No per-file policy flags. Users today cannot say "this file is mesh-only" or "this file should auto-download but only from the CDN, not from peers." Policy is global per drive. (See §5 — Blocker.)
- No CDN URL convention.
drive-transport-httpsis described as "server fallback" but the URL layout is unspecified. Without a convention, every deployment hand-rolls one. (See §4 — Major.) - No retroactive key distribution. The user's brief calls for "decryption keys could be included as part of the signed config, or would have to be retro-actively added to the drive for files to be unlocked." Today there's only the share-grant flow inside
drive-sharing. (See §8 — Major.) - No lock-icon UX contract. No drive method tells the UI whether a file is encrypted-locked, encrypted-unlocked, or unencrypted. (See §6 — Major.)
- No progress UX contract.
Drive.list()omits chunk presence/total, so a UI cannot render███░░░░░░░ 33%next to a file. (See §7 — Major.) - No retention strategy. A drive with
autoDownloadenabled fills disk forever. No GC policy is specified. (See §9 — Major.) - No CDN write-side authentication. Without server-issued upload tokens, anyone with the URL could poison the CDN. (See §12 — Major.)
- No template replay protection. A captured template within its expiry could be replayed against a fresh client. (See §13 — Major.)
The architecture this proposal codifies is documented in docs/drive/dehydrated-drives.md. This change folder is the formal OpenSpec mandate.
What changes
ADDED — drive-template capability spec
26 ADDED requirements under specs/drive-template/spec.md:
DTP-01..04—DriveTemplateshape, schema versioning, canonical serialization, signature.DTP-05..09— 8-step validation order, replay protection (nonce + issuance window), folder depth cap, file consistency.DTP-10..13—PerFilePolicy(autoDownload/meshAllowed/cdnAllowed/retention) and global defaults.DTP-14..16— Retention modes ('permanent' / 'lru' /{ maxAgeMs }) and chunk-granularity eviction.DTP-17..19— CDN reversed-ID URL convention, parameterized levels and padding, write-back viaCdnUploadToken.DTP-20..23— Inline and retroactive key distribution,KeyGrantEnvelope, lock-icon state transitions.DTP-24— Inline files (kind: 'inline') carry their own Ed25519 sig.DTP-25..26—Drive.hydrateFrom(),Drive.getKeyStatus(),Drive.getPresence()API contracts; hydration error codes.
ADDED — new package @suaveplan/drive-template
Types-and-codecs package mirroring @suaveplan/drive-mesh-protocol's structure: DriveTemplate schema, canonical serialization, signature verification, KeyGrantEnvelope, CDN URL builder. Zero runtime side effects.
MODIFIED — @suaveplan/drive capability spec
Adds three drive surface methods documented in this proposal:
drive.hydrateFrom(template, options)— instantiates DirIndex + FileLocationIndex from a verified template; schedules autoDownload fetches.drive.getKeyStatus(path): 'unencrypted' | 'encrypted-locked' | 'encrypted-unlocked'— for lock-icon UX.drive.getPresence(path): { chunksPresent, chunksTotal, sourceHints, stuckChunks }— for progress UX.
DirEntry returned by Drive.list() gains optional keyStatus and presence fields.
MODIFIED — @suaveplan/drive-plugin-chunks capability spec
FileLocation.storageHints kind: 'inline' variant gains a required sig: Uint8Array field (Ed25519 over fileId ‖ bytes). Backward-incompatible at the wire format; bumps inline-format minor version.
REMOVED — none.
Strictly additive at the surface level; the inline-sig change is the only on-wire format bump and only affects inline files (a small minority).
Source mapping
Genesis-original. The dehydrated-drive concept is novel to genesis; antecedents are NPM lockfiles (deterministic dependency descriptions), Apple App Thinning (server-issued resource manifests), and IPFS pinning services (declarative content lists).
| Requirement | Source / inspiration | Notes |
|---|---|---|
DriveTemplate shape |
Genesis-original | Composes @suaveplan/bitpacker-schema for canonical serialization. |
| Reversed-ID URL convention | Custom | Distributes leaves regardless of how IDs were generated. Levels parameterized. |
PerFilePolicy flags |
User's design brief | Three booleans + retention enum. |
| Inline vs retroactive key distribution | User's design brief; partial parallel to JWE direct vs key-agreement | crypto_box_seal for envelope. |
KeyGrantEnvelope |
Genesis-original | Standalone bus event; not coupled to share-grant flow. |
CdnUploadToken |
Standard pattern (signed S3-style URL with payload binding) | Server-attested upload intent. |
| Lock-icon UX states | User's design brief | Three-state union maps directly to UI. |
| Replay protection (nonce + 24h window) | Standard cryptographic pattern | LRU-cached for last 1024 nonces. |
Requirements with no source line
| Requirement | Justification |
|---|---|
| 32-level folder depth cap | Real filesystems rarely exceed 16; cap prevents DoS via maliciously deep templates. |
| 24-hour issuance window | Long enough for templates to propagate via API/launcher, short enough that a captured template doesn't outlive its intended distribution channel. |
| 1024-entry nonce LRU | Bounded memory (~16 KiB) supporting reasonable issuance rates per drive. |
60-second CdnUploadToken expiry |
Stolen tokens expire before useful re-use; clock skew tolerable. |
Parity Checklist
Genesis-original. No upstream
suaveplan/mainanalogue exists.
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| Server-signed drive config | n/a | ✅ | Genesis-original. |
| Per-file policy flags | n/a | ✅ | Genesis-original. |
| CDN reversed-ID layout | n/a | ✅ | Genesis-original. |
| Retroactive key grant | partial (suaveplan had share-grant) | ⬆️ | Enhanced: per-file granularity, separate from share partitions. |
Drive.hydrateFrom() |
n/a | ✅ | Genesis-original. |
Drive.getKeyStatus() / getPresence() |
n/a | ✅ | Genesis-original; surface additions to drive core. |
| Replay protection | n/a | ✅ | Genesis-original. |
| Chunk-granularity LRU | n/a | ✅ | Genesis-original. |
Out of scope
- Server-side template authoring tools. This proposal defines what a template is and how it's verified. The CLI / web tool that produces templates is a separate concern and is out of scope.
- Subscription / payment flows. Retroactive key grants are the mechanism; the policy that decides when to grant (paid the user, joined the group, won the tournament) is application logic above the drive.
- Server-side CDN implementation. This proposal defines the URL convention readers and writers MUST follow. Building a CDN that hosts the URLs is application infrastructure.
- Cross-template diffs. Issuing a new template that adds files to an existing drive is straightforward (just a fresh template with the same
driveId); explicit "diff" or "patch" templates are out of scope. - Compression of file plaintext. The chunks plugin handles per-chunk AEAD; whole-file compression (zstd, brotli) before chunking is a future change.
Verification approach
@suaveplan/drive-template package tests
tests/template-roundtrip.test.ts—canonicalSerialize/ sign / verify cycle.tests/template-replay.test.ts— second hydrate with same nonce rejected.tests/template-expired.test.ts— expired template rejected withDRIVE.TEMPLATE_EXPIRED.tests/template-stale.test.ts—issuedAtmore than 24h ago rejected withDRIVE.TEMPLATE_STALE.tests/template-tampered.test.ts— fast-check property: any single bit flip breaks the sig.tests/template-untrusted-issuer.test.ts— issuer not intrustedKeyAuthoritiesrejected.tests/template-too-deep.test.ts— folder recursion > 32 rejected.tests/template-malformed.test.ts—chunkSize <= 164,totalChunks * chunkSize < size, etc. rejected.tests/cdn-reverse-id-url.test.ts— URL builder produces correct path for variouslevels/padding.tests/cdn-bad-sig.test.ts— corrupted CDN response is discarded; mesh fallback succeeds.tests/cdn-upload-token.test.ts— CDN write requires valid signed token; expired/tampered tokens rejected.
@suaveplan/drive integration tests
tests/hydrate-from.test.ts— successful hydration creates DirIndex + FileLocationIndex matching the template.tests/policy-meshallowed-false.test.ts— bus interceptor verifies nocd:requestemitted formeshAllowed: falsefiles.tests/policy-cdnallowed-false.test.ts—drive-transport-httpsnot invoked forcdnAllowed: falsefiles.tests/policy-autodownload.test.ts— fetches scheduled on hydrate forautoDownload: truefiles only.tests/retention-lru.test.ts— eviction respectsmaxLocalCacheBytes;'permanent'files immune.tests/retention-max-age.test.ts—{ maxAgeMs }chunks evicted at GC pass.tests/inline-key-unlock.test.ts— hydrate flips lock icon to 🔓 for files with inline keys.tests/retroactive-key-grant.test.ts— incomingkey:grantflips lock icon and unlocks reads.tests/key-grant-untrusted-issuer.test.ts— grant from non-keyAuthorityrejected.tests/key-grant-replay.test.ts— second grant with same payload rejected (nonce-equivalent).tests/get-key-status.test.ts— returns'unencrypted' | 'encrypted-locked' | 'encrypted-unlocked'correctly per file state.tests/get-presence.test.ts— returns{ chunksPresent, chunksTotal, sourceHints, stuckChunks }matching internal state.tests/list-with-presence.test.ts—Drive.list()DirEntries carrykeyStatusandpresencefields.tests/inline-file-sig.test.ts— inline files (kind:'inline') carry valid Ed25519 sig; bad sig rejected.
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/drive-template and bunx turbo ls --filter=...@suaveplan/drive once the packages exist.
@suaveplan/drive— [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: gainshydrateFrom,getKeyStatus,getPresencesurface methods)@suaveplan/drive-plugin-chunks— [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason:kind: 'inline'storage hint gains requiredsigfield — minor format bump)@suaveplan/drive-mesh— [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: hydration triggers chunk fetches via mesh-protocol)@suaveplan/drive-transport-https— [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: implements the CDN reversed-ID URL builder and upload token flow)@suaveplan/drive-sharing— [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: retroactive key grants are independent of partition KEKs but coexist;KeyRinginterface stays the same)@suaveplan/crypto-vault— [ ] tests run / [x] docs reviewed / [ ] no action needed (reason: provides X25519 sealed-box for wrappedKey unwrap; no API changes)@suaveplan/network(viaNetworkBus) — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: bus is generic; newkey:granttopic is just anotherMessageEnvelope.type)@suaveplan/api-plugin-file-upload(pending — main) — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: server-side counterpart forCdnUploadTokenminting; will be addressed when ported)
Cumulative blast radius: 8 packages, 3 of which gain new surface (drive, drive-plugin-chunks, drive-transport-https). drive-plugin-chunks is the only package with a wire-format change (inline-file sig field).
Migration
From the previous design
This is largely additive: drives that don't call hydrateFrom are unaffected. Three migrations are required:
-
Inline storage hint format.
FileLocation.storageHintsentries withkind: 'inline'MUST add asig: Uint8Arrayfield. Drives reading old-format inline entries SHALL accept them with a warning and rewrite them on next write. Search command:grep -rln "kind: ['\"]inline['\"]" packages/ --include="*.ts" -
Drive.list()consumers. UIs that consumeDirEntrySHOULD start readingentry.keyStatusandentry.presenceto render lock icons and progress bars. The fields are optional; consumers that ignore them work as before. -
Server template signing keys. Servers issuing templates MUST publish their issuer pubkey (out of band) so clients can populate
globalPolicy.trustedKeyAuthorities. Without this,hydrateFromrejects every template withDRIVE.TEMPLATE_UNTRUSTED_ISSUER.
Behavior changes
The five behavior changes consumers need to know about (per AGENTS §13a):
- New error codes:
DRIVE.TEMPLATE_*family (8 codes),DRIVE.UNAVAILABLE(when read fails because no source has the chunks),DMP.*family from the mesh-protocol package. Drive.readmay now block longer when chunks need fetching —AbortSignalhonored throughout but expect higher p99 latency on dehydrated drives.- Disk usage may grow asynchronously post-hydrate. UIs displaying free space should subscribe to
drive:cache:evictedevents. - Lock icon state transitions emit
drive:key:granted/drive:key:revokedevents — UIs should rebind their indicators on these. - Bus topic added:
key:grant(perDTP-21..23). Plugins that snoop the bus for security audit purposes should add this to their watchlist.
References
- Architectural reference:
docs/drive/dehydrated-drives.md - System review findings:
docs/system-review.md§3, §4, §5, §6, §7, §8, §9, §12, §13, §14, §15, §16 - Companion change:
drive-mesh-protocol-v1— the chunk-request protocol used during hydration - Composition map:
docs/drive-storage-network.md