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

33
docs/storage/README.md Normal file
View File

@@ -0,0 +1,33 @@
# Storage
The **storage** layer is a pluggable, content-agnostic key→bytes substrate. It is the lowest level of the file stack: it knows nothing about files, paths, chunks, encryption, or peers — it stores `Uint8Array` values addressed by string keys, and exposes a uniform capability matrix so the layer above it (`@suaveplan/drive`) can decide which optimisations are safe.
This folder is the architectural reference for the storage layer. It does not duplicate the OpenSpec capability spec — it explains it.
## Documents in this folder
| File | Purpose |
| --- | --- |
| [`architecture.md`](./architecture.md) | The `AbstractStorage` contract, the capability matrix (`partialReads`/`partialWrites`/`nativeStreams`/`atomicWrites`/`contentAddressable`), `setPartial` semantics, and `NamespacedStorage` scoping. |
| [`backends.md`](./backends.md) | Every storage backend the platform supports or will support: `MemoryStorage`, `S3`, browser (`IndexedDB`/`localStorage`/OPFS), Node fs, SQLite, FAT/exFAT/ext4 via WASM, and the planned WebRTC/HTTPS chunk transports. Each entry lists capability values, runtime, and migration status. |
| [`implementation-status.md`](./implementation-status.md) | What is actually shipped on `genesis` today vs what still lives on `main` (and what is brand-new). |
## One-paragraph summary
`AbstractStorage` is a Zod-validated, OTel-instrumented base class that defines `get`, `set`, `setPartial`, `delete`, `has`, `clear`, `list`, `batch`, `createReadStream`, `createWriteStream`, plus an explicit `capabilities` field. Every concrete backend extends `AbstractStorage` and overrides exactly the methods it can implement natively; the base class provides default fallbacks (most importantly a read-modify-write `defaultSetPartial`) so consumers can call any method on any backend without runtime probing. `NamespacedStorage` wraps another storage to give it a key prefix and inherits the inner backend's capabilities. Encryption, signing, integrity, manifests, paths, directories, and CoW versioning all live above this layer — the storage layer never sees a plaintext byte that doesn't belong to it.
## What storage is not
- **Not a filesystem.** It has no concept of directories, `..`, `cwd`, mode bits, owners, mtime, or symlinks. The drive layer (above) builds those concepts on top.
- **Not a database.** No queries, joins, indexes, or relations. SQLite-backed storage uses SQLite as a blob store, not as a query engine.
- **Not a transport.** It does not push bytes to peers. The drive-mesh and drive-transport-* layers do that.
- **Not encrypted by itself.** Storage stores the bytes you give it, byte-for-byte. If you want encryption-at-rest, write ciphertext (the `chunks` plugin does this for you).
## Reading order
If you are new to this stack, read in this order:
1. [`architecture.md`](./architecture.md) — understand the contract.
2. [`backends.md`](./backends.md) — see what concrete substrates are available.
3. [`../drive/architecture.md`](../drive/architecture.md) — see how the layer above consumes it.
4. [`../drive-storage-network.md`](../drive-storage-network.md) — see the whole composition.

View File

