From f91489005b4e983bd2ddf3bf0e93b7e2fa021d90 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Sat, 4 Jul 2026 21:00:57 -0700 Subject: [PATCH] =?UTF-8?q?feat(memory):=20W3=20parity=20harness=20(Layer?= =?UTF-8?q?=201)=20=E2=80=94=20on-disk=20format=20regression=20pins=20(#45?= =?UTF-8?q?32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/openhuman/tinycortex/mod.rs | 2 + src/openhuman/tinycortex/parity.rs | 100 +++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/openhuman/tinycortex/parity.rs diff --git a/src/openhuman/tinycortex/mod.rs b/src/openhuman/tinycortex/mod.rs index 09a7630f7..aabcb7ce5 100644 --- a/src/openhuman/tinycortex/mod.rs +++ b/src/openhuman/tinycortex/mod.rs @@ -36,6 +36,8 @@ mod chat; mod config; mod embeddings; +#[cfg(test)] +mod parity; pub use chat::{build_chat_provider, SeamChatProvider}; pub use config::memory_config_from; diff --git a/src/openhuman/tinycortex/parity.rs b/src/openhuman/tinycortex/parity.rs new file mode 100644 index 000000000..496873be8 --- /dev/null +++ b/src/openhuman/tinycortex/parity.rs @@ -0,0 +1,100 @@ +//! On-disk format parity — Layer-1 regression pins (migration W3 gate, spec §0.3). +//! +//! Existing user workspaces must open unchanged after the store cutover. These +//! are the cheap, fixture-free asserters from the parity checklist: they pin the +//! crate's deterministic **on-disk contracts** to the exact byte forms that +//! historical OpenHuman workspaces were written with, so any future crate change +//! that would silently reshape chunk IDs, vector encoding, or vault paths fails +//! here instead of corrupting a real workspace. +//! +//! The golden constants were computed from the format spec (SHA-256 first-32-hex +//! chunk IDs; little-endian packed f32 vectors) and cross-checked against the +//! crate at the W3 baseline. The Layer-2 golden-workspace differential harness +//! (a real `chunks.db` + vault opened and compared) is the merge gate for the +//! actual store flips; this layer runs on every PR. +//! +//! Test-only module — no runtime code. + +#[cfg(test)] +mod tests { + use tinycortex::memory::chunks::{chunk_id, SourceKind}; + use tinycortex::memory::store::content::chunk_rel_path; + use tinycortex::memory::store::vectors::{bytes_to_vec, vec_to_bytes}; + + /// P1 — the deterministic chunk ID is SHA-256 over + /// `source_kind \0 source_id \0 seq_be \0 content`, first 32 hex chars. + /// This golden is the value historical workspaces indexed by; a change to + /// the hash inputs / order / separators would strand every existing chunk. + #[test] + fn chunk_id_matches_historical_golden() { + let id = chunk_id(SourceKind::Document, "src-1", 5, "hello world"); + assert_eq!(id, "2be5fac18b12bfb417736b54deaf5f9d"); + assert_eq!(id.len(), 32); + assert!(id.chars().all(|c| c.is_ascii_hexdigit())); + } + + /// P1 — every field participates in the hash, and `seq` is order-sensitive. + /// Guards against an input being dropped or reordered (which a property-free + /// golden alone would miss on a symmetric swap). + #[test] + fn chunk_id_is_sensitive_to_every_field() { + let base = chunk_id(SourceKind::Document, "src-1", 5, "hello world"); + assert_ne!(base, chunk_id(SourceKind::Chat, "src-1", 5, "hello world")); + assert_ne!( + base, + chunk_id(SourceKind::Document, "src-2", 5, "hello world") + ); + assert_ne!( + base, + chunk_id(SourceKind::Document, "src-1", 6, "hello world") + ); + assert_ne!( + base, + chunk_id(SourceKind::Document, "src-1", 5, "hello worlds") + ); + // Determinism: same inputs, same id. + assert_eq!( + base, + chunk_id(SourceKind::Document, "src-1", 5, "hello world") + ); + } + + /// P2 — vectors persist as little-endian packed f32, 4 bytes/element, no + /// header. The golden byte string is what existing `vectors.embedding` + /// BLOBs and `mem_tree_*_embeddings` sidecars were written with. + #[test] + fn vector_encoding_is_le_packed_f32() { + let v = vec![1.0f32, -2.0, 0.5]; + let bytes = vec_to_bytes(&v); + assert_eq!(bytes.len(), v.len() * 4); + assert_eq!(hex(&bytes), "0000803f000000c00000003f"); + // Round-trips exactly. + assert_eq!(bytes_to_vec(&bytes), v); + } + + /// P6 — vault paths sanitize IDs to cross-platform-safe filenames. Chunk IDs + /// contain colons (`chat:slack:#eng:0`) that are illegal on Windows NTFS; + /// the path must not leak them, and must be deterministic so an existing + /// vault file is found in place. + #[test] + fn content_paths_are_windows_safe_and_stable() { + let p1 = chunk_rel_path("chat", "slack:#eng", "chat:slack:#eng:0"); + let p2 = chunk_rel_path("chat", "slack:#eng", "chat:slack:#eng:0"); + assert_eq!(p1, p2, "path derivation must be deterministic"); + assert!( + !p1.contains(':'), + "path must not contain Windows-illegal ':' -> {p1}" + ); + assert!(p1.ends_with(".md"), "chunk files are markdown -> {p1}"); + } + + fn hex(bytes: &[u8]) -> String { + use std::fmt::Write; + bytes + .iter() + .fold(String::with_capacity(bytes.len() * 2), |mut acc, b| { + let _ = write!(acc, "{b:02x}"); + acc + }) + } +}