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).
154 lines
5 KiB
Python
154 lines
5 KiB
Python
"""Stage 8 — Promote. The only SPECULATIVE → COHERENT bridge.
|
|
|
|
Promotion is the **single** authorized mutation path from a Mastered course
|
|
into the language pack / teaching store. Every other artifact in the
|
|
pipeline is proposal-only. Per ADR-0021 §Schema impact, the bridge here
|
|
routes through ``teaching/review.py`` so the existing review gate is the
|
|
sole pack-mutation entry point in the entire system.
|
|
|
|
Promotion requirements (all must hold):
|
|
|
|
P1. The ``MasteryReport`` must be ``ratified == True``.
|
|
P2. Its ``report_sha256`` must verify (self-seal intact).
|
|
P3. Every ``requires_courses`` entry in the ``SubjectSpec`` must be
|
|
present in the ``MasteredCoursesIndex``.
|
|
P4. Promotion is idempotent — calling ``promote`` twice with the same
|
|
``report_sha256`` is a no-op.
|
|
|
|
Promotion stamps every triple with ``report_sha256`` so the teaching store
|
|
carries the chain-of-custody back to the receipt that authorized the
|
|
mutation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from formation.course import (
|
|
MasteryReport,
|
|
SubjectSpec,
|
|
ValidatedTripleSet,
|
|
)
|
|
from formation.index import MasteredCourseEntry, MasteredCoursesIndex
|
|
from formation.mastery import verify_report
|
|
from generate.intent import DialogueIntent, IntentTag
|
|
from teaching.correction import CorrectionCandidate
|
|
from teaching.epistemic import EpistemicStatus
|
|
from teaching.review import (
|
|
ReviewOutcome,
|
|
ReviewedTeachingExample,
|
|
review_correction,
|
|
)
|
|
from formation.hashing import sha256_of
|
|
|
|
|
|
class PromoteRefused(Exception):
|
|
"""Raised when a promotion is refused. Carries the failure reason."""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PromotedTriple:
|
|
"""A reviewed teaching example carrying the authorizing Mastery Report SHA."""
|
|
|
|
report_sha256: str
|
|
example: ReviewedTeachingExample
|
|
triple: tuple[str, str, str]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PromotionResult:
|
|
report_sha256: str
|
|
course_id: str
|
|
promoted: tuple[PromotedTriple, ...]
|
|
rejected: tuple[PromotedTriple, ...]
|
|
idempotent_skipped: bool = False
|
|
|
|
|
|
def _candidate_for(
|
|
triple: tuple[str, str, str], report_sha: str
|
|
) -> CorrectionCandidate:
|
|
head, relation, tail = triple
|
|
correction_text = f"{head} {relation.replace('_', ' ')} {tail}."
|
|
intent = DialogueIntent(tag=IntentTag.CORRECTION, subject=head)
|
|
candidate_id = sha256_of(
|
|
{"head": head, "relation": relation, "tail": tail, "report": report_sha}
|
|
)[:16]
|
|
return CorrectionCandidate(
|
|
correction_text=correction_text,
|
|
intent=intent,
|
|
prior_surface="",
|
|
prior_turn=-1,
|
|
candidate_id=candidate_id,
|
|
)
|
|
|
|
|
|
def promote(
|
|
*,
|
|
report: MasteryReport,
|
|
spec: SubjectSpec,
|
|
validated_set: ValidatedTripleSet,
|
|
index: MasteredCoursesIndex,
|
|
) -> PromotionResult:
|
|
"""Run the Promote gate and route validated triples through the review path."""
|
|
# P1: ratified.
|
|
if not report.ratified:
|
|
raise PromoteRefused(
|
|
f"report not ratified: failure_reasons={list(report.failure_reasons)}"
|
|
)
|
|
|
|
# P2: seal intact.
|
|
if not verify_report(report):
|
|
raise PromoteRefused("report self-seal does not verify")
|
|
|
|
# P3: prerequisites.
|
|
missing = [
|
|
c for c in spec.requires_courses if not index.contains_course(c)
|
|
]
|
|
if missing:
|
|
raise PromoteRefused(f"missing prerequisites: {missing}")
|
|
|
|
# P4: idempotency — already promoted under this exact report SHA?
|
|
if index.contains_report(report.report_sha256):
|
|
return PromotionResult(
|
|
report_sha256=report.report_sha256,
|
|
course_id=report.course_id,
|
|
promoted=(),
|
|
rejected=(),
|
|
idempotent_skipped=True,
|
|
)
|
|
|
|
promoted: list[PromotedTriple] = []
|
|
rejected: list[PromotedTriple] = []
|
|
|
|
for rel in validated_set.relations:
|
|
triple = (rel.head, rel.relation, rel.tail)
|
|
candidate = _candidate_for(triple, report.report_sha256)
|
|
example = review_correction(
|
|
candidate, epistemic_status=EpistemicStatus.SPECULATIVE
|
|
)
|
|
record = PromotedTriple(
|
|
report_sha256=report.report_sha256, example=example, triple=triple
|
|
)
|
|
if example.outcome is ReviewOutcome.ACCEPTED:
|
|
promoted.append(record)
|
|
else:
|
|
rejected.append(record)
|
|
|
|
# Register in the index even if some triples were rejected by the review
|
|
# path — the report itself is ratified, and individual triple rejections
|
|
# are audit data, not a failure of the course.
|
|
index.add(MasteredCourseEntry(
|
|
course_id=report.course_id,
|
|
report_sha256=report.report_sha256,
|
|
issued_at=report.issued_at,
|
|
course_sha256=report.course_sha256,
|
|
validated_set_sha=report.validated_set_sha,
|
|
))
|
|
|
|
return PromotionResult(
|
|
report_sha256=report.report_sha256,
|
|
course_id=report.course_id,
|
|
promoted=tuple(promoted),
|
|
rejected=tuple(rejected),
|
|
idempotent_skipped=False,
|
|
)
|