Files
2026-05-27 23:29:53 +01:00

6.3 KiB

OpenSpec Change: drive-template-drive-surface

  • Change ID: drive-template-drive-surface
  • Capability: drive (extending the existing surface) + cross-cutting consumption of drive-template
  • Status: SHIPPED
  • Date: 2026-05-04
  • Follow-up to: drive-template-v1 (archived 2026-05-04)

Summary

Lands the @suaveplan/drive surface additions deferred from drive-template-v1: Drive.hydrateFrom(template, options), Drive.getKeyStatus(path), Drive.getPresence(path), the bus:key:grant listener that flips encrypted-locked entries to encrypted-unlocked when a valid retroactive grant arrives, and the FileLocationIndex projection that backs all three. Adds the four hydration error codes (DRIVE.NOT_FOUND, DRIVE.UNAVAILABLE, DRIVE.ALREADY_HYDRATED, DRIVE.HYDRATE_ABORTED), extends DriveEntry with optional keyStatus / presence fields, and ships 15 integration tests across hydrate / get-key-status / get-presence / retroactive-key-grant / file-location-index test files at 100/100/100/100 coverage.

This change does NOT touch @suaveplan/drive-plugin-chunks (the inline-sig modification, DTP-25, lives in drive-plugin-chunks-inline-sig follow-up) and does NOT implement the cache-eviction loop (DTP-13..15 retention LRU / max-age GC) — both ship as separate changes since they require touching sibling packages.


Why

drive-template-v1 shipped the standalone @suaveplan/drive-template package (12 of 26 spec requirements package-local). The remaining requirements (DTP-22..24, DTP-21 listener integration) require modifying @suaveplan/drive because they extend its public surface. Splitting those into this follow-up change keeps the original drive-template ship reviewable while letting the surface additions go through their own gate cycle.

What changes

  • ADDED: Drive.hydrateFrom(template: DriveTemplate, options?: HydrateOptions): Promise<HydrateResult> — 8-step validation pipeline reused from @suaveplan/drive-template, populates FileLocationIndex, schedules byte fetches per flags.autoDownload.
  • ADDED: Drive.getKeyStatus(path: string): KeyStatus — synchronous three-state lookup; throws DRIVE.NOT_FOUND for unknown paths.
  • ADDED: Drive.getPresence(path: string): ChunkPresence — chunk-level progress lookup; throws DRIVE.NOT_FOUND for unknown paths.
  • ADDED: Drive.fileLocationIndex getter — exposes the index for instrumentation and tests.
  • ADDED: FileLocationIndex class with set / getByPath / getByFileId / hasPath / deleteByPath / clear / size / [Symbol.iterator].
  • ADDED: KeyStatus, ChunkPresence, HydrateOptions, HydrateResult, HydrateProgressEvent types.
  • ADDED: DriveOptions.ourPubkey?: PeerId so the key:grant listener can filter to grants addressed to us.
  • ADDED: bus-listener registration during hydrateFrom for kind: 'retroactive' files; flips keyStatus from 'encrypted-locked' to 'encrypted-unlocked' on validated grants.
  • ADDED: 4 new DRIVE_CODES: NOT_FOUND, UNAVAILABLE, ALREADY_HYDRATED, HYDRATE_ABORTED.
  • MODIFIED: DriveEntry — optional keyStatus?: KeyStatus and presence?: ChunkPresence fields. Strictly additive at the consumer surface.
  • MODIFIED: Drive constructor — new optional ourPubkey field threaded into the hydration context.

Mid-iteration abort handling: the hydration loop yields between iterations via await Promise.resolve() so caller-side aborts scheduled via queueMicrotask actually land and trigger rollback. This is a behavior improvement over the synchronous-loop original draft.

Out of scope (deferred to follow-ups)

  • DTP-13..15 retention LRU / { maxAgeMs } cache-eviction loop — needs deeper integration with @suaveplan/drive-plugin-chunks to know per-file chunk inventory.
  • DTP-25 FileLocation.storageHints inline-sig modification — lives on @suaveplan/drive-plugin-chunks (follow-up change drive-plugin-chunks-inline-sig).
  • DTP-13 chunk-arrival accounting (the chunks plugin updates entry.presence directly) — needs the chunks plugin to import @suaveplan/drive's FileLocationIndex type.
  • CDN bad-signature mesh fall-back (DTP-18) — needs drive-transport-https and drive-mesh integration; ships with the transport packages.

Migration impact

None. The API additions are strictly additive and the DriveEntry extensions are optional. Existing code that ignores the new fields continues to work unchanged. Consumers that opt in to Drive.hydrateFrom MUST also pass ourPubkey if they want retroactive key grants to land.

Verification approach

  • tests/hydrate-from.test.ts — successful hydration, abort cancellation, encrypted-vs-plain counting, preFetch modes ('none' / 'auto-download-files'), already-hydrated rejection, progress events, mid-iteration abort with rollback.
  • tests/get-key-status.test.ts — three-state contract + NOT_FOUND.
  • tests/get-presence.test.ts — progress shape contract + NOT_FOUND.
  • tests/retroactive-key-grant.test.ts — listener wiring, defensive branches (unencrypted file / already-unlocked file / no-ourPubkey), bad-issuer rejection.
  • tests/file-location-index.test.ts — direct unit tests for the index methods.

15 new tests across 5 files at 100/100/100/100 coverage. Includes restoration of the previously-removed rollback path with a real test exercising it via queueMicrotask-scheduled abort (per AGENTS.md §7a).

Side effect: AGENTS.md §2b and §7a additions

This change folder authored two new sections in openspec/AGENTS.md:

  • §2b "NEVER ship stubs, placeholders, or work-in-progress code" — captures the user-driven rule that genesis is an enterprise-grade collection of fully working libraries, not a workspace. Bans stubs, TODOs, tautological tests, half-baked docs, and "Phase 1 / Phase 2" splits where Phase 2 is unwritten.
  • §7a "Never delete code that serves a purpose to make tests / coverage pass" — captures the rule that defensive guards, rollback helpers, and forward-defensive catches must be tested, not deleted. Includes an explicit list of banned anti-patterns and the priority order for what to do instead (write a test → restructure to make the path reachable → annotate as unreachable).

Both rules ride along here because the work that surfaced them happened during this change's implementation; future changes will reference them directly.