← Về thư mục
📄 / / academic-research-skills / docs / design / 2026-05-21-v3.10-182-promote-citation-gate-spec.md

ARS v3.10 — Issue #182 Promote Deterministic Citation Verification Gate

Date: 2026-05-21 Issue: #182 Umbrella: #198 (v3.10 deterministic verification layer) Target release: v3.10 Companion specs: 2026-05-21-v3.10-183-epistemic-status-spec.md (full, the source of truth for the §3.6 verification_gate shared contract), 2026-05-21-v3.10-184-extend-eval-harness-spec.md (lite, depends on the citation-extraction subset of this spec for baseline metrics) Upstream v3.8 spec: docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md (826 lines; this spec extends but does not duplicate it)

This is a lite spec. It documents only the v3.10 promote-and-extend delta on top of the existing v3.7.3 / v3.8 / v3.9 citation verification surface. The umbrella issue gap analysis confirmed that ~75% of #182's original "Proposal" already shipped between v3.7.3 (anchor markers + claim-faithfulness audit) and v3.9.0 (cross-index triangulation); v3.10 #182 unifies, promotes, and extends rather than rebuilds.


0. v3.11 Amendment (2026-06-02) — read this first

Status of #182 since this spec was written. This spec was authored 2026-05-21 under the v3.10 umbrella (#198). The umbrella shipped its other two children — #183 (epistemic-status gate) and #184 (eval harness + ranking-lift gate) — in v3.10.0 and both are CLOSED. #182 itself was specced but NOT implemented in v3.10: none of the §3 deliverables (arxiv_client.py, verification_cache.py, citation_verification_summary.{schema.json,py}, the verification_gate/ package, verify_passport.py, their tests) exist on main, and the v3.10-delta content described in §2 never landed in the four pre-existing files it touches. Two shipped surfaces carry forward-references waiting on this work: scripts/run_evals.py ("verification_gate.verify_citation has not shipped yet (#182 Delta 5)") and evals/gold/citation_extraction/README.md (the Delta 4 reducer reconciles "when it does"). #182 is now scoped into v3.11 as the one self-assessed P0 (#299). The §1–§8 body below stays valid as the implementation plan; this section amends the Delta 3 gate model only.

What changed: Delta 3 / formatter Rule 12 gate model. As originally written, Delta 3 promotes ARS_CLAIM_AUDIT=1 to a blocking default and Rule 12 is an unconditional formatter REFUSE on lookup_verified == false. That predates the v3.10 policy-layer rescope (docs/design/2026-05-31-ars-v3.10-policy-layer-rescope-spec.md), which shipped the repo's canonical deterministic hard-block model: a passport-level terminal_policies block, default advisory, opt-in strict, where strict co-emits a terminal TERMINAL-BLOCK severity=HIGH-BLOCK token (not ack-able) alongside the advisory suffix, with the finalizer as sole policy evaluator and manual entries structurally exempt. A new gate must inherit that model, not introduce a second, inconsistent hard-block philosophy (the "blocking default" + the would-be per-hit override are both out of step with the shipped opt-in discipline).

