New module aborist/qa/retrieval_plan.py:
RetrievalPlan dataclass — frozen, captures the operator-
influenceable retrieval inputs that determine source
selection (retrieval_keywords, top_k, over_fetch,
max_context_chars, shard_ids).
retrieval_plan_hash() — SHA-256 over canonical-JSON.
Deterministic per call; folds into the run-DAG retrieval
stage as a bound input alongside the existing
sources_summary output.
build_run_dag (aborist/qa/dag.py) accepts retrieval_plan_hash:
When provided, the retrieval stage hash binds BOTH plan
(input) and result (output): H({"retrieval_plan_hash":...,
"sources_summary_hash":...}).
When omitted (e.g. legacy / quote-mode callers that haven't
plumbed it yet), the retrieval stage falls back to the
historical sources-summary-only hash so pre-#000001
run_dag_root values stay stable.
query.py constructs the plan per call and passes it through.
Question text intentionally NOT in the plan — already covered
by question_hash. Shard ids included so audit can reproduce
which shards the search ran against.
Two runs with identical sources but different retrieval keywords
now produce different run_dag_root values — the provenance gap
on operator hints (auditor recovers "these were the keywords
that pulled in those sources") closes for the run-DAG path.
Marker test in tests/test_directives.py flipped from "absent"
assertion to "present": test_d4_retrieval_plan_binding_landed.
Plus test_d4_retrieval_plan_hash_module_exists pinning the
module shape. Full suite: 712 passed.
Deferred per ticket §6:
- audit events (retrieval_plan_built / retrieval_result_selected)
- providence_cache.retrieval_keywords SQL column
- optional strict cache_key mode (retrieval_plan_affects_cache_key)
These are ergonomic affordances atop the run-DAG binding; earn
their own tickets when bench evidence shows the blob path is
too friction-heavy for real workflows.
Directive D4 status: ½ → ✓. Ticket #000001 closed.
Replaces the two-rung EVIDENCE-LINKED / EVIDENCE-LINKED-PARTIAL
display label for claim-lattice methods with a four-rung ladder
that names a strictly stronger property at each rung:
POINTER-LINKED pointer/source/chunk verified;
warrant either didn't apply or failed
ANCHOR-WARRANTED pointer-linked + warrant passed where it ran;
other soft demotes may apply
EVIDENCE-WARRANTED anchor-warranted + no soft demotes
UNGROUNDED no verified pairs
HYBRID gets a -PARTIAL suffix on whichever rung applies.
Implementation: _render_audit_label gains a violations parameter
(defaults to None for backward-compat). _ladder_rung_for_lattice
discriminates rungs from the existing violations list:
- WARRANT_MISSING in violations → POINTER-LINKED
- any of {LAZY_ANCHOR_DEMOTED, POINTER_OVERFLOW_TRIMMED,
TOO_MANY_CLAIMS, BARE_NAME_CLAIM} → ANCHOR-WARRANTED
- else → EVIDENCE-WARRANTED
Design simplification vs the ticket's §3 sketch: the proposed
verifier_steps_ran field on the verdict dict was NOT needed. The
existing violations list carries enough signal to discriminate
all rungs. Per the five-step algorithm step 2: don't add fields
you don't need.
Quote / span / entity / paraphrase methods stay unchanged (their
STRICT verifies pinned spans, not synthesis).
Schema column audit_mode enum stays {STRICT, HYBRID, UNGROUNDED}
— pure renderer transformation, no governance_policy_hash bump,
no cache invalidation, no mesh-wire-format change. Existing
providence records render under the new ladder on next read.
5 new renderer tests in tests/test_cli_render.py covering each
rung mapping. D7 anti-regression test in tests/test_directives.py
updated to gate on the ladder labels. Bench helper docstring
follows. Full suite: 711 passed.
Directive D7 stays at ✓; ticket #000005 closed.
Three new question-shape classes dispatched through warrant_check
alongside the existing relation + date anchors:
(1) Entity-list shape — `name X`, `list X`, `who are the members
of X`. List-aware extractor `extract_entity_list_anchors`
(multi-word phrases ∪ solo-cap individual names) so comma-
separated entities each contribute. ANY-match semantics:
demote-don't-reject when an extra entity from training-prior
appears alongside grounded ones.
(2) Count shape — `how many X`, `how much X`. Digit ↔ word
equivalence (claim says "six", span says "6", or vice versa)
with ordinal collapse (`sixth → 6`). Year-shaped digits
filter out (those belong to the existing date anchor class).
ALL-match semantics: every count token in the claim must
appear in some cited span as digit or word.
(3) Why-cause shape — `why X`. Cause-anchor pool widens to
≥5-char lowercase common nouns (post a generic stopword set
that filters quantifier-adjective fillers like "various",
"factors", "situation") PLUS proper-noun anchors from the
existing extractor. Gated on why-shape only: lowercase
common-noun extraction has higher false-positive risk
elsewhere.
Per-class policy gate (proposed `claim_lattice_warrant_classes`
dict) deferred per the five-step algorithm step 2: single
`warrant_check_enabled: bool` is the minimum viable gate; per-
class flags earn their slot when bench evidence shows over-firing
on a specific class.
17 new warrant tests (detector + extractor + integration).
Marker test in test_directives.py flipped from "absent" to
"present" assertion: test_d6_warrant_generalization_landed.
Full suite: 709 passed (was 692, +17).
Directive D6 status flipped to ✓ in seven-point-program.md.
Ticket #000003 closed.
Each directive gets a structural pin in tests/test_directives.py.
A future PR that silently weakens a directive fails by name.
D1 (no LLM in hard verifier path):
- verify_quotes / verify_claim_lattice / verify_claim_lattice_json
signatures must NOT carry chat_client / llm / judge / model
parameters.
- SCHEMA_SQL verifier_method CHECK constraint enum excludes
'llm', 'model', 'judge', 'nli', 'hermes', 'ai'.
D2 (Hermes emits pointer clauses):
- ANSWER_MODES contains both lattice variants.
- DEFAULT_ANSWER_MODE in ANSWER_MODES.
- parse_pointer_claims returns structured (claim_text, pointer_ids)
nodes from pointer-line input.
D3 (build CTI internally):
- Runtime parses pointer-line prose into typed claim nodes
(text + pointer_ids list); model never produces structured
output directly in pointer mode.
- Evidence map built by runtime from retrieved chunks; pointer
ids minted sequentially; evidence ids content-addressed.
D4 (bind retrieval AND evidence map):
- build_run_dag accepts evidence_map_root parameter (✓).
- Marker test: build_run_dag does NOT yet accept retrieval_plan_hash
parameter; flip when ticket #000001 lands.
D5 (deterministic pointer verify):
- verify_claim_lattice is deterministic: same inputs → same
verdict byte-for-byte.
- _claim_textually_overlaps_evidence (the per-pair coverage
check) is a pure function with no LLM-side parameters.
D6 (anchor-class warrant before NLI):
- warrant_check signature is lexical-only (no chat_client / llm
/ judge / nli params).
- Date-anchor failure case fires (year missing from cited spans).
- Relation-shape failure case fires when ALL proper-noun anchors
are missing from cited spans.
- Marker test: per-shape detectors for entity-list / count /
why-cause shapes are absent today; flip when ticket #000003 lands.
D7 (rename labels honestly):
- Renderer maps STRICT → EVIDENCE-LINKED for claim_lattice* methods.
- Renderer keeps STRICT for quote / span / entity / paraphrase
methods (those verify against pinned spans, not synthesis).
- SCHEMA_SQL audit_mode CHECK constraint enum stays
{STRICT, HYBRID, UNGROUNDED} regardless of renderer relabel.
D8 (test-pinning before automation):
- docs/seven-point-program.md exists.
- bench/qa_sweep.py exposes _directive_compliance helper.
- docs/TICKETS.md indexes the partial directives (D3, D4, D6) so
the design log doesn't drift from the program doc.
23 new tests; full suite 692 passed (was 669, +23).
The "marker" tests for D4 and D6 document open work: today they
assert that a parameter / detector is ABSENT; when the corresponding
ticket lands, the assertion flips to PRESENT. The test failing
during ticket implementation is the expected signal, not a
regression — the test message names the ticket so the work-in-
progress is legible.