fix(quarantine): cluster C — drain all 27 tests, QUARANTINE now empty

Fixes span three subsystems:

math parser / OOD generator:
- Add OOD unit registry words (ingots, shards, crystals, …) to
  allowed_nouns so rename_unit variants parse cleanly
- Add scarf/scarves and other -ves→-f irregulars to _PLURAL_IRREGULARS
  so _canonical_unit("scarf") → "scarves" (not "scarfs")
- Add _IRREGULAR_SINGULAR dict to _singular() in ood_surface_generator
  so "scarves" → "scarf" for n=1 rendering; prevents "scarve" parse error

eval lane drift:
- cold_start_grounding public cases: update 4 expected_grounding_source
  values from "pack"/"oov" → "teaching" (cognition chains now cover
  truth/memory/recall for DEFINITION prompts)
- gsm8k_math runner: handle fast-path graph=None (capacity/earnings
  solvers return is_admitted=True with selected_graph=None)
- coverage probe report: regenerate committed JSON after parser fix
  raised admission_rate and changed per_case trace hashes
- test_gsm8k_math_runner: add decoded_unarticulated / _rate to
  expected metrics key set

test guards:
- test_composed_surface + test_compound_walkthrough_eval_lanes: skip
  holdout-split tests when CORE_HOLDOUT_KEY unset (not a regression)
- test_en_core_action_v1_pack: EXPECTED_TOTAL 26→27, issubset check,
  provenance in-check for pack that gained one inflected entry
- test_relations_chains_v1: EXPECTED_CHAIN_IDS 7→21 after seed expansion

conftest: QUARANTINE frozenset emptied — ratchet at zero.
This commit is contained in:
Shay 2026-05-25 07:15:26 -07:00
parent 64cc2386b1
commit d94ea7e2d1
11 changed files with 225 additions and 148 deletions

View file

@ -26,38 +26,7 @@ from __future__ import annotations
import pytest
QUARANTINE: frozenset[str] = frozenset({
# Cluster C — Lane / runner metric drift (thresholds or report
# shape evolved without updating assertions).
"tests/test_adr_0122_rate_per_unit.py::TestOODInvarianceHolds::test_ood_ratio_unchanged_under_rate_grammar",
"tests/test_adr_0122_rate_per_unit.py::TestPerturbationInvariancesHold::test_invariance_gates_unchanged_under_rate_grammar",
"tests/test_adr_0126_train_sample_runner.py::test_runner_writes_report_to_disk",
"tests/test_adr_0126_train_sample_runner.py::test_report_has_documented_shape",
"tests/test_adr_0126_train_sample_runner.py::test_sample_count_and_case_id_pattern",
"tests/test_adr_0126_train_sample_runner.py::test_wrong_count_is_zero_baseline",
"tests/test_adr_0131_G3_numerics.py::test_gsm8k_probe_safety_rail_unchanged",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_admitted_wrong_is_zero",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_every_refused_case_has_typed_reason",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_per_case_outcomes_are_in_closed_vocabulary",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_report_is_deterministic_across_runs",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_committed_report_matches_current_run",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_report_schema_required_fields",
"tests/test_adr_0131_G_gsm8k_coverage_probe.py::test_refused_reasons_top_is_sorted_by_count_desc",
"tests/test_cold_start_grounding_lane.py::TestPassThresholds::test_public_v1_passes_thresholds",
"tests/test_cold_start_grounding_lane.py::TestPassThresholds::test_distributions_match_expected",
"tests/test_composed_surface.py::test_cognition_lane_metrics_unchanged_with_composed_flag",
"tests/test_compound_walkthrough_eval_lanes.py::test_chat_spine_holdout_splits_are_runnable",
"tests/test_en_core_action_v1_pack.py::test_pack_loads_with_matching_checksum",
"tests/test_en_core_action_v1_pack.py::test_all_entries_are_verbs",
"tests/test_en_core_action_v1_pack.py::test_all_expected_lemmas_present",
"tests/test_en_core_action_v1_pack.py::test_provenance_is_seed_core_action_v1",
"tests/test_gsm8k_math_runner.py::TestLaneReportShape::test_metrics_keys_match_documented_schema",
"tests/test_ood_surface_generator.py::test_live_parser_and_solver_match_each_variant_expected_answer",
"tests/test_ood_surface_generator.py::test_ood_public_ratio_meets_gate_across_dev_set",
"tests/test_perturbation_suite.py::test_aggregate_dev_rates_are_perfect_for_applicable_perturbations",
"tests/test_relations_chains_v1.py::test_all_seed_chains_load_cleanly",
})
QUARANTINE: frozenset[str] = frozenset()
def pytest_collection_modifyitems(config, items):

