feat(evals,tests): Perception Arc Phase 0 — the read-rate instrument and floor ratchet (G-21, G-24)
The 1.28% is now an instrument instead of testimony. evals/perception/read_rate.py measures the logic reader over holdout_dev/v1 deterministically (23/1,798 sentences, refusal taxonomy included, 0.57s); tests/test_read_rate_floor.py pins it in both directions — a comprehension regression fails the gate, and an improvement must move the recorded constants in the same reviewed commit, so capability motion is a decision with a diff, never an accident with a story. Non-vacuity guarded (the matcher must still say no; counts must conserve). Two sabotages observed red. Registered in smoke (~0.8s). This is one of exactly two old-plan items that survive the keel triage (the other is FA-1): it is the baseline number the keel's K4 perception layer is accountable for moving in chunks. PR-8, PR-10, Track B widening and Track C are superseded by keel phases K6/K6/K4/K5 and are not being finished here — finishing them would be polishing the quarry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
This commit is contained in:
parent
64ecad00cf
commit
472fc0a83f
4 changed files with 186 additions and 0 deletions
|
|
@ -156,6 +156,13 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
||||||
# ON surface being the only part where staleness is immediately dangerous.
|
# ON surface being the only part where staleness is immediately dangerous.
|
||||||
# Four sabotages observed red. Pure file parsing, <1s.
|
# Four sabotages observed red. Pure file parsing, <1s.
|
||||||
"tests/test_flag_register.py",
|
"tests/test_flag_register.py",
|
||||||
|
# Perception Arc Phase 0 / G-21 / G-24 — the read-rate floor. 23 of 1,798
|
||||||
|
# holdout sentences read (1.28%): the binding constraint every substrate
|
||||||
|
# experiment independently hit, and the baseline the keel's perception
|
||||||
|
# layer is accountable for moving in chunks. Both directions pinned: a
|
||||||
|
# regression fails; an improvement must move the recorded constants in
|
||||||
|
# the same reviewed commit. Deterministic, ~0.8s.
|
||||||
|
"tests/test_read_rate_floor.py",
|
||||||
# PR-11 / G-5 / R-9 — the soak's evidence is committed, pinned, and CURRENT.
|
# PR-11 / G-5 / R-9 — the soak's evidence is committed, pinned, and CURRENT.
|
||||||
# Two failure modes, deliberately separated: the digest catches a regression,
|
# Two failure modes, deliberately separated: the digest catches a regression,
|
||||||
# and the attested-source hashes catch STALENESS — evidence that no longer
|
# and the attested-source hashes catch STALENESS — evidence that no longer
|
||||||
|
|
|
||||||
1
evals/perception/__init__.py
Normal file
1
evals/perception/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Perception measurement lane — the read-rate instrument (Perception Arc Phase 0, G-21/G-24)."""
|
||||||
97
evals/perception/read_rate.py
Normal file
97
evals/perception/read_rate.py
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
"""The read-rate instrument — what fraction of real English the reader actually reads.
|
||||||
|
|
||||||
|
Perception Arc Phase 0 (G-21's open ask, G-24's headline number, the keel's baseline).
|
||||||
|
|
||||||
|
The 2026-07-28 diagnosis measured, ad hoc, that ``generate.meaning_graph.reader``
|
||||||
|
reads **23 of 1,798** sentences of `holdout_dev/v1` (1.28%), with 93.16% failing as
|
||||||
|
``no_template_match`` — upstream of vocabulary, upstream of every flag. That number
|
||||||
|
is the binding constraint every substrate experiment independently hit, and it is
|
||||||
|
the metric the keel's perception layer (K4) is accountable for moving in chunks.
|
||||||
|
|
||||||
|
An ad-hoc measurement is testimony. This module is the instrument: deterministic,
|
||||||
|
committed, and pinned by ``tests/test_read_rate_floor.py`` as a floor ratchet — the
|
||||||
|
recorded counts may only be updated deliberately, and only upward for ``read``.
|
||||||
|
|
||||||
|
Deliberately narrow: the LOGIC reader at sentence granularity. The math reader's
|
||||||
|
decision rate (5/500 holdout cases, G-21) is recorded where it was measured
|
||||||
|
(`docs/analysis/relational-operator-ablation-dossier-2026-07-19.md`, split table)
|
||||||
|
and will get its own lane if it ever moves; folding a slow full-solve sweep into
|
||||||
|
this fast lane would couple two instruments that fail differently.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from collections import Counter
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from generate.meaning_graph.reader import Refusal, _split_sentences, comprehend
|
||||||
|
|
||||||
|
_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
CORPUS = _ROOT / "evals" / "gsm8k_math" / "holdout_dev" / "v1" / "cases.jsonl"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ReadRateReport:
|
||||||
|
corpus: str
|
||||||
|
problems: int
|
||||||
|
sentences: int
|
||||||
|
read: int
|
||||||
|
refusals: tuple[tuple[str, int], ...] # (reason, count), descending
|
||||||
|
|
||||||
|
@property
|
||||||
|
def read_rate(self) -> float:
|
||||||
|
return self.read / self.sentences if self.sentences else 0.0
|
||||||
|
|
||||||
|
def as_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"corpus": self.corpus,
|
||||||
|
"problems": self.problems,
|
||||||
|
"sentences": self.sentences,
|
||||||
|
"read": self.read,
|
||||||
|
"read_rate": round(self.read_rate, 6),
|
||||||
|
"refusals": dict(self.refusals),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def measure(corpus_path: Path = CORPUS) -> ReadRateReport:
|
||||||
|
"""Run the logic reader over every sentence of the corpus. Deterministic."""
|
||||||
|
problems = [json.loads(line) for line in corpus_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||||
|
reasons: Counter[str] = Counter()
|
||||||
|
read = 0
|
||||||
|
sentences = 0
|
||||||
|
for case in problems:
|
||||||
|
for sentence, *_rest in _split_sentences(case["problem"]):
|
||||||
|
sentence = sentence.strip()
|
||||||
|
if not sentence:
|
||||||
|
continue
|
||||||
|
sentences += 1
|
||||||
|
try:
|
||||||
|
result = comprehend(sentence)
|
||||||
|
except Exception as exc: # a crashing reader is a finding, not a skip
|
||||||
|
reasons[f"EXCEPTION:{type(exc).__name__}"] += 1
|
||||||
|
continue
|
||||||
|
if isinstance(result, Refusal):
|
||||||
|
reasons[result.reason] += 1
|
||||||
|
elif result is None:
|
||||||
|
reasons["returned_none"] += 1
|
||||||
|
else:
|
||||||
|
read += 1
|
||||||
|
return ReadRateReport(
|
||||||
|
corpus=str(corpus_path.relative_to(_ROOT)),
|
||||||
|
problems=len(problems),
|
||||||
|
sentences=sentences,
|
||||||
|
read=read,
|
||||||
|
refusals=tuple(sorted(reasons.items(), key=lambda kv: (-kv[1], kv[0]))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
report = measure()
|
||||||
|
print(json.dumps(report.as_dict(), indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
81
tests/test_read_rate_floor.py
Normal file
81
tests/test_read_rate_floor.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
"""Perception Arc Phase 0 — the read-rate floor ratchet (G-21, G-24, keel baseline).
|
||||||
|
|
||||||
|
The reader decides **23 of 1,798** sentences of `holdout_dev/v1` (1.28%). That number
|
||||||
|
is the capability constraint every substrate experiment independently hit (wedge,
|
||||||
|
operator ablation, §5 — all starved by the reader), the headline of G-24, and the
|
||||||
|
baseline the keel's perception layer (K4) is accountable for moving **in chunks**.
|
||||||
|
|
||||||
|
This pin makes the number an instrument instead of testimony, with the same
|
||||||
|
both-directions discipline as every honest baseline in this repo:
|
||||||
|
|
||||||
|
* ``read`` may not FALL below the recorded floor — a silent comprehension
|
||||||
|
regression in the serving reader fails the gate;
|
||||||
|
* the recorded counts may not silently DRIFT from the measurement — if read-rate
|
||||||
|
rises (a reader change, a keel absorption), the constants here move in the same
|
||||||
|
reviewed commit, so capability motion is a decision with a diff, never an
|
||||||
|
accident with a story.
|
||||||
|
|
||||||
|
Corpus and reader are deterministic; the counts are exact, not tolerances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from evals.perception.read_rate import CORPUS, measure
|
||||||
|
|
||||||
|
#: Measured 2026-07-28 at `64ecad00` — the diagnosis baseline (G-24).
|
||||||
|
FLOOR_READ = 23
|
||||||
|
RECORDED_SENTENCES = 1798
|
||||||
|
RECORDED_PROBLEMS = 500
|
||||||
|
#: The dominant failure, pinned so the *shape* of the gap cannot drift unnoticed:
|
||||||
|
#: 93.16% of sentences die before vocabulary — upstream of packs, upstream of flags.
|
||||||
|
RECORDED_NO_TEMPLATE_MATCH = 1675
|
||||||
|
|
||||||
|
|
||||||
|
def test_corpus_is_the_declared_one() -> None:
|
||||||
|
"""The floor means nothing if the corpus quietly changes underneath it."""
|
||||||
|
assert CORPUS.exists(), "holdout_dev/v1 moved — re-anchor the instrument first"
|
||||||
|
report = measure()
|
||||||
|
assert report.problems == RECORDED_PROBLEMS, (
|
||||||
|
f"corpus changed shape: {report.problems} problems vs {RECORDED_PROBLEMS} recorded — "
|
||||||
|
"this pin measures the reader, not the corpus; re-baseline deliberately"
|
||||||
|
)
|
||||||
|
assert report.sentences == RECORDED_SENTENCES, (
|
||||||
|
f"sentence split moved: {report.sentences} vs {RECORDED_SENTENCES} — either the "
|
||||||
|
"splitter changed or the corpus did; both are reviewed decisions"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_rate_holds_the_floor_and_the_record() -> None:
|
||||||
|
report = measure()
|
||||||
|
assert report.read >= FLOOR_READ, (
|
||||||
|
f"READ-RATE REGRESSION: {report.read} sentences read, floor is {FLOOR_READ}. "
|
||||||
|
"The serving reader lost comprehension it had."
|
||||||
|
)
|
||||||
|
assert report.read == FLOOR_READ, (
|
||||||
|
f"read-rate MOVED UP: {report.read} vs recorded {FLOOR_READ}. Good news is still "
|
||||||
|
"a reviewed decision — update FLOOR_READ in this commit so the record matches "
|
||||||
|
"reality (the G-22 lesson: two agreeing stale records are not evidence)."
|
||||||
|
)
|
||||||
|
refusals = dict(report.refusals)
|
||||||
|
assert refusals.get("no_template_match") == RECORDED_NO_TEMPLATE_MATCH, (
|
||||||
|
f"the gap's shape moved: no_template_match={refusals.get('no_template_match')} "
|
||||||
|
f"vs {RECORDED_NO_TEMPLATE_MATCH} recorded — re-baseline deliberately"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_instrument_is_not_vacuous() -> None:
|
||||||
|
"""A measurement that cannot fail is not a measurement (R-11's lesson).
|
||||||
|
|
||||||
|
The matcher must still say *no*: the overwhelming majority of real English must
|
||||||
|
be refused by the template reader, or the instrument (or reader) changed class
|
||||||
|
entirely and every number above means something different.
|
||||||
|
"""
|
||||||
|
report = measure()
|
||||||
|
assert report.sentences > 1000, "corpus too small to mean anything"
|
||||||
|
assert report.read < report.sentences * 0.5, (
|
||||||
|
"the template reader reads >50% of GSM8K English — that is not this reader; "
|
||||||
|
"the instrument is measuring something else"
|
||||||
|
)
|
||||||
|
assert sum(dict(report.refusals).values()) + report.read == report.sentences, (
|
||||||
|
"counts don't conserve — the instrument dropped sentences silently"
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue