arborist/docs/tickets/ticket-000003-anchor-class-warrant.md
russell@unturf.com 8d6961fcc1
aborist/arborist
modified:   .gitlab-ci.yml
	modified:   bench/qa_questions.txt
	modified:   bench/qa_sweep.py
	modified:   bench/run.sh
	modified:   docs/TICKETS.md
	modified:   docs/_source/README.md
	modified:   docs/_source/_ext/makefile_targets.py
	modified:   docs/_source/api/cli.rst
	modified:   docs/_source/api/distill.rst
	modified:   docs/_source/api/mesh.rst
	modified:   docs/_source/api/qa.rst
	modified:   docs/_source/api/retrieval.rst
	modified:   docs/_source/api/storage.rst
	modified:   docs/_source/api/substrate.rst
	modified:   docs/_source/concepts.rst
	modified:   docs/_source/conf.py
	modified:   docs/_source/cookbook.rst
	modified:   docs/_source/index.rst
	modified:   docs/_source/license.rst
	modified:   docs/_source/quickstart.rst
	modified:   docs/bench-maxing.md
	modified:   docs/benchmarks.md
	modified:   docs/cti-architecture.md
	modified:   docs/diagrams/aborist-modules.dot
	modified:   docs/diagrams/aborist-modules.svg
	modified:   docs/diagrams/mesh-data-flow.dot
	modified:   docs/diagrams/mesh-epoch-lifecycle.dot
	modified:   docs/diagrams/mesh-epoch-lifecycle.svg
	modified:   docs/diagrams/mesh-group-decisions.dot
	modified:   docs/diagrams/mesh-group-decisions.svg
	modified:   docs/diagrams/mesh-identity-stack.dot
	modified:   docs/diagrams/mesh-secret-envelope.dot
	modified:   docs/mesh.md
	modified:   docs/qa-modes-bench.md
	modified:   docs/seven-point-program.md
	modified:   docs/tickets/ticket-000001-retrieval-keywords-audit-gap.md
	modified:   docs/tickets/ticket-000002-reference-frame-polarity-contract.md
	modified:   docs/tickets/ticket-000003-anchor-class-warrant.md
	modified:   docs/tickets/ticket-000005-label-ladder-migration.md
	modified:   docs/tickets/ticket-000006-bench-emergent-findings.md
	modified:   docs/tickets/ticket-000007-query-layer-hyphen-fold.md
	modified:   docs/tickets/ticket-000008-broad-quantifier-preflight-guard.md
	modified:   docs/tickets/ticket-000009-quantifier-preflight-dag-binding.md
	modified:   docs/tickets/ticket-000010-metacognition-preflight-guard.md
	modified:   docs/tickets/ticket-000011-soft-preflight-hint-sidecar.md
	modified:   scripts/backfill_concepts.py
	modified:   scripts/bench_emergent.py
	modified:   tests/crawler/test_async_web_fetcher.py
	modified:   tests/crawler/test_bridge.py
	modified:   tests/crawler/test_web_fetch.py
	modified:   tests/test_bench_qa_sweep.py
	modified:   tests/test_burn.py
	modified:   tests/test_burn_doc.py
	modified:   tests/test_claim_lattice.py
	modified:   tests/test_cli_render.py
	modified:   tests/test_compress.py
	modified:   tests/test_concepts.py
	modified:   tests/test_dag.py
	modified:   tests/test_directives.py
	modified:   tests/test_distill.py
	modified:   tests/test_distill_recursive.py
	modified:   tests/test_evict.py
	modified:   tests/test_frame.py
	modified:   tests/test_grok_source.py
	modified:   tests/test_html_source.py
	modified:   tests/test_ingest.py
	modified:   tests/test_inspect.py
	modified:   tests/test_journal.py
	modified:   tests/test_keys.py
	modified:   tests/test_llm_context_base.py
	modified:   tests/test_merkle.py
	modified:   tests/test_mesh.py
	modified:   tests/test_mesh_aead.py
	modified:   tests/test_mesh_chain.py
	modified:   tests/test_mesh_cli.py
	modified:   tests/test_mesh_cli_pull.py
	modified:   tests/test_mesh_wire.py
	modified:   tests/test_mesh_wire_e2e.py
	modified:   tests/test_metacognition.py
	modified:   tests/test_migration_audit_mode.py
	modified:   tests/test_providence_source.py
	modified:   tests/test_qa.py
	modified:   tests/test_qa_quality_live.py
	modified:   tests/test_quantifier_caps.py
	modified:   tests/test_quantifier_classifier.py
	modified:   tests/test_quantifier_phase4.py
	modified:   tests/test_quantifier_reminder.py
	modified:   tests/test_query.py
	modified:   tests/test_reclassify.py
	modified:   tests/test_repair.py
	modified:   tests/test_resume.py
	modified:   tests/test_snapshot.py
	modified:   tests/test_soft_preflight.py
	modified:   tests/test_tfidf.py
	modified:   tests/test_vcs_source.py
	modified:   tests/test_verify.py
	modified:   tests/test_verify_json.py
	modified:   tests/test_versioned_ingest.py
	modified:   tests/test_warrant.py
	modified:   tests/test_wikipedia_old.py
	modified:   tests/test_wikipedia_xml.py
	modified:   tests/test_wikitext.py