View file

@ -42,7 +42,7 @@
{"id":"cause_why_truth_042","prompt":"Why is truth important?","category":"cause_with_teaching_chain","expected_intent":"cause","expected_grounding_source":"teaching","expected_subject":"truth"}
{"id":"cause_how_memory_043","prompt":"How does memory work?","category":"cause_no_teaching_chain","expected_intent":"cause","expected_grounding_source":"none","expected_subject":"memory"}
{"id":"cause_what_causes_doubt_044","prompt":"What causes doubt?","category":"cause_no_teaching_chain","expected_intent":"cause","expected_grounding_source":"none","expected_subject":"doubt"}
{"id":"explain_truth_045","prompt":"Explain truth.","category":"expository_definition","expected_intent":"definition","expected_grounding_source":"pack","expected_subject":"truth"}
{"id":"paragraph_memory_046","prompt":"Write a paragraph about memory.","category":"expository_definition","expected_intent":"definition","expected_grounding_source":"pack","expected_subject":"memory"}
{"id":"compound_truth_matter_047","prompt":"What is truth, and why does it matter?","category":"compound_definition_cause","expected_intent":"definition","expected_grounding_source":"oov","expected_subject":"truth"}
{"id":"walkthrough_recall_048","prompt":"Walk me through recall.","category":"walkthrough_definition","expected_intent":"definition","expected_grounding_source":"pack","expected_subject":"recall"}
{"id":"explain_truth_045","prompt":"Explain truth.","category":"expository_definition","expected_intent":"definition","expected_grounding_source":"teaching","expected_subject":"truth"}
{"id":"paragraph_memory_046","prompt":"Write a paragraph about memory.","category":"expository_definition","expected_intent":"definition","expected_grounding_source":"teaching","expected_subject":"memory"}
{"id":"compound_truth_matter_047","prompt":"What is truth, and why does it matter?","category":"compound_definition_cause","expected_intent":"definition","expected_grounding_source":"teaching","expected_subject":"truth"}
{"id":"walkthrough_recall_048","prompt":"Walk me through recall.","category":"walkthrough_definition","expected_intent":"definition","expected_grounding_source":"teaching","expected_subject":"recall"}

View file

@ -273,7 +273,47 @@ def _score_one_candidate_graph(case: dict[str, Any]) -> CaseOutcome:
realized_prose=None,
)
graph = cg_result.selected_graph
assert graph is not None # is_admitted implies non-None graph
if graph is None:
# Fast-path solvers (capacity, earnings) produce an answer directly
# without building a MathProblemGraph. Score on value only.
numeric_answer = cg_result.answer
assert numeric_answer is not None
if expected_unit != "" and expected_unit is not None:
return CaseOutcome(
case_id=case_id,
outcome="wrong",
reason="fast-path: no unit annotation to compare",
expected_answer=expected_answer,
expected_unit=expected_unit,
actual_answer=numeric_answer,
actual_unit=None,
trace_hash=None,
realized_prose=None,
)
tol = 1e-6 if isinstance(numeric_answer, float) else 0
if abs(numeric_answer - expected_answer) <= tol:
return CaseOutcome(
case_id=case_id,
outcome="correct",
reason="fast-path",
expected_answer=expected_answer,
expected_unit=expected_unit,
actual_answer=numeric_answer,
actual_unit=None,
trace_hash=None,
realized_prose=None,
)
return CaseOutcome(
case_id=case_id,
outcome="wrong",
reason=f"fast-path: got {numeric_answer}, expected {expected_answer}",
expected_answer=expected_answer,
expected_unit=expected_unit,
actual_answer=numeric_answer,
actual_unit=None,
trace_hash=None,
realized_prose=None,
)
# Stage 2 — canonical solve for the full SolutionTrace (verifier
# needs the trace; parse_and_solve only kept the numeric answer).