The amended design (reconciled through independent cross-model design review, 2026-06-02):

  1. New terminal_policies key citation_existence — closed enum {advisory, strict}, added to shared/contracts/passport/terminal_policies.schema.json alongside contamination_triangulation. Per-key absence = advisory, matching the existing evaluator convention (not a JSON-Schema default). This replaces Delta 3's ARS_CLAIM_AUDIT default-flip as the gate's on/off control. The v3.8 audit pipeline's detection still runs unconditionally (it always populates the citation_verification_summary[] so the signal is visible); citation_existence governs only whether lookup_verified == false is terminal or advisory.

  2. advisory (default) — lookup_verified == false surfaces as a visible, auditable advisory: the ref marker stays byte-equivalent to v3.9.x (no citation_existence advisory suffix — the marker advisory slot is reserved for the contamination CONTAMINATED-* suffix and the v3.7.3 grammar caps the marker at one advisory token), and the visibility is carried by the always-populated citation_verification_summary[] aggregate (the resolver_outcomes make the criterion fully inspectable) plus the formatter's mandatory provenance_summary.md Citation Existence Advisories section, which lists every advisory false row so the warning travels with the human-reviewed deliverable. It is /ars-mark-read ack-able, consistent with all other advisory tiers. This default avoids false negatives on the cases that motivated strict_articles_only (resolver outage, metadata mismatch, non-English / regional / DOI-less coverage gaps) becoming hard stops.

  3. strict (opt-in) — promotes lookup_verified == false to the existing terminal token: TERMINAL-BLOCK severity=HIGH-BLOCK policy=citation_existence reason=lookup_verified_false mode=strict policy_hash=<h>, appended additively to the base-status token (it does not replace the base status). The "why" survives in reason=lookup_verified_false + the citation_verification_summary[] aggregate — NOT in a marker advisory suffix: unlike contamination_triangulation, citation_existence adds no CONTAMINATED-*-style advisory token in either mode (C-V6(b)/(c)/(e); the marker's single advisory slot stays reserved for contamination). Terminal: not /ars-mark-read ack-able, exactly as contamination_triangulation=strict. The human decision lives at the opt-in layer (do I run this corpus under citation_existence=strict?), not at a per-hit override — same as the shipped precedent. This satisfies "the human decides at every gate" without inventing a new override surface.

  4. Two coupled changes, after an independent design review (2026-06-02) rejected the intermediate "data-invariant decoupling" form for false-blocking legitimate unindexed citations and for a "Zombie pipeline" UX contradiction (advisory-but-ack-able yet unconditionally blocking). The root cause was not the gate shape but the over-broad false definition. Both are fixed:

(4a) Narrow the lookup_verified == false definition by query form (Delta 4 refinement). The shipped Delta 4 classifies false as "≥1 resolver unmatched AND no matched" without distinguishing how the resolver was queried. That conflates two very different signals: - ID-keyed unmatched = positive non-existence evidence. A resolver queried by an exact key (DOI or arXiv ID) that returns unmatched is strong fabrication evidence: the ID is a precise lookup key, so "this DOI does not resolve" means the cited identifier is bogus. - Title-only unmatched = coverage gap, NOT fabrication. A resolver queried only by author/year/title fragment that returns unmatched is weak evidence: a real regional / non-English / pre-digital paper indexed nowhere produces exactly this, and is a legitimate coverage gap — the same case strict_articles_only was designed to leave advisory.

So lookup_verified == false is narrowed to require at least one ID-keyed unmatched (a DOI or arXiv ID that provably fails to resolve) with no matched. A citation whose only negative signals are title-only unmatched (no DOI/arXiv ID was present to key on, or the ID-keyed lookups were unreachable/skipped) resolves to unresolvable, not false — it carries the advisory triangulation suffix (visible coverage-gap signal) but is never a false. This aligns the existence gate's false with the strict_articles_only precision philosophy (fabrication requires a falsifiable identifier, not mere absence-from-index).

(4b) ready-for-review gate is a true opt-in, symmetric with contamination_triangulation. With false now meaning "provably-bogus identifier," the gate returns to the shipped opt-in symmetry: ready-for-review is blocked by lookup_verified == false only under citation_existence == strict, exactly as contamination_triangulation == strict gates its own terminal block. Under default advisory, a false row is a /ars-mark-read-ack-able advisory and the pipeline completes — no Zombie trap, no back-compat break (a key-absent v3.10 passport behaves identically to v3.9.x). The P0 intent (a draft cannot reach "ready for human review" status if any citation has lookup_verified: false) is honored for users who opt into strict, which — given the narrowed false — now means "I want provably-bogus DOIs/arXiv IDs to hard-block," a precise and safe default to opt into. The earlier "data invariant independent of policy" form (4-prev) is withdrawn: it false-blocked legitimate unindexed citations because it inherited the over-broad false.

Why this is not the Co-Scientist L1 red line. L1 forbids hidden ranking/culling: AI scores → AI selects top-K → user sees only survivors, with the filter criterion unauditable. Rule 12 is the opposite on every axis: the judgment is external-API factual (paper exists or not), not AI subjective scoring; the blocked/flagged citation stays visible and explicitly annotated; and resolver_outcomes makes the criterion fully auditable. Nothing is silently narrowed. The relevant boundary is L4 question 3 (who may ROUTE to the next stage): the finalizer — already the sole policy evaluator under the shipped model — performs both the marker stamping and the ready-for-review data-invariant check, adding no new control-plane writer. The user-confirmation invariant is preserved on two independent axes: the marker severity is the user's opt-in (citation_existence advisory/strict), and the ready-for-review certification is a deterministic, auditable data check — not an AI routing decision — that the user still confirms at the checkpoint.

See INVARIANT C-V6 (§5) for the binding form of this amendment. The §6 acceptance criteria B4 and B8 are re-pointed accordingly: B4's "default flip" reads as "the citation_existence key governs marker severity (absence = advisory) and gates ready-for-review only under strict (C-V6(d)), with false narrowed to ID-keyed unmatched (C-V6(a))"; B8's "Reviewer column separate from blocking" is satisfied, under strict, by the terminal block and the strict-only ready-for-review gate — not by an unconditional Rule 12.


1. What already ships (do not rebuild)

The following surface lands before v3.10 and is consumed by this spec without modification. Read the cited v3.8 spec for the contract details; this lite spec assumes familiarity.

Capability Existing path First shipped
Crossref DOI lookup + title fallback + backoff scripts/crossref_client.py v3.8
OpenAlex production client scripts/openalex_client.py v3.9.0
Parallel Semantic Scholar / OpenAlex / Crossref triangulation deep-research/agents/bibliography_agent.md (Triangulation Extension) v3.9.0
Three-layer source-text anchor markers <!--anchor:<kind>:<value>--> (in writer agent prompts) v3.7.3
Claim-faithfulness audit pipeline + 8-row finalizer matrix + 5 HIGH-WARN refusal classes scripts/claim_audit_pipeline.py, scripts/claim_audit_finalizer.py, academic-paper/agents/formatter_agent.md v3.8
First-party Crossref issued + pdftotext cover-page provenance shared/contracts/passport/citation_provenance.schema.json v3.9.4
Contamination signals (preprint / S2-unmatched / OpenAlex-unmatched / Crossref-unmatched) shared/contracts/passport/literature_corpus_entry.schema.json (contamination_signals object) v3.7.3 (S2), v3.9.0 (OpenAlex + Crossref)
ARS_CLAIM_AUDIT=1 opt-in flag academic-pipeline/SKILL.md (v3.8 mode flag rationale, §5 of v3.8 spec) v3.8 (default OFF)

182's original proposal asked for "a new module performs Crossref/DOI/arXiv resolution with caching." Everything except arXiv and caching is already shipped; the unified blocking gate is also already shipped at Stage 4→5 but gated behind ARS_CLAIM_AUDIT=1. v3.10 ships five delta items below.

2. Five v3.10 delta items

Delta 1 — arXiv API resolver

The current arXiv surface is a preprint-risk heuristic at scripts/contamination_signals.py plus venue-string matching in bibliography_agent. There is no API call to verify a cited arXiv ID resolves to a real preprint with matching metadata. v3.10 adds:

Out of scope for Delta 1: arXiv full-text resolution. The arXiv API verifies metadata and existence; it does not feed the v3.10 #183 epistemic-status extractor's Tier 2 pdftotext pathway. A user who wants Tier 2 verification on an arXiv preprint must still provide a local PDF via source_pointer. This separation keeps the citation gate (this spec) orthogonal to the epistemic-status gate (#183).

Delta 2 — Persistent verification cache

Crossref / OpenAlex / S2 clients today implement per-request backoff but no persistent caching across pipeline runs. The same paper cited in multiple drafts re-hits the API every time. v3.10 adds:

Cache integration touches the four existing resolver modules (crossref_client.py, openalex_client.py, the S2 lookup in bibliography_agent, the new arxiv_client.py) with a uniform wrapper: each resolver's public entry point grows a cache: VerificationCache | None = None parameter and, when present, consults the cache before making the network call. v3.8 calibration runner and audit pipeline tests pass a cache=None to preserve byte-equivalent behavior on test inputs.

Delta 3 — Promote ARS_CLAIM_AUDIT=1 from opt-in to blocking default

⚠ Superseded by §0 / C-V6 (v3.11 amendment). The "blocking default" gate model below is the v3.10 original design and is retained for rationale. The gate's on/off control is now the terminal_policies.citation_existence key (default advisory, opt-in strict), NOT an ARS_CLAIM_AUDIT default flip, and Rule 12 is the strict-only terminal block, NOT an unconditional REFUSE. Read §0 and C-V6 for the binding form; the calibration-evidence and load-bearing-safety reasoning in this Delta still applies to why the existence signal is detected unconditionally.

v3.8 ships ARS_CLAIM_AUDIT=1 as default OFF per v3.8 spec §5 mode flag rationale ("ramp-on plan deferred to post-calibration evidence"). v3.9.x runs accumulated the empirical evidence the v3.8 rationale required (calibration runner at scripts/claim_audit_calibration.py, T-C1/T-C2/T-C3 thresholds passing on the 20-tuple gold set per academic-pipeline/references/claim_audit_calibration_protocol.md). v3.10 promotes the gate.

The promotion is policy-only (no code-path change beyond the flag default flip and one branch deletion):

The promotion's load-bearing safety property: the formatter HIGH-WARN refusal classes (rules 6-10 per v3.8 spec) now block production output by default for the five classes the v3.8 audit already detected (HIGH-WARN-CLAIM-NOT-SUPPORTED, HIGH-WARN-NEGATIVE-CONSTRAINT-VIOLATION, HIGH-WARN-FABRICATED-REFERENCE, HIGH-WARN-CLAIM-AUDIT-ANCHORLESS, HIGH-WARN-CONSTRAINT-VIOLATION-UNCITED). Per #198 acceptance criterion, this delta is what makes the v3.10 verification layer the default critical path rather than an opt-in advisory pass.

Formatter REFUSE list extension (v3.10 rule 12): Delta 3 adds a new formatter refusal class to academic-paper/agents/formatter_agent.md Cite-Time Provenance Hard Gate, sibling to rules 6-10 (v3.8) and rule 11 (v3.10 silent-upgrade per #183 §6.1):

This refusal class is what satisfies acceptance criterion B8 below. The rule is cross-referenced into the #183 formatter refusal list extension table at §6.1 so the two specs share a single source of truth for the v3.10 refusal surface. The unresolvable outcome does NOT trigger Rule 12 per OQ-4's advisory-pass default; only false does.

Backwards-compatibility note: v3.8 / v3.9.x corpora that contain unreviewed audit findings stored in the passport pre-v3.10 are not retroactively re-audited. v3.10 applies only to new pipeline runs.

Delta 4 — Unified lookup_verified + anchor_present per-citation status surface

Today the citation-level verification signals are split across three schemas: anchor presence lives in v3.7.3 anchor markers (read at formatter_agent's Cite-Time Provenance Hard Gate), Crossref/OpenAlex/S2 unmatched signals live in contamination_signals, and claim-audit verdicts live in claim_audit_result. A consumer that wants to know "is this citation verified or not" must read three schemas and apply join logic. v3.10 adds a derived view:

This summary is the read interface for the §3.6 verification_gate contract (per #183 spec). The other two children of #198 reference this surface:

lookup_verified semantics intentionally omit a "partial match" or "conflict" value. v3.9.0's triangulation already encodes those nuances in contamination_signals.k_unmatched; the v3.10 summary collapses them to a binary "did at least one external index agree." A consumer that needs the fine-grained breakdown reads resolver_outcomes directly.

Delta 5 — verification_gate standalone API surface

198's acceptance criterion 4 requires "each child gate emits output in the shared shape so downstream consumers can read all three uniformly." Delivering this on the citation side requires extracting the gate logic from its current Stage 4→5 finalizer-step encoding into a callable API.

The API stability contract: function signatures and return shapes are frozen at v3.10.0 and only additively extended in patch releases. Removing a field from CitationVerificationOutcome requires a minor-version bump and a deprecation cycle.

3. Deliverable list (paths)

  1. scripts/arxiv_client.py (Delta 1)
  2. shared/contracts/passport/literature_corpus_entry.schema.json — contamination_signals.arxiv_unmatched field addition (Delta 1)
  3. scripts/contamination_signals.py — arxiv triangulation extension to k=0..4 advisory matrix (Delta 1)
  4. scripts/verification_cache.py (Delta 2)
  5. scripts/crossref_client.py, scripts/openalex_client.py, the Semantic Scholar lookup helper invoked from deep-research/agents/bibliography_agent.md (implementation lives in the bibliography pipeline; needs verification at implementation time for exact path/function name), and the new scripts/arxiv_client.py (Delta 1) — cache: VerificationCache | None = None parameter addition on each resolver entry point (Delta 2)
  6. commands/ars-cache-invalidate.md — new /ars-cache-invalidate <citation_key> slash command (Delta 2)
  7. (amended, C-V6) shared/contracts/passport/terminal_policies.schema.json — add citation_existence closed-enum {advisory, strict} key (default advisory via the existing per-key-absence evaluator convention). Plus academic-pipeline/SKILL.md — document the citation_existence policy (marker severity + the strict-only ready-for-review gate, symmetric with contamination_triangulation per C-V6(d)) and the narrowed false definition (ID-keyed unmatched only, C-V6(a)). (Supersedes the original Delta-3 ARS_CLAIM_AUDIT flag-flip deliverable.)
  8. (amended, C-V6) academic-pipeline/agents/pipeline_orchestrator_agent.md — finalizer (sole evaluator) does two recomputed-each-pass things (C-V6(h)): (i) under citation_existence == strict, co-emit TERMINAL-BLOCK ... policy=citation_existence reason=lookup_verified_false per the shipped grammar, additively with any contamination terminal token (C-V6(g)); (ii) gate ready-for-review on lookup_verified == false ONLY under citation_existence == strict (C-V6(d), symmetric with contamination). (Supersedes the original Delta-3 §3.6 dispatch-flip deliverable.)
  9. shared/contracts/passport/citation_verification_summary.schema.json (Delta 4)
  10. scripts/citation_verification_summary.py (Delta 4)
  11. scripts/verification_gate/__init__.py (Delta 5)
  12. scripts/verify_passport.py — standalone CLI entry point (Delta 5)
  13. Tests — scripts/test_arxiv_client.py, scripts/test_verification_cache.py, scripts/test_citation_verification_summary.py, scripts/test_verification_gate.py, and (amended, C-V6) scripts/test_citation_existence_policy.py covering: (a) narrowed false — an ID-keyed (DOI/arXiv) unmatched yields false; a title-only unmatched with no resolvable ID yields unresolvable, NOT false (the unindexed-real-paper case); (b) default-advisory does NOT block flow and renders the ack-able advisory; (c) opt-in-strict co-emits the terminal HIGH-BLOCK token; (d) ready-for-review is blocked by false ONLY under strict and completes under advisory (true opt-in, symmetric with contamination); (f) manual-exempt + unresolvable (incl. title-only) rows never block; (g) a ref hit by both contamination_triangulation=strict and citation_existence=strict carries two terminal tokens but dedupes to one affected ref; (h) flipping advisory→strict re-applies the gate on the next finalize and a resumed passport recomputes (no stale ready-for-review survives a false under strict). Renamed from the original test_default_blocking_gate.py. Mutation checks per repo discipline: a trivial that treats title-only unmatched as false MUST fail case (a); a trivial accept-all that ignores citation_existence MUST fail case (d).
  14. CI wiring: extend .github/workflows/spec-consistency.yml path filter to trigger on scripts/arxiv_client.py, scripts/verification_cache.py, scripts/verification_gate/**, the two new schemas, the terminal_policies.schema.json change, and the new test paths. The existing scripts/check_v3_10_policy.py lint (which already parses terminal_policies) gains citation_existence enum + grammar coverage.
  15. CHANGELOG.md + docs/ARCHITECTURE.md + ROADMAP §3.10 anchor — v3.10 release-alignment 6-invariant set per repo discipline.

4. Out of scope

5. INVARIANTS

I-V1 through I-V15 from 2026-05-21-v3.10-183-epistemic-status-spec.md §9 are not duplicated here. The following are #182-specific:

C-V1 — Triangulation cardinality. contamination_signals.k_unmatched reports the count of resolvers that returned unmatched among those that ran. With arXiv added, k ranges from 0 (all matched) to 4 (all four unmatched on a citation that has an arXiv ID); for citations without an arXiv ID, the upper bound is 3. The arxiv_unmatched field is optional in the schema and absent when the citation has no arXiv ID; lint asserts this absence-vs-false distinction.

C-V2 — Cache transparency. Cache hits and misses produce byte-equivalent outputs at the v3.10 API level. A consumer cannot distinguish a cache hit from a fresh API call by inspecting the returned CitationVerificationOutcome (other than via verification_timestamp, which carries the original verification time on cache hits).

C-V3 — Flag back-compat (scoped). The opt-out path (ARS_CLAIM_AUDIT=0) MUST suppress all v3.10-added passport aggregates introduced by this spec (citation_verification_summary[], verification_gate standalone API output, the new arxiv_unmatched contamination signal field). When suppressed, the passport surface on existing v3.8 / v3.9.x aggregates (claim_audit_results[], contamination_signals minus arxiv_unmatched, etc.) MUST be byte-equivalent to a v3.9.x run on the same input. The byte-equivalence guarantee is scoped to the existing aggregates, not to the passport as a whole; the v3.10-added aggregates are present-or-absent depending on the flag, not "byte-equivalent to v3.9.x absent state" (a v3.9.x run had no concept of these aggregates, so byte-equivalence is undefined).

C-V4 — verification_gate API stability. The verify_citation and verify_passport signatures are frozen at v3.10.0. Patch releases add optional keyword arguments only; minor releases may add new required fields with a deprecation cycle and migration script.

C-V5 — Anchor-present semantics. anchor_present == true is the strict complement of v3.7.3's anchor-none case. anchor_kind ∈ {quote, page, section, paragraph} maps to true; anchor_kind == "none" maps to false. No third value is permitted; if an unrecognized anchor_kind appears, the row is a v3.7.3 contract violation and the v3.8 audit pipeline rejects it before this summary runs.

C-V6 — Citation-existence gate inherits terminal_policies with a narrowed false definition (v3.11 amendment, supersedes Delta 3's blocking-default form). The lookup_verified == false gate is governed by the terminal_policies.citation_existence key (enum {advisory, strict}), NOT by an ARS_CLAIM_AUDIT default flip and NOT by an unconditional formatter REFUSE. Binding sub-clauses: - (a) false requires an ID-keyed unmatched (Delta 4 refinement, §0(4a)). lookup_verified == false requires at least one resolver queried by an exact identifier (DOI or arXiv ID) returning unmatched, with no matched. Title-only unmatched (no resolvable DOI/arXiv ID was available to key on) does NOT yield false; it yields unresolvable and carries the advisory triangulation suffix. This narrows the shipped Delta 4 line-112 definition, which classified any unmatched-without-matched as false and would false-block legitimate unindexed (regional / non-English / pre-digital) citations. Rationale: a falsifiable identifier that provably fails to resolve is fabrication evidence; absence-from-index without a falsifiable identifier is a coverage gap. Mirrors the strict_articles_only precision philosophy. - (b) Default advisory. Per-key absence ⟹ advisory (evaluator convention, not a JSON-Schema default); a whole-object-absent passport ⟹ advisory for this key (so a v3.10 passport behaves identically to v3.9.x — no back-compat break). Under advisory, lookup_verified == false is a visible, /ars-mark-read-ack-able advisory — never a block, and the pipeline completes normally. The ref marker stays byte-equivalent to v3.9.x (no citation_existence advisory suffix — the marker's single advisory slot is the contamination CONTAMINATED-* suffix; the v3.7.3 grammar caps the marker at one advisory token, so a second would break it). The advisory's visibility is carried NOT by the marker but by the always-populated citation_verification_summary[] aggregate and the formatter's mandatory provenance_summary.md Citation Existence Advisories section (see academic-paper/agents/formatter_agent.md), which lists every advisory false row (ref_slug, citation_key, the falsifiable DOI/arXiv ID, resolvers_unmatched) so the warning travels with the human-reviewed deliverable. This resolves the apparent tension between "byte-equivalent to v3.9.x" and visibility: the marker bytes do not change, and the visibility lives in the output package, not the marker. - (c) Opt-in strict is terminal and not ack-able. Under citation_existence == strict, the finalizer (the sole policy evaluator — no new control-plane writer is introduced, satisfying L4 question 3) appends TERMINAL-BLOCK severity=HIGH-BLOCK policy=citation_existence reason=lookup_verified_false mode=strict policy_hash=<h> to the ref marker. This is additive to the base-status token, mirroring the terminal-token mechanism of contamination_triangulation=strict — but UNLIKE contamination, citation_existence adds no advisory suffix to the marker's advisory slot (per C-V6(b) / (e), in either mode); the false "why" survives in the terminal token's reason=lookup_verified_false plus the citation_verification_summary[] aggregate (and, default-mode, the provenance_summary.md advisory section), NOT in a marker advisory suffix. The block is terminal: NOT /ars-mark-read-ack-able. There is NO per-hit override token; the human decision is the opt-in itself. - (d) ready-for-review is gated symmetrically with contamination — a true opt-in, NOT a policy-independent data invariant. Status promotion to ready-for-review is blocked by lookup_verified == false only under citation_existence == strict, exactly as contamination_triangulation == strict gates its own terminal block. Under default advisory the run completes (the false row is an ack-able advisory). This avoids the "Zombie pipeline" contradiction (an advisory-yet-unconditionally-blocking state) that the withdrawn policy-independent-invariant form created. The P0 intent (a draft cannot reach "ready for human review" status if any citation has lookup_verified: false) is honored for users who opt into strict; given the narrowed (a) false, opting in means "provably-bogus DOIs/arXiv IDs hard-block," a precise and safe choice. Both the prior "requires strict" form and the prior "data invariant independent of policy" form are withdrawn. - (e) Detection is unconditional; only terminality is policy-gated. The citation_verification_summary[] aggregate (Delta 4) is always populated so the signal is always visible regardless of citation_existence; the policy governs whether a false row promotes to a terminal marker (strict, the TERMINAL-BLOCK token) or stays advisory (default). Under default advisory the ref marker is byte-equivalent to v3.9.x — there is NO citation_existence marker suffix in either mode (the marker's single advisory slot is the contamination CONTAMINATED-* suffix, per C-V6(b)); the advisory's visibility is carried by the aggregate + the formatter's mandatory provenance_summary.md Citation Existence Advisories section, NOT by a marker annotation. C-V6(b) supersedes the original C-V3 ARS_CLAIM_AUDIT=0 suppression for the existence gate specifically — the existence signal is never suppressed, only de-escalated to advisory. - (f) Manual-entry and unindexed exemptions preserved. A manual entry whose applicable resolvers are all skipped yields unresolvable (not false); a legitimately-unindexed citation with only title-only unmatched yields unresolvable per (a). Neither triggers the strict terminal block, matching the shipped terminal_policies manual-entry exemption and the strict_articles_only coverage-gap carve-out. unresolvable follows OQ-4's advisory default regardless of citation_existence. - (g) Multiple terminal policies co-emit independently. A single ref that violates both contamination_triangulation == strict (k=3) AND citation_existence == strict (lookup_verified == false) carries two TERMINAL-BLOCK tokens in its marker, one per policy= value, alongside the shared advisory slot — the tokens are additive at the marker, but the ref is counted once in any "total affected refs" aggregate (dedupe by ref slug across policy buckets, mirroring the shipped contamination terminal_blocked[] non-additive rule). The formatter's generic "refuse on any unresolved HIGH-BLOCK" rule already handles N tokens without per-policy enumeration. - (h) Policy flip and resume re-evaluate; nothing is cached. Both the marker severity (c) and the ready-for-review strict gate (d) are recomputed by the finalizer (the sole evaluator) at each finalization pass — they are functions of the current terminal_policies state and the current citation_verification_summary[], not cached status. Flipping citation_existence advisory→strict between passes re-stamps markers and re-applies the strict gate on the next finalize; a resume_from_passport / reset that re-enters finalization re-evaluates against the resumed summary. A previously-granted ready-for-review is never inherited across a resume without re-passing the gate under the then-current policy — there is no path where a stale "ready" certification survives a citation that resolves to false under strict.

6. Acceptance criteria

B1 — arXiv client shipped. scripts/arxiv_client.py implements DOI-equivalent ID lookup, title fallback, exponential backoff, and resolve_arxiv_unmatched. Unit tests pass against fixture responses; integration test against the live arXiv API runs nightly (gated behind a network-available CI flag).

B2 — Triangulation extended to four indexes. contamination_signals.arxiv_unmatched field lands in literature_corpus_entry.schema.json. Signal computation lands in scripts/contamination_signals.py. The k=0..4 advisory matrix lands in academic-pipeline/agents/pipeline_orchestrator_agent.md §3.6 with four new annotation tokens (CONTAMINATED-ARXIV-UNMATCHED, CONTAMINATED-QUADRANGULATION-UNMATCHED, and their two PREPRINT-composed variants). Dispatch lands in scripts/claim_audit_finalizer.py. v3.9.0 formatter pass-through allowlist in academic-paper/agents/formatter_agent.md extends from 9 to 13 to cover the four new tokens.

B3 — Persistent cache shipped. scripts/verification_cache.py operational; SQLite WAL mode; default path ~/.cache/ars/verification.db; ARS_VERIFICATION_CACHE_PATH env-var override functional. Four resolver modules accept the cache parameter and consult/populate it correctly. scripts/test_verification_cache.py exercises hit / miss / expired / WAL-concurrent-read cases.

B4 — Citation-existence gate shipped (amended, C-V6 — supersedes the original "blocking-default promotion"). The terminal_policies.citation_existence key lands (default advisory; opt-in strict), with false narrowed to ID-keyed unmatched (C-V6(a)). A default-advisory run produces the existence annotation as an ack-able advisory and preserves v3.9.x non-blocking behavior; a strict run co-emits the TERMINAL-BLOCK token and gates ready-for-review, symmetric with contamination_triangulation (C-V6(d)). Existing v3.8 audit detection still runs unconditionally (C-V6(e)). A title-only unmatched real-but-unindexed citation resolves to unresolvable and never blocks (C-V6(a)/(f)). (Original B4 text, for rationale: "ARS_CLAIM_AUDIT=1 default flip lands … ARS_CLAIM_AUDIT=0 opt-out preserves v3.9.x behavior byte-for-byte (C-V3)." — the ARS_CLAIM_AUDIT flag is no longer the gate control; C-V6 governs.)

B5 — Citation verification summary shipped. citation_verification_summary.schema.json and citation_verification_summary.py land. The summary aggregate appears in passport runs after v3.10 audit pipeline completes. Downstream readers (#183 epistemic-status gate, #184 eval harness) can consume the summary by reading the schema.

B6 — verification_gate API shipped. scripts/verification_gate/__init__.py exposes verify_citation and verify_passport. scripts/verify_passport.py callable as python -m scripts.verify_passport <passport>. API stability contract documented in module docstring.

B7 — Six-doc release alignment. At tag time, scripts/check_version_consistency.py reports zero drift across CHANGELOG, README, ARCHITECTURE, PERFORMANCE, bilingual mirror docs, and package metadata per repo release discipline.

B8 — Shared-contract claim verified. The §3.6 verification_gate contract per #183 spec is satisfied for the citation gate row: - Source of truth = Crossref / DOI / arXiv (external API state); satisfied by verification_gate.verify_citation invoking the four resolvers (Crossref + OpenAlex + S2 + arXiv). - Verification target = citation exists and anchor resolves; satisfied by the lookup_verified (citation exists) + anchor_present (anchor resolves) outputs. - Output column = lookup_verified + anchor_present per citation, written to the new citation_verification_summary[] aggregate; satisfied by citation_verification_summary.py per Delta 4. - Reviewer column = separate from reviewer_approved (blocking); satisfied by Rule 12 as amended by C-V6: under citation_existence == strict an ID-keyed lookup_verified == false row (C-V6(a)) is a deterministic terminal block (TERMINAL-BLOCK ... policy=citation_existence) independent of any reviewer agent verdict, and the same strict opt-in gates ready-for-review (C-V6(d), symmetric with contamination). The block is governed by the opt-in policy, not by an unconditional REFUSE. Rule 12 is cross-referenced from #183 §6.1 refusal list extension table.

The CitationVerificationOutcome Python return type carries additional implementation-detail fields (resolver_outcomes, verification_timestamp) beyond what the §3.6 contract enumerates; those are API ergonomics, not contract surface.

B9 — Calibration baseline preserved. v3.10 #182 deltas do not modify scripts/claim_audit_calibration.py or the 20-tuple gold set at scripts/fixtures/claim_audit_calibration/gold_set.json. T-C1 / T-C2 / T-C3 thresholds remain green; #184's eval harness extension is the separate workstream for new gold-set tracks.

7. Open questions deferred to implementation

OQ-1 — Cache TTL value. The 90-day cache TTL is a guess. Implementation may tune based on observed Crossref / OpenAlex / S2 / arXiv metadata churn rates. Tuning is a runtime parameter (env-var configurable), not a schema change.

OQ-2 — arXiv ID format strictness. arXiv supports both old-style IDs (hep-th/9711200) and new-style IDs (2605.07723). Client must accept both; whether to canonicalize old-style to new-style for cache key purposes is deferred.

OQ-3 — Multi-user cache concurrency. Single-process SQLite WAL is adequate for v3.10 production use. If multi-user shared cache demand appears, a follow-up issue scopes the change (likely an HTTP-backed cache service, not a shared file).

OQ-4 — lookup_verified == unresolvable ramp-up policy. When every applicable resolver is unreachable (total outage), or when every applicable resolver is skipped (manual entry exempt from all four), lookup_verified is unresolvable per Delta 4. Whether this state should block ship (gate-refuse) or pass (advisory) is deferred to operational observation. v3.10 default is advisory (pass), matching v3.9.x triangulation's advisory tier. Note that the partial-outage case (some unmatched + some unreachable) is classified false per the Delta 4 anti-fabrication bias only when at least one of those unmatched is ID-keyed (C-V6(a)); a partial outage whose only unmatched are title-only resolves to unresolvable, not false. (v3.11 amendment, C-V6(a)/(f)): the narrowed false definition means the advisory-pass default never extends to citations with a provably-bogus DOI/arXiv ID, but DOES cover legitimately-unindexed (title-only-unmatched) citations, which are unresolvable. unresolvable follows this advisory default regardless of citation_existence — only ID-keyed lookup_verified == false is promoted to terminal under citation_existence == strict. So the unresolvable ramp-up remains genuinely open here; the existence gate's amendment does not resolve it either way.

OQ-5 — Acknowledged recall limit of the narrowed false (v3.11 amendment). The C-V6(a) narrowing (only ID-keyed unmatched yields false) is a deliberate precision-over-recall choice, and it has a named blind spot: a fabricated citation that carries NO DOI/arXiv ID and only a bogus title resolves to unresolvable, not false, and so is NOT terminal even under citation_existence == strict. This is the same recall limit strict_articles_only accepts by design — absence-from-index without a falsifiable identifier is treated as a coverage gap, because in the target humanities / non-English / regional-journal corpus that pattern is overwhelmingly legitimate, not fabrication. The independent design review (2026-06-02) judged this an acceptable, well-rationalized tradeoff rather than a logical hole. It is recorded here as an Acknowledged Limitation, not silently accepted: a no-identifier fabrication is caught by the existing v3.8 claim-faithfulness audit (anchorless / claim-not-supported routing) and by human review, not by the existence gate. A future fuzzy-title-fabrication signal (e.g. title-embedding distance against the matched corpus) could narrow this gap; it is out of scope for v3.11 and would be specced separately if the operational false-negative rate warrants it. Per repo discipline, the implementation MUST surface this limit in user-facing docs and carry a by-design false-negative fixture (no-DOI + bogus-title + strict → stays unresolvable, does not block), mirroring the strict_articles_only coverage-gap fixture.

8. References