core/tests/test_vaultstore_codec.py
Shay c0aebf142c feat(persistence): Shape B+ Phase B — VaultStore (de)serialize, exact recall preserved
Adds VaultStore.to_dict/from_dict on top of Phase A's array codec. Persists the
versors (bit-exact via the codec), metadata, store_count, reproject_interval,
and max_entries; rebuilds the derived _exact_index on load and leaves the lazy
_matrix_cache None.

Bright line (vault/store.py is a CLAUDE.md forbidden normalization site): the
load path performs NO reprojection / normalization / repair — it restores the
exact persisted bytes (already null-projected at their last live reproject
boundary) and rebuilds only the pure index. Proven by a test asserting the
restored versors are BIT-IDENTICAL to the originals (a reproject would change
them via null_project) and that exact CGA recall — including the score==inf
exact-match short-circuit — is identical after a save/load cycle.

5 new tests + INV-02 (normalize-not-called-outside-gate) + all vault tests pass
(116 passed). Part of the A->E Shape B+ scope (Phase B).
2026-06-05 11:49:58 -07:00

91 lines
3.2 KiB
Python

"""VaultStore round-trip — Shape B+ Phase B.
Exit gate: exact CGA recall is identical after a save/load cycle, and the
restored versors are BIT-IDENTICAL to the originals — which proves the load path
did NOT reproject/normalize them (``vault/store.py`` is a CLAUDE.md forbidden
normalization site; persistence must be pure (de)serialization). The derived
``_exact_index`` is rebuilt on load and the lazy ``_matrix_cache`` stays None.
"""
from __future__ import annotations
import json
import numpy as np
from teaching.epistemic import EpistemicStatus
from vault.store import VaultStore
def _versors(n: int = 6) -> list[np.ndarray]:
out = []
for i in range(n):
v = np.zeros(32, dtype=np.float32)
v[0] = 1.0
v[(i % 31) + 1] = 0.1 * (i + 1) # distinct, near-identity
out.append(v)
return out
def _populated_store() -> VaultStore:
store = VaultStore(reproject_interval=0) # no auto-reproject; isolate persistence
for i, v in enumerate(_versors()):
store.store(
v,
{"turn": i, "role": "user" if i % 2 == 0 else "assistant"},
epistemic_status=EpistemicStatus.SPECULATIVE,
)
return store
def test_vaultstore_round_trip_preserves_exact_recall() -> None:
store = _populated_store()
query = _versors()[2]
before = store.recall(query, top_k=5)
restored = VaultStore.from_dict(store.to_dict())
after = restored.recall(query, top_k=5)
assert [(r["index"], r["score"]) for r in before] == [
(r["index"], r["score"]) for r in after
]
assert [r["metadata"] for r in before] == [r["metadata"] for r in after]
assert [r["versor"].tobytes() for r in before] == [
r["versor"].tobytes() for r in after
]
def test_vaultstore_restore_does_not_reproject_versors() -> None:
# Bit-identical versors prove the load path called no null_project/normalizer.
store = _populated_store()
restored = VaultStore.from_dict(store.to_dict())
assert len(restored) == len(store)
for original, recovered in zip(store._versors, restored._versors):
assert recovered.tobytes() == original.tobytes()
assert recovered.dtype == np.float32
def test_vaultstore_round_trip_is_json_safe_and_preserves_scalars() -> None:
store = _populated_store()
restored = VaultStore.from_dict(json.loads(json.dumps(store.to_dict())))
assert len(restored) == len(store)
assert restored.store_count == store.store_count
assert restored.reproject_interval == store.reproject_interval
def test_vaultstore_restore_rebuilds_exact_match_index() -> None:
# The exact-match short-circuit (score == inf) must work after restore,
# which requires _exact_index to be rebuilt over the restored bytes.
store = _populated_store()
restored = VaultStore.from_dict(store.to_dict())
exact_query = _versors()[3]
hits = restored.recall(exact_query, top_k=3)
assert hits[0]["score"] == float("inf") # exact match found via rebuilt index
def test_empty_vaultstore_round_trips() -> None:
store = VaultStore(reproject_interval=50, max_entries=10)
restored = VaultStore.from_dict(store.to_dict())
assert len(restored) == 0
assert restored.reproject_interval == 50
assert restored.recall(_versors()[0], top_k=3) == []