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

61 lines
2.1 KiB
Python

"""Source allow-list with authority tiers.
The Forge consults this list to decide whether a candidate's source SHAs are
permitted to feed the manifold at all, and whether any single source has
enough authority to graduate a candidate alone.
Two tiers:
"primary" — authoritative source (textbook, peer-reviewed paper, etc.)
; a single primary citation suffices to validate a triple.
"secondary" — non-authoritative but admissible (e.g. Wikipedia, blog)
; two independent secondary citations are required.
LLM-sourced candidates are always treated as ``"llm"`` regardless of which
model produced them. They require ≥2 non-LLM corroborators.
Identity-override and adversarial-pattern entries are not part of this list —
those are handled separately in ``formation.forge._identity_axis_collision``.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Final
@dataclass(frozen=True, slots=True)
class AllowedSource:
source_sha: str
tier: str # "primary" | "secondary" | "llm"
label: str = ""
_VALID_TIERS: Final[frozenset[str]] = frozenset({"primary", "secondary", "llm"})
class SourceAllowlist:
"""In-memory allow-list keyed by source SHA."""
def __init__(self, entries: tuple[AllowedSource, ...] = ()) -> None:
for entry in entries:
if entry.tier not in _VALID_TIERS:
raise ValueError(
f"invalid tier {entry.tier!r}; allowed: {sorted(_VALID_TIERS)}"
)
self._by_sha: dict[str, AllowedSource] = {e.source_sha: e for e in entries}
def tier_of(self, source_sha: str) -> str | None:
entry = self._by_sha.get(source_sha)
return entry.tier if entry else None
def contains(self, source_sha: str) -> bool:
return source_sha in self._by_sha
def add(self, entry: AllowedSource) -> None:
if entry.tier not in _VALID_TIERS:
raise ValueError(f"invalid tier {entry.tier!r}")
self._by_sha[entry.source_sha] = entry
def __len__(self) -> int:
return len(self._by_sha)