core/generate
Shay 5ba1359a79 fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B)
The ratified, flag-ON v1b categorical band served "all dog are mammal" for
every noun: the A/E/I/O templates supply all/no/some + are, which demand a
plural, and the slots were filled with singular canonical entity ids. Four
of the 47 ratified corpus cases were affected, and wrong=0 never noticed
because every verdict was correct — only the prose was wrong.

Two fixes that compose into a closed round trip:

1. generate/morphology.py becomes the single owner of the number RULES
   (Phase 2A gave the TABLES one owner). Both directions live there:
   pluralize + singularize, over lexicon.IRREGULAR_PLURALS /
   IRREGULAR_SINGULARS, with mass-noun and compound-head handling.
   templates.pluralize and reader._singularize were two independent copies
   of the regular suffix rules that disagreed about which irregulars they
   knew; both now delegate.

2. render._display_noun re-inflects at render time and swaps _ for a space,
   so compound ids read "guard dogs" rather than "guards dog" or
   "guard_dog".

Result: reader gives wolves -> wolf, renderer gives wolf -> wolves. The
round trip closes.

  0 malformed of 47 (from 4)
  reader-vs-reader singularization 20/20 (from 8/20), 0 silently wrong
  s_surface_match_rate 0.0 -> 0.625, now EQUAL to s_renderable_rate, so the
    only remaining losses are surfaces the renderer cannot express at all

NEAR-MISS — widening a reader broke soundness, caught by the lane:

