chore(derivation): clean Inc3 diff hygiene

This commit is contained in:
Shay 2026-06-17 09:51:13 -07:00
parent 3fc81f66a4
commit 64eaf43cd8
3 changed files with 97 additions and 257 deletions

View file

@ -1,7 +1,7 @@
# CORE Problem-Solving Capability Roadmap v2 — 2026-06-17 # CORE Problem-Solving Capability Roadmap v2 — 2026-06-17
**Status:** Living document (docs-only update) **Status:** Living document (docs-only update)
**Date:** 2026-06-17 **Date:** 2026-06-17
**Context:** Post PR #797 (rate injection) + #798; preparing Inc3 rate follow-up before Gate A1 comparative injection. **Context:** Post PR #797 (rate injection) + #798; preparing Inc3 rate follow-up before Gate A1 comparative injection.
## Overview ## Overview

View file

@ -1,9 +1,9 @@
# GSM8K Workstream A Increment 3 — rate followup (post-#797) ratification # GSM8K Workstream A Increment 3 — rate followup (post-#797) ratification
**Date:** 2026-06-17 **Date:** 2026-06-17
**Workstream:** A **Workstream:** A
**Increment:** 3 — post-#797 rate frontier evidence loop closure (narrow) **Increment:** 3 — post-#797 rate frontier evidence loop closure (narrow)
**Status:** Ratified for implementation (BEFORE code changes) **Status:** Ratified for implementation (BEFORE code changes)
**Scope lock:** Bounded to making the rate "recognized_no_injection" bucket produce actionable evidence by resolving the explicit remaining connector blocker left open in #797. One smallest change only. **Scope lock:** Bounded to making the rate "recognized_no_injection" bucket produce actionable evidence by resolving the explicit remaining connector blocker left open in #797. One smallest change only.
## 1. Which exact refusal bucket is being attacked? ## 1. Which exact refusal bucket is being attacked?

View file