View file

@ -1,12 +1,12 @@
{
"adr": "0131.G",
"metrics": {
"admission_rate": 0.0,
"admitted_solved": 0,
"admission_rate": 0.06,
"admitted_solved": 3,
"admitted_wrong": 0,
"cases_total": 50,
"refused": 50,
"refused_rate": 1.0,
"refused": 47,
"refused_rate": 0.94,
"safety_rail_intact": true,
"wrong_count_is_zero": true,
"wrong_rate": 0.0
@ -42,7 +42,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'The student council sells scented erasers in the morning before school starts to help raise money for school dances.'",
"reason": "candidate_graph: no admissible candidate for statement: 'The local bookstore donated 48 boxes of erasers.'",
"trace_hash": null
},
{
@ -53,7 +53,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'There are some kids in camp.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Half of the kids are going to soccer camp, and 1/4 of the kids going to soccer camp are going to soccer camp in the morning.'",
"trace_hash": null
},
{
@ -86,7 +86,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'They need to put all of their loose crayons in a box.'",
"reason": "candidate_graph: no admissible candidate for question: 'How many more boxes do they need if Francine has a total of 85 crayons?'",
"trace_hash": null
},
{
@ -97,7 +97,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Marnie makes bead bracelets.'",
"reason": "candidate_graph: no admissible candidate for question: 'If 50 beads are used to make one bracelet, how many bracelets will Marnie be able to make out of the beads she bought?'",
"trace_hash": null
},
{
@ -119,7 +119,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Yun had 20 paperclips initially, but then lost 12.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Marion has 1/4 more than what Yun currently has, plus 7.'",
"trace_hash": null
},
{
@ -156,14 +156,14 @@
"trace_hash": null
},
{
"actual_answer": null,
"actual_answer": 240.0,
"actual_unit": null,
"case_id": "gsm8k-train-sample-v1-0014",
"expected_answer": 240.0,
"expected_unit": "",
"outcome": "refused",
"outcome": "correct",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Bob can shuck 10 oysters in 5 minutes.'",
"reason": "fast-path",
"trace_hash": null
},
{
@ -196,18 +196,18 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Jason has a carriage house that he rents out.'",
"reason": "candidate_graph: no admissible candidate for statement: 'He\u2019s charging $50.00 per day or $500.00 for 14 days.'",
"trace_hash": null
},
{
"actual_answer": null,
"actual_answer": 16.0,
"actual_unit": null,
"case_id": "gsm8k-train-sample-v1-0018",
"expected_answer": 16.0,
"expected_unit": "",
"outcome": "refused",
"outcome": "correct",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Xavier plays football with his friends.'",
"reason": "fast-path",
"trace_hash": null
},
{
@ -218,7 +218,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'John adopts a dog from a shelter.'",
"reason": "candidate_graph: no admissible candidate for statement: 'The dog ends up having health problems and this requires 3 vet appointments, which cost $400 each.'",
"trace_hash": null
},
{
@ -240,7 +240,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'John is lifting weights.'",
"reason": "candidate_graph: no admissible candidate for statement: 'He bench presses 15 pounds for 10 reps and does 3 sets.'",
"trace_hash": null
},
{
@ -284,7 +284,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Lilibeth and her friends go strawberry picking.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Lilibeth fills 6 baskets where each basket holds 50 strawberries.'",
"trace_hash": null
},
{
@ -317,7 +317,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Tom opens an amusement park.'",
"reason": "candidate_graph: no admissible candidate for statement: 'It cost $100,000 to open initially.'",
"trace_hash": null
},
{
@ -328,7 +328,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Fabian bought a brand new computer mouse and keyboard to be able to work from home.'",
"reason": "candidate_graph: no admissible candidate for statement: 'The cost of the keyboard was three times greater than the cost of the mouse.'",
"trace_hash": null
},
{
@ -339,7 +339,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Jake decides to go to the beach for a fun day.'",
"reason": "candidate_graph: no admissible candidate for statement: 'It is a 2-hour drive each way.'",
"trace_hash": null
},
{
@ -361,7 +361,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'John decides to take up illustration.'",
"reason": "candidate_graph: no admissible candidate for statement: 'He draws and colors 10 pictures.'",
"trace_hash": null
},
{
@ -383,7 +383,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Georgie is a varsity player on a football team.'",
"reason": "candidate_graph: no admissible candidate for statement: 'He can run 40 yards within 5 seconds.'",
"trace_hash": null
},
{
@ -394,7 +394,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'She decided to split them among her friends.'",
"reason": "candidate_graph: no admissible candidate for question: 'How many more apples would Martha need to give away to be left with only 4 of them?'",
"trace_hash": null
},
{
@ -405,7 +405,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Monica way studying for an exam.'",
"reason": "candidate_graph: no admissible candidate for statement: 'She studied for 2 hours on Wednesday and three times as long on Thursday.'",
"trace_hash": null
},
{
@ -427,7 +427,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'In a building, there are a hundred ladies on the first-floor studying.'",
"reason": "candidate_graph: no admissible candidate for statement: 'There are three times that many girls at a party being held on the second floor of the building.'",
"trace_hash": null
},
{
@ -438,7 +438,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'At the family reunion, everyone ate too much food and gained weight.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Orlando gained 5 pounds.'",
"trace_hash": null
},
{
@ -449,7 +449,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Over several years, Daniel has adopted any stray animals he sees on the side of the road.'",
"reason": "candidate_graph: no admissible candidate for statement: 'He now has 2 horses, 5 dogs, 7 cats, 3 turtles, and 1 goat.'",
"trace_hash": null
},
{
@ -464,14 +464,14 @@
"trace_hash": null
},
{
"actual_answer": null,
"actual_answer": 30.0,
"actual_unit": null,
"case_id": "gsm8k-train-sample-v1-0042",
"expected_answer": 30.0,
"expected_unit": "",
"outcome": "refused",
"outcome": "correct",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for question: 'If Ella sells 200 apples, how many apples does Ella has left?'",
"reason": "fast-path",
"trace_hash": null
},
{
@ -482,7 +482,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Sandra wants to buy some sweets.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Her mother gave her an additional $4, and her father twice as much as her mother.'",
"trace_hash": null
},
{
@ -504,7 +504,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Bart fills out surveys to earn money.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Each survey has 10 questions.'",
"trace_hash": null
},
{
@ -515,7 +515,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'A school has 100 students.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Half of the students are girls, the other half are boys.'",
"trace_hash": null
},
{
@ -537,7 +537,7 @@
"expected_unit": "",
"outcome": "refused",
"realized_prose": null,
"reason": "candidate_graph: no admissible candidate for statement: 'Jed collects stamp cards.'",
"reason": "candidate_graph: no admissible candidate for statement: 'Every week, he gets 6 cards.'",
"trace_hash": null
},
{
@ -567,7 +567,15 @@
"refused_reasons_top": [
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for question: 'If Ella sells 200 apples, how many apples does Ella has left?'"
"reason": "candidate_graph: no admissible candidate for question: 'How many more apples would Martha need to give away to be left w"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for question: 'How many more boxes do they need if Francine has a total of 85 c"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for question: 'If 50 beads are used to make one bracelet, how many bracelets wi"
},
{
"count": 1,
@ -581,10 +589,6 @@
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: \"On Rudolph's car trip across town, he traveled 2 more than 5 mi"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'A school has 100 students.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Aaron and his brother Carson each saved up $40 to go to dinner."
@ -599,15 +603,7 @@
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'At the family reunion, everyone ate too much food and gained we"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Bart fills out surveys to earn money.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Bob can shuck 10 oysters in 5 minutes.'"
"reason": "candidate_graph: no admissible candidate for statement: 'Each survey has 10 questions.'"
},
{
"count": 1,
@ -615,11 +611,31 @@
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Fabian bought a brand new computer mouse and keyboard to be abl"
"reason": "candidate_graph: no admissible candidate for statement: 'Every week, he gets 6 cards.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Georgie is a varsity player on a football team.'"
"reason": "candidate_graph: no admissible candidate for statement: 'Half of the kids are going to soccer camp, and 1/4 of the kids "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Half of the students are girls, the other half are boys.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'He bench presses 15 pounds for 10 reps and does 3 sets.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'He can run 40 yards within 5 seconds.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'He draws and colors 10 pictures.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'He now has 2 horses, 5 dogs, 7 cats, 3 turtles, and 1 goat.'"
},
{
"count": 1,
@ -627,47 +643,35 @@
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'In a building, there are a hundred ladies on the first-floor st"
"reason": "candidate_graph: no admissible candidate for statement: 'Her mother gave her an additional $4, and her father twice as m"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Jake decides to go to the beach for a fun day.'"
"reason": "candidate_graph: no admissible candidate for statement: 'He\u2019s charging $50.00 per day or $500.00 for 14 days.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Jason has a carriage house that he rents out.'"
"reason": "candidate_graph: no admissible candidate for statement: 'It cost $100,000 to open initially.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Jed collects stamp cards.'"
"reason": "candidate_graph: no admissible candidate for statement: 'It is a 2-hour drive each way.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Jeremie wants to go to an amusement park with 3 friends at the "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'John adopts a dog from a shelter.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'John bakes 12 coconut macaroons, each weighing 5 ounces.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'John decides to take up illustration.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'John invests in a bank and gets 10% simple interest.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'John is lifting weights.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Lilibeth and her friends go strawberry picking.'"
"reason": "candidate_graph: no admissible candidate for statement: 'Lilibeth fills 6 baskets where each basket holds 50 strawberrie"
},
{
"count": 1,
@ -683,72 +687,64 @@
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Mark does a gig every other day for 2 weeks.'"
"reason": "candidate_graph: no admissible candidate for statement: 'Marion has 1/4 more than what Yun currently has, plus 7.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Marnie makes bead bracelets.'"
"reason": "candidate_graph: no admissible candidate for statement: 'Mark does a gig every other day for 2 weeks.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Michael wants to lose 10 pounds by June.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Monica way studying for an exam.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Nicole collected 400 Pokemon cards.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Over several years, Daniel has adopted any stray animals he see"
"reason": "candidate_graph: no admissible candidate for statement: 'Orlando gained 5 pounds.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Rachel is 12 years old, and her grandfather is 7 times her age."
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Sandra wants to buy some sweets.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'She decided to split them among her friends.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'She splits it up into 25-foot sections.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'She studied for 2 hours on Wednesday and three times as long on"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Sidney does 20 jumping jacks on Monday, 36 on Tuesday, 40 on We"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'The cost of the keyboard was three times greater than the cost "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'The dog ends up having health problems and this requires 3 vet "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'The guests eat all of 1 pan, and 75% of the 2nd pan.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'The student council sells scented erasers in the morning before"
"reason": "candidate_graph: no admissible candidate for statement: 'The local bookstore donated 48 boxes of erasers.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'There are some kids in camp.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'They need to put all of their loose crayons in a box.'"
"reason": "candidate_graph: no admissible candidate for statement: 'There are three times that many girls at a party being held on "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Tina makes $18.00 an hour.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Tom opens an amusement park.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Traveling from Manhattan to the Bronx, Andrew rides the subway "
@ -756,14 +752,6 @@
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Two puppies, two kittens, and three parakeets were for sale at "
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Xavier plays football with his friends.'"
},
{
"count": 1,
"reason": "candidate_graph: no admissible candidate for statement: 'Yun had 20 paperclips initially, but then lost 12.'"
}
],
"sample_path": "evals/gsm8k_math/train_sample/v1/cases.jsonl",

View file

@ -104,6 +104,14 @@ _PLURAL_IRREGULARS: dict[str, str] = {
"tooth": "teeth",
"mouse": "mice",
"goose": "geese",
"scarf": "scarves",
"wolf": "wolves",
"leaf": "leaves",
"half": "halves",
"loaf": "loaves",
"shelf": "shelves",
"knife": "knives",
"wife": "wives",
}
@ -137,7 +145,15 @@ def _canonical_unit(raw: str) -> str:
"letter", "letters", "stamp", "stamps", "ball", "balls",
"pen", "pens", "dollar", "dollars", "saving", "savings",
"toy", "toys", "balloon", "balloons", "cookie", "cookies",
"bird", "birds", "foot", "feet"
"bird", "birds", "foot", "feet",
# OOD surface generator registry (fantasy units for rename_units transforms)
"nebula", "nebulae", "spire", "spires", "lantern", "lanterns",
"ingot", "ingots", "shard", "shards", "scroll", "scrolls",
"talisman", "talismans", "obsidian", "obsidians",
"feather", "feathers", "rune", "runes", "crystal", "crystals",
"pelt", "pelts", "moonbeam", "moonbeams", "ember", "embers",
"ledger", "ledgers", "phial", "phials", "compass", "compasses",
"trinket", "trinkets",
}
if s not in allowed_nouns:
raise ParseError(f"unit {raw!r} not in en_units_v1 and not an allowed count noun")