2026-05-07 09:31:49 -04:00

11 KiB
Raw Blame History

Ticket #000003 — Anchor-class warrant generalization (Module H+)

Status: closed · landed in commit (this commit pair) Opened: 2026-05-01 Closed: 2026-05-02 Directive: D6 — General anchor-class warrant before semantic NLI. Scope: Generalize the warrant-lite hard check (Rule 7 in verify_claim_lattice) beyond relation-shape and date-shape anchors to cover entity-list, why-cause, count, and how-much shapes. Pin the broader class of lazy-anchor failures structurally before any NLI substrate exists in the proof path. Audience: fox + future blackops shifts. Hard constraint: the warrant check stays lexical and binary. No NLI, no embeddings, no model self-grading. Soft signals continue to live in the sidecar layer (docs/verifier-semantic-gap-design.md covers that path separately).


1. Problem statement

Today's warrant-lite (arborist/qa/warrant.py:warrant_check) covers two anchor classes:

  • Proper-noun anchors (relation-shape questions): for "who is X's Y?" / "who founded Z?" / etc., at least one Title-Case proper-noun phrase from the claim must appear in some cited span. Catches the Homer-Simpson lazy-anchor where the cited span talks about Castellaneta's voice work but never names "Mr. Burns".

  • Date anchors (any question shape): for any claim that names a specific year, that year must appear in some cited span. Catches the Back-to-the-Future lazy-anchor where the cited span lacks the asserted "1985".

These two classes leave four other shapes uncovered, where the same lazy-anchor pattern can pass the existing seven hard checks:

1.1 Concrete failure shapes pending coverage

Entity-list shape ("name the simpsons family members including pets?"):

  • Claim: "The Simpsons family consists of Homer, Marge, Bart, Lisa, Maggie."
  • Cited span: a Simpsons-family page paragraph that mentions Homer and Marge but not the children. The bare-name guard demands ≥2 content tokens per claim, but doesn't demand all named entities appear in some cited span. The verifier passes on the partial evidence.
  • Warrant gap: at least one of the named entities (the most specific one — "Maggie" over the more-common "Homer") must appear in a cited span, OR the claim must be split.

Why-cause shape ("why did the titanic sink?"):

  • Claim: "The Titanic sank after striking an iceberg in 1912."
  • Cited span: a passenger-list page that mentions the Titanic but not the iceberg.
  • Warrant gap: a cause word (or the claim's primary noun for the cause) must appear in a cited span. Without this, a model can cite the topical-but-causally-irrelevant chunk.

Count shape ("how many wives did henry the eighth have?"):

  • Claim: "Henry VIII had six wives."
  • Cited span: a wife's individual bio page that doesn't say "six".
  • Warrant gap: the asserted count number must appear (in numeric or word form) in some cited span. Same shape as the date anchor but on count-tokens.

How-much shape ("what is the boltzmann constant?"):

  • Claim: "The Boltzmann constant is approximately 1.38 × 10⁻²³ J/K."
  • Cited span: a Boltzmann biography paragraph.
  • Warrant gap: the asserted numeric value must appear in some cited span. Same shape as date/count.

2. Generalization

The pattern across these shapes: the claim asserts a structurally load-bearing token (an entity, a year, a count, a numeric value, a cause noun); the warrant requires that token to appear in some cited span.

Three new anchor classes proposed:

2.1 Entity-list anchor

When the question shape is name X / list X / who are the members of X / name X family + pets, the claim is expected to enumerate proper nouns. Warrant: every Title-Case proper noun in the claim text (after a small stopword set) must appear in some cited span.

Conservative: a single un-anchored proper noun in an otherwise-anchored claim does NOT fail the warrant — the bench evidence on entity-list questions shows models occasionally name an extra entity from training. Mark such claims EVIDENCE_LINKED_PARTIAL rather than fail outright (per the demote-don't-reject pattern).

2.2 Count anchor

Claim contains a number (digit form 6, word form six, ordinal form sixth). For each such number, at least one cited span must contain the same number in any of the equivalent forms. Bidirectional match: word→digit and digit→word both count.

The numeric-value variant (Boltzmann constant 1.38 × 10⁻²³) is a special case of count-anchor where the number includes scientific notation; treat by tokenizing on [-+×^×*0-9.eE]+ and string-matching.

2.3 Cause anchor (why-shape questions)

Question starts with "why". Claim is expected to name the cause noun (e.g. "iceberg", "asteroid impact", "propaganda"). The existing proper-noun extractor mostly catches this when the cause is Title-Cased, but lowercase common nouns slip through. Extension: for why-shape questions, also extract bare common-noun candidates from the claim (≥5-char tokens not in a generic stopword list) and check that at least one appears in some cited span.

Conservative scope: only fire on why-shape questions. False-positive risk on lowercase common-noun extraction is real; gate by question shape to constrain it.

3. Implementation sketch

3.1 Question-shape detector extension

arborist/qa/warrant.py already has _question_is_relation_shape. Add:

_QUESTION_IS_ENTITY_LIST_RE = re.compile(
    r"^\s*(?:name|list|who\s+are|name\s+the|list\s+the|"
    r"members?\s+of|family\s+of)\b",
    re.IGNORECASE,
)

_QUESTION_IS_COUNT_RE = re.compile(
    r"^\s*how\s+(?:many|much)\b", re.IGNORECASE,
)

_QUESTION_IS_WHY_RE = re.compile(
    r"^\s*why\b", re.IGNORECASE,
)

3.2 Per-shape anchor extractors

def _extract_entity_anchors(claim_text: str) -> list[str]:
    """Title-Case proper-noun phrases (existing extractor reused)."""

def _extract_count_anchors(claim_text: str) -> list[str]:
    """Tokens matching count-shape regex; expand digit↔word
    equivalents (six↔6, twelve↔12) before substring testing."""

def _extract_cause_anchors(claim_text: str) -> list[str]:
    """≥5-char common nouns + Title-Case proper nouns in the claim.
    Gated to fire only on why-shape questions."""

3.3 Dispatch in warrant_check

Today's implementation runs proper-noun anchor + date anchor in sequence and returns (ok, missing). Extend to dispatch by question shape:

def warrant_check(claim_text, cited_spans, *, question):
    anchors_required = []
    if _question_is_relation_shape(question):
        anchors_required.append(_extract_entity_anchors(claim_text))
    if _question_is_entity_list_shape(question):
        anchors_required.append(_extract_entity_anchors(claim_text))
    if _question_is_count_shape(question):
        anchors_required.append(_extract_count_anchors(claim_text))
    if _question_is_why_shape(question):
        anchors_required.append(_extract_cause_anchors(claim_text))
    # Date anchor stays unconditional (any claim with a 4-digit year).
    anchors_required.append(_extract_date_anchors(claim_text))
    # ... existing pass/fail logic ...

Each anchor class returns its own missing list; failure on any class fires WARRANT_MISSING with missing_anchors carrying the union.

3.4 Policy gate

policy["claim_lattice_warrant_check_enabled"] (already exists) extended to a per-class dict:

"claim_lattice_warrant_classes": {
    "relation": True,
    "date":     True,
    "entity_list": True,    # new
    "count":      True,     # new
    "why_cause":  True,     # new
},

Folds into governance_policy_hash so disabling a class invalidates prior records on lookup. Default-on for safety; operators can disable individual classes per-query if they over-fire on a specific corpus.

4. Tests required

Already covered (relation shape + date anchor — landed earlier):

  • [x] Relation-shape claim with proper-noun anchor missing → fails.
  • [x] Date-anchor claim with year missing → fails.
  • [x] Vacuous-pass when claim has no anchor.

To add when this ticket lands:

  • [ ] Entity-list claim with all named entities present in some cited span → passes.
  • [ ] Entity-list claim with one Title-Case entity missing → demotes to EVIDENCE_LINKED_PARTIAL (not failure).
  • [ ] Count-shape claim with "six" and span containing "6" → passes (digit↔word equivalence).
  • [ ] Count-shape claim with "twelve" and span containing "12" → passes.
  • [ ] Count-shape claim with "six" and no count token in any span → fails.
  • [ ] Why-shape claim with cause noun missing from spans → fails.
  • [ ] Why-shape claim where claim's Title-Case anchor IS present (existing extractor) → passes via proper-noun path.
  • [ ] Bench: entity-list / count / why-cause coverage measured on the existing question set; baseline strict-rate before vs after.

5. Out of scope

  • Per-question-type rule libraries. The general anchor-class primitive catches the failures listed in §1.1 deterministically without a per-type rule book. Typed contracts (e.g. release_date_lookup with required predicate cues) earn their slot only when bench evidence shows the general primitive misses cases.
  • NLI-grade entailment. Soft signal, separate ticket if/when the verifier-semantic-gap design (docs/verifier-semantic-gap-design.md) lands.
  • Cross-claim warrant. When claim A asserts X and claim B asserts Y, the warrant runs per-claim independently. Cross-claim consistency (does claim B contradict claim A's evidence?) is out of scope.

6. Status

Closed 2026-05-02. Landed via per-shape detectors + extractors in arborist/qa/warrant.py:

  • _question_is_entity_list_shape / _question_is_count_shape / _question_is_why_shape regex detectors.
  • extract_entity_list_anchors (multi-word phrase solo-cap individual names — list-aware so comma-separated entities each contribute).
  • extract_count_anchors (digit + word forms; year-shaped digits filter out).
  • extract_cause_anchors (proper nouns + ≥5-char lowercase common nouns post-stopword; gated on why-shape only).
  • _count_anchor_present (digit ↔ word equivalence including ordinal collapse: sixth → 6).

Dispatch in warrant_check: each shape's anchors get extracted when the question shape matches; failures accumulate across shape classes. ANY-match for relation / entity-list / cause classes (demote-don't-reject); ALL-match for date / count classes (unambiguous lexical anchors).

17 new tests in tests/test_warrant.py covering each detector + extractor + integration via warrant_check. Marker test in tests/test_directives.py flipped to positive assertion: test_d6_warrant_generalization_landed. Full suite: 709 passed.

Per-class policy gate (claim_lattice_warrant_classes dict proposed in §3.4) deferred — current warrant_check_enabled: bool gates everything. Per the five-step algorithm step 2: per-class flags earn their slot only when bench evidence shows over-firing on a specific class.

Out-of-scope items from §5 stay deferred: typed-contract framework, NLI-grade entailment, cross-claim warrant.