@ -57,214 +57,91 @@ from generate.math_problem_graph import Comparison, Operation, Quantity, Rate
# Surface verbs that grammatically place the actor as the *gainer* of the # Surface verbs that grammatically place the actor as the *gainer* of the
# operand quantity. Past tense and present tense both registered. # operand quantity. Past tense and present tense both registered.
ADD_VERBS: Final[frozenset[str]] = frozenset( ADD_VERBS: Final[frozenset[str]] = frozenset({
{ # acquisition
# acquisition "buy", "buys", "bought",
"buy", "get", "gets", "got",
"buys", "find", "finds", "found",
"bought", "receive", "receives", "received",
"get", "earn", "earns", "earned",
"gets", "add", "adds", "added",
"got", "pick", "picks", "picked", # "picks up N"
"find", "collect", "collects", "collected",
"finds", "gather", "gathers", "gathered",
"found", "catch", "catches", "caught",
"receive", "save", "saves", "saved",
"receives", # production (actor creates instances of the unit)
"received", "bake", "bakes", "baked",
"earn", "make", "makes", "made",
"earns", "cook", "cooks", "cooked",
"earned", "slice", "slices", "sliced",
"add", "pack", "packs", "packed",
"adds", "build", "builds", "built",
"added", "grow", "grows", "grew",
"pick", })
"picks",
"picked", # "picks up N"
"collect",
"collects",
"collected",
"gather",
"gathers",
"gathered",
"catch",
"catches",
"caught",
"save",
"saves",
"saved",
# production (actor creates instances of the unit)
"bake",
"bakes",
"baked",
"make",
"makes",
"made",
"cook",
"cooks",
"cooked",
"slice",
"slices",
"sliced",
"pack",
"packs",
"packed",
"build",
"builds",
"built",
"grow",
"grows",
"grew",
}
)
# Surface verbs that grammatically place the actor as the *loser* of the # Surface verbs that grammatically place the actor as the *loser* of the
# operand quantity. # operand quantity.
SUBTRACT_VERBS: Final[frozenset[str]] = frozenset( SUBTRACT_VERBS: Final[frozenset[str]] = frozenset({
{ "eat", "eats", "ate",
"eat", "lose", "loses", "lost",
"eats", "sell", "sells", "sold",
"ate", "donate", "donates", "donated",
"lose", "use", "uses", "used",
"loses", "spend", "spends", "spent",
"lost", "drop", "drops", "dropped",
"sell", "remove", "removes", "removed",
"sells", "break", "breaks", "broke",
"sold", "destroy", "destroys", "destroyed",
"donate", "throw", "throws", "threw", # "throws out N"
"donates", "discard", "discards", "discarded",
"donated", "return", "returns", "returned", # ambiguous — see TRANSFER_VERBS
"use", "consume", "consumes", "consumed",
"uses", "give", "gives", "gave", # ambiguous — see TRANSFER_VERBS
"used", "send", "sends", "sent", # ambiguous — see TRANSFER_VERBS
"spend", })
"spends",
"spent",
"drop",
"drops",
"dropped",
"remove",
"removes",
"removed",
"break",
"breaks",
"broke",
"destroy",
"destroys",
"destroyed",
"throw",
"throws",
"threw", # "throws out N"
"discard",
"discards",
"discarded",
"return",
"returns",
"returned", # ambiguous — see TRANSFER_VERBS
"consume",
"consumes",
"consumed",
"give",
"gives",
"gave", # ambiguous — see TRANSFER_VERBS
"send",
"sends",
"sent", # ambiguous — see TRANSFER_VERBS
}
)
# Surface verbs that grammatically place the actor as the *sender* and a # Surface verbs that grammatically place the actor as the *sender* and a
# named target as the *receiver*. These verbs ALSO appear in SUBTRACT_VERBS # named target as the *receiver*. These verbs ALSO appear in SUBTRACT_VERBS
# because the same surface token can take a transfer reading (with target) # because the same surface token can take a transfer reading (with target)
# or a subtract reading (without target) — both candidates fire and the # or a subtract reading (without target) — both candidates fire and the
# decision rule picks based on whether a target slot was grounded. # decision rule picks based on whether a target slot was grounded.
TRANSFER_VERBS: Final[frozenset[str]] = frozenset( TRANSFER_VERBS: Final[frozenset[str]] = frozenset({
{ "give", "gives", "gave",
"give", "send", "sends", "sent",
"gives", "hand", "hands", "handed",
"gave", "pass", "passes", "passed",
"send", "mail", "mails", "mailed",
"sends", "deliver", "delivers", "delivered",
"sent", "return", "returns", "returned",
"hand", })
"hands",
"handed",
"pass",
"passes",
"passed",
"mail",
"mails",
"mailed",
"deliver",
"delivers",
"delivered",
"return",
"returns",
"returned",
}
)
MULTIPLY_VERBS: Final[frozenset[str]] = frozenset( MULTIPLY_VERBS: Final[frozenset[str]] = frozenset({
{ "double", "doubles", "doubled",
"double", "triple", "triples", "tripled",
"doubles", "quadruple", "quadruples", "quadrupled",
"doubled", "multiply", "multiplies", "multiplied",
"triple", })
"triples",
"tripled",
"quadruple",
"quadruples",
"quadrupled",
"multiply",
"multiplies",
"multiplied",
}
)
DIVIDE_VERBS: Final[frozenset[str]] = frozenset( DIVIDE_VERBS: Final[frozenset[str]] = frozenset({
{ "halve", "halves", "halved",
"halve", "split", "splits", "split",
"halves", "divide", "divides", "divided",
"halved", "share", "shares", "shared",
"split", })
"splits",
"split",
"divide",
"divides",
"divided",
"share",
"shares",
"shared",
}
)
# Comparison "verbs" — the surface anchor for compare_additive / # Comparison "verbs" — the surface anchor for compare_additive /
# compare_multiplicative is usually 'has'/'have' + comparator phrase # compare_multiplicative is usually 'has'/'have' + comparator phrase
# ('N more than', 'twice as many as', etc.). The matched_verb slot for # ('N more than', 'twice as many as', etc.). The matched_verb slot for
# comparison candidates carries the comparator phrase head ('more', # comparison candidates carries the comparator phrase head ('more',
# 'fewer', 'twice', 'times', 'half'). # 'fewer', 'twice', 'times', 'half').
COMPARE_ADDITIVE_ANCHORS: Final[frozenset[str]] = frozenset( COMPARE_ADDITIVE_ANCHORS: Final[frozenset[str]] = frozenset({
{ "more", "fewer", "less", "additional", "extra",
"more", })
"fewer", COMPARE_MULTIPLICATIVE_ANCHORS: Final[frozenset[str]] = frozenset({
"less", "twice", "thrice", "times", "half", "double", "triple",
"additional", "quadruple", "third", "quarter",
"extra", })
}
)
COMPARE_MULTIPLICATIVE_ANCHORS: Final[frozenset[str]] = frozenset(
{
"twice",
"thrice",
"times",
"half",
"double",
"triple",
"quadruple",
"third",
"quarter",
}
)
# Rate anchors (ADR-0122): "per", "each", "every", "a"/"an" (when followed # Rate anchors (ADR-0122): "per", "each", "every", "a"/"an" (when followed
# by a unit in a rate surface such as "$18 an hour" or "$2 a cup"). # by a unit in a rate surface such as "$18 an hour" or "$2 a cup").
@ -272,16 +149,9 @@ COMPARE_MULTIPLICATIVE_ANCHORS: Final[frozenset[str]] = frozenset(
# so that roundtrip_admissible / CandidateOperation post-init grounding # so that roundtrip_admissible / CandidateOperation post-init grounding
# succeeds. "a"/"an" were documented in the comment but missing from the # succeeds. "a"/"an" were documented in the comment but missing from the
# set; added here (Inc 2) with corresponding injector tests. # set; added here (Inc 2) with corresponding injector tests.
RATE_ANCHORS: Final[frozenset[str]] = frozenset( RATE_ANCHORS: Final[frozenset[str]] = frozenset({
{ "per", "each", "every", "a", "an", "one",
"per", })
"each",
"every",
"a",
"an",
"one",
}
)
KIND_TO_VERBS: Final[Mapping[str, frozenset[str]]] = { KIND_TO_VERBS: Final[Mapping[str, frozenset[str]]] = {
@ -302,40 +172,15 @@ KIND_TO_VERBS: Final[Mapping[str, frozenset[str]]] = {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
WORD_NUMBERS: Final[Mapping[str, int]] = { WORD_NUMBERS: Final[Mapping[str, int]] = {
"zero": 0, "zero": 0, "one": 1, "two": 2, "three": 3, "four": 4,
"one": 1, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9,
"two": 2, "ten": 10, "eleven": 11, "twelve": 12, "thirteen": 13,
"three": 3, "fourteen": 14, "fifteen": 15, "sixteen": 16, "seventeen": 17,
"four": 4, "eighteen": 18, "nineteen": 19, "twenty": 20, "thirty": 30,
"five": 5, "forty": 40, "fifty": 50, "sixty": 60, "seventy": 70,
"six": 6, "eighty": 80, "ninety": 90, "hundred": 100, "thousand": 1000,
"seven": 7,
"eight": 8,
"nine": 9,
"ten": 10,
"eleven": 11,
"twelve": 12,
"thirteen": 13,
"fourteen": 14,
"fifteen": 15,
"sixteen": 16,
"seventeen": 17,
"eighteen": 18,
"nineteen": 19,
"twenty": 20,
"thirty": 30,
"forty": 40,
"fifty": 50,
"sixty": 60,
"seventy": 70,
"eighty": 80,
"ninety": 90,
"hundred": 100,
"thousand": 1000,
# ordinals as factor-bearing forms ("a third", "a quarter") # ordinals as factor-bearing forms ("a third", "a quarter")
"half": 2, "half": 2, "third": 3, "quarter": 4,
"third": 3,
"quarter": 4,
} }
@ -343,7 +188,6 @@ WORD_NUMBERS: Final[Mapping[str, int]] = {
# Public dataclass — what the candidate-graph parser will emit per match. # Public dataclass — what the candidate-graph parser will emit per match.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class CandidateOperation: class CandidateOperation:
"""An Operation candidate plus the source-span provenance proving it. """An Operation candidate plus the source-span provenance proving it.
@ -389,26 +233,29 @@ class CandidateOperation:
raise ValueError("CandidateOperation.source_span must be non-empty") raise ValueError("CandidateOperation.source_span must be non-empty")
if not isinstance(self.matched_verb, str) or not self.matched_verb: if not isinstance(self.matched_verb, str) or not self.matched_verb:
raise ValueError("CandidateOperation.matched_verb must be non-empty") raise ValueError("CandidateOperation.matched_verb must be non-empty")
if ( if not isinstance(self.matched_actor_token, str) or not self.matched_actor_token:
not isinstance(self.matched_actor_token, str) raise ValueError(
or not self.matched_actor_token "CandidateOperation.matched_actor_token must be non-empty"
): )
raise ValueError("CandidateOperation.matched_actor_token must be non-empty")
if self.op.kind == "transfer": if self.op.kind == "transfer":
if not self.matched_target_token: if not self.matched_target_token:
raise ValueError( raise ValueError(
"matched_target_token required when op.kind='transfer'" "matched_target_token required when op.kind='transfer'"
) )
elif self.matched_target_token is not None: elif self.matched_target_token is not None:
raise ValueError("matched_target_token only valid when op.kind='transfer'") raise ValueError(
"matched_target_token only valid when op.kind='transfer'"
)
if isinstance(self.op.operand, Comparison): if isinstance(self.op.operand, Comparison):
if not self.matched_reference_actor_token: if not self.matched_reference_actor_token:
raise ValueError( raise ValueError(
"matched_reference_actor_token required when operand is Comparison" "matched_reference_actor_token required when operand is "
"Comparison"
) )
elif self.matched_reference_actor_token is not None: elif self.matched_reference_actor_token is not None:
raise ValueError( raise ValueError(
"matched_reference_actor_token only valid when operand is Comparison" "matched_reference_actor_token only valid when operand is "
"Comparison"
) )
@ -531,7 +378,6 @@ def _value_grounds(value_token: str, haystack_tokens: frozenset[str]) -> bool:
if "-" in value_token and not value_token[0].isdigit(): if "-" in value_token and not value_token[0].isdigit():
try: try:
from language_packs.numerics_loader import parse_compound_cardinal from language_packs.numerics_loader import parse_compound_cardinal
parsed = parse_compound_cardinal(value_token) parsed = parse_compound_cardinal(value_token)
if parsed is not None: if parsed is not None:
components = [c for c in value_token.lower().split("-") if c] components = [c for c in value_token.lower().split("-") if c]
@ -551,7 +397,6 @@ def _value_grounds(value_token: str, haystack_tokens: frozenset[str]) -> bool:
# through to the hard-coded table. # through to the hard-coded table.
try: try:
from language_packs.loader import lookup_cardinal from language_packs.loader import lookup_cardinal
entry = lookup_cardinal(lowered) entry = lookup_cardinal(lowered)
if entry is not None: if entry is not None:
digit = str(entry.numeric_value) digit = str(entry.numeric_value)
@ -576,7 +421,6 @@ def _value_grounds(value_token: str, haystack_tokens: frozenset[str]) -> bool:
# Pack-backed reverse lookup: digit -> cardinal surface in haystack # Pack-backed reverse lookup: digit -> cardinal surface in haystack
try: try:
from language_packs.loader import lookup_cardinal from language_packs.loader import lookup_cardinal
for tok in haystack_tokens: for tok in haystack_tokens:
entry = lookup_cardinal(tok) entry = lookup_cardinal(tok)
if entry is not None and entry.numeric_value == n: if entry is not None and entry.numeric_value == n:
@ -590,7 +434,6 @@ def _value_grounds(value_token: str, haystack_tokens: frozenset[str]) -> bool:
# The load-bearing primitive. # The load-bearing primitive.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def roundtrip_admissible(c: CandidateOperation) -> bool: def roundtrip_admissible(c: CandidateOperation) -> bool:
"""True iff every content slot in ``c`` grounds in ``c.source_span`` """True iff every content slot in ``c`` grounds in ``c.source_span``
AND the matched verb is registered for the operation kind. AND the matched verb is registered for the operation kind.
@ -619,10 +462,7 @@ def roundtrip_admissible(c: CandidateOperation) -> bool:
# Skipped only for multiplicative comparison anchors that carry # Skipped only for multiplicative comparison anchors that carry
# the factor implicitly ("twice", "half", "thrice") — those use # the factor implicitly ("twice", "half", "thrice") — those use
# the anchor itself as the value token and pass via step (2). # the anchor itself as the value token and pass via step (2).
if ( if c.op.kind == "compare_multiplicative" and c.matched_value_token == c.matched_verb:
c.op.kind == "compare_multiplicative"
and c.matched_value_token == c.matched_verb
):
pass # anchor already grounded by verb check pass # anchor already grounded by verb check
elif not _value_grounds(c.matched_value_token, haystack): elif not _value_grounds(c.matched_value_token, haystack):
return False return False