core/tests/formation/test_hashing.py
Shay 64c5bc4619 feat(epistemic): truth-seeking schema audit — 3 leaks closed, 4 new lanes, 3 new invariants
Audit of the one-mutation-path invariant (ADR-0021 §3) found three leaks
where pack authority or session-state writes could substitute for coherence
judgment. All three landed fixes or partial closures in this push.

Leaks closed:
- Leak A: pack vocab defaulted to COHERENT — flipped to SPECULATIVE in
  language_packs/{compiler,schema}.py; docstring corrected to align with
  ADR-0021 (it was rationalizing the leak).
- Leak B: vault.recall was epistemic-blind — VaultStore.store() now stamps
  every entry with EpistemicStatus (default SPECULATIVE); recall(min_status=)
  filters to admissible-as-evidence tier. All 4 vault-write sites updated.
- Leak C (write-side): generate/proposition.py:198 stored articulated
  propositions unmarked — now stamps SPECULATIVE, breaking the
  fabrication-feedback loop in principle. Read-side audit of 5 call sites
  is the residual.

New architectural invariants (tests/test_architectural_invariants.py):
- INV-21: one-mutation-path allowlist (caught Leak C on first run)
- INV-22: pack lexicon default is SPECULATIVE (Leak A guard)
- INV-23: vault recall epistemic-aware (Leak B guard)

New eval lanes:
- teaching_injection_resistance — ships GREEN at 1.00/1.00/0 (the
  structural anti-injection claim is real and measurable)
- refusal_calibration — honest gap: 0% refusal, 0% fabrication
- contradiction_detection — honest gap: 50% flag via versor-delta heuristic,
  100% false-positive; motivates the proper coherence-checker
- articulation_of_status — honest gap: 0% speculative articulation, 60%
  false certainty; output-side leak surface

New benchmarks:
- benchmarks/footprint.py — total deployed runtime is 7.06 MiB
  (109,358x smaller than Llama 3.1 405B, runs offline, no GPU)
- benchmarks/learning_curve.py — monotonic + replay-deterministic curve
  per lane

Documentation:
- docs/truth_seeking_schema.md — foundational architectural commitment,
  five rules, mapped to human failure modes, leaks published openly
- evals/CLAIMS.md — five-tier public claims doc; Tier 4.5 publishes
  known gaps with named fixes; verification contract at top
- README.md — new pillar between algebraic substrate and language pillar

Includes in-flight formation pipeline scaffolding (formation/, tests/formation/,
docs/formation_pipeline_plan.md) and minor CLI/contracts/gitignore edits
that were already in the working tree at session start.

Verification: 798 passed, 2 skipped, 1 deselected (pre-existing pack-count
test drift unrelated to schema changes).
2026-05-17 07:27:41 -07:00

78 lines
2.6 KiB
Python

"""Tests for ``formation.hashing`` — the content-addressing foundation."""
from __future__ import annotations
import pytest
from formation.hashing import (
canonical_json,
self_seal,
sha256_of,
verify_seal,
)
class TestCanonicalJson:
def test_sorted_keys(self) -> None:
assert canonical_json({"b": 1, "a": 2}) == b'{"a":2,"b":1}'
def test_tight_separators(self) -> None:
assert canonical_json([1, 2, 3]) == b"[1,2,3]"
def test_nested_stability(self) -> None:
payload = {"z": [3, 2, {"y": 1, "x": 2}], "a": "hello"}
# Same content, different construction order, must serialize identically.
twin = {"a": "hello", "z": [3, 2, {"x": 2, "y": 1}]}
assert canonical_json(payload) == canonical_json(twin)
def test_utf8(self) -> None:
assert canonical_json({"k": "wisdom λόγος"}) == '{"k":"wisdom λόγος"}'.encode()
def test_floats_forbidden(self) -> None:
with pytest.raises(TypeError, match="float values are forbidden"):
canonical_json({"x": 1.5})
def test_floats_forbidden_nested(self) -> None:
with pytest.raises(TypeError):
canonical_json([1, 2, [3, 4.0]])
def test_non_string_dict_keys_forbidden(self) -> None:
with pytest.raises(TypeError, match="dict keys must be strings"):
canonical_json({1: "x"})
class TestSha256Of:
def test_deterministic(self) -> None:
payload = {"a": 1, "b": [1, 2, 3]}
assert sha256_of(payload) == sha256_of(payload)
def test_different_content_different_sha(self) -> None:
assert sha256_of({"a": 1}) != sha256_of({"a": 2})
def test_hex_64_chars(self) -> None:
sha = sha256_of({"a": 1})
assert len(sha) == 64
assert all(c in "0123456789abcdef" for c in sha)
class TestSelfSeal:
def test_seal_then_verify(self) -> None:
sealed = self_seal({"course_id": "x", "report_sha256": ""})
assert verify_seal(sealed) is True
assert sealed["report_sha256"] != ""
def test_tamper_breaks_seal(self) -> None:
sealed = self_seal({"course_id": "x", "report_sha256": ""})
tampered = dict(sealed)
tampered["course_id"] = "y"
assert verify_seal(tampered) is False
def test_blank_sha_fails_verify(self) -> None:
assert verify_seal({"course_id": "x", "report_sha256": ""}) is False
def test_missing_field_raises_on_seal(self) -> None:
with pytest.raises(ValueError, match="missing required field"):
self_seal({"course_id": "x"})
def test_missing_field_returns_false_on_verify(self) -> None:
assert verify_seal({"course_id": "x"}) is False