core/tests/formation/test_ratify.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

172 lines
5.9 KiB
Python

"""Tests for ``formation.ratify`` — Stage 7 gate checks."""
from __future__ import annotations
import pytest
from formation.candidate import RelationCandidate, SourceRef
from formation.course import ValidatedTripleSet
from formation.mastery import verify_report
from formation.ratify import StepResult, ratify
def _src() -> SourceRef:
return SourceRef(
source_sha="a" * 64,
span="span",
adapter="wikipedia",
retrieved_at="2026-05-16T00:00:00Z",
)
def _r(head: str, relation: str, tail: str) -> RelationCandidate:
return RelationCandidate(head=head, relation=relation, tail=tail, sources=(_src(),))
def _vts(*triples: tuple[str, str, str]) -> ValidatedTripleSet:
return ValidatedTripleSet(
subject_id="subject.x",
concepts=(),
relations=tuple(_r(h, r, t) for h, r, t in triples),
counters=(),
ordering_hints=(),
)
def _step(
step_type: str,
head: str = "",
relation: str = "",
tail: str = "",
trace: str = "t",
accepted: bool = True,
has_provenance: bool = True,
vc: str = "0.0",
) -> StepResult:
payload: dict[str, object] = {}
if head:
payload["head"] = head
if relation:
payload["relation"] = relation
if tail:
payload["tail"] = tail
return StepResult(
step_type=step_type,
payload=payload,
trace_hash=trace,
versor_condition_repr=vc,
accepted=accepted,
has_provenance=has_provenance,
)
_BASE_KW = dict(
course_id="x.v1",
source_bundle_sha="0" * 64,
validated_set_sha="1" * 64,
course_sha256="2" * 64,
plan_sha256="3" * 64,
issued_at="2026-05-16T00:00:00Z",
)
class TestAllGatesGreen:
def test_full_ratification(self) -> None:
vts = _vts(("wisdom", "is", "judgment"))
run = (
_step("seed_concept", trace="t1"),
_step("introduce_relation", "wisdom", "is", "judgment", trace="t2"),
_step("walk_step", "wisdom", "is", "judgment", trace="t3"),
_step("adversarial_probe", trace="t4", accepted=False),
_step("replay_assertion", trace="t5"),
)
rpt = ratify(
validated_set=vts,
first_run=run,
second_run=run,
**_BASE_KW,
)
assert rpt.ratified is True
assert verify_report(rpt) is True
assert all(g.passed for g in rpt.gates)
assert rpt.failure_reasons == ()
class TestG1Replay:
def test_trace_mismatch_fails(self) -> None:
vts = _vts(("a", "is", "b"))
first = (_step("walk_step", "a", "is", "b", trace="t1"),)
second = (_step("walk_step", "a", "is", "b", trace="DIFFERENT"),)
rpt = ratify(validated_set=vts, first_run=first, second_run=second, **_BASE_KW)
g1 = next(g for g in rpt.gates if g.name == "G1_replay_determinism")
assert g1.passed is False
assert rpt.ratified is False
def test_length_mismatch_fails(self) -> None:
vts = _vts(("a", "is", "b"))
first = (_step("walk_step", "a", "is", "b", trace="t1"),)
second: tuple[StepResult, ...] = ()
rpt = ratify(validated_set=vts, first_run=first, second_run=second, **_BASE_KW)
assert rpt.ratified is False
class TestG3Adversarial:
def test_adversarial_accepted_fails(self) -> None:
vts = _vts(("a", "is", "b"))
run = (
_step("walk_step", "a", "is", "b", trace="t1"),
_step("adversarial_probe", trace="t2", accepted=True),
)
rpt = ratify(validated_set=vts, first_run=run, second_run=run, **_BASE_KW)
g3 = next(g for g in rpt.gates if g.name == "G3_adversarial_rejection_rate")
assert g3.passed is False
class TestG4Legit:
def test_legit_rejection_fails(self) -> None:
vts = _vts(("a", "is", "b"))
run = (_step("walk_step", "a", "is", "b", trace="t1", accepted=False),)
rpt = ratify(validated_set=vts, first_run=run, second_run=run, **_BASE_KW)
g4 = next(g for g in rpt.gates if g.name == "G4_legitimate_acceptance_rate")
assert g4.passed is False
class TestG5Provenance:
def test_missing_provenance_fails(self) -> None:
vts = _vts(("a", "is", "b"))
run = (_step("walk_step", "a", "is", "b", trace="t1", has_provenance=False),)
rpt = ratify(validated_set=vts, first_run=run, second_run=run, **_BASE_KW)
g5 = next(g for g in rpt.gates if g.name == "G5_provenance_nonempty_rate")
assert g5.passed is False
class TestG6Coverage:
def test_unwalked_relation_fails(self) -> None:
vts = _vts(("wisdom", "is", "judgment"), ("number", "is", "quantity"))
run = (
_step("walk_step", "wisdom", "is", "judgment", trace="t1"),
# "number is quantity" never walked.
)
rpt = ratify(validated_set=vts, first_run=run, second_run=run, **_BASE_KW)
g6 = next(g for g in rpt.gates if g.name == "G6_phase2_relation_coverage")
assert g6.passed is False
assert any("unwalked_relations" in r for r in rpt.failure_reasons)
def test_all_walked_passes(self) -> None:
vts = _vts(("wisdom", "is", "judgment"), ("number", "is", "quantity"))
run = (
_step("walk_step", "wisdom", "is", "judgment", trace="t1"),
_step("walk_step", "number", "is", "quantity", trace="t2"),
)
rpt = ratify(validated_set=vts, first_run=run, second_run=run, **_BASE_KW)
g6 = next(g for g in rpt.gates if g.name == "G6_phase2_relation_coverage")
assert g6.passed is True
class TestEmptyInputs:
def test_no_steps_ratifies_when_no_required_relations(self) -> None:
vts = _vts() # no relations required
rpt = ratify(validated_set=vts, first_run=(), second_run=(), **_BASE_KW)
# All gate ratios are "n/a" → considered passed.
assert all(g.passed for g in rpt.gates)
assert rpt.ratified is True