_unit_grounds() previously refused multi-word units like 'Pokemon cards'
even when both component words appeared as tokens in the source span.
The function checked unit_token against the haystack as a single key,
but the tokenizer splits source into per-word tokens — 'Pokemon cards'
was never going to match.
Fix is conjunctive by design: every component word must appear in the
haystack. A missing component refuses, preserving wrong=0.
Truth-test: case 0023 (Nicole/Pokemon cards) previously refused with
'recognizer matched but produced no injection' on its first sentence.
After this fix, sentence 1 passes injection cleanly; the case now
refuses on sentence 2 (Cindy/Rex compositional clause) — a more
honest refusal reason that reflects the actual remaining gap.
Score unchanged at 3/47/0 (no overall lift; correctness win).
smoke 67/67, packs 141/141, lanes 8/8 all green.
The wrong-answer firewall for the candidate-graph parser topology.
A CandidateOperation carries an Operation plus source-span provenance
for every content slot the parser claimed (verb, value, unit, actor,
transfer target, comparison reference). roundtrip_admissible() checks
each slot grounds in the source span AND the matched verb is
registered for the claimed kind.
Two consequences:
- A regex that mis-reads 'loses' as add fails (loses not in ADD_VERBS).
- A regex that hallucinates a number/unit not in source fails to ground.
KIND_TO_VERBS is the new single source of truth for {kind -> verbs};
P2 will refactor math_parser to consume it. Verb tables are
permissive by design (much wider than current narrow regex tables)
because the filter rejects wrong candidates downstream — narrowness
is no longer the safety mechanism.
Deterministic: pure byte/regex containment. No randomness, no
learning, no approximation. Preserves wrong==0, trace_hash byte-
equality, replay determinism.