@@ -0,0 +1,98 @@
# Storage Architecture
Canonical capability spec: [`openspec/specs/storage/spec.md`](../../openspec/specs/storage/spec.md) (`STORAGE-01`..`STORAGE-04`). This document explains the contract; the spec defines it.
## The structural contract: `StorageLike`
The minimum any storage adapter must satisfy lives in `@suaveplan/types/storage-like` so that consumers (drive, plugins, tests) can accept any conformant implementation without taking a runtime dependency on `@suaveplan/storage`. This is the wire that breaks the circular-dep risk between drive and storage.
```ts
interface StorageLike {
get(key: string): Promise<Uint8Array | undefined>;
set(key: string, value: Uint8Array): Promise<void>;
delete(key: string): Promise<boolean>;
exists(key: string): Promise<boolean>;
list(opts?: { prefix?: string; limit?: number }): Promise<{
keys: string[]; total: number; hasMore: boolean; continuationToken?: string;
}>;
clear(): Promise<void>;
// optional batch surface
getMany?(keys: readonly string[]): Promise<ReadonlyMap<string, Uint8Array | undefined>>;
setMany?(entries: readonly (readonly [string, Uint8Array])[]): Promise<void>;
deleteMany?(keys: readonly string[]): Promise<number>;
}
```
## The concrete base class: `AbstractStorage`
`AbstractStorage` (in `@suaveplan/storage`) extends `StorageLike` with the rest of the platform surface. Every concrete backend extends `AbstractStorage`.
| Method | Purpose |
| --- | --- |
| `get(key)` / `set(key, value, options?)` | Whole-blob read/write. `options` carries TTL and metadata. |
| `setPartial(key, offset, bytes, signal?)` | **Sparse byte-range write.** See `STORAGE-01..03` below. |
| `getPartial(key, start, end)` | Byte-range read for resume-friendly downloads. |
| `delete(key)` / `has(key)` / `clear(pattern?)` | Lifecycle. |
| `list(pattern?, options?)` | Glob-prefixed enumeration. |
| `batch(operations)` | Multi-op grouping (atomic only when the backend says so). |
| `createReadStream(key)` / `createWriteStream(key)` | WHATWG streams; falls back to whole-blob when `nativeStreams=false`. |
| `namespace(prefix)` | Returns a `NamespacedStorage` view. |
| `capabilities` | Readonly record describing what the backend can do natively. |
## The capability matrix (`STORAGE-04`)
Every backend declares a `capabilities` record so the drive layer can pick the right strategy without runtime probing.
```ts
interface StorageCapabilities {
partialReads: boolean; // getPartial(key, start, end) is native
partialWrites: boolean; // setPartial(key, offset, bytes) is native (no RMW)
nativeStreams: boolean; // createReadStream/createWriteStream are native (no buffering)
atomicWrites: boolean; // set is atomic across crash
contentAddressable: boolean; // key is the content hash; same-content writes coalesce
}
```
Defaults are all `false` (`DEFAULT_STORAGE_CAPABILITIES`). Subclasses opt in only for what they truly support.
The drive-plugin-chunks layer reads these flags directly to choose its layout (see `CHUNKS-LAYOUT-AUTO-01`):
- `partialWrites === true` → use the **sparse-file** layout (one key per file, byte ranges).
- `partialWrites === false` → use the **chunk-keys** layout (one key per chunk).
## `setPartial` semantics (`STORAGE-01..03`)
`setPartial` is the load-bearing method that lets large files survive a backend that does not support random-access writes. The contract is:
1. **Always available** (`STORAGE-01`). `AbstractStorage.setPartial` is abstract; every concrete subclass must provide one. `defaultSetPartial` is a protected RMW fallback that subclasses can call when `capabilities.partialWrites === false`.
2. **Sparse extension** (`STORAGE-02`). Writing to an offset past the current end zero-fills the gap on subsequent reads. This lets the chunks plugin pre-allocate a sparse file and fill chunks in arbitrary order.
3. **In-place overwrite** (`STORAGE-03`). Writing into an existing range overwrites only the addressed bytes; everything outside `[offset, offset+bytes.length)` is preserved exactly. Zero-length writes are a no-op.
When a backend genuinely cannot support partial writes (`S3`), `setPartial` falls back to `defaultSetPartial`, which downloads the full object, splices the new bytes in, and re-uploads — correct but expensive. The drive layer reads `partialWrites` and routes around this when it can.
## `NamespacedStorage` (scoping)
`storage.namespace("drive/users/alice/")` returns a `NamespacedStorage` that prefixes every key with `"drive/users/alice/"` before delegating to the inner storage. The wrapper inherits the inner storage's `capabilities` exactly — namespace scoping is purely a key transform. This is how `drive`, `drive-plugin-chunks`, and `drive-sharing` give each tenant/file/partition its own keyspace without dragging tenancy concerns down into every backend.
## What lives above storage, not in it
| Concern | Where it lives | Why |
| --- | --- | --- |
| Paths, directories, `..` resolution | `@suaveplan/drive` | Storage keys are opaque strings. |
| File metadata (size, mtime, owner) | `@suaveplan/drive` | Stat goes through the drive plugin chain. |
| Chunk headers, signatures, AEAD tags | `@suaveplan/drive-plugin-chunks` | Storage stores ciphertext bytes, never plaintext. |
| Manifests, BLAKE3/SHA-256 integrity | `@suaveplan/drive-plugin-chunks` | Storage does not verify content. |
| Replication, redundancy, peer fetch | `@suaveplan/drive-mesh` + `@suaveplan/network-plugin-content-delivery` | Storage is single-replica by definition. |
| Encryption keys, vault, KDF | `@suaveplan/crypto-vault`, `@suaveplan/keys` | Storage never sees a key. |
This separation is what lets the storage layer accept **any** backend — local fs, S3, OPFS, IndexedDB, an embedded FAT image, a Garage cluster — and have the drive layer treat them all the same.
## OTel and errors
Every concrete subclass instruments `storage.ops` (counter), `storage.op.duration_ms` (histogram), and `storage.value.bytes` (histogram). Errors are `StorageError` (and the four typed subclasses `StorageNotFoundError`, `StorageQuotaError`, `StorageInvalidKeyError`, `StorageBatchError`) with codes in the frozen `STORAGE_CODES` registry. Backends MAY add their own namespaced registries (`S3.S3_CODES`) but MUST extend the base error class.
## Reading further
- [`backends.md`](./backends.md) — every concrete backend.
- [`implementation-status.md`](./implementation-status.md) — what's shipped, what's pending.
- [`../drive/architecture.md`](../drive/architecture.md) — the consumer.

145
docs/storage/backends.md Normal file
View File

@@ -0,0 +1,145 @@
# Storage Backends
This is the catalogue of every concrete `AbstractStorage` implementation the platform ships or plans to ship. Each entry lists runtime, capabilities, migration status, and the distinct value it adds.
A backend is "shipped" only if it lives under `packages/storage/<pkg>/` on the `genesis` branch. Anything else is referenced from `../suaveplan` (the main branch) or planned.
## Capability cheat-sheet
| Backend | Runtime | partialReads | partialWrites | nativeStreams | atomicWrites | contentAddressable | Status |
| --- | --- | :-: | :-: | :-: | :-: | :-: | --- |
| `MemoryStorage` | universal | ✓ | ✓ | ✓ (zero-copy) | ✓ | ✗ | **shipped** |
| `NamespacedStorage` | universal | mirrors inner | mirrors inner | mirrors inner | mirrors inner | mirrors inner | **shipped** |
| `S3` (`storage-s3`) | server | ✓ | ✗ (RMW) | ✓ | ✓ (single-PUT) | ✗ | **shipped** |
| `OpfsStorage` (`storage-browser-opfs`) | browser | ✓ | ✓ | ✓ | partial | ✗ | pending — `main` |
| `IndexedDbStorage` (`storage-browser`) | browser | ✗ (RMW) | ✗ (RMW) | ✗ | ✓ (per-tx) | ✗ | pending — `main` |
| `FileStorage` (`storage-file`) | Node | ✓ | ✓ (`fs.write`) | ✓ | ✓ (rename) | ✗ | pending — `main` |
| `SqliteStorage` (planned) | Bun + browser | ✓ (incrblob) | ✓ (incrblob) | partial | ✓ (per-tx) | ✗ | planned — wire to `@suaveplan/db-sqlite` |
| `FatStorage` (planned, FAT16/32) | universal (WASM) | ✓ | ✓ | ✓ (Node) / emulated | ✗ | ✗ | planned — `fatfs-wasm` or `fatfs` |
| `ExfatStorage` (deferred) | universal | — | — | — | — | — | deferred — `node-exfat` is parser-only |
| `Ext4Storage` (deferred) | server (native) | ✓ | ✓ | ✗ | ✓ (journal) | ✗ | deferred — `lwext4` needs a JS binding + GPL audit |
| `ContentAddressedStorage` (planned) | wraps any | mirrors inner | ✗ | mirrors inner | inherited | ✓ | planned — derive key from `BLAKE3(content)` |
| `GarageStorage` | server (test fixture) | ✓ | ✗ (uses S3) | ✓ | ✓ | ✗ | shipped via `testing-integration-garage-s3` |
## Backends in detail
### `MemoryStorage` — `@suaveplan/storage`
In-process map-of-`Uint8Array` with TTL, OTel metrics, and zero-copy streaming. Used as the default for tests, ephemeral caches, and the in-browser fallback when no persistent backend is available. Capabilities: `partialReads`, `partialWrites`, `nativeStreams`, `atomicWrites`. Not content-addressable.
### `NamespacedStorage` — `@suaveplan/storage`
Wraps another `AbstractStorage` and prefixes every key. Capability mirror: whatever the inner backend can do, the namespaced view can do. This is the primary tool for tenant/partition/share scoping.
### `S3` — `@suaveplan/storage-s3`
AWS-SDK-backed S3-compatible backend (works with AWS S3, Garage, MinIO, etc.). `set` is a single-PUT atomic write; `setPartial` falls back to RMW because S3 multipart upload requires the upload to be planned in advance and is not byte-addressable in the way `setPartial` demands. Adds `createPresignedUrl(key, options)` for delegated browser uploads/downloads.
### `OpfsStorage` (planned — to be ported from `main` as `storage-browser-opfs`)
Origin Private File System inside the browser, accessed through the WASM `FileSystemSyncAccessHandle` API for true byte-addressable reads/writes. This is the right backend for in-browser drives that need real partial writes without round-tripping through a service worker. Capabilities expected: `partialReads`, `partialWrites`, `nativeStreams`. Not yet on genesis.
### `IndexedDbStorage` (planned — to be ported as `storage-browser`)
IndexedDB key→`Blob` store. No native partial reads/writes; both fall back to RMW. Right for older browsers, very large numbers of small keys, and structured indexes. Atomic per-transaction.
### `FileStorage` (planned — to be ported as `storage-file`)
Node `fs.promises` backed, one file per key. `setPartial` maps to `fs.write(fd, buf, 0, len, offset)`. Atomic full writes via `rename` of a temp file. The straight-line backend for any server environment.
### `SqliteStorage` (planned)
Built on `@suaveplan/db-sqlite` (already shipped). One blob column per key, with `incremental_blob_open` for byte-addressable reads/writes. The advantage over `FileStorage` is that you get a journal, transactions, atomic multi-key updates, and a single file to back up. Useful for embedded apps and the browser via `@sqlite.org/sqlite-wasm`.
### `FatStorage` — FAT16/FAT32 via WASM
Two reference implementations live in `/home/ubuntu/web/tmp/`:
- **`fatfs-wasm`** (WASM over CHaN's FatFs, MIT). Production-quality. Supports the full surface — `read`, `write`, `lseek`, `unlink`, `rename`, `mkdir`, `stat`, dir iteration. Synchronous; whole disk is a `Uint8Array`. Recommended.
- **`fatfs`** (pure JS, Apache-2 + BSD-2). Async + native streams, but missing `unlink`/`rename` ("Coming Soon™"). Useful only if streaming is critical and you can ship the missing ops yourself.
A `FatStorage` adapter is a *thin* mapping:
```text
setPartial(key, offset, bytes) → disk.open(key, WRITE|OPEN_ALWAYS); file.lseek(offset); file.write(bytes); file.close();
read(key) → disk.readFile(key)
delete(key) → disk.unlink(key)
list(prefix) → disk.openDir('/'); iterate; filter
```
Why FAT? Two real use cases:
1. **Removable media simulation.** A FAT-formatted `Uint8Array` can be mounted by a real OS — useful for exporting drive snapshots, generating bootable disk images, or interoperating with embedded devices.
2. **Browser-side filesystems with a fixed-size budget.** A FAT image inside OPFS gives you POSIX-ish semantics with predictable allocation and trivial backup (it's one blob).
Capabilities: `partialReads`, `partialWrites`. `nativeStreams=true` only for the `fatfs` (Node) variant; the WASM variant emulates streams via chunked `read`/`write`. **Not** atomic — FAT has no journal.
### `ExfatStorage` — deferred
`/home/ubuntu/web/tmp/node-exfat` is a parser scaffold, not an FS. It can read VBR/AllocTable/Cluster but exposes no `read`/`write`/`mkdir`/`unlink`. Until a real exFAT JS binding lands, this backend is deferred. Genesis does not need it — `FatStorage` covers the FAT family for files <4 GiB; for large files inside an embedded FS, prefer `Ext4Storage` once it lands.
### `Ext4Storage` — deferred
`/home/ubuntu/web/tmp/lwext4` is a C library that supports the full ext2/3/4 surface (extents, sparse files, atomic rename, journal, hardlinks, symlinks, full POSIX permissions, case-sensitive paths). It needs:
1. A Node binding (node-gyp / FFI / N-API). ~2 weeks of work.
2. License clearance — `lwext4` is mixed BSD-3 + GPL-2 (the xattr and extents modules are GPL). Either replace the GPL files or accept GPL-2 for the resulting wasm/binding.
Once both are resolved, the `Ext4Storage` adapter looks like `FatStorage`: thin map of `setPartial → fseek+fwrite`. Capabilities will be `partialReads`, `partialWrites`, `atomicWrites` (journaled).
### `ContentAddressedStorage` — planned wrapper
Wraps any `AbstractStorage` and replaces the caller-supplied key with `BLAKE3(content)` (or `SHA-256(content)`, configurable). `set` becomes idempotent for identical content (cheap dedup), `delete(key)` becomes a refcount, and `capabilities.contentAddressable=true`. This is the substrate for drive-level deduplication and for content-addressed chunk stores in `network-plugin-content-delivery`.
### `GarageStorage` — test only
Garage is an S3-compatible distributed object store used as the integration-test target via `@suaveplan/testing-integration-garage-s3`. From the application's point of view it is just `S3`; the fixture spins up a Docker container, configures a layout, creates a bucket, and hands you `S3Options` you can drop into `createS3()`. This is how every drive integration test that needs a real network-attached object store gets one.
## Choosing a backend
The drive layer is backend-agnostic, but you almost always know which backend is right for a given deployment:
| Use case | Backend |
| --- | --- |
| Tests, ephemeral caches | `MemoryStorage` |
| Server-side production, single tenant | `FileStorage` or `SqliteStorage` |
| Server-side production, distributed | `S3` (any S3-compatible target) |
| Browser, persistent, fast random access | `OpfsStorage` |
| Browser, structured queries / older browsers | `IndexedDbStorage` |
| Removable-media export, fixed-size budget, FAT compat | `FatStorage` |
| Content-addressed chunk pool | wrap any backend in `ContentAddressedStorage` |
| Multi-tenant scoping | wrap any backend in `NamespacedStorage` |
You can compose: `NamespacedStorage(ContentAddressedStorage(S3(...)))` gives you per-tenant content-addressed S3 — the same construction used by `drive-plugin-chunks` for shared chunk pools.
## On-disk shape comparison — 100-file drive
When the drive layer above writes 100 files of ~50 MiB each, the actual files-on-disk count varies dramatically by backend:
| Backend | On-disk shape for 100 × 50 MiB drive | Layout chosen | Why |
|---|---|---|---|
| `MemoryStorage` | 0 files (in-process map) | sparse-file | `partialWrites=true` |
| `FileStorage` (Node fs) | **~202 files** (100 chunk-files + 100 manifests + 2 indexes) | sparse-file | `partialWrites=true` |
| `OpfsStorage` (browser) | ~202 OPFS handles | sparse-file | `partialWrites=true` |
| `S3` | **~80,202 objects** (one per chunk) | chunk-keys | `partialWrites=false`; RMW prohibitive |
| `IndexedDbStorage` | ~80,202 IDB rows | chunk-keys | `partialWrites=false` |
| `SqliteStorage` | **1 file** (one `.sqlite` with ~80k blob rows) | sparse-file via `incremental_blob_open` | Single-file partition |
| `FatStorage` | **1 file** (FAT disk image, mountable by OS) | sparse-file inside FS-in-a-file | True partition file |
| `ContentAddressedStorage` (wrapper) | ~80k unique chunks (deduped across files) | chunk-keys | Hashes coalesce identical content |
So:
- `FileStorage` produces ~2 files per logical file (chunk-file + manifest).
- `S3` produces ~800 objects per logical file (one per chunk + manifest).
- `SqliteStorage` and `FatStorage` produce one file total — pick these for atomic backup, mountable export, or fixed-size budgets.
- The drive's `kind: 'inline'` storage hint lets sub-1-KiB files skip the chunks pipeline entirely, reducing the count further.
The drive layer never produces "plaintext files mirroring user paths" on any backend, because that would leak structural metadata to whoever holds the bytes (S3 admins, FAT disk inspectors, etc.). Path mirroring is by-design absent. If plaintext path-mirror semantics are needed, skip the drive layer and write to `FileStorage` directly — the drive's job is to abstract over storage.
## Reading further
- [`architecture.md`](./architecture.md) — the contract every backend implements.
- [`implementation-status.md`](./implementation-status.md) — port-from-main status.
- [`../drive/plugins.md`](../drive/plugins.md) — how the chunks plugin picks layouts based on backend capabilities.
- [`../drive/index-and-paths.md`](../drive/index-and-paths.md) — the same comparison from the drive side, plus how the two indexes work.
- [`../drive/dehydrated-drives.md`](../drive/dehydrated-drives.md) — server-signed templates and CDN fallback that consume the storage layer.

View File

@@ -0,0 +1,49 @@
# Storage — Implementation Status
Source of truth: [`STATUS.md`](../../STATUS.md) (auto-generated from the suaveplan→genesis migration manifest) and the `packages/storage/` directory on the `genesis` branch.
## Shipped on `genesis`
| Package | Notes |
| --- | --- |
| `@suaveplan/storage` | `AbstractStorage`, `MemoryStorage`, `NamespacedStorage`, `StorageFactory`, four typed errors. Spec-conformance for `STORAGE-01..04`. |
| `@suaveplan/storage-s3` | `S3` backend. Multipart upload via `@suaveplan/retry`, presigned URLs, OTel. Integration-tested against Garage. |
## Pending — to be ported from `main` (`/mnt/s/web/suaveplan`)
| Package | Description (from main `README.md`) |
| --- | --- |
| `@suaveplan/storage-browser` | Browser storage (IndexedDB, localStorage). |
| `@suaveplan/storage-browser-opfs` | OPFS backend for `@suaveplan/storage`. |
| `@suaveplan/storage-file` | Node.js filesystem storage. |
These three are the **immediate** backlog: they unblock browser-side drives, server-side drives, and the OPFS drive that everyone wants. None of the three needs new design — port the existing implementation, retest against the canonical `STORAGE-01..04` conformance suite, and verify the capability flags are correct.
## Planned — new on `genesis`
| Package | Why it's new |
| --- | --- |
| `@suaveplan/storage-sqlite` | We have `@suaveplan/db-sqlite`; a thin `AbstractStorage` adapter on top is straight-line work and unlocks single-file backups for embedded/browser drives. |
| `@suaveplan/storage-fat` | Wraps `fatfs-wasm` (preferred) or `fatfs` (Node-only). Enables removable-media export and embedded FS use cases. |
| `@suaveplan/storage-content-addressed` | Wrapper backend that derives key from `BLAKE3(content)`. Substrate for drive-level dedup and the chunk pool that `network-plugin-content-delivery` consumes. |
## Deferred
| Package | Reason |
| --- | --- |
| `@suaveplan/storage-exfat` | `node-exfat` is parser-only. Wait for a real exFAT JS implementation. FAT covers most of the gap. |
| `@suaveplan/storage-ext4` | `lwext4` needs (a) a Node/WASM binding (~2 weeks) and (b) GPL-vs-BSD license clearance on the extents/xattr modules. Defer until there is a use case that FAT cannot serve. |
## Migration checklist (for each port)
1. Copy source structure from `/mnt/s/web/suaveplan/packages/storage-*/` into `packages/storage/<pkg>/` on `genesis`. Do not pull in old transitive deps that have been replaced (`@suaveplan/error`, `@suaveplan/types`, `@suaveplan/streams` are already migrated; rewire imports).
2. Subclass `AbstractStorage` from `@suaveplan/storage`. Reuse the canonical `STORAGE-01..04` conformance test from `packages/storage/storage/src/abstract/abstract.spec-conformance.test.ts` — point it at the new backend.
3. Set `capabilities` honestly. If you can't do partial writes natively, leave `partialWrites=false` and let `defaultSetPartial` handle it.
4. Add OTel: `meter.createCounter("storage.ops")`, `meter.createHistogram("storage.op.duration_ms")`, `meter.createHistogram("storage.value.bytes")`. The base class wires this up; you only need to call it.
5. Add a `<pkg>` entry to `MANIFEST.md`, run `bun scripts/folder-reorg/generate-manifest.ts`.
6. Pre-publish gate: `bunx turbo lint typecheck test:coverage --filter=@suaveplan/<pkg>` green at 100% coverage.
## Cross-cutting work that unblocks storage
- **`enterprise-types-cross-cutting`** ([proposal](../../openspec/changes/enterprise-types-cross-cutting/proposal.md)) — adds the canonical `StorageLike`, `ChunkSource`, `ChunkRef` interfaces to `@suaveplan/types`. Already implemented; awaits archive. **Block clearing this unlocks downstream packages from re-declaring the same types locally.**
- **`storage-set-partial`** (already archived) — added `setPartial` to the abstract surface, including the `defaultSetPartial` RMW helper. This is what makes browser backends tolerable for chunk-based drives.