core/tests/test_morphology_irregular.py
Shay 3952da11bc fix(gaps): close G1+G2+G3 + identity vocab + pipeline safety-stub honour
Six contained-tier gap closures from the cross-phase gap audit.
Every gaps.md file involved gains a resolution block; the historical
findings are preserved for traceability.

Identity (teaching/review.py)
- _ROLE_FRAMES gains the philosophical-axis family (axiology, ontology,
  telos, ethos, epistemology, morality, ethics, virtues, norms,
  doctrine, creed, convictions, beliefs, values, principles).
- _REDIRECT_VERBS gains the dismissal family (disregard, dismiss,
  bypass, circumvent, renounce, reject, repudiate).
- Closes multi_agent_composition holdout MAC-HOLD-001 ("disregard your
  axiology") and the matching adversarial_identity gap.
- Multi-agent holdouts: 8/8 attacks rejected, 3/3 legits accepted.

Pipeline (core/cognition/pipeline.py + docs/runtime_contracts.md)
- When the unknown-domain gate fires, ChatRuntime returns the
  "I don't have field coordinates for that yet." stub and
  vault_hits == 0.  The pipeline now honours that stub as the
  user-facing surface instead of overriding with the realizer's
  fallback articulation.  walk_surface is unchanged either way.
- New contract test
  tests/test_semantic_realizer_integration.py::test_pipeline_honours_safety_stub_when_gate_fires
  locks the contract; the existing semantic-surface test now primes
  the vault first so the gate doesn't fire on the probe.
- Closes calibration gaps.md Finding 2.

Realizer morphology (generate/morphology.py)
- G1: ~100-entry irregular-verb table replaces the previous list which
  contained only regular forms.  Includes bind→bound, run→ran,
  stand→stood, write→wrote/written, eat→ate/eaten, fly→flew/flown,
  swim→swam/swum, etc.
- CVC doubling rule for -ed and -ing (stop→stopped/stopping,
  plan→planned, run→running).
- Short-ies disambiguation (die/lie/tie keep -ie- in the base; cry/fly
  collapse to -y).  Lie is also irregular (lay/lain) — uses
  _IRREGULAR_FORMS first.
- 28-case regression test (tests/test_morphology_irregular.py).

Realizer plural agreement (generate/templates.py)
- G2: under universal/existential/many/few/most quantifiers, count-noun
  subjects pluralise (molecule → molecules) and the verb de-conjugates
  (binds → bind).  Negation toggles does-not → do-not.  Aspect toggles
  has → have, is → are.  All other constructions unchanged.
- Mass nouns (evidence, wisdom, knowledge, truth, water, …) stay
  singular under quantifiers — "all evidence supports truth" is right;
  "all evidences support" would be wrong English.
- 17-case regression test
  (tests/test_realizer_quantifier_agreement.py) covering count vs mass,
  irregular plurals (child→children, analysis→analyses), and the
  quantifier-tense / quantifier-aspect / quantifier-negation grid.

Rubric punctuation tolerance (evals/grammatical_coverage/runner.py)
- G3: _check_word_order strips trailing/leading punctuation
  (.,;:!?—–) before exact-word comparison so "river," still satisfies
  word_order=["river"].  must_contain also accepts punctuation-
  stripped token matches.
- Affects every lane that uses grammatical_coverage scoring; the OOD
  case generators no longer need to pin punctuated accept_surfaces for
  C06.

Case generator + lane regeneration
- scripts/generate_english_fluency_ood.py uses generate.templates.pluralize
  for C07/C08 must_contain + word_order so case-side constraints stay
  aligned with the (more correct) realizer.
- All Phase 5 OOD lane cases (5.1, 5.4–5.7) regenerated; results files
  re-scored.

CLI (core/cli.py)
- cmd_eval no longer crashes on lanes whose case_details use "id"
  instead of "case_id" (adversarial_identity, multi_agent_composition).
- Cognition CLI lane gains the two new morphology/quantifier
  regression test files.

Lane sweep (all 100%, no regression):
  english_fluency_ood              117/117 public + 39/39 holdouts
  elementary_mathematics_ood       117/117 + 39/39
  foundational_physics_ood         117/117 + 39/39
  foundational_biology_ood         117/117 + 39/39
  classical_literature_ood         117/117 + 39/39
  grammatical_coverage             back to 100% on its own seed cases
  hebrew_fluency / koine_greek_fluency  3/3 each

CLI lane health:
  smoke 54, runtime 19, teaching 17, packs 6, cognition 103 (was 57),
  algebra 132.
2026-05-16 21:21:06 -07:00

102 lines
3.7 KiB
Python

"""Realizer morphology — irregular verb + doubling regression tests.
Closes english_fluency_ood gaps.md G1 (irregular past tense): the
realizer's `past_tense` no longer turns `bind` into `binded`. Also
locks the CVC-doubling rule (`run` → `running`, `stop` → `stopped`)
and the short-`ies` exception (`die` keeps the `-ie-` stem).
These checks are tight enough that any regression in
`generate/morphology.py` is caught immediately.
"""
from __future__ import annotations
import pytest
from generate.morphology import (
base_form,
past_participle,
past_tense,
present_participle,
)
# (3sg, expected_past, expected_past_participle, expected_present_participle, expected_base)
_IRREGULAR_CASES: list[tuple[str, str, str, str, str]] = [
("binds", "bound", "bound", "binding", "bind"),
("runs", "ran", "run", "running", "run"),
("stands", "stood", "stood", "standing", "stand"),
("writes", "wrote", "written", "writing", "write"),
("brings", "brought", "brought", "bringing", "bring"),
("thinks", "thought", "thought", "thinking", "think"),
("eats", "ate", "eaten", "eating", "eat"),
("breaks", "broke", "broken", "breaking", "break"),
("flies", "flew", "flown", "flying", "fly"),
("swims", "swam", "swum", "swimming", "swim"),
("knows", "knew", "known", "knowing", "know"),
("hides", "hid", "hidden", "hiding", "hide"),
]
@pytest.mark.parametrize("verb_3sg,past,pp,pres,base", _IRREGULAR_CASES)
def test_irregular_verb_forms(
verb_3sg: str, past: str, pp: str, pres: str, base: str
) -> None:
assert past_tense(verb_3sg) == past
assert past_participle(verb_3sg) == pp
assert present_participle(verb_3sg) == pres
assert base_form(verb_3sg) == base
# Doubling rule: CVC bases double the final consonant before -ed / -ing.
_CVC_CASES: list[tuple[str, str, str]] = [
("stops", "stopped", "stopping"),
("plans", "planned", "planning"),
("begs", "begged", "begging"),
# Non-CVC: should NOT double.
("cooks", "cooked", "cooking"), # CVCk pattern not doubled
("flows", "flowed", "flowing"), # ends in vowel+consonant but flow has 2 vowels
("plays", "played", "playing"), # CVC but ends in y (excluded)
]
@pytest.mark.parametrize("verb_3sg,past,pres", _CVC_CASES)
def test_cvc_doubling_rule(verb_3sg: str, past: str, pres: str) -> None:
assert past_tense(verb_3sg) == past
assert present_participle(verb_3sg) == pres
# Short-ies disambiguation: dies → die, cries → cry.
_IES_CASES: list[tuple[str, str, str]] = [
("dies", "died", "die"),
("lies", "lay", "lie"), # lie is irregular; base must still be lie
("ties", "tied", "tie"),
("cries", "cried", "cry"),
("flies", "flew", "fly"),
]
@pytest.mark.parametrize("verb_3sg,past,base", _IES_CASES)
def test_short_ies_disambiguation(verb_3sg: str, past: str, base: str) -> None:
assert base_form(verb_3sg) == base
assert past_tense(verb_3sg) == past
# Regular cases still pass — the suffix rules are unchanged for the
# Phase 5.1+ OOD lane vocabulary.
_REGULAR_CASES: list[tuple[str, str, str, str]] = [
("flows", "flowed", "flowed", "flowing"),
("reveals", "revealed", "revealed", "revealing"),
("grounds", "grounded", "grounded", "grounding"),
("precedes","preceded", "preceded", "preceding"),
("yields", "yielded", "yielded", "yielding"),
]
@pytest.mark.parametrize("verb_3sg,past,pp,pres", _REGULAR_CASES)
def test_regular_verbs_still_pass(
verb_3sg: str, past: str, pp: str, pres: str
) -> None:
assert past_tense(verb_3sg) == past
assert past_participle(verb_3sg) == pp
assert present_participle(verb_3sg) == pres