arborist/docs/qa-modes-bench.md
russell@unturf.com 7bb11ed62f
#000048 step 2.4 — parse_pointer_claims clause segmentation
Closes the 8 mis-segments #000046 left in formulate-hard-v1.jsonl.
The parser was line/bullet-only — one line ⇒ one claim — so a line
that crammed several pointered claims onto one row ("Water is wet
[E1]; fire is hot [E2]", "X happened [E1]. Y followed [E2]") became
one monolithic claim with all the pointers, and a wrapped bullet
became two.

arborist/qa/parse_claims.py: _SEGMENT_SEP_RE splits a line on ';',
sentence boundaries ('. '/'! '/'? ' then a Capital), spaced dashes
(' - '/' — '/' – '), ' and '/' or '/' because '/' although '/' since
'/' while ', inline '(N)' enumeration markers, and commas — with
'(?![^\[]*\])' so a comma inside a [E1, E2] bracket never splits it.
_segment_line keeps the split ONLY IF every resulting non-empty
segment is a well-pointered claim — a legit single claim ("The cat
is black and white [E1].", "The cast: A, B, C [E1].") is never
broken because splitting it would manufacture pointer-less prose
fragments → guard rejects; a leading colon-terminated header with no
pointer ("Two facts:", "Key points:") is allowed and dropped. Plus a
wrapped-bullet join: a continuation line (leading whitespace then a
lowercase letter, no bullet glyph) folds its text + pointers into the
previous claim.

Effect: formulate-hard rate 4/12 → 12/12 (the pack is now at ceiling
— a harder Formulate tier would re-open below-ceiling headroom; a
#000046 follow-up). Remaining #000048 headroom: 2 STRICT_PARAPHRASE
recombinations in falsification-hard (Mercury, Einstein — step 2.2).

Bench gate: make bench-qa (n=3 × 75 × 3 = 675 cells; parse_pointer_claims
feeds the 450 claim_lattice_pointer + claim_lattice cells) after
(bench/qa_results/2026-05-11T20-26-37Z) vs the pre-step-2.4 baseline
(...T17-12-41Z = HEAD's parse_claims.py). STRICT-rate quote 0.54→0.55,
pointer 0.22→0.22, lattice 0.43→0.45 — all within the 5-pp noise
floor. Per-row diff: the segmenter changed the parsed-claim count on
the SAME answer text for 7 of the 450 lattice cells (0 in
claim_lattice, 7 in claim_lattice_pointer); of those, 2 caused an
audit_mode change — both correct: a wrap-join recovered an answer's
intended structure (4 claims, 2 pointer-less wrap-fragments → HYBRID)
into 2 well-pointered claims → STRICT; and a crammed-one-line blob (1
monolithic claim, all pointers → STRICT) split into 8 claims, some
not individually verifying → HYBRID (the honest verdict — false-
positive STRICT was the corruption). Every other lattice/quote delta
is LLM re-answer variance. No regression — the segmenter's only
visible effects on real traffic are honest improvements. Summarized
in qa-modes-bench.md Addendum 7 + ticket-000048 §5 step 2.4.

Tests: 8 new in test_claim_lattice.py (semicolon/sentence/conjunction
splits; pointerless-fragment + cast-list guards; leading-colon-header
drop; wrapped-bullet join; pointer-order/multi-pointer); existing
parse_pointer_claims tests pass untouched; test_5f_formulate_hard_pack
re-pinned 4/12 → 12/12. make test 2358 passed, 28 skipped.

#000048 → steps 2.1 + 2.4 landed; #000046 / #000012 §8 / TICKETS.md /
Makefile / fixture _meta + notes updated.
2026-05-11 17:09:06 -04:00

30 KiB
Raw Blame History

QA-modes bench — 2026-05-02

Date: 2026-05-02 Endpoint: https://hermes.ai.unturf.com/v1 (Hermes-3-Llama-3.1-8B-FP8-Dynamic, vLLM, 82K ctx) Corpus: Wikipedia 2003-05-16 cur snapshot, sharded under ~/.arborist/shards

Two sweeps landed today, each 71 questions × 3 modes:

stamp samples concurrency scheduling runs wall-clock
11:31Z n=2 c=4 cell-grouped 426 51 min
15:07Z n=3 c=4 sample-shuffled, --seed 0 639 48 min

The 15:07Z sweep is the authoritative state of the substrate at end-of-day; the 11:31Z sweep is the intermediate witnessed before the second wave of hardening landed. Headlines below show both.

Hardening since the prior bench (2026-04-30 post-retry)

Substrate-side (between 2026-04-30T18:46Z and 2026-05-02T11:31Z):

  • Rule 8 — title-relevance promoted to hard verifier check (TITLE_MISMATCH violation demotes STRICT → HYBRID when no cited evidence's source title shares a stemmed token with the claim).
  • Anchor-class warrant generalized to entity-list / count / why-cause shapes (Ticket #000003).
  • Retrieval-plan hash bound into the run-DAG retrieval stage (Ticket #000001).
  • Reference-frame polarity contract (Ticket #000002).
  • Four-rung ladder display (Ticket #000005) — POINTER-LINKED → ANCHOR-WARRANTED → EVIDENCE-WARRANTED → (ENTAILMENT-VERIFIED reserved) mapped from the v9.8 audit_mode trichotomy at render time.
  • Bench harness extended with directive coverage + log-scale buckets to 1M (Ticket #000004).

Substrate-side (between 2026-05-02T11:31Z and 2026-05-02T15:07Z):

  • Sprint 1b — per-mode max_context_chars. Bench's recommended-context-budget table flows back into DEFAULT_QUERY_POLICY["max_context_chars_by_mode"]: quote 24 KB, pointer 24 KB, JSON 48 KB. Folds into governance_policy_hash.
  • Sprint 2 — pointer Rule 9: chunk-specificity instruction added to the lattice-pointer system prompt.
  • DRY collapse — the four lattice prompts (system + grounding × pointer + JSON) lifted to arborist/qa/prompts.py as a single source of truth, imported by both runner.DEFAULT_POLICY and query.DEFAULT_QUERY_POLICY.
  • httpx persistent client — the chat-completion path used to construct a fresh httpx.Client per call, paying a TLS handshake every request. Move to __init__; HTTP/1.1 keep-alive across calls. Save 1-2 min on a 426-call bench.
  • Sample-level shuffled bench scheduling — every (question, mode, sample_idx) is a task, shuffled with --seed, dispatched concurrently. Per-cell Lock dict serializes burn-then-write on the shared cache_key. True i.i.d. n=3 variance; vLLM batcher fed a diverse request stream.
  • Bench --resume — read existing JSONL, skip done tasks, append fresh rows. Stop/start-able.
  • Concurrency sweep on the smoke fixture. vLLM peaks at c=3-4; saturates badly past c=4 (see Wall-clock & throughput below).
  • UTF-16 surrogate fix v2 (41d1d9b) — corpus chunks with lone surrogates broke httpx's outbound JSON encode. Scrub at the client boundary by WTF-8 → UTF-8-with-replace roundtrip. (The earlier v1 fix 3b91223 hardened the OUTPUT side — SHA-256 hashers — but missed the INPUT side. Both paths now safe.)

Aggregate (15:07Z, authoritative)

mode runs STRICT HYBRID UNGROUNDED err strict-rate mean ratio mean latency
quote 213 116 51 46 0 0.54 0.699 16.9 s
claim_lattice_pointer 213 43 146 21 3 0.20 0.642 18.5 s
claim_lattice (JSON) 213 89 82 39 3 0.42 0.698 18.3 s

Δ across the day

mode 2026-04-30 (post-retry) 2026-05-02T11:31Z 2026-05-02T15:07Z net Δ
quote 0.47 0.50 (+3pp) 0.54 (+4pp) +7pp
claim_lattice_pointer 0.24 0.23 (1pp) 0.20 (3pp) 4pp
claim_lattice (JSON) 0.50 0.44 (6pp) 0.42 (2pp) 8pp

Quote climbed the most across the day, ending at 0.54. The Sprint 1b 24 KB cap surfaces tighter retrievals that quote can ground verbatim (the bucket data confirms — quote's peak migrated to 8-16 KB at 0.58 strict-rate).

Pointer & JSON each took an honesty cost. Rule 8 promotion + warrant-class generalization demote more cases that would have classified STRICT under the looser pre-2026-05-02 verifier. The architectural win — 99% directive coverage on lattice modes — is the price for those drops; false-positive STRICT was corruption, and we converted it to honest HYBRID.

Per-bucket strict-rate (15:07Z)

mode bucket runs strict-rate note
quote 8-16 KB 105 0.58 peak
quote 16-32 KB 105 0.52
claim_lattice_pointer 16-32 KB 210 0.20 peak (only bucket)
claim_lattice (JSON) 32-64 KB 99 0.48 peak
claim_lattice (JSON) 16-32 KB 108 0.38

Sprint 1b's intent confirmed. JSON's peak at 32-64 KB justifies the 48 KB default. Quote's peak migrated to 8-16 KB after the 24 KB cap; the cap may be tighter than optimal — quote could plausibly be cut to 16 KB (mid of 8-16 KB bucket) for another small lift in a follow-up sprint.

claim_lattice (JSON) peaks at LARGER context than the other modes. The structured per-claim evidence linkage that JSON enforces benefits from more evidence per claim. Pointer & quote degrade past 16-32 KB.

5-run minimum to reduce noise; from the 15:07Z bucket data:

mode peak bucket strict-rate n
quote 8-16 KB 0.58 105
claim_lattice_pointer 16-32 KB 0.20 210
claim_lattice (JSON) 32-64 KB 0.48 99

These flow into DEFAULT_QUERY_POLICY["max_context_chars_by_mode"] (currently 24 / 24 / 48 KB — mid of each peak bucket). When the bench shifts those peaks, retune the policy and let governance_policy_hash partition the cache.

Directive coverage (seven-point program)

mode D2 pointer D3 cti-ready D4 ev-map bound D6 warrant D7 honest label
quote 0/213 (0%) 0/213 (0%) 213/213 (100%) 0/213 (0%) 213/213 (100%)
claim_lattice_pointer 210/213 (99%) 210/213 (99%) 210/213 (99%) 210/213 (99%) 210/213 (99%)
claim_lattice 210/213 (99%) 210/213 (99%) 210/213 (99%) 210/213 (99%) 210/213 (99%)

Lattice modes hit 99% on every observable directive; the 1% gap is the 6 surrogate errors (already fixed in 41d1d9b, next bench will hit 100%). Quote shows 0% on D2 / D3 / D6 by construction (those are lattice-only directives) and 100% on D4 / D7.

Pointer-mode signal — where the gap lives

Pointer mode's strict-rate did not lift on Sprint 2's chunk-specificity prompt nudge. The load-bearing failure pattern is lazy-anchoring: the model cites a topic-overview chunk for every claim instead of the specific chunk that supports each individual claim. Lazy-anchor ratio histogram (11:31Z bench, 142 pointer rows):

lazy_anchor_ratio bucket rows
0.00 25
<0.25 9
<0.5 11
<0.75 46
>=0.75 49 (35%)

35% of pointer rows anchor ≥75% of claims to a topic chunk. This is the failure pattern Rule 8 was promoted to catch — when the lazy-anchor target's source title shares zero stemmed content tokens with the claim, the run demotes STRICT → HYBRID via TITLE_MISMATCH. The honesty surfaces; the structural lift requires a stronger intervention (evidence-map ranking change, model fine-tune, or a different prompt frame).

19 of 71 questions show JSON ≥ 50pp above pointer, including:

pointer=HH  json=SS   did napoleon really die on saint helena?
pointer=HH  json=SS   name simpsons family members including pets?
pointer=HH  json=SS   what are the planets of our solar system?
pointer=HH  json=SS   what country is the city of prague in?
pointer=HH  json=SS   what is the boltzmann constant?
pointer=UU  json=SS   what is the difference between http and ftp?
pointer=HH  json=SS   when did the soviet union dissolve?
pointer=HH  json=SS   where does the nile river begin?
pointer=HH  json=SS   where is mount kilimanjaro located?
pointer=HH  json=SS   who is supermans girlfriend?
pointer=HH  json=SS   who painted the mona lisa?
pointer=HH  json=SS   who said may the force be with you?
pointer=HH  json=SS   who were the original seven mercury astronauts?
pointer=HH  json=SS   why did the dinosaurs go extinct?

(plus 5 more.) These are factual single-fact questions where JSON's structured per-claim evidence linkage forces the model to pick the specific chunk; pointer's prose-with-tags lets the model lazy-anchor to the topic article and Rule 8 catches it.

subject_in_answer is high across all rungs (STRICT 100%, HYBRID 92%, UNGROUNDED 84%) — the subject is in the answer; the answer just doesn't anchor cleanly to a single chunk. This is structural-honesty signal, not deflection.

Wall-clock & throughput evolution

stamp concurrency scheduling n tasks wall-clock tasks/min mean latency (mode)
11:31Z 4 cell-grouped 2 426 51 min 8.4 26-29 s
15:07Z 4 sample-shuffled 3 639 48 min 13.3 17-19 s

Sample-level shuffled scheduling at the same concurrency delivers +58% throughput. n=3 (50% more total work) ran in 6% LESS wall-clock than n=2 cell-grouped. Per-call mean latency dropped 35-42% across all modes (quote 29.1 s → 16.9 s, pointer 26.7 s → 18.5 s, JSON 28.2 s → 18.3 s) because vLLM's continuous batcher fills better when fed a diverse, uncorrelated request stream.

Concurrency sweep (smoke fixture, 15-task)

c wall-clock tasks/min normalized
3 102 s 8.8 1.00 (peak)
4 106 s 8.5 0.96 (chosen — within 4% of peak, more forgiving on single-call hiccups)
5 119 s 7.6 0.86
6 185 s 4.9 0.55 (vLLM batching ceiling, brutal)

vLLM saturates at c=3-4 on this endpoint. More concurrent requests fill the batch better, but only up to the point where per-call latency growth outpaces parallelism gain. Re-sweep when the endpoint is upgraded, when corpus shape changes the average context size, or when other tenants change the queue.

Errors

Across both benches: 2 errors at 11:31Z, 6 errors at 15:07Z — all on the same question (tell me about the roman empire). Lone UTF-16 surrogates in Wikipedia chunk content, two distinct paths:

  • v1 (3b91223) — hardened SHA-256 hashers on the OUTPUT side (arborist/qa/dag.py, keys.py, evidence.py, store.py audit chain) with errors='surrogatepass' so the run-DAG roots survive surrogate-bearing model output.
  • v2 (41d1d9b) — scrubs message content INSIDE OpenAICompatibleClient.chat_completion before httpx's outbound JSON encode. The corpus chunk text was the path; httpx's .encode('utf-8') on the request body raised before the call left the client.

Verified: tell me about the roman empire under claim_lattice now classifies HYBRID 5/7 instead of erroring. Next bench will land 0 errors.

Verdict & per-mode recommendation

Quote leads on raw lexical grounding (0.54 strict-rate). Best when the four-rung ladder isn't needed and the answer is short-and-cite-able. Doesn't surface anchor-class warrants.

JSON leads among lattice modes (0.42). Recommended default for cache-grade provenance. Structured per-claim evidence linkage; peaks at larger context (32-64 KB) so it scales with evidence growth.

Pointer (0.20) stays useful for low-context-budget scenarios (16-32 KB peak) and prose-distribution models that struggle with JSON grammar; the lazy-anchor honesty cost is the trade.

The directive-coverage table (99% across every observable D2-D7 on lattice modes) is the architectural win. False-positive STRICT was corruption; converting it to honest HYBRID was the price of calibration. Strict-rate slipped on lattice modes; the substrate is more honest.

Outputs

  • bench/qa_results/2026-05-02T11-31-55Z.{jsonl,md} — 426 rows
  • bench/qa_results/2026-05-02T15-07-24Z.{jsonl,md} — 639 rows

(Both gitignored under bench/qa_results/. Headlines & analysis live in this journal.)


Addendum — 2026-05-03 broad-quantifier A/B (Ticket #000008)

This journal froze the 2026-05-02 substrate baseline. On 2026-05-03 Ticket #000008 (broad-quantifier preflight guard) landed Phases 04 and ran a four-cell A/B on a 9-question broad subset (bench/qa_questions_quantifier_subset.txt, 81 runs per cell).

Findings relevant to this journal's per-mode recommendations:

  • JSON-mode strict-rate moves under cap-only: 0.19 baseline → 0.33 with quantifier_guard_apply_caps=true (+14pp on the broad subset). The recommendation here that "JSON leads among lattice modes" still holds, but the headroom above 0.42 globally is partly dependent on broad-vs-narrow question mix.
  • Pointer-mode stays at 0/27 STRICT on broad questions across all four A/B cells. The 0.20 strict-rate above is across the full 71-question set; the broad subset alone is structurally unfavorable to pointer mode regardless of cap or reminder.
  • Reminder eliminates FORMAT_COLLAPSED: 2/27 → 0/27 on pointer mode under quantifier_reminder_enabled=true.
  • Cap and reminder help DIFFERENT failure modes: reminder rescues UNGROUNDED → HYBRID (restates citation rule); cap rescues HYBRID → STRICT (forces fewer-but-better claims). Compound effect on pointer mean-ratio is best at 0.684 (vs 0.473 baseline).

See docs/tickets/ticket-000008-broad-quantifier-preflight-guard.md §12 for the four-cell data and the §10.8 decision-tree verdict. Bench artifacts: bench/qa_results/2026-05-03T12-{29-53,38-53,47-23,54-11}Z.{jsonl,md}.

Addendum 2 — preflight ON vs OFF validation (2026-05-03T23-06-21Z)

After ticket #000010 flipped quantifier_reminder_enabled=True and shipped metacognition_enabled=True defaults, ran a preflight-OFF cell to validate the flip didn't regress baseline behavior. Same 9-question broad subset (bench/qa_questions_quantifier_subset.txt), n=3, policy override --policy metacognition_enabled=false --policy quantifier_reminder_enabled=false.

Compared against §12.6 reminder-only baseline (cleanest single-knob on-cell). On this 9-question subset, none of the metacognition detectors fire (no temporal / contradiction / false-premise / out- of-corpus shapes), so the comparison effectively isolates the reminder contribution.

Metric OFF ON Δ
quote strict-rate 0.63 0.52 11pp (noise band on 27 samples)
pointer strict-rate 0.00 0.00 0
JSON strict-rate 0.26 0.22 4pp
pointer mean ratio 0.483 0.643 +16pp
JSON mean ratio 0.570 0.735 +17pp
JSON UNGROUNDED rate 7/27 (26%) 1/27 (4%) 22pp
pointer FORMAT_COLLAPSED 2/27 0/27 100%
pointer NO_EVIDENCE_POINTER 8/27 6/27 7pp

Interpretation:

  • Mean-ratio improvement is robust (+16-17pp on lattice modes). Grounded rows are MORE thoroughly grounded under preflight ON.
  • JSON UNGROUNDED collapse is dramatic (22pp). The "didn't ground" pool reclassifies into "partially grounded" — operator- visible win.
  • FORMAT_COLLAPSED elimination on pointer mode (2 → 0). The reminder restates the [E\d+] citation rule and Hermes follows it.
  • STRICT-rate moves are within noise. Quote-mode dipped 11pp, but quote is mode-gated off the guard so this is pure Hermes nondeterminism — the §10.8 5pp floor exists exactly to filter this. JSON SR moved 4pp (within floor).

Verdict: the #000010 default-on flip is doing what was claimed. Mean-ratio + UNGROUNDED + FORMAT_COLLAPSED metrics all improve by ≥5pp on lattice modes; STRICT-rate is within noise. Defaults stay on.

Bench artifact: bench/qa_results/2026-05-03T23-06-21Z.{jsonl,md}.

Addendum 3 — full-bench regression check (2026-05-03T23-30-12Z)

Validates that the #000010 default flip (reminder default-on for lattice modes) doesn't regress narrow-question performance. The prior validations in Addendum 1 + Addendum 2 used the 9-question broad subset only; this run sweeps the full 75-question bench/qa_questions.txt (~10% broad, ~89% narrow factoid / descriptive / list shapes).

Same harness, n=3 × 3 modes × 75 questions = 225 runs per mode. Compared against the frozen 15:07Z baseline (the authoritative pre-#000008/9/10 state of the substrate, 71 questions × n=3 = 213 runs per mode):

Mode Pre-flip (15:07Z) Post-flip (23:30Z) Δ SR Δ mean ratio
quote 116S/51H/46U · SR 0.54 · ratio 0.699 116S/60H/49U · SR 0.52 · ratio 0.692 2pp 1pp
claim_lattice_pointer 43S/146H/21U · SR 0.20 · ratio 0.642 48S/145H/32U · SR 0.21 · ratio 0.663 +1pp +2pp
claim_lattice (JSON) 89S/82H/39U · SR 0.42 · ratio 0.698 99S/80H/46U · SR 0.44 · ratio 0.730 +2pp +3pp

All STRICT-rate deltas within the 5pp signal floor. Same question set ±2 (3 added: winners of all major sports?, name all members of the beatles, list all planets in the solar system).

Substrate-level findings:

  • Pointer-mode FORMAT_COLLAPSED: 0/225 across the full sweep. The default-on reminder eliminates the collapse mode globally, not just on broad questions where it was originally measured.
  • Pointer-mode NO_EVIDENCE_POINTER: 29/225 (13%) — lower than the 33% we saw on broad-only (9/27) when reminder was off. Reminder discipline propagates to non-broad questions even though the reminder text only fires on broad shapes (the broader signal is that the reminder text reinforces the citation rule across the model's session attention).
  • Quote-mode is essentially unchanged (-2pp SR, -1pp ratio). Quote opts out of the guard (quantifier_guard_modes default excludes quote), so this is pure Hermes nondeterminism on a 225-sample cell.
  • JSON-mode mean ratio +3pp — small but positive, consistent with the broad-subset finding that reminder boosts per-claim grounding density.

Latency note. Wall-clock per call jumped from ~17-19s (pre-flip baseline) to ~33-35s on this run. This is endpoint contention on Hermes-3 at the time of measurement, not a feature regression (the new substrate adds zero LLM calls — preflight is pure regex). The throughput numbers in §6 of the journal pre-date this evening's network conditions; future bench cycles should normalize.

Verdict — no regression. The #000010 default flip ships clean across the full corpus, not just the broad-quantifier subset where it was originally validated. Defaults stay on. The substrate is strictly more honest under preflight (FORMAT_COLLAPSED → 0, NO_EVIDENCE_POINTER ↓) without sacrificing throughput on non-broad questions.

Bench artifact: bench/qa_results/2026-05-03T23-30-12Z.{jsonl,md}.

Addendum 4 — metacog-trigger detector validation (2026-05-04T02-18-42Z)

The validations above (Addendum 1-3) covered the broad-quantifier subset and the full corpus, neither of which contains questions that fire the #000010 metacog detectors (temporal sensitivity, contradiction, false-premise-lite, out-of-corpus). This addendum closes that empirical gap.

Fixture: bench/qa_questions_metacog_subset.txt — 6 questions, one per detector kind plus a control:

who is the current CEO of OpenAI?               # stale_risk
which character is alive and dead?              # contradictory
when did George Washington stop being president  # false_premise +
  of France?                                    #   stale_risk (multi)
what does my uploaded contract say about        # out_of_corpus
  clause 9?
when did the current CEO of Twitter stop        # stale_risk +
  being CEO of Tesla?                           #   false_premise
who painted the mona lisa?                      # control (well_formed)

Run: n=3 × 6 questions × 3 modes = 54 rows.

Per-question verdict matrix:

question (detector) quote pointer JSON
Current CEO (stale_risk) 0/0/3 U 0/0/3 U 0/0/3 U
Alive+dead (contradictory) 0/0/3 U 3/0/0 S 3/0/0 S
Wash. stop France (FP) 0/0/3 U 0/3/0 H 3/0/0 S
Uploaded contract (OOC) 0/3/0 H 0/1/2 U 3/0/0 S
Twitter→Tesla (multi) 0/0/3 U 3/0/0 S 0/0/3 U
Mona Lisa (control) 3/0/0 S 1/2/0 S 3/0/0 S

(S/H/U = STRICT/HYBRID/UNGROUNDED; n=3 each cell.)

Findings:

  1. Detector accuracy is 6/6. All trigger questions fire the expected logical_statuses value during preflight (verified programmatically before the bench: stale_risk, contradictory_question, false_premise_suspected, out_of_corpus_risk — matched 1:1 with fixture intent). The detectors are doing what their unit tests claim.

  2. Quote mode is the most honest fallback. 4 of 5 trigger questions land all-UNGROUNDED on quote mode. The paraphrase verifier won't substring-match across the corpus when the question's premise has no anchor. Quote mode's mode-gated-off guard works in our favor here.

  3. Lattice modes accidentally ground 2 trigger questions to STRICT. Schrödinger's cat (alive+dead) JSON STRICT is defensible — the corpus contains quantum-mechanics articles that legitimately discuss the state. But:

    • JSON STRICT on "when did George Washington stop being president of France?" is NOT defensible. False premise; the model invented an answer that lexically grounded against some chunk. The metacog detector correctly flagged false_premise_suspected; the audit-line tail surfaced · false premise; but the verdict still lands STRICT.
    • JSON STRICT on "what does my uploaded contract say about clause 9?" is the same shape: out-of-corpus reference, model fabricates a grounding.
  4. Audit-line tails are doing operator-warning duty correctly. The bench rows persist preflight_logical_statuses and the render layer tails (· stale risk, · false premise, etc.) even on STRICT verdicts — operator sees the warning. But the substrate doesn't refuse execution by default for these shapes.

Implication for #000011 (SOFT_PREFLIGHT_HINT). This bench validates the design rationale for the soft-sidecar ticket: the deterministic metacog detectors flag these shapes correctly, but the corpus accidentally grounds 2/5 of them to STRICT. A model- assisted soft preflight could add independent semantic skepticism ("does George Washington being president of France match historical reality?") that the lexical detectors can't supply. Soft sidecar output would surface as · soft: false_premise_* on the audit-line, distinct from the hard · false premise tail, giving operators a stronger warning when both signals fire.

Implication for default policy. Keep metacognition_block_on_contradiction=False as the default. Schrödinger's cat (alive+dead) would have been rejected unnecessarily under a hard-block, and that's a real-world question with a legitimate answer. The label-only default is correct; operators wanting strictness opt in via --block-on-contradiction.

Bench artifact: bench/qa_results/2026-05-04T02-18-42Z.{jsonl,md}.

Addendum 5 — #000046 Phase 3 verifier numeric-gate regression check (2026-05-11)

arborist/qa/verify.py gained a paraphrase numeric-agreement gate (_numeric_signature + a check in _check_each_with_paraphrase): a span that token-covers the source ≥ paraphrase_coverage but asserts a digit-number the source lacks (modulo thousands-comma) is no longer paraphrase-grounded — it goes to unverified. Closes #000046 by lifting the falsification-hard-v1.jsonl rate 4/12 → 6/12 (5f-fal-hard-004 50-vs-100 and -007 300-vs-300,000 → UNGROUNDED).

Before/after make bench-qa (n=3 × 75 questions × 3 modes = 675 cells):

mode STRICT-rate before → after Δ
quote 0.53 → 0.50 3pp
claim_lattice_pointer 0.23 → 0.25 +2pp
claim_lattice 0.44 → 0.45 +1pp

All within the 5-pp noise floor. Per-row diff (675 common cells, 74 changed audit_mode): the only clearly gate-attributable QA shift was the fictional "our cold fusion breakthrough" year-claim demoting STRICT → HYBRID across all 3 samples — a correct demotion (the year isn't grounded). Every other transition was quote→quote / claim_lattice→claim_lattice LLM re-answer variance — the answer text changed on the re-run, not the verifier (the gate touches only the paraphrase fallback, never the verbatim/span/entity/claim-lattice paths). No regression on legitimate answers; the gate ships.

Bench artifacts: bench/qa_results/2026-05-11T13-42-38Z.{jsonl,md} (before) · bench/qa_results/2026-05-11T14-19-51Z.{jsonl,md} (after). Full per-ticket detail: docs/tickets/ticket-000046-harder-5sf-fixture-tier.md §5 Phase 3.

Addendum 6 — #000048 step 2.1 verifier entity-gate regression check (2026-05-11)

arborist/qa/verify.py gained an entity salient-token-disagreement gate (_entity_salient_disagrees + _is_single_sentence): in verify_quotes' entity branch (proximity policy), the weakest grounding — not cluster AND len(verified) <= 1 AND single sentence — is declined (→ UNGROUNDED) when the answer asserts a > 4-char capitalized content token (stopword-filtered) or a digit-number the source lacks. Catches "Insulin was discovered by Alexander Fleming" against "Penicillin was discovered by Alexander Fleming" (shared "Alexander Fleming" matched; swapped subject "Insulin" trips the gate). Lifts the falsification-hard-v1.jsonl rate 6/12 → 10/12 (the 4 HYBRID_ENTITY over-grounds: Insulin / Berlin / 1889 / Pacific).

Before/after make bench-qa (n=3 × 75 questions × 3 modes = 675 cells):

mode STRICT-rate before → after Δ
quote 0.50 → 0.54 +4pp
claim_lattice_pointer 0.25 → 0.22 3pp
claim_lattice 0.45 → 0.43 2pp

All within the 5-pp noise floor. Per-row diff (675 common cells, 30 quote-mode rows changed audit_mode): 0 quote-mode rows demoted to UNGROUNDED from the entity path — the gate didn't fire on a single legitimate QA answer in the whole bench. Every quote-mode transition was LLM re-answer variance (verifier quote→quote with the verdict flipping = a different answer text); the pointer/lattice deltas are noise too (the gate is in verify_quotes / quote mode, not the claim-lattice verifier). The gate is provably narrow on real traffic; it ships.

Bench artifacts: bench/qa_results/2026-05-11T14-19-51Z.{jsonl,md} (before — HEAD's verify.py) · bench/qa_results/2026-05-11T17-12-41Z.{jsonl,md} (after). Full per-ticket detail: docs/tickets/ticket-000048-verifier-upgrade-recombination-segmentation.md §5 step 2.1.

Addendum 7 — #000048 step 2.4 parse_pointer_claims clause-segmentation regression check (2026-05-11)

arborist/qa/parse_claims.py gained a clause segmenter: a line that crams several well-pointered claims onto one row is split into one claim per clause (split on ;, sentence boundaries, spaced dashes, and/or/because/although/since/while, inline (N) enumeration markers, commas — with (?![^\[]*\]) so a comma inside a [E1, E2] bracket never splits it). _segment_line keeps the split only if every resulting segment is well-pointered (a legit single claim like "The cat is black and white [E1]." or "The cast: A, B, C [E1]." is never broken — splitting would create pointer-less fragments → guard rejects; a leading colon-terminated header — "Two facts:", "Key points:" — is dropped). Plus a wrapped-bullet join: a continuation line (leading whitespace then a lowercase letter, no bullet) folds into the previous claim. Closes the 8 mis-segments in formulate-hard-v1.jsonl → rate 4/12 → 12/12 (that pack now at ceiling).

Before/after make bench-qa (n=3 × 75 questions × 3 modes = 675 cells; parse_pointer_claims feeds the 450 claim_lattice_pointer + claim_lattice cells):

mode STRICT-rate before → after Δ
quote 0.54 → 0.55 +1pp
claim_lattice_pointer 0.22 → 0.22 0pp
claim_lattice 0.43 → 0.45 +2pp

All within the 5-pp noise floor. Per-row diff (675 common cells): the segmenter changed the parsed-claim count on the same answer text for 7 of the 450 lattice cells (0 in claim_lattice, 7 in claim_lattice_pointer); of those, 2 caused an audit_mode change — both correct: (a) a wrap-join recovered an answer's intended structure (4 claims, 2 of which were pointer-less wrap-fragments → HYBRID) into 2 well-pointered claims → STRICT; (b) a crammed-one-line blob (1 monolithic claim, all pointers → STRICT) split into 8 claims, some of which don't individually verify → HYBRID — the honest verdict (false-positive STRICT was the corruption). Every other lattice/quote delta is LLM re-answer variance (answer_chars changed, often drastically). No regression — the segmenter's only visible effects on real traffic are honest improvements.

Bench artifacts: bench/qa_results/2026-05-11T17-12-41Z.{jsonl,md} (before — HEAD's parse_claims.py) · bench/qa_results/2026-05-11T20-26-37Z.{jsonl,md} (after). Full per-ticket detail: docs/tickets/ticket-000048-verifier-upgrade-recombination-segmentation.md §5 step 2.4.