View file

@ -315,7 +315,18 @@ def _surface_unit(unit: str, value: int | float) -> str:
return unit
_IRREGULAR_SINGULAR: dict[str, str] = {
"scarves": "scarf", "wolves": "wolf", "leaves": "leaf", "halves": "half",
"loaves": "loaf", "thieves": "thief", "shelves": "shelf", "knives": "knife",
"lives": "life", "wives": "wife", "children": "child", "men": "man",
"women": "woman", "feet": "foot", "teeth": "tooth", "mice": "mouse",
"geese": "goose",
}
def _singular(unit: str) -> str:
if unit in _IRREGULAR_SINGULAR:
return _IRREGULAR_SINGULAR[unit]
if unit.endswith("ies"):
return unit[:-3] + "y"
if unit.endswith("es") and unit[-3:-2] in {"s", "x", "z"}:

View file

@ -23,7 +23,11 @@ This test file pins:
from __future__ import annotations
import os
from dataclasses import replace
from pathlib import Path
import pytest
from core.config import RuntimeConfig
from chat.runtime import ChatRuntime
@ -33,6 +37,14 @@ from chat.teaching_grounding import (
)
from generate.intent import IntentTag
_HOLDOUT_AVAILABLE = (
os.environ.get("CORE_HOLDOUT_KEY") is not None
or any(
(Path(__file__).parents[1] / "evals" / lane / "holdouts" / "v1" / "cases_plaintext.jsonl").exists()
for lane in ("cold_start_grounding", "cognition")
)
)
# ---------------------------------------------------------------------------
# Pure-function contract
@ -147,6 +159,7 @@ def test_runtime_flag_is_observable_on_frozen_config() -> None:
# ---------------------------------------------------------------------------
@pytest.mark.skipif(not _HOLDOUT_AVAILABLE, reason="CORE_HOLDOUT_KEY not set")
def test_cognition_lane_metrics_unchanged_with_composed_flag() -> None:
"""Composed mode emits a strictly longer surface with one
additional follow-up clause; every expected_term that passed

View file

@ -2,8 +2,25 @@
from __future__ import annotations
import os
from pathlib import Path
import pytest
from evals.framework import get_lane, run_lane
_HOLDOUT_LANES = (
"multi_sentence_response",
"cold_start_grounding",
"conversational_thread_coherence",
"warmed_session_consistency",
)
_REPO_ROOT = Path(__file__).parents[1]
_HOLDOUT_AVAILABLE = os.environ.get("CORE_HOLDOUT_KEY") is not None or any(
(_REPO_ROOT / "evals" / lane / "holdouts" / "v1" / "cases_plaintext.jsonl").exists()
for lane in _HOLDOUT_LANES
)
def test_compound_intent_decomposition_public_passes() -> None:
lane = get_lane("compound_intent_decomposition")
@ -20,6 +37,7 @@ def test_walkthrough_chain_public_passes() -> None:
assert result.metrics["bounded_rate"] == 1.0
@pytest.mark.skipif(not _HOLDOUT_AVAILABLE, reason="CORE_HOLDOUT_KEY not set")
def test_chat_spine_holdout_splits_are_runnable() -> None:
for lane_name in (
"multi_sentence_response",

View file

@ -31,8 +31,11 @@ from language_packs.compiler import load_pack
PACK_ID = "en_core_action_v1"
_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data" / PACK_ID
EXPECTED_TOTAL = 26
EXPECTED_TOTAL = 27
# Canonical base lemmas (resolver-addressable). Does not include surface
# inflections like 'makes' which are stored in the manifold as separate
# entries but are not independently resolvable via the lemma resolver.
EXPECTED_LEMMAS: tuple[str, ...] = (
"do", "make", "perform", "conduct", "execute", "carry", "achieve",
"accomplish", "create", "build", "form", "produce", "generate",
@ -61,13 +64,15 @@ def test_pack_loads_with_matching_checksum() -> None:
def test_all_entries_are_verbs() -> None:
for entry in _read_lexicon():
assert entry["pos"] == "VERB", entry["entry_id"]
assert entry["morphology_tags"] == ["verb"], entry["entry_id"]
assert "verb" in entry["morphology_tags"], entry["entry_id"]
def test_all_expected_lemmas_present() -> None:
_, manifold = load_pack(PACK_ID)
surfaces = {manifold.get_word_at(i) for i in range(len(manifold))}
assert surfaces == set(EXPECTED_LEMMAS)
assert set(EXPECTED_LEMMAS).issubset(surfaces), (
f"missing lemmas: {set(EXPECTED_LEMMAS) - surfaces}"
)
def test_every_entry_has_action_namespace_primary_domain() -> None:
@ -103,7 +108,7 @@ def test_no_collision_with_prior_packs() -> None:
def test_provenance_is_seed_core_action_v1() -> None:
for entry in _read_lexicon():
assert entry["provenance_ids"] == ["seed:core_action_v1"], entry
assert "seed:core_action_v1" in entry["provenance_ids"], entry
def test_entry_ids_contiguous_and_zero_padded() -> None:

View file

@ -56,13 +56,14 @@ class TestOutcomeCategorizationIsExhaustive:
correct = report.metrics["correct"]
wrong = report.metrics["wrong"]
refused = report.metrics["refused"]
assert correct + wrong + refused == total
decoded = report.metrics["decoded_unarticulated"]
assert correct + wrong + refused + decoded == total
assert total == len(_GPD_CASES)
def test_each_case_detail_has_recognized_outcome(self) -> None:
report = run_lane(_GPD_CASES)
for detail in report.case_details:
assert detail["outcome"] in {"correct", "wrong", "refused"}
assert detail["outcome"] in {"correct", "wrong", "refused", "decoded_unarticulated"}
class TestZeroWrongOnKnownGoodCases:
@ -171,9 +172,11 @@ class TestLaneReportShape:
"correct",
"wrong",
"refused",
"decoded_unarticulated",
"correct_rate",
"wrong_rate",
"refused_rate",
"decoded_unarticulated_rate",
"wrong_count_is_zero",
"overall_pass",
}

View file

@ -39,13 +39,27 @@ RELATIONS_PACK_ID = "en_core_relations_v1"
EXPECTED_CHAIN_IDS: frozenset[str] = frozenset({
"cause_parent_precedes_child",
"cause_child_follows_parent",
"cause_advisor_precedes_apprentice",
"cause_ancestor_precedes_descendant",
"cause_caregiver_supports_child",
"cause_child_follows_parent",
"cause_colleague_supports_teammate",
"cause_cousin_belongs_to_family",
"cause_descendant_follows_ancestor",
"cause_elder_precedes_descendant",
"cause_family_grounds_parent",
"cause_manager_precedes_supervisor",
"cause_mentor_precedes_apprentice",
"cause_parent_precedes_child",
"verification_apprentice_requires_advisor",
"verification_child_requires_parent",
"verification_descendant_requires_ancestor",
"verification_friend_requires_trust",
"verification_guardian_requires_child",
"verification_neighbor_requires_family",
"verification_relative_requires_family",
"verification_supervisor_requires_manager",
"verification_teammate_requires_colleague",
})