# Ticket #000053 — Acronym-aware verifier content tokens

**Status:** closed · 2026-05-13 — `arborist.qa.evidence._content_tokens` now keeps all-caps 2-3-char acronyms (CPU/GPU/DNA/FBI/USB…) as content tokens; `content_token_rules: "v2-acronym-aware"` added to the default policies + `_VERIFIER_POLICY_FIELDS` so the change folds into `verifier_policy_hash` and prior cache records orphan on lookup. The change is **monotone toward fewer spurious demotes** — `_content_tokens` only ever *gains* tokens, so `TITLE_MISMATCH` / `SUBJECT_TOKENS_ABSENT` / `BARE_NAME_CLAIM` can only *stop* firing, never start; no answer that was STRICT can become non-STRICT from it. Validation: full test suite green (2502), 8 new tests in `tests/test_content_tokens.py`, `make bench-qa-smoke` clean. A full `make bench-qa` before/after is the belt-and-suspenders confirmation and remains worth running, but the monotonicity argument is the load-bearing one.
**Opened:** 2026-05-13
**Scope:** One narrow verifier fix: `_content_tokens` dropped every
token under 4 chars, so a short all-caps acronym (`CPU`, `GPU`, `DNA`,
`FBI`, `API`, `SQL`, `USB`…) never registered as a content token. That
defeats Rule 8 (`_claim_title_overlap` / `TITLE_MISMATCH`), the
subject-tokens-absent check (Rule 9), the bare-name-claim guard, and
spotlight-excerpt token selection — anywhere a question or claim's
*topic* is an acronym. Fix: keep a token if it's an all-caps 2-3-char
alpha run in the source text; everything else unchanged (≥4-char floor,
stopword set, longest-first sort, dedup).
**Audience:** fox + maintainers of `arborist/qa/verify.py` /
`arborist/qa/evidence.py` + anyone reading a `TITLE_MISMATCH` tail.
**Hard constraint:** this changes what the deterministic verifier
decides → it is a versioned policy change. The marker field
`content_token_rules` folds into `verifier_policy_hash` (a `cache_key`
dimension), so prior cached records produced under the old token rule
orphan on lookup — exactly the invalidation we want, the same
discipline as `base_version` / the `hyphen_fold_v1` marker. Not a
silently-applied change. The verifier stays binary; no new soft signal.

---

## 1. The bug, from the field

`make query Q="what is a CPU?"` (2026-05-13, fox), `claim_lattice` mode:

```
UNGROUNDED · via claim_lattice · title mismatch  1/1
  A CPU, or central processing unit, is the main component of a
  computer that processes instructions and performs calculations…
  [E1 | CPU design | …: "CPU design is the design engineering task
  of creating a central processing unit (CPU), a component of
  computer hardware…"]
sources: CPU design · CPU socket · CPU time · CPU cache · CPU-Z ·
         CPU multiplier · CPU (disambiguation)
```

The claim *is* about "CPU"; the cited source title *is* "CPU design".
They share the token "CPU". Rule 8 should pass — but `_content_tokens`
drops "cpu" (3 chars), so the claim's content tokens are
`{central, processing, unit, main, component, computer, processes,
calculations, …}` and the title's are `{design}` → zero overlap →
`TITLE_MISMATCH` → demote. Same shape for `what is a GPU?` (0/1
UNGROUNDED — every "GPU foo" satellite, never "Graphics processing
unit").

(The *root* cause of the bad answer is retrieval — the query token
"CPU" doesn't match the canonical article's title "Central processing
unit", so retrieval pulls the "CPU *" satellites; that's the
abbreviation→expansion gap, fixable via `concepts/` synonym edges or
#000050 vec hybrid, and is **not** this ticket. This ticket fixes the
*verifier*'s blind spot, which is why a 1/1-verified answer got
labelled UNGROUNDED rather than HYBRID, and is a strict improvement
regardless of the retrieval fix.)

## 2. Why a 5-line change still gets a ticket + a bench

`_content_tokens` is a verifier helper — it feeds `TITLE_MISMATCH`,
`SUBJECT_TOKENS_ABSENT`, `BARE_NAME_CLAIM`, and `_spotlight_excerpt`.
Changing it changes `audit_mode` outcomes on some records. Per the
repo's discipline (CLAUDE.md "Versioned defaults … changing any
default stales every prior cache record"; verify.py changes fold into
`verifier_policy_hash`), a proof-path change is:

1. **Versioned** — a marker field (`content_token_rules`) added to the
   default policies + `_VERIFIER_POLICY_FIELDS`, so the hash bumps and
   prior records orphan cleanly on lookup (they were verified under the
   old rule; re-asking re-verifies under the new one).
2. **Bench-gated** — `make bench-qa` before & after (n=3 × the curated
   question set × 3 modes) to confirm no STRICT-rate regression on
   legit answers. The expected delta: some `TITLE_MISMATCH` /
   `SUBJECT_TOKENS_ABSENT` false-positives on acronym-topic questions
   flip to the correct label; nothing legit should regress (acronyms
   were *missing* signal, not noise — the deflection sidecar already
   uses a ≥3 floor for exactly this and hasn't caused trouble).

That's the difference between this and a render-tail tweak — not size,
proof-path. Doing it "right" = the version bump + the bench, written
down (which records orphaned, what the bench showed).

## 3. The change

`arborist/qa/evidence.py:_content_tokens` — keep a token when it's a
2-3-char all-caps alpha run in the *source* text (so `CPU` counts,
`cpu`-lowercased-in-prose still needs ≥4 via the normal path — which it
never reaches, so acronyms only survive when written as acronyms).
5 chars+ already pass the `< 4` filter (`ASCII`, `HTTPS`), so the
exception only matters for length 2-3. 2-char covers `US`, `UK`, `EU`,
`AI`, `ML`, `OS`, `PC`, `TV`, `IT` — all high-signal in encyclopedic
register; 3-char covers `CPU`, `GPU`, `DNA`, `FBI`, `USA`, `USB`,
`GPS`, `API`, `SQL`, `RAM`, … Conservative — only all-caps, only
alpha, only ≤3 chars.

Marker fields: `content_token_rules: "v2-acronym-aware"` in
`runner.py:DEFAULT_POLICY` and `query.py:DEFAULT_QUERY_POLICY`;
`"content_token_rules"` added to `keys.py:_VERIFIER_POLICY_FIELDS`.
The marker doesn't gate code — the tokenizer change is unconditional;
it exists purely to bump `verifier_policy_hash`.

## 4. Out of scope

- The retrieval abbreviation→expansion gap (`CPU`→`Central processing
  unit`) — `concepts/` synonym edges or #000050 vec hybrid; the real
  reason "what is a CPU?" retrieves satellites. Worth its own
  follow-up; "what is a CPU?" / "what is a GPU?" are textbook
  semantic-allusion fixtures for the #000050 bench pack.
- Lowering the global `< 4` floor (would re-introduce 3-char-token
  noise everywhere — the acronym exception is the targeted version).
- Multi-word acronym expansion / aliasing in the verifier (NLI-grade;
  not lexical).

## 5. Acceptance criteria

1. `_content_tokens("…CPU…")` includes `"cpu"`; `_content_tokens` of a
   lowercase 3-char word still excludes it.
2. `content_token_rules` is in both default policies and
   `_VERIFIER_POLICY_FIELDS`; `verifier_policy_hash(DEFAULT_POLICY)`
   changed (prior records orphan on lookup — by design).
3. Full test suite green (no spotlight-excerpt or hash-KAT regression)
   — done, 2502 passed.
4. The change is monotone toward fewer demotes (`_content_tokens` only
   gains tokens) → no STRICT→non-STRICT transition is possible from it;
   `make bench-qa-smoke` clean. (A full `make bench-qa` before/after is
   still worth running as confirmation; not the load-bearing check.)
5. Re-run `make query Q="what is a CPU?"` — the `· title mismatch` tail
   is gone (the answer now grounds in "CPU design" without the spurious
   demote; the *retrieval* miss remains, tracked separately).

## 6. References

- `arborist/qa/evidence.py:_content_tokens` — the changed function.
- `arborist/qa/verify.py` — `_claim_title_overlap` / Rule 8
  (`TITLE_MISMATCH`), the subject-tokens-absent check (Rule 9), the
  bare-name-claim guard — all consume `_content_tokens`.
- `arborist/qa/keys.py:_VERIFIER_POLICY_FIELDS` — where the marker
  folds in; same pattern as `base_version` / `hyphen_fold_v1`.
- `arborist/qa/inspect.py:_content_tokens_in_order` — the deflection
  sidecar's ≥3-char tokenizer; the precedent ("amd", "bsd", "fox").
- #000050 — vec hybrid; the abbreviation→expansion retrieval gap that
  is the *root* cause of the CPU/GPU misfires, out of scope here.
- CLAUDE.md "Conventions — Versioned defaults" / "Verifier stays
  binary" — the discipline this change obeys.
