revert(reader-arc): hold frame-anchoring out of increment 1 — defer to rate-frame increment (#77 ruling)
Reverts the c3aed13b parser change (frame-anchored comparative regexes +
verb-free connector + _COMPARE_POLARITY_BLOCK + 'as much'); the reader stays
on the _comparison_anchor_verb() whitelist (fail-closed). Increment 1 ships
the compiler tier + the funnel finding only.
Three reasons on record: (1) frame-anchoring converts 0 of 500 official cases
— zero measured benefit today; (2) its consumer is rate-frame admission, a
later increment — the next increment (seeding-sentence injection) is a shared
layer that doesn't touch it; (3) the polarity blocklist is fail-OPEN — an
unenumerated depletion verb in a complete frame is a coherent-but-inverted
graph = a wrong on the run-once sealed arbiter (round-trip guards parse
consistency, not gold-correctness), a trade the fail-closed doctrine never
takes for zero gain.
Verified before revert-merge: with the whitelist reader, the 5 real compare
parses are non-divergent — test_real_compare_cases_solved_wrong_zero still
sees 5, solves 5, wrong 0 (corridor real-reach 0->5/500 preserved).
Frame-anchoring recorded as an OPEN DESIGN TASK owned by the rate-frame
increment: frame + POSITIVE polarity determination (neutral-polarity allowlist
or small polarity classifier), not assume-safe-unless-blocklisted. Detail in
docs/research/compare-increment-funnel-2026-07-18.md.
This commit is contained in:
parent
c3aed13bbe
commit
6f1ef49018
2 changed files with 64 additions and 72 deletions
|
|
@ -116,7 +116,13 @@ the project is large.
|
|||
|
||||
## MEASURED OUTCOME — the reader half (frame-anchored recognizer)
|
||||
|
||||
### What shipped
|
||||
> **STATUS: BUILT + MEASURED, then DEFERRED out of increment 1 (PR #77 ruling).** The code change
|
||||
> (`c3aed13b`, frame-anchored recognizer) was reverted from the merged increment; only the compiler
|
||||
> tier + this finding land. Frame-anchoring is now an **open design task owned by the rate-frame
|
||||
> increment** — see *Deferral ruling* below. This section is the on-record measurement that motivated
|
||||
> the split.
|
||||
|
||||
### What was built and measured
|
||||
Per the construction constraint (anchor on the comparative FRAME, not verb-whitelist growth), the two
|
||||
multiplicative-frame regexes (`_COMPARE_MULT_ANCHOR_RE`, `_COMPARE_MULT_NTIMES_RE`) were re-anchored:
|
||||
the closed-class frame — `<factor> as many/much <unit> as <REF>` — is the gate, the verb slot became a
|
||||
|
|
@ -124,17 +130,21 @@ verb-free bounded connector, and `as much` joined `as many`. `_comparison_anchor
|
|||
is left in place for the ADDITIVE / NESTED frames (out of scope). Both frame regexes remain `$`-anchored,
|
||||
so multi-clause / nested surfaces stay refused by construction.
|
||||
|
||||
### The polarity refutation (and the sanctioned fallback)
|
||||
### The polarity refutation — the real lesson
|
||||
Pure verb-free anchoring is **not** wrong=0-safe: a polarity-inverting / depletion verb inside the frame
|
||||
("Alice **lost** twice as many apples as Bob") makes the compared quantity a loss, so
|
||||
`actor = factor × reference` reads the relation backwards → wrong>0. The frame does NOT disambiguate this
|
||||
(pinned by `test_adr_0131_G2a…::test_polarity_inverting_verb_not_admitted`, `…spend…`,
|
||||
`test_recognizer_comparative_inject…[Alice lost…]`). Resolution = the fallback the ruling anticipated:
|
||||
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. Admission stays
|
||||
open-class for every *safe* verb (scored/wrote/caught/taught/baked/grew/…); the blocklist is the one
|
||||
piece of verb knowledge wrong=0 genuinely requires — a small closed semantic class, not a whitelist.
|
||||
**The frame is the gate either way.**
|
||||
`test_recognizer_comparative_inject…[Alice lost…]`). The prototype fix was a closed-class polarity
|
||||
**blocklist** the connector may not span — but the ruling named the deeper problem this exposes, and it
|
||||
is decisive: **the verb carries a polarity bit that must be positively determined, not
|
||||
assumed-safe-unless-blocklisted.** A blocklist is fail-*open* — it admits every unenumerated verb, so a
|
||||
depletion verb nobody listed reaches a *complete* frame as a coherent-but-inverted graph: a **wrong** on
|
||||
the run-once sealed arbiter (the round-trip net guards parse consistency, not gold-correctness). The
|
||||
fail-closed doctrine never takes that trade — least of all for the **zero** measured benefit below. So
|
||||
the wrong=0-safe resolution is genuine design work: frame **plus positive polarity determination** (a
|
||||
neutral-polarity *allowlist*, or a small polarity classifier), exercised end-to-end where it earns its
|
||||
keep — the rate-frame increment — not bolted on here.
|
||||
|
||||
### Footprint — provable, tiny, safe
|
||||
Develop-on-tune / measure-once discipline held. A new-vs-old frame-fire diff over all 500 official cases:
|
||||
|
|
@ -164,18 +174,51 @@ extension (multi-word reference) converts **0** cases end-to-end — and introdu
|
|||
hazard — so it was reverted.
|
||||
|
||||
**Ruling per the pre-committed funnel-conditioned escalation rule:** the mass is dying in **shared
|
||||
layers**, not compare-specific logic ⇒ the next increment is a **shared-layer design increment**
|
||||
(highest-leverage: the summation-question reader — evidenced by `0228` + `0441` both dying there), NOT
|
||||
a practice-lane escalation, and NOT more compare-family frame surgery. X is re-applied *after* the
|
||||
layers**, not compare-specific logic ⇒ the next increment is a **shared-layer design increment**, NOT a
|
||||
practice-lane escalation, and NOT more compare-family frame surgery. **Shay's ruling (#77): next =
|
||||
seeding-sentence injection** — the 135/261 "no injection (discrete_count)" wall, the dominant shared
|
||||
bucket that gates every family (production/possession verbs like *made/baked/grew/wrote* that don't
|
||||
inject an initial state). The compare-specific unblock (summation-question reader + **inverse compare** —
|
||||
known=actor, solve reference; `0441` needs both) stays queued behind it. X is re-applied *after* the
|
||||
shared layer lands.
|
||||
|
||||
### Triple report
|
||||
- **correct**: measure new-correct = **0**. The compiler tier solves the compare cases the reader can
|
||||
already reach (`0101` "double what", `0108` "times the number of", `0411`) — all **pre-existing**, not
|
||||
new from the frame-anchoring.
|
||||
- **refused-by-reason (traps broken out)**: polarity-inverting (lost/spent) → refused by
|
||||
`_COMPARE_POLARITY_BLOCK` (absent from the real splits; pinned by synthetic confusers). Family funnel:
|
||||
16 multiclause/nested, 10 frame-no-match, 5 number/amount-of, 5 as-ADJ, 2 double-what — all now
|
||||
refuse-and-record.
|
||||
- **refused-by-reason (traps broken out)**: polarity-inverting (lost/spent) → refused fail-closed by the
|
||||
`_comparison_anchor_verb()` whitelist (the merged state, after the frame-anchoring revert; the
|
||||
blocklist prototype that would have admitted-then-guarded them is deferred with the frame change).
|
||||
Family funnel: 16 multiclause/nested, 10 frame-no-match, 5 number/amount-of, 5 as-ADJ, 2 double-what —
|
||||
all refuse-and-record.
|
||||
- **wrong = 0**: held on tune (261) **and** measure (239) **and** smoke (176). No over-admission from
|
||||
the verb-free widening; no branch-disagreement regression (0 tune divergences).
|
||||
|
||||
### Deferral ruling (#77) — the split
|
||||
Increment 1 merges the **compiler tier + this finding**; the **frame-anchoring reader change is held
|
||||
out** (`c3aed13b` reverted from the PR). Three compounding reasons, on record:
|
||||
|
||||
1. **Zero measured benefit today** — 0 conversions across all 500 official cases.
|
||||
2. **Its consumer is a later increment** — frame-anchoring exists to serve rate-frame admission
|
||||
(increment 2). The *next* increment is seeding-sentence injection, a shared layer that does not touch
|
||||
it. Nothing on the critical path needs it now.
|
||||
3. **Fail-open surface at the sealed test for that zero benefit** — the polarity blocklist admits every
|
||||
unenumerated verb; an unlisted depletion verb in a complete frame is a coherent-but-inverted graph =
|
||||
a wrong on the run-once arbiter. The fail-closed doctrine never risks that, least of all for no gain.
|
||||
|
||||
Verified before merge: with the whitelist reader restored, the 5 real compare parses are **non-divergent**
|
||||
— `test_real_compare_cases_solved_wrong_zero` still sees **5, solves 5, wrong 0** (corridor real-reach
|
||||
`0→5/500` preserved). The tier keeps its win without the frame change.
|
||||
|
||||
### OPEN DESIGN TASK (owned by the rate-frame increment)
|
||||
**Frame-anchored comparative admission with wrong=0-safe polarity determination.** The frame is the right
|
||||
gate; the open problem is the verb's polarity bit. It must be **positively determined**, not
|
||||
assumed-safe-unless-blocklisted:
|
||||
|
||||
- a **neutral-polarity allowlist** (fail-closed: only verbs affirmatively known non-inverting admit), or
|
||||
- a small **polarity classifier / lexicon** (possession/production/acquisition = neutral → `factor×ref`;
|
||||
depletion/transfer/loss = inverting → refuse or model as a delta).
|
||||
|
||||
Deliverable when the rate-frame increment lands it: exercised end-to-end on rate frames, measured on the
|
||||
disjoint split, wrong=0 preserved on the sealed arbiter. Until then the reader stays on the
|
||||
`_comparison_anchor_verb()` whitelist (fail-closed) for all comparative frames.
|
||||
|
|
|
|||
|
|
@ -1217,76 +1217,25 @@ _COMPARE_ADDITIVE_RE: Final[re.Pattern[str]] = re.compile(
|
|||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
||||
# ADR-0250 reader-arc increment 1 — FRAME-ANCHORED admission (verb-free).
|
||||
#
|
||||
# The multiplicative comparison FRAME — "<factor> as many/much <unit> as
|
||||
# <REF>" / "<N> times as many/much <unit> as <REF>" — is the closed-class
|
||||
# structure that carries the relation. The verb between the actor and the
|
||||
# frame is open-class ("scored", "caught", "taught", "has", ...) and does NO
|
||||
# disambiguating work the frame doesn't already do; growing a verb whitelist
|
||||
# only adds maintenance and trap surface ("scored twice" = event count,
|
||||
# "reads twice a day" = frequency — both verb-adjacent traps). So the
|
||||
# connector below admits ANY 0-3 word tokens in the verb slot and lets the
|
||||
# COMPLETE frame be the gate. This is safe by construction:
|
||||
# * both regexes remain ``$``-anchored, so the frame must end the clause —
|
||||
# multi-clause / nested surfaces ("…, but 3 fewer…", "6 more than three
|
||||
# times…") do NOT match and stay refused (a later layer / practice lane);
|
||||
# * the connector is word-only (``\w+``), so it cannot span a comma or
|
||||
# clause boundary — the actor stays the sentence-initial subject;
|
||||
# * the frequency/event-count traps lack the "as many/much <unit> as <REF>"
|
||||
# tail, so the frame refuses them WITHOUT any verb reasoning;
|
||||
# * the round-trip filter + branch-disagreement + compiler conservation
|
||||
# remain the wrong=0 net unchanged.
|
||||
# ``_comparison_anchor_verb()`` is intentionally left in place for the
|
||||
# ADDITIVE / NESTED frames (out of scope for this increment).
|
||||
#
|
||||
# ONE class of verb is NOT frame-neutral: polarity-inverting / depletion
|
||||
# verbs. In "A <verb> N as many X as REF", a verb like lose/spend/give/sell
|
||||
# makes the compared quantity a LOSS or transfer delta, not a possession —
|
||||
# so ``actor = factor × reference`` (what compare_multiplicative asserts)
|
||||
# reads the relation backwards → wrong>0. The frame does NOT disambiguate
|
||||
# this, so the connector must refuse to span any member of this closed set
|
||||
# (the exact set ``_comparison_anchor_verb`` documents as EXCLUDED). This is
|
||||
# the sanctioned fallback: the frame is still the gate for every OTHER verb;
|
||||
# this blocklist is the minimal, closed, semantic-class piece of verb
|
||||
# knowledge wrong=0 requires — NOT an open-class whitelist. Every non-listed
|
||||
# verb (scored/wrote/caught/taught/baked/grew/…) is admitted verb-free.
|
||||
_COMPARE_POLARITY_BLOCK: Final[str] = (
|
||||
r"lose|lost|loses|losing|"
|
||||
r"spend|spent|spends|spending|"
|
||||
r"use|used|uses|using|"
|
||||
r"give|gave|gives|giving|given|"
|
||||
r"sell|sold|sells|selling|"
|
||||
r"win|won|wins|winning|"
|
||||
r"donate|donated|donates|donating|"
|
||||
r"pay|paid|pays|paying|"
|
||||
r"eat|ate|eats|eating|eaten|"
|
||||
r"drop|dropped|drops|dropping|"
|
||||
r"lend|lent|lends|lending"
|
||||
)
|
||||
_COMPARE_FRAME_CONNECTOR: Final[str] = (
|
||||
rf"(?:(?!(?i:{_COMPARE_POLARITY_BLOCK})\b)\w+\s+){{0,3}}"
|
||||
)
|
||||
|
||||
# Multiplicative: anchor-as-value form ("twice"/"thrice"/"half"/"quarter"/
|
||||
# "third" carry the factor implicitly). "as many/much <unit>" required; unit
|
||||
# "third" carry the factor implicitly). "as many <unit>" required; unit
|
||||
# ellipsis ("twice as many as Bob") is deferred to keep wrong==0 strict.
|
||||
# "quarter" / "third" admit an optional article ("a quarter", "a third") —
|
||||
# the article is not a named group; matched_verb is the anchor word itself,
|
||||
# which is a substring of the source and registered in
|
||||
# COMPARE_MULTIPLICATIVE_ANCHORS, so round-trip checks pass.
|
||||
_COMPARE_MULT_ANCHOR_RE: Final[re.Pattern[str]] = re.compile(
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_COMPARE_FRAME_CONNECTOR}"
|
||||
r"(?:a\s+)?(?P<anchor>twice|thrice|half|quarter|third)\s+as\s+(?:many|much)\s+"
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_comparison_anchor_verb()}\s+"
|
||||
r"(?:a\s+)?(?P<anchor>twice|thrice|half|quarter|third)\s+as\s+many\s+"
|
||||
r"(?P<unit>\w+(?:\s+\w+)?)\s+as\s+"
|
||||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
||||
# Multiplicative: explicit "N times as many/much <unit> as <REF>".
|
||||
# Multiplicative: explicit "N times as many <unit> as <REF>".
|
||||
# ADR-0131.G.2a — unit slot admits an optional second word ("jumping jacks").
|
||||
_COMPARE_MULT_NTIMES_RE: Final[re.Pattern[str]] = re.compile(
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_COMPARE_FRAME_CONNECTOR}"
|
||||
rf"(?P<value>{_VALUE})\s+times\s+as\s+(?:many|much)\s+"
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_comparison_anchor_verb()}\s+"
|
||||
rf"(?P<value>{_VALUE})\s+times\s+as\s+many\s+"
|
||||
r"(?P<unit>\w+(?:\s+\w+)?)\s+as\s+"
|
||||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue