Three things in one commit because they're tightly coupled (README
points at the diagrams; diagrams index in modules/index.md points
back at README; module pages embed the diagrams).
(1) README — label refresh:
- Quickstart label changed from STRICT/HYBRID/UNGROUNDED to the
four-rung ladder POINTER-LINKED → ANCHOR-WARRANTED →
EVIDENCE-WARRANTED → UNGROUNDED with -PARTIAL suffix on HYBRID.
- Verifier section spells out both layers (schema trichotomy +
display ladder), the seven hard checks of claim_lattice, and
the five anchor classes of warrant.
- Architecture tree updated: concepts/ package added, qa/
sub-modules expanded (warrant, evidence, parse_claims, dag),
verify.py described as quote/span/entity/paraphrase + claim_lattice.
- Concept overlay description updated for corpus-derived layer
(concept_relations table, link_reciprocity extractor, 1.6%
tax cite).
- Test count: 326+ → 641+.
(2) docs/diagrams/ — Graphviz dot sources:
- aborist-modules.dot — top-level package graph (substrate /
storage / sources / retrieval / qa / mesh / cli)
- query-pipeline.dot — question → cache → retrieval → LLM →
verify → render → cache write, with phase budgets
- ingest-pipeline.dot — source doc → canonicalize → chunk →
Merkle → upsert (+ optional distill)
- verifier-ladder.dot — (audit_mode, violations) → display rung
decision tree
Existing mesh-*.dot kept as-is. Makefile `make docs` target
extended to also emit .svg alongside the existing .png so the
diagrams render in markdown viewers.
(3) docs/modules/ — per-module reference pages:
- index.md (links to every diagram + every module page)
- merkle.md, document.md, store.md, ingest.md, evict.md,
sources.md, search.md, concepts.md, qa.md, distill.md,
wikitext.md
Each page is a one-screenful concise reference: what the
module is for, public API, key invariants, embedded diagrams
where useful, link to source. Mesh stays at the existing
docs/mesh.md + docs/mesh-deploy.md (already comprehensive).
Tests: 641 passed (no code change).
6.9 KiB
aborist.qa
The Q&A pipeline. Question → cache → retrieval → LLM → verify → render → cache write. Lives in 9 sub-modules; this page is the map.
Sub-modules
qa.client — LLM transport
ChatClient ABC with three concrete implementations:
StubClient— deterministic test fixture; returns canned responses keyed on the input. Used in unit tests to avoid network.OpenAICompatibleClient— talks to any OpenAI-shape/v1/chat/completionsendpoint (Hermes-3 on vLLM by default). Includes HTTP retry layer (3× exponential backoff on 5xx).- (Future)
AnthropicClient— Claude API direct.
qa.keys — the 8-dim cache_key
cache_key = sha256(
source_root | question_hash | model_profile_hash |
conversation_hash | governance_policy_hash |
schema_version | canonicalization_version | chunking_version
)
Two question-hash modes (strict vs equivalence_class) live here.
Bumping any of these eight dimensions invalidates prior records on
lookup. The verifier_policy_hash (v9.9 9th dim) is also
implemented here.
qa.runner — ask() for single-doc Q&A
The simplest entry point. Take one document, ask one question, get back an answer + audit_mode + cache record. Used by the CLI for focused queries against one URI.
qa.query — query() for multi-source RAG
The main retrieval entry point. Walks shards, runs FTS5 BM25 with
AND→OR fallback (with synonym-pool injection in OR mode), filters
by title relevance with 4 accept paths, reranks by body coverage +
title boost + source role + title purity, assembles a 60 KB context
budget, calls the LLM, runs the verifier, persists to
providence_cache.
qa.verify — the layered verifier
Five strategies run in sequence; first to find evidence classifies:
quote—"..."-wrapped claims tested verbatimspan— bullet/sentence units substring-testedentity— multi-word proper nouns with proximity gatingparaphrase— token coverage on prose-shaped spans (≥85%)claim_lattice— pointer-line[E1,E2]or JSON; runs seven deterministic hard checks:- parser succeeded
- evidence_id resolves
- source_role allowed
- claim text non-empty
- citation coverage threshold
- pointer count cap (trim-and-verify)
- anchor-class warrant (see
qa.warrant)
The classifier output rolls up into the v9.8 trichotomy
audit_mode ∈ {STRICT, HYBRID, UNGROUNDED}. Display layer (in
cli.py) maps (audit_mode, violations) → four-rung ladder.
qa.warrant — anchor-class warrant
Five lexical anchor classes the verifier composes:
- Proper-noun — relation-question shape; at least one Title-Case anchor must appear in some cited span
- Date — claim has a 4-digit year + month name; ALL components required in some cited span
- Entity-list — entity-list-shape question; ≥1 named entity must anchor (demote-don't-reject)
- Count — count-shape question; count token must appear in word OR digit form (digit↔word equivalence)
- Cause — why-shape question; ≥1 cause anchor (proper noun OR ≥5-char common noun outside stopword pool)
The warrant layer earns proof-path entry by staying lexical —
no NLI, no embeddings. Substring tests over already-canonicalized
spans. See docs/concept-relations-design.md (sibling section)
for the relationship to retrieval-time synonym expansion.
qa.evidence — EvidenceObject + spotlight
Builds the runtime evidence map for claim-lattice modes. Each
chunk becomes one EvidenceObject carrying TWO ids:
pointer_id— short prompt-facing tag (E1,E2, …)evidence_id— content-addressedE########(sha256-derived)
The model sees only pointer_ids in the prompt; the runtime maps
to evidence_id for the cache & run-DAG (run-stable identity).
The spotlight excerpt picks the load-bearing slice via density rank — find ALL match positions for ALL claim content tokens, pick the position with maximum distinct-token cluster within ±half-window. Replaces the older first-match-of-longest-token approach which lost the load-bearing slice on noisy chunks.
qa.parse_claims — pointer-line parser
Walks lines of the model output, pulls every [E\d+] and
[E\d+,E\d+,…] bracket payload, returns
(claim_text, pointer_ids[]) per line. Lines without a tag get
parse_status='NO_EVIDENCE_POINTER' & count toward the denominator
so unsourced prose can't smuggle past the verifier.
qa.dag — per-run Merkle DAG
Commits each provenance step independently as a stage hash. Two shapes:
- 7-stage (quote mode): question / retrieval / context / prompt / answer / verify / final_label
- 9-stage (claim-lattice / CTI): question / retrieval / evidence_map / prompt / raw_answer / parsed_claim_lattice / verify / render / final_label
The run_dag_root is persisted alongside every providence record;
run_dag_blob carries the full {root, nodes} JSON so an auditor
can recompute & verify any step.
qa.inspect — read-only sidecar
Pulls source chunks for a given cache_key & classifies each
unverified span: verbatim_in_base / verbatim_in_raw_only /
trailing_artifact / paraphrase / partial_paraphrase /
no_overlap. Also includes the deflection-detection sidecar
(subject-anchor heuristic for adversarial-premise topic shift).
Sidecars never write to providence_cache or audit_events —
they're diagnostic only. That invariant is what keeps audit_mode
a binary classification rather than a soft score.
qa.concepts — backwards-compat shim
Delegates to aborist.concepts (the corpus-derived synonym/rivalry
layer). Pre-2026-05-01 the data lived as hand-curated frozensets in
this file; now it's a per-shard SQLite table. The shim preserves
the legacy public API (synonym_expand, rivalry_excluded,
has_compare_phrasing) so call sites in qa/query.py didn't have
to change.
aborist/qa/concepts.py →
aborist/concepts/
Source papers
- Whitepaper §13.8 covers the layered verifier in depth
- Whitepaper §13.9 covers claim-lattice / CTI mode
docs/cti-architecture.mdis the architecture referencedocs/seven-point-program.mdenumerates the seven hard checksdocs/concept-relations-design.mdcovers the synonym layer