9.7 KiB
ScalarEquivalence Facade PR-1 Handoff (2026-06-18)
This handoff is the execution brief for the first runtime slice after the Kernel Knowledge Layer PR-0 (#828).
It is intentionally narrow: implement a ScalarEquivalence facade over the existing ADR-0128 en_numerics_v1 pack. Do not change serving behavior in this PR.
Current anchor
- PR #827 is merged.
- PR #828 is merged.
- Current expected
train_sample:30 correct / 20 refused / 0 wrong. - Current observed
holdout_dev:5 correct / 495 refused / 0 wrong.
Existing repo foothold
The implementation should reuse existing pack machinery rather than create a duplicate scalar pack.
language_packs/numerics_loader.py already exposes:
lookup_cardinal(token)lookup_ordinal(token)lookup_fraction(token)lookup_quantifier(token)lookup_multiplier(token)lookup_comparison_anchor(token)lookup_comparison_anchors(token)match_number_format(token)parse_compound_cardinal(text)
Important existing behavior:
lookup_fraction()supports article-bound forms such asa halfanda quarter.lookup_fraction()supports compound forms such astwo-thirds,three quarters, andone halfthrough_parse_compound_fraction().match_number_format()supports ratified exact-format parsing for decimals, slash fractions, mixed numbers, percentages, signed integers, and thousands-separated integers.- The existing ADR-0128 numeric-format tests deliberately refuse ambiguous/malformed formats such as spaced slash fractions (
1 / 2) and percent forms without the exact ratified shape.
The facade should respect those invariants unless a later ADR explicitly extends ADR-0128.
Mission
Create:
language_packs/scalar_equivalence.py
The facade should expose source-grounded scalar candidates with canonical rational values and ambiguity hazards.
It must not solve problems, bind bases, choose operations, or admit serving answers.
Required public type
from dataclasses import dataclass
from fractions import Fraction
from typing import Literal
@dataclass(frozen=True)
class ScalarLexeme:
canonical: Fraction
source_surface: str
source_span: tuple[int, int]
surface_kind: Literal[
"digit_fraction",
"decimal",
"percent",
"word_fraction",
"unicode_fraction",
]
provenance: Literal["problem_text"]
hazards: tuple[str, ...]
Required public API
def extract_scalar_lexemes(text: str) -> tuple[ScalarLexeme, ...]: ...
def canonicalize_scalar_surface(surface: str) -> Fraction | None: ...
def hazards_for_scalar_surface(
surface: str,
*,
context: str | None = None,
) -> tuple[str, ...]: ...
Optional helpers are allowed only if they remain facade-local and are directly tested.
Hard non-goals
Do not touch:
generate/math_candidate_graph.py- any
generate/derivation/*organ evals/gsm8k_math/**/report.json- sealed artifacts
- train or holdout data
- scoring logic
Do not implement:
- a new duplicate scalar data pack
- a broad parser
- a generic fraction solver
- serving integration
- case-id logic
- hardcoded benchmark answers
- direct final-answer extraction
Canonical values for v1
At minimum:
1/21/32/31/43/41/101/100
Represent canonical values as fractions.Fraction, not float.
Required surface families
Word fractions
Examples:
halfone halfone-halfthirdone thirdtwo thirdsquarterone quarterthree quarters
Use lookup_fraction() where possible.
Digit fractions
Examples:
1/23/4
Use match_number_format() where possible.
Do not silently broaden ADR-0128. If 1 / 2 is unsupported by match_number_format(), either leave it unsupported in PR-1 or document why a later ADR should extend the numerics pack.
Decimals
Examples:
0.50.250.75
Use match_number_format() where possible.
Do not silently add .5 if ADR-0128 currently refuses it. If .5 is desired, document it as a future numerics-pack extension unless the PR intentionally includes a reviewed ADR-0128 extension.
Percentages
Examples:
50%25%75%100%
Use match_number_format() where possible.
For phrase forms such as 50 percent, implement conservatively and test exact span/provenance. Do not let 50 percentage points become an unhazarded scalar-of-base.
Unicode fractions
Examples:
½⅓⅔¼¾
Use lookup_fraction() where possible.
Hazard policy
The facade may emit scalar candidates with hazards, but it must not emit ambiguous surfaces as silently safe.
Required hazard labels:
unbound_base_quantityhalf_durationquarter_coinquarter_calendar_periodquarter_school_termthird_ordinalordinal_contextcurrency_contexttemporal_contextpercent_change_vs_percent_ofmultiple_scalar_ambiguity
Examples:
halfby itself should carryunbound_base_quantityunless a caller-supplied context explicitly narrows it.half an hourshould carrytemporal_contextandhalf_duration, or the facade may refuse to emit it as a usable scalar candidate.third placeshould carryordinal_contextandthird_ordinal, or be refused as scalar.quarter dollarshould carrycurrency_contextandquarter_coin, or be refused as scalar.50 percentage pointsshould carrypercent_change_vs_percent_of, or be refused as scalar-of-base.
A downstream derivation organ or future ProblemFrame must still license the base quantity, actor, object, unit, relation, and question target.
Provenance policy
Every emitted ScalarLexeme must have:
- exact
source_surface - exact
source_span provenance == "problem_text"- no derived or synthesized values
A scalar value produced by arithmetic is not a ScalarLexeme and must not be represented as problem-text provenance.
Determinism requirements
- Extraction order must be deterministic by source span.
- Duplicate/overlapping scalar candidates must be handled deterministically.
- If two candidates occupy the same span, prefer the more specific surface family only if the rule is explicitly tested.
- If ambiguous, refuse or emit hazards; do not guess.
Tests
Create:
tests/test_language_packs_scalar_equivalence.py
Required tests:
halfmaps toFraction(1, 2)withproblem_textprovenance.one halfmaps toFraction(1, 2)with exact span.one-halfmaps toFraction(1, 2)with exact span if supported bylookup_fraction(); otherwise document unsupported.1/2maps toFraction(1, 2).3/4maps toFraction(3, 4).0.5maps toFraction(1, 2).0.25maps toFraction(1, 4).50%maps toFraction(1, 2).50 percentmaps toFraction(1, 2)if phrase support is implemented.three quartersmaps toFraction(3, 4).- Unicode
½maps toFraction(1, 2)if supported. source_surfacepreserves exact text.source_spanslices the original text to exactlysource_surface.- extraction order is by source span.
- multiple scalar surfaces emit multiple deterministic lexemes.
third placeis hazardous or refused; it must not be silently safe.quarter dollaris hazardous or refused; it must not be silently safe.half an houris hazardous or refused; it must not be silently safe.50 percentage pointsis hazardous or refused; it must not be silently safe.- unsupported forms from ADR-0128 remain unsupported unless explicitly extended.
Do not weaken existing ADR-0128 tests.
Documentation
Create:
docs/analysis/scalar-equivalence-facade-pr1-2026-06-18.md
Required sections:
- Purpose
- Relationship to #828
- Relationship to ADR-0128/en_numerics_v1
- Why this is a facade, not a new pack
- Supported canonical scalars
- Supported surfaces
- Explicit unsupported surfaces
- Hazard policy
- Provenance policy
- Determinism policy
- Non-goals
- Tests
- Next PR recommendation
Validation
Run:
git diff --check origin/main...HEAD
pytest tests/test_language_packs_scalar_equivalence.py -q
pytest tests/test_adr_0128_numeric_formats.py -q
pytest tests/test_math_candidate_graph_xhigh_sprint13_lift.py -q
pytest tests/test_math_candidate_graph_sprint12_singleton_contract_lift.py -q
pytest tests/test_math_candidate_graph_sprint11_cluster_contract_lift.py -q
Confirm no score change:
uv run python - <<'PY'
from evals.gsm8k_math.train_sample.v1.runner import _CASES_PATH, _load_cases, build_report
r = build_report(_load_cases(_CASES_PATH))
c = r["counts"]
print("train_sample:", c["correct"], c["refused"], c["wrong"])
print("wrong_ids:", sorted(x["case_id"] for x in r["per_case"] if x["verdict"] == "wrong"))
PY
Expected:
train_sample: 30 20 0
wrong_ids: []
Holdout safety:
uv run python - <<'PY'
from evals.gsm8k_math.holdout_dev.v1.runner import build_report
r = build_report()
c = r["counts"]
print("holdout_dev:", c, "n=", r["n"])
print("wrong_ids:", [x["case_id"] for x in r["per_case"] if x["verdict"] == "wrong"])
PY
Expected:
wrong_ids: []
If practical:
uv run python -m core.cli test --suite smoke -q
Commit / PR
Suggested commit:
feat(language-packs): add scalar equivalence facade
Suggested PR title:
feat(language-packs): add scalar equivalence facade
PR body must include:
- Implements
ScalarEquivalencefacade over ADR-0128/en_numerics_v1. - No serving integration.
- No
generate/math_candidate_graph.pychange. - No A2 organ changes.
- No report.json change.
- No sealed artifact change.
- Preserves
train_sample30/20/0. - Preserves
holdout_devwrong=0. - Includes provenance and ambiguity hazards.
- Validation outputs.
Do not merge without review.