feat(reader-arc): compare_multiplicative increment 1 — compiler tier + frame-anchored reader (measured delta=0, reroutes to shared-layer) #77
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/compare-mult-increment"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Increment 1 of the design-first reader arc (ADR-0250), both halves, measured.
Per the ruling (PR #76): per-family increments, reader-grammar + compiler-tier in lockstep, develop-on-tune / measure-once, X=15 ratified. Honest headline: X=15 MISSED, measured holdout delta = 0 — and that zero is the deliverable. It precisely locates the real bottleneck: the compare_multiplicative reader is not the GSM8K wall.
Compiler half — real capability (commit
9136c181,260e56ca)compare_multiplicative(actor = factor × reference) as a cross-register dilation: read the reference register's field state, dilate by-ln(factor), write the actor. New bits: reads-one-writes-another; actor may be defined by the compare (no seed) yet still summed (amendment 1, registers-driven summation with unit propagation); record binds the reference entity + its state digest for records-alone re-verification (amendment 2, conditionalsource_entityso pre-compare digests stay byte-identical); no conservation pin (compare creates quantity). Fail-closed guards: inverse-mis-direction (compare_redefines_register), chained-reference-before-definition,compare_additiveout-of-scope, self-reference. 27/27 (tests/test_adr_0250_compare.py).Reader half — frame-anchored recognizer (commit
c3aed13b)Per the construction constraint (anchor on the comparative FRAME, not verb-whitelist growth): the two multiplicative-frame regexes now gate on the closed-class frame
<factor> as many/much <unit> as <REF>; the verb slot is a verb-free bounded connector;as muchjoinsas many._comparison_anchor_verb()stays for the additive/nested frames (out of scope). Both regexes remain$-anchored, so multi-clause/nested surfaces stay refused by construction.Polarity refutation + sanctioned fallback. Pure verb-free is not wrong=0-safe: a polarity-inverting/depletion verb in the frame ("Alice lost twice as many apples as Bob") makes the compared quantity a loss, so
actor = factor × referencereads backwards → wrong>0 (pinned bytest_adr_0131_G2a…::test_polarity_inverting_verb_not_admitted,…spend…,test_recognizer_comparative_inject…[Alice lost…]). Fix = frame-anchored plus a closed-class polarity blocklist (_COMPARE_POLARITY_BLOCK: lose/spend/use/give/sell/win/donate/pay/eat/drop/lend) the connector may not span. The frame is the gate for every other verb; the blocklist is the minimal closed-class piece of verb knowledge wrong=0 requires — not a whitelist.Measured footprint — provable, tiny, safe (develop-on-tune / measure-once held)
New-vs-old frame-fire diff over all 500 official
holdout_dev/v1cases:0441"Zig wrote four times as many books as Flo."0441is the exact verb-blocked archetype. Its frame now fires correctly — and it still refuses, one layer downstream: "no admissible candidate for question: 'how many books did they write altogether?'". It advanced frame-refusal → question-refusal (the pre-registered sanctioned delta), proving the frame reads but the binding wall is the shared summation-question layer (+ inverse-compare:0441has the known quantity as the actor).X=15 missed → shared-layer, not family-exhaustion (per the pre-committed funnel rule)
The 38 tune compare-family cases, classified by actual blocker: 16 multiclause/nested, 10 frame-no-match (multi-word refs /
than≠as/ decimals / unit-ellipsis / subject-is-comparison), 5 "times the number/amount of" (self-ref/possessive), 5 "as ADJ as" (attribute register), 2 "double what". End-to-end traces (0228/0274/0491/0165/0299) show every one dies at a non-compare layer: summation-question, seeding-sentence injection (135/261 "no injection" wall), additive-compare, attribute registers, simultaneous equations. Even the cleanest compare-family extension (multi-word reference) converts 0 end-to-end and adds an actor-misbinding hazard → reverted. Ruling: next increment = shared-layer (seeding-sentence injection, the dominant wall). X re-applied after. Full detail + triple report:docs/research/compare-increment-funnel-2026-07-18.md.Files
evals/multi_register_program.py— compare compiler tier + registers-driven summationgenerate/math_candidate_parser.py— frame-anchored regexes + polarity blocklistevals/gsm8k_math/holdout_dev/v1/split.py— deterministic tune/measure split (pinned before grammar)tests/test_adr_0250_compare.py— 27 testsdocs/research/compare-increment-funnel-2026-07-18.md— funnel + measured outcome[Verification]: local worktree
uv run core test --suite smoke -q→ 176 passed @ c3aed13b; targeted comparative suites 97 passed, 2 skipped; compare tier 27/27. wrong=0 held on tune (261), measure (239), smoke (176).🤖 Generated with Claude Code
Increment 1 reader half. Re-anchor the two multiplicative-comparison frame regexes on the closed-class frame ('<factor> as many/much <unit> as <REF>') instead of the _comparison_anchor_verb() whitelist: the verb slot becomes a verb-free bounded connector, 'as much' joins 'as many'. Admission is now open-class for safe verbs (scored/wrote/caught/taught/…), gated by the frame. One closed class is NOT frame-neutral — polarity-inverting/depletion verbs (lose/spend/give/sell/win/donate/pay/eat/drop/lend): in a compare frame they make the compared quantity a loss, so actor=factor×reference reads backwards → wrong>0. _COMPARE_POLARITY_BLOCK excludes exactly that set (the sanctioned fallback; the frame is the gate for every other verb). Pins the wrong=0 guards in test_adr_0131_G2a + test_recognizer_comparative_inject. Develop-on-tune / measure-once held. Footprint over 500 official cases: tune byte-identical (0 divergences, wrong=0, PARSED=3); measure 1 divergence (0441 'Zig wrote four times as many books as Flo') which advances frame-refusal -> question-refusal (sanctioned delta) but does not solve — the binding wall is the shared summation-question layer. X=15 measured delta=0. Ruling per the funnel-conditioned escalation rule: mass is in shared layers, so the next increment is shared-layer (summation-question reader), not a practice escalation and not more compare frames. wrong=0 held on tune (261), measure (239), smoke (176).Review — compiler tier + finding: approved. Reader half: hold it out of this PR.
Verified clean and mergeable:
multi_register_program.py): four amendments correct. Registers-driven summation includes compare-defined registers (register_order, seed-then-defined) so a defined register is never dropped from a total;source_entity/operand_source_digestare conditional in the payload, so pre-compare record digests are byte-identical (criterion 4 ✓, pinned bytest_summation_record_has_no_source_entity); unit propagation from the reference; answer-target validation moved after the ops loop so a compare-defined unknown resolves. Fail-closed guards comprehensive — redefine-known-register (mis-directed inverse), chained-reference-before-definition, additive-out-of-scope, self-reference, non-finite/non-positive factor. 27/27.split.py): deterministic id-only (sha256(id)parity) — disjointness enforced by construction, not by discipline; wrong=0 across the full 500, delta attributed to measure only. Criteria 1–3 ✓.The reader half (
math_candidate_parser.py,c3aed13b) should not land here. Three facts compound:The polarity refutation is the real lesson: pure verb-free frame-anchoring is not wrong=0-safe — the verb carries a polarity bit that must be positively determined, not assumed-safe-unless-blocklisted. Resolving that properly (frame + a real polarity determination) is design work that belongs in the rate-frame increment where it's exercised end-to-end and measured, not a blocklist bolted on to reach "frame-anchored" for an increment whose delta is 0. Revert
c3aed13b, confirm the compare-tier tests stay green under the whitelist reader (the 5 solved cases are non-divergent, sotest_real_compare_cases_solved_wrong_zeroshould hold), merge the tier + finding, and record frame-anchoring as an open design task owned by the rate-frame increment.