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

205 lines
7 KiB
Python

"""Stage 5 — Compile. ``CourseYAML`` -> ``FormationPlan``.
The plan is a deterministic, content-addressed sequence of ``PlanStep``
objects. Each step carries the typed payload the Runner needs to issue a
single ``CognitiveTurnPipeline.run()`` invocation.
Same ``CourseYAML`` -> same ``FormationPlan`` -> same ``plan_sha256``. This
property is load-bearing for replay determinism and the ratify gates.
"""
from __future__ import annotations
from typing import Any
import yaml
from formation.course import CourseYAML, FormationPlan, PlanStep
from formation.hashing import sha256_of
def compile_course(course: CourseYAML) -> FormationPlan:
"""Convert a ``CourseYAML`` to a deterministic ``FormationPlan``."""
body = yaml.safe_load(course.yaml_bytes)
if not isinstance(body, dict):
raise ValueError("compile_course: course body must be a mapping")
# The Definition template emits course fields at the top level; we accept
# either a wrapped ``course:`` mapping (future templates) or the bare form.
course_block = body.get("course") if "course" in body else body
if not isinstance(course_block, dict):
raise ValueError("compile_course: course payload must be a mapping")
steps: list[PlanStep] = []
steps.extend(_seed_concept_steps(course_block))
steps.extend(_introduce_relation_steps(course_block))
steps.extend(_walk_step_steps(course_block))
steps.extend(_adversarial_probe_steps(course_block))
steps.extend(_replay_assertion_steps(course_block))
plan_sha = sha256_of({
"course_id": course.course_id,
"course_sha256": course.course_sha256,
"steps": [
{"step_type": s.step_type, "payload": _canonicalize(s.payload)}
for s in steps
],
})
return FormationPlan(
course_id=course.course_id,
course_sha256=course.course_sha256,
steps=tuple(steps),
plan_sha256=plan_sha,
)
# ---------- per-phase extractors ----------
def _seed_concept_steps(course: dict[str, Any]) -> list[PlanStep]:
phase = course.get("phase_1_ontological_seeding", {}) or {}
items = phase.get("concepts", []) or []
out: list[PlanStep] = []
for c in items:
if not isinstance(c, dict):
continue
term = str(c.get("canonical_term", ""))
definition = str(c.get("definition", ""))
if not term:
continue
out.append(PlanStep(
step_type="seed_concept",
payload={
"canonical_term": term,
"definition": definition,
"utterance": f"What is {term}?",
},
))
return out
def _introduce_relation_steps(course: dict[str, Any]) -> list[PlanStep]:
phase = course.get("phase_2_axiomatic_rotor_scaffolding", {}) or {}
items = phase.get("relations", []) or []
out: list[PlanStep] = []
for r in items:
if not isinstance(r, dict):
continue
head = str(r.get("head", ""))
relation = str(r.get("relation", ""))
tail = str(r.get("tail", ""))
if not head or not relation or not tail:
continue
out.append(PlanStep(
step_type="introduce_relation",
payload={
"head": head,
"relation": relation,
"tail": tail,
"utterance": f"{head} {relation.replace('_', ' ')} {tail}.",
},
))
return out
def _walk_step_steps(course: dict[str, Any]) -> list[PlanStep]:
phase = course.get("phase_3_holonomic_syllabus_walk", {}) or {}
# Accept the Definition template's ``walks:`` key as well as the future
# ``ordered_walks:`` form proposed in the plan doc.
walks = phase.get("walks") or phase.get("ordered_walks") or []
out: list[PlanStep] = []
for walk in walks:
if not isinstance(walk, dict):
continue
edges = walk.get("steps") or walk.get("edges") or []
for edge in edges:
if not isinstance(edge, dict):
continue
head = str(edge.get("head", ""))
relation = str(edge.get("relation", ""))
tail = str(edge.get("tail", ""))
if not head or not relation or not tail:
continue
out.append(PlanStep(
step_type="walk_step",
payload={
"head": head,
"relation": relation,
"tail": tail,
"utterance": f"What does {head} {relation.replace('_', ' ')}?",
},
))
return out
def _adversarial_probe_steps(course: dict[str, Any]) -> list[PlanStep]:
phase = course.get("phase_4_epistemic_boundary_hardening", {}) or {}
out: list[PlanStep] = []
for false_claim in phase.get("false_claims", []) or []:
if not isinstance(false_claim, dict):
continue
head = str(false_claim.get("head", ""))
relation = str(false_claim.get("relation", ""))
tail = str(false_claim.get("tail", ""))
if not head or not relation or not tail:
continue
out.append(PlanStep(
step_type="adversarial_probe",
payload={
"head": head,
"relation": relation,
"tail": tail,
"kind": "false_claim",
"utterance": f"{head} {relation.replace('_', ' ')} {tail}.",
},
))
for probe in phase.get("adversarial_corrections", []) or []:
if isinstance(probe, dict):
utterance = str(probe.get("prompt") or probe.get("utterance") or "")
probe_id = str(probe.get("probe_id", ""))
else:
utterance = str(probe)
probe_id = ""
if not utterance:
continue
out.append(PlanStep(
step_type="adversarial_probe",
payload={
"kind": "identity_override",
"probe_id": probe_id,
"utterance": utterance,
},
))
return out
def _replay_assertion_steps(course: dict[str, Any]) -> list[PlanStep]:
phase = course.get("phase_5_ratified_consolidation", {}) or {}
replay = phase.get("replay")
if isinstance(replay, dict):
if not replay.get("deterministic", False):
return []
return [PlanStep(
step_type="replay_assertion",
payload={
"prior_regression_allowed": bool(
replay.get("prior_regression_allowed", False)
),
},
)]
# Definition template form: presence of ``ratification_gates`` implies a
# deterministic replay step.
gates = phase.get("ratification_gates")
if isinstance(gates, list) and gates:
return [PlanStep(
step_type="replay_assertion",
payload={"gates": [str(g) for g in gates]},
)]
return []
# ---------- helpers ----------
def _canonicalize(payload: dict[str, Any]) -> dict[str, Any]:
"""Return a copy with stable iteration order — dict literal already sorts via sha256_of."""
return dict(payload)