(tasks) add tasks for harness
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# T0.7 — `BlobStore` port + `redb` implementation
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Phase | P0 — Foundations |
|
||||
| Size | M — 1 to 3 days |
|
||||
| Status | Not started |
|
||||
| Flags | — |
|
||||
| Spec | inlined below |
|
||||
| Blocks | — |
|
||||
|
||||
## Goal
|
||||
|
||||
Content-addressed blob storage, deduplicated **within** a tenant and never
|
||||
across. `delete` ships now, not later.
|
||||
|
||||
## Facts (inlined — no spec read needed)
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait BlobStore: Send + Sync {
|
||||
async fn put(&self, tenant: TenantId, content: Bytes) -> Result<BlobRef>;
|
||||
async fn get(&self, tenant: TenantId, r: &BlobRef) -> Result<Option<Bytes>>;
|
||||
/// Reduction and tenant deletion both require this. A store that cannot
|
||||
/// delete cannot honour either, and both are obligations.
|
||||
async fn delete(&self, tenant: TenantId, r: &BlobRef) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
- **Blobs are namespaced per tenant even though they are content-addressed.**
|
||||
Global dedup of prompt and output blobs is tempting — identical system prompts
|
||||
across tenants are common — and it is a leak: a shared blob makes one tenant's
|
||||
storage accounting depend on another's, and a hash becomes an oracle for "does
|
||||
anyone else have this content".
|
||||
- `get` returning `None` is a normal outcome, not an error. After reduction
|
||||
(T8.4) the original body is deleted and the log carries a
|
||||
`Reduced { original, summary }` event explaining what stands in its place.
|
||||
- Content addressing means the ref is a hash of the body. A body must never be
|
||||
rewritten under an existing ref — that makes the ref a lie.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Define `BlobRef` as a Blake3 hash newtype. Define the trait as above.
|
||||
2. `redb` table keyed on `Scoped<BlobRef>` — tenant in the key is what enforces
|
||||
per-tenant namespacing structurally rather than by convention.
|
||||
3. `put`: hash the content, insert if absent, return the ref. Insert-if-absent is
|
||||
the dedup, and it is scoped by the key, so no cross-tenant path exists.
|
||||
4. `get`: point lookup, `Ok(None)` on miss.
|
||||
5. `delete`: remove the key. Deleting a ref one tenant holds must not touch
|
||||
another tenant's identical content — which follows from the key shape, and the
|
||||
test below is what proves it.
|
||||
6. Write the conformance suite generic over `impl BlobStore` so T7.2's
|
||||
object-store backend reuses it unchanged.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Identical content put under two tenants produces two independent blobs.
|
||||
- Deleting tenant A's blob leaves tenant B's readable.
|
||||
|
||||
## Verify
|
||||
|
||||
**Harness:** conformance suite generic over `impl BlobStore`, reused verbatim by
|
||||
T7.2's object store.
|
||||
|
||||
**Integration test** — `tests/it_blobstore_conformance.rs`:
|
||||
1. `put` identical bytes under tenant A and tenant B. Assert the returned
|
||||
`BlobRef`s are equal (same content hash) **and** that a raw table scan shows
|
||||
**two** stored entries.
|
||||
2. `delete` tenant A's ref. Assert `get(A, ref)` is `Ok(None)` and
|
||||
`get(B, ref)` still returns the bytes.
|
||||
3. `put` the same bytes twice under one tenant; assert one stored entry (dedup
|
||||
within the tenant) and a stable ref.
|
||||
4. `get` a ref that was never written: `Ok(None)`, not an error.
|
||||
5. `delete` a ref that does not exist: `Ok(())`, so reduction can run twice.
|
||||
6. Re-hash every stored body and assert it matches its key.
|
||||
|
||||
**Command:** `cargo test -p storage --test it_blobstore_conformance`
|
||||
|
||||
**False pass:**
|
||||
- Step 1 asserting only that both `get`s return the right bytes. A single shared
|
||||
blob passes that. The **raw scan showing two entries** is the isolation proof.
|
||||
- Step 2 passing because `delete` is a no-op stub. Assert the `None` on A
|
||||
explicitly, not just B's survival.
|
||||
|
||||
## Traps
|
||||
|
||||
- A global content-addressed table with tenant tracked in a side index. It
|
||||
dedups across tenants by construction, which is the leak.
|
||||
- Deferring `delete` because nothing calls it yet. Reduction and tenant deletion
|
||||
both need it, and retrofitting a delete path into a store designed without one
|
||||
is a rewrite.
|
||||
|
||||
---
|
||||
|
||||
Background (not required to do this task):
|
||||
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §3, §7, §8.6 ·
|
||||
[rust-agentic-task.md](../../../rust-agentic-task.md)
|
||||
Reference in New Issue
Block a user