Routing the reader through the full 29-entry table first produced wrong=1 on
band v6-EX. ds-ex-0012 ("No fish are mammals. Therefore some fish are
mammals.") answered invalid where gold is refuted.

_singularize returning None makes the reader REFUSE, and the serving
composer tries the categorical band FIRST, falling through to more capable
bands. "fish" previously returned None (it matches no suffix rule), so v6-EX
got the case and decided it correctly. Resolving "fish" made v1b accept a
sentence it cannot decide.

Fixed by a principle, not a patch: a number-INVARIANT form is ambiguous in
number — "fish are mammals" is plural, "a fish is a mammal" is singular, and
the token cannot tell you which — so a reader that must not guess number
declines it. lexicon.INVARIANT_NUMBER is derived from the singularizer's own
key == value rows, so the rule cannot drift from the table.

  => Coverage and correctness are different axes. Fixing a corrupted VALUE is
     safe; widening ACCEPTANCE changes which band answers, and in a
     first-match composer that turns a right answer into a wrong one. Same
     family as ADR-0261 5.1 refuse-don't-drop.

Also fixed, found by testing the inverse law: the two number tables were not
mutual inverses. The pluralizer lacked cactus->cacti, fungus->fungi,
die->dice and the invariants aircraft/means/offspring, so CORE could read
"cacti" but wrote "cactuses", and would have written "aircrafts", "meanses",
"offsprings". No round trip can close across a non-invertible table.

THE LANE SHA PINS ARE BLIND TO SERVED ENGLISH:

2B changed 4 served surfaces and the pins came back 11/11 byte-identical.
The deduction_serve_v1 hashed report holds only n/counts/by_gold/
correct_by_gold/all_cases_correct/mismatch_examples — no prose at all.
Confirmed by sabotage: with _display_noun returning "SABOTAGE_" + ..., so
every clause reads "all SABOTAGE_dogs are SABOTAGE_animals",

  11 lane SHA pins                        -> 11/11 byte-identical, blind
  test_deduction_serve_lane + _license    -> 20 passed, blind
  Phase 1 grammar_roundtrip               -> 3 tests RED, caught it

This corrects #129: byte-identity of the pins is the arbiter for values and
verdicts, NOT for surface text, so the 2A/2B split is not a partition of
"changes users can see." Phase 2A's 11/11 conclusion still stands on its own
independent evidence (24/24 table-equality checks vs the pre-migration
literals), but the justification was thinner than stated.

Before Phase 1 no test in the tree would have noticed CORE's served prose
turning into word salad. That is exactly how "all dog are mammal" survived
on a ratified flag-ON band with wrong=0 intact.

Deliberately not fixed: mass-noun verb agreement ("all evidence ARE truth"
should be "is"). The copula is fixed text inside the templates, so agreeing
it changes the template shape rather than a slot, and no mass noun reaches a
categorical clause in any serve corpus. Recorded in render.py.

No lane pin edited — none moved.

[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
smoke 621 unchanged; deductive 403 passed (383 + 20 new/rewritten);
scripts/verify_lane_shas.py 11/11 (see blindness note above — gate on
grammar_roundtrip for surface work, not on these pins).
2026-07-26 18:42:42 -07:00
..
answer_choices
binding_graph Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
combined_rate_comprehension
composition
comprehension Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
constraint_comprehension
contemplation feat: 3lang depth PropGraph unification - phase refinements, wiring, tests + session pickup cleanups/governance 2026-07-08 07:07:11 -07:00
cue_precision
derivation feat: retire fraction_decrease prose regex — bind dilation from frame scale 2026-07-08 20:09:49 -07:00
determine feat(provenance,teaching): close the lateral gaps the assessment actually found 2026-07-25 04:51:15 +00:00
frame_verdict feat(frame-verdict): closed-world FrameVerdict substrate — PR-1..4 + hardening (ADR-0222 B4) (#787) 2026-06-16 06:23:03 -07:00
linguistic_pipeline feat(cognition): fail-closed linguistic governance + trilingual constraint pipeline 2026-07-20 16:16:56 -07:00
meaning_graph fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B) 2026-07-26 18:42:42 -07:00
observed_he_morph_v0 feat(logos): bulk live morph authority on turn + teaching seams 2026-07-20 16:35:04 -07:00
proof_chain fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B) 2026-07-26 18:42:42 -07:00
rate_comprehension
realize
structure_mapping fix(trackb): pure-S1 extract refuse multi-clause overmatch (0361 wrong=0) 2026-07-19 20:04:27 -07:00
__init__.py
admissibility.py
articulation.py
articulation_legality.py
attention.py
bridge_trace.py chore: Refactor CLI and Governance Anchors (#926) 2026-07-03 12:34:56 -07:00
candidate_operator.py style(kernel): place quantity-entity builder before exports 2026-06-23 05:43:17 -07:00
compute_budget.py feat(kernel): implement diagnostic-only ComputeBudgetDecision 2026-06-22 13:13:49 -07:00
construction_affordances.py chore(kernel): tighten construction_affordances catalog invariants (#930) 2026-07-06 08:38:23 -07:00
contract_residuals.py feat(kernel): add ContractResidual projection 2026-06-22 08:06:32 -07:00
dialogue.py
discourse_planner.py refactor(generate): one owner per linguistic fact (Phase 2A) 2026-07-26 17:45:27 -07:00
epistemic_basis.py feat(frame-verdict): closed-world FrameVerdict substrate — PR-1..4 + hardening (ADR-0222 B4) (#787) 2026-06-16 06:23:03 -07:00
exhaustion.py
field_incidence_reader.py work done towards furthering comprehension, contemplation, learning, and also began working on fixing some failing tests 2026-06-16 12:27:59 -07:00
foundational_families.py fix(kernel): align unary-delta implementation with conformance decision 2026-06-21 20:19:53 -07:00
geometric_search_run.py feat(kernel): implement inert GeometricSearchRun trace shell 2026-06-22 14:09:52 -07:00
graph_constraint.py
graph_planner.py refactor: consolidate duplicate depth extraction — graph_planner.get_node_depths now delegates to build_node_depths (single source) 2026-07-08 07:21:17 -07:00
grounding_accessors.py
intent.py feat(deduction-serve): Phase 1 — deduction turn spine (flag-gated, propositional Band v1) 2026-07-23 12:14:28 -07:00
intent_bridge.py
intent_ratifier.py fix(cognition): pre-stage land repairs after L2/PASSTHROUGH excision 2026-07-20 13:34:59 -07:00
kernel_facts.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
lexicon.py fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B) 2026-07-26 18:42:42 -07:00
logic_canonical.py
logic_equivalence.py
math_candidate_graph.py fix: fold Gemini CGA/fraction_decrease compatibility into deck PR 2026-07-08 20:00:51 -07:00
math_candidate_parser.py feat(reader-arc): #78 positive-polarity allowlist substrate (seed + comparison) — foundations, band-solve=0 2026-07-18 21:18:02 -07:00
math_completeness.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
math_parser.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
math_problem_graph.py feat(derivation): Gate A2b case 0002 fractional rest composition (#810) 2026-06-17 19:08:24 -07:00
math_realizer.py feat(derivation): Gate A2b case 0002 fractional rest composition (#810) 2026-06-17 19:08:24 -07:00
math_roundtrip.py feat(reader-arc): #78 positive-polarity allowlist substrate (seed + comparison) — foundations, band-solve=0 2026-07-18 21:18:02 -07:00
math_solver.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
math_symbolic_equivalence.py
math_symbolic_normalizer.py
math_verifier.py feat(derivation): Gate A2b case 0002 fractional rest composition (#810) 2026-06-17 19:08:24 -07:00
morphology.py fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B) 2026-07-26 18:42:42 -07:00
ood_surface_generator.py
operators.py
perturbation_suite.py
problem_frame.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
problem_frame_bound_relations.py refactor(kernel): split ProblemFrame builder phases (#919) 2026-06-25 16:16:04 -07:00
problem_frame_builder.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
problem_frame_contracts.py fix(a2k): share fraction-decrease scale domain on geometric admission 2026-07-20 04:40:35 +00:00
problem_frame_extractors.py Lane 4: Registry Consolidation (language_packs to packs) 2026-07-04 15:11:28 -07:00
problem_frame_mentions.py refactor(kernel): split ProblemFrame builder phases (#919) 2026-06-25 16:16:04 -07:00
problem_frame_proposals.py refactor(kernel): split ProblemFrame builder phases (#919) 2026-06-25 16:16:04 -07:00
process_frames.py feat: implement kernel substrate tranche 1 base-layer foundations (#829) 2026-06-18 18:46:20 -07:00
proposition.py
quantitative_comprehension.py
quantitative_expr.py
realizer.py feat: integrate 3-core-language depth into PropositionGraph spine for bidirectional unification 2026-07-06 09:01:43 -07:00
realizer_guard.py refactor(generate): one owner per linguistic fact (Phase 2A) 2026-07-26 17:45:27 -07:00
recognizer_anchor_inject.py feat(derivation): Gate A2a unit partition injection (#809) 2026-06-17 18:14:24 -07:00
recognizer_match.py fix(reader-arc): compound extractor refuses no-digit compare clauses (wrong=0 hazard) 2026-07-18 21:44:34 -07:00
recognizer_registry.py
relational_field_reader.py
relational_operator_ablation.py feat(generate): deterministic relational operator ablation runners 2026-07-19 21:00:15 -07:00
relational_symbolic_reader.py
replay_adapter.py feat(kernel): add quantity-entity replay target shell 2026-06-23 06:13:28 -07:00
result.py
rotor_admissibility.py
run_attempt_binding.py feat(kernel): implement run-attempt binding shell 2026-06-22 23:12:16 -07:00
salience.py
sealed_practice_trace.py fix(kernel): fail-closed bound trace structural validation 2026-06-23 00:11:14 -07:00
search_gate.py feat(kernel): implement diagnostic-only SearchGateDecision 2026-06-22 11:36:05 -07:00
semantic_templates.py refactor(generate): one owner per linguistic fact (Phase 2A) 2026-07-26 17:45:27 -07:00
stream.py fix(tests): clear pre-existing full-suite reds (INV-21 pins + rotor_power) 2026-07-15 15:39:09 -07:00
surface.py
templates.py fix(generate): CORE writes plural nouns in categorical clauses (Phase 2B) 2026-07-26 18:42:42 -07:00