Rank 5 on the docket, and the first item this arc whose expected outcome is LOSING capability. conservative_floor is a one-sided Wilson lower bound, and Wilson assumes independent trials. CORE's pipeline is deterministic, so replaying an identical case is not a second trial — it is the same trial with a guaranteed outcome. The deduction sealer folded the raw practice corpus and counted every replay, so bands recorded 720/720 committed whether that was 720 decisions or 28 decisions seen 26 times each. conservative_floor(720,720)=0.990868 cleared theta_SERVE=0.99; conservative_floor(28,28)=0.808413 is not close. RESULT: 25 licensed bands -> 4. Twenty-one demoted. Survivors: en_conditional_chain, en_disjunctive, en_verb_fact, en_verb_universal — the only bands whose corpus holds enough independent cases. What the demotion did and did not do, stated exactly. NO ANSWER CHANGED. No answer became wrong. `wrong` stayed 0 across all 25 bands. The engine is exactly as correct as it was. What changed is the CLAIM attached to the answer: 21 bands now serve the same sound conclusion prefixed with "(reasoned, but I haven't yet earned a verified track record on arguments of this shape)". The reasoning did not get worse; the boast did. The producer is hardened, which is the half that stops this recurring. seal_ledger now refuses outright — assert_sealed_evidence_distinct compares the ledger about to be written against the corpus's distinct-case count and raises BEFORE anything is written. The curriculum sealer has carried this guarantee since ADR-0264 R9; the deduction sealer had no equivalent, which is exactly how the exposure arose. Catching it at seal time rather than in an audit matters: an audit finds a padded ledger after it is committed, trusted, and gating a live flag, and unwinding that then needs a ruling — it needed one. A HOLLOW GUARD OF MY OWN, caught by sabotage rather than trusted. The first version compared all_gold_problems() with distinct_gold_problems() — two functions that agree with each other by construction, and neither of which is what build_ledger folds. Reverting build_ledger to the raw corpus walked straight past it AND re-sealed the inflated artifact. Same defect class the ledger already suffered: a guard checking something adjacent to the thing it protects. It now takes the BUILT LEDGER as input, because the invariant is about the artifact. Re-running the identical attack: refused, wrote nothing. Pinned as test_sealer_refuses_a_ledger_that_counts_replays. A SECOND MISS, caught by the gate rather than by me. My blast-radius scan grepped for deduction_serve_license / deduction_serve_ledger / 720 and found nine files. It missed tests/test_construction_inventory.py, which asserts deduction SURFACE STRINGS and never mentions the ledger — so a pattern search over the wrong noun could not see it. The full gate found it. Recorded because "I scanned for the blast radius" is worth exactly as much as the scan's key, and mine was the wrong key. That test is the #138 fabrication defect pin, and it is updated WITHOUT being weakened. Both surfaces now carry the disclosure prefix, and the fabrication survives untouched — which is a sharper finding than before: a disclosure prefix looks like it might cover the hazard, and it does not. The user is still told they said "furthermore". Exact-match assertions kept on the full strings so neither the fabrication nor the licence state can drift unnoticed, plus an isolating assertion that the only difference between clean and leaked recitals is the fabricated premise. Four records corrected in the same PR, each of which would otherwise have gone on asserting capability that no longer exists: - CAPABILITY_LEDGERS' manifest note ("25 bands at 720/720 wrong=0") - the published CLAIMS.md claim — caught immediately by G-22's own pin, which is that fix earning its keep three days after landing - test_volume_honesty.py's module docstring, which said the exposure was "NOT silently repaired ... changing it is Shay's ratification, not a test's". R-13 IS that ratification, so the line went stale by being acted on - the exposure inventory's framing: it now pins the repair, not the gap test_volume_honesty's sealed-vs-producer assertion is strictly stronger than it was. It compared sealed committed counts to the producer's committed counts and both were the inflated 720 — the assertion held perfectly while the number it agreed on was the wrong number. Two records agreeing is not evidence that either is right. It now pins committed == distinct, plus a non-vacuity check. Nine tests encoded the unearned expectation and were updated to the honest one. Closes G-19 and H-1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
338 lines
15 KiB
Python
338 lines
15 KiB
Python
"""Pins for the construction-inventory lane (grammar-unification Phase 5, item 1).
|
|
|
|
Three kinds of test live here, and the difference matters:
|
|
|
|
**Measurements** record what the two inventories currently share. ``overlap`` is
|
|
ratcheted *upward* — that is the number Phase 5 exists to raise.
|
|
|
|
**Defect pins** record something that is wrong. Each says so in its docstring and
|
|
states which direction it may be revised. ``w2r_fabricating`` may only be
|
|
revised *downward*; raising it to accommodate a new fabrication would be
|
|
recording a regression as progress.
|
|
|
|
**Structural guards** are AST-keyed so the tables cannot rot. A table of "what
|
|
the reader reads" that someone must remember to update is worth nothing — the
|
|
guards make forgetting fail loudly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from generate.graph_planner import RhetoricalMove
|
|
from generate.meaning_graph.reader import Refusal, comprehend
|
|
from generate.templates import render_step
|
|
|
|
from evals.construction_inventory.corpora import COUNT_FILLERS, MASS_FILLER, SECONDARY
|
|
from evals.construction_inventory.reader_space import (
|
|
READER_CONSTRUCTIONS,
|
|
READER_MINT_SITES,
|
|
render_instance,
|
|
)
|
|
from evals.construction_inventory.runner import run_lane
|
|
from evals.construction_inventory.skeleton import skeleton
|
|
from evals.construction_inventory.writer_space import WRITER_ASPECTS, WRITER_TENSES
|
|
|
|
_REPO = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Measurements
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_overlap_is_six_constructions() -> None:
|
|
"""The headline. Phase 5 item 1 raises this; it may be revised UPWARD only.
|
|
|
|
Six constructions are shared: ``X is a Y``, ``the X is a Y``, and the four
|
|
categoricals (``all`` / ``no`` / ``some`` / ``some ... not``). All six are
|
|
set-theoretic. The writer cannot express a single ordering, propositional or
|
|
interrogative construction, and the reader has no template for the bare
|
|
transitive clause that is the writer's entire default output.
|
|
"""
|
|
metrics = run_lane().metrics
|
|
assert metrics["overlap_constructions"] == 6
|
|
assert metrics["w2r_faithful"] == 6
|
|
assert metrics["r2w_expressible"] == 6
|
|
|
|
|
|
def test_the_overlap_is_the_same_six_seen_from_both_sides() -> None:
|
|
"""Internal consistency: the two directions must name the same set.
|
|
|
|
``w2r_faithful`` counts writer constructions the reader reads truly;
|
|
``r2w_expressible`` counts reader constructions the writer can emit. These
|
|
are computed by different code over different corpora, and if they ever
|
|
disagree, one of the two inventories is measuring something else.
|
|
"""
|
|
report = run_lane()
|
|
faithful = {
|
|
d["skeleton"] for d in report.case_details
|
|
if d.get("kind") == "writer_construction" and d["bucket"] == "faithful"
|
|
}
|
|
expressible = {
|
|
d["skeleton"] for d in report.case_details
|
|
if d.get("kind") == "reader_construction" and d["writer_can_emit"]
|
|
}
|
|
assert faithful == expressible
|
|
|
|
|
|
def test_reader_side_rate_is_pinned() -> None:
|
|
"""The reader's own inventory is 19 constructions; the writer reaches 6."""
|
|
metrics = run_lane().metrics
|
|
assert metrics["reader_constructions"] == 19
|
|
assert metrics["r2w_rate"] == 0.315789
|
|
|
|
|
|
def test_the_shared_six_do_not_depend_on_vocabulary() -> None:
|
|
"""Morphology control: the overlap must be the same under every filler.
|
|
|
|
If a construction reads for ``dogs`` but not for ``wolves`` or ``children``,
|
|
the lane would be reporting a morphology gap as a construction gap. Zero
|
|
filler-dependence is what licenses calling this a *construction* count.
|
|
"""
|
|
assert run_lane().metrics["w2r_filler_dependent"] == 0
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Defect pins
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_reader_fabricates_on_twenty_two_writer_constructions() -> None:
|
|
"""DEFECT PIN. Revise DOWNWARD only — never up to admit a new fabrication.
|
|
|
|
On these the reader neither reads nor refuses: it accepts the surface and
|
|
mints an argument the writer never wrote. That breaks the reader's own
|
|
stated contract ("a fabricated reading is the only failure") and it is on a
|
|
serving path, so it is a ``wrong=0`` hazard, not a cosmetic one.
|
|
"""
|
|
metrics = run_lane().metrics
|
|
assert metrics["w2r_fabricating"] == 22
|
|
assert metrics["cells_fabricating"] == 150
|
|
|
|
|
|
def test_reader_invents_propositions_from_ordinary_english() -> None:
|
|
"""DEFECT PIN. These are user-typable sentences, not writer artefacts.
|
|
|
|
``every``/``each`` are not in ``_RESERVED``, so they are chunked into the
|
|
entity id and the asserted fact is about an entity that does not exist. A
|
|
later query about ``dog`` then answers UNKNOWN even though the user said it.
|
|
"""
|
|
reading = comprehend("every dog is a mammal")
|
|
assert not isinstance(reading, Refusal)
|
|
assert [tuple(r.arguments) for r in reading.meaning_graph.relations] == [
|
|
("every_dog", "mammal")
|
|
]
|
|
|
|
reading = comprehend("each metal is a conductor")
|
|
assert not isinstance(reading, Refusal)
|
|
assert [tuple(r.arguments) for r in reading.meaning_graph.relations] == [
|
|
("each_metal", "conductor")
|
|
]
|
|
|
|
|
|
def test_reader_promotes_a_discourse_connective_to_a_proposition() -> None:
|
|
"""DEFECT PIN. ``_parse_propositional`` accepts ANY single token as a fact.
|
|
|
|
``_split_clauses`` splits on commas and strips only a leading ``and``/``or``,
|
|
so a one-word discourse opener becomes its own clause, and the bare-atom rule
|
|
mints it. CORE then recites it back to the user as a premise — see
|
|
``test_fabricated_premise_reaches_a_served_deduction_surface``.
|
|
"""
|
|
reading = comprehend("furthermore, all dogs are mammals")
|
|
assert not isinstance(reading, Refusal)
|
|
minted = [(r.predicate, tuple(r.arguments)) for r in reading.meaning_graph.relations]
|
|
assert ("asserted", ("furthermore",)) in minted
|
|
|
|
|
|
def test_fabricated_premise_reaches_a_served_deduction_surface() -> None:
|
|
"""DEFECT PIN — this is the one that shows the hazard is not theoretical.
|
|
|
|
The verdict is unaffected here, but the *recital* is: CORE tells the user
|
|
they said ``furthermore``. Revise only by removing the fabrication.
|
|
|
|
**Updated 2026-07-28 (R-13), and deliberately not weakened.** The re-count
|
|
demoted ``conditional_single``, so both surfaces now carry the unverified-shape
|
|
disclosure. The prefix is licensing, not comprehension: it says *"I have not
|
|
earned a track record on this argument shape"*, which is a true statement about
|
|
evidence volume and says nothing about whether the recited premises are real.
|
|
**The fabrication survives the demotion untouched** — that is the finding, and it
|
|
is sharper now than before: a disclosure prefix might look like it covers the
|
|
hazard, and it does not. The user is still told they said ``furthermore``.
|
|
|
|
The assertion therefore stays exact-match on the full string, including the
|
|
prefix, so neither the fabrication nor the licence state can drift unnoticed.
|
|
"""
|
|
from chat.deduction_surface import deduction_grounded_surface
|
|
|
|
disclosure = (
|
|
"(reasoned, but I haven't yet earned a verified track record on "
|
|
"arguments of this shape) "
|
|
)
|
|
clean = deduction_grounded_surface("if p then q. p. therefore q.")
|
|
leaked = deduction_grounded_surface("furthermore, if p then q. p. therefore q.")
|
|
|
|
assert clean == disclosure + "Given: p implies q; p. Your premises entail: q."
|
|
assert leaked == disclosure + "Given: furthermore; p implies q; p. Your premises entail: q."
|
|
|
|
# The defect, isolated from the licence state: the ONLY difference between a
|
|
# clean and a leaked recital is the fabricated premise. If this ever passes
|
|
# because both surfaces became refusals, the exact-match assertions above fail
|
|
# first — which is why they are kept rather than replaced by this.
|
|
assert leaked.replace("furthermore; ", "") == clean
|
|
|
|
|
|
def test_aspect_arms_drop_negation() -> None:
|
|
"""DEFECT PIN — ADR-0265's defect class, surviving in the module ADR-0265
|
|
named as the single owner of clause grammar.
|
|
|
|
The four aspect arms of ``_inflect_predicate`` match on ``(aspect, ...)``
|
|
with ``negated`` bound to a wildcard and never consult it, so a denial is
|
|
rendered as its own assertion. ADR-0265's invariant is *structural* — it
|
|
checks that ``negated`` is threaded to each call — and cannot see an arm that
|
|
receives the flag and ignores it. This pin is behavioural and can.
|
|
|
|
Not reachable today: no producer sets ``aspect`` on the serving path (only
|
|
``generate/realizer.py`` forwards ``step.aspect``, which is always None). It
|
|
is a loaded gun, not a casualty — the distinction ADR-0265 got wrong once
|
|
already, so it is stated explicitly rather than assumed.
|
|
|
|
Revise DOWNWARD only; ``0`` is the fixed state.
|
|
"""
|
|
assert run_lane().metrics["negation_collapsed_points"] == 10530
|
|
|
|
for aspect in ("perfective", "imperfective"):
|
|
affirmed = render_step(
|
|
RhetoricalMove.ASSERT, "dog", "is_defined_as", "mammal",
|
|
negated=False, aspect=aspect,
|
|
)
|
|
denied = render_step(
|
|
RhetoricalMove.ASSERT, "dog", "is_defined_as", "mammal",
|
|
negated=True, aspect=aspect,
|
|
)
|
|
assert affirmed == denied, f"aspect={aspect} now distinguishes polarity — revise DOWN"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Structural guards
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def _mint_sites(path: Path) -> list[tuple[str, int]]:
|
|
"""Every ``relations.append`` / ``queries.append`` in *path*."""
|
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
found: list[tuple[str, int]] = []
|
|
for node in ast.walk(tree):
|
|
if (
|
|
isinstance(node, ast.Call)
|
|
and isinstance(node.func, ast.Attribute)
|
|
and node.func.attr == "append"
|
|
and isinstance(node.func.value, ast.Name)
|
|
and node.func.value.id in ("relations", "queries")
|
|
):
|
|
found.append((node.func.value.id, node.lineno))
|
|
return found
|
|
|
|
|
|
def test_reader_inventory_covers_every_mint_site() -> None:
|
|
"""STRUCTURAL GUARD. A new reader template must be entered in the table.
|
|
|
|
A ``Comprehension`` can only be populated through a ``relations.append`` or
|
|
``queries.append`` call, so the count of those calls bounds the number of
|
|
constructions. When someone adds a template, this goes red until
|
|
``READER_CONSTRUCTIONS`` names its mint site — which is the only thing
|
|
keeping the overlap number honest as the reader grows.
|
|
"""
|
|
sites = _mint_sites(_REPO / "generate" / "meaning_graph" / "reader.py")
|
|
assert len(sites) == 10, f"reader mint sites changed: {sites}"
|
|
assert len(READER_MINT_SITES) == 10
|
|
|
|
|
|
def test_every_reader_construction_is_read_by_the_reader() -> None:
|
|
"""STRUCTURAL GUARD. The table must describe the reader, not an aspiration.
|
|
|
|
A construction the reader refuses would inflate ``reader_constructions`` and
|
|
depress ``r2w_rate`` for a reason that has nothing to do with the writer.
|
|
"""
|
|
for construction in READER_CONSTRUCTIONS:
|
|
for filler in COUNT_FILLERS:
|
|
instance = render_instance(construction, filler)
|
|
reading = comprehend(instance)
|
|
assert not isinstance(reading, Refusal), (
|
|
f"{construction.construction_id} under {filler.name}: "
|
|
f"{instance!r} -> {reading}"
|
|
)
|
|
|
|
|
|
def _match_arm_literals(path: Path, function: str) -> set[str]:
|
|
"""String literals appearing in *function*'s ``match`` case PATTERNS."""
|
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
literals: set[str] = set()
|
|
for node in ast.walk(tree):
|
|
if not (isinstance(node, ast.FunctionDef) and node.name == function):
|
|
continue
|
|
for stmt in ast.walk(node):
|
|
if not isinstance(stmt, ast.Match):
|
|
continue
|
|
for case in stmt.cases:
|
|
for pat in ast.walk(case.pattern):
|
|
if isinstance(pat, ast.MatchValue) and isinstance(pat.value, ast.Constant):
|
|
if isinstance(pat.value.value, str):
|
|
literals.add(pat.value.value)
|
|
return literals
|
|
|
|
|
|
def test_declared_writer_axes_cover_the_inflection_arms() -> None:
|
|
"""STRUCTURAL GUARD. The sweep must not miss a tense or aspect.
|
|
|
|
``WRITER_TENSES`` / ``WRITER_ASPECTS`` are declared for readability, so they
|
|
are pinned against the arms of ``_inflect_predicate``. A new aspect added to
|
|
the writer without being added to the sweep would silently shrink the space
|
|
this lane claims to measure.
|
|
"""
|
|
literals = _match_arm_literals(_REPO / "generate" / "templates.py", "_inflect_predicate")
|
|
declared = {v for v in (*WRITER_TENSES, *WRITER_ASPECTS) if v is not None}
|
|
assert literals - declared == set(), f"undeclared inflection axis values: {literals - declared}"
|
|
|
|
|
|
def test_committed_corpus_matches_the_fillers_in_use() -> None:
|
|
"""STRUCTURAL GUARD. The reviewable corpus must be the one that ran.
|
|
|
|
``corpora.py`` holds the fillers as typed objects and ``public/v1/cases.jsonl``
|
|
is what a reviewer reads. Two copies of one fact is how this codebase got two
|
|
grammars; since the runner cannot import a JSON file as a dataclass without
|
|
losing the typing, they stay separate and this guard keeps them identical.
|
|
"""
|
|
path = _REPO / "evals" / "construction_inventory" / "public" / "v1" / "cases.jsonl"
|
|
committed = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
|
|
|
in_use = [
|
|
{
|
|
"id": f"filler-{f.name.replace('_', '-')}",
|
|
"kind": "count_filler" if f in COUNT_FILLERS else "mass_filler",
|
|
"x": f.x, "y": f.y,
|
|
"plural_x": f.plural_x, "plural_y": f.plural_y,
|
|
"secondary": SECONDARY[f.name],
|
|
}
|
|
for f in (*COUNT_FILLERS, MASS_FILLER)
|
|
]
|
|
assert [{k: v for k, v in row.items() if k != "note"} for row in committed] == in_use
|
|
|
|
|
|
def test_writer_emits_no_interrogative_construction() -> None:
|
|
"""STRUCTURAL GUARD. Three reader query templates are unreachable BY SHAPE.
|
|
|
|
``member_query`` and ``subset_query`` need a ``?``; no writer template
|
|
contains one, so no amount of vocabulary work reaches them. Recording this
|
|
as a shape fact keeps Phase 5 from trying to close that gap with corpora.
|
|
"""
|
|
report = run_lane()
|
|
writer_skeletons = {
|
|
d["skeleton"] for d in report.case_details if d.get("kind") == "writer_construction"
|
|
}
|
|
assert not any("?" in sk for sk in writer_skeletons)
|
|
|
|
interrogative = [c for c in READER_CONSTRUCTIONS if c.interrogative]
|
|
assert len(interrogative) == 2
|
|
for construction in interrogative:
|
|
instance = render_instance(construction, COUNT_FILLERS[0])
|
|
assert skeleton(instance) not in writer_skeletons
|