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

95 lines
2.9 KiB
Python

"""Candidate dataclasses + ``CandidateState`` lifecycle for the Forge.
A candidate's lifecycle inside the Forge is:
PROPOSED -- candidate just out of the Smelter; untrusted.
QUARANTINED -- failed a Forge rule; retained for audit, not promoted.
VALIDATED -- passed every Forge rule; emitted with
``EpistemicStatus.SPECULATIVE`` into a ``ValidatedTripleSet``.
This is deliberately disjoint from ``teaching.epistemic.EpistemicStatus``.
``EpistemicStatus`` is a position in the reviewed-revision graph; it does not
include an "unverified" pre-state. ``CandidateState`` lives entirely
upstream of that enum — once a candidate is ``VALIDATED`` it crosses the
trust boundary and acquires ``EpistemicStatus.SPECULATIVE``. See ADR-0021.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum, unique
@unique
class CandidateState(Enum):
"""Lifecycle position of a candidate inside the Forge."""
PROPOSED = "proposed"
QUARANTINED = "quarantined"
VALIDATED = "validated"
@dataclass(frozen=True, slots=True)
class SourceRef:
"""Pointer to the source span that produced a candidate.
``source_sha`` is the SHA-256 of the source bundle entry (a URL+bytes
pair captured at mining time). ``span`` is a quoted excerpt so a human
reviewer can verify attribution without re-fetching. ``adapter`` names
the adapter that produced the candidate (e.g. ``"wikipedia"``,
``"user_documents"``, ``"llm_ideation/claude-opus-4-7"``).
"""
source_sha: str
span: str
adapter: str
retrieved_at: str # ISO-8601 UTC string; floats forbidden in canonical JSON
@dataclass(frozen=True, slots=True)
class ConceptCandidate:
"""A canonical term with a definition and >=1 source citation."""
canonical_term: str
definition: str
sources: tuple[SourceRef, ...]
state: CandidateState = CandidateState.PROPOSED
rejection_reason: str = ""
@dataclass(frozen=True, slots=True)
class RelationCandidate:
"""A typed (head, relation, tail) triple with source attribution."""
head: str
relation: str
tail: str
sources: tuple[SourceRef, ...]
state: CandidateState = CandidateState.PROPOSED
rejection_reason: str = ""
@dataclass(frozen=True, slots=True)
class CounterCandidate:
"""A claim labelled false-but-plausible, mined from errata/corrections."""
head: str
relation: str
tail: str
sources: tuple[SourceRef, ...]
state: CandidateState = CandidateState.PROPOSED
rejection_reason: str = ""
@dataclass(frozen=True, slots=True)
class OrderingHint:
"""Prerequisite signal between two concepts.
Mined from textbook chapter ordering, paper dependency graphs, etc.
``before`` should be introduced prior to ``after`` during Phase III
walks.
"""
before: str
after: str
sources: tuple[SourceRef, ...] = field(default_factory=tuple)