Phase 5 item 1 asked for the overlap to be sized "by measuring the reader's construction set against the writer's rather than by growing corpora blindly". New lane `evals/construction_inventory` does that: it sweeps the writer's whole parameter space (every RhetoricalMove x IntentTag x predicate x quantifier x tense x aspect, both public entry points, 32292 cells), quotients it by the reader's OWN function-word skeleton, and comprehends each construction under three vocabularies. writer constructions 1739 reader constructions 19 (mint-site AST-guarded) overlap, faithful 6 accepts but MIS-READS 22 refuses 1711 vocabulary-dependent 0 Faithfulness, not acceptance, is the criterion — and that reverses two claims in the plan's §6 RESULT, using a metric that was already on the page: * g_read_rate is 1/293 but **g_args_rate is 0.0**. The one surface that "reads", `all molecules are defined as compounds`, is comprehended as `subset(molecule, defined_as_compound)` — the reader chunked the writer's verb phrase into a class name. It accepted; it did not comprehend. * "one construction wide" was wrong both ways: zero on that corpus, six over the writer's actual output space. A corpus cannot report an inventory's size. Three findings re-order Phase 5 item 1: 1. The reader FABRICATES on 22 constructions — neither reads nor refuses. `every dog is a mammal` -> member(every_dog, mammal); `furthermore, all dogs are mammals` -> asserted(furthermore). Both are ordinary user English, not writer artefacts. Root cause: _RESERVED lacks the function words the writer emits, and _parse_propositional accepts any single token as a fact. 2. It reaches SERVED output. deduction_surface recites `Given: furthermore; p implies q; p.` — a premise the user never stated — and chat/runtime.py realizes declarative turns into the held self, so a fabricated atom is vault-writable. Widening the inventory first would widen the fabrication surface with it. 3. ADR-0265's defect class survives inside ADR-0265's designated owner of clause grammar: the four aspect arms of _inflect_predicate bind `negated` to a wildcard and never read it, so `dog has been defined as mammal` serves both the assertion and the denial (10530/16146 points). Unreachable today — no producer sets aspect — so a loaded gun, not a casualty. It survived because ADR-0265's invariant is structural (is `negated` threaded?) and cannot see an arm that receives the flag and ignores it. A behavioural sweep can. The two fixes are written already, as the mutations that turn the defect pins red. They are NOT applied here: they change what CORE comprehends from user input, which is a serving change on the truth path — authorization-gated, ADR first, on the ADR-0261 §5.1 refuse-don't-drop precedent. Guards, so the tables cannot rot: reader constructions are pinned to an AST count of the reader's 10 mint sites; the declared tense/aspect axes are pinned to _inflect_predicate's match arms; the committed corpus is pinned to the fillers in use. 14 pins, 13 mutations observed RED. [Verification]: in-worktree, CPython 3.12.13, uv sync --locked. deductive 517 (was 503, +14 — count moved, registration confirmed) smoke 641 (unchanged; the file is registered in `deductive`) lane SHAs 11/11 match, no pin edited pyright 0 errors on all new files mutations 13/13 red, including both fabrication fixes
8.1 KiB
Construction-Inventory Eval Lane — Contract
Lane: construction_inventory
Version: v1
Created: 2026-07-27
Plan: docs/plans/grammar-unification-2026-07-26.md (Phase 5, item 1)
What this lane measures
How many constructions the reader and the writer share — the quantity §6's RESULT identified as the arc's frontier, and asked to be measured directly "rather than by growing corpora blindly."
Why grammar_roundtrip could not answer this
grammar_roundtrip measures a corpus. Its G-direction reports
g_read_rate = 1/293, and §6 read that one case as the shared construction,
concluding the overlap was "one construction wide."
Both halves of that were wrong, and the lane's own metrics already said so:
g_args_rateis0.0. The one surface that "reads" —all molecules are defined as compounds— is comprehended assubset(molecule, defined_as_compound). The reader chunked the writer's verb phrase into a class name. It is a mis-read, so on that corpus the faithful overlap is zero, not one.- The true overlap is six, and the corpus never exercised five of them. 293 cases cannot report the size of an inventory; only the inventory can.
The lesson generalises: acceptance is not comprehension. A lane that counts
read without counting args will reward a reader for guessing.
The construction quotient
A construction is defined by the reader's own dispatch. reader.py reads
"STRUCTURE symbolically ... via templates keyed on FUNCTION WORDS + ORDER", so
replacing every non-function word with * yields a form that is, by the
reader's design, indistinguishable to its parser. The function-word set is read
off reader._RESERVED — the lane cannot pick a quotient that flatters a number,
and it widens automatically when the reader learns a word.
Known limit: _chunk_class refuses on unrecognized plurals, which depends on
the token rather than the shape. So the skeleton determines the verdict up to
morphology. The lane sweeps three count-noun vocabularies (regular, -f/-ves,
suppletive) and counts a construction as shared only when all three agree;
disagreement is reported as w2r_filler_dependent, currently 0.
The two directions
| direction | question | how |
|---|---|---|
| W → R | of everything the writer can emit, what does the reader read truly? | sweep the writer's whole parameter space, quotient to constructions, comprehend each |
| R → W | of everything the reader can read, what can the writer emit? | instantiate each reader construction, quotient, test membership in the writer's construction set |
The writer's space is enumerated from live source — RhetoricalMove,
IntentTag, PREDICATE_DISPLAY, PREDICATIVE_NOMINAL, PLURAL_QUANTIFIERS,
and the tense/aspect literals in _inflect_predicate's match arms (AST-pinned).
Nothing is a hand-copied list of "what the writer can say."
Both writer entry points are swept: render_semantic (the serving writer,
called by core/cognition/pipeline.py) and render_step (the clause owner it
delegates to, also reached by the eval-only realize_target).
Acceptance is split three ways
| bucket | meaning |
|---|---|
faithful |
the reader recovered the writer's proposition and minted nothing else — the only bucket that counts as overlap |
fabricating |
accepted, but minted an argument the writer never wrote, or manufactured a query from a declarative |
refused |
the honest outcome: no template, and the reader says so |
Metrics
| metric | definition |
|---|---|
writer_constructions |
distinct constructions the writer can emit |
overlap_constructions |
shared and faithful under every count-noun filler — the Phase 5 number |
w2r_fabricating |
writer constructions the reader mis-reads — a wrong=0 hazard, ratcheted down |
w2r_filler_dependent |
shared under some vocabularies but not others (morphology gaps) |
reader_constructions |
the reader's inventory, guarded against rot by mint-site AST count |
r2w_expressible |
reader constructions the writer can emit |
negation_collapsed_points |
writer parameter points whose denial is byte-identical to its assertion — ADR-0265 sentinel, ratcheted down |
v1 baseline — measured on main @ 8927c563
writer_cells 32292
writer_constructions 1739
w2r_faithful 6
w2r_fabricating 22
w2r_refused 1711
w2r_filler_dependent 0
w2r_faithful_rate 0.00345
cells_faithful 12
cells_fabricating 150
reader_constructions 19
r2w_expressible 6
r2w_rate 0.315789
reader_interrogative_constructions 2
overlap_constructions 6 <-- the Phase 5 number
negation_collapsed_points 10530 <-- defect, see below
What this lane found
1. The overlap is six, and all six are set-theoretic.
X is a Y, the X is a Y, all Xs are Ys, no Xs are Ys, some Xs are Ys,
some Xs are not Ys. The writer cannot express a single ordering, propositional
or interrogative construction; the reader has no template for the bare
transitive clause (X supports Y) that is the writer's entire default output.
The two halves meet only on set membership and subsumption.
2. Three of the reader's constructions are unreachable by SHAPE, not by
vocabulary. member_query and subset_query require ?; no writer template
contains one. Corpus work cannot close that gap — a template can.
3. The reader fabricates on 22 constructions, and it is on a serving path.
Three distinct leaks, one root cause: _RESERVED does not contain the
function-word vocabulary the writer actually emits.
| leak | example | minted |
|---|---|---|
| determiner glued to entity | every dog is a mammal |
member(every_dog, mammal) |
| verb phrase chunked as class | all dogs are defined as mammals |
subset(dog, defined_as_mammal) |
| connective promoted to proposition | furthermore, all dogs are mammals |
asserted(furthermore) |
The first and third are ordinary user English, not writer artefacts. And the third reaches served output:
"if p then q. p. therefore q." -> Given: p implies q; p. ...
"furthermore, if p then q. p. therefore q." -> Given: furthermore; p implies q; p. ...
CORE recites a premise the user never stated. chat/runtime.py additionally
realizes declarative turns into the held self, so asserted(furthermore) is
writable to the vault.
4. ADR-0265's defect class survives in the module ADR-0265 named as the single
owner of clause grammar. The four aspect arms of _inflect_predicate bind
negated to a wildcard and never read it:
aspect=perfective affirm: dog has been defined as mammal
denied: dog has been defined as mammal <-- identical
10530 of 16146 writer parameter points collapse this way. It is not reachable
today — no producer sets aspect on the serving path — so it is a loaded gun,
not a casualty. It survived because ADR-0265's invariant is structural (is
negated threaded to the call?) and cannot see an arm that receives the flag and
ignores it. This lane's sentinel is behavioural, and does.
What this lane does NOT prove
The overlap is a count of shapes, not a quality judgement. Six shared
constructions that are all set-theoretic is a narrow grammar, and nothing here
says the shared six are well written — render_step could emit stilted
English inside every one of them and this lane would be satisfied.
It also says nothing about MeaningGraph/PropositionGraph type compatibility
(§1.8). That question is still open and still un-forced: no case measured here
fails for that reason and no other.
Mutation guarantees
Every pin in tests/test_construction_inventory.py has been run against a
mutation of its own subject and observed RED — 12 of 12, recorded in the Phase 5
RESULT block of the plan. The two fabrication fixes (every/each into
_RESERVED; discourse connectives stripped in _split_clauses) are among the
mutations, so the defect pins are known to move when the defect is fixed.