core/evals/construction_inventory/contract.md
Shay c69f99485d feat(evals): measure the reader/writer construction inventories against each other
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
2026-07-27 14:11:07 -07:00

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_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 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.