# T7.2 — Object-store `BlobStore` | Field | Value | |---|---| | Phase | P7 — Distribution | | Size | M — 1 to 3 days | | Status | Not started | | Flags | — | | Spec | inlined below | | Blocks | — | ## Goal S3-compatible `BlobStore`, tenant-namespaced, passing the same conformance suite as the embedded implementation. ## Facts (inlined — no spec read needed) - Same port as T0.7: `put` / `get` / `delete`, all async, all taking `TenantId`. - **Blobs are namespaced per tenant even though they are content-addressed.** Global deduplication across tenants 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". Deduplicate within a tenant, never across. - `delete` is not optional — reduction (T8.4) and tenant deletion both require it. A store that cannot delete cannot honour either. - `get` returning `None` is a normal answer, not an error. - Cross-tenant isolation must be verified by **attempting** a cross-tenant fetch, not by reading the key-construction code. ## Steps 1. Implement over an `object_store`-style client. Key layout puts the tenant in the path prefix: `{tenant}/{blake3-hash}`. 2. `put`: hash, then conditional put (skip if present) — dedup falls out of the content-addressed key, scoped by the tenant prefix. 3. `get`: fetch, map a not-found response to `Ok(None)`, everything else to an error. 4. `delete`: delete the object. A missing object is not an error — reduction can run twice. 5. Set retry and timeout policy on the client explicitly. A blob fetch stalling forever inside a verifier is a hang the deadline should catch, but the client should not depend on that. 6. Run the T0.7 conformance suite unmodified, plus the explicit cross-tenant fetch attempt. ## Acceptance - Same conformance suite as the embedded store passes. - Tenant isolation verified by **attempting a cross-tenant fetch**. ## Verify **Harness:** the T0.7 conformance function against MinIO or an S3-compatible container. Same suite, unmodified. **Integration test** — `tests/it_object_store_conformance.rs`: 1. Run the T0.7 suite verbatim against the object store. 2. **Cross-tenant attempt:** with tenant B's credentials/prefix, attempt to fetch tenant A's ref. Assert it fails or returns `None` — attempt it, do not infer isolation from the key-construction code. 3. Assert identical content under two tenants produces **two objects**, verified by listing the bucket, not by the return values. 4. Assert a 404 on `get` maps to `Ok(None)` and a 404 on `delete` maps to `Ok(())`, so reduction is idempotent. 5. Re-hash every stored object against its key. 6. Latency/failure behaviour: inject a slow response; assert the configured timeout fires rather than hanging a verifier indefinitely. 7. Run the same suite against `redb` in the same job to catch backend divergence. **Command:** `cargo test -p storage-s3 --test it_object_store_conformance` **False pass:** - Step 2 performed with the same credentials for both tenants. That tests the key prefix, not isolation. Use separate scoped credentials where the deployment will. - Step 3 asserting on the returned refs, which are equal by design (same content hash). The **bucket listing** is what shows two objects. - Mocking the object store. The failure modes worth catching here — eventual consistency, 404 shapes, timeouts — exist only in a real implementation. ## Traps - A flat bucket layout with the tenant in metadata rather than the key. It dedups across tenants by construction. - Treating a 404 on delete as a failure, which breaks idempotent reduction. --- 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)