# M0.2 — Domain types and sha256 identity | Field | Value | |---|---| | Phase | M0 — Read-only spine | | Size | S — under 1 day | | Status | ⬜ Not started | | Flags | — | | Spec | inlined below | | Blocks | M0.1 | ## Goal The vocabulary every other crate speaks, and the content-hash identity the whole provenance graph hangs on. ## Facts (inlined — no spec read needed) ```rust /// Closed. L0 evidence, L1 per-query memory, L2 project synthesis. #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Level { L0, L1, L2 } /// A normalised unit from any source. Adapters produce these; nothing /// downstream learns whether it came from pi, claude, or a socket. pub struct Record { pub role: Role, // User | Assistant | ToolResult | System pub text: String, pub timestamp: OffsetDateTime, pub provenance: Provenance, // source id + offset within it } /// One or more Records, under the token budget, never split mid-Record. pub struct Chunk { pub t: u32, // 1-based turn index within a run pub records: Vec, pub tokens: usize, pub sha256: Sha256Hash, } pub struct MemoryNode { pub level: Level, pub project: ProjectId, pub query_id: Option, // None at L2 pub run_id: RunId, pub t: u32, pub text: String, pub sha256: Sha256Hash, pub parents: Vec, } ``` **Identity is the content hash, not a counter.** `sha256` is computed over the canonical serialization of the semantic content — for `Chunk`, the concatenated record texts and their provenance; for `MemoryNode`, `(level, project, query_id, text)`. It must **not** include the timestamp or the run id, or re-running the same input produces different hashes and `mem rebuild` stops being idempotent. Newtypes with no `Default`: `ProjectId`, `QueryId`, `RunId`, `Sha256Hash`. A placeholder that type-checks is invisible — a hardcoded `"current"` compiles, passes tests, and makes every downstream result unattributable. ## Steps 1. Declare `Level`, `Role`, `Record`, `Provenance`, `Chunk`, `MemoryNode`. 2. Declare the newtypes. None derives `Default`. None has `From` without validation. 3. `fn content_hash(&self) -> Sha256Hash` on `Chunk` and `MemoryNode`, over a canonical byte encoding that excludes timestamps and run ids. 4. `Level` serializes as the literal strings `"L0" | "L1" | "L2"` — the JSONL and the SQL `CHECK` constraint both depend on that spelling. 5. Round-trip serde tests for every type. ## Acceptance - Two `Chunk`s built from identical records in different runs hash identically. - Changing one character of any record text changes the hash. - `Level` round-trips through JSON as `"L0"`, not `0` and not `"l0"`. ## Verify **Harness:** unit tests in `mem-core`, plus a hash-stability fixture committed as bytes. **Integration test** — `tests/it_identity.rs`: 1. `a1_same_content_same_hash` — build the same chunk twice with different `RunId` and timestamps, assert equal hashes. 2. `a2_text_change_changes_hash` — flip one byte, assert the hash differs. 3. `a3_level_wire_format` — `serde_json::to_string(&Level::L0) == "\"L0\""`. 4. `a4_hash_stability_across_versions` — hash a committed fixture record set, assert it equals a hash literal written into the test. This catches a canonicalization change that would silently orphan every stored node. 5. `a5_newtypes_have_no_default` — compile-fail test (`trybuild`) asserting `ProjectId::default()` does not compile. **Command:** `cargo test -p mem-core identity` **False pass:** - Asserting only that hashing is deterministic *within one process*. A hash that includes the timestamp is deterministic per run and still breaks rebuild. Assertion 1 must vary the run id and timestamp deliberately. - Omitting assertion 4. Without a committed expected hash, any future canonicalization change passes every other test and silently invalidates the database. ## Traps - Including `run_id` or `created_at` in the hash. Rebuild then produces new nodes every time and `memory_edge` accumulates orphans. - Deriving `Default` on an id newtype "for tests". That default reaches production and every row attributes to the same fake project. --- Background: [DESIGN.md](../DESIGN.md) — The tier model, Storage schemas