# Changelog All notable changes to this project will be documented in this file. ## [Unreleased] ### Added - **`claude plugin eval` suite for the `academic-paper` revision-coach flow (`plugin-evals/`; toward #851, not closed).** Seven cases (five that should fire, two that should not) with twenty graders, run as a with/without-plugin ablation so the headline is the uplift Δ, not a pass rate. The author-defined quality axis is no unauthorised rewriting: the response must not draft manuscript prose, change anything no reviewer asked for, or assert results or changes that have not happened; secondary axes are no comment dropped, push-back allowed on a factually wrong reviewer point, and a committee letter yields a tracker with no peer-review grading. Inputs are synthetic and cover four shapes (mixed-format journal comments, an unnumbered decision-letter email, OpenReview-style scores with a "should we push back" question, and a formal ethics-committee letter), in zh-TW and English. Calibration against the 2026-09-12 pilots is recorded in `plugin-evals/README.md` together with the run command, the `timeout_seconds` / `max_turns` ceilings, the observed pilot cost, and three caveats — including that the ICLR case did not invoke the skill in either pilot, which is filed as #851 and uses that case's display-only `skill-fired` grader as its acceptance check. `plugin-evals/results/` is gitignored. This adds a regression guard only; it claims no measured uplift figure (a single-run pilot is not a measurement). - **es-ES (Spanish, Spain) README and trigger phrases (#855, #856; toward #850, not closed).** `README.es-ES.md` is a full translation of `README.md` at v3.21.2 by a native speaker, structure kept byte-comparable with the English file (same heading, table, and fence order; code blocks, identifiers, and links untouched), contributed by @didacrios. The five existing READMEs gain the `[Español](README.es-ES.md)` nav link. Drift is policed the same way as the Korean README: `check_readme_es_sections()` in `scripts/check_spec_consistency.py` pins the version badge, release-note headings, mode headings, and forbidden retired phrases, and `scripts/check_reviewer_role_label.py` gains the es-ES REQUIRED entry. Translation only; no skill, mode, or trigger changes. The companion trigger change (#856, merged as a366e39, also by @didacrios) follows the #509 Korean precedent: each of the four `SKILL.md` files gains one body `**Español**:` line of intent-specific compounds (no broad standalone words) and a conservative Spanish subset in its frontmatter `description`, which is the surface the router reads; `revisar artículo` stays exclusive to the reviewer and `enmendar mi artículo` marks revision intent, pinned by two es-ES boundary fixtures under `tests/fixtures/issue_133_routing/` (11 and 12). `academic-pipeline/SKILL.md` is content-locked, so its `CONTENT_LOCKS` hash in `scripts/check_pipeline_boundary_semantics.py` is re-pinned in the same change. All four descriptions stay under the Agent Skills 1,024-character ceiling counted in code points of the parsed value (deep-research 1,021, academic-paper 867, academic-paper-reviewer 878, academic-pipeline 783); the lint that enforces that ceiling is filed as #864. Evidence: the `05-apa-es-locale` citation-check eval case fires the skill 2 of 2 on the branch against 0 of 2 on the previous `main`; a five-phrase Claude Code routing smoke (sonnet, two runs each) fired 2/2 on the lit-review, full-pipeline, and Socratic phrases, 1/2 on `revisa este artículo` (0/2 on the previous `main`), and 0/2 on `redacta un artículo` (also 0/2 on the previous `main`; the model writes the article in one turn without calling any skill, the bypass tracked in #851 and #857). Two small follow-ups (three broad terms left in the deep-research body line; fixture 11 asking for reviewer comments it does not attach) are #865. Activation layer only; no mode, IRON RULE, schema, or workflow text changes. - **`claude plugin eval` suite for the `academic-paper` citation-check flow (`plugin-evals-citation-check/`; surfaces #857 and #858, neither closed).** Eight cases (six that should fire, two that should not), run as a with/without-plugin ablation so the headline is the uplift Δ, not a pass rate. The author-defined quality axis is the four citation failures that matter: a reference the user has no source for, the right paper with the wrong authors, a hedged or minor finding cited as an established result, and a retracted or concern-flagged paper cited as live evidence. Every fire case ships a complete synthetic source pack so all four are detectable offline; the sandbox has no network, so "no source" is graded as flagging the entry as unverifiable, never as a real lookup. Secondary axes: mechanical errors, no false positives on clean entries, no claim of online verification, no rewriting of the manuscript. Cases cover APA 7 (en, zh-TW mixed, es), IEEE, Vancouver with the style unnamed, and Chicago notes-bibliography; the negatives are an APA-to-IEEE conversion request and a Python unused-import question. Cases pin `model: sonnet` and the run command sets `--judge-model opus` so the judge is never the agent model. Calibration against the 2026-09-13 pilots is recorded in `plugin-evals-citation-check/README.md` with the run command, the `timeout_seconds` / `max_turns` ceilings, the observed pilot cost, and the caveats: the with-plugin arm cannot load the mode's own prompt in the eval sandbox because the command stub references plugin files by relative path (#857), so Δ ≈ 0 is the honest current reading; plain-language prompts fired the skill in 3 of 6 cases (#858, with the Spanish case as one data point for #850). `plugin-evals-citation-check/results/` is gitignored. This adds a regression guard only; it claims no measured uplift figure. - **Reviewer-calibration harness with isolated dispatch and audited scoring (#835; toward #653 and #828, neither closed).** `evals/heldout/reviewer_calibration/` gains the held-out calibration suite and five scripts: `fetch_calibration_corpus.py` / `assemble_calibration_corpus.py` freeze a hash-verified manuscript corpus, `dispatch_calibration_panel.py` and `dispatch_e4_panel.py` run isolated reviewer-panel cards through the shared Claude CLI transport, `build_calibration_measurement_row.py` emits contract-validated measurement rows, and `score_calibration_run.py` scores categorical panel decisions (no AUC: the panel produces decisions, not probabilities). Every dispatch retains write-once outputs, raw transport streams, timestamps, and prompt/output hashes; the transport isolates operator configuration (empty `CLAUDE_CONFIG_DIR`, environment allowlist) and reconstructs every assistant message from `stream-json`, including replacement and retraction events, so a synthesis whose Decision line sits in an earlier message is not lost. A zero-cost credential preflight refuses deterministic authentication retries and preserves aborted-run evidence; the optional `--require-preflight-ok` gate stops before transport construction when a skipped or inconclusive preflight would violate the run's acceptance criteria, and TLS trust failures get a safe diagnostic. Complete assistant frames are preserved ahead of a truncated stream tail; interrupted cards and panel stages are recorded as blocked with their call ledger, which counts CLI dispatch attempts, not provider requests. The retained ICLR 2026 corpus is marked SUPERSEDED and usable only for harness rehearsals (#828: its page-1 layout reveals labels), and corpus freeze and verification reject nonconstant page-1 layout signals. No calibration profile and no rehearsal measurement values ship with this change; the remaining ICLR 2027 corpus capture and the Phase 3.5 two-family judge work are documented as open. ### Fixed - **`academic-paper` revision-coach now advertises its rebuttal-shaped triggers where the model can see them (#851).** The `revision-coach` trigger phrases "I got reviewer comments", "revision roadmap", "should we push back", "conference rebuttal", and "grant panel response" were listed only in the SKILL.md body (the mode table and the Trigger Keywords section), which the model reads *after* it has decided to load the skill; the frontmatter `description` carried "parse reviews" and the rebuttal-audit phrase "audit my rebuttal" but none of the revision-coach ones. In the #852 eval pilots the with-plugin arm answered an English ICLR "should we push back" prompt without invoking the skill in 2 of 2 runs. The five English phrases, the zh-TW 「我收到審查意見」「修訂路線圖」, and the Korean 「심사 의견을 받았어」 are added to the description (699 characters, under the 1,024-character Claude Code allowance; the claude.ai 200-character cap was already exceeded by design, see `docs/SETUP.md` Method 4a), and the three English phrases missing from the body Trigger Keywords line are added there for parity. "audit my rebuttal" stays distinct so Routing Rule 6 still separates rebuttal-audit from revision-coach by input shape. Verification: `plugin-evals/03-iclr-rebuttal-en` `skill-fired` went from 0 of 2 to 7 of 7 with-plugin runs. That case's two llm rubrics (`no-fabrication`, `pushback-per-reviewer`) are rewritten in the same change into the enumerate-then-quote style the #852 calibration settled on, and `no-fabrication` now states that facts the author supplied and results already in the submitted paper are not fabrication; with the earlier claim-list phrasing the runner's judge returned 3-vote FAILs on skill-fired outputs that a reasoning judge passed. Not addressed here: the wider body-vs-description gap (19 English body phrases for this skill, 5 for `deep-research`, mostly covered by mode names) and a 1-of-7 misroute of ICLR reviews into the #668 committee branch observed during verification, both noted on #851. - **`/ars-mark-read` no longer fails on Windows: the ledger lock has an `msvcrt` backend (#843, PR #844 by @dajiaohuang).** `scripts/ars_mark_read.py` imported the POSIX-only `fcntl` module at load time, so on Windows the documented CLI raised `ModuleNotFoundError` before argument parsing and every test in `tests/test_mark_read_args.py` failed. The module now imports `fcntl` where available and falls back to `msvcrt`; two small helpers (`_lock_nonblocking`, `_unlock`) select `fcntl.flock(LOCK_EX | LOCK_NB)` on POSIX and `msvcrt.locking(LK_NBLCK, 1)` on Windows, inside the unchanged bounded retry loop (Windows contention raises `EACCES`, which the loop already retries). Review dropped a proposed empty-file NUL pre-write because `msvcrt.locking` can lock a byte beyond EOF and the write sat outside the retry loop. The POSIX path is byte-for-byte the same lock sequence. This fixes one entry point only: the remaining `fcntl` imports (`adjudication_activity`, `inquiry_branch_ledger`, `review_criteria_binding`, and their tests) are tracked in #845, and there is no Windows CI job, so Windows behaviour rests on the contributor's reported 4-passed focused run. - **One shared file-lock helper replaces the six per-file `fcntl` sites; Windows gets a documented `msvcrt` backend (#845).** `scripts/adjudication_activity.py` and `scripts/review_criteria_binding.py` still imported POSIX-only `fcntl` at load time (so their CLIs and test modules failed on Windows before parsing arguments), `scripts/inquiry_branch_ledger.py` carried its own try/except, and `scripts/ars_mark_read.py` carried the #844 backend split inline. New `scripts/file_lock.py` owns the backend choice (`BACKEND`): `acquire(fd, exclusive=, timeout=)` / `release(fd)` over `fcntl.flock` on POSIX and `msvcrt.locking` on byte 0 on Windows, never writing the lock file, with contention on either backend surfacing as one `LockTimeout` (a `BlockingIOError` carrying `EAGAIN`). The POSIX lock sequences are unchanged; the one POSIX-visible difference is textual: the adjudication store's contention message now embeds `LockTimeout` where it embedded `BlockingIOError` (the error code `ERROR:LOCK` and exit status are the same, and nothing parses the class name). A signal that interrupts a lock attempt is retried but never past the deadline. The two semantic gaps are decided per site rather than hidden: the adjudication store's shared read lock degrades to an exclusive lock with a 5-second bounded wait where shared locks are unavailable (writers keep the non-waiting exclusive lock); the review-criteria manifest lock still blocks indefinitely on POSIX and is capped at `WINDOWS_BLOCKING_WAIT_SECONDS` (30 s) on Windows, surfacing as `BindingError`; the inquiry branch ledger alpha keeps refusing non-POSIX hosts (it now checks `file_lock.BACKEND`) because its durable writes have no Windows verification. Two release paths that unlocked an unacquired lock in `finally` (a no-op under `flock`, an `EACCES` under `msvcrt` that would have masked the real error) now acquire in their own `try` block and release only after a successful acquire, so a `LockTimeout` raised inside the guarded body is also never reported as the outer lock failing. `scripts/test_file_lock.py` (CI manifest id `845-shared-file-lock`) covers both backends: the real `fcntl` backend for contention, bounded and blocking waits, shared/exclusive interplay, and `tests/fake_msvcrt.py` (one model of the documented `_locking` contract) for the Windows branch, plus a subprocess test that imports all four consumers with `fcntl` blocked and exercises each site's Windows decision. `docs/SETUP.md` / `SETUP.zh-TW.md` state the platform posture. No Windows CI job is added; real Windows verification remains a manual step requested from the #843 reporter. - **Socratic non-convergence path F6 no longer ranks or preselects a direction, and the two reference files no longer carry their own auto-end round count (#834).** `deep-research/references/failure_paths.md` § F6 offered "[the most promising direction]", told the mentor to "identify the 1-2 directions with the most convergence potential", and prescribed "restrict discussion scope", contradicting the #735 non-ranking boundary that `POSITIONING.md` and the mentor agent carry (the directions are the user's own, so this was ranking and preselection, not generation). F6 and `socratic_mode_protocol.md` § Dialogue Management Rules also still said "round 15 → end" after #490 made the mentor agent's § Auto-End Conditions (Precise) the single authority (40 goal-oriented / 60 exploratory). F6 now lists the directions the user has expressed in the order they were expressed, leaves the choice to the user, and names the visible exit marker on the `full`-mode option; both reference files point at the agent file for round caps and state none of their own. `scripts/test_socratic_rq_non_generation_contract.py` gains ranking/preselection-vocabulary and own-round-count checks (plus a pointer/heading parity check) that fail on the pre-fix bytes. This closes a contract contradiction between prompt surfaces found during a cross-model fact-check of the v3.21.2 claim surfaces; it claims no breadth or diversity improvement (that remains #659) and no measured behavior change. - **OpenAI request builders no longer send parameters GPT-6 Astra rejects; Astra's API effort set is validated before any request (#823).** The executable smoke entrypoint (`scripts/cross_model_smoke_test.sh`) and the canonical OpenAI example in `shared/cross_model_verification.md` sent `temperature: 0.1` to `/v1/responses`; the official Astra migration guide lists `temperature`, `top_p`, and `top_logprobs` as unsupported, so a caller following the v3.21.2 recommendation built an API-incompatible request before the grounding checks could run. Both builders drop the sampling parameter (Gemini and compatible-provider examples keep theirs). Astra's documented API effort vocabulary (`low|medium|high|xhigh|max`) replaces the "not confirmed" wording and lives in one canonical per-model table, `scripts/cross_model_verification/openai_effort_guard.sh`, sourced by both builders; an explicitly configured Astra value outside that set now fails with `CROSS-MODEL-ERROR: invalid_astra_reasoning_effort` before `curl` runs, an unset effort still omits the field so the provider default applies, and ids without a table row stay pass-through. A hermetic test in `scripts/test_cross_model_verification_guards.py` executes both shipped builders (the smoke script and the documented Bash example located by content) against a fake `curl` and asserts the emitted JSON and that both source the guard. Astra stays provisional: request compatibility is not a bakeoff result. - **The contained Codex citation transport rejects `effort=ultra` instead of forwarding it (#824).** v3.21.2 added `ultra` to `ACCEPTED_REASONING_EFFORTS` in `scripts/cross_model_codex_transport.py` as an additional reasoning amount. The codex-cli 0.153.4 app-server schema (`v2/TurnStartParams.json`) defines `effort: "ultra"` as the replacement for the deprecated `multiAgentMode` — "proactive multi-agent behavior" — which is outside the transport's single-reference, no-other-agents contract. `ultra` is removed from the set, and a new `validate_reasoning_effort` step rejects it with `REASONING_EFFORT_REQUIRES_DELEGATION` before transport detection, auth access, temporary-state creation, or app-server launch, on both the wrapper-facing `verify_once` path and the direct `run_app_server` path; `max` forwarding and the provider-default (unset) path are pinned unchanged. The check is model-independent by design: this transport speaks the app-server effort enum, and per-model API vocabularies stay with the provider. This is a scope restriction, not a finding that `ultra` bypasses the disabled features, and it does not apply to a general Codex research session. No code-mode, local-tool, or network permission is widened. - **Independent audit launcher and claim-audit provenance no longer carry an implicit GPT-5.5 identity (#826).** `scripts/run_codex_audit.sh` launched every new audit with a hardcoded `codex exec -m gpt-5.5` and its sidecar recorded no model at all, which is why the stale pin could mislabel runs silently. It now pins `gpt-6-astra` at `xhigh` for new runs (an explicit override of the caller's project default, so a fresh-context audit never inherits a moving alias; historical records keep their actual identities) and records both in a new optional sidecar `model` block (`shared/contracts/audit/audit_sidecar.schema.json`: `requested` + `reasoning_effort`, a request, not a provider attestation); the mock `codex` in `scripts/test_run_codex_audit_e2e.py` asserts the exact model, effort, and fresh-run argv and that the sidecar matches. `scripts/claim_audit_pipeline.py` defaulted `judge_model` to `gpt-5.5-xhigh` and stamped that label into every result row and cache key whether or not that model ran; the caller must now supply the actual judge identity (effort included where it changes judgment), a missing / null / blank / `unknown` identity is recorded as `unknown` and binds the cache key to the run (the same fail-closed shape as an unknown `judge_prompt_version`: no cross-run reuse, within-run dedup kept), and a non-string identity is rejected before retrieval. Tests pin that a changed identity partitions the cache and that an unknown identity can never hit a prior run's verdict. `academic-pipeline/agents/claim_ref_alignment_audit_agent.md` and `shared/templates/codex_audit_multifile_template.md` state the same contract; frozen benchmark defaults and historical measurements are untouched. ### Changed - **Locale packs are community-maintained, and a single-owner application can be recorded as provisional (#861, #862).** `CONTRIBUTING.md` gains a "Locale packs (community-maintained)" section written ahead of the #850 mechanism: ARS ships one default locale (English plus Traditional Chinese); every other output locale is a locale pack, in-tree under `locales//` or as a sibling distribution, owned by its contributors. A pack stays listed as supported only with two named owners, recorded currency against each minor release (14-day window), visible staleness in CI that never delays a core release, configuration-and-presentation-only content (never a core `SKILL.md`, agent, IRON RULE, schema, mode, or oversight rule), and #509 trigger discipline. The maintainer owns the extension interface and the default behaviour and does not translate, review, or support pack content. The follow-up amendment adds a provisional route for the first applicant's situation (one owner, no backup yet): a single-owner application is recorded in a dedicated issue, is not a supported pack, and gets a 14-day backup window that opens with the first minor release after both the locale mechanism and the primary owner's recorded acceptance; an unfilled window lapses the application, and a supported pack that loses either owner leaves the supported list. The interim pointer for activation-layer contributions now names #862 (Phase 1) alongside #850. Policy text only; no code, schema, or skill behaviour changes. - **Gartenberg et al. (2026) joins the human-in-the-loop anchors as the first journal-side evidence, and volume is recorded as a non-goal (#833).** `README.md` and `README.zh-TW.md` gain a fourth motivation paragraph (the *Organization Science* AI Task Force editorial "More versus better", 37(3):795-812; one journal, observational, aggregate; cited as design rationale, not as evidence about ARS). `POSITIONING.md` "Rejected mechanisms" records the volume non-goal: no batch manuscript generation, no fan-out of one run into several submissions, time-to-draft booked as a resource cost. `shared/collaboration_depth_rubric.md` 1.0 → 1.0.1 adds a related-construct citation to the Cognitive Vigilance dimension (uncritical acceptance of AI output; "cognitive surrender" as the editorial cites Shaw & Nave 2026) without changing dimensions, scoring, or the descriptive-only reporting rule. No new effectiveness claim is made, and no number from the editorial is presented as being about ARS. - **Writer and compiler prompts: unsupported factual claims cannot be rescued by hedging, and generic prose quotas become diagnostics (#825).** The citation-density recovery tree in `academic-paper/agents/draft_writer_agent.md` told the writer to rewrite a claim with no usable source "using hedging language" (its CER-chain fallback row said the same, so did the scored writer contract `shared/contracts/writer/full.json` D2, and rule 5 of the M3 temporal iron rule in the writer and both `report_compiler_agent.md` mirrors allowed a bare hedge when the verifying dates were absent); hedging calibrates uncertainty but cannot supply evidence, so an unsupported premise could pass as recovered. All four sites now route missing factual support to a supporting source or attribution, omission, or an explicit `[MATERIAL GAP]` for author review, and an inference or hypothesis must rest on supported premises and be distinguished from an observed finding. The universal prose quotas in the writer prompt, both `report_compiler_agent.md` mirrors, `academic-paper/references/writing_quality_check.md`, the `academic-paper/SKILL.md` anti-pattern rows, and writer contract D6 (80% TEEL as a scored dimension) are rewritten as context-sensitive diagnostics subordinate to author, venue, and discipline requirements — prompts for judgment, never rewrite gates or a pass/fail score (the exact rules are enumerated in the audit correction). Venue word limits, quote/anchor grammar, protected hedges, and revision authority are preserved. Every live consumer of the reference was checked (`academic-paper/SKILL.md`, `deep-research/SKILL.md`, both compiler mirrors, `writing_judgment_framework.md`, `academic_writing_style.md`); versioned records (README version-history entries, the skills' own changelogs) keep their original wording. `audits/harness-retirement-2026-09-model-update.md` gains an in-place post-release correction naming the exact files and rules the September scan missed (and the #823 / #824 / #826 items). A synthetic held-out scenario set for the unsupported / contradicted-claim recovery path is added under `evals/heldout/unsupported_claim_recovery/` with status `NOT_RUN`; no measured quality improvement is claimed. ## [3.21.2] - 2026-09-06 — Model currency for Claude Fable 5.1 and GPT-6 Astra, checkpoint decision provenance, and CJK title-matching repairs ### Added - **GPT-6 Astra listed as a provisional cross-model verifier; the OpenAI recommendation moves to the current generation (2026-09 model update).** `gpt-6-astra` (released 2026-09-03) joins the canonical model table in `shared/cross_model_verification.md` as **provisional on both transports** — no bakeoff run exists; the only evidence is an entry-gate smoke on the ChatGPT-subscription citation transport (`scripts/cross_model_smoke_test_codex.sh`, 2026-09-05, codex-cli 0.153.4: `VERIFIED` with one bound source on the Vaswani et al. fixture), which is the precondition for a Promotion Bakeoff, not one. The recommendation moves to `gpt-6-astra` under the existing #783 policy (recommendation follows generation currency; `validated` is earned only by the sealed bakeoff), so the move carries no measurement claim. `gpt-5.6-sol` keeps its validated status on the citation transport and its provisional status on the API route; `gpt-5.5` / `gpt-5.5-pro` / `gemini-3.1-pro-preview` are unchanged. The id-status allowlist, the quick-setup and codex blocks in `docs/SETUP.md` / `docs/SETUP.zh-TW.md` (same example set in both, parity-linted), `.claude/CLAUDE.md`, and the bakeoff section (now naming the per-transport baseline: `gpt-5.5` on the API route, `gpt-5.6-sol` on the citation transport) move together. Two vendor-reported facts are recorded where they bite: high verbalized evaluation awareness (system card §8.6 / §8.8.1) as a caveat on any bakeoff or calibration result, and GPT-6 Astra's unrecorded list pricing in the cost table. The contained Codex citation transport's reasoning-effort vocabulary gains `ultra` (system card §10.1.2.5: the Codex harness ran at Ultra effort) as a named constant with a test pinning turn/start forwarding and fail-closed rejection of unknown values; the app-server schema on 0.153.4 types `ReasoningEffort` as any non-empty string, so this set is ARS's own guard and the provider still rejects what the served model does not advertise. - **Checkpoint decision provenance: state-machine authority, orchestrator mirror, risk register R11.** New `### Checkpoint decision provenance` authority section under the Stage 6 boundary semantics in `academic-pipeline/references/pipeline_state_machine.md`, mirrored operationally by a `## Checkpoint authority fidelity` section in `academic-pipeline/agents/pipeline_orchestrator_agent.md`: only a user turn is a checkpoint decision (never a subagent report, hook or tool result, template default, or the orchestrator's own paraphrase); decisions, consent grants, overrides, and authorizations are re-transmitted to subagents verbatim and labelled as the user's, never widened; consent or approval the user did not give is never asserted; completion and Process Record surfaces report what the user actually decided. Motivation is vendor-documented, not ARS-measured: the Claude Fable 5.1 system card records a fabricated user quotation written to satisfy an approval gate, distorted user intent in subagent instructions, and approval represented that was never given (§6.2.1 / §6.6.1), plus a slightly higher willingness to bypass approval gates (§6.4.5); the GPT-6 Astra system card records proceeding on automated messages after asking for permission (§8.8). The rule is prompt-level and says so; the deterministic authorization inputs (#670, `/ars-mark-read` scope) remain the enforced layer where they exist. `docs/RISK_REGISTER.md` gains R11 indexing the rule, its controls, and the residual gap. Both files are whole-file content-locked pipeline surfaces, so both hash constants in `scripts/check_pipeline_boundary_semantics.py` are updated in the same commit. The orchestrator section is scoped out of the historical v3.6.7 orchestrator line budget with its own bounded cap (`scripts/test_v3_6_7_phase_6_6.py`, 13 lines measured, budget 18), the convention every prior independent extension follows. - **Provider-side monitoring and safety interventions named as a transport-failure case.** New `### Provider-side monitoring and safety interventions (2026-09)` subsection under Graceful Degradation in `shared/cross_model_verification.md`, grounded in the GPT-6 Astra system card: the provider's misalignment monitor can pause or end a Codex / Responses API conversation and stopped API conversations cannot be resumed (§10.2.3.1); misuse monitors and activation classifiers can block a generation mid-stream (§10.2.3.2); a stricter cyber boundary applies to higher-risk accounts (§10.2.2.2); flagged accounts can be escalated to manual review (§10.2.5). Contract: an intervention is never a verdict — on the API route it surfaces either as an HTTP error (the existing `CROSS-MODEL-ERROR: openai_http_` transport-failure marker) or as a completed response with no grounding evidence, which the existing `NOT_SEARCHED` guard already catches; on the contained codex adapter it is the adapter's nonzero exit or fail-closed receipt; none of these is ever a citation judgment, a reviewer finding, or a checkpoint decision; ARS calls are stateless one-per-item, so nothing is lost and the item is re-run; a manuscript is never rephrased to route around a provider's boundary, while ARS's own prompt wording prefers process vocabulary over attack vocabulary; consent must assume provider staff may read escalated content (recorded as R4's residual gap); and ARS never consumes the verifier's reasoning narrative, a design rationale the card's monitorability findings (§9) now support explicitly. The `cross_model_unavailable` row of `shared/contracts/degradation_registry.json` is worded vendor-neutrally (an API error or an adapter failure) and anchors the new subsection. - **Harness-retirement audit for the Fable 5 → Fable 5.1 and GPT-5.6 Sol → GPT-6 Astra change** (`audits/harness-retirement-2026-09-model-update.md`). Both vendor system cards read in full and each behavioral finding mapped to the ARS mechanism that assumes it. Result: 0 prompt-text retirements — both cards report the failure classes ARS's remaining scaffolds guard against (stated guesses as facts, exaggerated completeness, unhedged estimates, framing extension, repeated failing actions, suppressed caveats, permissive reading of instructions) as still present, so 8 keep-as-debt items now carry a system-card citation; 4 applied currency fixes (MU-001 – MU-004); 2 deferred items (legacy `gpt-5.4*` ids pending a first-party deprecation check; a possible authorship-cue rule for reviewer inputs after Fable 5.1 §6.5.3's self-recognition bias); and the four guardrail additions above. The eval-harness model default in `scripts/dispatch_e4_panel.py` is annotated as measurement identity, not prompt debt. - **Skill-inventory parity lint (#809).** New `scripts/check_skill_inventory_parity.py` takes the top-level `/SKILL.md` directories as the authority and requires set-equality against the three surfaces that package or advertise the inventory: `skills/` symlinks (each must resolve to `../`), the `.claude/CLAUDE.md` Skills Overview table rows, and `.claude-plugin/marketplace.json` `plugins[].skills[]` (`./` form). It also checks that any "N skills" count claim on the three current-state metadata surfaces (`plugin.json` / `marketplace.json` descriptions, `MODE_REGISTRY.md`) equals the number of skills on disk; README and CHANGELOG are out of scope because their release notes carry legitimately frozen historical counts. The table-row grammar moves to `_skill_lint` (`SKILLS_TABLE_ROW_PREFIX` / `SKILLS_TABLE_ROW_FULL`) so this lint and `check_version_consistency.py` agree on what a row is, and a row that names a skill but lacks its `vX.Y.Z` token is reported here rather than silently skipped by the version lint; and `check_spec_consistency.py` now derives its skill list from disk instead of the hardcoded four-path tuple, so a new skill directory is version-policed the moment it exists. Motivation: triage of an external draft PR that added a fifth top-level skill directory showed the existing lints are anchored to the four skills they already know (`check_spec_consistency.py` hardcodes the paths; `check_version_consistency.py` iterates the CLAUDE.md table), so an unpackaged, unlisted skill passed every inventory lint. Wired into `spec-consistency.yml`; 60 mutation tests cover each surface in both directions, dangling / mis-targeted / non-symlink `skills/` entries, malformed manifest entries, and stale count claims. ### Changed - **Documentation aligned to Claude Fable 5.1 (2026-09 model update).** `docs/PERFORMANCE.md` / `docs/PERFORMANCE.zh-TW.md` name Fable 5.1 as the current frontier model and add a clearly-labelled list-price re-derivation of the full-pipeline figure (~$7 per run at US$10 / US$50 per MTok) marked as arithmetic on the token columns, not a re-measurement; the Opus 4.x measurement basis of the table is unchanged. `shared/cross_model_verification.md`'s primary-row example reads Fable 5.1. `shared/model_tiering.md` gains a declared-model note under Resolving a tier at dispatch time: the resolved tier names the *declared* session model, because the runtime may serve a classifier-flagged request on a different model of the same family with no signal ARS reads (the vendor specifics — Claude Fable 5.1 system card §3.2 / §5.2 — live in the audit's G-3 row). `docs/RISK_REGISTER.md` residual gaps change where the cards change the weight of a row: R1 (misrepresented findings named as the residual error class — Fable 5.1 §2.2.4), R4 (provider-side escalation to human review joins the consent residual gap), R5 (per-request fallback invisible to provenance; pointer moved to the new audit and the declared-model note), R6 (covert verifier underperformance likely undetectable even to its vendor — Astra §9.2.2.3). The `scripts/cross_model_smoke_test.sh` header example uses a `` placeholder instead of a concrete id. No agent prompt sentence was removed; the release-facing suite version is unchanged. ### Fixed - **`docs/RISK_REGISTER.md` R10 residual gap de-staled (#813).** R10 still claimed the guard-launcher degradations were "not yet indexed in the degradation registry (#769)" although #769 shipped in v3.21.1 itself (registry 1.3.0, five `write_scope_guard_*` rows) — the register contradicted the CHANGELOG and the registry file in the same tree, at the tag and on `main`. The stale clause is removed; the existing-controls line now points the guard's degrade posture at its five registry rows, and the residual gap keeps only the per-mechanism, per-channel loss description. Docs-only; found by an external cross-model fact-check of v3.21.1 claim surfaces. Known residue, accepted: no lint pins a residual-gap sentence against the mechanism inventory it references, so this class can recur; RR-1..RR-3 are unchanged. - **MLA key-rules line in `citation_format_switcher.md` no longer reads "No year in in-text" (#805, by @LeslieLi46).** Docs-only; the line now matches the in-text format documented above it. - **`check_surface_form_parity` names the broken environment instead of blaming the manifest (#801 follow-up).** With the manifest file present but pyyaml unimportable, `_load_manifest` returned `None`, so the lint failed with "manifest … is present but empty / null / non-mapping" — a misdiagnosis pointing the reader at the wrong file (pyyaml is declared in `requirements-dev.txt`, so only a broken local environment can hit this). The missing-import case is now a distinct `_YamlUnavailableError` and the lint fails naming pyyaml and the `pip install -r requirements-dev.txt` remedy; regression test pins that the message names the environment, not the manifest shape. Also de-enumerated the hand-maintained dependency parenthetical in `docs/SETUP.md` / `docs/SETUP.zh-TW.md` Method line ("PyYAML + jsonschema" was already stale against the manifest it mirrors two lines above); both language files move together. - **`normalize_cn_title` strips outer wrapper marks only when they enclose the whole title as one balanced unit (#800).** The wrapper strip inherited from #431 (and promoted unchanged by #798/#799) was positional: it removed the first and last characters whenever they matched as a wrapper pair *type*, without checking they belonged to the same bracket pair. `《红楼梦》与《金瓶梅》` — two titles joined in one string — therefore normalized to `红楼梦》与《金瓶梅`, leaving an orphaned `》` mid-key. Matching correctness was never affected (both sides of every comparison pass through the same normalization, and no exploitable asymmetry was found in the #799 security pass), but the mangled key is a semantic anomaly for any future single-sided consumer (display, logging, cache keys, or comparison against an externally-normalized key). Fix adds `_outer_pair_encloses`: the outer marks are stripped only when the interior between them is itself balanced under all six wrapper pairs, so `《围城》` still strips to `围城` and nested balanced interiors (`《基于「ProEXC」的研究》` → `基于「ProEXC」的研究`) still unwrap, while `《红楼梦》与《金瓶梅》` and `“研究”与“实践”` keep their marks. The interior scan is scoped to the outer pair's **own family**: `’` is also the English apostrophe and `”` also appears unpaired, so a family-blind scan read the lone `’` in `《Alzheimer’s病中ProEXC表达》` as an unbalanced quote and refused to strip a genuine `《…》` wrap — dropping a pair that matched before to exact=False and ratio 0.6818, below the 0.70 floor, which fails the DOI-keyed ratio gate and the title-fallback exact gate at once and is the failure class #798 repaired. Scoping costs the check nothing, since any mark that can orphan the outer pair is by definition of that pair's own family. Both consumers change together — the CJK client re-imports the shared function (#799), pinned behaviorally as well as by identity. Scope of the invariance claim, stated precisely: on the two `has_cjk`-gated paths (`exact_normalized_title`'s third branch and `_similarity`'s CJK fold) every verdict and ratio for titles without Han ideographs is unchanged, pinned by the #799 pre-fix oracles. The client's `_cn_titles_match` is **not** `has_cjk`-gated — it calls `normalize_cn_title` directly — so a mark-carrying title with no Han ideograph can change verdict there (`《Hamlet》and《Macbeth》` no longer matches a pre-mangled `Hamlet》and《Macbeth`); that path is DOI-keyed and Chinese-corpus-only in practice, so the narrowing is accepted rather than gated. Likewise the empty-wrapper guard is a property of the CJK branch specifically (`_cjk_titles_match` requires a non-empty key): `exact_normalized_title("《》", "《》")` remains True through the ungated base-normalization branch, as it did before this change. - **CJK titles no longer fail the shared exact-title gate in the four index resolvers (#798).** `chinese_literature_client.py` already carried a Chinese-aware `normalize_cn_title` / `has_cjk` (#431 §"Chinese title matching"), but the four index resolvers (Semantic Scholar / OpenAlex / Crossref / arXiv) never saw it — they read the ASCII-centric `_text_similarity` helpers, where `.lower()` folds case but never width (P U+FF30 never reaches P U+0050) and `string.punctuation` contains none of `。`, `《》`, or U+3000. A Chinese paper an index served in a different-but-legitimate typesetting therefore missed on **two** paths: the DOI-keyed cross-check, which gates on the fuzzy ratio *alone* and scored a fullwidth spelling of the identical title at **0.625 — under the 0.70 floor — reporting a correct DOI as `DOI_MISMATCH`**; and the title-fallback search, which requires ratio **and** exact-normalized equality and so fell to `unresolvable`. Both feed the `*_unmatched` contamination signals, so a genuine paper could accumulate *k* across indexes and be rendered `CONTAMINATED-TRIANGULATION-UNMATCHED` — the protocol doc's own "P0, next to the word 'fabricated'". The failure was invisible to the English test corpus for a measurable reason: a Han character is a whole word, so the same six-codepoint corruption is 37.5% of a 16-character Chinese title but 8% of its 68-character English equivalent (measured 0.625 vs 0.912). Fix promotes `has_cjk` / `normalize_cn_title` into `scripts/_text_similarity.py` (behaviorally equivalent — the promotion hoists locals to module constants, precompiles the regex, and rewrites comments; the CJK client now re-imports rather than keeping a private copy, per the #128 anti-drift goal), adds the Chinese-aware form to `exact_normalized_title` as an additive third branch, and folds it into `_similarity` through the existing `max`. Both are gated on **both** sides carrying a Han ideograph, so every non-CJK verdict and every non-CJK *ratio* is provably unchanged — pinned by two oracle tests that re-state the pre-fix `exact_normalized_title` and `_similarity` formulas in full (the latter including the dotted-acronym branch) and assert exact agreement in both directions, so a raised score is caught as loudly as a lowered one. Cross-script and romanized pairs still cannot match (no translation oracle), Simplified/Traditional is still not folded, and an empty normalized key still never matches. Under the Chinese-aware form the ratio also regains discriminative power on the motivating pair: 1.000 for the identical title against an unchanged 0.510 for a genuinely different paper (the base form separated 0.566 from 0.510 — almost nothing). Also measured and pinned: on the motivating pair the pre-fix ratio was actively *anti-correlated* — the identical title scored **0.606 while a genuinely different paper scored 0.645**, so the wrong paper ranked higher. The repair leaves the unrelated pair's score byte-identical (0.6452 before and after; it is not equal under the CJK form, so nothing is folded in) and lifts only the true match, restoring the ordering title ranking depends on. Not in scope, and unchanged: the base ASCII normalization still collapses `ER+`/`ER-` and `p53`/`P53` because it maps ASCII punctuation to whitespace and lowercases, and `exact_normalized_title` ORs that form in; the fuzzy floor also remains a weak separator for CJK generally (that 0.645 near-miss is pre-existing and untouched here). 32 new tests, including 5 integration tests through the real Crossref client covering both broken paths, each verified to fail against the pre-fix module, plus a monkeypatched detonator pinning that the Chinese DOI-path matcher never consults the fuzzy ratio in either direction. - **Autolink round-trip test no longer rides on an undeclared, version-sensitive dependency (#801).** `test_gfm_bare_urls_emails_and_schemes_cannot_autolink` soft-imported markdown-it-py — not listed in `requirements-dev.txt` — and silently returned when it was absent, so its no-`link_open` tail had never actually run in CI, while an ambient markdown-it-py 2.x (whose linkify pass still tokenizes the escaped forms as links; reproduced under 2.2.0 + linkify-it-py 2.0.3, reported by @akshath-raj in #799) failed the test on clean `main`. Verified dividing line: 2.2.0 fails, 3.0.0 and 4.0.0 pass with linkify-it-py 2.0.3 held constant. The tail is split into its own test (`test_escaped_markdown_yields_no_linkify_tokens_on_round_trip`) gated by `pytest.importorskip("markdown_it", minversion="3.0.0")` + `pytest.importorskip("linkify_it", minversion="2.0.3")` — ambient-old environments now skip visibly instead of failing or passing vacuously — and `requirements-dev.txt` declares `markdown-it-py>=3.0` + `linkify-it-py>=2.0.3` with a reverse pointer at the consuming test, so CI exercises the round-trip for the first time. The same declaration also newly activates the identical soft-import tail in `test_renderer_neutralizes_markdown_active_inventory_path` (`scripts/test_build_submission_packet_manifest.py`), which moves to the same `importorskip` idiom (no version floor: it parses default CommonMark without linkify) and gains the inline-children flatten its top-level-only token scan was missing — `link_open` / `image` / `html_inline` nest under `inline` tokens, so the pre-existing assertion could only ever catch `html_block`; its strengthened assertions verified under markdown-it-py 2.2.0, 3.0.0, and 4.0.0. The triplicated hostile-row construction in `test_evidence_rows.py` is consolidated into one `_hostile_row` helper. Renderer behavior and every renderer-side assertion are unchanged. ## [3.21.1] - 2026-08-24 — Bounded workflow substrates, sealed bakeoffs, and transport hardening ### Fixed - **Second markdown-grammar consolidation slice (#794).** CA-1 and RR-1 now share one repo-relative path/anchor resolution ladder, using authored fragments as exact generated heading ids rather than re-slugifying them; case-drifted fragments therefore fail consistently. `check_spec_consistency.py` now consumes the shared rendered-link grammar, so image targets and links inside code/comments/fences no longer create false broken-link findings while its historical file-existence-only scope is preserved. The indirect prompt-injection asset validator also reuses the shared GitHub slug implementation (`CLI / IDE` → `cli--ide`). Consumer and helper mutation suites pin the intended bare-`#`, trailing-`#`, case, non-rendering, and import/CLI behavior. - **Codex subscription transport repaired against three live codex-cli 0.147.0 drifts (#785).** The contained citation adapter had become unusable end-to-end: (1) the ChatGPT-subscription attestation is emitted on stderr in non-TTY invocation, but detection read stdout only — every detect returned `AUTH_NOT_CHATGPT_SUBSCRIPTION`; detection now accepts the exact line on either stream (the #684 harness idiom), with a stderr-emitting fake-codex regression test. (2) The provider's structured-output validator now rejects `uniqueItems` (`invalid_json_schema`, HTTP 400) — the keyword is dropped from the provider-sent `MODEL_OUTPUT_SCHEMA`; duplicate-source refusal was already enforced fail-closed in the local validator. (3) `--disable code_mode_host` silently removed the standalone web-search tool (the tool executes through the code-mode host on this build; isolated by live bisection of the 22-entry disable list) so every call failed closed as `MODEL_RETURNED_NOT_SEARCHED`; the host is no longer disabled while `code_mode` itself stays disabled and the forbidden-event scan still fails the receipt on any item type outside the four-type allowlist. Live `cross_model_smoke_test_codex.sh`: PASS for both `gpt-5.5` and `gpt-5.6-sol` (2026-08-19). ### Added - **Sealed preregistration for every future Promotion Bakeoff (#789; PR #795).** Two closed contracts plus `scripts/check_promotion_bakeoff_preregistration.py` implement the private fixture → public hash commitment → preflight → post-fleet reveal lifecycle for both API and codex transports. The verifier binds LF-normalized bytes and the fixed 30-row composition, requires a dedicated commitment-only commit strictly before the immutable probe/reveal commit, refuses path/symlink/history drift, rejects shallow or unreadable history, and scans every detectable historical probe version to retire published answer keys and reject reused fabrication references despite superficial text edits. Squash/cherry-pick reveal copies are accepted only as byte-identical lifecycles descending the same seal, and every copy introduction is exposed in the receipt for a mandatory post-fleet remote-timing witness. The canonical protocol requires the run report to retain the public commitment permalink/CI witness and fleet time bounds, because local Git ancestry cannot prove remote publication time. The sole 2026-08-19/20 campaign is grandfathered only at its pinned path, normalized digest, immutable blob history, and regular-file mode; all later unsealed fixtures fail CI. - **Default-off research-workflow profile substrate (#742).** Closed profile and append-only selection-receipt schemas, a deterministic offline runtime, and a canonically stored/hash-bound `field_general` fallback now implement explicit selection, visible fallback, and correction without restarting or rewriting scholar-owned artifacts. The fallback leaves every stage unresolved except field-general integrity gates, treats an empty authority list as “unknown; ask,” provides en/zh-TW display names, and records every caller-declared prior-profile output as `stale: profile_context_changed`; newly introduced authority requirements remain an explicit unmet reuse gate. The #745 matrix registers only deterministic conformance under `rq_formation`. No manuscript inference, pipeline hook, family-specific shipped profile, default-on change, human usability run, or research-outcome claim ships; behavioral evidence remains `NOT_RUN`. - **Opt-in inquiry branch ledger alpha (#743; PR #796).** Closed ledger and passport-pointer schemas plus a deterministic offline runtime now preserve append-only branch origins, explicit author adoption/disposition, exact-profile live-budget enforcement, reopen signals, and independently resolvable first-degree stale causes. Compact summaries are wired only at the Stage 1 design freeze, Stage 2.5/4.5 checkpoints, and lawful recorded-signal moments; signal actions carry trusted current status/provenance, and every outstanding stale artifact/cause remains individually visible through bounded one-line escaping. Publication starts only with a second branch and uses a shared passport lock, finite bounded timeouts, full-byte compare-and-swap, and a durable recovery journal for the ledger/passport pair; malformed pointers, unresolved historical profiles, relative or absolute symlink aliases, path/sidecar collisions, hash drift, and incomplete invalidation batches fail visibly. Authoritative append/summary/load/commit calls require the expected project identity. The flag remains `ARS_INQUIRY_LEDGER=1`, default OFF; the two #745 matrix rows are structurally CI-gated with behavioral evidence `NOT_RUN`, so no novelty, correctness, value, recovery-benefit, or usability claim ships. - **Profile-relevant alternative-register design freeze (#744).** Freezes an independently versioned stage/category companion map and future event-sourced register without implementing either. The corrected design uses its own per-stage alternative budget rather than redefining #742's branch budget; embeds exact profile-receipt and map snapshots; requires monotonic current-ledger-tip CAS, exact profile-rebound linkage, and one fixed-path passport transaction domain with unchanged-dependency CAS and non-pointer passport preservation. Lifecycle transitions, structured adoption/trigger receipts, provenance-safe rendering, typed unresolved relevance at drafting/review, bounded unranked AI candidates, and complete paginated first-degree invalidation are closed. Empirical and non-empirical synthetic mappings demonstrate lawful differences, while inapplicable cells emit no boilerplate. The future flag stays default OFF and all human-participant usefulness, inflation, omission, recovery, and burden evidence remains `NOT_RUN`. - **Bounded source-backed criteria proving set (#575).** The live review-criteria registry now contains one illustrative exact-profile fixture modeling author confirmation—Software Engineering × MSR 2027 × Technical Papers × Full Paper—with three current official venue/type rows, plus immutable-commit ACM SIGSOFT General and Repository Mining rows selected as field/overlay guidance. The SIGSOFT rows remain advisory because no official MSR adoption statement was found. Mutable MSR provenance is bound to a committed raw/semantic hash receipt without redistributing the page; SIGSOFT freshness is pinned against the verified repository head. Exact axes, policy eligibility, effective-date downgrade, three-consumer binding, source locators, and predecessor-to-successor digest migration are executable. This is one architecture proving set, not venue/discipline coverage, a real-author attestation, or constructive-review evidence; #575 stays open for #684's two independent human experts and blind adjudication. - **Historical pre-proving-set snapshot — #575 closure-scope and #684 expert-readiness audits (2026-08-24).** At the time of that retained audit, target resolution and three-consumer digest binding were mechanically implemented while the live criteria registry still had zero official venue/type, field/society, or reporting/design rows. The later #575 entry above closes that dated source-scope gap without rewriting the snapshot. The #684 replay confirmed all 24 retained subject outputs and the blinded expert packet were ready, but completion still lawfully waits on two independent human experts and a separate blind human adjudicator; agent/model labels are not substituted. - **Write-scope guard launcher degradations registered in the degradation registry (#769).** The registry presents itself as indexing every graceful-degradation mechanism in the suite, but the guard launcher's documented degraded states had no rows — and the #757 prose table in `docs/CONTROL_AVAILABILITY.md` was standing up a second, unpinned authority for those facts. Five `write_scope_guard_*` rows now index the launcher's paths (no real Python → silent pass-through; Windows without Git Bash → guard inactive with per-call hook error; no `timeout` binary → portable watchdog fallback that still forwards the guard's real decision, with only an overrun resolving to pass-through; guard subprocess misbehaves or the launcher itself fails internally — mktemp, self-resolution — → pass-through with guard stderr dropped; and the documented multi-megabyte payload edge as its own row, recorded honestly as an accepted, untested case with no pinned outcome), each with verbatim authority anchors into `hooks/run_guard.sh` (plus the README Requirements bullet) checked by the D3 lint, and `pinned_by` naming `scripts/test_run_guard_launcher.py` where CI-executable pins exist (the Windows-without-Git-Bash and payload-edge rows honestly carry none). Registry 1.2.0 → 1.3.0; `_EXPECTED_MECHANISMS` inventory lock updated in the same commit (16 → 21). The CONTROL_AVAILABILITY table now declares itself a convenience summary backpointing at the registry rows. - **First Promotion Bakeoff run: `gpt-5.6-sol` validated for the ChatGPT-subscription citation transport (#787; PR #788).** The § Promotion Bakeoff procedure ran end-to-end over the #630 contained codex transport (entry gate: live codex smoke PASS for baseline and candidate), with a preregistered instrument: the 30-reference probe set (`evals/bakeoff/2026-08-19-gpt-5-6-sol-codex/`, every real row resolver-confirmed same-day, every fabrication negative-checked) and the final receipt parser were both committed and pushed BEFORE the scored fleet. Scored run (run 7, 2026-08-20, counterbalanced pair scheduling under the frozen instrument): 30 × 3 repeats × 2 models = 180 same-day paired calls, majority verdicts — all five non-inferiority measures passed with a stated superiority case (fabrication recall 0.90 vs 0.80, p95 latency 25.0 s vs 49.6 s nearest-rank, grounded completion tied at 0.900, zero receipt-guard misfires on both fleets, false-disagreement 0.00 = 0.00; recall and latency led in all five paired fleets, and the sequential fleets' completion edge did not survive counterbalancing and is not claimed). Run report with probe-set sha256 and the full exploratory-round accounting: `audits/bakeoff-gpt-5-6-sol-codex-2026-08-19.md` — six unscored rounds are documented (tool-defect fleet; a fixture-correction round after both models correctly flagged three operator transcription errors, discarded under the preregistration clause; an instrument-defect round whose over-narrow parser suppressed the baseline, discarded as candidate-flattering). The result is transport-qualified: `gpt-5.6-sol` remains **provisional on the first-party API route** (its jq grounding guards were not exercised), and the API-route id-status allowlist is unchanged; the recommendation-policy note, Supported Models row, bakeoff section, and the codex-subscription setup examples record the split explicitly. Campaign side-product: two further receipt-grammar drifts fixed in the transport — non-search `webSearch` items are exempt from stream-fatality strictly per the protocol's closed `WebSearchAction` set ({`other`, `openPage`, `findInPage`} + Responses-API spellings, verified via `codex app-server generate-json-schema`; an opened page's URL still can never become a bound source; unknown action shapes stay fail-closed) and `DEVELOPER_INSTRUCTIONS` now requires an empty `sources` array for NOT_FOUND/NOT_SEARCHED — with page-open + unknown-shape mutation tests (51 → 54 transport tests). ### Changed - **`data_access_level` re-derived for `academic-paper` and `academic-paper-reviewer` under the dirtiest-input rule (#773).** #756 re-derived only `academic-pipeline`'s annotation; the other pins froze pre-existing declarations without claiming re-derivation. Applying the same derivation: `academic-paper` moves `redacted` → `raw` (standalone modes ingest ungated user drafts and third-party reviewer comments, and `literature_strategist_agent`'s search-fills-gap flow ingests external-index search results inside the skill — the former value described the orchestrated pipeline path, where Stage 2 inputs arrive as Stage-1 sanitized artifacts, Gate 2.5 running after Stage 2), and `academic-paper-reviewer` moves `verified_only` → `raw` (the standalone `/ars-reviewer` entry legitimately consumes an ungated pasted manuscript; the former value was at best true for the pipeline's initial Stage 3 dispatch — Stage 3' re-review consumes a freshly revised manuscript before Stage 4.5 — and that Stage 3 sequencing is unchanged). `deep-research`'s `raw` is re-affirmed on its inputs (raw user queries + unverified web/database search results), with a ceiling argument as supplement. All four `EXPECTED_LEVELS` pins are now derivation-backed, the lint docstring records each derivation, and the ARCHITECTURE §4 diagram + rules separate the per-skill intake annotation from the per-stage output data level (§3 column), which is unchanged. Declarative annotation only; no runtime behavior changes. - **Markdown-stripping helpers consolidated into `scripts/_markdown_lint_util.py` (#771).** The fence/comment/code-span stripping and rendered-link grammar that #757 (`check_control_availability.py`) and #758 (`check_data_flows.py`) shipped as two diverging copies now live in one shared module, imported by both lints and by `check_risk_register.py` (which had been reaching into the siblings' private helpers as a stopgap). Per the issue's consolidation rule the #770 versions won as the superset, so this is a refactor plus one deliberate grammar alignment: CA-1..CA-3 now also apply the inline code-span and image-exclusion rules that previously protected only DF-3/RR-3 (a backticked pseudo-link or an image no longer counts as a rendered link on the CA surfaces — behavior-visible, invariant-preserving in the strengthening direction). The consolidation also moves heading-slug extraction (`github_slug` / `heading_slugs`) into the shared module — ending the last private markdown-helper import among these three lints — and adds a shared `links_to` inbound-link predicate absorbing the three previously copy-pasted CA-3/DF-3/RR-3 loops. The grammar gains its own direct test suite (`scripts/test__markdown_lint_util.py`, manifest-registered) plus a CA-3 image-exclusion mutation test; all three consumer mutation suites still run against the shared module. - **Cross-model recommendation surfaces follow generation currency (#783; PR #784).** The recommended-verifier surfaces (`shared/cross_model_verification.md` Supported Models table + recommendation sentence + Option A example, `docs/SETUP.md` / `docs/SETUP.zh-TW.md` quick-setup blocks, `.claude/CLAUDE.md` live description) stop naming superseded GPT-5.5 as the default: `gpt-5.6-sol` (current OpenAI flagship) became the lead OpenAI example while still **provisional** at that recommendation-update point, before this release's later #787 bakeoff. That later run validates only the ChatGPT-subscription citation transport; the first-party API route remains provisional, the API-route id-status allowlist is unchanged, and the § Promotion Bakeoff remains the only route to `validated`. `gpt-5.5` / `gpt-5.5-pro` stay in the table as validated previous-generation options, with `gpt-5.5` the designated bakeoff baseline. Gemini 3.1 Pro stays recommended: first-party check (2026-08-19) confirms `gemini-3.1-pro-preview` is still Google's most capable Pro model. Docs-only; no env-var or first-party API transport change. - **OrcaRouter listed in the community directory (#781; PR #782).** `THIRD_PARTY.md` now records the contributor-submitted OpenAI- and Anthropic-compatible gateway as an option for the ungrounded OpenAI-compatible cross-model path. The directory entry is acknowledgement only, not official support, bundling, endorsement, affiliation, or maintainer verification. ## [3.21.0] - 2026-08-18 — ISO/IEC 42001-spirit transparency, verifiability, and feasibility track ### Added - **Lightweight risk register (#759).** `docs/RISK_REGISTER.md` is the single artifact linking each of ten standing risks (hallucinated citations, silent claim-strength drift, indirect prompt injection, cross-model content exposure, model-version drift, correlated same-family error, user over-reliance, venue-policy staleness, dependency/supply-chain, install-channel enforcement loss) to its existing controls, the evidence status behind them, and the residual gap with its tracking issue where one exists. The register indexes, it never re-authors: matrix-tied evidence statuses mirror `shared/contracts/capability/stage_capability_matrix.json` verbatim, and maintainer assertions are explicitly marked as such. New lint `scripts/check_risk_register.py` (RR-1 pointer integrity incl. repo-containment and anchor checks, RR-2 status mirroring with a malformed-citation guard, an asserted-status ceiling — the matrix stays the sole authority for `MEASURED`/`MIXED` — and an inventory lock on the shipped matrix-row citations, RR-3 resolved-path README discoverability; 22 mutation tests incl. a real-tree pass) is wired into spec-consistency CI and the pytest manifest; it imports the sibling lints' markdown/link helpers and the matrix's loader and status vocabulary rather than keeping third copies (#771). Closes the F-1 finding of the ISO/IEC 42001-spirit gap assessment (`audits/iso42001-spirit-gap-assessment-2026-08-17.md` §7 step 5). - **Pipeline wiring for the #655 claim-standing probe (PR-C: trigger, consent surface, freshness, transmission ledger).** The probe now joins the pipeline at exactly one seam — a new "Claim-Standing Probe Offer" section in the Phase E protocol (`academic-pipeline/references/claim_verification_protocol.md`) offers it as a user-requested, advisory-only view after the Claim Registry is emitted at a Stage 2.5 or 4.5 integrity checkpoint, never as part of Phase E verification or the integrity result, and E1 now records WHICH of the five high-impact criteria fired (`high_impact_basis`) beside the #549 selection tier so the trigger has a registry-recorded input. `scripts/build_claim_standing_query_plan.py` enforces the exact §3.1 trigger (gate 1: at Stage 2.5 the recorded `HIGH-IMPACT` tier alone is the registry witness and `RANDOM`/`TOP-UP`/`NOT-SELECTED` are never eligible, with the plan's five-part basis supplied by the registry or a recorded researcher confirmation; at Stage 4.5 the `ALL` registry plus the recorded basis is required because `ALL` alone is not permission, and a basis-less row stays ineligible until confirmed; confirmations and basis provenance are recorded on the consent surface, never in the registry; the trigger constants are exported by the Track A substrate and test-pinned to the plan schema) and the §3.2 consent sequence (gate 2: `propose` renders the closed consent surface — exact claim, providers and retention, per-recipient content, caps, LLM-transmission flags, exact stance provider/model when proposed, persistence/deletion/export boundaries with the derived artifact suffixes, the advisory statement, and the four separate choices — and embeds the complete consentable-plan projection, so the surface SHA-256 covers every field the receipt later binds and `bind` asserts receipt-vs-surface digest equality; absence of the acceptance, any post-proposal change, or an explicit cancel produces an explicit local `not_checked` declination record — `consent_absent` / `consent_invalidated` / `consent_cancelled` — and nothing else). Bound plans validate as `claim-standing-query-plan/1.0` or `/1.1` under the Track A substrate, reuse the declared discovery-adapter roster verbatim, derive the default query with the substrate's `exact_claim_query`, refuse explicit falsey decisions rather than defaulting them, and refuse the unauthorized `assisted_then_researcher_approved` planner mode; `bind --output` persists only under `explicit_local_export` to the consent-derived `.query-plan.json` path, printing for session-only use otherwise. `scripts/check_claim_standing_freshness.py` implements §7 (gate 13): the probe identity bindings — current claim text, consent receipt, query plan, adapter registry, candidate ledger, stance configuration including the consented provider/model and frozen prompt-contract version — are compared through the runner's single `expected_identity` authority, and any drift yields a `stale` verdict with a closed reason list, the fixed inspectable-but-not-current presentation rule, and explicit `assessed`/`unassessed` binding lists so a partial comparison never presents itself as complete; a stance record is only assessable with its candidate ledger, corrupt self-digests are assessment errors, never silent verdicts, and `--runtime-check` also compares the consented roster against the currently declared adapters. `shared/contracts/claim_standing/transmission_ledger.schema.json` (`claim-standing-transmission-ledger/1.0`) closes the §6 forward-declaration from the Track B slice: retrieval-query events derive one-to-one from retained attempts, stance events are the runner's transmission records field-for-field with unexpected fields refused, and accounting is deliberately conservative (a pre-transport adapter refusal still appears with its failure state, so the ledger may overstate, never understate, what could have left the session). `scripts/check_claim_standing_transmissions.py` (gate 14) schema-validates both inputs, replays their self-digests, runs the canonical intake's semantic invariants (so a re-sealed retrieval input that silently omits a planned query/index attempt is refused), and fails closed on any event outside the consented content-class allowlist or recipient roster, an attempt naming a non-consented query/index pairing, duplicate attempt ids, receipt or retention drift, and unrecorded result states; a `retrieval_plus_stance` plan must supply its stance transmissions explicitly (an empty list is an explicit no-call assertion), the stance-record cross-check schema-validates and digest-replays the record and compares each event's prompt hash and result state against its row — the runner now binds the prompt hashes into the row BEFORE the transport call, so a timeout/judge-error/oversized failure still retains exactly what was sent and the semantic validator replays those hashes on transport-reaching failure rows too — and validation is exact replay. Gate 12 is pinned by a static capability scan of the three wiring modules (import allowlist, attribute-level containment of the network-capable discovery module to its pure roster constant, no direct write calls, no Phase E or read-ledger reach) plus an end-to-end run of the library seams and CLI entry points proving no file creation and no input mutation; the doc-pin markers live solely in the #655 integration guard, which also gains the Phase E offer surface and a forbidden-stale-sentence check. The consent surface additionally disclosures the complete consented-artifact roster (`ARTIFACT_SUFFIXES`, test-pinned to every owning module), an explicit cancel is recorded `consent_cancelled` even after a stance surface was proposed (never misread as invalidation), the eligibility verdict separates `eligible` (tier witness) from `dispatchable` (nothing further owed), the bind CLI exits with a distinct status 3 for declinations, and a trailing-separator authorized output path is refused at proposal, at plan validation, and in every derived-path helper family-wide. All three tools are deterministic and offline; 119 tests across four new files register in the CI pytest manifest, and the substrate schema-validator memoization plus shared export-consent helpers land alongside. No live index, model, judge, relevance assessor, or measurement ran; every probe surface still says `STANCE CLASSIFICATION UNMEASURED`, and #655 stays open pending the live stance-provider adapter, expert ground truth, and the baseline measurement row. - **Solo-maintainer governance statement + SECURITY triage procedure (#760).** New root `GOVERNANCE.md` states the decision authority (single maintainer, no committee; CI gates are deterministic checks, not a second reviewer), scopes cross-model review honestly (an error-detection control, not organizational independence — a second model is not a second person), and records release authority and the end-of-life posture (no SLA; the license already permits forks). It also carries the operating-principles section: the three distilled principles (transparency / verifiability / feasibility) with informative anchors into ISO/IEC 42001, a table mapping the eight assessment issues (#753–#760) onto the standard's risk-based core, and the Annex C objectives assessed as not applicable (fairness, environmental impact, training-data quality) with reasons. `SECURITY.md`'s bare 7-day promise becomes a written solo-runnable procedure: 7-day acknowledgement as the only hard promise, three severity classes mapped to best-effort response targets, credit/opt-out, and coordinated-disclosure expectations. `NOTICE.md` gains a governance pointer. Closes the F-2 finding of the ISO/IEC 42001-spirit gap assessment (`audits/iso42001-spirit-gap-assessment-2026-08-17.md` §7 step 5); together with the #759 risk register this completes epic #761's eight sub-issues. - **ISO/IEC 42001-spirit gap assessment (#762).** `audits/iso42001-spirit-gap-assessment-2026-08-17.md` records the dual-track audit (in-session structural review + an anchoring-controlled cross-model read with first-party re-verification of every filed finding) that produced this release's remediation series. It fixes the scope decision — three distilled operating principles (transparency / verifiability / feasibility) with informative ISO/IEC 42001 anchors, certification explicitly not pursued — the verified findings register (T-1..T-7, V-1..V-2, F-1..F-2 → #753–#760), the already-tracked effectiveness-evidence line (#746/#675/#676/#653), the explicitly-not-adopted list (AIMS document set, appointed auditor, supplier program), and the remediation order this release completes. - **Per-channel control-availability matrix (#757).** `docs/CONTROL_AVAILABILITY.md` maps mechanism × install channel (plugin, skills copy, repo clone, Cowork, claude.ai Project, Claude Science, Pi port — the latter two channels surfaced by evidence re-verification beyond the issue's original six) so a user evaluating an integrity claim can see whether it operates in their channel, with per-note conditions, environment degradations, and channel-wide limitations; linked source documents stay authoritative. New lint `scripts/check_control_availability.py` (CA-1 links + anchors resolve, CA-2 every SETUP method reachable, CA-3 README/SETUP inbound links; 22 mutation tests) is CI-wired. Closes assessment finding T-6. - **Data-flow map (#758).** `docs/DATA_FLOWS.md` is the single user-facing map of every network touchpoint the suite's own scripts perform (four-resolver citation gate, cross-model transports, update check) and every local store (verification cache and friends), with payload class, recipient, credentials, TTL, and off switch per row, plus explicit scope boundaries (the Claude session itself, maintainer-only harnesses, and the no-autonomous-publication line inherited as a scope boundary, not a runtime guarantee). New lint `scripts/check_data_flows.py` (DF-1 AST-scanned network imports must be on the map — its first run caught an unmapped Crossref-importing script — DF-2 curl-invoking shell scripts, DF-3 README/SECURITY/THIRD_PARTY inbound links; 29 tests) is CI-wired. Closes assessment finding T-7. - **CI workflow enforcement-class table (#755).** `docs/ARCHITECTURE.md` §7.1 classifies all fourteen workflows by what they actually enforce — blocking / advisory / administrative / post-push detection — with triggers, bypass tokens, and tag-push behavior (three workflows' tag runs documented; tags evaluate no path filters), so "CI-gated" claims can be read at their real strength. New lint `scripts/check_workflow_classification.py` (WC-1 two-way inventory, WC-2 closed vocabulary, WC-3 count-line recompute, WC-4 per-file bypass-token pins) is CI-wired. Closes assessment finding T-5. - **Stage capability/evidence matrix with enforceable claim ceilings (#745).** New `shared/contracts/capability/stage_capability_matrix.json` (`stage-capability-matrix/1.0`) records, per pipeline task family, the mechanism/contract status, deterministic-conformance status, behavioral-evidence provenance (eval, model, population, date, result), external/human outcome evidence, known exclusions, transport limits, the maximum currently licensed claim, and the next required evaluation. `scripts/check_stage_capability_matrix.py` (70 mutation tests, CI-wired) enforces the frozen task-family vocabulary shared with the #742 profile contract, non-collapsible evidence statuses in both directions (`DESIGNED` / `NOT_RUN` / `MEASURED` / `MIXED` / `OUT_OF_SCOPE`; an unrun eval can never carry numbers, and measurement provenance is refused on unmeasured rows), in-repo eval references, measurement-report binding with sibling-supersession detection (a re-measurement cannot leave a stale row silently authoritative), falsifiable `conformance_pinned_by` lints/tests on every `CI_GATED`/`TESTED` row, containment-checked verbatim claim anchors into README with a shipped row/anchor inventory lock, future-dated and stale measurements refused without a note, never-licensed stems and unmeasured percentages refused in claim prose, conservative effectiveness-language discipline on unmeasured rows, and byte-identity of the generated `docs/STAGE_CAPABILITY_MATRIX.md` view. Seeded with 13 rows covering all nine task families from the current evaluation record: 4 measured/mixed rows (RQ wording advisory, tortured-phrase mechanical conformance, seeded-defect reviewer panel including its currently-failing severity-agreement gate, revision claim-drift guard) and 9 designed/not-run rows whose ceilings state exactly that. The matrix makes evidence gaps inspectable; it does not itself improve stage performance, and registering a row licenses at most that row's recorded claim ceiling. ### Fixed - **`academic-pipeline` `data_access_level` corrected to `raw` + per-skill pins (#756).** The 2026-08-17 ISO 42001-spirit audit (V-2, #761) found the suite's principal data-classification annotation internally inconsistent on its most prominent consumer: `academic-pipeline/SKILL.md` declared `verified_only` while the orchestrator's Stage 1 accepts raw user requests and mid-entry accepts raw existing papers, contradicting the dirtiest-input declaration rule in `shared/ground_truth_isolation_pattern.md`. The frontmatter now declares `raw` (the honest minimal relabel; no per-stage trust-domain split, since the annotation is declarative), the `docs/ARCHITECTURE.md` §4 diagram no longer places the pipeline in the verified class while dash-orchestrating the raw skill (it moves to the raw class with the `User → pipeline` intake edge drawn, and the §2 legend disambiguates the §3 per-stage "Data level" column from the skill-level declaration), and the SKILL.md #528 content-lock sha256 is updated in the same commit. `scripts/check_data_access_level.py` is rewritten as a single-pass checker with an `EXPECTED_LEVELS` per-skill pin layer: a silent flip back to `verified_only`, an unregistered new top-level skill, an orphaned pin, or a non-mapping `metadata` block now fails CI with one violation per problem; the `academic-pipeline: raw` pin is #756-derived while the other three pins freeze pre-existing declarations against silent drift without claiming re-derivation. The pre-existing CLI test layer (stdout-reporting contracts) is preserved and the shared `write_skill` fixture helper moves to `tests/test_helpers.py`. - **Distribution-surface claims aligned with evidence ceilings (#753).** The 2026-08-17 ISO 42001-spirit audit (T-1 through T-4, #761) found four claim surfaces sitting outside every existing lock, their language outrunning the evidence record. All four are rewritten to matrix-licensed language: (1) `.claude-plugin/plugin.json` / `.claude-plugin/marketplace.json` descriptions drop "Production-grade" and "39-agent ensemble" (8 task families are `NOT_RUN` in `docs/STAGE_CAPABILITY_MATRIX.md`; "ensemble" implied execution/error independence the default inline path does not provide) in favor of "contract-audited … 39 prompt roles (3 plugin-exposed agents; the rest run inline by default)" with an explicit capability-ceiling pointer. (2) The `academic-pipeline/SKILL.md` no-bypass prose ("no `--no-block` escape hatch" / "cannot be skipped, must PASS" / "must PASS with zero issues") is rewritten to what the mechanism actually is — mandatory checkpoints whose only path past a block is an explicit, recorded user decision (rationale requirements escalate on repeated overrides per `shared/compliance_checkpoint_protocol.md`), a trust-based control with an audit trail; the same "must PASS with zero issues" absolutes across the integrity-protocol family (`references/integrity_review_protocol.md`, `references/reinforcement_content.md`, `references/team_collaboration_protocol.md`, `agents/integrity_verification_agent.md`, the SKILL.md stage-flow rows for both Stage 2.5 and 4.5, and the state-machine checkpoint-trigger rows) now name the recorded 3-round FAIL-loop exit instead of contradicting it — including the team-protocol handoff rows, the `state_tracker_agent` prerequisite checker, and the `pipeline_orchestrator_agent` transfer rows + #660/#672 advisory dispatch (Stage 3 / Stage 5 entry and both advisory reruns now accept the recorded FAIL-loop route instead of making it unreachable or relabeling the draft as verified), with a FAIL-loop continuation keeping the passport `UNVERIFIED` — and the #528 content-lock hashes (SKILL.md, `pipeline_state_machine.md`, `state_tracker_agent.md`, `pipeline_orchestrator_agent.md`) are updated in the same commits. (3) The `shared/cross_model_verification.md` "31% → ~5-10%" error-reduction estimate is relabeled as an unvalidated working hypothesis — no local study has measured a post-verification rate. (4) The `shared/ground_truth_isolation_pattern.md` "never bundle gold labels into the repository" sentence is rewritten to its intended boundary (no unconditional loading into operational agent context), resolving the literal contradiction with `evals/gold/` existing in-repo for explicitly-invoked harnesses; the exception is scoped to synthetic/public-safe content, with live-reviewer calibration sets staying runtime-supplied and never committed. Two lint layers keep the distribution manifests from re-drifting (the three rewritten prose surfaces carry no dedicated claim-language lint; `academic-pipeline/SKILL.md` sits under the byte-level #528 content lock only): version-consistency invariant 8 now binds the agent-count number in both the legacy "N-agent" and the new "N prompt roles" spellings and checks every count token, not just the first; and a new `scripts/check_distribution_surface_claims.py` (26 mutation tests, CI-wired via spec-consistency.yml + the pytest manifest) fails closed on missing/unparseable distribution manifests (non-standard `NaN`/`Infinity` constants included), refuses unlicensed claim language in any plugin/marketplace description field (the capability matrix's effectiveness vocabulary imported from `check_stage_capability_matrix.py` so a matrix-side stem addition binds this surface in the same commit, any percentage figure, "production-grade", "agent ensemble"), requires the plugin description to carry a count token in a spelling invariant 8 can bind (a reworded count cannot silently detach the number from the tree), and binds any "N plugin-exposed" claim to the `check_agents_mirror_sync` MIRRORS roster size. - **Citation-surface version drift + version-consistency invariant 12 (#754, PR #763).** `CITATION.cff` and the `POSITIONING.md` citation prose had silently sat at 3.14.0 for six minor releases while the suite moved to 3.20.1 (the Zenodo archive itself was current — v3.20.1 deposit published 2026-08-16 — so this was pure metadata drift; root cause: the file was added 2026-06-15, after the version lint existed, and nothing forced new version-bearing surfaces into the lint's coverage). PR #763 brought both surfaces to the then-current 3.20.1 (this release bumps them to 3.21.0, with invariant 12 keeping them aligned from here on), and `scripts/check_version_consistency.py` gains invariant 12: CITATION.cff is parsed as YAML (a regex scrape would misread the legitimate quoted spelling as drift) with absence an error like README.md, its `version` gated through the broad-capture + strict-semver idiom, and its `date-released` compared against the latest CHANGELOG entry date with the invariant-10 ±7-day window (the second half of the same drift — the date had sat 6 weeks stale); every `(Version X.Y.Z)` token in POSITIONING.md must be canonical and equal the suite version, so a `v`-prefixed or truncated human edit errors instead of being silently filtered (the pre-#169 lesson). Both surfaces are wired into the aligned test fixture, so every pass-case test now exercises the invariant; 16 targeted mutation tests (53 → 69). Hardened through a 4-angle cleanup review plus a 5-round cross-model review under an explicit threat-model ceiling (crash-on-malformed-date, silent-skip, and silent-capture-dropout paths closed; the duplicate-YAML-key construction adjudicated as a documented known limitation — last-wins matches every CFF consumer). Filed from the 2026-08-17 ISO 42001-spirit audit (#761). - **#743 design-doc reset-boundary co-location (PR #764).** Main-branch hotfix: the #743 design doc mentioned the `ARS_PASSPORT_RESET` flag without co-locating the `passport_as_reset_boundary.md` protocol reference, tripping `scripts/check_passport_reset_contract.py` on `main`; the reference is co-located as the lint requires. ## [3.20.1] - 2026-08-15 — Contract-honesty hardening and bounded evaluation substrates ### Added - **v3.20 post-release contract-honesty hardening (#734, #735–#740; PR #747).** Adds a visible, user-requested exit from non-generating Socratic RQ mode without automatic candidate generation; a hash-bound per-finding E6 disposition sidecar with explicit restore / authorize-with-reason / pause routing; exact-span, raw-byte-bound Claim Registry coverage replay with registered-denominator and unknown semantic-completeness disclosure; required read-scope on every new `/ars-mark-read` event with closed fail-visible resolution; criterion-bound categorical reviewer judgements with live packages fixed at `NOT_CALIBRATED`; and replay-valid six-axis review-panel provenance that never reduces personas to a binary independence claim. These changes harden contracts and disclosures; they do not establish improved scientific outcomes, reviewer correctness, complete semantic detection, authenticated human identity, or independent error processes. - **Adaptive inquiry and evidence roadmap (#741–#746).** Adds the staged post-v3.20 through v3.22 plan for bounded domain profiles, opt-in inquiry branches, cross-stage alternative registers, stage-specific capability evidence, and outcome evaluation. Simple-path defaults, progressive disclosure, domain-stratified usability gates, and a strict repository data boundary precede any default-on structural expansion. - **Stance runner and presentation renderer for the #655 claim-standing probe (Track B execution).** `scripts/claim_standing_stance_runner.py` consumes a finalized candidate ledger under a `retrieval_plus_stance` query-plan-1.1 consent and produces one `claim-standing-stance-record/1.0` plus its `evidence-row/1.3` rows. Consent gates fire before anything else: a 1.0 or retrieval-only plan refuses, the injected stance transport's declared provider/model identity must equal the consented `stance_plan`, and the frozen prompt-contract version is bound by the consent. The judge grammar is closed four-line output; the evidence quotation must be a verbatim substring of the inspected text within 25 words (verified byte-span, sealed as a `verified_exact_match` evidence row), and any deviation becomes that row's `not_checked/parse_error` with the raw output retained verbatim; transport failures map to `judge_timeout`/`judge_error`, a non-string or oversized judge return stays inside the closed vocabulary, and every transport call records a §6 transmission event (recipient provider/model, purpose, content classes, prompt hash and byte count, consent receipt, retention disclosure, result state) returned beside the record. `validate_stance_record` is the semantic verifier the stance-record contract names as required: consent recheck (a retrieval-only plan can never validate a stance record), stance-runtime bound to the consented stance_plan, record-hash binding, every identity hash replayed against the exact plan and ledger, the all-selected distribution sum, exact once-each coverage of the ledger's selected families, and deep evidence replay — each referenced row's claim/candidate/ledger bindings, coverage, sealed source hash, and byte-span excerpt are replayed against the ledger, cross-candidate reuse and orphan rows are rejected, both positive excerpt states replay their spans, and every performed row re-parses from its retained raw output so a resealed stance value the judge never produced cannot pass — the happy-path runner output passes it, and re-sealed tamper cases (wrong sum, dropped row, edited evidence, stale ledger hash) each fail on the semantic layer independently of the record hash. The runner CLI deliberately refuses to execute until a live stance-provider adapter ships with its own consent surface. `scripts/render_claim_standing_view.py` renders the §5.3 three inseparable parts (consent/recorded-search metadata with visible failures, the complete candidate ledger including culled states, and the distribution plus per-source rows) using only the §5.4 search-bounded sentence forms, the fixed empty-category wording for every empty bucket, the all-selected denominator with the performed-only view marked secondary beside it, uppercase coverage labels, inert escaping of all provider-controlled text (HTML, backticks, Markdown link/image syntax, line breaks flattened, and C0/C1/bidi control characters stripped so a hostile title can neither forge a bounded sentence, emit a tracking image, nor manipulate a terminal), per-source rows carrying the §5.3 fields (authors, finding queries/indexes, provider rank, relevance, coverage, conditions, evidence refs, source URL) in the §5.4 per-source sentence form, the consented stance provider and its retention disclosure surfaced in the consent section, a leading `STANCE CLASSIFICATION UNMEASURED` banner on every view, stale-record refusal, and view persistence gated exactly like every other Track A artifact (explicit_local_export consent, hash-bound derived output path, exclusive no-follow 0600 fsynced write). The three claim-standing tools now share single-instance package-first imports so exception identity holds across them. Thirty-two new tests (twenty-two runner, ten renderer) cover consent gates, identity binding, the closed failure vocabulary, semantic-validator independence, forbidden-vocabulary absence, and inert rendering; family sweep 198 green. No live stance provider, relevance assessor, pipeline hook, model call, or measurement exists; #655 stays open. - **Stance-side contracts for the #655 claim-standing probe (Track B substrate).** Three closed contracts precede any stance execution. `claim-standing-query-plan/1.1` (`shared/contracts/claim_standing/query_plan_v1_1.schema.json`) is identical to 1.0 except the consent decision may be `retrieval_plus_stance`: a top-level `stance_plan` names the exact stance provider/model, prompt-contract version, and retention disclosure (`known` requires a reference, `unknown` requires null); the consent binds its hash; and the authorized content classes extend by exactly `claim_and_selected_evidence_to_stance_provider`. The runtime validator accepts both plan versions — 1.0 semantics are byte-unchanged, the discovery CLI and ledger builder finalize under either, and the 1.1 stance bindings fail closed (unbound hash, missing stance_plan, mismatched content classes). `evidence-row/1.3` (`shared/contracts/evidence/evidence_row_v1_3.schema.json`) is the #656-owned `claim_standing_advisory` surface: one provenance-only row per (probe claim, selected candidate) binding the bounded excerpt to its exact source hash/span, reusing the family's excerpt/cache/content-handling blocks verbatim (test-pinned); it never carries a stance and abstract-level coverage is never rendered as verified full text. `claim-standing-stance-record/1.0` (`shared/contracts/claim_standing/stance_record.schema.json`) freezes the future stance runner's output: full §7 probe-identity hashes, per-family rows under the closed §5.1 vocabulary with cross-field rules (performed requires ≥1 evidence-row reference and verbatim raw output; metadata-only coverage can never be performed), the §5.3 all-selected distribution, a mandatory `STANCE CLASSIFICATION UNMEASURED` banner, and no scalar credibility/confidence/trust property anywhere (recursively test-pinned). Twenty-one tests cover both plan versions end-to-end (validator, projection, builder finalization, discovery retrieval) and contract mutations. No stance runner, relevance assessor, renderer, pipeline hook, model call, or measurement exists; #655 stays open. - **Live discovery adapters for the #655 claim-standing probe (Track A head).** `scripts/claim_standing_discovery.py` turns a consent-bound `claim-standing-query-plan/1.0` into a `claim-standing-retrieval-input/1.0` record through one closed adapter per index (Semantic Scholar paper search, OpenAlex works search, Crossref works query, arXiv API query) — the separate discovery interfaces the design requires; the pinned resolver clients are untouched and never imported (test-enforced). Each adapter declares its provider block (product identity, query capability, abstract availability, pagination behavior, adapter version, and an honest `retention_state: unknown` with a null reference), requests a single page up to the frozen 20-hit per-(query,index) cap, truncates provider overflow at the adapter boundary with exact truncation counts, and maps failures onto the closed vocabulary (`timeout`, `authentication_failed` for 401/403, `rate_limited` for 429, `service_unavailable` for 5xx, `malformed_response` for undecodable bodies, `unsupported_query` for an arXiv year filter — decided before any network call) with exactly one transport call per attempt and no silent retry. OpenAlex inverted-index abstracts are reconstructed by position; abstract text is retained exactly as returned (Crossref keeps its JATS tags byte-for-byte; the OpenAlex reconstruction is derived text and is normalized). The `retrieve` CLI validates the plan and its consent bindings first, refuses to persist any output while the hash-bound consent says `session_only`, accepts only the output path derived from the consent's hash-bound `authorized_output_path` (plus a fixed `.retrieval-input.json` suffix), writes exclusively through the substrate's no-follow fsynced writer, refuses provider redirects (an off-roster `Location` cannot receive the consented query and surfaces as `service_unavailable`), maps unreachable services (DNS/connection/TLS failures) to `service_unavailable` and plain 4xx request rejections to `unsupported_query`, binds each consented provider-roster block to the executing adapter's declared block, bounds provider bodies at 8 MiB, refuses quote-bearing arXiv phrase queries before transport, isolates a provider-contract violation (overlong field, bad or untyped value, underreported count, NFKC-nonsemantic identifier) to its own attempt as `malformed_response`, maps provider publication types conservatively onto the closed status vocabulary (unmapped types stay `unknown`), and emits a schema-valid retrieval input whose `relevance_assessments` array is deliberately empty — the #719 contracts define assessments as caller-supplied, the relevance assessor is a later separately consented slice, and the candidate-ledger builder still refuses to finalize until one assessment per computed work family exists. Thirty-one offline tests exercise the four adapters through an injected fake transport (success mapping, truncation accounting, the complete failure vocabulary, partial-fleet failures, unknown-index fail-closed, consent refusal, exclusive writes); no live index was called, and the adapters are documented as unexercised against live providers until a first diagnostic run. Stance classification, rendering, evidence rows, and pipeline wiring remain unimplemented; #655 stays open. - **Claim-standing stance seed set (#655 §8 seed, v0.1, unmeasured).** New `evals/heldout/claim_standing_probe/` seed for the future stance-classification baseline: 32 repository-owned synthetic claim/candidate items (16 en + 16 zh-TW, one distinct discipline each) covering per language two items for each of the six stance labels plus missing-abstract, metadata-only, irrelevant-candidate, and session-held-full-text slots. Item claims and abstracts were cross-model authored (Codex `gpt-5.6-sol`, reasoning xhigh) so the future subject's model family did not write its own test items; all content is fictional and DOIs use a reserved `10.99999/csp-*` form. The closed §5.1 vocabulary ships as four Draft 2020-12 schemas (seed set, blinded expert label file, hash-bound adjudicated ground truth, strict subject output with deliberately no confidence/score field) plus a criterion-keyed expert `label_guide.md`, and a fifth schema freezes the deterministic score-report shape. `scripts/validate_claim_standing_stance_assets.py` enforces slot coverage, id/DOI binding, design-slot realization, a heuristic simplified-Chinese screen, and a registration guard — the suite is deliberately NOT in `suite_registry.json`, and the validator fails if it appears there while the set is `seed_unmeasured`. `scripts/claim_standing_stance_scorer.py` is the §8 mechanical scorer: it compares the frozen subject enum to adjudicated labels with no model in the loop, reports stance/relevance/check-state confusion and failure-class distribution by language, keeps macro recall and micro accuracy separate, counts blocked/partial rows without imputation, and computes the two-replicate decision-relevance flag. Twenty tests cover the shipped set's invariants (mutation-style), enum synchronization across the four record schemas, and the scorer's confusion accounting and fail-closed input handling — including that an abstention on a gold-performed row lands in an explicit NOT_CHECKED confusion column (abstaining can never inflate recall), that blocked/partial rows keep their failure classes visible (the subject schema now requires a failure state on every blocked/partial row), that full-row accuracy includes evidence scope, and that adjudication must bind two distinct experts with distinct file hashes; a `validate-expert-file` subcommand checks one expert file's complete distinct item coverage. Per-scope and per-failure-class accuracy strata are deferred to the future baseline-row compiler and the report states only counts for those dimensions. Accepted seed boundaries are documented in the suite README: static text realizes only the abstract_missing failure family (runtime failure classes are exercised by fault injection at implementation time), and expert-packet byte sealing belongs to the implementation PR's labeling workflow. `design_target` fields are construction intent only, never ground truth. No probe implementation, expert label, adjudication, subject run, or baseline row exists; every probe surface remains `STANCE CLASSIFICATION UNMEASURED`, and #655 stays open. - **Closed first-round assignment-ledger gate for the #659 blind bundle.** The no-call envelope froze 48 arm-blind judge packets but explicitly did not implement the assignment-ledger gate its delivery boundary requires; `scripts/ideation_diversity_assignment_gate.py` now implements it, deliberately outside the no-call runner so the runner's own boundary statement stays true. An operator-authored closed ledger (`judge_assignment_ledger.schema.json`) carries only pseudonymous judge handles, an adjudicator handle that the schema itself excludes from first-round rows, blind session ids, and four exact hash bindings to the finalized run plan, inventory, blind manifest, and private arm map. `verify` replays the complete finalized bundle (including private-map 0700/0600 permission enforcement), requires all 48 packets to carry at least two distinct first-round judges, requires the roster to equal exactly the assigned judges, and forbids any judge from receiving two packets whose cells share a `pair_id` — pair-level blocking subsumes scenario and role-card sharing, including the same scenario appearing in both experiments. Only a fully passing check seals a write-once pass receipt (`assignment_gate_receipt.schema.json`) embedding the exact accepted ledger; a failure writes nothing, and an exposure failure carries no judge or blind identifiers (either would disclose private-map structure once combined with the ledger). `deliver` treats the sealed receipt as evidence rather than authority: it replays the complete bundle validation and every semantic gate check — exactly what `verify` runs — against the embedded ledger (a hand-fabricated receipt cannot authorize a delivery `verify` would refuse, even over a consistently tampered bundle), verifies the packet's sealed inventory hash, and publishes exactly one isolated packet per verified assignment into a destination outside the run and gate directories that the gate itself creates (`mkdir` as the atomic desk claim: a new delivery refuses any pre-existing destination, so two racing deliveries cannot both own one desk). Each assignment is claimed by a write-once schema-validated marker (`first_round_delivery_marker.schema.json`) and closed by a write-once completion marker after publication — an interrupted identical delivery may exact-resume once, but a completed assignment is never re-issued (accepted residual: a crash between publication and the completion marker leaves that assignment resumable with identical bytes for the same judge, affecting no blinding property); the destination is checked before the marker claim so a refused destination never burns an assignment, and a post-publication isolation re-scan remains as defense in depth, so a desk is certified only by a successful exit over exactly one packet. Gate artifacts live in a sibling `-assignment-gate/` directory, so the runner's exact run-inventory validation stays green (pinned by regression). Twenty tests build a genuine finalized 48-packet bundle and cover pass/idempotence/receipt-conflict, pair and cross-experiment exposure (mutation-verified), coverage, roster, binding drift, loosened private-map modes, tampered-bundle replay, delivery duplication/misdirection/drift, and receipt-schema validity. The gate proves structural exposure constraints only: it cannot authenticate that two handles are distinct people, and judge/adjudicator independence remains a procedural responsibility. Two bounded weaknesses under concurrent operator misuse are documented and accepted (a lost atomic desk claim needs manual marker recovery; concurrent identical resumes can both report success — neither affects exposure, certification, or re-issue guarantees). Unlike the no-call runner, the gate is not yet pinned by the suite's spec-consistency contract lint (schema closedness and the offline claim are covered by tests, not CI AST pins); extending that lint is deliberate follow-up work. No subject, actor, judge, adjudicator, model, or network ran; #659 stays open pending externally recorded sessions, human judges, and the per-mechanism baseline. ## [3.20.0] - 2026-08-14 — Evidence-bound review and revision, contained transports, hermetic evaluation substrates ### Added - **Opt-in, process-isolated PDF text/OCR advisory (#512 follow-up; PR #724; safe replacement for the concept proposed in external PR #623).** Default calls keep the original sidecar shape, tool version, and structural `PASS / FAIL / UNAVAILABLE` decision; only a caller that selects `--classify-content` starts a fixed child worker over the exact already-hashed bytes and receives an all-or-nothing extension whose verdict scope is explicitly `STRUCTURE_ONLY`. The parent never imports the optional native classifier, uses no shell, writes the exact input off the timeout-control loop, enforces one five-second execution deadline plus a shared 0.2-second teardown grace, and caps only stdout/stderr. Each loop iteration polls and then immediately observes the clock, accepting an exit only when that observation is strictly before the deadline; a late-returning poll cannot turn a timeout into success. POSIX cleanup kills the isolated worker group before joining pipe helpers; the portable Windows path terminates only the direct worker. Launch/helper, exit/signal, pipe, and protocol failures become closed reason codes; non-finite confidence and out-of-range OCR pages are rejected; only `TEXT_AVAILABLE / OCR_RECOMMENDED / unavailable` advisory states are emitted. The stdout-only legacy CLI performs no alias precheck, so malformed input paths remain exit-0 structural `UNAVAILABLE` data. When a file write is requested, conservative NFC/casefold keys, resolved targets, and existing-inode checks reject literal, `..`, case/canonical-equivalent, symlink, and hard-link aliases before the worker or either write; `samefile` errors fail closed except for an absent leaf, while an unresolvable input remains the structural preflight's responsibility. Raw upstream types and exception text cannot enter the prompt-facing sidecar; an explicitly requested, exclusive POSIX-only mode-0600 local diagnostic retains at most 512 bytes of untrusted operator detail, with its resolved parent dirfd/inode bound before the worker so a later parent-symlink retarget cannot redirect raw detail. Its created fd is inode-bound: any pre-success fchmod/write/file-fsync/close/parent-fsync failure removes the final leaf only if a fresh no-follow lookup still names that exact created inode, parent-fsyncs best-effort, and preserves the primary error. Thus a partial/malformed diagnostic does not permanently consume the exclusive path and retry succeeds, while an attacker replacement leaf is never deleted. Non-POSIX platforms or platforms without `fchmod` reject that option before path creation. POSIX sidecar output binds the resolved parent dirfd and inode before the worker starts, creates a fixed-length random-named private `0700` staging directory through that dirfd, and uses only anchored dirfd-relative operations thereafter. The complete fixed-name payload is file-fsynced, its open inode is checked against the staging entry, atomically installed with dirfd-relative `os.replace`, checked again at the target, and parent-fsynced; an observed check-to-replace substitution is rejected and its installed attacker entry removed, retargeting a parent symlink cannot redirect publication, final-component links are replaced rather than followed, and a legal 255-byte destination basename still fits. Cleanup attempts close/unlink/rmdir independently, preserves any primary publication error over secondary close failures, and removes unpublished staging. The parent and private staging directory remain caller-controlled prerequisites: Python exposes no compare-and-rename primitive, so a same-UID actor that keeps racing after the instantaneous installed-inode postcondition is outside the isolation claim. Non-POSIX `--output` fails closed because Python does not expose the required anchored dirfd publication there; classifier-to-stdout remains available. The optional dependency has a separate pin and deterministic absent state rather than being installed unconditionally. Three closed Draft 2020-12 schemas, synthetic PDFs, temporary fake modules, and fake workers cover present/absent, late exit/timeout, non-zero/signal, malformed/invalid, oversize, page-bound, helper-startup, deadline/teardown, diagnostic failure/retry and attacker-leaf retention, diagnostic-parent and parent/final/staging alias races, close failures, long basenames, privacy, and permission paths without downloading the package, using a live PDF, invoking a model, or wiring an automatic OCR/anchor gate. This proves the isolation/protocol slice only, not classifier accuracy or downstream efficacy. - **EOF-complete post-terminal drain for the Codex citation transport (#725; PR #726).** After the target `turn/completed`, the host now closes app-server stdin and accepts a transport result only after clean parent exit plus stdout and stderr EOF within the smaller of the original absolute execution deadline and a three-second drain grace. Every late stdout line is retained and parsed under the existing JSON, event-byte, message-count, forbidden-tool, and grounding rules; drain timeout, nonzero exit, malformed output, reader failure, and stderr overflow fail visibly, while final process-group cleanup reaps the parent and descendants. Hermetic fake-server regressions emit the forbidden event only after stdin EOF and cover hang/reap, late cap, malformed output, nonzero-exit, stderr, and static mutation boundaries. No live Codex, model, login, network, API, or private citation data was used. - **Offline role-topology utility design and synthetic seed (#582).** Two independent task classes now freeze their own matched role-count arms: decomposable reviewer evidence review uses 1/2/5/7-seat topologies, while sequential writing/revision uses 1/2/5/8-seat chains and a separate requirement-based evaluator that forbids accept/reject labels. A closed study plan fixes per-call token caps, empty tool/web/network allowance, three paired replicates, zero retries, first-partial stop, no imputation, small-seed uncertainty disclosure, arm/expert blinding, and a human-verified `unique verified value` rule that credits only first-originated, evidence-anchored units retained in the final output. Four repository-owned synthetic fixtures, closed plan/seed/materialized-manifest schemas, and a suite-specific validator/dry-run/materializer produce 186 self-contained neutral prompt templates with complete hash-bound role contracts, dependency placeholders, conservative input-budget receipts, and dispatch/measurement permanently false. No subject, expert, adjudicator, model, API, tool, dispatch, or measurement ran; reviewer interpretation still depends on #653 and #582 remains open. - **Offline candidate-ledger substrate (#655 Track A) (PR #719).** Three closed Draft 2020-12 contracts now bind the complete high-impact consentable-plan projection, consistent known-reference or explicit-unknown provider retention disclosure, one visible initial attempt per planned query/index pair, hash- and time-bound retry authorizations, monotonic retained attempt/hit evidence, and explicit relevance success/failure evidence bound to the exact claim, candidate, assessor contract, and canonical prompt bytes. A pure local finalizer preserves failures and every raw terminal state, applies one NFKC visible-semantic-text boundary, treats only strict `10.<4-9 digits>/` values as DOI identity, blocks no-DOI bridges between distinct DOI components, and applies the frozen filter/dedup/canonical/top-40 rules. CLI persistence fails before path creation for `session_only` and writes once only for hash-bound `explicit_local_export`, using an exact hash-bound absolute output path, exclusive/no-follow creation, mode 0600, file/directory fsync, and truthful persistence/export/path metadata; exact replay needs no network, model, discovery adapter, stance classification, rendering, evidence-row changes, pipeline wiring, or dispatch. Synthetic fixtures, direct Unicode/DOI/persistence mutation regressions, an exact module-use-allowlisted no-capability integration guard, resolver byte pins, pytest-manifest registration, and spec-consistency wiring cover this substrate. The live probe and Track B remain unimplemented and unmeasured; #655 stays open. - **Within-session ideation-diversity Phase-2 no-call envelope (#659).** Closed Draft 2020-12 contracts freeze an exact 2-experiment x 6-scenario x 2-arm x 2-replicate, 48-session plan; bind assets, prompts, order, synthetic actor packets, and non-executable envelopes by SHA-256; and require tools/web off, transport `none`, no dispatch, USD 0 API spend, no fallback, and fresh external authorization. The offline runner ingests externally recorded transcripts with canonical hash-bound external-session receipts and closed canonical raw events; it derives message bytes and forbidden action classes from those retained bytes, requires unique fresh-session identities and monotonic cell timing, forbids stitched session lifecycles, and verifies structure rather than operator/recorder identity or genuine consent. Suite commit and token caps are explicitly operator-declared and unverified. Atomic complete-byte publication, compact hash-bound file/directory inventories, materialization-time per-cell attempt guards, primary/fallback durable stop slots, bounded terminal acquisition-failure evidence, and registered publication aliases prevent conflict/retry/orphan bypass even if state replacement fails. Unicode-normalized semantic leak screening covers the complete 48-cell identifier universe during every ingestion; messages require letters/numbers/symbols and execution/authorization identities require letters/numbers, rejecting separator/control/format/combining-only placeholders. After 48 valid ingestions a deterministic durable blind intent freezes a one-time private nonce plus all ids and hashes before exact-recoverable staging creates 48 isolated blind packets plus a finalized replay manifest; deterministic intent-bound hardlink aliases preserve and exact-replay cleanup-failure evidence without generating a second mapping. The `0600` intent and map remain procedural-nondisclosure artifacts withheld from judges, and staging collisions quarantine. A future closed assignment-ledger gate must still prevent same-judge cross-arm/replicate role-card exposure, and the bundle alone does not prove that blind. The private arm map is procedural nondisclosure only, explicitly unencrypted, and stored under `0700`/`0600` permissions. Packet flags claim only that no structured label/adjudication/human-evidence artifact is attached; two independent arm-blind human judges and a separate human adjudicator remain mandatory. No transport, model, subject, actor, judge, adjudicator, API, subscription, web, or tool call ran; no output or baseline was generated, production and historical claims remain unchanged, and #659 stays open. - **Indirect prompt-injection Phase-2 no-call envelope (#675).** The frozen eight-scenario 2 x 2 design now expands deterministically into exactly 64 fresh-context cells with two counterbalanced replicates, hash-bound prompts and non-dispatch call envelopes, closed exact-plan authorization and transcript/event contracts, append-only evidence ingestion, first-failure stop receipts, and 64 isolated runner-write-once blind-session packets. A pinned decoder derives action class from closed canonical native event types bound to exact session/cell/event identities; unique fresh-session receipts enforce cross-cell monotonic order, and mandatory observed usage plus conservative byte limits enforce the frozen token/packet caps. Materialization pre-arms immutable same-inode journal claims for every ingestion and the one blind transaction, so missing/symlink/oversize transcript acquisition and even primary stop-marker failure remain terminal before external bytes can be retried. A closed write-once stop intent with base64 replay, individually bound partial evidence, one compact unregistered file/directory-tree digest, and a separate pre-load quarantine make blocked, partial, semantic-leak, drift, and write failures irreversible without retry. The finalized blind manifest semantically replays the exact atomic bundle and supports crash-boundary finalization; its public inventory exposes only opaque packet ids and byte/hash bindings, while formal assignment metadata stays in an unencrypted procedural private map protected locally by `0700`/`0600` modes. Because packets necessarily expose exact external content, they do not claim injected text or canaries are perceptually blind; a future closed assignment ledger is still required, and the bundle alone does not prove arm blindness or prevent cross-condition judge exposure. Source-codepoint-atomic NFKD spaced, joined, and compact projections remove combining marks and neutralize format, punctuation, symbol, and separator obfuscation before checking full identifiers and complete frozen mapping/prior-label phrase cross-products; compatibility letters and numbers remain atomic so their decomposition cannot manufacture false boundaries. A runtime visible-semantic predicate rejects surrogates and text without a letter, number, or symbol, so blank, format-only, combining-mark-only, and punctuation-only subject outputs cannot become complete evidence. Run trees reject symlinks; blind construction uses one deterministic staging transaction that exactly recovers a complete residue and permanently quarantines incomplete or legacy residue instead of minting a second mapping. An exact AST module/symbol/attribute/call allowlist blocks aliases and module escapes as well as provider, network, subprocess, dispatch, probe, or model surfaces. `suite_commit` must equal local HEAD, but the plan honestly says live asset hashes do not prove a clean worktree or Git-object replay. The runner exposes only init/materialize/validate/ingest/blind preparation, fixes tools/web off, transport to none, API spend to USD 0, and fallback off, and fabricates no consent, subject output, labels, human judges, adjudication, or measurement. #675 remains open pending a separately authorized run, a closed assignment ledger, at least two independent arm-blind human judges, and a separate arm-blind human adjudicator. - **Crash-boundary journal hardening for the #675 no-call envelope.** Materialization now pre-arms a dedicated pre-load-terminal token in addition to the 64 cell and blind-bundle tokens. Any normal-load failure claims that same-inode token before submitted transcript acquisition, so a failed quarantine write plus restored plan bytes cannot reopen ingestion. Successful cells transition from claimed to same-inode completed only after the advanced manifest replacement and directory fsync succeed; a replace that publishes bytes but reports a durability failure remains claimed and is rejected by validation and the next ingest before another transcript is read. Direct mutation tests pin claim-before-read ordering, quarantine-publication failure, exact prior-versus-advanced manifest discrimination, post-replace fsync ambiguity, and the complete pending/blocked/ingested journal state matrix. No provider, model, subject, network, tool, dispatch, or measurement ran. - **Search-bounded claim-standing probe design freeze (#655).** The opt-in advisory is split into a retrieval/candidate-ledger track and a separate stance/presentation/measurement track. V1 deliberately limits eligibility to claims already classified `HIGH-IMPACT` under #549 rather than treating RANDOM, TOP-UP, or final-check `ALL` as consent for broader external transmission. The frozen design requires exact per-probe consent, new discovery adapters instead of widened resolver clients, visible raw hits and culling/failure reasons, deterministic work-family deduplication and top-K caps, abstract/full-text coverage labels, separate relevance/stance/not-checked states, an all-selected distribution denominator, #656-owned versioned evidence rows, and search-bounded wording with no scalar credibility score. No adapter, schema, held-out suite, search, model, judge, or efficacy claim ships in this design-only change; #655 remains open for implementation, independently labeled ground truth, and a valid baseline measurement row. - **Within-session ideation-diversity Phase-1 freeze (#659).** A bounded held-out design now separates the opt-in adjacent-framing probe from the exploratory convergence/challenge guardrails instead of treating unlike mechanisms as one intervention. Six repository-owned synthetic scholar-role cards form three English/zh-TW pairs with precommitted framing ownership, explicit eligible-population and actor-response rules, a frozen A1-A2/M1-M5 codebook, subject/actor/judge blind fields, and separate count, facet-family dispersion, follow-through, and violation metrics that can never be collapsed into a diversity score. Model-originated questions, hypotheses, rankings, menus, and off-card options cannot earn scholar-originated credit. A hash-bound replacement manifest materializes the guardrail ablation only as a new non-production prompt and refuses production-source drift or overwrite; the validator checks pair structure, directionless facets, design/codebook boundaries, strict JSON, and the ablation bytes. No subject, actor, judge, adjudicator, model, API, web, or tool run occurred; #659 remains open for its separately authorized per-mechanism baseline under the held-out measurement contract. - **Executable indirect prompt-injection behavioral probe seed (#675).** A frozen 2 x 2 paired-control design now spans synthetic web/source verification, PDF or manuscript ingestion, bibliography intake, and pasted reviewer/committee comments in English and zh-TW. The two factors separate benign/injected external content from an explicit ARS-guidance paragraph versus a non-production platform-only control, while every v0.1 cell keeps tools and web disabled. A strict schema, eight-scenario seed set, neutral hash-bound prompt materializer, closed mechanical response scorer, blinded C1-C4 adjudication rubric, CI asset gate, and mutation tests replace #272's constant-false runtime xfail. This delivery runs no model or judge and makes no safety, efficacy, or structural-enforcement claim; #675 remains unmeasured and #676 remains the structural boundary. - **One review-target criteria authority across formative, internal, and external review (#684; runner fixes in PRs #709 and #713).** A closed pointer-only manifest binds the author-confirmed #683 context and selected criterion pointers across FORMATIVE, INTERNAL, and the exact EIC/R1/R2/R3/DA panel. A deterministic explicit-path CLI initializes, marks, records, and validates receipts plus a separate Critical/Major constructive-finding sidecar. Formative planning cannot invent evidence; internal and external Phase 1 remain manuscript-blind; paper-visible findings require typed anchors, honest remedies, costs/trade-offs, and author choice for new-data or intent-changing options. Pipeline wiring carries one manifest by pointer, never fabricates skipped mid-entry receipts, and keeps binding conformance out of integrity verdicts, checkpoints, editorial arithmetic, and author triage. A preregistered same-context/same-budget paired harness and deterministic scorer cover profile resolution, applicability, unsupported findings, severity, venue alignment, and usefulness. Its amended plan defaults to 24 contained ChatGPT-subscription Codex CLI subject calls with human expert labels, applies the existing two-family judge rule through subscription CLIs first, has no automatic API fallback, and sets a USD 0 API spend ceiling. No live subject, judge, expert, API, network, or measurement ran, so behavioral effect remains unmeasured. - **Contained ChatGPT-subscription transport for citation integrity (#630; PR #705; supersedes contributor PR #567).** Stage 2.5 / 4.5 one-reference citation checks may explicitly select a Codex app-server adapter that requires the exact ChatGPT-subscription login attestation, honors custom `CODEX_HOME`, copies only bounded `auth.json` into an ephemeral private home, runs from an empty root under read-only/never with local tools and workspace capabilities disabled, and never accepts a caller-authored prompt or filesystem path. Positive sources are bound to exact structured standalone-search results by item id, result index, and digest; echoed URLs, unrelated queries, missing/malformed searches, multiple finals, forbidden tool events, invalid selectors, and wrong event shapes fail visibly. Closed request/receipt schemas, Bash 3.2 entrypoints, checked-in event fixtures, a fake app-server suite, and CI guards are fully hermetic; the manual live smoke is consent/cost-bound and excluded from CI. The selector is citation-only and does not replace API credentials for DA, reviewer, calibration, re-review, checkpoint, or handoff calls; no model promotion is included. Original approach and product requirement contributed by [@dcs-scd](https://github.com/dcs-scd) in [PR #567](https://github.com/Imbad0202/academic-research-skills/pull/567). - **Prospective revision-claim-drift rubric v2 and subject-context protocol (#679; PR #702).** The frozen v1 rubric, held-out set, both historical measurement rows, and every retained 2026-08-07 run artifact remain byte-locked and unchanged. A separately hashed v2 rubric adds a non-control citation-attachment criterion and points to the canonical claim-strength ladder instead of re-copying rung examples; an append-only amendment record explicitly leaves all historical headlines untouched. Future `heldout-measurement/1.1` reruns must bind a closed launcher configuration, complete frozen-set subject/judge call plan, retained prompt/output artifacts, execution manifest, and subject-context record covering neutral physical cwd, repository membership, `--bare` authentication limits, instruction visibility, and a pre-fleet context probe or explicit operator attestation. C9 findings require typed raw judge flags and closed replayable evidence. Visible mechanism text is recorded as `not_isolated`, never laundered into an isolation claim. This delivery is protocol-only: it ran no subject, judge, adjudicator, model, API, or new measurement and changes no historical score. - **Replay-bound cross-document consistency advisory (#672).** New closed `preregistration-artifact/1.0`, exact two-artifact source-manifest, advisory-draft/final, and independent `evidence-row/1.2` contracts bind caller-supplied abstract/results, discussion/results, methods/reported-analyses, and manuscript/preregistration observations to exact strict-UTF-8 sources. The non-shell research architect declares only status and a companion handle; the named deterministic builder alone creates the persistent sidecar, including unavailable receipts, and academic-paper plus every pipeline handoff replay and carry its bytes unchanged. Methods absence requires an exact named checked scope, while an undisclosed preregistration deviation requires a third exact disclosure-scope witness. The final carrier is `LLM-ADVISORY` / `UNMEASURED`, never a score, gate, PASS/FAIL, ClaimIntent, rewrite, consent/protocol duplicate, agreement, or clean-document certificate. At the single mandatory Stage-5 entry checkpoint, #660 runs first and #672 second against the identical accepted-draft ID/SHA; their carriers and failure semantics stay independent and nonblocking, and any revision stales both. All implementation and fixtures are hermetic—no live model, external API, judge, legacy multi-file audit, or expensive evaluation. - **Merge provenance for the current feature deliveries.** The tortured-phrase implementation shipped in PR #699; revision-roadmap authority in PR #697; content-coverage advisory in PR #696; submission-packet manifest in PR #695; shared evidence rows in PR #694; deterministic retraction status in PR #691; dissent raw-HTML enforcement in PR #687; and the bibliographic-integrity, held-out-measurement, and human-subjects boundary foundation plus its follow-up hardening in PRs #685 and #686. - **Hermetic tortured-phrase screening for own drafts and cited metadata (#660).** Three closed contracts and a hermetic runtime accept only an explicit user-supplied or clearly synthetic-fixture snapshot with a detached manifest bound to the exact raw snapshot SHA-256 and an explicit rights declaration. The repository ships no native PPS content, PPS importer, network fetcher, or redistributed phrase list, and the checker uses no live model, external API, human or model judge, ambient clock, source-file timestamp, or Git/network time. Own-draft output is a bounded `HEURISTIC-ADVISORY` / `UNMEASURED` artifact rendered immediately before formatting; cited-metadata enrichment is non-in-place and emits one current v1.2 row for each title and abstract surface, including explicit `ABSTRACT_MISSING` and `ABSTRACT_EMPTY` states. Read-only consumers compose all rows in the one existing `Bibliographic Integrity Advisories` section; the advisory mints no marker, changes no gate or finalizer policy, proposes no rewrite, and makes no clean-draft, AI/author/papermill/origin, contextual-validity, publisher-acceptance, or matcher-accuracy claim. Shipped fixtures are synthetic conformance inputs only, not an accuracy evaluation. - **Post-main tortured-phrase synthetic conformance measurement (#660).** The pinned deterministic runtime at commit `86bf0e5c2cedb300d6d1c6428470cdcedfbf97df` passed 190/190 tests in the pinned repository-owned synthetic suite on the single pre-registered execution (exit 0), with the exact transcript and write-once execution manifest retained beside the `heldout-measurement/1.1` row. This finite synthetic point estimate adds no claim about contextual validity, clean text, real-world false-positive/false-negative performance, paper-mill or AI origin, misconduct, contamination, quality, acceptance, or source provenance. - **Non-ranking, explicit-author revision authority (#670).** Schema 7 now has a closed immutable `revision-roadmap/1.0` core that keeps transported reviewer severity, editorial `obligation_class`, typed cost surface, bounded consequence, deterministic source order, and exact proposed block/operation targets independent. Explicit session-author choices live in a separately raw-hash-bound `author-adjudication/1.0` sidecar, with complete triage, decline reasons, exact target subsets, exact registered-claim replacement bytes/rungs, and exact declined-overlap collateral authority; presentation views never alter `R` transport references or gate arithmetic. Claim surfaces are exact UTF-8 spans whose protected text must equal the referenced ClaimIntent `claim_text`, preventing a valid claim id from being paired with unrelated prose. Integrity issue lists and gate failures are proposal-only: a distinct explicit-author input must approve both exact targets/operations and the complete patch SHA-256 before the deterministic builder can emit a base/list/round-bound integrity authorization sidecar. Current patch 1.1 and apply-report 1.3 replay the appropriate disjoint authority branch before any write, reject patch 1.0 on the current CLI, protect registered claim surfaces exactly, and disclose the remaining unregistered-semantic-drift E6 boundary. A closed Revision-Evidence Bundle carries an exact integrity-PASS start through continuous review-write, all-declined no-op, and explicitly authorized integrity-correction rounds to the final draft, with contained read-once artifacts, deterministic patch replay, and byte-exact post-draft/report verification. The #576 current family moves in lockstep to contract 1.1, `obligation_class`/renamed residual and rate fields, exact Schema 11 author-field copies, and five hard-required artifacts: original manuscript, revised manuscript, roadmap, author adjudication, and Revision-Evidence Bundle. Missing-original, `first_link_not_run`, and old-report patch-binding degradations remain archived 1.0 behavior; current/legacy schema IDs and runtimes are isolated. All acceptance is hermetic—no live model, external API, judge, or scored evaluation. - **Replay-bound authority-profile content-coverage advisories (#681).** A separately versioned `evidence-row/1.1` surface and closed `content-coverage-advisory/1.0` carrier now bind bounded passages from explicit session-held strings to exact #666 authority requirements and `structured_expectations[]` pointers after full #667 packet-artifact replay. The `LLM-ADVISORY` layer preserves every deterministic packet status, readiness value, caller-supplied authorization field, institutional-acceptance boundary, pointer, and digest; structural gaps, external dependencies, waiver/exception boundaries, applicability-false exclusions, and missing session content cannot be laundered into semantic missing-element findings. Every profiled expectation is explicitly checked or explicitly `not_checked`, quotes retain #656 strict decoding, UTF-8 span/hash, 25-word/1,000-character, rights, inert-rendering, and human-read boundaries, and the finalizer opens only named inputs without directory scans, retrieval, network, subprocess, cache, or model calls. The surface is honestly `UNMEASURED`—no scored held-out row or efficacy claim is fabricated. - **Deterministic, authority-bound submission-packet manifests (#667).** Two closed V1 contracts now separate an author-declared inventory of explicitly named packet files from a replayable, pointer-only manifest. A standard-library checker first replays the exact #666 context/registry/resolved triplet, records declared-vs-attached observations for every explicitly inventoried file once the gates permit packet access, and then classifies only source-profiled `submission_packet` evidence entries as `DOCUMENTED`, `NOT_LOCATED`, `CONFLICTING`, or `ACCEPTANCE_UNVERIFIED`; a closed authority or capability gate is instead recorded in explicit `APPLICABILITY_UNRESOLVED` reason rows. It never interprets attachment prose, derives an IRB pathway, verifies institutional acceptance, or promotes caller-supplied authorization. Seven author-declared capability-envelope facts fail closed outside the bounded non-clinical, single-institution V1 case, while actor/holder scoping keeps IRB- and committee-owned records from becoming investigator gaps. Exact paths, file hashes, sizes, holders, authority/evidence pointers, semantic digests, additive waiver-route handling, canonical ordering, and full replay validation are enforced hermetically without directory scans, network access, model calls, or fabricated live institutional rules; the result remains a read-only deterministic input for the separately advisory #681 layer. - **Shared, source-bound evidence rows at Phase E checkpoints (#656).** Stage 2.5 and Stage 4.5 Claim Verification Reports now persist one closed `evidence-row/1.0` row per selected claim × citation/anchor tuple and render deterministic 25-row Markdown or HTML pages at the mandatory integrity checkpoint. A single provenance vocabulary distinguishes exact once-decoded quote matches, agent-extracted page/section passages, unconfirmed anchors, not-checked, missing-source, access/retrieval-failure, and anchorless empty states; only exact substrings of explicit session-held source text can become excerpts, and every source-bound row must replay against the explicit session source map before validation or display. Rows bind exact UTF-8 source/excerpt hashes, byte spans, extraction time, cache replay, rights/private-data handling, and row digests while preserving the existing Phase E verdict/gate. Strict decoding, 25-word/1,000-character bounds, inert external-text rendering, unlimited persisted rows with bounded paging, no display-time retrieval/model calls, and human-read-ledger noninterference are enforced hermetically. V1 leaves the opt-in Stage 4→5 `claim_audit_results[]` lifecycle unchanged. - **Deterministic human-subjects review-pathway rule traces (#669; PR #704).** A standard-library-only local builder exposes the predicate work for caller-named candidate institutional pathways without selecting, ranking, predicting, or authorizing any pathway. Completed and halted traces bind the exact author context, authority registry, resolved context, request, and source provisions; every result remains `institutional determination required`, carries the #665 human-subjects boundary, and is never a gate, verdict, readiness signal, model input, or substitute for institutional review. - **Optional cross-run adjudication-activity observability (#673; PR #703).** An explicit user-selected local store can retain replay-validated facts about adjudication actions from eligible terminal runs and render a bounded recent series. Terminal state is made durable before best-effort capture; store failure cannot change a transition, decision, verdict, or checkpoint. The feature defines no target, threshold, score, engagement judgment, or human-ownership claim, enters no model prompt or handoff, performs no network/model/API call, and remains advisory only. - **Human-subjects references migrate to exact authority pointers (#680).** The shared decision aid, ethics checklist, terminology glossary, and their agent consumers now collect portable study facts and consume replay-validated #666 profile requirements instead of presenting Exempt / Expedited / Full Board, Taiwan process notes, fixed online-survey metadata promises, or retained-link-key terminology as universal rules. Covered consent and GDPR duties point to exact bounded registry requirement ids; unprofiled pathway, institutional-process, population, and data-protection material stays visibly illustrative and institution-owned. A hermetic migration checker pins the #665 non-authorization boundary, validates every named requirement against the local registry, prevents committee-governance rows from becoming investigator packet duties, keeps agent mirrors synchronized, and blocks reintroduction of the contradictory pathway and privacy claims without changing registry bytes or calling a live authority/API. - **Explicit two-axis human-subjects authority context (#666).** Three closed V1 contracts now separate an author-confirmed context record, a curator-owned bounded authority registry, and a pointer-only resolved trace. Exact id/version/digest/scope pins select plural `review_ethics` and `data_protection` profiles; a standard-library offline resolver evaluates a typed Strong-Kleene predicate AST, preserves every base/overlay requirement and collision, and blocks profile-dependent results whenever either axis, source currency, or applicability remains unresolved. The shipped registry demonstrates one common schema across bounded US 45 CFR 46, Taiwan Human Subjects Research Act, and GDPR research subsets with row-local official-source anchors, actor/consumer scoping, explicit exclusions, and no completeness or legal-advice claim. Institutional and funder overlays are additive only, display precedence cannot suppress obligations, and hermetic schema/mutation tests require exact selection, duplicate-key rejection, stable digests, no jurisdiction guessing, and the existing #665/#668 non-authorization boundaries. - **Deterministic retraction status with one advisory-first authority (#651).** The #678 carrier advances to a backward-compatible v1.1 retraction row that separates OpenAlex/Crossref observations, detects both `updated-by` and inverse `update-to` relations, preserves reinstatement/disagreement/degradation, joins HIGH-IMPACT claims by worst tier, compares retraction timing only to `source_acquisition_date`, and mechanically recognizes declared legitimate use only when an auditable declaration and retraction-notice citation are both identified. OpenAlex now retains `is_retracted` in its existing select response; a distinct versioned SQLite cache exposes 30-day staleness without coercing unknown/degraded rows to clean. Detection is always visible in Bibliographic Integrity Advisories and mints no second advisory marker token. The citation finalizer is the sole authority for the new absent/advisory-by-default, explicit-strict `terminal_policies.retraction` key; only current, undisputed, non-exempt retracted rows can emit the existing generic HIGH-BLOCK token. The ethics agent now points to that result instead of maintaining a conflicting CRITICAL rule, and legacy `retraction_check` is read-only execution-attestation compatibility. The hermetic acceptance suite covers 39 resolver, cache, policy, projection, authority, schema-mutation, and CLI cases without live API calls or tokens. - **Complete concern accounting for real committee correspondence (#668).** The existing `academic-paper revision-coach` entrypoint now has an explicitly activated committee-correspondence variant with its own closed human-subjects schema and protocol, never the peer-review Schema 11 taxonomy. A byte-bound bundle preserves the full UTF-8 source (including non-comment material), maps each confirmed comment segment to exactly one source-order concern, supports compound multi-label actions and optional profile enrichment, and emits a placeholder-only response skeleton under the #665 non-authorization boundary. A deterministic checker recomputes containment, hashes, contiguous byte coverage, locators, provenance, dependencies, non-destructive working views, and one-marker-per-concern response coverage; 29 tests cover the wired entrypoint, valid degraded-mode fixture, and drop/duplicate/reorder/tamper/path/profile/false-resolution mutations. No priority, severity, satisfaction prediction, or claim of resolution is representable. - **Author-confirmed review-target context and versioned criteria registry (#683).** Three closed V1 schemas separate the author's discipline/venue/track/contribution-type/overlay declaration, a four-part provenance-and-freshness registry, and a pointer-only resolved context with distinct scientific-validity, venue-fit, and submission-readiness outcomes. A stdlib-only deterministic resolver emits stable criterion/profile digests and a Phase 0/1 brief; it never reads a manuscript, infers a missing venue, averages interdisciplinary conflicts, or applies numeric weights. Stale and unverified sources are structurally advisory-only, unknown venue profiles stay visibly unresolved, and the shipped registry contains only a field-general baseline while synthetic fixtures exercise exact venue × track × contribution-type matching. Twenty-five registered tests cover schema closure, provenance partitions, fallback states, digest sensitivity, conflicts, freshness/as-of handling, explicit selection, and a sibling-manuscript sentinel. - **August harness-retirement removes a conflicting peer-review scaffold (#617).** The `academic-paper` peer reviewer now has one canonical report template instead of two overlapping shapes, and its review workflow no longer simulates a 15–20 minute human read, records unobservable gut reactions, or creates a second initial-impression score beside the evidence-weighted score. Strengths are evidence-led with zero allowed and a checked-dimensions receipt, replacing the fixed three-item quota that could manufacture praise. The five-dimension rubric, verdict mapping, issue bands, revision loop, and all integrity/phase contracts remain intact. - **Dissent-span non-comment raw-HTML hiding is machine-rejected (#682).** `check_phase_conformance.py` now rejects raw HTML tags and delimiters inside `## Scoring Plan Dissent` as `[DISSENT-RAW-HTML]`, including script/style/template blocks, hidden-element wrappers, container-prefixed tags, declarations, processing instructions, and malformed openers. The guard is span-scoped and code-aware: inline-code mentions remain prose, fenced examples and content outside the dissent span keep their prior semantics, and comment-only cards retain the existing `[DISSENT-HIDDEN]` path. Canonical Phase 2 prompts, all five seat mirrors, the protocol, and the report template state the same grammar, so no hidden field can mint a trigger-binding exemption. - **Held-out measurement contract 1.1 adds execution and preregistration provenance (#664).** New reports now distinguish treatment/cohort arms from variant-packet arms, identify each judge's blinding dimensions, declare whether the headline is a point estimate or lower bound, and bind timing/sequence claims to a hashed execution manifest with call IDs and timestamps. Frozen plans and adjudication rubrics are hash-pinned before outputs, adjudication rows record resolution direction/rule, flags-only lower bounds cannot be promoted to two-sided claims, and preregistration amendments are append-ordered. Version 1.0 is accepted only for the exact path and hash of its frozen historical row; a canonical 1.1 template, execution-manifest schema/fixture, I13-I15 + R4-R5 validation, and CI drift guards define new work. - **Human-subjects output is explicitly institution-owned (#665).** Research planning now reports candidate pathways plus independent submission-readiness and authorization states, never an IRB determination or fabricated institutional timeline; the ethics reviewer scopes its verdict to AI-assisted research integrity and carries a fixed institution-owned footer. The ethics checklist/decision tree are labeled navigation aids pending institutional determination, with a CI-locked output contract and mutation tests while the pre-existing Ethics Decision Log stays byte-stable. - **Product positioning states the empirical-verification limit and rejects simulated review boards (#657, #671).** `POSITIONING.md` now distinguishes manuscript/process/package conformance from validation of actual execution, raw-data truth, or reproducibility, and all maintained README languages point to that boundary. It also rejects a simulated human-subjects committee as a statutory composition/accountability claim, leaving only non-authoritative question generation as a possible future RFC surface. - **Canonical bibliographic-integrity signal carrier (#678).** A closed versioned schema and additive literature-corpus array separate resolver/list observations, heuristic indicators, and check-execution attestations; carry evidence/provenance, timestamp/version/hash/staleness, affected citations/claims, explicit policy ownership, and unresolved degradation states; and round-trip #651 retraction plus #660 tortured-phrase fixtures without undeclared fields. A deterministic legacy projection covers `contamination_signals`, omission metadata, and `retraction_check` without treating an execution attestation as a clean result. New signals compose in one `provenance_summary.md` section and mint no ref-marker token, preserving the existing single `CONTAMINATED-*` advisory slot and terminal policy until a separately versioned cutover. Formatter/finalizer sync checks, migration tests, and CI enforce the boundary. - **Citation gold-set documentation separates coverage from representativeness (#674).** The missing genuine all-resolver-unmatched tuple is now consistently described as #250's accepted corpus-representativeness limit with no current action item. Static authored-outcome fixtures are no longer claimed to exercise title matching; direct client HTTP-path regression coverage is attributed to the #431 fix shipped in #432. No tuple, reducer, resolver, or policy behavior changed. - **Pi installation guidance is visible from every maintained README (PR #642).** The post-merge community wrapper is installed with `pi install git:github.com/Imbad0202/academic-research-skills`; each README points to `pi/README.md`, which keeps the original ARS content authoritative and documents Pi-specific orchestration and hook limitations. - **Held-out measurement contract: multi-judge, pre-registered-adjudication report envelope (#654).** New `evals/heldout/MEASUREMENT_CONTRACT.md` + `evals/heldout/measurement_report.schema.json` + authoritative `evals/heldout/suite_registry.json` define the opt-in `heldout-measurement/1.0` envelope for scored held-out rows — the premise correction from #654 applies: `scripts/run_evals.py` still discovers `evals/gold/` only, and the contract is a report schema + per-suite adoption notes, never a runner. Scored decision-relevant runs disclose their judges fully (exact model id, model family, prompt ref, evidence provided, judging budget per judge; different-model-families and preregistered maintainer adjudication labeled as ARS choices layered on Ren et al. arXiv:2607.13104 §8.1.2), enumerate cross-judge divergent items individually, replicate >= 2 per item, and publish raw pre-adjudication numbers alongside adjudicated ones against a rubric committed and hashed before any judge output exists, with the adjudicator's blinding dimensions enumerated and every override citing its rubric criterion. The contract's headline rules are structural, not self-declared: the judge minimum is **derived** from `(decision_relevant, suite_class)` — >= 2 judges from >= 2 distinct model families, with no author-writable `minimum_for_scored` field to relax it; a report's `suite_class` is verified against the suite registry so mislabeling cannot shed clauses; the agreement rate is recomputed from the per-judge rows rather than trusted; the honesty attestations (`rubric_precommitted`, `raw_published`, `raw_outputs.retained`) are schema `const: true`; and report loading rejects duplicate JSON keys so a second `"raw_published"` cannot shadow the first. `judge_plan.exception: "legacy_comparability"` encodes the #652 pattern (a re-measurement keeps the original judge as its comparability row; new judges report separately); `mechanical_suite` is legal only on `mechanical_match` suites; E4-shaped suites adopt at the cohort roll-up level (a summary row referencing `runs/` records — per-run shapes emitted by `dispatch_e4_panel.py` unchanged). Dual-track pre-ship review (security opus subagent + codex gpt-5.6-sol xhigh) drove a hardening round: judge identity hygiene (duplicate judge ids, one model under two family labels, duplicated (model, prompt) configurations all rejected), NFKC + format-character-stripped item-id folding (the #524 fold-before-compare lesson) so zero-width re-spellings cannot split or hide divergence, type-aware payload comparison (JSON `true` != `1`), declared-divergence set equality with per-item resolution binding (every divergent item needs its override or note), blocked-run coverage for partial judge failure on decision-relevant runs, per-row evidence minimums, real-calendar-date validation, and reference resolution R1-R3 (rubric file must exist and match its sha256, raw-output paths must exist, the suite commit must be a real object) plus location binding L1 (a row filed under a suite directory must declare that suite). Enforcement: `scripts/check_heldout_measurement_report.py` (JSON Schema branches B1-B4 + invariants I1-I12 + R1-R3 + L1, 109 mutation tests incl. CLI-level scan tests via the unified manifest) wired into spec-consistency CI in `--all` mode, which discovers by the marker key across `evals/heldout/` (case-insensitive extensions, directory symlinks followed with a cycle guard) — filename conventions do not gate validation; marked files that fail strict parsing (duplicate keys, non-finite numbers), near-miss or non-string marker values, escaped-key spellings, non-UTF-8 bytes, and out-of-repo symlink targets all fail loudly. A second verification round closed the reviewers' remaining must-fix items (depth-1/symlink location bypasses, escaped-marker discovery regression, token-boundary blocked-run matching, folded identity fields, 40-hex commit pinning, raw-path suite scoping), and MEASUREMENT_CONTRACT.md gained a Known-residue section naming what stays human-audited (verdict semantics, decision_relevant self-declaration, aliased judge identities, pre-registration history, override transcription). The five suite READMEs gain adoption notes; legacy rows (2026-07-11, 2026-07-22, the E4 cohorts) are never retrofitted; `shared/benchmark_report_pattern.md` is cross-referenced as the sibling envelope for ARS-versus-human benchmark reports so a third vocabulary never grows. - **Dissent-span comment hiding closed by an output-grammar rule (#613; PR #650).** The two CommonMark comment shapes the #612 block-visibility model deliberately did not read — a ``/`` close by reusing the opener's dashes — the inline scanner now resumes at +2 like the block scanner, so an empty comment no longer false-aborts rendered fields) and a genuine mid-line close-and-reopen pin. - **Standing preferences via CLAUDE.md documented as the supported mechanism (#634).** `docs/SETUP.md` + `docs/SETUP.zh-TW.md` gain a "Standing preferences via CLAUDE.md" section: a copy-pasteable preferences template (citation style, preprint exclusion, journal-tier preference, OA preference), the two honest limitations (journal tiers are model judgment — no quartile data exists in any of the four lookup indexes; there is intentionally no output-directory setting, per the v3.6.8 R4-003 single-discovery-anchor decision), the boundary against `search_strategy` (the ambient block sets defaults; the Annotated Bibliography's Schema 2 `search_strategy` is the auditable record for anything that changes what a systematic review may include), and the recorded re-evaluation triggers. Converts the #632 discussion answer into durable documentation and states "no config layer" as a design position rather than a gap; the env-flags table cross-references it as behavior-toggles vs content-preferences. Docs only — no agent, schema, or behavior change. - **#610 §8 step-5 script adapter: isolated numeric input surface + deterministic receipt calculator (#610).** The methodology seat's arithmetic moves from the model to the harness, with the receipt seam unchanged. A new paper-visible **extraction call** (dispatched between Phase 1 and Phase 2, methodology seat only) has the seat transcribe manuscript-reported values into a closed typed `## Recompute Extraction` grammar — one `### RR` per arithmetic claim with per-procedure numeric fields and explicit `unavailable` sentinels, or the `no_recomputable_statistics:` attestation; no arithmetic, no judgment. A new deterministic, stdlib-only calculator (`scripts/recompute_receipts.py`) computes every receipt from the extraction alone (it never sees the manuscript, the manifests, or the repo): GRIM/GRIMMER/n_from_df on exact rational arithmetic (`fractions.Fraction`; SD reachability decided on SD² so no square root enters a verdict; GRIMMER by budget-bounded exhaustive enumeration that refuses with `reachability_not_completed` rather than approximating), t/F/χ²/z tails via stdlib regularized incomplete beta/gamma with a 1e-9 boundary guard that resolves endpoint grazes to `rounding_boundary_ambiguous` / `inequality_unresolvable` rather than a float coin-flip, §5.1's both-tails display generated structurally under an unstated tail (F/χ² normalized to their family `upper-tail`), and every `unavailable` sentinel mapped to its closed `not_computable` reason deterministically; `not_applicable` stays grammar-legal but is never script-emitted. The seat's Phase 2 receives the computed receipts as a `` block and must reproduce them **verbatim**, adding only the `finding_ref:` linkage lines on mismatch receipts. `check_phase_conformance.py` gains the `--extraction` stage (machine-lines-only section, RR field matrix, anchor grammar; the calculator's own parser is the grammar authority so gate and consumer cannot disagree) and the `--injected-receipts` identity gate (`[RECEIPT-IDENTITY]`: the card's receipt section, read fence-transparently, must equal the injected bytes modulo canonical `finding_ref:` lines — a paraphrased derivation that still passes the receipt grammar is exactly what this gate exists to catch). `dispatch_e4_panel.py` dispatches the three-call methodology shape under successor evidence contract `reviewer-e4/2026-08-06`: the extraction call carries one structural retry of the Phase 1 evidence class recorded under its own `extraction_retries` list; a calculator refusal of a gate-passed extraction is a panel-fatal harness infra fault (`[RECOMPUTE-CALCULATOR]`, exit-2 class, stderr preserved in `methodology.recompute.log`), never a conformance abort; `methodology.receipts.md` joins the bundle. Prompt-side, the canonical source gains a `methodology-extraction` fragment (new dispatcher-visible `### Phase 2E` mirror in the methodology agent, sync-linted and content-locked like Phase 1/Phase 2) and the `methodology-receipt` fragment gains the injected-mode conditional — **no `` block, no behavior change**: self-compute #644 behavior is the explicit degradation path for every orchestrator that cannot run scripts, so nothing degrades for Bash-denied environments. Design doc `docs/design/2026-08-06-610-step5-script-adapter-spec.md` records the approved architecture and the measurement design: a separately authorized `script_adapter` smoke + 2 × 3 fleet from a frozen post-merge checkout, compared against the 2026-08-05/06 post row (recompute 4/4 with arithmetic correctness now structural; recall/clean/severity/abort gates per §7.1), with per-RR extraction fidelity and the calculator's `not_computable` reason distribution as new report rows. Dual-track pre-ship review (security opus subagent + codex gpt-5.6-sol xhigh) closed 2+4 P1 and 5+5 P2 findings in round 1 — extraction-call injection boundary (`DATA_BOUNDARY` + fragment paragraph), shared numeric-domain caps with verified-convergence p-magnitude bounds, GRIM window / GRIMMER element budgets and a calculator subprocess timeout, analytic-complement tails (a near-zero t/F no longer collapses to p = 1), rule-true `rounding_interval:` rendering, negative-SD mismatch, n = 1 population SD, family-consistent df shapes at the gate, CommonMark-stateful extraction fences, machine-markup/CR bans in pass-through values, plain-only `finding_ref:` under identity, LF-pinned write-once receipts, and contract witnesses (`RECEIPT-IDENTITY: PASS` gate-log line + resume-time deterministic receipt re-derivation) — design doc §7 carries the closure list, including the round-2 verification closures (10-decimal domain with an out-of-domain-no-RR fragment rule, near-1 display precision, parity-true half-even and zero-covering truncation intervals, GRIMMER iteration budget, argv-free timeout log, corner-swept convergence domain, content-line identity as the stated contract). 78 calculator tests (spec §5 worked cases pinned, published critical values, cross-family identities, brute-force enumeration cross-check, determinism, gate-grammar compatibility of every emitted line, review-hardening regressions), 22 new conformance-gate tests, 7 new dispatch tests (three-call order, retry evidence, panel-shrink, infra-fault classification, prompt injection, tamper detection, end-to-end mismatch receipts), 3 new resume tests, 3 new sync mutation tests. - **2026-08-05/06 #610 post cohort promoted and adjudicated (#610).** Byte-for-byte promotion of the seven-panel post fleet at the frozen #644 receipt-grammar SHA `305884b` under the #608 harness (`claude-opus-5` @ xhigh, isolated per-seat two-phase `claude -p --bare` dispatch, `--tools ""`): six score-eligible records (smoke ms00 r99 + MS00 r2 + MS01 r1/r2 + MS02 r1/r2) in `runs/`, one blocked panel (MS00 r1, `[TRANSPORT: exit 1]` at synthesis dispatch with all five seats complete and the severed partial preserved — a transport failure, not a conformance abort) recorded — not replaced — in `runs/blocked/`; provenance valid on all seven, `check_e4_promotion.py` PASS on every stem. Maintainer-adjudicated 2026-08-06 against the held-out manifests outside any session. **The #610 recompute-specific gate passes in full: receipt-backed strict recompute recall 4/4 `VERIFIED` in both score-eligible MS01 replicates under the mandatory formal `AR` grammar (baseline 3/4 / 2/4 on prose receipts), receipt coverage and arithmetic correctness 1.00 per replicate — the two adjudicated baseline drop points (SD-03's both-tails display; SD-01's rounding-interval reachability) are now carried in the raw output, and SD-11 carries its required formal reachability fields as well.** Strict recall stays 1.00 on both fixtures (critical band 1.00), class-wide statistical recall 5/5, clean-control false findings 0 (0 numeric / 0 narrative on both eligible clean panels), and the conformance-abort rate is 0/6 with zero retries fleet-wide (baseline 2/6) — the mandatory Phase 2 grammar cost no abort rate. Severity agreement is 0.607 against the baseline 0.672 and FAILS as frozen-measured, so **the #610 delta as a whole does not pass the full pre-existing gate set**; the result is recorded with its decomposition per the #581 precedent. An adjudication-consistency correction is part of this record: first-pass post scoring gave MS01 SD-01 a 1.0 on the same highest-tag-Major evidence the baseline had scored 0.5; the pre-merge cross-review caught the inconsistency and SD-01 was re-scored 0.5 in both post replicates before promotion. Decomposition: MS01 drops 0.705 → 0.659 (its movement is SD-08 and SD-09, one band in one replicate each) and MS02 drops 0.639 → 0.556 (the MS02 movement is one band each on its SD-03/SD-04/SD-08/SD-09, SD-07 improving one band); the MS02 receipts yield no computable mismatch signal (predominantly `not_computable`), so no direct receipt-verdict linkage is evident, but causal attribution for the severity drop remains unresolved — consistent with the open #574 A3/B1 band-scatter residual. The README run-history table gains the post row with the full gate verdicts. - **#610 §7 step-3 behavior change: formal `AR` arithmetic-receipt grammar for the methodology seat (#610).** The methodology reviewer's sprint Phase 2 card now carries a mandatory `## Arithmetic Receipts` section — one `### AR` receipt per attempted recomputation under the four bounded procedures (`p_from_test_statistic` / `grim` / `grimmer` / `n_from_df`), or a single `no_recomputable_statistics:` attestation when nothing is recomputable — a mandatory declaration with adjudicated honesty, never machine-checked triggering: the checker verifies only that the declaration exists, annotates the attestation path with a `[RECEIPT-ATTESTATION: declaration-only …]` advisory line, and a false attestation surfaces at adjudication as `MISSED`. Each receipt carries the spec §4 logical fields as canonical `key: value` machine lines with closed `procedure_id` / `status` / 16-reason `not_computable_reason` enums, and the grammar forces the two adjudicated baseline drop points as dedicated conditional fields: an unstated-tail p receipt claiming a verdict must display BOTH labeled tail values, each label sharing its own `;`-segment with its derived number (either order), hyphen/Unicode label variants folded with letter boundaries (the SD-03 two-tailed-only failure), and a GRIM/GRIMMER verdict requires explicit `rounding_interval:` + `nearest_achievable:` lines (the SD-01 r3 missing reachability check); `n_from_df` verdicts must name their `df_identity:`. Every `mismatch` links to exactly one `W` weakness that carries a `**Arithmetic Receipt**: AR` back-reference; `consistent`/`not_computable`/`not_applicable` receipts can never mint a finding. Prompt-side, the receipt block leads with an epistemic-status declaration — the layer does not replace the human reviewer; conformance proves auditability, never arithmetic truth, and correctness stays with maintainer adjudication (`VERIFIED`/`CLAIM_ONLY`/`MISCOMPUTED`/`MISSED`). Mechanically: the canonical prompt source gains a `methodology-receipt` fragment that `check_reviewer_sprint_prompt_sync.py` splices into the shared scoring fragment at the terminal-preflight anchor (shared text exists once; the composed methodology mirror is byte-pinned and the splice-anchor bytes are part of the canonical content-lock digest; 7 new sync mutation tests); `check_phase_conformance.py` gains the methodology-only `[RECEIPT-MISSING/-GRAMMAR/-TAILS/-LINKAGE]` gate plus `[RECEIPT-SECTION-FORBIDDEN]` on every other seat (140 new mutation tests) — the receipt section is required to be the card's final H2 (a 1-3-space-indented `##` counts as a rendered heading for that rule) and is read fence-transparently in display form (#637/#609 discipline: fenced receipt content is read, an indented fence dedented as CommonMark renders it, and a fenced AR block cannot hide beside an unfenced attestation); the section is a comment-free zone (any unfenced HTML comment markup aborts — a paragraph-inline `prose ` satisfying the paragraph floor — is declined under the declared closed-separator-list bound: recognizing comment openers behind arbitrary CommonMark container prefixes is the renderer-rewrite slope both this round and #609 exist to refuse, and §4's TIED judgment stays with the seat's preflight. A forty-seventh round — codex r46's successor — admitted lone list markers (`-`/`*`/`+` with no item text) into the closed separator list (zero parse slope, zero false-abort surface — a marker WITH text still counts), and honored the record contract's MUST at emission: a terminal abort whose named artifact fails to resolve is rewritten once from its own diagnostic (the bytes ARE the diagnostic, so equality holds by construction) before the downgrade fires, and the README now states plainly that the downgrade is the terminal fallback for any unresolvable named location, not an attestation that a retry occurred. A forty-eighth round — codex r47's successor — closed the zero-content axis with an explicit termination bound written into the counter itself: variants beyond the closed list (container-prefixed comments, malformed comments like ``, entity/whitespace tricks) are declared out of scope — the variant space is unbounded, the observed base rate in committed panels is zero, and §4's substantive judgment sits with the seat's preflight, not this floor. 259 new cases (239 harness + 20 conformance) run the whole contract on a scripted transport with no model call, reusing the two checkers' own valid fixtures so a card that stops satisfying a real gate stops satisfying the harness tests too. Measurement-side only: no reviewer prompt, contract, template, or fixture changed. - **An empty-but-present dissent section no longer destroys the panel through an unretryable Phase 2 abort (#609).** `scripts/check_phase_conformance.py` treated a `## Scoring Plan Dissent` heading carrying no canonical `dimension_id:` line as a hard conformance failure; because Phase 2 permits no retry, that seat's abort took the DA and the synthesizer with it. On the 2026-07-27 Spec-A fleet it fired on 1 of 2 launched panels, an observed conformance-abort rate of 0.50 against the Spec-A diagnostic expectation of approximately zero, so every measurement of the review stage ran through a gate whose completion rate was bounded by how often four or five seats all avoided one formatting slip. The prompt layer was not the cause and was not changed again: both prompt-side preflight rounds (#591, #592) were already ancestors of the suite root the fleet ran on, every seat agent states the omit-the-whole-section rule twice, and the observed Perspective card restated the rule in its own output and still left the heading standing. A section that spells **no dissent field at all** is now read exactly as an absent section — both inputs mean *no dissent* — and the run record carries the visible `[DISSENT-EMPTY-SECTION: …]` diagnostic, including the section's non-blank line count, so the occurrence stays auditable rather than silently absorbed. The tolerance is keyed on field shape, not on canonical spelling, and the shape test is decoration-agnostic by construction: a line is a *claimed* dissent when the letters preceding its first colon spell exactly a field name, whatever non-letters surround them (`NFKC`-folded first, so a fullwidth re-spelling cannot present itself as prose), and a claimed dissent aborts unless it is canonical. Enumerating Markdown wrappers instead would leave the next unenumerated one reading as prose — successive cross-model review rounds against a character-class version produced `- dimension_id:`, then `- [ ] dimension_id:` and `| dimension_id: |`, each absorbed as no-dissent; the inverted rule covers those plus link labels, HTML comments, indentation, and hyphen/space spellings without naming any of them. Letters are collected with `str.isalpha`, so CJK prose that happens to name a field keeps its own letters and stays prose instead of collapsing onto the field name and aborting a panel it should tolerate (zh-TW, ja, and ko prose shapes are pinned); markup spans carrying letters of their own — HTML tags, reference targets, task-list markers, and link destinations up to one nesting level deep — are dropped before the colon is located rather than after, so an absolute link target's own `https:` cannot win the partition, and decoration beyond those (a deeper nested destination, a quoted HTML attribute containing `>`, an entity-encoded colon, exotic custom syntax) is deliberately out of scope, with a test pinning that declared limit so it cannot move by accident, because absorbing it costs one advisory-flagged record while a broader rule costs the false aborts this tolerance exists to remove. The scan runs on the section's **raw span**, not on the sanitized body, so a field the sanitizers delete (fenced block, HTML comment) or relocate (a field spelled as its own `## dimension_id: D1` heading, which `split_sections` moves out of the body entirely) cannot reach the tolerance branch as an empty section; a field line the canonical parse never saw is a dissent the seat cannot be credited with, so it fails whether or not it is canonically spelled, counted by occurrence rather than matched by value so a hidden copy of a canonical field cannot ride in on its twin's identity. That scan also closes a **pre-existing** hole it sits on top of: because `strip_fences` leaves HTML comments in place, canonical fields written inside `` parsed as a genuine dissent on `main` and collected the trigger-binding exemption while the visible card claimed nothing, so a drifting trigger passed; a commented-out field is now struck from the canonical parse and fails as an unparsed occurrence, with the delimiter-opening confined to lines a comment actually covers so a canonical `rationale:` merely mentioning ` ` and ``, so ordering the scan does not turn those into unterminated openers that abort a card a presence test had passed; the state machine is pinned line by line in both directions. Only an *unfenced, uncommented* heading delimits the span — fence state is tracked exactly, mirroring `panel.strip_fences` under a test that pins the two to agree line-for-line across backtick, tilde, longer-closer, indented, unterminated and CRLF shapes — so a heading written inside a fence or an HTML comment cannot end the span early and hide the fields after it, while a comment opener counts only as a block opener — at most three spaces of indentation, never inside a fence — list and blockquote markers included, since CommonMark opens raw HTML for `- ` remains valid report text rather than being misclassified as a comment. Positive pre-table prose plus symmetric CRITICAL/MAJOR-tail, outer-pipe, pipe-less, list, blockquote, heading, multiline, shadow-table, fenced-payload, and HTML-comment regressions cover both checker paths. The failed root remains frozen and E4 restarts from a new root after this corrective merge. - **Sprint reviewer retry diagnostics and `text:` anchor preflight now match the deterministic gates (#574).** The first fresh E4 root after #592 stopped the fleet at the clean-control pair: one methodology Phase 1 plan still omitted a mandatory fatal trigger after its single allowed retry, because the checker diagnostic named the missing field but not dimension `D3`; the other panel reached its fifth Phase 2 seat but the DA emitted a `text:` anchor with no quoted excerpt, which the shared parser correctly rejected. Both panels aborted before synthesis, no malformed Phase 2 output was retried, quant/qual fixtures were not started, and the root remains frozen. Phase 1 field-cardinality diagnostics now name the exact dimension, and duplicate-subsection diagnostics preserve the sorted duplicate headings, so the one retry receives the protocol's required specific lint gap across that grammar family. All five delivered Phase 2 prompts, the sprint protocol, and the report template now state that every `text:` anchor contains at least one balanced quoted verbatim excerpt as well as enforcing the existing 25-word per-excerpt ceiling; lockstep lint mutations pin both detailed and terminal wording. Runtime acceptance, anchor parsing, scoring, decision, fatality, and retry semantics are unchanged; formal E4 restarts from a new root after this corrective merge. - **Sprint reviewer terminal preflight now closes the whole structural family instead of one observed token form (#574; PR #588).** The next fresh E4 root confirmed that the empty-dissent placeholder fixed by #591 no longer recurred, but stopped again before a complete panel: one emitted EIC card paraphrased its D6 block trigger instead of copying a Phase 1 substring and failed `[TRIGGER-DRIFT]`; the other EIC paper-visible call exited with no output, while one paper-blind plan still needed the allowed retry after emitting a forbidden D5 fatal sentinel. No malformed Phase 2 card was retried and the root remains frozen. The five delivered Phase 1 prompts now end with a complete structural audit over required section order and paraphrase cardinality, exact headings and field multiplicity, fatal-key cardinality, forbidden paper-visible output, acknowledgement placement, and manuscript blindness. The five delivered Phase 2 prompts now end with one closed-family audit over required/retired sections, dissent cardinality, role/dimension and abstention shape, character-for-character trigger binding, fatal/block-class scope, scoring-seat findings and receipts, exact DA table grammar and dense IDs, typed anchors, and B1 bands; the protocol mirrors both and lint relocation/mutation checks pin terminal delivery. This changes no runtime checker, scoring, decision, severity, dissent, fatality, or retry semantics; E4 restarts from a new root after merge. - **Sprint reviewer terminal preflights now make omission rules executable at generation time (#574).** The first fresh formal E4 root after #590 aborted both clean-control panels before synthesis: two independent paper-visible cards emitted an empty `## Scoring Plan Dissent` heading with an `omitted` placeholder despite the delivered omission rule, and one paper-blind EIC plan first emitted a forbidden non-mandatory fatal sentinel before the one allowed Phase 1 retry corrected it. The deterministic gates rejected both malformed Phase 2 cards, no Phase 2 call was retried, and the failed root remains frozen as operational evidence. All five delivered Phase 1 system prompts now require a terminal mandatory/non-mandatory fatal-key count, while all five delivered Phase 2 prompts require a terminal heading/content check that deletes empty dissent placeholders; the sprint protocol mirrors both and role-scoped lint mutations pin every delivered surface. No checker, scoring, decision, severity, dissent, or fatality semantic changes; formal E4 restarts from another fresh root after this corrective merge. - **Sprint reviewer live grammar instructions now close the E4 delivery gaps exposed after #589 (#574).** The first post-merge isolated Opus 5 pre-measurement attempts aborted five of seven panels: one Phase 1 reviewer exhausted its retry after emitting a forbidden `what_triggers_fatal: NOT_APPLICABLE` line on a non-mandatory dimension, while four paper-visible cards independently failed on a duplicate report-level `contract_role`, an empty `## Scoring Plan Dissent` section, `Severity: Strength` under strength headings, or an over-25-word `text:` anchor excerpt. The deterministic checkers rejected every malformed card as designed, and no Phase 2 output was retried. All five delivered Phase 1/Phase 2 system-prompt subsections now spell out the corresponding omission/placement rules, exact contract dimension names, weakness-only Severity, and the pre-output quote-length check; the sprint protocol and report template mirror them, while role-scoped lint witnesses and mutation tests pin the lockstep surfaces. This clarification changes no checker, decision, scoring, severity, or anchor semantics; E4 restarts from a fresh output root after the corrective merge. - **Sprint reviewer typed-anchor grammar made self-contained at the delivered Phase 2 boundary (#574).** E4 preflight exposed that the checker correctly required `: `, while the isolated sprint call receives only each agent's `### Phase 2 — Paper-visible review` subsection and that delivered text described wrapping variants without spelling out the colon grammar. All five delivered reviewer subsections now state the exact value grammar, demonstrate whole-value wrapping, reject both observed type-only wrapping forms, require matched and unpadded outer wrappers plus balanced straight/curly quote pairs, and define the executable `absence: — expected ; checked ` subgrammar with each reserved separator appearing exactly once. The peer-review template and sprint protocol mirror the rule; template replacement instructions sit on a separate non-value line, while the shared phase/synthesis validator uses total deterministic delimiter parsing and a type-aware quote stack so balanced nested quotations remain valid and rejects reversed or backtracking separators, padded or unbalanced wrapper debris, crossed quote pairs, and incomplete absence anchors without mistaking balanced locator content such as `Eq. [3]` or inline code for an outer wrapper. `check_role_scoped_contract.py` mutation-tests every seat, both mirrors, all three shipped examples, the template's ordered canonical text/table/absence examples, every nested quote pair, square-bracket ordering, exact wrapper assignments, and each executable parser witness. - **Reviewer contradiction batch: C1/C2/C4/C6 + manuscript data-fencing + panel-reference no-invention rule + Top Blocking Issues section + overlap-prohibition removal (#574 PR 1).** Seven prompt-level defects from the #574 audit, none changing the review's behavioral direction (the behavior batch — quota removal, typed anchors, severity transport — waits on the E4 baseline). (C1) `eic_agent` frontmatter claimed the EIC "delivers the final editorial decision" while the body forbids exactly that — the description now names the review-card role and the synthesizer's decision ownership. (C2) The "Recommendation to Peer Reviewers" output field is retired: reviewers run independently in parallel (Iron Rule #2), so no channel exists to deliver it — dead output at best, an independence leak at worst; the SKILL.md flow line "Sets the review tone" goes with it. (C4) The Roadmap's Priority-2 rule cited `[CONSENSUS-2]`, a label the same file's taxonomy explicitly says does not exist — now "corroborated findings (`agree = 2, conflict = 0`)". (C6) The EIC's "first impression score (1-10)" fed no output field and is retired. (A6) All five reviewer agents now receive the manuscript wrapped in a `...` data delimiter with an explicit data-not-instructions rule (SKILL.md Iron Rule #7 operationalized at the call boundary; injection attempts are themselves a reportable weakness); `sprint_contract_protocol.md` §2 step 4 specifies the delivery side in lockstep, and the re-review protocol marks both the revised manuscript and the Response to Reviewers as untrusted author-authored inputs. (A5) Panel-suggested references gain the no-invention rule in `domain_reviewer` + `perspective_reviewer`: the v3.11 citation gate covers the author's citations, not the panel's suggestions, so recommendations must be attestable or carry `[UNVERIFIED]` as search leads; the perspective reviewer's "3-5 references" quota is removed (zero allowed). (E7) The Editorial Decision Letter gains a mandatory "Top Blocking Issues (0–3, ranked)" section — each row with source reviewer(s), evidence anchor, and resolving roadmap item; zero rows is valid for a genuine Accept (template + synthesizer letter format in lockstep, replacing the synthesizer's unanchored "Summary of Key Issues"). (P0-3) The DA's "No repeating other reviewers" discipline — unexecutable under Iron Rule #2 blindness and a suppression incentive against legitimate corroboration — is reframed: report what you find, deduplication is synthesis work. - **Re-review plumbing: yardstick continuity + Stage 4→3' handoff completeness + apply-report output binding (#574/#576 pre-work).** Three bug-class gaps surfaced by the 2026-07-24 reviewer-stage audit, fixed ahead of the #576 contract design because each is wrong today independent of it. (1) **Yardstick continuity**: re-review re-ran `field_analyst_agent` over the *revised* manuscript (`academic-paper-reviewer/SKILL.md` mode table), regenerating the reviewer configuration mid-loop so Round-2 verdicts could be judged against a different yardstick than the one that produced the Roadmap being verified. Re-review now reuses the Round-1 Reviewer Configuration Cards (new § Yardstick Continuity in `re_review_mode_protocol.md`; target-venue freeze included); the standalone-invocation fallback regenerates visibly with a `[YARDSTICK-REGENERATED: manuscript — ]` marker recorded on a new Judge Record "Reviewer configuration" line — advisory, never a block. (2) **Handoff completeness**: the orchestrator's Stage 4 → 3' transfer row omitted the Revision Roadmap and the #390 apply report even though both are declared required re-review inputs (`revision_patch_protocol.md`, `academic-paper/SKILL.md`); the row now carries both plus the Round-1 configuration cards, and the re-review protocol's own input list gains the same two entries. The #528 orchestrator content lock was re-pinned per its documented procedure. (3) **Apply-report output binding**: the apply report recorded `base_draft_hash` but no hash of the revised draft it describes, so a post-apply rewrite (finalizer pass, manual edit) could silently invalidate the untouched-block evidence while the report stayed paired with the draft. Report format 1.1 adds `output_draft_hash` (same 12-hex format, hashed over the exact written bytes); `revision_patch_protocol.md` and the re-review protocol now instruct consumers to check it before relying on the report, and the Stage 4→3' row names the check. 3 new tests (`TestReportOutputHash`) pin the binding; `check_390` / `check_268` / boundary-semantics / spec-consistency lints all green. The 8-round cross-model review trajectory (13 P1 + 8 P2 closed, final round 0 findings; `/security-review` 0 findings) drove the fixes across every operative mirror: all six Stage 4→3' authority surfaces carry the new inputs in lockstep (SKILL.md, state machine transition + artifact lineage, team collaboration, state tracker, reviewer SKILL.md, two-stage protocol), each gated on the re-review-default vs user-requested fresh-full-review split (mid-entry quick→full path) so the legitimate no-Roadmap path is never rejected; `shared/model_tiering.md`'s Stage 3' caching roster no longer re-dispatches field_analyst; Schema 6 `judge_record` gains the optional `reviewer_configuration` member (`round1_cards_reused` / verbatim `[YARDSTICK-REGENERATED ...]`); the #390 spec records format 1.1 in a §0.1 amendment; and the three registered examples execute the new contract (EIC-only verification, reused cards, apply-report retention). ## [3.19.0] - 2026-07-22 — Revision-round claim-drift guards, PDF read-integrity preflight, read-scope attestation ### Added - **PDF read-integrity preflight for locally-extracted page anchors (#512).** Closes the local-extraction-channel gap between the v3.7.3 locator-presence rules and the #182 existence gate: PDF readers silently truncate documents with malformed cross-reference tables, so a real, correctly-cited source could acquire an apparently valid `page` anchor from a truncated or mispaginated read and pass every existing gate. New `scripts/pdf_read_preflight.py` (pypdf-backed with the `verify_submission_package.py` ImportError-degradation precedent; not a "grep the first `/Count`" check — xref streams, `/Prev` chains, and object streams ride pypdf's machinery) compares three independent page-count signals — the raw root page-tree `/Count`, the script's own cycle-guarded `/Kids`-walk leaf count, and pypdf's flattened page list — and emits a JSON sidecar (`pdf_read_preflight/1`: verdict + file sha256 + the three counts + captured parser-repair warnings). `PASS` requires all three to agree with zero parser warnings; count disagreement is `FAIL` (the truncation/mispagination signal itself); everything the preflight cannot vouch for — encryption, cycles, repair chatter even with agreeing counts, missing pypdf — is `UNAVAILABLE`. Enforcement sits upstream of the writers per the issue: the three v3.7.3 emitters gain firm rule R-L3-1-D (a locally-read PDF's `page` anchor requires a `PASS` sidecar in context; otherwise `anchor:none` or an independently-visible locator + explicit warning), `claim_ref_alignment_audit_agent` Step 4 gains the precondition bound to the existing `ref_retrieval_method == manual_pdf` discriminator (sidecars join on `ref_slug`; the sha256 is confirmatory until #513 supplies an anchor-side hash field) with the `[pdf_read_integrity_unverified]` advisory rationale tag (never an UNSUPPORTED verdict on this basis alone), and the §3.6 orchestrator — the layer that CAN run Bash — runs the preflight once per locally-read corpus PDF and passes sidecars into audit and drafting context. Cross-model review rounds 1-8 (3 P1 + 2 P2, then 7, 2, 1, 2, 1, 1, 1 P1, all closed): rounds 6-8 finished the header scan's lexer fidelity (ten-digit object numbers; %-comment token separators; signed and zero-padded integer tokens — the scan now implements the full ISO 32000 separator AND numeric-token model, closing the lexical-obfuscation family structurally); round 5 extended the header scan's boundary/separator class to full ISO 32000 whitespace (NUL-preceded replacement headers are seen) and added the compressed-object variant (a direct raw replacement of an object whose active copy lives inside an object stream, appended after its container with a stale startxref, now vetoes PASS; a raw copy before the container is the legitimate superseded-into-objstm case and stays clean); round 4 fixed the object-header scan for ISO-valid bare-CR line endings (Python's multiline anchor does not treat CR as a line start, which blinded both xref-coverage checks on CR-only files); round 3 hardened the stale-startxref check against redefined-object-number variants (the newest raw copy of every directly-stored object must be the copy the active xref chain references, with a calibration guard for offset-shifted files) and switched the trailing-data predicate to ISO 32000 §7.2.2 PDF whitespace (NUL padding passes, vertical tab is data). Round 2 added the stale-startxref xref-coverage cross-check (raw object headers absent from the active xref chain veto PASS), plus a strict integer-object requirement on `/Count` (float/string coercion rejected), the FAIL-vs-UNAVAILABLE split in R-L3-1-D (positive truncation evidence refuses the page anchor; mere absence of verification — standalone dispatch, no-Python installs, unpreflighted files — is an explicit-warning advisory, never a manufactured refusal), the `pdf_preflight_verdict` key on citations handed to `retrieve_fn` (content-based passage selection BEFORE the judge reads a page-scoped passage), the orchestrator sha256 freshness re-check + cross-runtime coverage note, and the tagged-SUPPORTED row in the operational finalizer matrix. Round 1: trailing-data-after-final-`%%EOF` veto (a truncated incremental update otherwise PASSes on the older revision's agreeing counts); preflight moved to Stage 1 corpus intake, independent of the opt-in audit mode (an audit-gated preflight left default-mode runs sidecar-less at R-L3-1-D, gate-refusing valid citations); the executable-path enforcement — `run_audit_pipeline(pdf_preflight_sidecars=...)` tags rows at the Step-6 emission point after cache resolution so cache hits cannot bypass, and the finalizer surfaces `[LOW-WARN-PDF-READ-INTEGRITY-UNVERIFIED]` on SUPPORTED rows so content-based-fallback support does not render the advisory invisible; parser warnings survive early exits (appended in the capture handler's finally). 18-test synthetic-PDF suite + 8 pipeline/3 finalizer tests (no binary fixtures); `agents/` mirrors re-synced; the #528 orchestrator content lock re-pinned per its documented procedure. Provenance: mechanism observed in kengo006/alexandria; ranked P1 of three in the 2026-07-11 dual-track adoption review. Spec: `docs/design/2026-07-20-512-pdf-read-preflight-spec.md`. - **`read_scope` honest-coverage attestation on the human-read ledger + anchor-aware finalizer promotion (#513).** ARS recorded source possession, AI verification, and a binary human-read mark — but nothing recorded HOW MUCH of a source was read: a TOC-only reading promoted a citation whose `page` anchor pointed at a chapter the user never opened. `/ars-mark-read` gains an optional declaration-only attestation — `--scope {full_text,sections,abstract_only,toc_only,unknown}` + repeatable `--locator` (requires `sections`; contradictory attestations are refused, not recorded) + `--note` (requires `--scope`) — written as an optional `read_scope` object on the ledger entry; absent means `unknown`, never fabricated or backfilled, and attestation args are rejected with `--unmark`. Placement honors the v3.6.8 firm rule the dual-track review verified: corpus entries are adapter-owned and never carry human-read state, so the attestation lives on the user-owned `_human_read_log.yaml`, now covered by a sidecar schema (`shared/contracts/passport/human_read_log.schema.json`, `version_records` precedent, registered in the contracts README; test-time validation only — the CLI stays dependency-light). The consumer ships in the same PR (an attestation with no consumer is dead metadata): the Cite-Time Provenance Finalizer's LOW-WARN → ok promotion becomes read-scope-aware — absent/`unknown`/`full_text` promote (legacy marks impose no migration); `abstract_only`/`toc_only` resolve to the new draft-visible acknowledged-partial status `LOW-WARN-PARTIAL-COVERAGE` with an explicit coverage note; `sections` promotes only when a `page`/`section`/`paragraph` anchor falls unambiguously within a declared locator, and `quote` anchors promote only under full coverage. The governing attestation rides the settled latest-timestamped-event-wins precedence (a latest rescind always wins), the idempotency evidence enumeration names the attestation explicitly, and `LOW-WARN-PARTIAL-COVERAGE` keeps LOW-WARN's severity everywhere (contamination suffixes attach identically; the formatter passes it as an acknowledged variant with the note surfaced — distinguishable from an unacknowledged LOW-WARN at the terminal gate, never refused). CLI presence checks use `is not None` (an explicit empty string is invalid, not absent) and locator/note length bounds are enforced at write time in lockstep with the schema, so the CLI can never produce a ledger the schema rejects; two cross-model review rounds (5 P1, then 3 P1, all closed) drove the hardening: round 2 added `LOW-WARN-PARTIAL-COVERAGE` to every base-status enumeration (`scripts/check_v3_10_policy.py` `_BASE_STATUS`, the hard-gate handoff, the contamination/policy-stamp sections — partial markers get suffixes and `policy_hash` stamps like any LOW-WARN and are never refused by the freshness gate), put the citation's own anchor into the idempotency/revision-loop evidence enumeration (a revision moving an anchor out of declared coverage re-resolves the marker even with corpus and ledger unchanged), and mirrored the locators-require-sections rule as an if/then conditional in the sidecar schema so audit-time validation matches the writer contract. `commands/ars-mark-read.md` + formatter LOW-WARN remediation updated in lockstep; #528 orchestrator content lock re-pinned. Provenance: mechanism observed in kengo006/alexandria; ranked P2 of three in the 2026-07-11 dual-track adoption review. Spec: `docs/design/2026-07-20-513-read-scope-attestation-spec.md`. - **Revision-round claim-drift guards: claim-strength ladder + deterministic token conservation (#569 / #570).** Stands an advisory watch over the epistemic and token halves of the #390 honest-claim residual — the patch mechanism confines silent-distortion exposure to touched blocks but never checks a touched block's interior (DELEGATE-52, arXiv:2604.15597, the same round-trip subtle-modification finding #390 already anchors); a guard now watches each half, and whether the guards reduce the measured drift is a separate re-measurement (#652), not a claim this entry makes. Two complementary layers, both advisory-first, mechanism shape borrowed from [Yila-AI/sci-ssci-skills](https://github.com/Yila-AI/sci-ssci-skills) by @MissOrangePeel (its `sci-ssci-polishing` claim-strength ladder + `check_invariants.py`). (1) **Claim-strength ladder** (`shared/references/claim_strength_ladder.md`): an ordered epistemic scale (`is consistent with < is associated with < predicts < contributes to < affects/leads to < causes`) whose invariant is "no silent move, either direction, without an authorizing roadmap item" — wired into `draft_writer_agent` revision mode (a patch op changing a claim's rung must name the item that authorizes the strength change, not merely an item touching the block) and into a new advisory Phase E6 in `claim_verification_protocol.md` (revision-round-only `STRENGTH-DRIFTED` → `ADV-E6-` rows, mirroring the ADV-E4/E5 pattern, never gating). (2) **Deterministic token conservation** (`scripts/check_revision_token_conservation.py`): a stdlib checker over numeric tokens, ``/`` markers, bracketed/author-year citations, and protected terms, in pair mode or per-op against a #390 patch, with Unicode fold-before-split (the #524 ordering lesson) and per-op `roadmap_item_ids` attribution; framed necessary-but-not-sufficient, mirroring the v3.11 deterministic-gate/LLM-semantic split. Rather than cite an earlier-generation-model study as motivation (issue #569 precondition), a held-out set (`evals/heldout/revision_claim_drift/`, codex-generated scenarios, cross-model + maintainer-adjudicated judging) measured the CURRENT frontier model's baseline first: 2/8 = 25% claim-strength/hedge drift under hedge-drop and null-reframe pressure, 0/8 unauthorized numeric/citation drift (the one authorized change correctly isolated), 2/2 controls clean — the evidence that the ladder earns its place rather than closing as documented-negative-scope. 31 mutation tests, registered in the CI pytest manifest. Dual-track pre-ship review (`/security-review` + `/codex review` xhigh): security 0 findings (read-only stdlib lint, empty attack surface); codex closed 6 (4 P1 + 2 P2) — the checker's number regex now keeps signs inside brackets/after commas and scientific exponents (`6.02e23`→`6.02e24` was a false-negative), narrative author-year citations now carry the author (`Smith (2020)`→`Jones (2020)` on the same ref slug was a false-negative), E6 was rewired to consume the durable per-round revision-patch sidecars via a declared Revision-Evidence Bundle (its original inputs were never transferred across the Stage 4→4.5 handoff) and to audit every round on a multi-round path (not only the last pair), step 3a's attribution semantics corrected (the checker emits the deterministic signal; E6 supplies the authorization verdict), and E6/ADV-REV report tables + checkpoint options defined. ### Fixed - **SETUP Method 4a description-length figure de-drifted (#564).** `docs/SETUP.md` / `docs/SETUP.zh-TW.md` stated the four skill `description` fields "currently sit in the 440-842 range"; the actual lengths have since grown to 566-986 characters. Replaced the hardcoded range with the durable comparative statement (each exceeds claude.ai's 200-character upload cap while staying under Claude Code's 1,024-character allowance), so the sentence cannot silently drift again as descriptions evolve. Rationale for not trimming the descriptions themselves is unchanged. - **Write-scope guard launcher: watchdog-path stall removed; #545 flaky launcher tests fixed (#545).** Two-layer root cause. (1) Production: on hosts without a `timeout` binary (stock macOS), `hooks/run_guard.sh`'s watchdog subshell — and the `sleep` it forks — inherited the caller's `$(...)` capture pipe; after the parent killed the subshell, the orphaned `sleep` kept the write end open, so every healthy probe/guard run blocked for the full wall-clock bound (~2× bound per PreToolUse call; measured 6.1 s at the default bound). The watchdog now redirects to `/dev/null`, cutting the healthy watchdog path to ~0.15 s with decisions unchanged. (2) Tests: the harness pinned `ARS_PROBE_BOUND=1`, leaving so little margin that loaded machines pushed real interpreter + guard runs past the bound into the launcher's designed pass-through degradation — which every deny-expecting test read as a rotating, load-dependent failure. The harness default is now a generous 30 s (pure margin; only the hanging-candidate tests wait a bound out, and they override to 5 s), and a new `LauncherSlowInterpreterTest` pins the regression (a python3 that takes ~1.5 s per invocation must still forward the real deny). Suite: 21 tests / 70-80 s → 22 tests / ~23 s, green under 8-way CPU load. ## [3.18.0] - 2026-07-18 — Self-improvement survey integration: advisory quality layers, risk-stratified claim gate, cross-model reviewer & judge tracks, cache re-validation ### Added - **Re-review judge independence + judge record (#539).** The Stage 3' re-review judges revisions on the same model family that drove them — an analogous correlated-judge configuration to the one §8.1.2 warns about. When cross-model verification is active (configured + consented), every Priority 1 roadmap item's addressed-ness verdict gets an independent judgment-specific cross-model pass (provider transport reused, no grounding gate, closed verdict set incl. MADE_WORSE, non-conforming → `unavailable`); results land in the R&R matrix's Cross-model column (`agree` / `diverges: ` / `unavailable` / `not_configured`) — `diverges` is a Phase 2 synthesis review trigger, never a vote. Single-family runs carry a never-omitted verbatim disclosure line. Both cases emit a Judge Record (verification judge; Round-1 panel provenance copied seat-level from the #540 provenance block — carried into Stage 3' via the Editorial Decision Letter, now named on all handoff surfaces incl. the re-hashed state-machine lock; prompt/rubric surfaces; evidence seen; judging budget separate from generation) — Schema 6 optional `judge_record`, Schema 11 optional Priority-1 row fields. External motivation: Ren et al. (2026, arXiv:2607.13104 §8.1.2). - **Cross-model reviewer track (#540).** With `ARS_CROSS_MODEL` configured, ONE existing peer-reviewer slot (Reviewer 2) runs on the cross-model family — a substrate swap inside the fixed five-seat panel, explicitly NOT the retired 6th-reviewer design (whose five counterproductive conditions all attach to an added seat). Transport follows #523 dispatching-layer ownership with a two-call sprint-contract split (paper-blind Phase 1, then Phase 2 with the paper); the slot's vote enters the panel matrix unchanged; the synthesizer computes NO cross-family aggregate (forbidden-operations list holds) — splits are visible by inspection in the matrix, and the provenance block names which seat ran on which family. Activation requires the explicit cross-model consent, not the env var alone. Without a second provider, the new Editorial Decision Letter `Review Panel Provenance` block discloses the single-family correlated-error caveat instead of silently implying independence; dispatch failure falls back with the fallback disclosed. Authority: `shared/cross_model_verification.md` § Cross-Model Reviewer Track. External motivation: Ren et al. (2026, arXiv:2607.13104 §5.2) — repeated sampling of one model may amplify a confidently-wrong conclusion; heterogeneous critique models are among the named safeguards. - **Cache-source staleness advisory + opt-in live re-validation (#541).** The citation-verification cache (v3.11 Delta 2, 90-day TTL) gains an age-based advisory layer: `VerificationCache.entry_age_days` / `stale_report` compute the oldest-live-row age per citation, and Phase A § A0.5 emits `ADV-CACHE-` advisory rows (citation, age, threshold) for cache-served verifications older than `ARS_CACHE_STALE_ADVISORY_DAYS` (default 30; `0` disables) — displayed with per-row options at the MANDATORY checkpoint via the #547 advisory-row template, never gating. The wiring is real code, not prose: `verify_citation`/`verify_passport` run cache-through by default (closing the #182 Delta-2 forward-decl — the four resolvers route via the detailed contamination-signals wrappers with interoperable cache keys), the CLI gains `--no-cache`, and `ARS_CACHE_REVALIDATE=1` re-verifies stale rows live at the gate (per-row bypass + re-population; cost scales with stale-row count). Invalidation cascade is unconditional: after any re-validation the citation's summary row regenerates and Phase E verdicts re-run at the gate (no baseline exists to diff against). Cache robustness hardened: naive timestamps read as UTC, malformed rows are misses, clock-skew clamps to 0, and the advisory flag is computed from the emitted rounded value. Summary schema gains optional `cache_age_days` + `cache_stale_advisory`; cache/gate/CLI suites extended (52 gate+cache tests green). Framing: Ren et al. (2026, arXiv:2607.13104 §6.2.3) names scheduled review-and-attenuation and the staleness failure mode; applying it to the citation cache is ARS's design inference. - **Risk-stratified Stage 2.5 claim verification (#549).** Phase E Mode 1 sampling moves from a uniform 30% random spot-check to the #518 tier logic at claim level: HIGH-IMPACT claims (headline / numerical / causal / methods-critical / disputed — same definition family as `shared/cross_model_verification.md` step 2) verified 100% uncapped, plus a 10% RANDOM sentinel of the remainder (min 3 / max 10) for unbiased drift detection, with a top-up floor preserving the pre-#549 minimum of 10. Each audited claim's tier is recorded in the Claim Registry so coverage is inspectable. Mode 2 (Stage 4.5, 100%) unchanged — the delta is WHEN consequential distortions surface: before the review stage instead of at the final-gate backstop, cutting late-rework loops. Authority `claim_verification_protocol.md` § Sampling Strategy; mirrored in `integrity_verification_agent` + `integrity_review_protocol.md`. External motivation: Ren et al. (2026, arXiv:2607.13104) §3.3 active-acquisition targeting of failure modes/verifier disagreement + §9.2 resource-aware improvement; the high-impact-first allocation is ARS's design inference from those principles. - **Search-bounded novelty claims (#548).** Absolute priority language ("the first study to...", "no prior work has...") asserts the absence of literature — no cited source can support it, so the citation gate structurally cannot catch inflation there. `draft_writer_agent` gains a `## Search-Bounded Novelty Claims (#548)` section: default emission is the bounded form ("To our knowledge, based on searches of [databases] through [date]...") filled from the Schema 2 `search_strategy` actually used, nearest prior work named instead of claiming a vacuum, the bounding qualifier marked as a protected hedge (so abstract compression cannot silently restore the absolute claim), and the absolute form emitted only on explicit user confirmation (recorded for AI disclosure). Phase E gains an E5 novelty-claim classification (authority: `claim_verification_protocol.md` § E5; mirrored in `integrity_verification_agent` + `integrity_review_protocol.md`): primacy assertions classify as `SUPPORTED_WITHIN_SEARCH` or `UNRESOLVED` against the documented search — never "globally verified" — advisory-only, never gating, per-row user decision. `failure_paths.md`'s Insufficient Novelty guidance stops advising bare "strengthen the novelty claim" and routes through the bounded form. External motivation: Ren et al. (2026, arXiv:2607.13104 §7.4) — discovery agents cannot easily verify novelty on their own and may exploit weak proxies. - **Pipeline behavior robustness held-out set, seed v0.1 (#550).** New `evals/heldout/pipeline_behavior_robustness/` — metamorphic paired cases for the runtime routing/gate layer: 8 documented-ground-truth base scenarios (explicit-trigger routing, cross-phase clarify obligation, rebuttal-audit vs revision-coach input-shape gate, rejected-mechanism refusals, Stage 4.5 IRON-RULE hold under deadline pressure) × 3 perturbation axes (terse / verbose / long-context padding), en + zh-TW pairs, invariant = same expected behavior per pair. Lives under `heldout/` (LLM-judged, no script entrypoint) with per-item `rule_anchor` citations so ground truth stays reproducible from the shipped rules; paired-consistency + long-context-degradation metrics and a fresh-session measurement protocol mirror the #505 runs. v0.2 forward note: wrapper/reorder axes + cross-model variant authoring before the set gates routing-layer changes. External motivation: Ren et al. (2026, arXiv:2607.13104 §8.2.1) — evaluate prompt-policy behavior under paraphrases, formatting shifts, and longer contexts. - **Per-sub-question scope bindings + Phase E scope-conformance advisory (#547).** RQ Brief Schema 1 (`shared/handoff_schemas.md`) gains an optional `sub_question_bindings` field — each sub-question records which parent-scope constraints (population / timeframe / geography / domain) it inherits and any user-approved deviations; absent = full-scope inheritance (pre-#547 briefs remain valid inputs; the E4 advisory then compares against the whole `scope` object — the advisory itself is new behavior). `research_question_agent` emits the bindings and never silently broadens; `structure_architect_agent` outline sections name the sub-question they serve and inherit its bindings. Phase E gains an E4 scope-conformance advisory (authority: `claim_verification_protocol.md` § E4; mirrored in `integrity_verification_agent` + `integrity_review_protocol.md`): claims whose stated scope exceeds their inherited scope surface as `SCOPE-BROADENED` rows at the Stage 2.5/4.5 checkpoints — advisory-only, never gating, per-row user decision (proceed open, or accept with justification; a requested reword is an ordinary revision instruction in the normal flow), no automatic rewriting. Closes the silent-broadening path from a narrow RQ to conclusions the evidence never covered. External motivation: Ren et al. (2026, arXiv:2607.13104 §5.1) — decomposition becomes vulnerable when sub-problems stop preserving the original task's constraints. - **SessionStart update-available reminder for plugin installs (#543 → #544).** New `scripts/ars_update_check.sh` (Bash 3.2, no jq, always-exit-0 fail-silent) compares the installed plugin version against `main`'s `.claude-plugin/plugin.json` — 24 h cache at `~/.cache/ars/update-check`, 3 s network ceiling, `ARS_UPDATE_CHECK=0` kill switch — and the SessionStart announce prepends a one-line reminder pointing at `/plugin update academic-research-skills` when behind. Motivated by #543 (reporter six minor versions behind on a long-fixed bug): third-party marketplaces default auto-update OFF and surface no behind-signal. Hermetic 15-test suite (`scripts/test_ars_update_check.py`, manifest id `544-update-reminder`). Spec: `docs/design/2026-07-18-544-update-reminder-spec.md`. - **README motivation + calibration docs: self-improvement survey anchor (#542).** The README / `README.zh-TW.md` "Why human-in-the-loop, not full automation?" sections gain Ren et al. (2026, arXiv:2607.13104, *Self-Improvements in Modern Agentic Systems: A Survey*) as a third, survey-level literature anchor — §7.4 scientific-verification limits, §5.1–§5.2 human-auditing safeguards, §2.2 EURISKO user-as-external-evaluation-signal precedent — framed explicitly as design rationale, not empirical proof. `academic-paper-reviewer/references/calibration_mode_protocol.md` § Why this mode exists cites the survey's §8.1.2 evaluator-independence + calibration-against-a-verifiable-subset safeguards as the external statement of what calibration mode instantiates. The survey's actionable deltas stay tracked as issues (#539–#541, #547–#550). External motivation: Ren et al. arXiv:2607.13104 (2026-07). ## [3.17.0] - 2026-07-16 — Pipeline boundary semantics, canonical cross-model handoff envelope, executable panel checker ### Security - **Tools allowlist for the three top-level plugin agents (#514, implemented in PR #521 by [@madtriceps](https://github.com/madtriceps)).** The three plugin-exposed agents (`synthesis_agent`, `research_architect_agent`, `report_compiler_agent`; deep-research sources + byte-identical `agents/` mirrors, six files) now declare `tools: Read, Write, Edit, Grep, Glob` in frontmatter — no Bash, no WebFetch/WebSearch — so dispatch-time capability is least-privilege even in hook-less installs, complementing the runtime Bucket A Bash deny (`scripts/ars_write_scope_guard.py`), which keys on agent name and continues unchanged. Retrospective entry: the code merged just after the v3.16.0 tag; documented here per the changelog-covers-merges gate. ### Fixed - **Blind-checkpoint transport moved to the dispatching layer (#523).** The #518 blind disagreement checkpoints told their Bucket A primary owners (`research_architect_agent`, `editorial_synthesizer_agent`) to execute the cross-model curl transport themselves — unexecutable under the runtime Bash deny and, for the architect, the #514 dispatch-time allowlist, so on every hook-active run the check at an irreversible decision silently degraded to single-model, indistinguishable from a transient API outage. New **Transport ownership (#523)** contract in `shared/cross_model_verification.md` § Blind Disagreement Checkpoints: the owner commits its structured decision and emits the sanitized cross-model input as a handoff artifact; the dispatching layer (the main session running the skill, or `pipeline_orchestrator_agent` in pipeline Mode A — neither is Bucket A) executes § API Call Patterns, applies the mechanical enum comparison, and re-invokes the owner only for the divergence rebuttal; the editorial checkpoint's dispatched shape is an explicit, justified exception to the before-the-roadmap ordering (safe because the sprint-contract boundary keeps cross-model drivers out of the roadmap). The rule generalizes to any Bucket A cross-model owner — `devils_advocate_reviewer_agent`'s independent DA critique routes the same way, with every successful response returned to the owner (no mechanical comparison exists for the dispatcher to resolve); non-fenced owners (`integrity_verification_agent` at the Stage 2.5/4.5 gates, deep-research `devils_advocate_agent`, the main session) execute directly, unchanged. No Bash/WebFetch re-added to any fenced agent (resolution (a); (c) rejected). Converged 0 P1/P2 across first-party security review + two codex `gpt-5.6-sol` xhigh rounds. - **Pipeline prompt-surface contradictions from the #528 Mode-A replay (#529).** The two genuine contradictions of the four ambiguities the replay surfaced: (1) the Methodology Blueprint was listed as a Stage 1 deliverable and in the Material Dependency Matrix but omitted from all three Stage 1→2 handoff surfaces — added to `academic-pipeline/SKILL.md`, `references/pipeline_state_machine.md`, and `agents/pipeline_orchestrator_agent.md`; (2) the post-review coaching trigger read "After Stage 3 or Stage 3' completion, Decision = Minor/Major", but routing sends a Stage 3' Minor directly to Stage 4.5 — the trigger is now split by stage (Stage 3 = Minor/Major; Stage 3' = Major only) and the Coaching Rules exclusion list extended to match. Text-only. Retrospective entry: the code merged before this entry was written; documented here per the changelog-covers-merges gate. - **Stage 5 / Stage 6 boundary semantics — the two under-specified boundaries from the #528 Mode-A replay (#528).** New authority section `references/pipeline_state_machine.md` § Stage 5 and Stage 6 Boundary Semantics, mirrored in `academic-pipeline/SKILL.md` and `agents/pipeline_orchestrator_agent.md` + `references/process_summary_protocol.md`. Stage 5: "Before finalization: always MANDATORY" now names exactly one checkpoint — the **entry gate** between Stage 4.5 PASS and the Stage 5 dispatch, carrying the format decisions; the in-stage content confirmation before the final PDF is Stage 5 execution (not a pipeline checkpoint), and the Stage 5 **completion checkpoint** (Final Paper delivered, before Stage 6) is FULL — never SLIM — but not MANDATORY. Stage 6: the state machine previously ended at `Stage 5 → END` with no Stage 6 at all; it now defines the Stage 5→6 transition, the decline path (Stage 6 is non-mandatory: declining marks it `skipped` and the pipeline still terminates `completed`), the **terminal checkpoint** after the Process Record is delivered, and the canonical terminal-acknowledgement vocabulary (`finish` / `end` / `done` / `confirm`, or an unambiguous natural-language equivalent) whose acceptance sets the pipeline global state to `completed`. All derived from existing text — no architecture change, no checkpoint relaxed. Thirteen codex `gpt-5.6-sol` xhigh review rounds drove the consequential closure across the wider surface set: the `state_tracker_agent` contract gains Stage 6 (stage_id enum, SSOT block, prerequisite rows, terminal/decline action pairs), the FULL checkpoint-type row stops claiming "before finalization" (it collided with the MANDATORY row), Stage 5 execution consumes the entry-gate citation-style decision instead of re-asking, Stage 6 joins the explicitly-skippable list (the skip validator would otherwise reject the pinned decline path), the engagement-tracking SLIM downgrade gains its FULL-checkpoint exception, and the whole-pipeline collaboration-observer pass is re-timed to Stage 6 record compilation (before delivery — "at pipeline completion" could not coexist with completion-after-acknowledgement). New `scripts/check_pipeline_boundary_semantics.py` defrift lock pins all four #528 resolutions across the five surfaces with 66 mutation tests (one adverse-value witness per closed gap), wired into spec-consistency.yml and the unified pytest manifest; because twelve rounds showed sentence-level pins alone cannot converge on prompt surfaces, all five files also carry bibliography_agent-style whole-file sha256 content locks — any byte change fails CI until the pinned hash is updated in the same commit. Closes #528. ### Added - **Canonical cross-model handoff envelope + dispatcher consumer contract (#527).** The #523 owner→dispatcher→owner transport path was internally coherent but enforced by prose only — no canonical delimiter, no machine-stable schema, no malformed-result mapping, no pinned consumer trigger, so every test could stay green while a dispatcher silently treated a checkpoint owner's handoff as an ordinary deliverable. #527 closes that: one canonical `[CROSS-MODEL-HANDOFF v1]` envelope (checkpoint_kind / owner_agent / correlation_id / expected_result / owner_decision-outside-payload / payload) defined in `shared/cross_model_verification.md` § Cross-model handoff envelope, with `scripts/cross_model_handoff.py` as the NORMATIVE grammar (parse + outcome routing as pure functions) and a deterministic owner→dispatcher→owner fixture suite on a fake transport (`scripts/test_cross_model_handoff.py` — no external API, no manuscript upload; literal pins guard the module constants against self-referential testing). The three checkpoint owners (`research_architect_agent`, `editorial_synthesizer_agent`, `devils_advocate_reviewer_agent`) emit the envelope with their closed kind/result-shape pair; the Mode-A orchestrator pins the consumer contract (recognition as a transport request never a deliverable; malformed envelope/result → `[CROSS-MODEL-ERROR: malformed_*]` → outcome `unavailable`, never a fabricated judgment; agreement → mechanical fill with NO owner re-invocation; divergence → re-invoke the original owner with the minimum return context, the dispatcher never authors the rebuttal; DA full-return: every successful response goes back to the owner; `ARS_CROSS_MODEL` unset stays byte-equivalent). New `scripts/check_cross_model_handoff_contract.py` pins the contract across all five surfaces (including a prose-enums-follow-the-module invariant) with a per-branch mutation-witness suite; both suites wired into spec-consistency.yml + the unified pytest manifest. Closes #527. - **Defrift lock for the #514 tools allowlist (#524).** New `scripts/check_tools_allowlist.py` + a 74-test suite (a failing witness per invariant branch), wired into spec-consistency.yml and the unified pytest manifest. YAML is the authority, not a line scan: every semantic decision reads a duplicate-preserving node tree (`yaml.compose`, which keeps a shadowed duplicate key visible and resolves an alias into shared node identity), the frontmatter fence is a column-0 `---` only (an indented `---` inside a block scalar can't truncate the block and hide keys below it), and any frontmatter that will not compose to a mapping or uses a merge key (`<<`) / alias is a fail-closed error. Invariant 1 pins the `tools: Read, Write, Edit, Grep, Glob` value on all six #514 surfaces: the node tree must carry exactly one `tools` key whose value normalizes to exactly the canonical five, plus an additive byte-exact raw-line witness (CR-sensitive, so a symmetric LF→CRLF conversion is drift; fires when the verbatim pinned line is absent). This closes the drift scenario where a future PR edits a source+mirror pair symmetrically (re-adding Bash, dropping a tool, or typoing a name) and passes every CI gate green, because `check_agents_mirror_sync.py` pins only pairwise byte-equality and the runtime guard keys on agent name, never frontmatter; changing the allowlist now requires touching the lint's pinned value in the same commit (standard lock semantics). Invariant 2 reconciles the frontmatter channel against the runtime channel: any agent whose `name` is a Bucket A key in `scripts/ars_phase_scope_manifest.json` must not declare Bash in a `tools:` key in any YAML-legal form — comma string, quoted string, flow/block list, inline comment, `Bash(...)` permission specifier (`BashOutput` is a different tool and not flagged) — failing closed on a missing/non-mapping manifest, unparseable Bucket A frontmatter, or an unrecognized `tools` shape. Twelve rounds of codex `gpt-5.6-sol` xhigh review plus first-party self-probing drove the design from a line-scan first cut to a `yaml.compose` node-tree authority (the byte-exact witness anchored to the composed key's `start_mark.index`; frontmatter fences found with `splitlines`, which recognizes every YAML line break; the whole `tools` value folded through Cf-format-char stripping + NFKC BEFORE any split — so every compatibility separator becomes ASCII first: a fullwidth comma `,` U+FF0C that `split(",")` would miss, and a fullwidth-paren specifier `Bash(git:*)` U+FF08/U+FF09 that the `(` split would miss, both reduce to their `Bash` base — so an invisible-character or homoglyph re-spelling of a tool name, or of a separator around it, cannot masquerade as a different token), closing a series of YAML-form fail-opens — quoted/flow/escaped Bash, escaped-key duplicates, merge-key injection (plain `<<`, chained `<<: [*a, *b]`, alias-to-merged-mapping, a merge tag on a non-scalar `? !!merge [x]` key, and a merge buried in a sequence), a `#`-in-quoted-key comment-strip bypass, an indented-fence truncation, `!!binary`/`!!str` tag tricks, a leading-BOM skip, parser-dependent duplicate `name`/`tools` keys, nested agent files missed by a non-recursive glob (now `rglob`), directory symlinks `rglob` does not descend (now fail closed), bare-CR (old-Mac) frontmatter read as absent, a `RecursionError` on pathologically deep nesting (now fails closed), and a zero-width/BOM/fullwidth re-spelling of `Bash` slipping the exact-string membership test (`Bash`, `​Bash​`, `Bash` — `str.strip()` leaves Cf format chars, so the token stayed distinct from `Bash`; now folded, with the byte-witness confirmed to still fire on an invisible-char canonical value), and the two ordering corollaries where a compatibility separator escaped an ASCII split and so `Bash` was never isolated as its own token — a fullwidth-paren permission specifier `Bash(git:*)` (U+FF08/U+FF09) past the `(` split, and a fullwidth comma `Read,Bash` (U+FF0C) past the `,` split — both fixed by folding the whole value before any split — plus two byte-witness false-positives (a `tools:` line inside a `description:` block scalar, and Unicode line breaks NEL/LS/PS that YAML counts but `split("\n")` does not, both fixed by anchoring the witness to the composed key's byte offset); each closure carries a witness. The round-12 pass established the separator-class fold as complete: an exhaustive first-party scan of NFKC-stable alternate separators (ideographic/Arabic commas U+3001/U+060C, division/fraction slashes, semicolons) confirmed none can isolate a bare `Bash` token for any consumer — they do not fold to the ASCII `,`/`(` the split (or a normalizing consumer) honors, so `Read、Bash` stays one non-`Bash` token everywhere — plus a scan confirming no codepoint NFKC-decomposes INTO `ash` and no non-`Cf` codepoint folds to empty (no token-merging attack); this boundary is pinned by a documenting non-bug test so the separator set is not later over-broadened into false positives. Plus the one-line allowlist mention the #521 review flagged as absent: `docs/PERFORMANCE.md` / `docs/PERFORMANCE.zh-TW.md` § plugin agents and the SessionStart announce script's plugin-agents line. - **Machine-readable degradation registry + omission reason-provenance (#511 Part A).** New `shared/contracts/degradation_registry.json`: a six-row auditable INDEX of every graceful-degradation mechanism (citation resolver outage → `unreachable`/`unresolvable`; contamination-signal API degradation → omit-field; VLM absence → skipped/`PASS WITH NOTES`; submission-package incompleteness → `not_checked`/exit 3; cross-model absence → warn-and-continue with required disclosure; non-SR compliance warn-cap — the one legitimate severity cap, because compliance already has a native block>warn>info scale). Each row records failure class → emitted degraded state → diagnostic marker → downstream consumer → terminal-policy effect → the per-mechanism authority as a verbatim CONTENT anchor (line numbers forbidden — the issue's own line refs had drifted by implementation time) → the tests/lints/schemas pinning the behavior. The registry indexes, it never re-authors: semantics stay in each authority file, and new `scripts/check_degradation_registry.py` (31 mutation tests, spec-consistency + pytest-manifest wired) fails CI when any anchor or pin stops resolving. No score caps — rows use native semantics per the issue's explicit rejection. Plus the one live weakness closed: `literature_corpus_entry.schema.json` gains optional `contamination_signal_omissions` (closed enum `api_degraded` only; derivable omissions — manual exemption, no-arxiv-id skip — deliberately not recordable), guarded by manual-entry forbid + per-key mutual exclusion with `contamination_signals` + arxiv-requires-id rules, and written on BOTH paths: `bibliography_agent` at ingest (sha256 baseline updated per the F2 procedure) and the backfill layer — new `contamination_signals.build_signals_with_omissions()` (manual checked upstream so a `None` from the resolver means exactly "degraded"; `build_signals_object` becomes an equivalence wrapper) + idempotent `record`/`clear` helpers, with both migration tools recording the omission once on a degraded lookup and clearing it when a later run computes the signal (recovery + idempotency + dry-run tests). A degraded lookup is no longer indistinguishable from "never computed". Advisory-only: R-L3-2-C k/k_max counting is unchanged. Design: `docs/design/2026-07-15-511-degradation-registry-design.md`. Closes #511. - **Transport-fixture integration test for the citation gate (#511 Part B).** New `scripts/test_transport_fixture_citation_gate.py` + checked-in redacted raw API bodies under `scripts/fixtures/transport_bodies/` (success / miss / error per resolver, all metadata synthetic — `10.5555` example-prefix DOI, fictitious arXiv ID). A URL-dispatch fake at `urllib.request.urlopen` (any unrouted URL fails the test) feeds the bodies through the four ACTUAL client implementations (`crossref_client` / `openalex_client` / `semantic_scholar_client` / `arxiv_client`) into `verification_gate.verify_citation`, pinning the gate's 3-class verdict end-to-end: all-hit → `true` (ID fast path, one request per resolver), fabricated IDs (404 + real empty-result bodies through the title fallback) → `false`, total 5xx outage → `unresolvable` never `verified`. Closes the gap where the per-client suites pin one client at a time and the citation eval replays already-reduced `resolver_outcomes` authored from the same reducer rule — client parsing was never integration-tested. Wired into the unified pytest manifest. Deliberately NOT a product `--offline` mode and NOT a replication of the 51-case gold set (scoped out in #511 as inflation). Part A (degradation registry) remains open. - **Executable sprint-contract panel checker (#510).** New `scripts/check_panel_synthesis.py` recomputes both v3.6.2 decision layers from the primary artifacts — Layer 1: each reviewer's own scores → declared fired conditions → own `## Editorial Decision`; Layer 2: the panel scoring matrix → quantifier thresholds → precedence → the synthesizer's declared `fired_conditions:` set AND emitted decision — and fails on mismatch (self-consistency gate on LLM output, not a correctness gate). Exit codes are classified by artifact source (1 synthesis-layer → retry synthesizer once; 2 contract/infra → abort; 3 reviewer-report → unusable reviewer ⇒ `[PANEL-SHRUNK]`; precedence 2>3>1), with a `--layer1-only` mode for per-reviewer verification at Phase-2 lint time. The §9 expression vocabulary is implemented as a closed grammar (unknown forms, orphan dimension literals, and empty priority scopes all fail closed — no vacuous truth), report/synthesis output grammars are pinned in the five reviewer agents + synthesizer prompts (role line, `score:`/`fired:` lines, exactly-once decision line, fenced-code stripped, duplicate sections rejected), and cardinality is guarded against duplicate paths/byte-identical reports/role-set forgery. Ships with protocol §8.1 runtime wiring, the zero-fired accept-grade fallback aligned across all surfaces (derived from the contract's F0 action, never hardcoded), and the majority quantifier corrected from a `⌈N/2⌉+1` transcription error to simple majority `⌊N/2⌋+1` (evidence chain in #531 — every concrete threshold in the v3.6.2 design says 3/5, 2/3). Design: `docs/design/2026-07-15-510-panel-synthesis-checker-design.md`; cross-model design review (gpt-5.6-sol xhigh) drove the grammar pinning, fired-set verification, and exit-code classification. ## [3.16.0] - 2026-07-12 — Model tiering, cross-model gate hardening, WP advisory sharpening ### Added - **Model tiering: judgment/execution split with two opt-in directions, default untouched (#517).** New `ARS_MODEL_TIERING` env switch and canonical `shared/model_tiering.md`, motivated by Lance Martin's "Cost effective harnesses with Fable" (2026-07-10; advisor-checkpoint configs measured ~90% of frontier-solo quality at ~34% of token cost, with delegation paying only when workers absorb enough tokens to offset per-handoff coordination cost). **Default (unset): byte-equivalent pre-#517 behavior** — every agent stays `model: inherit` (same opt-in philosophy as `terminal_policies`). **`economy`** (frontier-tier session): the 13 execution-type agents dispatch exactly one tier below the session model, **floor Opus-class, never Sonnet** (academic-prose tolerance is untested; the article's numbers came from ML tuning) — `draft_writer` explicitly flagged as the highest-savings / most quality-sensitive downgrade point. **`quality-boost`** (below-frontier session): the judgment-type agents dispatched at the Stage 2.5/4.5 integrity gates and the final-review surfaces step up to the frontier tier; nothing is ever downgraded. Both directions have explicit no-op conditions with a one-line announcement; unknown values warn once and behave as unset (fail-open to the safe default). Tiers are **relative positions, never hard-pinned model ids** (the v3.7.0 `opus` command floor retired in the Fable 5 harness pass is the cited precedent). Because many ARS roles execute inline today (no per-role model choice), the mechanism is dispatch-shaped: when a direction applies to a role, the session dispatches it as a subagent pinned to the target tier — inline roles included — and falls open to inline-on-session-model with a one-line announcement where subagent dispatch is impossible; `docs/PERFORMANCE.md`'s (en/zh-TW) "no separate model routing layer" sentence is reconciled with a pointer. The frozen 39-agent classification (26 judgment / 13 execution — the issue header's 25/12 arithmetic corrected, membership unchanged) lives twice on purpose: a machine-readable `scripts/model_tiering_manifest.json` and the canonical doc's table, pinned to each other AND to the `*_agent.md` files on disk by new `scripts/check_model_tiering.py` — set equality with a repo-wide stray sweep (a new skill dir can't smuggle unclassified agents), tier-enum + duplicate checks, and EXACT per-(tier, skill) token-set comparison against the doc table (missing/extra/duplicate tokens, per-row counts, duplicate rows all fail; 15 mutation tests; wired into spec-consistency.yml + the local pytest manifest). Prompt-caching guidance (when a direction is active, route repeated same-stage calls to the SAME worker so its cache accumulates) documented in the canonical doc and each of the four `SKILL.md` files' compact `## Model Tiering (#517, optional)` dispatch block — scoped so the unset default stays byte-equivalent, dispatch shapes included. No agent-file edits (the sha256-locked `bibliography_agent.md` untouched), no schema change, no hook. Spec: `docs/design/2026-07-12-517-model-tiering-spec.md`. - **Cross-model gate hardening: risk-stratified sampling, blind disagreement checkpoints, id-status allowlist, promotion bakeoff (#518).** Four upgrades to `shared/cross_model_verification.md` and its consumers, from a 2026-07-11 cross-model consult (gpt-5.6-sol, xhigh). (1) The integrity-gate cross-model sample moves from uniform random 30% (min 5, max 15) to **risk stratification** across four mutually-exclusive tiers (highest-precedence tier wins, one verification per reference): HIGH-IMPACT references (headline conclusions, numerical claims, causal claims, methods-critical, disputed) verified 100% uncapped at both gates; a 10% RANDOM sample of the remainder at Stage 2.5 (round-up, min 3, max 10); at Stage 4.5, NEW-CHANGED references (behind claims new or changed since 2.5) verified 100% uncapped plus a 10% CONTROL sample of the unchanged remainder replacing RANDOM — verification budget concentrates where the paper's weight rests, and the results table gains a Tier column (`integrity_verification_agent` updated in lockstep). (2) The two irreversible checkpoints — research-design freeze (`research_architect_agent`) and final editorial decision (`editorial_synthesizer_agent`) — gain optional **blind disagreement checks**: the primary commits its own decision in the same structured form first (the architect in a new Design-Freeze Checkpoint Audit blueprint section; the synthesizer's is its emitted decision), the cross-model then produces an independent structured decision from the same inputs (never seeing the primary's decision — same anchoring-prevention rule as the integrity samples; the editorial input is the panel's `panel_size` N usable reviewer cards, never a hardcoded five), differing enum values trigger a targeted rebuttal addressing each cross-model driver against the evidence on file, and divergence escalates to the user — a review trigger, never a vote, never averaged; under a sprint contract the check runs strictly post-Step-3 against the mechanical protocol's `editorial_decision` and its drivers never enter the scoring matrix. (3) The **"6th reviewer — Planned" section is retired, not deferred**: the consult's counterproductive-conditions list (score averaging, role duplication, findings treated as confirmed defects, majority-vote false confidence, synthesizer context burn) matches ARS's documented anti-patterns one-for-one; the blind checkpoints are the replacement design, and the live mirrors (`.claude/CLAUDE.md`, `shared/raise_framework.md`, SETUP feature tables en/zh-TW) drop the "remains planned" claim. (4) The model-detection snippet separates "which provider endpoint" from "is this id known-good": new `CROSS_MODEL_ID_STATUS=validated|provisional|unlisted` announcement with an explicit warning for unlisted first-party-prefix ids (`gpt-made-up` no longer passes silently) — routing itself is byte-identical, an unlisted id still takes the grounded route and never falls through to the ungrounded compatible branch. Plus a **§ Promotion Bakeoff** operationalizing the `gpt-5.6-sol` provisional→validated criteria: a 30-reference paired same-day run (20 real / 10 fabricated; committed as a versioned, labeled, sha256-recorded fixture before any run counts; 3 repeats with ≥2/3 majority verdict, a 1–1–1 split scored conservatively against the model that produced it) against five non-inferiority thresholds (grounded-search completion, mismatch recall, false-disagreement rate, jq-guard shape stability — a hard requirement, p95 latency), entry-gated by `scripts/cross_model_smoke_test.sh`, results recorded under `audits/` either way — and a deliberate two-step outcome: a full pass makes the id `validated`, while the recommended default flips only with an additionally stated superiority or operational-benefit reason. Spec: `docs/design/2026-07-12-518-cross-model-gate-hardening-spec.md`. Related: #517 (model tiering) will reference the checkpoint surfaces added here. - **GPT-5.6 Sol listed as provisional cross-model verifier + explicit reasoning-effort control (#515).** OpenAI's `gpt-5.6-sol` (released 2026-07-08) joins the canonical model table in `shared/cross_model_verification.md` as **provisional pending ARS validation** — endpoint support (Responses API + hosted `web_search`), the reasoning-effort enum (`none|low|medium|high|xhigh|max`, default `medium`), and pricing (same standard rates as GPT-5.5; premium is `reasoning: {mode: "pro"}` on the standard slug, NOT a `-pro` model id) were verified first-party against OpenAI's model page and GPT-5.6 guide, but ARS-specific behavior (grounded-search completion rate, citation-mismatch recall, false-disagreement rate, jq-guard response-shape stability, p95 latency) has no operating history, so **GPT-5.5 stays the recommended default**. The documented OpenAI Responses call pattern gains an explicit reasoning-effort control via new `ARS_CROSS_MODEL_REASONING_EFFORT` — set, it is passed as `reasoning.effort` so the run's effort is visible and reproducible; unset, the field is omitted entirely and each model's own provider default applies (forcing one value would silently change behavior for existing `gpt-5.5-pro`/legacy setups, a codex-review P2) — and both SETUP quick-setup blocks (en/zh-TW, parity-linted) mirror the new example lines. New `scripts/cross_model_smoke_test.sh` — a live, manual (not CI; needs `OPENAI_API_KEY`) promotion gate asserting HTTP 2xx, a completed `web_search_call`, a single verdict token, VERIFIED-carries-source, model echo, and effort echo — is the prerequisite for ever flipping the default to Sol. The canonical doc's Chat-Completions-web-search claim was re-verified against OpenAI's current web-search guide and deliberately left unchanged (a cross-model review suggested it was stale; first-party docs confirm it is still accurate). - **WP advisory held-out miss-rate measurement + acceptance set, Part 2 (#501; direction from the PR #468 review thread, @brycewang-stanford).** New `evals/heldout/rq_framing_offlist/`: a 48-item held-out set (32 shells outside the WP01-WP20 surface forms and the four in-prompt examples — 23 family variants + 9 off-list — plus 16 domain-native hard negatives), generated cross-model (`gpt-5.6-sol`), shell items regex-filtered (four negatives intentionally carry listed surface substrings as hard-negative material), dual-annotated with documented drops, English-only per the #468 caveat. Scored against the runtime LLM judge (isolated `claude-sonnet-5` sub-agents, verbatim advisory section only, pre-#503 vs post-#503 variants, two post replicates): overall miss rate 0.34-0.38 (above the inherited FNR < 0.30 line), concentrated in decorated compound-title off-list shells (7/9 missed, stable across replicates; judges read generic topical nouns as the exemption's "specific mechanism"), family-variant generalization under the line post-#503 (0.17-0.22), false-fire 0/16 on both variants. Verdict: miss rate HIGH → per #501's decision rule the set is now the acceptance test for any future advisory change (protocol in the set's README; report at `audits/rq-advisory-heldout-measurement-2026-07-11.md`). Deliberately outside `evals/gold/` (LLM-judged; no `target.entrypoint`, labels not reducer-reproducible). Closes #501; the measured off-list gap is tracked as a follow-up design issue. - **Introduction & Title Rhetoric reference (#500; gap surfaced by PR #485, @lorenzo392).** New `academic-paper/references/intro_title_rhetoric_guide.md`: CARS three-move Introduction guidance (territory / niche / occupation, with a licensed-gap rule, the universal-negative trap, purpose-sentence discipline, and a common-failures table) plus a title-crafting section (anatomy, four title types with claim-level cautions, checklist, weak→stronger worked examples, and a cross-check against the WP06/WP17/WP18 wording-pattern shells). Wired as a `draft_writer_agent` Step 1 setup checklist item; `academic-paper/SKILL.md` File Structure reference list updated (stale count 20 corrected to the actual 28). The declined PR #485 skill shape (new top-level skill) stays declined; this lands the two genuinely-uncovered content areas as a reference file per the maintainer response there. - **Korean trigger keywords + routing boundary fixtures (#452 PR 1; proposal and Korean boundary phrases by [@devCharlotte](https://github.com/devCharlotte), who also authored the native-reviewed Korean README in #469).** All four `SKILL.md` files gain a `**한국어**` trigger-keyword line plus a conservative Korean subset in the frontmatter `description` — intent-specific compounds only (논문 심사 / 논문 수정 / 초록 작성 / 체계적 문헌고찰 / 연구부터 논문까지 …), deliberately avoiding the broad standalone terms the proposal flagged (연구, 논문, 작성, 검토). The key 수정-vs-심사 disambiguation lands as two new routing smoke-test fixtures (`tests/fixtures/issue_133_routing/09_korean_revision_not_review/`, `10_korean_review_not_revision/`) using the proposer's native-authored phrases; all six boundary cases from the issue pass a routing smoke test on the current primary model (6/6, recorded in the PR). No changes to agents, IRON RULEs, integrity protocols, schemas, modes, or output-language behavior. Closes #452 (the Korean README half shipped in #469/#471). ### Changed - **WP advisory exemption sharpening — decorated title-form shells now caught (#505; direction from the #501 Part 2 measurement).** The exemption clause in both `socratic_mentor_agent.md` files (deep-research + academic-paper) is narrowed: it now requires a *named or operationalized* specific (an actual instrument/scale name, a named theory/model/dataset/policy instrument, a named site or population, a specified causal pathway — through what mediator/condition/process A relates to B, not merely that it does — or a stated tension between two identified explanations), declares ordinary domain-flavored topic-label pairs swappable, and adds a decorated-compound-title rule (an evocative pre-colon phrase plus a generic "X and Y (in Z)" subtitle gains no specificity from the decoration — the noun-swap test applies to the part after the colon alone). This closes the failure mechanism the #501 Part 2 baseline measured: judges reading generic topical noun pairs as the exemption's "specific mechanism", which rescued 7/9 off-list shells (six decorated titles plus one interrogative). Measured against the held-out acceptance set per its README protocol in two rounds (initial wording, then a cross-model-review-driven refinement — demographic descriptors excluded from "named population", single-topic subtitles covered — re-measured from scratch; 2 replicates each, same judge model): overall miss 0.375/0.344 → **0.094 in all four post-#505 runs**, off-list 0.778 → all 9 items fired in at least one final replicate (final rep2: 9/9), false-fire 0/16 preserved in every run (including the four hard negatives carrying listed surface substrings); no shell missed in both final replicates; on-list gold set unaffected (regex detector untouched, fnr=0/fpr=0). All #505 constraints held: WP table unextended, advisory stays non-blocking and surface-phrasing-only, sentinel contract (`test_check_rq_framing_patterns.py`) unchanged; the new in-prompt example strings were substring-checked against every held-out item (zero hits) so the set stays held out. Measurement JSON `evals/heldout/rq_framing_offlist/measurement-2026-07-11-505.json` + reasoning excerpts appended; report at `audits/rq-advisory-505-exemption-sharpening-2026-07-11.md`. Closes #505. - **Reviewer calibration protocol notes LLM-as-judge leniency direction (#484 → PR #506, merged).** `academic-paper-reviewer/references/calibration_mode_protocol.md` gains a directional-prior subsection under "Failure cases this mode does NOT fix": when the simulated panel's output is read as a pass/fail signal, assume leniency relative to human expert review until your own calibration shows otherwise, anchored to FARS (Tang et al. 2026, arXiv:2606.31651 — automated reviewer mean 5.00 over 165 papers vs 3.23 paper-level mean from 282 human expert reviews over 140 papers; a descriptive ~1.8-point gap, and the automated score functioned only as a relative ranking). The direction is a working prior (heuristic extrapolation from one measured setup, default-until-measured); the magnitude is explicitly non-portable — never a correction factor or threshold change. Docs only; the panel remains advisory infrastructure behind human checkpoints. FARS added to References. - **WP advisory generalization, Part 1 (#501; direction from the PR #468 review thread, @brycewang-stanford).** Both `socratic_mentor_agent.md` files (deep-research + academic-paper) now state that the WP01-WP20 table is illustrative, not exhaustive, and name the operative judgment: the noun-swap test (phrasing is shell-like when it survives swapping its nouns for any other field's nouns). Off-list shells that clearly survive the swap may fire the advisory at the same high-confidence bar; domain-native phrasing that names a mechanism, instrument, site, or tension does not survive it and must not trigger. Advisory stays non-blocking and surface-phrasing-only; sentinel contract unchanged (`test_check_rq_framing_patterns.py`). Part 2 (held-out miss-rate measurement) landed separately — see the Added entry above. - **API-first retrieval refresh: OpenAlex API-key auth, budget-aware 429 handling, arXiv ToU-aligned backoff (#495; proposed by [@pikaqiu2333](https://github.com/pikaqiu2333)).** OpenAlex's current developer docs are API-key-first (freemium daily budget; the polite pool is no longer documented): `scripts/openalex_client.py` gains `OPENALEX_API_KEY` support (query-param auth; either credential selects the authenticated 10 req/s pacing tier, `OPENALEX_POLITE_EMAIL` stays as legacy compat), distinguishes daily-budget-exhausted 429s (`X-RateLimit-Remaining: 0` → raise `OpenAlexUnavailable` immediately — the budget refills at midnight UTC, so an in-process retry cannot succeed) from transient burst 429s (exponential backoff 2s → 4s → 8s per OpenAlex's documented guidance), and strips the query string from refusal-path error messages so the key never lands in logs (`scripts/crossref_client.py` gets the same redaction — its query string carries the polite-pool `mailto` email). `scripts/arxiv_client.py`'s 429 backoff moves from the shared 2s constant to the 3s ToU pacing floor (arXiv's Terms of Use ask for at most one request every three seconds — a sub-3s retry would itself violate the pacing the 429 enforces; verified verbatim against the ToU page). Both protocol docs (`deep-research/references/openalex_api_protocol.md`, `arxiv_api_protocol.md`) updated in lockstep, plus an explicit retrieval-order boundary in each: structured APIs are the primary channel, browser/WebFetch page inspection is a bounded first-party fallback whose output is data-not-instructions (`shared/ground_truth_isolation_pattern.md` §2A), and browser retrieval is never a rate-limit bypass (no parallel browsing, no bulk PDF harvesting, no multi-machine fan-out). 5 new client tests; the two 429-behavior tests updated to pin the new backoff shapes. ### Docs - **Third-party directory: `THIRD_PARTY.md` + README pointer (#497 → #498).** Community-submitted third-party projects that wrap or host ARS get a low-bar directory listing (visible ARS attribution + faithful description required) that is explicitly separate from endorsement — entries are not reviewed, tested, or verified by the maintainer, and the page says so up front. ClawMama listed as the first entry per issue #497. The README install section gains a neutral, non-recommending pointer to the page; the main install flow still links only to maintainer-verified paths. A separate "Getting officially recognized" track is documented for projects that want actual review. ## [3.15.0] - 2026-07-04 — Release-gate hardening, prompt-debt retirement round 2, defrift locks ### Added - **Phase Boundary enforcement-sentence defrift lock + SETUP cross-model parity lint + local-manifest gap closure (#491 → #492).** `scripts/check_v3_9_2_phase_boundary.py` gains invariant 4: every Bucket A block's enforcement paragraph must carry the canonical sentence verbatim (`CANONICAL_ENFORCEMENT`, version-matched v3.9.2/v3.9.4; per-file tails after the sentence stay free). Motivation: the pre-#294 copy of that sentence sat factually stale across 29 surfaces for a month while this very lint iterated those exact blocks pinning only the marker prefix — the drift class now fails CI, with 7 mutation tests including the literal stale sentence as the regression case. New `scripts/check_setup_cross_model_parity.py` (+9 tests, wired into spec-consistency.yml) pins the `docs/SETUP.md` / `docs/SETUP.zh-TW.md` `ARS_CROSS_MODEL` examples to each other and to the canonical model tables in `shared/cross_model_verification.md` — membership is scoped to "API ID" table columns after a codex-review P1 (legacy ids backticked in the accepted-for-existing-setups note must not count as recommended) and excludes glob prefix tokens per the re-review P2, failing closed when extraction finds nothing. The local pytest manifest gains `test_check_v3_9_4_temporal_verification.py` (58 → 60 entries): its CI-only coverage is what made #490 local-green/CI-red on the bibliography_agent sha256 F2 ownership pin. `tests/test_helpers.py` gains `load_module_from_path()`; the enforcement-sentence mechanism is cross-referenced with the `firm_rules.md` canonical-block framework in both directions (deliberately separate: factual status prose vs behavioral firm rules). - **Release-doc alignment gaps closed in the version-consistency lint — three new invariants + a tag-time gate (#487).** A 2026-07-03 audit of `scripts/check_version_consistency.py` against the suite-level release-doc discipline found the #357/#345 extensions had already closed the docs-version-ceiling, zh-TW-parity, and package-metadata invariants, but four sub-items had no machine check. Now enforced: invariant 9 — the latest CHANGELOG entry's body must be ≥ 100 characters (a bare heading is not release notes; historical entries stay exempt); invariant 10 — `.claude/CLAUDE.md` "Last Updated" must lie within ±7 days of the latest CHANGELOG entry's date (the CHANGELOG date is the baseline, never "today", so re-running the lint later cannot flip the result); invariant 11 — the newest `## vX.Y… Key Additions` heading must match the suite version at the heading's own precision. New `--tag` mode plus a `tag-version-match.yml` workflow (`push: tags: v*`) require the pushed tag name to equal the suite version — the one comparison nothing performed at tag time — and re-run the full lint at the moment of tagging. A fifth candidate check ("every version cited in docs/ must exist in CHANGELOG") was evaluated and rejected: ARS's spec-version naming culture (e.g. the v3.6.6-design-shipped-as-v3.6.8 note) plus component versions make it structurally unenforceable (600+ legitimate references would false-flag). The latest-entry body ends at the next release heading via a fence-aware line scan, so a `## [...]` line inside a fenced code block in the release notes can't truncate the body (even one shaped exactly like a real entry); CHANGELOG / Last-Updated dates are right-bounded and shape-gated to strict `YYYY-MM-DD` then parsed fail-closed, so a prefix like `2026-04-222` isn't mis-read as `2026-04-22` and an impossible date like `2026-02-30` reports a lint error instead of crashing. 20 new mutation tests (33 → 53), including the tag-gate hard-fail when the suite version is unreadable (a garbage tag co-occurring with a broken `.claude/CLAUDE.md` must not slip through the one gate meant to catch it). - **Command-invariants CI gate — announce list machine-checked against the actual command inventory (#486).** Vendors release-discipline-toolkit v0.3.0 under `tools/release-discipline/` and adds `.command-invariants.toml` + a `command-invariants.yml` workflow. The lint discovers `commands/*.md` (16 today) and asserts `scripts/announce-ars-loaded.sh` lists exactly that set — missing command, stale extra, and the hardcoded `Slash commands (N)` count all fail CI — plus a version-lockstep check pinning `.claude-plugin/plugin.json` to the newest CHANGELOG release entry. Motivated by the announce list drifting twice (PR #193: 10 listed vs 12 actual; this change: 14 vs 16); the validator was verified against the live drift before installation (caught exactly the two missing commands, zero false positives). - **CHANGELOG-covers-merges pre-tag release gate (#483).** `scripts/check_changelog_covers_merges.py` machine-checks that every release-worthy commit merged since the previous release tag is documented in `CHANGELOG.md` above the previous release's section ([Unreleased] or a release-prep-promoted newer section — spec §0.2), with conventional-prefix exemptions (`chore`/`test`/`ci`/`build`, `docs(design)`/`docs(superpowers)`/`docs(release)`/`docs(i18n)`) and fail-closed behavior on missing tags/headings. New `changelog-covers-merges.yml` workflow gates every `release/**`-headed PR into main; the CONTRIBUTING manual step covers branchless tag flows. Would have blocked the 16-entry Unreleased backlog rolled up in 3.14.0. 47 tests, registered in the CI pytest manifest. ### Changed - **Prompt-debt retirement round 2: the 17 never-deep-scanned agents (#489 → #490).** Continuation of the #476 track — the 2026-07 audit covered only the 22 Bucket A agents and explicitly deferred the rest. This pass deep-scanned the remaining 17 (all 5 academic-pipeline agents, 5 deep-research, 5 academic-paper, field_analyst, shared compliance_agent) plus 2 new commands and 3 carry-forward re-checks, via 4 parallel sub-agent batches + an independent codex cross-model challenge (12/12 excerpts verified; +1 finding, +1 scope amendment, +2 priority upgrades). 13 findings — 2 P1, 11 P2; the user rejected 2 (recorded so the next audit does not resurface them). The two P1s were live self-contradictions: both `socratic_mentor` agents carried stale pre-v3.0 "suggest quitting after 15 rounds" copies against a documented typical run of 20-30 rounds — auto-end machinery is now a single authority per file (threshold unified at 30), with stagnation/max-round constants single-sourced. The stale "prompt-level only / deterministic hook deferred to v3.10 #134" enforcement sentence (false since PR #294 shipped the write-scope guard) was rewritten at 23 agents + 4 SKILL.md + 2 mirrors, then tightened 39 → 35 words in the /simplify pass. Few-shot and duplicated-process scaffolds trimmed across 7 agents (question-template tables, citation-form examples, resume exemplars, triple-written dispatch rules incl. a missing `view progress` row restored to the authoritative table); SETUP model-lineup drift fixed in both languages. The 2026-06-10 F-007 deferred negative-framing item closes as **verified, no rewrite needed** — zero safely-reframeable negatives across the three top-density files, confirmed by both tracks. `bibliography_agent`'s F2 baseline hash updated per the lint's documented procedure. Audit report: `audits/harness-retirement-2026-07-04.md`. Net −181 deleted / −98 substantive prompt lines excluding the report. ### Fixed - **SessionStart announce updated to the full 16-command set.** `/ars-3w` and `/ars-rebuttal-audit` were missing from both the resume short-form list and the startup long-form listing, and the stated count said 14 — commands shipped after the list was last hand-synced were silently invisible at session start. Both forms now list all 16 with the correct count (announce script `# version` bumped 1.0.0 → 1.1.0); the new command-invariants gate prevents recurrence. - **DOI badge served from shields.io (#482).** Zenodo's badge endpoint rate-limits GitHub's camo image proxy (HTTP 429), intermittently rendering the README DOI badge as a broken image even though the DOI resolves fine. The badge image is now a static shields.io badge in all five READMEs; the link target stays the concept DOI (`10.5281/zenodo.20696614`), which always resolves to the latest version. ## [3.14.0] - 2026-07-02 — Claude Science importability, eval-comment rendering, prompt-debt retirement ### Added - **Claude Science / GitHub-importer compatibility — explicit skill paths in the marketplace manifest (#480).** The plugin entry in `.claude-plugin/marketplace.json` now declares `"skills": ["./academic-paper", "./academic-paper-reviewer", "./academic-pipeline", "./deep-research"]`. The repo-root `skills/` directory holds symlinks, which GitHub-API consumers cannot traverse — Claude Science's "Import from GitHub" previously reported *"Not importable: no skills/ dirs with SKILL.md"* and found zero skills (same failure class as the #413 `agents/` materialization). Per the Claude Code plugins reference, for a marketplace entry whose `source` is the marketplace root the listed paths REPLACE the default `skills/` scan, so Claude Code installs keep loading the same four skills from their real paths — no content change, no double-loading. Verified end-to-end on Claude Science (4/4 skills detected at the merge commit). New README / SETUP.md guidance documents the import flow and its limits: imported skills carry the methodology (research / writing / review protocols); Claude Code-specific machinery (slash commands, hooks, subagent orchestration) does not transfer, and imports are point-in-time snapshots that require re-import after ARS updates. - **Eval-harness PR comments render as a verdict + per-task table (#479).** The eval-harness workflow previously pasted the whole `eval_report.json` into every PR comment as one raw fenced block. New display-layer module `scripts/render_eval_comment.py` emits a one-line verdict (`✅ N/M measured tasks passed · K pending (not wired)`), a per-task markdown table (metric / value / threshold / result; pending tasks render `—` placeholders), and folds the full JSON into `
`. The row verdict mirrors the gate's failure signal (aggregate AND per-class, #328), with a test pinning agreement with `scripts._eval_threshold_gate` so the mirror drifts loudly in CI instead of rendering green on a blocked run; table cells escape pipes and all line boundaries so manifest-sourced strings cannot break or spoof rows (codex review finding, fixed in-PR). Evaluation logic is untouched: `run_evals`, the threshold gate, and the `[eval-regression-acknowledged]` ack contract are byte-identical. 13 unit tests, registered in the CI pytest manifest; the workflow-honesty test suite gains a pin so the comment cannot silently regress to a raw dump. ### Changed - **Prompt-debt retirement: expired writing-harness scaffolds removed from four Bucket A agents (#476 → #478, net −111 lines).** The 2026-07 harness-retirement audit (#477, report under `audits/`) classified prompt scaffolds that encoded assumptions about what prior models could not do. The five P2 findings plus one hands-on finding were applied to `abstract_bilingual_agent`, `citation_compliance_agent`, `draft_writer_agent` (academic-paper) and `research_question_agent` (deep-research). Three-track verification: sub-agent audit + independent codex review (two fabricated back-references caught and fixed; re-review clean) + eval harness (citation_extraction and rq_framing_patterns both at 100%). - **Platform Port Reminder CI (#473).** A remind-don't-block workflow surfaces the "Platform ports (community-maintained only)" CONTRIBUTING.md policy when a PR adds a new top-level directory (motivated by PR #470, where a contributor could not have known the policy existed), plus a PR-template pointer. ### Docs - **Native-reviewed Korean README (#469; contributor credit #471).** `README.ko-KR.md` contributed and translated by [devCharlotte](https://github.com/devCharlotte), with Contributors credit parity across the language READMEs. - **GitHub Copilot repository instructions (#465).** `.github/copilot-instructions.md` orients Copilot-based contributors to the repo's structure and conventions. - **Permission-mode guidance (#464).** Install docs now recommend Claude Code's `auto` permission mode over Skip Permissions. ### Rolled up from [Unreleased] (code landed before the v3.13.0 tag) > Provenance note: the entries below shipped in the repository between the v3.12.0 and v3.13.0 tags, but their changelog bullets had accumulated under `[Unreleased]` across releases. They are versioned here so the release record is complete — no new code ships with these bullets in v3.14.0. #### Added - **Diff/patch revision mode — Slice B revision-mode adoption (#89 Item 7, spec #390, sub-issue #424).** The MVP ship-gate slice: `academic-paper` revision mode now runs **anchorize → patch → deterministic apply → finalizer** instead of full re-emission. `draft_writer_agent` gains the `## Patch-Document Revision Emission (#390)` contract (patch document as a `phase6_*/revision_patch_round.json` sidecar — hashes copied from the block manifest, never computed; `[PATCH-ESCALATION-REQUIRED:]` pre-drafting escalation tag; retry-once; provisional Schema 8 items with mechanical fields left to the orchestrator). `pipeline_orchestrator_agent` gains `## Revision-Round Patch Sequencing (#390)` (five normative steps with a no-rewrite window between manifest generation and apply; two-layer escalation gate with the MANDATORY checkpoint wording; never auto-fallback to full re-emission; escalated rounds re-anchorize under a new ID generation and stamp `mode: full_reemission_escalated`; `preserved_ratio` surfaced next to the #389 round-trip count). Schema 8 `ResponseItem` gains optional `change_block_ids` (orchestrator-populated from the apply report, §3.5 role split). New protocol doc `academic-paper/references/revision_patch_protocol.md` (exact Mode B commands, exit codes, apply report as a required re-review input, marker lifecycle). Two recorded ship decisions land as a spec §0 amendment with cross-model concurrence: **`touched_ratio` threshold = 0.6** (now the apply-script CLI default, strict `>`, 1.0 disables) and the **`insert_after` heading-anchor exemption** (anchoring on a heading no longer flags when the inserted text carries no headings; heading-bearing text still flags). §10 open items closed the verified way: `formatter_agent` gains `## ARS Marker Stripping (#390)` (all marker kinds stripped from converted final outputs only AFTER marker-dependent gates; working drafts keep markers) and `word_count_conventions.md` gains the strip-``-before-count rule (first-party check found NEITHER rule previously existed — the spec's "expectation" had nothing to point at); max single-op `new_text` size folded into the existing triggers (no separate cap). New lint `scripts/check_390_revision_patch_discipline.py` (8 invariants: writer/orchestrator/SKILL/Schema 8/protocol-doc/marker-rules block-scoped literals, threshold value lock, spec-example schema validation) + 30 mutation tests, wired into `spec-consistency.yml` + the pytest manifest. - **Diff/patch revision mode — Slice A deterministic toolchain (#89 Item 7, spec #390).** First implementation slice of the DELEGATE-52 rank-1 item: the deterministic tools exist and are tested, zero prompts touched (Slice B wires revision-mode adoption). New shared parser `scripts/_block_parser.py` (fail-closed §3.1 block segmentation: fence/heading/table/list/blockquote/text + skipped YAML frontmatter; setext underlines, line-initial raw-HTML openers, and footnote definitions rejected **by name**, never swallowed; duplicate-ID / orphan-marker / marker-stack rejection; read-side-only hash normalization). New `scripts/ars_anchorize_draft.py` (script-owned `` stamping — the LLM never assigns IDs; idempotent and content-neutral; emits the block manifest `.block-manifest.json`, the ONLY legitimate hash source a patch may copy from). New `scripts/ars_apply_revision_patch.py` (two-phase fail-closed apply: validate-everything-touch-nothing then byte-span splicing, so untouched blocks are byte-identical **by construction**; structural-shape triggers gated by `--acknowledge-structural`; `touched_ratio` recorded in every report with the threshold VALUE deliberately deferred to Slice B; machine-verified `pure_move` pairs; atomic temp+rename writes; apply report with `preserved_ratio` counters). Two new schemas under `shared/contracts/patch/` (`revision_patch.schema.json` — the `DOC-BODY-START` branch is the only legal hash-less op shape; `block_manifest.schema.json`). 86 new tests across three suites incl. the §8.3 byte-identity property test (seeded randomized patches; untouched blocks + marker lines + separator bytes asserted byte-equal), wired as 3 new CI pytest manifest entries. - **Submission-package verifier Slice 4: terminality (#394 — closes the issue; all four slices landed).** The opt-in blocking layer, shaped by a cross-model gate-1 plan review (1 P0 / 4 P1 / 2 P2, all adjudicated; the P0 resolved as maintainer Option B). New `terminal_policies.submission_package` key (closed enum `{advisory, strict}`, per-key absence = advisory via the evaluator runtime convention — the citation_existence pattern; no JSON-Schema `default`). The **§5.3 single-homed boundary is sharpened, not moved** (Option B): the orchestrator stays the sole *reader/selector* of the policy and hands the resolved value down via the new `--policy` CLI flag; the script *mechanically applies* it — stamps `header.policy_slug` (argparse default **None**: a flag-less standalone run is *unevaluated*, stamped null, and a null-stamped report never satisfies pipeline freshness — never "default advisory"), and under `strict` emits the terminal verdicts: a strict-eligible `fail` → stdout token `TERMINAL-BLOCK policy=submission_package` + exit 1; else a strict-eligible `not_checked` → `VERIFICATION-INCOMPLETE` + new exit 4 (fail-closed §5.2 — a missing parser/profile cannot waive the class the scholar opted into; `not_applicable` never composes into either verdict, keyed on status not the eligibility bit). **Terminal signals are the stdout tokens, never raw exit codes** — exit 1 also carries nonterminal heuristic fails (gate-1 P1). New `--check-freshness` mode (REQUIRES `--policy`): recomputes the package fingerprint + compares the stamped slug, mismatch/null/missing → `STALE-REPORT` + new exit 5, no checks re-run, no writes. The fingerprint exclusion set grows to report + `provenance_summary.md` (gate-1 P1: the formatter appends the advisories section AFTER stamping — fingerprinting the advisory carrier would self-stale every evaluated report). Orchestrator gains the Stage 5 post-formatter **Submission-Package Terminal Gate** section (resolve-absence-to-advisory + always-explicit `--policy`, gate-on-tokens, fix loop bounded 2 rounds then surface, strict-needs-a-venue-profile remediation stated plainly, freshness-before-reuse, recompute-each-pass C-V6(h) mirror); formatter gains the **Submission Package Advisories** `provenance_summary.md` section (mandatory and non-empty iff any fail/warn/not_checked under advisory; stamp-only Invariant 13 untouched). New `scripts/check_394_submission_policy.py` (5 invariants) + 26-test companion — invariant 4 is an **AST single-homed guard** (Subscript/`.get` access of `terminal_policies`), not a literal grep, because the verifier's docstring legitimately says the word (gate-1 P2). 17 new verifier tests (79 in the verifier file; 105 total with the lint companion) incl. an `evaluate_policy` decision-table unit pin (the advisory/strict divergence lives inside the evaluator, not at the call site), byte-equivalence by before/after hashes, and three killed mutations (eligibility gate, fingerprint exclusion, null-freshness reason token). **Gate-2 cross-model diff review (2 P1 + 1 P2, all adopted) + an independent context-reviewer pass over the two prompt sections (2 P1 + 4 P2, all adopted):** a FRESH report now re-emits its policy verdict on `--check-freshness` (same token + exit semantics as a live run — a recorded terminal verdict can never evaporate across a resume), the report header gains `inputs_fingerprint` over venue-profile/passport/join-map bytes (a report produced under one venue profile is STALE under another; legacy reports without the field never read fresh), the v3.10 `policy_hash` marker stamp is scoped to CITATION-TIME keys (`submission_package` never stamps markers — a package-only strict passport no longer forces marker re-finalization or stale-refuses legacy markers; oracle + test updated), the orchestrator gate's advisory path now explicitly re-dispatches the formatter in append-only mode (the advisories section had no named writer), TERMINAL-BLOCK's stdout-vs-marker channel reuse is disambiguated in place (the `policy=` value is the discriminator), the fix-loop round is defined (dispatch formatter → re-run verifier; never a 3rd), VERIFICATION-INCOMPLETE remediation is routed away from the formatter fix loop (a missing profile is not formatter-fixable), token matching is pinned to line-prefix, and the freshness `policy_mismatch` line reprs the stamped slug (a forged report cannot inject a fake token line into stdout). A final confirmation round (2 P2 + 1 P3, all adopted) added the reuse-side roster guard (a hand-edited `checks: []` report is `STALE-REPORT reason=roster_mismatch`, never a clean re-evaluation — the report file is outside the package fingerprint, so content validation must not be skipped on reuse), `--join-map` to the orchestrator's live verifier command (live and freshness invocations must carry the same input set or the inputs fingerprint can never match), and direct-value enum comparison in lint invariant 5 (the string "None" must not pass for JSON null). - **Submission-package verifier Slice 3: Family A blind-review residue scan + Family D assessment (#394).** The high-embarrassment-cost class. Trigger is presence-or-declaration (§3.1): an anonymized variant in the package (filename stem token `anonymized`/`blind`/…) or a declared `blind_review: double` — untriggered packages report the new **`not_applicable`** status (additive report-schema enum), visibly distinct from `not_checked` so a single-blind package is not condemned to exit 3 forever. Checks: **A1** PDF `/Author` + XMP `dc:creator` via `pypdf` (the only new parser dependency; `defusedxml` is additionally declared in requirements-dev as XML-bomb hardening with a stdlib fallback; pypdf absent → `NOT-CHECKED(parser unavailable)`, never folded into pass per §1.4), **A2/A3** DOCX metadata + tracked-changes/comment authors read RAW from the zip parts via stdlib `zipfile` + XML (`defusedxml` hardening when available) — a refinement over the planned `python-docx`: closer to the §1.3 artifact≠rendered-view premise and the DOCX residue class has no missing-parser hole at all, **A4** acknowledgments section in the blind variant (deterministic signal, strict-eligible ONLY when the profile declares the new `acknowledgments_forbidden_in_blind: true` — the §3.1 load-bearing two-axes rule, enforced via a downward-only eligibility override), **A5** self-citation phrasing (heuristic by class; ships a first-party zh-TW draft list per §10 item 1; curated by the maintainer 2026-06-10, adding 本文作者先前), **A6** author-name tokens from the non-anonymized artifacts' metadata appearing in package filenames (heuristic; the metadata-source originals themselves are exempt), **A7** declared-double-blind with no anonymized variant = fail (the most basic residue: the blind version is missing). **Family D ships nothing**: the slice-3 deliverable is the adjudication-ready assessment (`docs/design/2026-06-10-394-family-d-repro-lock-assessment.md`, recommending no-check with the B4 `required_sections` escape hatch — the `repro_lock` gates-don't-read-it boundary stands untouched; adjudicated Option 2 by the maintainer 2026-06-10). 16 new tests (55 total; corrupt-docx honesty, parser-absence honesty, A4 conditional-strictness mutation killed). - **Submission-package verifier Slice 2: scholar-declared venue profile + Family B limits checks (#394).** Second slice of the #394 spec. New `shared/contracts/submission/venue_profile.schema.json` (standalone, Invariant 11 pattern; `declared_by: scholar` is the only provenance value and the CLI refuses a profile without the stamp) and a `--venue-profile` flag enabling five deterministic, strict-eligible checks: **B1** manuscript word count, **B2** abstract word count (both ±2% tolerance per §3.2), **B3** keyword count range, **B4** required sections (case-insensitive heading containment), **B5** reference-count ceiling against the same machine-readable reference list Family C uses. The no-inference rule is structural (R-L3-2-D mirror): without a profile every Family B check reports `NOT-CHECKED(no venue profile)`; a partially-declared profile runs what it can and `NOT-CHECKED`s the rest with the undeclared field named; declared limits whose actuals cannot be located (no abstract section, no keywords line) report `NOT-CHECKED` with the reason, never a guess. This also makes the exit-code semantics visibly honest: a profileless run that is otherwise green exits 3 ("passed what was checkable", §8), not 0. Word counting reuses the canonical whitespace-split convention (`shared/references/word_count_conventions.md`); LaTeX counting adjudicated per spec §10 item 4 as naive detex + whitespace-split, with the method and counted file declared in the report detail. Intake Step 3 gains the optional declared-values-only venue-profile follow-up (plan mode exempt, mirroring Steps 12/13) + a PCR `Venue Profile` row. 14 new tests (39 total) incl. tolerance-boundary and provenance-gate mutations; fixture `venue_clean` passes every B check against `profiles/full.yaml`, fixture `venue_violations` fails all five against `profiles/tight.yaml` (mutation discipline). Cross-model review (codex) adopted in full: schema-strict CLI validation (closed field set, bool≠int), `word_count_scope: all` counts everything, and canonical-name manuscript selection with `NOT-CHECKED(ambiguous manuscript)` instead of silently picking the wordiest candidate. - **Submission-package verifier Slice 1: CLI skeleton + Family C reference integrity (#394).** First implementation slice of the 2026-06-10 #394 spec (slices are dependency-ordered; Family C ships first because it needs zero new parser dependencies). New `scripts/verify_submission_package.py` standalone CLI: point it at any output package directory and it runs the two-way reference-integrity set check (orphan in-text citation = `fail`, uncited reference entry = `warn`) and writes `submission_verification_report.json` validating against the new `shared/contracts/submission/submission_verification_report.schema.json`. The deterministic **joined marker path** consumes `` markers plus a real prose-reference join — the passport's `citation_verification_summary[]` (`--passport`), an explicit scholar-supplied map (`--join-map`), or a package `.bib` via the documented slug==citation_key identity relation — and markers with NO join source report `NOT-CHECKED(missing prose-reference join)`, never a guessed comparison (§3.3). Non-ARS / post-converted packages fall back to format-aware **best-effort extraction** (`\cite{}` for LaTeX, author-year regex for Markdown), heuristic-classed: the schema STRUCTURALLY forbids `signal_class: heuristic` + `strict_eligible: true`, so the fallback path can never be promoted to blocking by any later policy slice. Report header carries `extraction_path`, `not_checked_count` (incompleteness is never folded into pass, §1.4), `package_fingerprint` (spec §10 open item 3 adjudicated: the audit-snapshot manifest convention — byte-sorted `path:sha256` lines, fingerprint = SHA-256 of the manifest, report file excluded), and `policy_slug` (always null until the slice-4 orchestrator hook; the script never reads `terminal_policies`, §5.3). Exit codes separate "all checked, pass" (0) from "passed what was checkable" (3) per §8. 19 tests + 7 fixture packages with mutation discipline (orphan / uncited / no-join failures proven to fire); CI-wired via the pytest manifest. Advisory-only: no manuscript byte changes, no pipeline hook yet (slice 4). #394 stays open until all slices land. - **Design doc: deterministic submission-package verifier (#394, blindspot-audit F-5, design-first — no implementation yet).** `docs/design/2026-06-10-394-submission-package-verifier-spec.md` designs `scripts/verify_submission_package.py`, the script-layer backstop for the mechanical subset of the formatter's prompt-layer submission checklists (the #182 promotion pattern: LLM self-check → deterministic gate). Three check families in adjudicated priority order — blind-review residue (raw-structure scan: PDF/DOCX metadata authors, tracked-changes/comment authors, self-citation phrasing; **artifact ≠ rendered view** is a stated premise), venue-declared limits vs actuals (scholar-declared `venue_profile` schema, never inferred from the journal name — R-L3-2-D mirror), reference integrity (two-way set check; the deterministic path requires an explicit slug↔key join source or reports `NOT-CHECKED`) — plus a stretch *assessment* of `repro_lock` presence/shape checking that leaves the recorded gates-don't-read-it boundary untouched. Two load-bearing rules: `signal_class` and `strict_eligible` are separate axes (heuristic checks are structurally excluded from strict; A4's deterministic signal still isn't block-worthy because the de-anonymization judgment is the scholar's), and **strict fails closed on incompleteness** (`VERIFICATION-INCOMPLETE` when a strict-eligible check can't run — a missing parser must not silently waive the one class the user opted into blocking on). Terminality via a new additive `terminal_policies.submission_package` key, evaluated by the orchestrator against a fingerprint+policy-slug-stamped report (package-level freshness guard — explicitly a new post-format gate, not the ref-marker stamp path). Cross-model reviewed (codex): 2 P1 (Family C join requirement; strict fail-open on NOT-CHECKED) + 4 P2 + 2 P3, all adopted. 4 dependency-ordered slices; advisory-only until slice 4. - **`Real-use findings` release-notes convention documented; showcase refresh tracked (#395, blindspot-audit F-9).** CONTRIBUTING.md gains a Release checklist section documenting the convention: a release's CHANGELOG entry includes a `Real-use findings` subsection when issues were discovered through actual use on a real paper — one line per issue naming the run — so lived-experience provenance has a fixed, greppable home instead of being buried in spec prose (the v3.6.7 production chapter run surfaced 17 drift patterns and nothing structural recorded that provenance class; release motivation since v3.8 has been almost entirely external papers, which is itself a per-release signal worth seeing). Omitted when empty, never padded. The showcase refresh half of F-9 (no post-v2.7 end-to-end artifact set exists) is split to tracking issue #404, blocked on the next real paper with no artificial deadline per the adjudication. - **POSITIONING records two non-goals; cross-paper workflow guide ships (#397, blindspot-audit F-1B/F-7).** POSITIONING.md's recording discipline (five Rejected mechanisms with rationale) had two adjacent boundaries existing only as silence. A new "Recorded non-goals" section records both with the same boundary-plus-review-criterion treatment: **post-publication lifecycle** (own-paper citation tracking / errata / OA self-archiving — the front is research-to-publication; `monitoring_agent` is unaffected since it alerts on *cited* literature, not the scholar's own output) and **research-program-level state** (no cross-paper claim registry / limitations memory / reviewer-history profile — the per-paper Material Passport stays the only state carrier, a deliberate anti-leakage consequence). The supported no-mechanism path for returning authors ships as `docs/cross-paper-workflow.md`: (1) re-feed the prior paper's passport through the existing input port — a prior `ok` is a head start, not a waiver, stamps re-derive under current policies; (2) bring prior limitations / unresolved reviewer points to RQ incubation as scholar-supplied Socratic input — ARS asks about *your* reading, never derives next-RQ candidates (Kong L2 cross-linked); (3) Claude Code assistant memory may serve as a personal reminder layer with the load-bearing caveat that ARS gates never read or trust it — the workflow must work identically on a machine with no memory at all. Documentation only; no schema, flags, or cross-run mechanism. - **Intake Step 13: citation-verification level surfaced at the configuration interview (#392, blindspot-audit F-8, adjudicated "keep default, add a prompt so the user chooses").** The v3.11 citation-existence gate's `strict` mode existed only in README prose and the v3.10/v3.11 specs — a QUICKSTART user had no signal the choice existed. The intake interview gains Step 13: "Citation verification: **mark only** (default) / **strict**", with one sentence of field guidance (strict suits DOI-dense fields; mark-only suits grey-literature-heavy fields). **Byte-equivalence is load-bearing**: a `strict` answer seeds `terminal_policies.citation_existence: strict` on the Material Passport at the point it is materialized (the finalizer stays the sole policy *evaluator*); a `mark only` or absent answer records the PCR row and **writes nothing** — per-key absence already means advisory (Invariant 7), so an unprompted run is byte-identical to pre-#392. Plan mode exempt (mirrors Step 12). No default changes anywhere. Guarded by `scripts/check_392_citation_verification_intake.py` (4 invariants, mutation-verified): Step 13 heading present (rename = fail-loud parse error), the no-handoff directive affirmatively reaches Step 13 (`then Step 13` — the exact #327 P1 orphaning that hit Step 12), PCR row present, and the advisory write-nothing rule + strict seeding target retained. 8 unit tests; wired into `spec-consistency.yml` + the CI pytest manifest. - **Layer-5 contribution-significance probes extended into plan mode and revision coaching (#393, blindspot-audit F-2, adjudicated shape 1).** ARS quality machinery was defect-oriented end-to-end — a paper could pass every gate and still be a micro-extension, because the only constructive contribution coaching (socratic_mentor Layer 5, SIGNIFICANCE & CONTRIBUTION) lived at the RQ-incubation stage. Layer 5 now defines three **later-stage anchored forms** with stable IDs — **L5-W1** "Ten years from now, what will citers say this paper established?", **L5-W2** "Remove this paper from the literature — what is missing?", **L5-W3** "If this paper succeeds, who would make different decisions as a result?" — and two later-stage surfaces consume them strictly by ID (the question text lives in Layer 5 and only there; a Layer-5 edit propagates by reference instead of forking — the cross-model review's P1 on a first draft that carried labeled copies): **(1)** `academic-paper` plan mode gains Step 2.5 CONTRIBUTION SHARPENING between chapter negotiation and the argument stress test — the mentor asks the user to articulate the contribution their own Chapter Summaries claim, quoting only user-written text; if the user articulates one, `[INSIGHT: contribution_claim]` records it in the user's words, otherwise the open question is carried into Step 3, never filled in; **(2)** `academic-paper-reviewer` Phase 2.5 gains step 3, a contribution framing probe alongside the existing prioritization steps (5→6 steps; no external step-number references existed), anchored to what the manuscript already claims. The orchestrator's Stage 3→4 coaching sketch now explicitly defers to the reviewer SKILL.md six-step list as authoritative (net-zero line edit — the surface has 1 line of v3.6.7 budget headroom left). Boundary is load-bearing (Kong L2 verb test, per the #393 adjudication that rejected shapes 2–3): questions only — never propose, substitute, rank, expand, or select a contribution claim. Prose-layer only; no schema, scoring, or agent-roster change. Two review gates, all findings adopted: codex cross-model (1 P1 + 2 P2 + 1 P3 — ID-based single-sourcing, verb-test tightening, orchestrator deferral, conditional INSIGHT) and an independent context reviewer (1 P1 + 1 P2 — the repo has TWO same-named `socratic_mentor_agent.md` files and plan mode dispatches the academic-paper variant, which had no Layer 5 and no Step 2.5 in its own flow, so the probe would never have fired: the agent prompt gains a Step 2.5 section referencing L5-W IDs by full path, the protocol's inline reference is path-disambiguated, and L5-W3's anchor permission is tightened to noun-phrase-swap-only). - **Interaction-count budget surfacing + Context Hygiene dispatch discipline (#388; DELEGATE-52 Items 4+5 from #89).** The two cheap, high-confidence follow-ups from the re-ranked DELEGATE-52 work order (arXiv:2604.15597), both prose-layer. **Item 4:** the v3.2 Budget Transparency block in `academic-pipeline/SKILL.md` now also presents an **interaction-count budget** at pipeline start — the paper's core result is that long-horizon corruption compounds with document round-trips, not token volume, so the block enumerates the round-trip caps the pipeline already enforces (2 revision loops, 8+5 Socratic coaching rounds, the integrity fix→re-verify loop), states the worst-case total for the chosen mode, and reports the accumulated count at every stage checkpoint. Advisory only — the per-loop caps remain the enforcement layer; exceeding the stated worst case signals an uncovered loop and must be surfaced, never silently continued. **Item 5:** `pipeline_orchestrator_agent.md` gains a **Context Hygiene at dispatch** block targeting the paper's distractor ablation (non-target documents in context measurably worsen outcomes): each handoff carries the receiving agent's declared inputs plus the Material Passport — never the accumulated pipeline as a convenience bundle; scratch output and superseded drafts do not ride forward (later stages read passport entries, not raw transcripts); supersession means dispatching the current version only, with prior versions retrievable through the versioned-artifact trail. The passport carry-forward obligations (claim/audit aggregates, `experiment_intake_declaration`, `slr_lineage`) are explicitly exempt — trimming applies to loose materials, never passport fields. Carries an epistemic-status line (dispatch-assembly discipline, not a runtime guarantee). Scope note recorded in #388: this lands the single-dispatch-point version; #89's per-downstream-agent sketch stays open under the parent. Items 1, 2, 7, 8, 9 remain tracked in #89. - **Repository-hygiene CI (#151).** A `repository-hygiene.yml` workflow runs gitleaks over the **full git history** on every PR and main push, with the upstream default ruleset and **no custom rules**. The binary is version-pinned (8.30.1) and **checksum-verified** rather than pulled via the marketplace action (which needs an org license key; a pinned release binary keeps the supply chain auditable), and `--redact` keeps any genuine hit out of public CI logs. The only local configuration is a false-positive allowlist (`.gitleaks.toml`): the 2026-06-10 baseline evaluation found **43 findings across 518 commits — every one a bibliographic citation key** (`Becht2019UMAP`, `vaswani2017-arxiv-v1`, `forthcoming2024`) in eval gold tuples / adapter fixtures / version-family examples matching the `generic-api-key` heuristic's key-shaped-string pattern, **zero true positives** — so those literature-identifier paths are allowlisted by path, never by rule edit (a new tuple under an allowlisted path needs no config touch). A seeded-credential mutation test confirms the configured scan still exits 1 on a real-pattern secret outside the allowlist (`github-pat` hit) — and recorded a method caveat: a low-entropy dictionary-word seed does NOT clear the entropy gate, so a valid mutation check needs a real-pattern, high-entropy seed. Closes the `defer:v3.10` evaluation with an **adopt** decision: all three decision criteria pass (no infra burden — public pinned binary; bounded maintenance surface — path entries only; post-allowlist FP rate 0 on the full history). - **Field-norm severity calibration across the reviewer surfaces (#215, Kim et al. 2026 arXiv:2605.20668v1 §5.1 W1 + §F.3.4).** Closes the paper's largest documented AI-reviewer failure class: a critique that is content-correct against a discipline-neutral standard but **severity-miscalibrated** because the reviewer lacks the subfield's accepted-practice prior (W1, n=54 — the CERN/LHCb reproducibility example), plus the sibling significance-boundary error from the "would addressing this change the core result?" formula (§F.3.4, 56 errors). Three reviewer surfaces are hardened, each at severity-assignment time and applying to **every** field-norm-dependent finding (not only CRITICAL): **`domain_reviewer_agent.md`** gains a Step 5 hard rule — a severity that rests on a field norm MUST be grounded in an external checkable source (a reference, venue/data policy, community standard, reporting guideline, or documented expert practice — explicitly **not** limited to a literature citation, and **not** model knowledge), else down-rate to advisory + `[FIELD-NORM UNVERIFIED]`; **`devils_advocate_reviewer_agent.md`** gains a 9th challenge dimension (the DA turns the lens on its own findings, since adversarial intensity amplifies an ungrounded norm into a CRITICAL) plus two required CRITICAL/MAJOR output columns `field_norm_boundary` + `evidence_crossing_rationale`; **`calibration_mode_protocol.md`** gains a Phase 3.5 severity-miscalibration measurement + a low/med/high histogram in the Calibration Report — a signal the binary FNR/FPR matrix cannot show, where the classifier rates whether the reviewer **supplied external grounding**, not whether the norm is factually correct (guessing norm-correctness would repeat the very W1 failure under audit). A **first-party regression fixture** ships at `evals/gold/field_norm_severity/` (10 cases — 5 W1 field-norm-boundary + 5 §F.3.4 significance-boundary — extracted verbatim from the paper with section/example-ID + paper-citation-token + verbatim-anchor provenance; the SAR 11.7T case flagged `exception: true` because experts concurred with the AI there). Because there is no deterministic detector for field-norm severity miscalibration, the fixture is a regression set, not a calibration set: `scripts/check_field_norm_severity.py` validates data integrity + first-party provenance (no FNR/FPR ritual), and `scripts/check_215_field_norm.py` asserts all three reviewer surfaces carry their blocks with **block-scoped** keyword checks (fence-aware) so a stray keyword cannot mask a missing rule. The two lints survived a three-pass cross-model (codex xhigh) review that drove finding count 4 → 2 → 0; every fix is mutation-tested (28 tests). Additive and backward-compatible; CI-wired via the spec-consistency workflow + pytest manifest. (#216 — the §F.3.6 reviewer-type parity half — was split out: it needs a different gold set of human-phrased vs AI-phrased paired cases.) - **Surface-Form Parity self-check (#216, Kim et al. 2026 arXiv:2605.20668v1 §F.3.6).** Closes the paper's reviewer-type asymmetry: an AI meta-reviewer applying **two standards keyed off prose style** — demanding literal precision from informal/vague (human-typical) wording, so it over-rejects correct concerns (29 of 41 correctness false negatives involved human reviewers), and crediting technical specificity in precise (AI-typical) wording, so it over-accepts incorrect ones (10 of 13 false positives involved AI reviewers). The root cause the paper names is a learned prior that *specificity correlates with correctness*. **Key design call (after a codex xhigh consult): the hook is prose style, NOT the author label** — so the mitigation is a *Surface-Form Parity* self-check (not "authorship parity"), and authorship is kept **out** of the runtime reviewer-item schema entirely (not merely audit-only). **Two verdict-time surfaces** carry the parity self-check (a codex review found the editorial synthesizer also arbitrates reviewer sub-claims and down-ranks "too vague" criticisms — exactly where §F.3.6 fires): **`devils_advocate_reviewer_agent.md`** gains a verdict-time parity self-check (a marker block, distinct from #215's severity-time gate) and **`editorial_synthesizer_agent.md`** gains a Step 1c arbitration-time check + a reworded "reduce weight if too vague" rule that fires only when vagueness makes a sub-claim unevaluable. The DA check: extract the checkable claim → judge it against the paper not the polish → do **not** down-rate informal/vague wording unless ambiguity changes truth conditions → do **not** credit technical specificity without checking → run the opposite-style counterfactual and revise / mark ambiguous on a flip. A **mixed-provenance regression fixture** ships at `evals/gold/surface_form_parity/` (7 cases: 4 `paper_verbatim` §F.3.6 examples + 2 maintainer-authored `counterfactual_rewrite` paired variants carrying `derived_from` + `semantic_equivalence_rationale` + 1 `maintainer_boundary` documenting the "unless unevaluable" clause). Because there is no deterministic detector for the surface-form bias and the 29/10 split is directional (§H), the fixture is a regression set, not a calibration set: `scripts/check_surface_form_parity.py` validates integrity + **provenance honesty** (paper_verbatim quotes the paper; maintainer-authored items never claim paper-verbatim) + **pair invariants** (paired items hold claim + verdict constant, differ only in framing) + no rotting pdftotext line anchors — no FNR/FPR ritual. The **schema decision is enforced at runtime** by `render_judge_view()`, a whitelist projection (judge sees only an index-derived opaque `handle` + `review_item_text`) proven by a serializer-strip test to leak no blind field — including the nested `provenance.reviewer_source` author label and the answer-encoding fixture `id` itself (`-cf` / `-ambiguous` suffixes, per codex review). `scripts/check_216_surface_form.py` asserts the DA carries every load-bearing clause **block-scoped + fence-aware** (six-class mutation suite). `run_evals` discovers the fixture and marks it `pending` (no native measurer, by design — pinned by a `test_run_evals` test so it cannot false-green through the eval gate). **Negative scope: #273 (rubric-aware calibration) is NOT folded in** — it is a different mechanism (an interpretive caveat with no detection claim); #216 carries a cross-reference only (design note + PR body + `manifest.yaml` `related_issues`), with no shared prompt / gold / lint / runtime wiring. Additive and backward-compatible; CI-wired via the spec-consistency workflow + pytest manifest. Design note: `docs/design/2026-06-09-216-surface-form-parity-design.md`. #### Changed - **Plugin-root `agents/` symlinks materialized as real byte-identical copies (#413, external audit).** The three `agents/*_agent.md` files were relative symlinks into `deep-research/agents/` (v3.7.0 Phase 2.1) — on Windows checkouts without developer mode / `core.symlinks`, and in zip-download installs, they materialise as one-line text files containing the link path, silently breaking the three plugin agents. Maintainer-adjudicated fix: real copies, with the single-source guarantee the symlinks provided (the v3.7.0 Pattern C3 rationale for symlinks-not-copies) taken over by a new CI lint, `scripts/check_agents_mirror_sync.py` — a hard-pinned mirror roster enforcing set equality (a deleted mirror silently un-ships an agent; an unrostered addition has no declared source), regular-file-never-symlink (the regression itself, checked *before* byte-equality because a symlink trivially byte-matches its own target), and byte-equality with the canonical source (fix hint names the copy direction: edit the source, re-copy, never edit the mirror). The two lints that leaned on symlink resolution adapt: `check_version_consistency.py` invariant 8 now excludes the mirror dir from the unique-agent count outright (real copies no longer dedup via `resolve()`; the exclusion is sound because the mirror lint pins every file there as a pure alias), and `check_v3_10_134_write_scope.py` I5 maps a root-`agents/` file BY NAME to its `deep-research/agents/` source before the roster check — with a negative test pinning that the mapping is not an allowlist (a name with no rostered source still flags as fail-open). 10 new mirror-sync tests (3 mutations killed: symlink-branch, byte-equality, unrostered-extra) + 5 adapted/added tests across the two existing suites; lint + pytest companion wired into spec-consistency CI. Cross-model review round (1 P2, adopted with an empirical repro): the I5 remap is restricted to DIRECT children of root `agents/` — a nested `agents/sub/agents/.md` no longer remaps to the deep-research source (which would have silently reopened the fail-open case the recursive glob exists to catch), pinned by a negative test. The `skills/` directory symlinks are unchanged — materializing those means duplicating the four skill trees, a separate decision if Windows source-checkout support is ever pursued. (2026-06-10 audit; follows the #301/#347 4.7→4.8 pattern).** Trigger: the primary session model moved to Fable 5, which inverts the v3.7.0 `model: opus` frontmatter floor on the three heavy commands (`/ars-full`, `/ars-reviewer`, `/ars-revision-coach`) into a **silent downgrade ceiling** — those commands now inherit the session model (the 11 light-mode `sonnet` pins are deliberate cost routing and stay; the plugin agents were already `model: inherit`). Display-name drift retired at the remaining pin sites: the `shared/cross_model_verification.md` primary-model row is now generation-agnostic ("the inherited Claude Code session model" — it stops needing a per-release bump), the SessionStart announce + `docs/PERFORMANCE.md`(+zh-TW) cost anchors are provenance-labelled ("measured on Opus 4.x", order-of-magnitude) instead of asserting a two-generations-stale "$4–6 on Opus 4.7", and the disclosure-protocol e.g. list is refreshed. OpenAI verifier lineup unified gpt-5.4 → **gpt-5.5 / gpt-5.5-pro**: the citation judge already defaulted to `gpt-5.5-xhigh` while the verification doc still taught 5.4 — and the availability case-glob `gpt-5.4*)` rejected 5.5 ids outright; web_search-on-Responses support and pricing were verified first-party 2026-06-10, legacy `gpt-5.4*` ids remain accepted, and the cost table is re-anchored on gpt-5.5 ($5/$30 per 1M). Routing smoke recalibration (#133 fixtures): **8/8 routing-class pass on Fable 5** (clarify/proceed plus all three escape-hatch behaviors — byte-0 honored, mid-message rejected, case-insensitive accepted); two destination picks additionally required the Routing-Rules/MODE_REGISTRY context the manual protocol provides. The acceptance threshold in `tests/fixtures/issue_133_routing/README.md` is reworded from "100% on Opus 4.7" to "100% on the current primary model" so the definition stops drifting per release. Two bare anti-hallucination tails on the compliance surfaces are kept as annotated debt (high-stakes domain, silent failure mode — in-file `harness-retirement` annotations added). Deliberately out of scope, tracked separately: re-baselining the #272/#273/#274 model-behavior premises against the Fable 5 system card, and a negative-framing sample-reframe of the top-3 agent files at the next minor. ## [3.13.0] - 2026-06-18 — Hook portability, provider-agnostic verification, guard correctness ### Fixed - **Write-scope guard: `CLAUDE.md` dropped from infra-protected globs (#459).** Closes the residual half of #448/#449. #449 anchored infra self-protection on `plugin_root` (fixing #448 for the plugin install layout), but under the traditional git-clone + symlink-into-`~/.claude/skills` layout there is no `CLAUDE_PLUGIN_ROOT`, the `plugin_root` fallback resolves to the cloned repo root, and a user working IN that repo has `plugin_root == workspace_root` — so the bare `CLAUDE.md` / `.claude/CLAUDE.md` infra globs matched the user's own `CLAUDE.md` and re-denied it (the #448 bug, on a layout #449 cannot distinguish: home turf and clone-as-user are the same runtime condition). Fix (codex-consulted, Option 2): remove `CLAUDE.md` and `.claude/CLAUDE.md` from `INFRA_PROTECTED_GLOBS`. Unlike every other infra entry, `CLAUDE.md` is NOT load-bearing — it documents the guard binding, it is not the binding, so editing it cannot fail the guard open. The load-bearing enforcement files (guard script, manifest, hooks, plugin metadata, agent frontmatter, lint) stay protected on home turf; protecting ARS's own instruction doc from agent edits belongs in review/CI, not the write-scope guard. 3 new tests + 1 retargeted, mutation-verified (re-adding the globs fails the new tests); 73 guard tests pass. - **Windows Python hook portability + graceful no-Python degradation (#454).** The `PreToolUse` write-scope guard was wired as a bare `python3 ".../ars_write_scope_guard.py"`. On Windows `python3` is commonly a 0-byte Microsoft Store App Execution Alias stub, so the hook errored before the guard's own fail-safes could run and spammed the hook log every call. A new cross-platform launcher `hooks/run_guard.sh` (POSIX sh; `hooks.json` now invokes it via `bash`) finds a REAL interpreter — `py -3` / `python3` / `python`, each verified by a marker probe that must exit 0 AND print the marker (a stub that prints then exits non-zero is rejected) — then runs the guard as a supervised, time-bounded subprocess. **Plan A graceful degradation** (the guard is optional v3.10 hardening; ARS core needs no Python): if no real interpreter is found OR the guard subprocess misbehaves (non-zero, timeout, empty, or non-JSON / missing-key output, validated by a real `json.load` not a substring grep), the launcher emits a valid pass-through hook JSON and exits 0 — it never exits non-zero and stays silent on stderr on these degraded paths (PreToolUse is a hot path; per-call stderr is the spam #454 is about). Healthy-guard stderr advisories are relayed. New `scripts/test_run_guard_launcher.py` (21 tests, run from a temp plugin layout so the guard is always resolved from the launcher's own `../scripts/` — no production env back door); the `hooks.json` CI assertion now requires a line-anchored non-comment guard assignment AND the `GUARD_OUT=$(... | run_bounded ... "$GUARD")` exec call site rather than a bare filename substring (a comment or an `echo`-wrapped decoy no longer false-passes). New `.gitattributes` pins `*.sh eol=lf`. README documents the Git Bash prerequisite (without it Claude Code falls back to PowerShell, which cannot run the `.sh` launcher, so the guard is inactive and the hook logs per call instead of no-opping quietly). - **Real-use findings**: a two-model dual-track implementation review (codex + gemini, both POSIX-reproduced) hardened the launcher far beyond the original wiring fix, and the cross-model split was load-bearing — each model caught real bugs the other missed. Round 5 (codex) found the marker probe ignored exit status, the guard ran unbounded, and the JSON check was a substring grep. Round 6, once the tests exercised the REAL watchdog on a host with neither `timeout` nor `setsid`, found three fail-open bugs the back-door tests had masked: the no-`timeout` fallback fed the guard an EMPTY stdin (a real `deny` was silently lost — the guard was dead on any timeout-less host), an un-reapable orphan grandchild could wedge the `$(...)` capture, and the watchdog could false-report a timeout for a command that finished within the bound. The independent **gemini track** then refuted codex's first race fix (a successful `kill` does NOT prove the child is still alive — after `wait` reaps it the pid can be RECYCLED, so a blind kill could hit an innocent process and still false-flag a timeout) and added two fail-open findings codex missed: a predictable `/tmp` fallback when `mktemp` fails is a symlink-attack surface whose redirect failure reads as a broken guard, and the CI exec assertion was still gameable by an inline comment or an `echo`-wrapped call. Final state: stdin stashed on fd 3, stdout captured via a private temp file, timeout decided by a **done-file handshake** (the parent disarms the watchdog before reaping it; the watchdog kills/flags only while the done-file is absent — no pid-reuse race), `mktemp` failure degrades to pass-through instead of a guessable path, and the CI assertion is line-anchored. Orphan-grandchild leakage in the doubly-degraded no-`timeout`/no-`setsid` path, and a multi-megabyte payload held in a shell variable, are documented as accepted trade-offs (the real probe and guard spawn no grandchildren and ordinary hook payloads are small; the robust alternatives add temp-file lifecycle / symlink surface to a hot path). - **`draft_writer` dual-phase static union documented + POSIX-safe Windows path matching (#451, #330).** Documentation + portability fix for the draft writer's dual-phase static union; path matching made POSIX-safe for Windows checkouts. ### Added - **Provider-agnostic cross-model verification (#455).** The cross-model verification layer now accepts OpenAI-compatible endpoints (MiMo, DeepSeek, self-hosted) alongside first-party OpenAI via a normalized compatible-verdict path (`scripts/cross_model_verification/normalize_compat_verdict.py` + `check_cross_model_verification_sync.py`). The grounded first-party OpenAI path is preserved and deliberately NOT routed through the standard `OPENAI_BASE_URL` (so an existing proxy user is never silently downgraded to the ungrounded compatible path — the grounded-proxy gap is tracked separately in #456). Design: `docs/design/2026-06-16-453-provider-agnostic-cross-model-verifier-spec.md`. - **Opt-in Socratic adjacent-framing probe (STORM-borrowed perspective expansion) (#461; `deep-research` 2.10.0 → 2.11.0).** When `ARS_SOCRATIC_ADJACENT_PROBE=1` is set, the Socratic Mentor may, in **exploratory** sessions during **Layer 1 (Problem Framing)**, surface ONE adjacent research framing the user has not raised — as a pure question ("an adjacent facet you haven't raised: — include it, or set it aside?"), never a proposed idea. Borrows the *intent* of Stanford OVAL STORM / Co-STORM (https://github.com/stanford-oval/storm): STORM's perspective discovery and Co-STORM's moderator inject framings adjacent to — but not directly answering — the current question to break local stagnation. ARS anchors framings in **LLM internal knowledge (zero retrieval)**; majority-favour is a deliberate tradeoff for the novice target (mainstream-facet visibility helps researchers who haven't seen enough), with external-TOC retrieval left as a pluggable forward note. Hard-bounded by the Kong L2 verb test — never propose/substitute/rank/expand/select; surface-and-ask only, one facet at a time, max 2 per session ≥3 rounds apart. S4 (Scope Stability) is repurposed as an intensity knob (early scope-lock raises the tendency), reusing existing state — no new counter. One-push-then-retreat on decline; `[ADJACENT-PROBE: ...]` log tag flows into Stage 6 self-reflection (a high decline rate is the bias-visibility signal). Default OFF. No new agent / mode / schema — prose-layer only, same shape as the v3.5.1 Reading Probe. Gate fires **exploratory** (opposite of the goal-oriented Reading Probe). Two review gates, all findings adopted: a spec-compliance pass (✅) and an independent semantic red-line reviewer that caught a Critical — the canonical GOOD example originally used "the teacher's mediating role," a hypothesis disguised as a category word (semantically identical to the BAD-propose row, and contradicting the agent's own WP14 flag); replaced with a true perspective phrase so the GOOD/BAD boundary is semantic, not grammatical. New lint `scripts/test_adjacent_framing_probe_lint.py` (10 tests, mutation-verified; adding the sibling env var also exposed and fixed an over-broad regex in `test_reading_probe_lint.py` that mis-flagged it as drift). See `deep-research/agents/socratic_mentor_agent.md` §"Optional Adjacent-Framing Probe Layer" and `docs/design/2026-06-18-socratic-adjacent-framing-probe-spec.md`. ### Chore - **Zenodo DOI added (#443, #434).** `CITATION.cff` + README DOI badge wired to the Zenodo concept DOI. ## [3.12.1] - 2026-06-15 — Reviewer-response triage modes (PR #433 integration) ### Added - **`deep-research` `three-way-scan` mode** — a lightweight WHY/HOW/WHAT paper-comparison triage that sits between `quick` and `lit-review`. Produces a per-paper WHY/HOW/WHAT shortlist plus a cross-paper synthesis (common WHY, divergent HOW, strongest WHAT, unresolved gap), and escalates to `lit-review` / `systematic-review` for full coverage. (`deep-research` 2.9.4 → 2.10.0) - **`academic-paper` `rebuttal-audit` mode** — standalone advisory QA of an author's existing rebuttal/response draft against the reviewer comments (per-comment coverage table + gap list + risk flags for tone/evidence/misread). It generates nothing and, because a standalone invocation runs outside the pipeline, it **explicitly suppresses** Schema 11 emission / Material Passport writes / `ready_to_submit` status — enforced by a new `check_rebuttal_audit_guard()` lint with mutation coverage. Routed by input shape: both reviewer comments AND an existing draft → `rebuttal-audit`; comments only → `revision-coach`. - **`revision-coach` scope extension** — its trigger/docs now cover pushback/disagreement posture and non-journal scopes (conference rebuttal, grant-panel response, transfer-after-review). - **`/ars-3w` and `/ars-rebuttal-audit` slash commands.** ### Credit Integrated from [@Yaobin29](https://github.com/Yaobin29)'s [PR #433](https://github.com/Imbad0202/academic-research-skills/pull/433). The original PR proposed a standalone `reviewer-response` skill; this release folds its genuinely-novel parts into existing skills as modes, per ARS's mode-based architecture. The `rebuttal-audit` mode rescues that PR's `audit` concept. Suite mode count 25 → 27 (still 4 skills). ## [3.12.0] - 2026-06-08 — Kong auto-research feature track: experiment provenance, figure fidelity, cross-paper contradiction, partial-evidence decomposition ### Added - **Experiment Provenance Intake + claim→experiment alignment — a schema-first evidence-ledger layer for experiment-backed claims (#260, Kong et al. 2026 §3.3 + §7.4.3).** ARS deliberately keeps experiment *execution* outside the pipeline; the scholar runs experiments externally and brings results back. This change adds the **intake + alignment** layer only — it does **not** run experiments, judge whether one was correctly designed/run/statistically-adequate/reproducible, auto-fill provenance, or require provenance for literature-only pipelines. Two blocks ship together. **Block A — `experiment_provenance[]` intake array:** a new optional Material Passport aggregate (`shared/contracts/passport/experiment_provenance_entry.schema.json`) where each scholar-entered entry carries a **nested `repro_lock`** (the same inline-object shape as the passport-level lock, re-declared not `$ref`'d because the source is inline prose, not a schema file), a `planned_vs_executed[]` record (each `executed:false` unit carries a gate-checked `skip_reason`), and `negative_results[]` / `known_limitations[]` arrays whose **key must be present** (an empty `[]` is well-formed and routes to a disclosure advisory; an *absent* key is malformed → gate FAIL, the absent-key rule ported from #261's C3). **Block B — claim→experiment alignment:** the claim manifest gains an optional per-claim `planned_experiment_ids[]` join field (parallel to `planned_refs`, minItems 1, optional-absent), and a new **fourth ref_slug-less claim-finding aggregate** `experiment_alignment_results[]` (`experiment_alignment_result.schema.json`) — alongside the existing `uncited_assertions` / `claim_drifts` / `constraint_violations` siblings — with an experiment-specific MECE verdict enum `{ALIGNED, OVERSTATED, NOT_SUPPORTED_BY_PROVENANCE, PROVENANCE_INSUFFICIENT}`. The verdict is **produced by the integrity verification agent AT the gate** (Stage 2.5 sampling / Stage 4.5 full), not by the citation-audit agent at the Stage 4→5 boundary — mirroring #261's Phase C3, so the row is emitted and gated in the same pass and the stage-ordering race (a verdict landing *after* the gate ran) cannot occur. A **mixed-evidence claim** carrying BOTH `planned_refs` and `planned_experiment_ids` is audited by both paths and the gate decision is **worst-verdict-wins** (an OVERSTATED experiment path blocks even when the citation path is SUPPORTED). **`experiment_id` is frozen at intake** (a post-intake rename is a re-intake event, not a silent edit). Seven new cross-array invariants land in `scripts/check_claim_audit_consistency.py` (JSON Schema cannot express cross-array integrity): **EP-INV-1** (experiment_id unique/passport), **EP-INV-2** (planned_experiment_ids resolve — doubles as the rename + forward-reference dangling-pointer guard), **EP-INV-3** (experiment ids ⟹ empirical kind; mixed literature+experiment allowed), **EP-INV-4** (declaration↔provenance symmetry), **EP-INV-5** (declaration well-formedness when present: `status` enum / `declared_by: scholar` / non-empty `declared_at` — so a malformed declaration like `status: "garbage"` FAILs deterministically instead of slipping past the symmetry check), **EA-INV-1** (finding_id unique), **EA-INV-2** (alignment-row references resolve; a dangling `experiment_id` is a structural FAIL, **never** a `PROVENANCE_MISSING` verdict — that value is deliberately absent from the enum, so no fake judge fields are forced for a row where no judge ran). A persisted passport-level **`experiment_intake_declaration`** closes the anti-skip circularity with a fail-closed legacy boundary, split across two enforcement layers (stated precisely, not conflated): the **lint deterministically enforces** declaration↔provenance *symmetry* (EP-INV-4) and declaration *well-formedness* (EP-INV-5); the **integrity gate (a Stage-1/Stage-4.5 check, NOT the lint) owns the `ars_version` numeric legacy decision and the declaration-presence FAIL** — a passport is `legacy_unknown` (advisory) only with positive `repro_lock.ars_version < #260-constant` proof, everything else (including a passport with no `repro_lock`, or one with no `ars_version`) is treated as post-#260 so the declaration is REQUIRED and its absence FAILs at the gate, meaning a new run cannot dodge it by making its version unprovable. The `ars_version` numeric half is deliberately left at the gate layer (not promoted to a lint constant) because the #260 release version it compares against is frozen at ship time, not at intake. Literature-only pipelines therefore still emit a one-line `no_experiments_declared` declaration (no `experiment_provenance[]` needed). Producers taught in lockstep (schema-first writer-binding discipline): the three manifest emitters (`synthesis_agent` / `draft_writer_agent` / `report_compiler_agent`) emit `planned_experiment_ids` when an experiment backs a claim; the integrity agent gains a new disclosure-only Phase (D6) carrying the POSITIONING non-goal verbatim ("does not judge whether the experiment was correctly designed, run, statistically adequate, or reproducible by ARS"); the orchestrator carries `experiment_alignment_results[]` + the declaration forward; README intake detection sets the declaration. **Drift guard:** the repro_lock field set is single-sourced in `scripts/repro_lock_validation.py` (imported by both `check_repro_lock.py` and the new standalone `check_experiment_provenance.py`), with a drift test asserting the nested schema's required keys equal the shared constants. **Three documented departures from the issue's literal text** (each corrected after a first-party tracked-repo read): `repro_lock` is an inline-prose object, not a schema file, so "inherit repro_lock" means nesting the shape, not `$ref`'ing a non-existent file; the claim manifest had no experiment pathway, so the join is *added*, not assumed; and "Path X / Tier-1 required / writer-binding" are not named conventions in the tracked repo, so the discipline is *described* rather than cited by a name a reader cannot find. Schema + manifest edit + 7 lint invariants + standalone shape validator + drift guard + integrity/writer/orchestrator agent prompts + README mirrors + `examples/passport_with_experiment_provenance.yaml` (2 experiments, a mixed-evidence claim, an OVERSTATED alignment row) + full TDD suite (schema ±, fail-closed symmetry, declaration well-formedness, mixed-evidence two-row, verdict-derivation, mutation-verified non-vacuous invariants, reverse-invariant producer pins, drift, literature-only regression). The new schemas, the manifest field, and all seven invariants are additive and backward-compatible. Spec: `docs/design/2026-06-08-260-experiment-provenance-intake-spec.md`. - **Cross-paper contradiction inventory — structured, inspectable enumeration in the synthesis layer (#262, Kong et al. 2026 §7.4.2).** `synthesis_agent` already had prose-level contradiction handling (Anti-Pattern 3, the Step 3 Contradiction Resolution procedure, and the Contradictions & Resolutions table), but that prose narrative-discussed contradictions (including reconcilable-vs-irreconcilable verdicts) without making the *set of assessed paper-pairs* and the *unresolved / checked-clear* pairs enumerable for the scholar to confirm — the multi-paper relational-reasoning gap Kong et al. 2026 (arXiv:2605.18661 §7.4.2) document for research-synthesis systems. A new **Step 3b — Cross-Paper Tension Inventory** is added **additive to** (not a replacement of) the existing Step 3 prose: the agent emits a `cross_paper_tensions[]` markdown block — one entry per assessed candidate pair carrying `pair_id`, `paper_a`/`paper_b`, `candidate_basis`, `overlap_topic`, `a_finding`/`a_evidence_pointer`, `b_finding`/`b_evidence_pointer`, `pair_assessment`, `resolution_status`, an iff-resolved `resolution_pointer`, and `scholar_confirmation`. **Prose-layer only — no JSON Schema, no lint invariant, no gold fixture** (mirroring the #214 / #261 prose-layer decision, NOT the #213 schema-layer one): the producer (`synthesis_agent`) and the readers (the scholar plus the report/integrity LLM agents) all read prose, there is no deterministic downstream parser, and the judgment that matters — "is this a genuine contradiction vs. a conditional difference" — is irreducibly semantic, so machine-validating the YAML shape would prove field presence, not contradiction fidelity. **This deliberately departs from the issue's literal acceptance** (which read "schema adds `contradiction_pairs[]` block" + "calibration gold set accuracy ≥ 0.75"): the named "downstream consumers" (formatter, integrity_verification) are themselves LLM agents reading markdown — there is no machine consumer — so a schema would be the exact false rigor #261 rejected, and 20 LLM-judged pairs are too few and too nondeterministic across runs to wire as a hard CI gate (**no calibration artifact ships** in this change — any future or manual calibration should be recorded out-of-band with its model/date/prompt + a confusion matrix and stay non-blocking, never a pass/fail gate). The **field model is corrected from the issue's non-MECE draft**: the issue's single `conflict_type ∈ {contradictory, conditional_difference, resolved_in_synthesis}` folded conflict *nature* and resolution *status* into one enum and referenced an `insufficient_overlap` value not in it — these are split into orthogonal axes (`pair_assessment ∈ {contradiction, conditional_difference, no_material_conflict, insufficient_overlap}` × `resolution_status ∈ {resolved_in_synthesis, flagged_unresolved, not_applicable}`), and each side gains an `evidence_pointer` so a finding cannot be stated as free text the paper does not support. **Candidate-pair scoping is a recall-limited heuristic, not an algorithm:** an LLM agent does not execute an O(K²) enumeration, so the design states it as bounded candidate-edge generation (include a pair on shared RQ subtopic / shared construct / opposite finding direction / bibliographic coupling / scholar flag) with two honesty rules — bibliographic coupling is an *inclusion* signal only, never an *exclusion* rule (same-camp papers cite the same priors and tend to agree; cross-camp contradictions have low coupling), and cross-neighborhood pairs can be missed, so every inventory carries a mandatory **Coverage Note** stating the denominator and the explicit recall limitation and the agent must never write "all contradictions addressed." Inherits `synthesis_agent`'s narrative-side discipline unchanged (advisory-only: the scholar makes the final call; the agent emits `scholar_confirmation: pending`, never self-confirms, simulates no audit step, and reads no entry frontmatter). No `#111` dependency (that is a single boolean, per the issue's own correction). Adds `examples/contradiction_pairs_example.md` (6-paper remote-work synthesis covering a genuine unresolved contradiction, a resolved conditional difference, an *un*resolved conditional difference (so both resolution states of one assessment are shown), a no-material-conflict pair, an insufficient-overlap pair, and a Coverage Note that names the still-unpaired cross-neighborhood paper). Agent-prompt + output-template + doc example only; no schema, lint, or executable change. - **Figure/Table Fidelity Gate — the visual analog of the §F.3.2 partial-evidence trap (#261, Kong et al. 2026 §3.4).** The VLM Figure Verification Protocol checked *"does the rendered figure match the source data?"* (a faithful-rendering check) but could not check *"does the caption's interpretation follow from the data, and does the manuscript cite this artifact for a claim it actually supports?"* — a figure can render perfectly while its caption overstates the data or the manuscript cites it for an unsupported claim (Kong et al. 2026, arXiv:2605.18661 §3.4). This is the visual counterpart of the prose partial-evidence trap addressed for citations in #213 and for review synthesis in #214; same trap, different artifact type, separate implementation. **Prose-layer only — no JSON Schema, no lint invariant, no gold fixture** (mirroring the #214 prose-layer decision, NOT the #213 schema-layer one): the `figure_table_trace[]` producer (`visualization_agent`) and consumer (`integrity_verification_agent`) are both LLM agents reading a markdown Figure Package, so there is no deterministic downstream parser and machine-validating the YAML shape would be false rigor. `academic-paper/references/vlm_figure_verification.md` gains a **Figure/Table Trace** section defining a `figure_table_trace[]` block — one entry per figure (or manuscript table that has an entry) carrying all six required keys — `artifact_id`, `source_data`, `transformation` (`{script, hash}` OR a precise manual-derivation pointer — vague values like "computed manually" are treated as untraceable), `caption_claim`, `supported_manuscript_claims` (each as claim text + optional locator, not a bare id, since the visualization agent can run before the draft's claim manifest exists), and `limitations` (present even when `[]`). `visualization_agent.md` emits the block in the Figure Package (new Step 6.6) and `integrity_verification_agent.md` Phase C gains **C3. Figure/Table Caption Fidelity** running at Stage 4.5: entry well-formedness (a malformed entry missing any of the six keys short-circuits to FAIL) plus four fidelity checks — trace completeness, caption-claim support (does the *interpretation* follow from data+transformation, with compound captions decomposed into atomic sub-claims using the #213 idea **as prose guidance only**, no `PARTIAL` verdict / `sub_claim_breakdown` imported; an entry takes its weakest sub-claim's verdict), bidirectional manuscript-claim linkage (each listed claim must reference the artifact and not overstate it, AND every substantive manuscript use of the artifact must be listed — incidental/structural mentions exempt), and limitation visibility (a known limitation must reach caption/Discussion/Limitations). **Severity is split, not blanket-advisory:** a caption that contradicts the data, an untraceable claim-bearing artifact, a missing/overstated manuscript link, or a dropped known limitation **FAIL (block)**; only uncertainty signals are advisory — an empty `limitations: []` emits a named `[FIGURE-LIMITATIONS-EMPTY]` note (never a silent pass) and a legacy figure with no trace surfaces a trace-unavailable note. At Stage 4.5, an updated Figure Package with no `figure_table_trace[]` (or one omitting an entry for a figure it contains) is a FAIL ("caption fidelity not verified"), so the check is not trivially skippable; a legacy figure with no Figure Package at all is the advisory case. C3 **inherits** the existing C1 data-cross-referencing layer (it does not re-render figures — that is VLM — or re-verify raw data — that is C1); its new coverage is interpretation and linkage. Adds `examples/figure_table_trace_example.md` (3-figure + 1-table ML ablation walkthrough covering a normal trace, a decomposed compound caption, and the empty-limitations advisory). Reference + agent-prompt/protocol text + doc example only; no schema, lint, or executable change. - **Sub-claim decomposition before citation judgment — the citation-layer half of the §F.3.2 partial-evidence trap (#213).** The unified citation judge (`academic-pipeline/agents/claim_ref_alignment_audit_agent.md`) emitted exactly one verdict per citation, so a compound claim ("X rose AND the effect held across Y") whose source supported one sub-claim but not the other was collapsed to a single binary check and the unsupported sub-claim was silently lost — the largest correctness-error class documented in AI meta-review (Kim et al. 2026, arXiv:2605.20668v1 §F.3.2). The judge now runs a required **Step 0**: decompose the claim into atomic sub-claims and judge each independently before choosing the citation-level verdict. A new **prompt-layer `PARTIAL` verdict** (supports some sub-claims, not all; no active constraint violated) is normalized at Step 6 to `judgment=UNSUPPORTED, defect_stage=source_description`, routing the unsupported sub-claim through the same gate-refuse path a fully-unsupported claim takes so partial support is never accepted as full resolution. **Baseline correction:** the issue body proposed adding `PARTIAL` to the schema `judgment` enum; first-party reading showed that is the wrong baseline — `PARTIAL` (like the existing `VIOLATED`) lives at the prompt layer, NOT in the schema enum, so the 18 cross-field invariants and the allowed-(judgment, audit_status, defect_stage)-matrix stay untouched (the normalized triple was already in the matrix). The decomposition is persisted in a new additive optional schema field `sub_claim_breakdown[]` on `claim_audit_result` (pre-#213 entries validate unchanged); its **presence — not the defect_stage value — is the machine-readable partial-support signal** for downstream consumers. A new lint invariant **INV-19** pins the full normalization (breakdown present ⟹ `judgment=UNSUPPORTED` AND `defect_stage=source_description` AND true-partial: ≥2 items with ≥1 SUPPORTED AND ≥1 valid non-SUPPORTED sub_verdict), mutation-verified to discriminate. **Malformed `PARTIAL`** (breakdown absent / <2 items / not true-partial) takes the `audit_status=inconclusive [partial_breakdown_malformed]` path, never a silent bare `UNSUPPORTED`. Calibration gains **5 partial-support gold fixtures + a `partial_support` subset metric** (`scripts/claim_audit_calibration.py`): because partial fixtures carry `expected_judgment=UNSUPPORTED`, a judge that stops decomposing and emits bare `UNSUPPORTED` passes the aggregate FNR gate; the subset metric counts a partial fixture as passed ONLY when the judge emits `UNSUPPORTED` AND a well-formed true-partial breakdown, so the regression surfaces as `miss_rate > 0` while the aggregate stays green. The synthesis-layer sibling (#214) is out of scope. Schema + lint + judge-prompt + calibration + protocol-doc; the schema field and INV-19 are additive and backward-compatible. - **Sub-claim inventory before consensus in the editorial synthesizer — the synthesis-layer half of the §F.3.2 partial-evidence trap (#214).** The synthesis-layer sibling of the citation-layer #213. The editorial synthesizer (`academic-paper-reviewer/agents/editorial_synthesizer_agent.md`) aggregated consensus over a whole weakness bundle, so a compound weakness whose sub-claims carried different reviewer support was collapsed to one verdict and the minority sub-claim was lost — the single largest correctness-error class in AI meta-review (Kim et al. 2026, arXiv:2605.20668v1 §F.3.2). **Prose-layer only:** the synthesizer emits a human-facing decision letter + revision roadmap, not machine-readable judge rows, so there is no deterministic consumer for a #213-style schema field / lint invariant / gold fixture — adding one would be unrequested abstraction. The `sub_claim` vocabulary aligns with #213; its architecture is not imported. **Step 1 splits** into `Step 1a — Reviewer Summary Matrix` (retained) + `Step 1b — Weakness Sub-Claim Inventory` keyed on `sub_claim_id` (only weakness bundles decompose; recommendation/confidence/counts stay in the 1a matrix). **Step 2 computes consensus per sub-claim** over an absolute denominator of the 4 non-DA reviewers (`position ∈ {raised, corroborated, not-mentioned, disputed}`; `not-mentioned` is silence, never opposition or agreement). **Mutually-exclusive dispositions with explicit precedence:** `conflict ≥ 1 → SPLIT` first, otherwise by `agree` count (`4→CONSENSUS-4`, `3→CONSENSUS-3`, `2→corroborated finding`, `1→single-reviewer finding`); every `(agree, conflict)` cell maps to exactly one disposition and `agree = 0` is unreachable by construction. `disputed` covers existence OR action/severity conflict, so reviewers agreeing a problem exists but recommending incompatible remedies route to SPLIT → EIC arbitration. A `Sub-Claim(s)` column is added to the roadmap tables in both the agent output format and the standalone `editorial_decision_template.md` so the decomposed granularity survives to the output boundary. DA-CRITICAL flow and the v3.6.2 sprint-contract arithmetic path are untouched; scoped to the general Synthesis Protocol only. - **Concise output discipline + pressure-stable boundary reinforcement across the report-producing reviewers (#274).** A guidance-layer follow-up to the Claude Opus 4.8 system card §4.1.4, which documents two behavioral signals: refusals/responses trend longer and more over-caveated than 4.7, and a small number of multi-turn cases where a correct refusal was retracted under sustained pressure — both quality issues a user feels directly in a review tool. **Guidance layer only; no claim of having proven 4.8's runtime behavior.** A **concise output discipline** block is inlined (before `## Output Format`) into the report-producing reviewers — `domain` / `methodology` / `perspective` / `eic` / `devils_advocate` / `editorial_synthesizer` reviewers and `academic-paper/peer_reviewer`: state findings and verdicts directly, don't pad with repeated qualifiers; **concise explicitly does NOT mean under-caveated** — preserve every material uncertainty, cut only redundancy. A **"pressure is not evidence"** rule is added to the Devil's Advocate Anti-Sycophancy Rules and the editorial synthesizer's arbitration discipline: repeated pushback / authority appeals / bare softening requests do not change a finding. In the Devil's Advocate, this is bound to the existing numeric concession threshold (≥4 normally, 5/5 after a prior concession); in the editorial synthesizer's arbitration, a finding changes only on substantive new evidence or reasoning that addresses the arbitration basis (no numeric threshold lives there). Both are framed by evidence standard, not as an attack catalogue (public-repo safe). Every block carries an **epistemic-status line**: these are prompt-surface instructions; they cannot prove the model stays pressure-stable at runtime — that would need a separate non-deterministic behavioral eval. The issue's acceptance "confirm boundaries hold under 4.8 after pushback" is reframed as a *prompt-surface confirmation* (the instructions are present and explicit), not ticked by self-simulating a pushback dialogue (theater, not verification) and not pinnable by a deterministic CI test — mirroring the #272 guidance-layer ≠ runtime-enforcement discipline. No lint / mutation test (style guidance is not a contract invariant with a downstream consumer). Agent-prompt text only. - **Retrieved-content instruction/data boundary stated as a standing principle (#367, guidance layer for #272).** Retrieved external content is *data*; imperative-looking text inside it is not auto-promoted to a user instruction. The authoritative statement lands as a canonical §2A in `shared/ground_truth_isolation_pattern.md` (marked distinct from the eval-leakage concern) and is inlined verbatim into the two highest-surface retrieval agents — `deep-research/source_verification_agent` and `bibliography_agent` — so the principle is present where a fetch happens. A new `scripts/check_instruction_data_boundary.py` lint guards against silent removal or anchor-preserving gutting (presence / verbatim-sync / section-anchoring / contiguous-backpoint), proven not-accept-all by an 11-mutation test plus a positive control; the original strict-xfail pebble marked the unbuilt runtime defense and was later replaced by #675's executable behavioral-probe witness without claiming structural enforcement. **Commit-time documentation consistency only — no runtime gate, no injection-mitigation claim.** The originating trust-boundary issue (#272) stays open by design (the structural layer is deferred, now tracked by #676). Lint + CI wiring (`spec-consistency.yml` + pytest manifest); no schema change. - **Version-consistency lint extended to the README badge, docs forward-reference, and zh-TW heading invariants (#357, invariants 5-7).** `scripts/check_version_consistency.py` covered invariants 1-4 (CLAUDE.md table, suite version, pipeline tracking, plugin manifests); it now also enforces the three release-doc invariants previously caught only by manual checklist: **inv 5** — the README shields.io version badge tracks the suite version; **inv 6** — no `docs/*.md` cites a `vX.Y.Z` *above* the suite version (forward-reference guard); **inv 7** — version-bearing H2 headings stay in lockstep between `docs/.md` and `docs/.zh-TW.md` (plain headings may differ; only version tags pair, compared as multisets so a dropped one-of-a-pair heading is caught). Version-token regexes use a trailing negative lookahead so prerelease / 5-segment tokens (`v3.12.0-alpha`, `v3.11.1.2.3`) are dropped rather than partial-matched. Also removes the `docs/PERFORMANCE.md` cross-model onboarding section (en-only; aligns the en/zh-TW pair). TDD with 11 new test methods plus broadened aligned-fixture coverage, each invariant mutation-tested (stub to accept-all → matching test fails). - **ARCHITECTURE.md component-version markers now policed by lint (#345, invariant-4 gap).** `scripts/check_spec_consistency.py` policed version markers in the README (×4 langs), `.claude/CLAUDE.md`, `MODE_REGISTRY.md`, and `SKILL.md` — but not `docs/ARCHITECTURE.md`, where six "current academic-pipeline component version" strings were missed by the v3.11.1 bump and caught only by a manual first-party sweep (#343/#344). A new `check_architecture_component_version()` parses the suite version from `.claude/CLAUDE.md` and asserts the six current-component markers equal it (the mermaid orchestrator node + the component table row + the four stage rows). It anchors on the `academic-pipeline ` component pattern and never inspects the `timeline` block, so a stale current-component marker fails while a feature-history marker (`vX.Y.Z : `, which records *which* version shipped a gate and must not be bumped on a patch) is left alone — the distinction a naive `v3.x` grep would corrupt. The version regex captures the repo's full 4-component grammar (the suite shipped v3.9.4.2) with a hard right boundary so a 3-component marker can't partial-match inside a longer one, and the component/stage row scan is anchored to markdown table rows so a narrative provenance mention isn't wrongly policed. Wired into `spec-consistency.yml`; 7 tests (aligned passes / stale component fails / stale timeline marker does NOT fail / missing markers fail / 4-component edge cases). - **Same-family / rubric-aware calibration epistemic note (#273, Claude Opus 4.8 system card §6.3.7 / §6.6.3).** An interpretive, doc-only follow-up to the system card's report of modest, partly-unverbalized **grader-awareness** signals (the model sometimes optimizes toward what a rubric appears to reward). ARS leans on rubric / gold-set judging (reviewer calibration, the citation-claim judge), so this affects how calibration numbers should be *read* — not what the suite does. **Zero detection / mitigation claim:** ARS does not and cannot detect or correct grader-awareness (the system card's own point is that it can be unverbalized); the only honest claim is interpretive. All changes land in `calibration_mode_protocol.md` "Failure cases this mode does NOT fix" plus a one-line pointer in `integrity_verification_agent.md`. An **umbrella "same-source evaluation risk" framing** names two forms — the existing *factual* form (same-source hallucination — fabricated references; canonical in the Anti-Hallucination Mandate, unchanged) and a new *behavioral* form (same-family rubric optimization), cross-referenced both ways; the integrity block's WebSearch counter-rules are explicitly scoped to the factual form only and are not edited to imply they mitigate rubric-aware judging. An **epistemic note** states that under same-family / rubric-aware judging the measured calibration error is a *possible under-estimate, not a ceiling*. A **cross-model positioning** clarification resolves the doc's own opt-in-vs-default-on tension (cross-model is opt-in "for best results" in ordinary reviewer / judge paths; calibration mode is the explicit default-on exception once invoked; absent cross-model is warn-and-continue, never a gate; the consent / privacy boundary for sending a manuscript to another provider is preserved). A **single-model paraphrase spot-check** is documented but honestly de-powered — reword the rubric and re-judge, stated plainly to reveal only *surface wording sensitivity*, unable to detect unverbalized grader-awareness, and no proof the judgment is correct (no score, no threshold, no gate). No schema, no lint, no gate, no calibration-threshold change. - **Kong auto-research META closeout — negative scope + Tier D design lessons (#255, Kong et al. 2026).** Closes the Kong et al. auto-research survey META after every feature sub-issue (Tier A #256–#259, Tier B #260–#262, Tier C #263, Schema follow-ups #266/#268/#269) had merged; the two remaining closing conditions were documentation-only and defined the project's *negative scope*. `POSITIONING.md` gains a **"Rejected mechanisms (autonomous-research anti-patterns)"** section placed after "What this is not", enumerating the five autonomous mechanisms ARS does not do — end-to-end pipeline, idea-generation agent, Paper2X auto-generation, autonomous experiment execution, wet-lab automation API — each with a Kong anchor and, for the three that abut shipped features, an operationally-checkable CONSIDER-vs-REJECT line (idea-generation ≠ shipped #257 wording advisory; Paper2X auto-gen ≠ fidelity audit; autonomous experiment execution ≠ shipped #260 provenance intake). Two **Tier D design-lesson docs** land under the existing `docs/design/…lX…` convention (not a new directory): **L1** frames copilot-vs-auto-research as a research-state-authority review test ("does this let ARS create / select / execute / advance a research object of record without a scholar-authored seed or confirmation?"), and **L2** sharpens the advisory-vs-idea-generation line for research questions with a verb test, cross-linked from POSITIONING.md and from #257. Verification notes split a verifiable claim (no autonomous mechanism in first-party ARS today; #257 / #260 are advisory / provenance gates) from a design commitment (a recorded boundary and review criterion, not a runtime guarantee). Documentation only — no schema, agent, or lint change. ### Fixed - **Originality weight reconciled to 20% across reviewer reference docs; rubric weights now lint-policed (#396).** `review_criteria_framework.md` stated Originality at 15% (plus a 7-dimension weighted formula and its own score-to-decision mapping) while the operative scoring source — `quality_rubrics.md`, which the peer-review report template instructs reviewers to score against — and `academic-paper/SKILL.md` rule 14 both say 20% with a 5-dimension aggregate. The framework doc no longer restates any number: its dimension headers drop the weight suffixes (qualitative level descriptors stay) and §4 defers weights, formula, and decision mapping to `quality_rubrics.md` by name, noting that Literature Integration and Significance & Impact are reviewer-specific optional dimensions outside the numerical aggregate. Recurrence is guarded by a new lint, `scripts/check_rubric_weight_consistency.py`: quality_rubrics dimension-header weights must match its own aggregation-formula terms, the weights must sum to 100%, SKILL.md rule 14 must agree, and the framework doc must not restate a weight (`Weight NN%` / `(NN%)` both fail). Mutation-verified on all four invariants; wired into `spec-consistency.yml` + the CI pytest manifest. Surfaced by codex during cross-model review of the 2026-06-10 researcher-blindspot audit (F-14). - **Score-trajectory scale contradiction reconciled to 0-100 (#399, found during the #396 reconciliation).** `shared/handoff_schemas.md` declared score_trajectory scores as "1-5 scale" while every producer and consumer is 0-100: the report template scores 0-100 per `quality_rubrics.md`, and the canonical Early-Stopping Criterion is explicitly "delta < 3 points **on the 0-100 rubric**" (`academic-pipeline/SKILL.md`). The 1-5 comment is a pre-v1.4 fossil — the reviewer changelog (2026-03-08) records "Dimension Scores upgraded from optional 1-5 to required 0-100". Schema comments now say 0-100 (scale sourced from `quality_rubrics.md`; dimension *names* still from the framework doc), and the trajectory protocol's Stage 6 example — which mixed both scales in one table (1-5 scores, a "-0.2 within tolerance" verdict, and an "overall delta = 4" that matched neither) — is rebuilt on 0-100 with internally consistent deltas and verdicts. **Exposure note (per the #399 acceptance):** the thresholds themselves were never wrong — they were always defined against 0-100 in SKILL.md; the risk was a consumer reading only `handoff_schemas.md`, whose 1-5 trajectories would make regression detection (delta < -3) near-unreachable and early-stop (delta < 3) near-always-on. No evidence either check ran on 1-5 data (no real re-review artifact set exists post-v2.7; see #395). The reviewers' Confidence Score `[1-5]` is a deliberately separate axis and is unchanged. - **Cross-model verifier now actually grounds its lookups, and an ungrounded result can no longer be laundered into `VERIFIED` (#346).** `shared/cross_model_verification.md` told the cross-model verifier to "search the web to confirm," but the shipped OpenAI / Gemini API call patterns wired in no web-search tool — so a copied example produced a verifier that was *told* to search but *could not*, answering from parametric memory and confidently returning `VERIFIED`. For a hallucinated-citation gate that is the worst failure (a false `VERIFIED` manufactures confidence), and it shares the generating model's exact failure mode — fluent-but-wrong from memory — for the one task (existence lookup) where grounding is the entire point. Two-part fix, both at the API-pattern layer: (1) the OpenAI pattern moves to the Responses API with the hosted `web_search` tool and the Gemini pattern enables the `google_search` grounding tool, so "search the web" is executable; (2) both patterns **gate the verdict text on proof a search ran** — they emit `NOT_SEARCHED` and discard the text when the API returns no grounding evidence (an OpenAI completed `web_search_call` item / a Gemini response whose `groundingMetadata` carries `webSearchQueries` *and* `groundingSupports` tying the verdict text to retrieved chunks), and a `VERIFIED` carrying no supporting source URL/DOI is downgraded to `NOT_SEARCHED`. The protocol moves from batched (≤5 refs/call) to **one grounded call per reference** so the grounding evidence maps 1:1 to each verdict (a single grounding trace on a 5-ref response proves *something* was searched, not that *each* reference was) — a deliberate cost-for-provenance trade (a 60-ref paper samples 30% capped at 15, so ~15 grounded integrity calls, documented in the cost table). `NOT_SEARCHED` is a new status distinct from a transport failure: a transport failure (non-2xx HTTP — `[CROSS-MODEL-ERROR]`) means "no cross-model opinion" (fall back to single-model); a `NOT_SEARCHED` (2xx, but no grounding evidence) means "an opinion we have decided not to trust," counted separately and surfaced for re-run or human review, never as agreement with a Claude `VERIFIED`. `academic-pipeline/agents/integrity_verification_agent.md` (the consumer) is aligned in lockstep: its behavior summary drops the stale "batches of 5", adds the `NOT_SEARCHED` / ungrounded handling, and splits transport-failure graceful-degradation from the `NOT_SEARCHED` path. Surfaced during the 2026-06 harness-retirement audit (#301) by a second-model cross-check pass and filed as a live correctness gap, not a harness-retirement item. Documentation + agent-prompt/protocol text only; no executable script or schema change. - **Cross-model grounding guards are now behavior-tested, and a fail-open in the Gemini source extractor is closed (#349, follow-up to #346).** The #346 grounding guards shipped as bash/jq inside `shared/cross_model_verification.md` with no automated test — a future edit to the jq, or a provider response-shape change, could silently stop it failing closed (the exact silent-false-`VERIFIED` class the guard exists to prevent). The contract-bearing jq is extracted into canonical files under `scripts/cross_model_verification/` (5 filters: OpenAI search-guard / text / sources, Gemini grounded-guard / sources), the documented bash now loads them via `jq -f` instead of inlining, and `scripts/test_cross_model_verification_guards.py` runs each filter against synthetic fixtures (grounded → extracts supported sources; from-memory / non-grounded → `NOT_SEARCHED` with blank sources), with two mutation tests proving the fixtures discriminate a working guard from an accept-all / naive one. **Fail-open fix (malformed-response hardening):** the source extractors trusted the shape and types of the model's grounding metadata. Several malformed-but-well-formed-JSON responses could fabricate a source (defeating the blank-source downgrade and resurrecting a false `VERIFIED`) or crash jq: a negative `groundingChunkIndices` silently selected a chunk from the *end* of the array; a string index, a `groundingChunks`/`groundingSupports` arriving as a string/object instead of an array, a Gemini chunk `uri` or an OpenAI `url_citation.url` that is a number/bool/object — each either crashed or surfaced a non-URL value as a "source". The canonical filters now fail closed on all of these: indices must be in-range non-negative numbers (`select(type=="number" and . >= 0 and . < ($chunks|length))`), the grounded-guard requires `webSearchQueries`/`groundingSupports` to be non-empty *arrays* (not merely truthy `length`, which strings/objects also have), every container on each extraction path is array-normalized before it is iterated or indexed (OpenAI `output` → `content` → `annotations`; Gemini `candidates` → `groundingChunks` / `groundingSupports` / `groundingChunkIndices`) so a container arriving as an object can't have its values surfaced, and extracted URLs are filtered to non-empty strings — so any malformed response yields blank sources → `NOT_SEARCHED` rather than a fabricated or crashing result. A doc-sync lint (`scripts/check_cross_model_verification_sync.py`) pins that the doc keeps wiring every canonical filter via `jq -f` and retains the `NOT_SEARCHED` / `CROSS-MODEL-ERROR` branches (with `REQUIRED_FILTERS` cross-checked against the on-disk `.jq` set so a new filter can't escape the lint). Both the test and the lint are wired into the CI pytest manifest + `spec-consistency.yml`, which now also ensures `jq` is present on the runner. Documentation + test/lint/CI only; no agent-prompt or schema change. - **Cross-model Gemini guard is rederived from the source extractor; malformed array elements no longer crash the OpenAI filters (#351, post-ship review of #349).** The post-squash review of #349 surfaced that the Gemini guard and the source extractor were two parallel jq programs asserted to agree, so each round found a new input where they diverged: a `groundingSupports` linking to no valid chunk (empty / negative / string / out-of-range / fractional index), a multi-candidate response where the guard's `any`-candidate scan passed on a grounded candidate while the extractor read the unsupported `candidate[0]`, or a non-string `uri`. In each, the guard passed while the extractor returned blank — and the blank-source downgrade only rescues `VERIFIED`, so an ungrounded `NOT_FOUND` / `MISMATCH` could be trusted as grounded. The fix is structural: `gemini_is_grounded.jq` now **embeds the exact same `candidate[0]` extraction `gemini_sources.jq` performs** and passes iff it yields ≥1 source AND a real `webSearchQueries` signal is present — so the safety invariant **guard-pass ⟹ at least one source extractable** holds by construction for every input shape, not by two predicates kept in sync by hand. (The guard is intentionally *stronger* than "has a source": a chunks-but-no-search response fails it.) Separately, `openai_text.jq` no longer crashes `join` on a non-string `text`, and all OpenAI filters type-check each array element as an object before reading `.type`, so a malformed element (`output: [5]`) is skipped rather than crashing. +17 behavior tests across the new invariant, multi-candidate / fractional / non-string-uri cases, and the array-element-crash paths (guards 27→44). Every hole and fix verified first-party. - **Judge-verdict cache key partitioned by prompt version so a prompt revision invalidates stale entries (#361).** The judge-verdict cache key included `judge_model` but no prompt-version component, so a judge-prompt revision (e.g. #213's Step-0 sub-claim decomposition) did not invalidate stale entries — a verdict cached under the old prompt was still served until the TTL expired, silently bypassing the new prompt logic (a pre-existing cache-key design gap surfaced as P2#1 in the #355 post-squash review; affects every prompt revision, not just the decomposition path). `_cache_key` gains a `prompt_version` component kept separate from `judge_model` (independent axes), and invalidation keys on **`JUDGE_PROMPT_SHA256`** — the SHA-256 of the canonical judge-prompt section, the single source of truth — so any prompt edit changes the key and invalidates stale entries with no reliance on a human bumping a label (`JUDGE_PROMPT_VERSION` is a decoupled human-readable label for logs/diffs only). **Fail-CLOSED on unknown version:** when the caller declares the prompt version `None`, the pipeline binds a run-local component (`__unknown__:`) so a stale entry is never served across an unknown-version boundary (cross-run hits disabled; within-run dedup for repeated citations preserved). A CI backstop `scripts/check_judge_prompt_version.py` hashes the canonical section (between the `JUDGE-PROMPT-CANONICAL` markers) and fails if it drifts from the pinned hash, forcing a re-pin in the same change (wired into `spec-consistency.yml` + the pytest manifest). The agent-prompt contract and lint docstring — which described invalidation as keyed on the `JUDGE_PROMPT_VERSION` label while the pipeline already falls back to the SHA256 — are re-attributed to the SHA256 fingerprint so a downstream implementer following the contract can't re-open the bug. RED→GREEN + mutation-verified. - **Judge-supplied rationale bounded on success-path rows + null rationale guarded (#360).** Judge-supplied rationale on success-path rows (`completed` + `constraint_violation`) is now bounded to the schema `maxLength=2000` via a shared length-budgeting choke point, and a non-string (null) rationale degrades to the default instead of aborting the audit run. RED→GREEN + mutation-verified. - **Failure-rationale bounding + PARTIAL gold-fixture requirement — two #213 sub-claim-decomposition gaps (#359, #213 follow-up).** Two correctness gaps in the new PARTIAL machinery (neither reachable on pre-#213 inputs), surfaced by the #355 post-squash integration review and confirmed first-party. (1) The malformed-PARTIAL fallback could emit a schema-invalid row: the parse error embedded the offending breakdown's repr in `detail`, which became the fallback row's rationale and could exceed the `claim_audit_result` `maxLength=2000`. Fixed by bounding `detail` at a single choke point — a shared `_AuditInvocationError` base whose `__init__` clamps `detail` so the `"{fault_class}: {detail}"` rationale always fits (budgeted against the widest fault-class prefix); `JudgeInvocationError` / `RetrievalInvocationError` inherit it. (2) Calibration could silently skip the atomic-decomposition metric: `validate_gold_set` did not require an `expected_prompt_verdict=PARTIAL` fixture to carry non-empty `expected_sub_claims`, so `_breakdown_covers_expected` early-returned `True` and scored `miss_rate=0` for any generic breakdown; a new rule (e) rejects such a fixture at ingestion (fail-closed). RED→GREEN + mutation-verified for both. - **Eval gold tuple 052 removed — a fabricated citation was mislabeled as a genuine unindexed paper (#250).** Gold tuple `052-valid-unindexed-regional-paper` was labeled `fabrication_intent: false` and its expert-verdict notes asserted it was a "GENUINE … real regional, non-English-indexed agronomy paper" (Sembiring & Ginting 2023, *Jurnal Penelitian Pertanian Regional*). First-party verification across all four resolvers (Crossref / OpenAlex / Semantic Scholar / arXiv) plus DOAJ, OpenAlex Sources, Crossref Journals, and general web search found no evidence the paper or the journal exists — a fabrication labeled as genuine, exactly the failure this repo exists to detect, and a direct violation of #250's closing condition (which requires a *first-party-verifiable* real-but-unindexed source). Functionally the tuple was redundant: the harness reduces pre-recorded `resolver_outcomes` (it does not live-query), and tuple 051 (the OQ-5 by-design false-negative, a no-identifier fabrication) already exercises the identical title-only-unmatched → unresolvable reducer path. Removes the tuple; `expected_outcomes.json` drops the 052 entry (51 entries); `manifest.yaml` `sample_n` 52→51 + drops the `valid_unindexed` distribution row; `check_evals_gold_set.py` drops `valid_unindexed` from `KIND_ENUM`; `citation_verification_summary.py` + test comments drop 052 references (expert-concordance 12→11, unresolvable support 7→6), realigning manifest/tests/summary with the gold-set README's already-described 51-tuple set. **#250 stays open** — the verified-real-but-unindexed canary is still genuinely unfilled; only a mislabeled synthetic proxy was removed. - **ACL/EMNLP disclosure rows regrounded to the ACL Admin Wiki canonical source (#242).** The ACL disclosure row pointed at the 2023 conference blog (still live, HTTP 200) but its content had drifted from ACL's current Exec-approved policy. The Admin Wiki — which ARR / EMNLP 2026 link to for current paper-integrity guidance — places disclosure in the Acknowledgements section and graduates it by use type, contradicting the old row's "dedicated Use of AI Assistance subsection". First-party verification: the Admin Wiki returns HTTP 200 via browser navigation with the full "Guidelines for Generative Assistance in Authorship" section present (the 418 reported in #242 was a curl-UA challenge, not a stable block; the repo has no CI link-checker, so the humans-vs-tooling URL tension recorded in #242 does not apply). The ACL row's Source URL → Admin Wiki anchor (access date 2026-06-07) with summary / required phrasing / disclosure location / prohibited uses / authorship / notes regrounded from the first-party wiki text and graduated per its clauses a–f (language-only and short-form input not disclosed; literature search needs no special disclosure but normal citation-accuracy rules apply; low-novelty text and AI-suggested ideas disclosed); the EMNLP sibling row → the EMNLP 2026 Paper Integrity Policy page (which defers to ACL's guidelines) and is consolidated to "see ACL row"; `disclosure_mode_protocol.md` prose aligned to Acknowledgements. An independent cross-model faithfulness pass against the first-party pages corrected two fluent-wrongness overstatements (the literature-search no-disclosure bucket; an EMNLP "adopts wholesale" claim). - **Stale Opus 4.7 primary-model strings retired + repro_lock run-time fields documented as placeholders (#347).** The 2026-06 harness-retirement audit (#301) found the agent prompts carry zero expired scaffolds, but two `shared/` files still pinned Opus 4.7 as the primary model after the 4.7→4.8 migration, and a `repro_lock` example hard-coded run-time snapshot values that readers copy verbatim. `shared/cross_model_verification.md`: primary model → Opus 4.8, with the primary "API ID" cell now reflecting that it is the inherited Claude Code session model rather than asserting an unverified `claude-opus-4-8` id string (cross-verifier ids `gpt-5.4*` / `gemini-3.1-pro-preview` confirmed current and left concrete), plus a note documenting why temperature is 0.1 (deterministic fact-check), closing the "undocumented sampling override" read. `shared/artifact_reproducibility_pattern.md`: the `repro_lock` example block uses placeholders for the three run-time snapshot fields (`ars_version`, `model.id`, `s2_api_protocol_version`) so a copy-paste records the actual run, not a stale literal (feature-introduction labels like `v3.3.5+` stay concrete). `examples/passport_with_repro_lock.yaml` left unchanged — a self-consistent historical snapshot, not a stale current-marker. ## [3.11.1] - 2026-06-06 — Post-ship correctness, hardening, and provenance fixes (#182 follow-up) A patch release rolling up the post-ship advisory fixes surfaced after v3.11.0: a cross-model consent-gate extension to the integrity + collaboration paths (#322), a per-entry backfill parallelization (#138), and seven correctness/hardening fixes across the citation-existence gate, the v3.10 policy layer, the eval harness, the domain evidence profiles, and the #310 security-boundary edge cases (#323/#327/#328/#329/#331/#332/#333). No new features and no breaking schema changes. One API note: the #332 `verify_citation`/ `verify_passport` signature gains required keyword-only parameters. This is a fix to a contract-violating code path that first shipped in v3.11.0, not a deliberate signature revision — the old signature emitted a schema-invalid `ref_slug: null`, so any v3.11.0 caller relying on it was already producing contract-invalid output. The only in-repo callers (the CLI + the internal `verify_passport`→`verify_citation` call) are updated in lockstep; see the #332 entry below for the full C-V4 rationale. ### Security - **Cross-model consent gate extended to the integrity-verification and collaboration-depth paths (#322).** The explicit-consent gate that fronts every `ARS_CROSS_MODEL` upload — established for the two Devil's Advocate paths in #310 — now also fronts the two remaining agent paths that send user-derived material to an external provider on the env var alone: `integrity_verification_agent` (sampled citation/reference metadata) and `collaboration_depth_agent` (raw dialogue turns, which can carry the user's private reasoning and unpublished material). The gate is also added at the `pipeline_orchestrator_agent` re-dispatch point so the observer's agent-internal gate cannot be bypassed at the orchestration layer (defense in depth). All three mirror the #310 wording: no automatic send, explicit user consent identifying provider + model + content class, `[CROSS-MODEL-SKIPPED]` + single-model fallback when consent is declined, and a backpointer to `shared/cross_model_verification.md`. The `collaboration_depth_agent` advisory-only / never-blocks contract is preserved — the gate gates only the upload, never the observer's scoring role. Agent-prompt text only; no schema or script change. ### Performance - **Parallelize the OpenAlex + Crossref backfill lookups per entry in `migrate_literature_corpus_to_v3_9_0.py` (#138).** When both `openalex_unmatched` and `crossref_unmatched` are missing for an entry, the two independent resolver calls (different hosts, per-instance throttle state, monotonic timing) now run concurrently via a 2-worker `ThreadPoolExecutor` instead of one-after-the-other, roughly halving per-entry network wait on a full backfill. Scope is deliberately bounded: only the two calls within one entry overlap — the corpus loop stays sequential (cross-entry parallelism is out of scope; the clients' per-instance throttle assumes serial use), all passport mutation / report bookkeeping / degradation logging stays single-threaded on the orchestrator thread, and an already-set field still never consults its client. A single missing field skips the pool and calls directly. Behavior is otherwise byte-equivalent to the sequential version, including the omit-on-`Unavailable` partial-degradation contract (now surfaced via `Future.result()`). Adds 2 tests (barrier-verified parallel dispatch + the previously-untested API-down degradation path); the 6 existing migration tests pass unchanged. ### Fixed - **Two edge-case correctness fixes from the #310 post-merge review (#323, closes #324).** Post-merge `codex` review of #310 (security-boundary hardening) surfaced two issues #310's happy-path/crash-free tests did not catch, both verified first-party before fixing. (1) In `scripts/adapters/folder_scan.py`, a symlink escaping the input root wrote `reason: symlink_outside_input_root` to `rejection_log.yaml`, but that value is not in the `rejection_log.schema.json` `reason` enum — so the rejection log was contract-invalid exactly in the new symlink-rejection path. It now uses `other` + `detail` (schema-valid; the schema's `allOf` requires `detail` when `reason == other`). (2) In `scripts/bootstrap_timeline_yaml.py`, the lookup queried `…/works/{quote(doi)}` (encoded) but `source_locator` recorded `…/works/{doi}` (raw), so provenance named a URL that was never queried — affecting every DOI, not only reserved-character ones (`/` encodes to `%2F`). It now records the encoded DOI to match the queried URL. Tests strengthened to assert emitted content (the rejection log is `jsonschema.validate()`d; a new test pins `source_locator` to the encoded lookup URL), not just exit code. - **Domain evidence profiles wired end-to-end (#327).** Three feature-logic gaps from the #259 post-ship review that survived on `main` because `check_domain_evidence_profile.py` only verified documentation-surface presence (C1–C7), never the control-flow bound, the consumer parse logic, or the date-gate semantics. **[P1]** Step 12 (the profile producer) was orphaned from the no-handoff flow directive (bounded at "Step 1-11"), so the profile silently never activated on the common path; `intake_agent.md`'s directive now affirmatively reaches Step 12 (new lint C8). **[P2]** The reserved-fallback row `unknown_user_defined (requested: )` was misparsed as case (c), emitting a wrong `[PROFILE-UNRESOLVED]` malformed signal; the consumer now parses the effective token + parenthetical and emits a new `[PROFILE-RESERVED-FALLBACK]`, with (c) narrowed to genuinely unresolvable rows (new lint C9). **[P2]** The currency (time-range) node was not profile-aware, so a canonical humanities source admitted at the peer-review node was re-excluded at the currency node (INVARIANT 5 violation); the currency node gains a humanities admit branch (purely additive — union/loosen-only, continues through the universal relevance + methodology gates, never short-circuits to Include) (new lint C10). TDD with a RED mutation fixture per defect. - **Eval-harness gates honor binding per-class thresholds and exclude non-measured tasks (#328).** Two correctness holes in the #263 eval-harness CI gates, invisible to the suite because no fixture exercised them. **[P1]** `scripts/_eval_threshold_gate.py` `failed_tasks()` inspected only `aggregate_metric.passed`, but manifests declare binding per-class thresholds distinct from the aggregate (e.g. citation_extraction aggregate `accuracy ≥ 0.90` **and** per_class `accuracy ≥ 0.85`); a PR regressing `citation_extraction.false.accuracy` below 0.85 while the aggregate stayed ≥ 0.90 passed the gate when it should block. `failed_tasks()` now also iterates `per_class`, keyed `..`. **[P2]** `scripts/check_ranking_lift.py` `_flatten_report()` flattened any task carrying an `aggregate_metric` with no status filter, so a not-yet-landed task's placeholder `value: 0.0` entered the lift baseline as a real metric — once the task landed, its real value hit the zero-baseline branch and was spuriously flagged as a regression. Both consumers now share the same positive `status == "measured"` skip-guard so a future status (e.g. `"error"`) is excluded consistently. Adds `scripts/test__eval_threshold_gate.py` (11 cases) + 5 `_flatten_report` status-filter tests. - **v3.10 policy layer: laundering guard wired to real entries + per-block terminal-marker validation (#329).** Two P2 enforcement/grammar gaps in the shipped v3.10 triangulation policy layer; the 45 policy-layer tests passed because each guard was only exercised in isolation, never wired to the surface it protects. **[P2]** `assert_venue_type_source_clean` (rejects a `venue_type_source` naming a lookup index under `trusted_source_declared`) had no production caller — the entry schema's own description promises *"enforced by check_v3_10_policy.py"* but nothing ran it over real entries, so a passport laundering a k=3-unmatched signal into a declared-trust signal passed both validators. It is now wired into `check_literature_corpus_schema.validate_passport`'s entry loop (a laundered source fails; a legitimate publisher/registry feed name still passes; string-guarded so a non-string `venue_type_source` surfaces as a clean schema error, not a `.strip()` traceback). **[P2]** `is_well_formed` accepted a terminal `TERMINAL-BLOCK` marker missing the mandatory `policy`/`reason`/`mode`/`policy_hash` fields; `_parse_inner` now keeps per-block metadata and `is_well_formed` validates each block independently plus the marker-level `policy_hash`, so a complete later block can no longer mask an earlier block's stripped metadata (C-V6(g) multi-policy co-emission handled correctly). - **arXiv resolver no-ID skip + non-Atom 200 guard + miss-safe cache decode (#331).** Three post-ship defects in the #182 Delta 1+2 citation-integrity data layer (arXiv resolver + verification cache), all verified first-party; the 106 PR tests never exercised these paths. **[P2]** `resolve_arxiv_unmatched` ran a title search for citations with no `arxiv_id` (e.g. a DOI-keyed journal article) and returned `true` on a title miss — inflating triangulation `k` (k=3→k=4, rendering `CONTAMINATED-QUADRANGULATION-UNMATCHED` on a clean journal citation) plus a wasted ~3s request; it now skips the resolver when `arxiv_id` is absent, matching the spec's ID-gated `skipped` rule and the guard already in `verification_gate._run_arxiv`. **[P2]** A well-formed non-Atom 200 body (e.g. a proxy/CDN HTML error page) parsed cleanly and its empty entry list was cached as a real 90-day miss; `arxiv_client._get` now validates `root.tag == {atom}feed` and raises `ArxivUnavailable` (omit-on-degradation, not cached) on a non-feed root, while a genuine empty Atom feed still resolves to a miss. **[P3]** `VerificationCache.get`'s bare `json.loads` aborted verification on a corrupt/non-dict payload; it now treats `JSONDecodeError`/`TypeError`/non-dict as a miss (clean recompute), honoring the documented "malformed cache payload = miss" contract. Two tests that codified the buggy behavior were reversed. - **`verification_gate` reads `ref_slug` from the prose join, not the corpus entry (#332).** `verify_citation`/`verify_passport` previously wrote `summary.ref_slug = entry.get("ref_slug")`, but `literature_corpus_entry.schema.json` is `additionalProperties: false` with no `ref_slug` property — so the normal (schema-valid) passport path emitted `ref_slug: null` and violated the summary contract (a required string). Two non-schema-conformant test fixtures masked it. `ref_slug` is now an explicit prose-sourced parameter parallel to `anchor`: `verify_citation(entry, clients, *, ref_slug, anchor=None, …)` and `verify_passport(passport, clients, *, ref_slug_by_key, anchors=None, …)`, with a `ValueError` on any invalid join — a missing key, or a present-but-empty/non-string slug (validated once at the `verify_citation` emission point via a shared `_is_valid_ref_slug` so the per-citation and passport layers can't drift; the passport layer re-checks only to name the offending `citation_key`) — rather than a contract-invalid summary. The standalone `verify_passport.py` CLI (which has no prose document) now refuses by default with a clear error and offers an explicit `--synthetic-ref-slug citation_key` diagnostic escape hatch instead of silently fabricating a slug. **API-stability note (C-V4):** these are new *required* keyword-only parameters. The spec's C-V4 freeze names v3.10.0, but #182 was specced-but-not-implemented in v3.10 (spec §0 amendment) and first shipped in the v3.11.0 minor release — so no v3.10.0 caller can depend on the old signature, and C-V4 itself permits a minor release to add required fields. The only in-repo callers (the CLI + the internal `verify_passport`→`verify_citation` call) are updated in lockstep. - **`check_evals_gold_set` enforces `status`↔`queried_by` coherence via the shipped schema (#332).** The gold validator's flat `queried_by ∈ {id, title, null}` enum check under-enforced the conditional coherence the summary schema requires (a ran resolver must carry `id`/`title`, a skipped/unreachable one must carry `null`, and `queried_by` must be present). It now validates each `resolver_outcome` against `citation_verification_summary.schema.json`'s `$defs.resolver_outcome` — single source of truth, matching the existing I9b reduce-and-compare philosophy — and the now-dead `STATUS_ENUM`/`QUERIED_BY_ENUM` constants are removed. The shipped gold set already satisfies the stricter check. - **Citation-existence advisory visibility + terminal-marker grammar reconciliation (#333).** Two P2 self-consistency issues in the #182 citation-existence gate, neither a gate hole (the formatter's generic `severity=HIGH-BLOCK` refusal catches the strict token regardless). **Item 1 (#342):** the spec was internally self-contradictory — C-V6(b) claimed an advisory `lookup_verified == false` is BOTH "byte-equivalent to v3.9.x" AND "co-emitted in the ref marker", impossible for a firing row, and a second advisory marker token has nowhere to go (the v3.7.3 grammar caps one advisory slot, already taken by contamination's `CONTAMINATED-*` suffix). Resolved by a third path: the marker stays byte-equivalent (no new suffix, no grammar churn), and the advisory's visibility is carried in the **output package** instead — `formatter_agent.md` now requires a mandatory `provenance_summary.md` `Citation Existence Advisories` section listing every advisory `false` row, and `provenance_summary.md` is added to the Output Package Files Delivered table so the carrier can't be dropped. Every "co-emitted in/alongside the advisory annotation" claim was removed from spec §0 / C-V6(b)/(c)/(e) / Rule 12 (the contamination strict clause, which legitimately does co-emit a suffix, is untouched); new C-V6(b) lint in `check_v3_10_policy.py` + 3 mutation tests. **Item 2 (#338):** the canonical "Two marker grammar shapes" terminal enumeration in `pipeline_orchestrator_agent.md` listed `policy=`, omitting `citation_existence` even though the finalizer prose just below emits `policy=citation_existence` tokens; the enumeration is extended and the `mode=` clause reconciled per-policy (`citation_existence` is `strict`-only), + 2 parser fixtures. ## [3.11.0] - 2026-06-04 — Deterministic citation verification gate (#182) The v3.11.0 minor release ships **#182 — a deterministic citation-existence verification gate** that runs independently of LLM peer review. It cross-checks every cited reference against up to four bibliographic indexes (Semantic Scholar + OpenAlex + Crossref + the new arXiv resolver) and surfaces a per-citation `lookup_verified` status, so a fabricated citation with a provably-bogus DOI/arXiv ID is caught by deterministic lookup rather than by hoping a reviewer agent notices. The gate **inherits the v3.10 `terminal_policies` opt-in model** — default advisory, opt-in `strict` — rather than introducing a second hard-block philosophy: detection always runs and populates the summary, but a `lookup_verified == false` row is terminal only under `terminal_policies.citation_existence == strict`. **Default behavior is non-blocking** (advisory, `/ars-mark-read`-acknowledgeable); a user must opt into `strict` to make existence-failure terminal. The `false` definition is deliberately **narrowed to ID-keyed unmatched** (an exact DOI/arXiv lookup that provably fails), so a legitimately-unindexed humanities / non-English / regional citation with only a title-unmatched stays `unresolvable` and never blocks (C-V6(a); an acknowledged precision-over-recall tradeoff documented in the spec, mirroring `strict_articles_only`). **Five delta items (#182):** - **Delta 1 — arXiv API resolver + four-index contamination rendering.** New `scripts/arxiv_client.py` verifies citation existence against `export.arxiv.org` (metadata + existence; no API key, no polite-pool email — built-in rate-limit pacing per arXiv ToU; accepts both old-style `hep-th/9711200` and new-style `2605.07723` IDs). `scripts/contamination_signals.py` extends the v3.9.0 cross-index triangulation advisory matrix from three indexes (k=0..3) to four (k=0..4) with an `arxiv_unmatched` signal, and the orchestrator finalizer + formatter render the four new advisory suffixes (`CONTAMINATED-ARXIV-UNMATCHED` at the k=1/k_max=1 arxiv-only carve-out; `CONTAMINATED-QUADRANGULATION-UNMATCHED` at k=4/k_max=4; plus their two `PREPRINT` compositions). All advisory — the terminal gate / refusal list is unchanged (R-L3-2-E). `arxiv_unmatched` field added to `literature_corpus_entry.schema.json`. - **Delta 2 — persistent verification cache.** New `scripts/verification_cache.py` — a local SQLite store (`~/.cache/ars/verification.db`, override via `ARS_VERIFICATION_CACHE_PATH`; WAL mode; 90-day TTL) keyed by `(citation_key, resolver_name, query_form)`, so the same paper cited across drafts is verified once. Each resolver entry point (crossref / openalex / S2 / arxiv) gains an optional `cache` parameter. New `/ars-cache-invalidate ` command removes every cached row for a key (idempotent no-op when absent). - **Delta 3 / C-V6 — citation-existence terminal policy.** New `terminal_policies` key `citation_existence` (closed enum `{advisory, strict}`, per-key absence = advisory) in `terminal_policies.schema.json`, alongside `contamination_triangulation`. This replaces the original Delta-3 `ARS_CLAIM_AUDIT` default-flip as the gate's on/off control. The finalizer is the sole policy evaluator; `formatter_agent.md` rule 12 refuses on a `lookup_verified == false` row **only under `strict`**, co-emitting `[UNVERIFIED CITATION — lookup_verified=false: ...]` alongside the advisory annotation. `HIGH-BLOCK` is terminal — not `/ars-mark-read`-clearable. Manual entries structurally exempt. - **Delta 4 — unified per-citation status surface.** New `shared/contracts/passport/citation_verification_summary.schema.json` + `scripts/citation_verification_summary.py` write a `lookup_verified` (enum `{true, false, unresolvable}`) + `anchor_present` + `resolver_outcomes` (per-resolver `{matched, unmatched, unreachable, skipped}`) row per citation. The classification is anti-fabrication-biased (one ID-keyed `unmatched` is positive evidence of non-existence; a single transient outage does not cancel it) and the `false` form is narrowed to ID-keyed unmatched per C-V6(a). - **Delta 5 — standalone `verification_gate` API.** New `scripts/verification_gate/__init__.py` extracts the gate logic into a callable API composing the four resolvers + the unified summary writer (a second caller of the same lower-layer infrastructure as the v3.8 audit, not a duplicate). New `scripts/verify_passport.py` CLI runs the gate over a Material Passport standalone. **Lint + CI:** - `scripts/check_v3_9_0_triangulation.py` (the canonical cross-version contamination-suffix oracle) rule 1 upgraded from subsection token-presence to a **matrix-row oracle**: each Delta-1 token must sit on the finalizer suffix-table row carrying its exact `(k, k_max)` cell, so deleting or mistokening an operational row fails even when the same token survives in surrounding prose. The formatter pass-through allowlist set-equality oracle extends 9 → 13 tokens. - `scripts/_ci_pytest_manifest.toml` backfills 5 data-layer test entries (citation-verification-summary / verification-gate / arxiv-client / verification-cache / verify-passport-cli) that shipped with the data layer but were not wired into the manifest runner at the time. Spec: `docs/design/2026-05-21-v3.10-182-promote-citation-gate-spec.md` (§0 v3.11 amendment + INVARIANT C-V6). ## [3.10.0] - 2026-06-01 — Triangulation policy layer, Kong et al. survey adoptions, eval harness, scoped-write guard The v3.10.0 minor release bundles the opt-in contamination-triangulation **terminal policy layer** (#127 PR-B — default behavior byte-equivalent to v3.9.0), several **Kong et al. 2026 survey adoptions** (Rebuttal Commitment Ledger #256/#266/#268/#269, discipline-relative domain evidence profiles #259), the **v3.10 measurement infrastructure** (generalized eval gold set + ranking-lift gate, #184), the **#134 scoped-write guard MVP** (a deterministic `PreToolUse` hook fencing the 23 single-phase agents to their own phase directory; all Bash denied for those agents), the `/ars-mark-read` plugin commands (#190) + a broken-on-arrival fix (#195), a Simplified-Chinese README (#185), and CI hardening (#156/#155). Default *citation-policy* behavior is byte-equivalent to v3.9.0 unless a user opts into a strict mode (#127). The one default-on behavior change is #134's `PreToolUse` write-scope guard: the 23 single-phase agents are now fenced to their own phase directory and denied Bash — this constrains those subagents, not the user-facing skill outputs. **v3.10 triangulation policy layer (#127 PR-B — opt-in terminal modes, default behavior byte-equivalent to v3.9.0):** - **#127 PR-B — terminal policy layer.** Ships the contamination-triangulation policy layer deferred by v3.9.0 (#102): opt-in `strict` modes that promote the advisory k=3 triangulation signal to a non-acknowledgeable terminal `HIGH-BLOCK` at the citation-emission boundary. **Default behavior is byte-equivalent to v3.9.0** — an absent or all-`advisory` `terminal_policies` block changes nothing (Invariant 7). Built on PR-A's canonical firm-rules + sync-lint base. - **Schema.** New passport-level `shared/contracts/passport/terminal_policies.schema.json` (standalone, NOT inside the entry schema — Invariant 11): `contamination_triangulation` ∈ {`advisory`, `strict`, `strict_articles_only`}; `temporal_integrity` accepts **only** `advisory` (forward-reserved namespace — a schema-accepted temporal `strict` with no wired behavior would be a false-safety bug, Invariant 3). `literature_corpus_entry.schema.json` gains `venue_type` (closed enum incl. explicit `unknown`), `venue_type_provenance` (closed enum; the API-`_inferred` values are deliberately absent per R-L3-2-D), and `venue_type_source` (required iff `trusted_source_declared`). Pair dependencies: type ⟺ provenance (bidirectional); `venue_type == unknown ⟹ provenance == unknown` (one-way — a known type may carry `unknown` provenance, no data loss). All adapter-declared only; never inferred from free-form `venue`. `check_literature_corpus_schema.py` extended to validate a passport-level `terminal_policies` block before iterating entries. - **Finalizer (sole policy evaluator).** `pipeline_orchestrator_agent.md` gains a `## Cite-Time Provenance Finalizer — v3.10 extension` section. Under a non-advisory passport it stamps `policy_hash=` on every ref marker (a fully-encoded human-readable canonical token of the non-advisory `terminal_policies` keys — sorted `key.value` join — so two distinct configs never collide). Under an all-advisory passport NO stamp is emitted: the marker is the bare v3.9.0 shape (byte-equivalent, Invariant 7) — the absence of a stamp is the advisory signal. Under `strict`, a k=3 ref co-emits a `TERMINAL-BLOCK severity=HIGH-BLOCK policy=... reason=... mode=... policy_hash=...` token ALONGSIDE (not replacing) its advisory `CONTAMINATED-*` suffix, so the "why" survives. `strict_articles_only` is a deliberate PRECISION mode — k=3 promotes only when DOI present ∧ `venue_type ∈ {journal-article, conference-paper}` ∧ declared provenance; a DOI-less or unknown-venue journal article stays advisory by design (humanities / non-English / regional coverage gap). Audit trail gains a `terminal_blocked[]` bucket; aggregate counts dedupe by ref slug across advisory + terminal buckets (non-additive). Manual-entry exemption preserved (k=3 structurally unreachable). `HIGH-BLOCK` is terminal — `/ars-mark-read` does NOT clear it. - **Formatter (STAMP-ONLY two-gate).** `formatter_agent.md` gains refusal rule 11 (generic `severity=HIGH-BLOCK` inside a ``, NOT a per-subtype list) plus a `## Cite-Time Terminal Policy Gate (v3.10)` section. Two ordered gates, never short-circuited: Gate 1 freshness (stamp mismatch / missing-stamp-under-non-advisory → `[STALE-POLICY-EVALUATION]`; missing-stamp-under-advisory passes, Invariant 7), Gate 2 HIGH-BLOCK refusal applied to every gate-1-passing marker (a stripped-stamp marker still carrying `TERMINAL-BLOCK` is still refused). The formatter never re-evaluates `strict_articles_only` logic (Invariant 13 — the finalizer is the sole evaluator). A bare-prose `HIGH-BLOCK` outside any ref marker never refuses (Invariant 12). v3.9.0 advisory pass-through allowlist unchanged. - **Firm rule.** R-L3-2-A reworded in `firm_rules.md` to the broad default-advisory + opt-in-strict form (covering contamination AND the forward-reserved temporal namespace; the wording explicitly states no temporal strict path exists yet, no over-promise). Contamination mirrors stay intentionally by-ID references (not full-block copies); the wording is single-sourced in the canonical block. `check_firm_rules_sync.py` gains a contradiction guard scoped to the R-L3-2-A reference sentence in each contamination-context file (rejects unqualified "advisory only / never block" claims now that strict can block) — deliberately NOT scanning the whole file, so the Collaboration Depth Observer's legitimate "never blocks" wording is not false-flagged. - **Migration + adapters.** `scripts/migrate_literature_corpus_to_v3_10.py` seeds passport-level `terminal_policies` (deep-merge — only absent keys, idempotent, dry-run; never backfills `venue_type` from free-form `venue`; clear error on a non-mapping `terminal_policies`; pre-v3.9.0 passports reported out-of-scope, not silently skipped). The three reference adapters (`folder_scan` / `zotero` / `obsidian`) now declare `venue_type` + `venue_type_provenance` (Zotero item type → `adapter_declared`; folder_scan → `unknown`/`unknown`; obsidian honors a frontmatter `venue_type` as `user_declared`, else `unknown`/`unknown`). - **Lint + CI.** New `scripts/check_v3_10_policy.py` (runs ALONGSIDE `check_v3_9_0_triangulation.py`, not a rename) covers the schema fields, the `_inferred`-rejection, the pair dependencies, the trusted_source laundering guard, the standalone schema home, the marker grammar (with a reusable parser + the five required fixtures: terminal co-emit / non-terminal advisory / non-terminal clean / legacy-no-stamp / bare-prose-no-refuse), the generic rule-11 shape, the formatter STAMP-ONLY two-gate, and the closed enums. Wired into `spec-consistency.yml` + `_ci_pytest_manifest.toml`. Spec: `docs/design/2026-05-31-ars-v3.10-policy-layer-rescope-spec.md`. ### Added - **#134 Slice 1 — scoped-write guard MVP (the Active Conductor rescope).** New `PreToolUse` hook `scripts/ars_write_scope_guard.py` fences the 23 single-phase (Bucket A) subagents to their own phase directory: for `Write`/`Edit`/`MultiEdit` it normalizes the single top-level `file_path` (`realpath`, so `..`/symlink traversal resolves in true filesystem order), denies workspace escapes, unconditionally protects the enforcement surface (`hooks.json`, the hook/manifest/lint, agent definition files, `.claude/CLAUDE.md`), then enforces the agent's `allowed_write_globs` with a segment-aware iterative glob matcher (`*` never crosses `/`; `dir/**` is descendants-only; no recursion-limit crash on deep paths). **All Bash is denied for a Bucket A agent** — it uses the Grep/Glob tools to search and the structured editing tools to write. (The spec's "best-effort literal-target Bash" was taken to its sound conclusion: neither "this Bash writes a file" nor "this Bash is read-only" can be decided reliably from a command string without a sandbox, so all-deny is the only zero-fail-open Bash policy; spec carries an Implementation-outcome note + aligned §3.2/§3.3 wording.) Backed by `scripts/ars_phase_scope_manifest.json` (machine-readable scope for the 23 agents) and the fail-open guard lint `scripts/check_v3_10_134_write_scope.py` (three-way name cross-check: classification roster == manifest keys == on-disk frontmatter names, + filesystem exhaustiveness at any nesting depth, so rename/typo/new-agent drift can't silently fail the hook open). `hooks.json` PreToolUse wiring + CI steps (lint + hooks.json wiring assertion) + pytest manifest entries; TDD throughout with lint mutation tests. The structured-tool determinism is the load-bearing win; the Bash deny closes the direct-shell-write path for fenced agents entirely. Slices 2-5 (write-provenance ledger, task envelopes, return contracts, persistent conductor) remain forward-scope. Spec: `docs/design/2026-06-01-ars-134-conductor-rescope-deterministic-write-guard-spec.md`. Closes #134. - Kong A4 (#259): Discipline-relative domain evidence profiles. New `academic-paper/references/domain_evidence_profiles.md` defines 4 ship-ready profiles (`cs_ml`, `general_social_science`, `humanities_interpretive`, `unknown_user_defined`) + 5 reserved. `intake_agent` Step 12 emits a scholar-selected `Domain Evidence Profile` PCR row (never auto-selected; reserved selections fall back to neutral with a surfaced advisory). `literature_strategist_agent` resolves the row and applies **loosen-only** gate + upstream-filter changes — monotonic admit-only, and profile-admitted sources still flow through the universal relevance + methodology gates. New `scripts/check_domain_evidence_profile.py` (C1–C7 documentation-surface lint, including a SHA-256 pin of the `source_quality_hierarchy.md` Field-Specific Adjustments block) + mutation suite, wired into `spec-consistency.yml` + the pytest manifest. Advisory only. Closes Kong et al. 2026 §7.4.6 domain-evidence-standards gap. - Kong A1 (#256): Schema 11 R&R Traceability Matrix gains `commitment_extracted` / `fulfillment_status` / `unfulfilled_rationale` optional fields. `revision_coach_agent` Step 3.5 extracts commitments; `re_review_mode_protocol` step 5 verifies + surfaces `COMMITMENT_GAP` advisory. Worked example at `academic-paper/examples/commitment_ledger_example.md`. Calibration seed at `evals/calibration/commitment_ledger_seed.yaml` (10 cases). Advisory only — author retains final responsibility. Closes Kong et al. 2026 §7.4.3 commitment-fulfillment gap. - Kong A1 follow-up (#269): Schema 11 `required_evidence_type` enum widened from 7 to 9 values, adding `prose_edit` and `other`. `prose_edit` is a seventh **manuscript-evidence** type for sentence-/paragraph-level changes too granular to bucket structurally (typo fixes, terminology clarifications, equation formatting, citation-style corrections); it verifies at `revision_location` like the other manuscript types. `other` mirrors the existing `commitment_type` escape hatch for genuinely uncategorizable evidence and triggers a new soft `EVIDENCE_TYPE_UNSPECIFIED` advisory at re-review (orthogonal to `COMMITMENT_GAP`; fires whenever `required_evidence_type == other`, regardless of fulfillment status). The prior 7-value closed set forced typo-level comments into wrong buckets (`methods_paragraph`) or out of the ledger entirely, violating the every-comment extraction rule. Synced across `shared/handoff_schemas.md` Schema 11, `revision_coach_agent` Step 3.5, `re_review_mode_protocol` Commitment Ledger Verification, and `revision_tracking_template.md`; worked example and calibration seed (now 12 cases, +E1/E2) extended. Advisory only. Surfaced by Gemini R3 review of PR #264, Finding 3. Closes #269. - Kong A1 follow-up (#268): Schema 11 Commitment Ledger refactored from three index-aligned **parallel lists** (`commitment_extracted` objects + top-level `fulfillment_status[]` + `unfulfilled_rationale[]`) to a **nested-object** shape — `fulfillment_status` and `unfulfilled_rationale` now nest INSIDE each `commitment_extracted` object. This makes length-mismatch / index-desynchronization structurally impossible, closing the Gemini R3 (PR #264) Finding 1 fragility where a dropped Markdown `
` or numbering error silently mispaired a status with the wrong commitment and produced a false `COMMITMENT_GAP` advisory. **REPLACE, not coexist** (spec §2): the parallel-list shape is removed entirely — no executable consumer, lint, or fixture carried it (the #263 calibration harness is unshipped; the seed is a non-runnable seed), so coexistence would only preserve the failure mode. Lifecycle fields are absent at extraction time (`revision_coach_agent` Step 3.5) and appended per-object during revision execution; the old `unfulfilled_rationale: ""` placeholder for fulfilled commitments is dropped (omitted, not empty-string). The equal-length validation invariant is retired (now structurally impossible); a legacy-normalization note instructs zipping any pre-#268 top-level arrays onto the nested objects before re-review. Synced across `shared/handoff_schemas.md` Schema 11 (incl. the #266 `residual_action` coherence prose, reworded from `unfulfilled_rationale[i]` index notation to object-field notation), `revision_coach_agent` Step 3.5, `re_review_mode_protocol` Commitment Ledger Verification, `revision_tracking_template.md` (three fragile `
`-separated columns collapsed into one per-commitment nested YAML ledger), worked example, and the 12-case calibration seed. `author_fulfillment_claim` (Gemini's promised-vs-claimed-vs-verified split) deferred — not required for the structural fix (spec §2). New `scripts/check_268_nested_commitment_ledger.py` (N1-N5 + N3b: seed extraction-field presence, no retired parallel-list keys, per-commitment lifecycle coherence via a `_blank_rationale` helper that treats missing/null/whitespace uniformly, case-level `expected_commitment_gap` oracle coherence with a real-boolean guard, no surviving index notation) + 18 mutation tests, wired into `spec-consistency.yml` + the pytest manifest. Advisory semantics unchanged. Surfaced by Gemini R3 review of PR #264, Finding 1. Spec: `docs/design/2026-05-31-ars-268-schema11-nested-commitment-ledger-spec.md`. Closes #268. - Kong A1 follow-up (#266): Schema 11 `residual_action` (concern-level) vs `unfulfilled_rationale` (per-commitment) coherence. Documented their semantic relationship (different granularity and tense — `unfulfilled_rationale[i]` is backward-looking and per-commitment, `residual_action` is forward-looking and concern-level, so a row may carry both without redundancy or contradiction), the multi-commitment single-string shape convention (`residual_action` stays one concern-level string, not expanded into a list), and a `re_review_mode_protocol` note that a populated `residual_action` alongside some `fulfillment_status[i] == fulfilled` is not a contradiction, cross-referencing the `shared/handoff_schemas.md` Schema 11 convention. Doc-only; advisory semantics unchanged. Closes #266. **Bug fixes (no version bump — corrects a broken-on-arrival behavior from #190):** - **#195 — `/ars-mark-read` crashed on real YAML passports.** `scripts/ars_mark_read.py:_load_corpus_keys` used `json.load()` to read the Material Passport, but every adapter (folder_scan / zotero / obsidian) and every other ARS tool produces / consumes `passport.yaml`. The existing 11-test fixture in `scripts/test_ars_mark_read.py` wrote JSON-formatted passports, so the suite was green while real-world `/ars-mark-read smith2024 --passport-path ./passport.yaml` exited with `json.JSONDecodeError` before reaching citation-key validation. Two new TDD tests pin the adapter-format expectation (YAML happy path + YAML invalid-key hard error); `_write_passport` helper switched to `yaml.safe_dump`. Companion P2 also closed: existing-but-unwritable read-log file now surfaces the canonical `[ARS-MARK-READ ERROR: ...]` fail-fast rather than a bare `PermissionError` traceback, via an extra `os.access(log_path, os.W_OK)` check after the parent-W_OK gate. 14 ars_mark_read tests pass (was 11), full suite 1623 / 3 skipped. Surfaced by post-squash codex review of PR #191 (issue #192). **Plugin commands (prep for v3.10 — no behavior change to existing skills):** - **#190 — `/ars-mark-read` + `/ars-unmark-read` plugin commands.** v3.6.8 spec §3.6 + Step 7 (round-2 R2-002, round-5 R5-003 amends) designed these commands as the user-facing affordance for the human-read signal, but the command surface itself was never shipped — `commands/` carried only the 10 `/ars-` skill triggers. New `scripts/ars_mark_read.py` deterministic CLI implements the four §3.6 R5-003 fail-fast modes (no active passport / passport not found / parent unreadable / read-log unwritable), the §3.6 firm-rule-2 hard error on invalid `citation_key`, batch-level all-or-nothing semantics (any invalid key rejects the whole batch), and the §3.6 firm-rule-3 append-only write to `_human_read_log.yaml` next to the active Material Passport. `/ars-unmark-read` writes `rescinded_at: ` to the matching entry, never deletes. Two new thin markdown command files (`commands/ars-mark-read.md`, `commands/ars-unmark-read.md`) invoke the CLI via Bash; both declare `model: sonnet` routing per `feedback_no_haiku.md`. New `scripts/check_v3_6_8_mark_read_commands.py` CI lint per spec Step 7 acceptance: 2 commands exist, carry the `literature_corpus[]` validation reference, reference the `human_read_log.yaml` peer-file write target (NOT entry frontmatter, per §3.1 firm rule 3), and declare `model: sonnet`. 11 unit tests for the CLI + 6 unit tests for the lint. `/ars-list-read` and `commands/ars-mark-read.zh-TW.md` were spec-marked optional and remain deferred. Closes #190. **v3.10 measurement infrastructure (prep for v3.10 — no behavior change to existing skills):** - **#184 Phase 1a — citation-extraction gold subset.** New top-level `evals/` directory holds v3.10 generalized gold-set corpora for `verification_gate.verify_citation` measurement targets. Ships `evals/gold/citation_extraction/` with 50 hand-curated tuples (all populated in this PR) + `manifest.yaml` + `expected_outcomes.json`. v3.10.0 binding thresholds: aggregate `accuracy >= 0.90` across 50 tuples, per-class `accuracy >= 0.85` for each of `true` / `false` / `unresolvable` (changing requires spec amendment per #184 §3.1.1 / E-V2). Distribution: 20 valid_doi + 10 valid_arxiv + 5 manual_exempt + 15 fabricated (= 50). The original `valid_unresolvable` source class was removed as unbuildable — no stable first-party-verifiable real-but-unmatched citation exists under current index coverage; tuples 031-040 were reassigned to `fabricated`; coverage gap tracked in #250. Tuple shape (locked per codex consult Q1-Q5): self-contained `corpus_entry` mirroring `literature_corpus_entry`, `arxiv_id` as tuple-level field (forward-looking — see #234 for #182 implementation alignment), `human_expert_verdict` optional (10/50 = 20% per Delta 5), `fabrication_intent` boolean enforced on fabricated tuples. New `scripts/check_evals_gold_set.py` enforces 9 invariants (I1 set equality / I2 tuple_id ↔ filename / I3 kind distribution / I4 no-dup-JSON-keys / I5 label ↔ kind / I6 arxiv_id placement / I7 fabrication_intent marker / I9 resolver_outcomes shape / I10 corpus_entry schema) via 17 mutation tests on a 3-tuple clean fixture. CI step wired into `.github/workflows/spec-consistency.yml`. Spec: `docs/design/2026-05-21-v3.10-184-extend-eval-harness-spec.md`. - **#184 Phase 1b — eval harness + ranking-lift gate.** New `scripts/run_evals.py` multi-task harness (`python -m scripts.run_evals [--task ] [--baseline ] [--compare ] [--output ]`): discovers every `evals/gold//manifest.yaml`, measures each task, and emits a report shaped by the new `shared/evals_lift_report.schema.json` (required `harness_version` / `run_id` / `gold_set_version` / `per_task[]` / `caveats[]` with the v3.8 honesty-disclosure `minItems:1` convention). For `citation_extraction` the harness computes the predicted `lookup_verified` 3-class enum itself from each tuple's `resolver_outcomes.*.status` via the #182 Delta 4 reducer (`verification_gate.verify_citation` has not shipped — reconcile when it does); the metric is symmetric 3-class accuracy, `unresolvable` is never collapsed into `false`. For `rq_framing_patterns` it dispatches to the existing `scripts/check_rq_framing_patterns.py` runner and adapts its FNR / FPR / balanced-accuracy output into the per-task lift shape. `--baseline` + `--compare` produce a side-by-side report carrying `lift_pre` / `lift_post`; `expert_concordance` is emitted per class over the 10 `human_expert_verdict`-labeled tuples (advisory, never gates per E-V3). Missing entrypoint module / Phase-2 gold set yields a `pending`/`skipped` notice, never a traceback. New `scripts/check_ranking_lift.py` lift gate: pure `compute_signed_lift(baseline, compare, direction)` (higher-is-better `(compare-baseline)/|baseline|`, lower-is-better numerator inverted, zero-baseline `+inf`/`-inf`); blocks on any `signed_lift < -0.05` or zero-baseline change unless the PR body carries `[ranking-regression-acknowledged]` + an OPEN issue URL and the declared `Affected metric: ..` matches the observed change (E-V4); OPEN-issue check via a monkeypatchable `_issue_is_open` seam (never networks in tests). New CI workflow `.github/workflows/eval-harness.yml` (Delta 3 path filter; concurrency group includes `github.event_name`; OQ-3 skip-guard for absent Phase-2 gold sets; deterministic `[eval-regression-acknowledged]` + OPEN-issue PR-body gate) and net-new `.github/pull_request_template.md` Eval-impact section. Tests: `scripts/test_run_evals.py`, `scripts/test_check_ranking_lift.py`, `scripts/test_evals_citation_extraction.py`, `scripts/test_evals_lift_report_schema.py` (incl. trivial-accept-all schema mutation). Spec: `docs/design/2026-05-21-v3.10-184-extend-eval-harness-spec.md`. **Localization (no version bump — no behavior change to skills):** - **#185 — Simplified Chinese README.** New `README.zh-CN.md` (630 lines, mirroring `README.zh-TW.md` structure) translated by external contributor [@xpfo-go](https://github.com/xpfo-go) ([PR #181](https://github.com/Imbad0202/academic-research-skills/pull/181)). Language switcher updated across the four READMEs (en / zh-CN / zh-TW / ja-JP); `CONTRIBUTING.md` README sync guidance extended to four locales. `scripts/check_spec_consistency.py` refactored to share zh-TW / zh-CN logic via `ZH_README_CONFIGS` tuple; both locales covered by `test_aligned_zh_cn_readme_passes` + `test_stale_zh_cn_badge_fails` regression tests (symmetric with the ja-JP tests added in #170). **CI / infrastructure (no version bump — no behavior change to skills):** - **#156 — Unified pytest invocation manifest.** Twelve `pytest scripts/test_*.py` invocations in `.github/workflows/spec-consistency.yml` are now declared in `scripts/_ci_pytest_manifest.toml` and run via `scripts/run_ci_pytest_manifest.py`. Drift guard `scripts/check_ci_pytest_manifest.py` rejects (a) missing `path`, (b) duplicate `id`, (c) duplicate `(path, args)`, (d) malformed `args`, (e) any `pytest scripts/test_*.py` re-introduced in the workflow outside the runner. `pip install pytest` consolidates from 12 redundant installs to one. 17 unit tests for runner + lint. `python3 -m unittest scripts.test_*` invocations stay inline (out of scope for #156). 41 disk `test_*.py` files that the manifest does not list remain unclassified — separate follow-up. - **#155 — Re-attempt F4: harden `test-count-monotonic.yml` to fail on pytest collection errors.** Both head and base count steps now capture pytest's exit code separately from the pipe, treat exit 5 (no tests collected) as a tolerable degenerate case, and fail the gate on any other non-zero exit. Previously, a `2>/dev/null | grep -c '::' || true` swallow on the base step would silently set BASE_COUNT to 0 on a broken-import or fixture-missing error in the base commit, making the head-vs-base monotonic check vacuously pass. The original F4 fix landed in PR #153 commit 8121dfa during the v3.9.4.2 cycle but was reverted in 4abf9de when it surfaced #154 (now closed by PR #158). With #154 fixed and #156 keeping CI test discovery clean, F4 v2 ships symmetrically across head and base. --- ## [3.9.4.2] - 2026-05-19 — Post-ship hotfix for PR #149 CI discipline gates **Trigger:** Codex post-ship review of PR #149 (7 CI discipline gates mechanizing the release-cycle review chain) surfaced 4 P2 findings. v3.9.4.2 hardens 3 of 4; the 4th (test-count-monotonic harden) was reverted because it surfaced a pre-existing `scripts/` package issue, tracked as #154 (since fixed by PR #158) and re-attempt #155. **CI gate hardening (PR #149 + #153):** - **F1 — harness-retirement scheduler context:** `harness-retirement-monthly.yml` adds `GH_REPO` so scheduled runs have repo context for `gh issue create` (workflow was silently failing on cron without it). - **F2 — release-cooldown tag filter:** `release-cooldown.yml` filters `PREV_TAG` lookup to `v*` tags so non-release tags (e.g., legacy plugin tags) cannot bypass the cooldown gate. - **F3 — release-cooldown hot-fix detection:** `release-cooldown.yml` also reads annotated tag subject + accepts the `hot-fix` spelling variant; v3.9.2 was previously a false-negative hotfix under the old detector. - **F4 (reverted):** `test-count-monotonic.yml` harden landed in 8121dfa and reverted in 4abf9de when it surfaced `scripts/` package import errors (`ModuleNotFoundError: No module named 'scripts'`) — pre-existing latent defect masked by the prior `2>/dev/null | || true` pattern. Tracked as #154 (now closed by PR #158) and re-attempt #155. **Release-cooldown symmetry follow-up (PR #157):** - Override token `[skip-cooldown]` now read from both the commit message AND the annotated tag message. This v3.9.4.2 tag itself is the self-bootstrapping fix — the gate correctly identified v3.9.4.1 (3h prior) as the previous hotfix and fired the 24h cooldown, proving F2+F3 work end-to-end. The override symmetry patch makes the tag shippable. **Closes:** #152. **Follow-ups:** #154 (closed by PR #158), #155, #156. --- ## [3.9.4.1] - 2026-05-19 — Post-ship hotfix for v3.9.4 temporal verification **Trigger:** Codex post-ship review of v3.9.4 squash commit `af09cf5` surfaced 4 real bugs that per-task subagent reviewers missed during v3.9.4 implementation. v3.9.4 tag remains immutable; v3.9.4.1 patches the verifier and schema layer + brings docs in alignment. **Bug fixes:** - **#135 P1 (audit wiring):** `audit()` now passes `citation_provenance` through to `_pass_2_anachronism` and `_pass_4_causal`. When a ref slug has `confidence: low` or `conflict` in citation_provenance.yaml, the verifier emits `TEMPORAL-METADATA-MISSING` instead of using timeline dates as arithmetic ground truth. v3.9.4 dropped citation_provenance on the floor — spec §3.4 first-party safety check was structurally broken. - **#135 P1 (date parser):** `_date_to_interval()` now parses all schema-valid date shapes including `YYYY-MM` (Crossref month-precision output) and `YYYY-MM-DD..YYYY-MM-DD` (interval precision used by effective_date_range). v3.9.4 only handled day/year/prose-month forms — schema-valid month/interval shapes raised ValueError and P2/P4 silently skipped the check via the existing `except ValueError: continue` guard. - **#135 P2 (P4 direct-date binding):** P4 now binds each side of a causal trigger to either a `` marker OR a direct date capture in the sentence. v3.9.4 required refs on both sides, silently dropping sentences like "The 2026 policy enabled the 2020 rollout." `bound_dates.source` distinguishes `timeline_ref` from `draft_capture`; `bound_refs` is empty when both sides came from direct date capture. - **#135 P2 (schema absent-property bypass):** `citation_provenance.schema.json` `confidence:high` allOf branch now requires both `crossref_issued` and `pdftotext_cover_first_line` to be present in addition to non-null (`then.required` added). v3.9.4 used `then.properties` only, which doesn't fire when a property is absent — so entries with `confidence:high` and both source fields omitted silently passed validation. **Documentation:** - `docs/ARCHITECTURE.md` updated from stale v3.8.0 baseline to v3.9.4.1; Section 8 Evolution Timeline filled in v3.8.1 / v3.8.2 / v3.9.0 / v3.9.1 / v3.9.2 / v3.9.3 / v3.9.4 / v3.9.4.1 entries; Section 9 Skill Modes table aligned to current versions. - Suite-version needles aligned across MODE_REGISTRY.md, README.md badge + tag URL + section heading, README.zh-TW.md badge + tag URL + section heading, academic-pipeline/SKILL.md frontmatter, `.claude-plugin/plugin.json`, `scripts/check_spec_consistency.py` expected-text constants, `.claude/CLAUDE.md` skill suite table. **Test count:** 1549 → **1561** (+12 net new tests covering all 4 fixes, 0 regression). --- ## [3.9.4] - 2026-05-18 — Temporal Verification Layer (advisory) **External motivation:** Issue #135 — LLM next-token objectives are systematically blind to deterministic factual classes including temporal ordering. v3.9.4 adds a deterministic advisory verifier at the Phase 4 → 5 boundary covering 5 failure modes. **Mechanisms:** - M1: new Phase 2 sibling `timeline_extraction_agent` owning `phase2_investigation/timeline.yaml` + `phase2_investigation/citation_provenance.yaml` - M2: Phase 4 → 5 deterministic verifier `scripts/temporal_integrity_audit.py` (5 passes) - M3: Temporal Integrity Iron Rule in `report_compiler_agent` + `draft_writer_agent` - M6-minimal: First-party Crossref `issued` + pdftotext cover verification - M7-minimal: Date provenance + comparator materialization - M5-stub: User-declared `version_family_id` only **Zero modification** to `literature_corpus_entry`, `claim_audit_result`, `claim_intent_manifest`. `bibliography_agent` unmodified (F2 invariant). 3 new sidecar schemas (aggregate-level with `$defs`). **Coverage estimate:** 55-70% baseline / 65-75% with M7 minimal (LLM extractor blindness on tuple extraction is structural; advisory architecture acknowledges this). **Out of v3.9.4 scope** (deferred to v3.10): M4 reviewer integration, M5 full version discovery, M6 full PDF audit, M8 relation manifest, CC5 catalog-completeness semantics, hard-block policy, OpenAlex lookup. Spec: `docs/design/2026-05-18-ars-v3.9.4-temporal-verification-spec.md`. --- ## [3.9.3] - 2026-05-18 — Housekeeping (#128 §1-3, §5-6) Pure refactor + one latent-bug fix carrying over from the v3.9.0 `/simplify` review backlog. The v3.9.0 cross-index triangulation client family (Semantic Scholar + OpenAlex + Crossref) shipped intentionally byte-equivalent across 3 client modules for code locality; now that the family is stable, the dedup prevents sibling drift when threshold tuning, normalization rules, or throttle measurement need adjustment. ### Refactor — extracted helpers (no behavior change) - **`scripts/_text_similarity.py`** — extracts 4 helpers + 4 constants previously triple-implemented byte-equivalent in `semantic_scholar_client.py` / `openalex_client.py` / `crossref_client.py`: `_PUNCT_TRANSLATION`, `_normalize_title`, `_similarity`, `_TITLE_SIMILARITY_THRESHOLD = 0.70`, `_BACKOFF_SECONDS = 2.0`, `_MAX_RETRIES = 3`. 14 new tests on the shared module. - **`scripts/_passport_yaml.py`** — extracts ruamel.yaml round-trip config (`preserve_quotes = True`, `indent(mapping=2, sequence=4, offset=2)`) + `load_passport` / `dump_passport` functions previously duplicated byte-equivalent in `migrate_literature_corpus_to_v3_7_3.py` + `migrate_literature_corpus_to_v3_9_0.py`. 7 new tests on the shared module. - **`contamination_signals._resolve_by_doi_then_title`** — private helper for the identical DOI-then-title control flow shared by `resolve_openalex_unmatched` (§3.4) + `resolve_crossref_unmatched` (§3.5). Both public wrappers preserve the v3.9.0 spec API surface; exception-type differentiation stays at the wrapper. 10 existing resolver tests verify byte-equivalent behavior. ### Latent-bug fix — throttle measurement standardized on `time.monotonic` - OpenAlex + Crossref clients now use `time.monotonic()` for `_throttle()` elapsed measurement + `_last_request_at` anchor refresh, matching Semantic Scholar (which had standardized on monotonic per #115 R5-2). NTP / manual clock adjustments could push `time.time()` backward, producing negative elapsed and either inflated sleep (negative compared less than min_interval) or zero sleep — latent throttle-bypass / API-spam bug. Documented as a "maintenance smell" in #128 §6. - New tests (`test_openalex_client::test_throttle_uses_monotonic_clock` + `test_crossref_client::test_throttle_uses_monotonic_clock`) lock NTP-safe semantics: throttle reads `time.monotonic` and never reads `time.time`. ### Dual-path import infrastructure - All 5 module-level cross-imports in `openalex_client.py` / `crossref_client.py` / `semantic_scholar_client.py` / `migrate_literature_corpus_to_v3_7_3.py` / `migrate_literature_corpus_to_v3_9_0.py` use the dual-path try/except pattern (sibling-first, namespace-package fallback). Follows `scripts/slr_lineage.py` precedent but inverted for class-identity preservation (pytest uses sibling-path imports; `SemanticScholarUnavailable` from `scripts.contamination_signals` is a different class instance than `contamination_signals.SemanticScholarUnavailable`). - Latent fix: `scripts.semantic_scholar_client` + `scripts.migrate_literature_corpus_to_v3_7_3` are now `import scripts.X`-clean from repo root (were silently broken on main due to pre-existing absolute cross-imports). Caught by codex round-1 reasoning trace. ### Deferred from #128 - **§4 — parallelize OA + CR per-entry calls in v3.9.0 migration tool** carried to #138 (target v3.9.4 or v3.10). Introduces new behavior + ThreadPoolExecutor + test-rebuild scope; incompatible with v3.9.3 patch boundary. ### Regression status - 1482 → **1505 passed** + 3 skipped + 111 subtests (+23 new tests, 0 regression). - `scripts/check_spec_consistency.py` + `scripts/check_version_consistency.py` green. - 6/6 `import scripts.X` paths verified clean from repo root (3 from-OK-to-OK, 2 latent-broken-now-OK, 1 OK throughout). - Cross-model review: codex round 1 + 2 both 0 explicit findings (one P1 self-caught from R1 trace, closed pre-R2). Gemini 3.1-pro-preview round 1: 0 findings. --- ## [3.9.2] - 2026-05-18 — Phase boundary hot-fix (#133) Hot-fix for issue #133 (phase scope inflation). A user incident showed that ARS auto-dispatched a single-phase agent (`bibliography_agent`) when given ambiguous cross-phase input (pre-written abstract + pre-collected literature), and the dispatched agent then autonomously executed Phases 3-6, skipping mandatory independent crosschecks (DA / EIC / Ethics). This release ships the prompt-discipline + advisory-verifier hot-fix. The deterministic gate (PreToolUse hook + multi-phase task envelope schema + author provenance) is tracked separately as **v3.10 active conductor (#134)** — long-term architectural fix. Design history: 4 design rounds (v1-v4) + mid-impl review. Triple-track reviewer use cases (codex `review --base main` + inline opus subagent + self-review). Codex 0.130 broke on this repo context 5x consecutive per memory `feedback_codex_0_130_docs_review_broken.md` (49 files / 1529 lines on full branch is firmly in the broken corner); inline opus was the substantive reviewer throughout. Net effect: design has been challenged thoroughly; honest framing applied where prompt-only mitigation is known insufficient. ### Added - **Routing Discipline (Phase L1)** — `.claude/CLAUDE.md` gains a new "Routing Discipline (v3.9.2)" section before existing Routing Rules 1-5. 3 routing classes: explicit intent → proceed directly; cross-phase materials → clarify with a-d options; no-materials ambiguous → clarify. `[direct-mode]` byte-0 escape hatch (case-insensitive; bracket-form strict). Anti-pattern explicitly named. - **Intent clarification protocol** — new `shared/references/intent_clarification_protocol.md` (~200 lines): trigger condition table, pipeline phase reference (Phase 0-7 marker conventions), clarification message template (a-d options, no AskUserQuestion tool), `[direct-mode]` mechanism spec with 5 worked examples, v3.10 carry-over notes. - **Phase Boundary block on 22 Bucket A agents (Phase 1)** — single-phase agents (deep-research × 9, academic-paper × 7, academic-paper-reviewer × 6) gain a `## Phase Boundary (v3.9.2)` block customized per agent: phase number, deliverable type, MUST-NOT cross-phase writes, MAY-READ upstream context (Phase 5 reviewers granted explicit cross-phase READ for review), explicit coexistence with skill-specific protocols (v3.6.2 / v3.6.5 / v3.6.6 / v3.6.7 / v3.7.1). 16 Bucket B/C/D agents (multi-phase / phase-orthogonal / cross-phase-meta) intentionally NOT fenced — honest framing per opus HIGH-2 (placebo prose creates false-enforcement illusion). - **Phase-by-phase invocation contract (Phase 3)** — 4 SKILL.md files gain a "Phase-by-phase Invocation Contract (v3.9.2)" section: Mode A (orchestrator-driven, default) vs Mode B (phase-by-phase cross-session resume), Bucket A enforcement scope, coexistence with skill-specific protocols. - **Advisory verifier (Phase 4)** — new `scripts/check_pipeline_integrity.py`: scans working directory for `phaseN_*/` (N=1-6), flags STRUCTURAL finding when phase5 dir lacks DA/EIC/Ethics filenames (the #133 pattern). HEURISTIC adjacent-phase-mtime rule (`--strict`, default OFF). Cross-platform, user-invokable, advisory output (exit 0 on findings), JSON + text output modes. Normative filename convention documented; v3.10 envelope provenance replaces filename matching. - **Phase Boundary coverage lint (Phase 5)** — new `scripts/check_v3_9_2_phase_boundary.py`: enforces 22 Bucket A agents have block, 16 Bucket B/C/D agents don't, and each Bucket A block contains 4 load-bearing phrases (Phase Boundary v3.9.2, MUST NOT, MAY READ, Enforcement v3.9.2). Wired to `.github/workflows/spec-consistency.yml`. - **Classification spec** — new `docs/design/2026-05-18-ars-v3.9.2-agent-phase-classification.md`: canonical 38-agent table with 4-bucket model (A=22, B=4, C=8, D=4) + per-agent out-of-scope inflation risk column. - **8 behavioral smoke test fixtures** — `tests/fixtures/issue_133_routing/`: cross-phase abstract+lit (the #133 root case), single-phase explicit, no-materials ambiguous, /ars-slash command, `[direct-mode]` byte-0 honored, mid-message NOT honored, case-insensitive accepted, full draft+abstract+lit+reviews. Honestly framed as LLM-behavior assertions with cross-model spot-check criterion (100% Opus 4.7, ≥75% Sonnet 4.6 + GPT-5.5). - **Plugin metadata bump** — `.claude-plugin/plugin.json` version 3.8.2 → 3.9.2 (was stale; also catches v3.9.0 + v3.9.1 deferrals); description updated for 38-agent ensemble and v3.9.2 phase boundary feature. ### Fixed - **`.claude/CLAUDE.md` Suite version was stale at 3.9.0** — v3.9.1 ship missed bumping it (latent lint bug surfaced during v3.9.2 work). v3.9.2 atomic bump fixes this. ### Tests - 12 new tests in `scripts/test_check_pipeline_integrity.py` (verifier). - 3 new tests in `scripts/test_check_v3_9_2_phase_boundary.py` (boundary coverage lint). - 4 additional tests after Phase 6 mid-impl review absorption (dotfiles ignored, multiple phase5 dirs independent, Unicode stem matching, nested subdir recursion). - Regression baseline: 1463 → 1482 passed (+19); 3 skipped + 111 subtests unchanged; 0 failures. ### Out of scope (carry to v3.10 conductor, issue #134) - PreToolUse hook (Phase 0.1 verified Claude Code payload includes `agent_type` field; hook implementation requires multi-phase schema first — both deferred to v3.10). - Multi-phase `ars_phase_writes` + `ars_phase_reads` envelope schema (scalar `ars_phase` cannot represent agents like `devils_advocate_agent` at Phases 1/3/5 or `report_compiler_agent` at Phases 4/6 — design correctly with envelope, not retrofit scalar). - Deterministic verifier with author provenance (advisory v3.9.2 filename-heuristic version flagged FP-prone in docstring). - Orchestrator cross-phase intake capability (`pipeline_orchestrator_agent` currently keyword-matches user phrasing; cannot reconcile cross-phase artifacts without explicit user signal — this is the conductor's core feature). ### Migration notes Existing in-flight projects: no break expected. v3.9.2 only adds prompt sections and an opt-in advisory verifier. Existing slash commands (`/ars-*`) continue to work without change. User-facing behavior change: if you previously dropped pre-existing materials (abstract + literature) into a fresh session without invoking a specific slash command, ARS may now clarify with a-d options instead of silent dispatch. To bypass clarification for direct agent dispatch, prefix your first message with `[direct-mode]`. To run the full pipeline on pre-existing materials, invoke `/ars-full`. If you see a Bucket B multi-phase agent (devils_advocate, report_compiler, argument_builder, visualization) producing out-of-scope content, this is a known v3.9.2 limitation — recurrence is expected for these 4 agents until v3.10 envelope ships. Remediation: switch to orchestrator-driven Mode A via `/ars-full` or report the case to issue #134 with transcript excerpt. --- ## [3.9.1] - 2026-05-18 — v3.9.0 client hardening (#129 + #130) Two-bug hotfix surfaced by codex review of `ars-codex` PR #13 (vendor sync to v3.9.0 `74413a4`). Both bugs exist in v3.9.0 main: #129 violates the v3.9.0 §3.7 per-API degradation contract; #130 crashes a defensive lint on malformed input. Neither changes the spec or schema. ### Fixed - **#129 — OpenAlex / Crossref response-read failures now translate to `*Unavailable`.** In `scripts/openalex_client.py:_get` and `scripts/crossref_client.py:_get`, `urlopen` succeeded but `resp.read()` / `body.decode("utf-8")` / `json.loads()` failures (socket drop mid-stream, truncated body, garbled UTF-8 body, HTML 503 page returned with 200 status) escaped the client as raw `OSError` / `http.client.IncompleteRead` / `UnicodeDecodeError` / `JSONDecodeError`. `scripts/migrate_literature_corpus_to_v3_9_0.py` only catches `OpenAlexUnavailable` / `CrossrefUnavailable`, so one transient response failure during a 500-entry backfill aborted the whole migration instead of dropping just the affected field. Narrow except block around read+decode+parse now catches `(OSError, http.client.HTTPException, UnicodeDecodeError, json.JSONDecodeError)` — `HTTPException` covers `IncompleteRead` (canonical mid-stream socket drop, inherits HTTPException not OSError, R1 codex P2 closure). Mirrors the existing 5xx-skip pattern: per-API tolerant per the v3.9.0 spec §3.7 documented degradation contract and `bibliography_agent.md` "Triangulation Extension". - **#130 — `check_claim_audit_consistency` non-string `manifest_id` guard.** `_build_manifest_index` (line 644) and `_build_manifest_constraint_index` (line 675) used `manifest_id` as a dict key via `setdefault(mid, set())` / `out[mid] = bucket` before checking type. For malformed passports where the schema validator already noted `manifest_id` as `array` / `object`, the index builder raised `TypeError: unhashable type: 'list'` and terminated lint with a traceback before `validate_passport()` could return the schema finding cleanly. Added `isinstance(mid, str) and mid` guard at both sites, matching the surrounding `_check_inv_17_for_manifest` / `claim_id` invariant-walker pattern. Schema validator still records the type mismatch — the guard just lets the lint surface findings cleanly instead of crashing. ### Tests - `scripts/test_openalex_client.py`: +4 tests covering OSError on `resp.read()`, invalid UTF-8 body, invalid JSON body, and `http.client.IncompleteRead` (R1 codex P2 closure). - `scripts/test_crossref_client.py`: +4 symmetric tests. - `scripts/test_claim_audit_schema.py`: new `TSManifestIdNonStringGuard` class with 2 tests (`manifest_id` as list / dict). - Regression baseline: 1453 → 1463 passed (+10), 3 skipped + 111 subtests unchanged, 0 failures. ### Out of scope - Spec / schema / CHANGELOG narrative not touched — the degradation contract is already documented in spec §3.7; this just makes code honor it. - `ars-codex` adapter sibling: the same two fixes will surface on next vendor sync (v3.9.1 → ars-codex v0.1.8). No action needed in this release. --- ## [3.9.0] - 2026-05-17 ### Added - Cross-index triangulation as v3.7.3 contamination_signals Vector 3 (issue #102). Two new optional boolean fields (`openalex_unmatched`, `crossref_unmatched`) inside `literature_corpus_entry.schema.json`. Manual-entry not-rule extended symmetrically to forbid all three lookup fields (preprint flag remains exempt — heuristic, not lookup). - OpenAlex API protocol (`deep-research/references/openalex_api_protocol.md`) + production client (`scripts/openalex_client.py`). - Crossref API protocol (`deep-research/references/crossref_api_protocol.md`) + production client (`scripts/crossref_client.py`). - `bibliography_agent.md` Triangulation Extension subsection — parallel S2/OpenAlex/Crossref lookups, per-API degradation, manual exemption, R-L3-2-D constraint, per-entry ingest log format. - Finalizer 4-tier advisory annotation in `pipeline_orchestrator_agent.md`: k=1 → `CONTAMINATED-COVERAGE-NOISE` (or legacy `CONTAMINATED-UNMATCHED` for k_max=1 S2-only), k=2 → `CONTAMINATED-PARTIAL-UNMATCH`, k=3 → `CONTAMINATED-TRIANGULATION-UNMATCHED`. All tiers advisory; gate refusal list unchanged. - `formatter_agent.md` pass-through allowlist extends from 3 v3.7.3 suffixes to 9 (3 legacy + 6 v3.9.0). Refusal rules 1-10 unchanged. - v3.9.0 lint (`scripts/check_v3_9_0_triangulation.py`): set-equality on formatter allowlist, refusal-list-unchanged guard. Exact-token extraction prevents substring collisions (R3 P2 closure). - Migration tool (`scripts/migrate_literature_corpus_to_v3_9_0.py`): backfill v3.7.3 → v3.9.0; stable-fields idempotency; per-API degradation tolerant; dry-run mode; daisy-chained migration scope (pre-v3.7.3 entries require v3.7.3 migration first). - 3 new firm rules in spec §3.3: R-L3-2-C (k computed over present fields, absent ≠ false), R-L3-2-D (no OpenAlex `primary_location.source.type` / Crossref `type` used for v3.9.0 classification logic), R-L3-2-E (refusal list unchanged; pass-through allowlist extends). ### Design philosophy - v3.9.0 is the **measurement layer** for cross-index triangulation. The **policy layer** (strict modes, hard-block tier, venue-type-scoped strict, `triangulation_policy` field, `venue_type` field) is deferred to v3.10 per spec §2.3. - The k=3 marker is `CONTAMINATED-TRIANGULATION-UNMATCHED` (describes observable condition), not `CONTAMINATED-LIKELY-FABRICATED` (would infer cause unsupportable on humanities / non-English / dissertation references where coverage gaps are real). - R-L3-2-A preserved verbatim: contamination signals never block emission on their own. ### Migration path - v3.7.3 corpora: run `python scripts/migrate_literature_corpus_to_v3_9_0.py PATH` to backfill the two new fields. - Pre-v3.7.3 corpora: run `python scripts/migrate_literature_corpus_to_v3_7_3.py PATH` FIRST, then v3.9.0 migration (daisy-chained per spec §3.7). ### Review trail - R1 (commit `d9280bf`): 15 findings (3 P0, 8 P1, 4 P2) — closed. - R2 (commit `7d51215`): 12 findings (0 P0, 3 P1, 9 P2) — closed. - R3 (commit `4297c27`): 4 P2 findings — closed in Task 1 of impl plan. - Both tracks (codex gpt-5.5 xhigh + Gemini 3.1-pro-preview) READY-FOR-IMPL after R3. --- ## [3.8.2] - 2026-05-17 — #118 uncited audit_tool_failure surface Fixes the #118 carry-over from #103 R3 codex P2 #5. The `ARS_CLAIM_AUDIT=1` uncited constraint-judging path used to silently substitute `{"judgment": "NOT_VIOLATED", "rationale": "..."}` on `JudgeInvocationError`, suppressing HIGH-WARN constraint checks on transient judge outage (judge timeout, API 5xx, network error, etc.). v3.8.2 routes those failures through a dedicated `uncited_audit_failures[]` aggregate at MED-WARN advisory tier, mirroring INV-14 semantics on the cited path but using a separate schema because `claim_audit_result.ref_slug` is required and the uncited path has no ref to bind. The #118 issue body listed four candidate options. Option 1 (extend `constraint_violation.schema.json`) would have broken the `judge_verdict: const VIOLATED` invariant and re-derived every CV-INV. Option 3 (overload `uncited_assertions[]` with a `fault_class` field) would have polluted the D4-c LOW-WARN advisory channel with audit-time infrastructure signal. Option 4 (re-raise `JudgeInvocationError` and abort the audit pass) would have dropped audit coverage for the entire run on a single transient outage — bad UX for N>50 papers running against flaky judge endpoints. Option 2 (new aggregate) ships here: structural honesty, schema integrity preserved, audit coverage preserved. ### Added - **`shared/contracts/passport/uncited_audit_failure.schema.json`** — new aggregate per spec §3.6. Required fields: `finding_id` (`UAF-NNN`), `claim_text`, `section_path`, `scoped_manifest_id`, `fault_class` (closed enum mirroring INV-14), `rationale` (MUST begin with fault_class prefix), `judge_model`, `judge_run_at`, `rule_version: D4-c-v1-uaf-v1`. Optional `manifest_claim_id` (non-null when failure was against an NC-C claim-level constraint, null when against MNCs only). - **UAF-INV-1..UAF-INV-6** lint coverage in `scripts/check_claim_audit_consistency.py` rule 4d: - UAF-INV-1: finding_id uniqueness across the aggregate - UAF-INV-2: scoped_manifest_id cross-array integrity - UAF-INV-3: (scoped_manifest_id, manifest_claim_id) pair integrity when manifest_claim_id non-null - UAF-INV-4: per-(sentence, manifest) dedup with key `(scoped_manifest_id, section_path, claim_text_hash)` - UAF-INV-5: rationale fault_class prefix matches the row's own `fault_class` field - UAF-INV-6: cross-aggregate exclusivity vs `constraint_violations[]` (VIOLATED and audit_tool_failure are mutually exclusive verdict states at per-(sentence, manifest) level) - **Finalizer §5 MED-WARN advisory row**: annotation `[CLAIM-AUDIT-TOOL-FAILURE-UNCITED — ]` next to the offending sentence. Always advisory; gate passes — retry on next pipeline pass is the remediation. Formatter REFUSE list unchanged (UAF is advisory, not gate-refuse). - **`UAF_RULE_VERSION = "D4-c-v1-uaf-v1"`** constant in `scripts/_claim_audit_constants.py` for shared use by pipeline runtime and lint. - **18 new tests** keeping the regression baseline 0 (694 → 712 tests): - 15 schema + lint tests in `scripts/test_claim_audit_schema.py::TSUAFUncitedAuditFailureInvariants` - 3 pipeline integration tests in `scripts/test_claim_audit_pipeline.py::TP23UncitedJudgeOutageEmitsUAF` proving the swallow is replaced with UAF emit and no synthetic NOT_VIOLATED leaks into any aggregate ### Changed - **`scripts/claim_audit_pipeline.py`**: swallow site at line 1211-1224 (the synthetic `NOT_VIOLATED` substitution) replaced with `_uncited_audit_failure_entry(...)` emission + `continue`. Pipeline return now includes `uncited_audit_failures` alongside the other five aggregates. - **`docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md`**: amended with new §3.6 (schema + UAF-INV-1..6 + co-emission rules), §4 step 5 stream (d) routing clause, §4 step 9 fourth error-handling bullet, §5 finalizer outputs list + advisory paragraph, §6 lint rule 4d + precedence rule 6 cross-aggregate exclusivity reference. - **`academic-pipeline/agents/claim_ref_alignment_audit_agent.md`**: Output emission table grows seventh row for `uncited_audit_failures[]`. Error handling table grows from 3 failure surfaces to 4 (the new uncited-path UAF row mirrors the cited-path `audit_tool_failure` row). ### Fixed - **#118**: uncited judge failure no longer swallowed as NOT_VIOLATED; the HIGH-WARN constraint check path is now observable on transient outage. Pre-v3.8.2 a flaky judge endpoint could silently pass a draft with a real MUST-NOT violation; v3.8.2 surfaces the operational failure at MED-WARN advisory tier so a retry pass picks it up. ### Review trail Single-PR ship after spec → TDD → impl. UAF schema design followed the design-phase brainstorming rule: option 1-4 trade-off analysis happened in conversation with the user before any code, captured in a local gitignored decision memo. Implementation followed strict TDD RED → GREEN — 15 schema/lint tests + 3 pipeline tests all failed in their intended way (no schema file, no lint logic, swallow site still active) before the schema, lint, helper, and pipeline change landed. No regression on the 694 pre-existing tests. --- ## [3.8.1] - 2026-05-17 — claim_audit lint hardening (#119 + #120 4×P2 closure) Defense-in-depth patch on `ARS_CLAIM_AUDIT=1` opt-in lint paths. Five fixes carried over from #103 R6 + R8 independent review, consolidated into one v3.8.1 release. No schema semantic change, no behavior change for well-formed payloads — pre-fix surfaces all crashed the CLI with `TypeError` / `AttributeError` instead of returning actionable lint findings or routing through the INV-14 `audit_tool_failure` translation boundary. ### Fixed - **#119 / #120 P2-2 — nested schema-invalid shapes no longer crash invariant walkers.** Added `_iter_dicts` helper and narrow `isinstance(str)` guards in `_check_inv_17_for_manifest`, `_check_manifest_invariants`, `_build_manifest_index`, `_build_manifest_constraint_index` so that nested `claim_intent_manifests[].claims` as string, `claims[].claim_id` non-string, or `audit_sampling_summaries[].audited_indices` mixed types now surface as clean schema findings instead of crashing on `for claim in "broken":`, regex against non-string, or `int <= str` comparison. The schema validator still records the type mismatch separately — narrow walker guards prevent the second-stage crash without masking schema-vs-invariant double coverage (option 2 refined, not aggregate-level skip). - **#120 P2-1 — CV-INV-4 dedupe scoped by `scoped_manifest_id`.** Dedupe key extended from `(section_path, claim_text_hash, violated_constraint_id)` to `(scoped_manifest_id, section_path, claim_text_hash, violated_constraint_id)`. Per M-INV-4, `manifest_id` is unique across the passport but constraint ids (`MNC-*` / `NC-*`) are only unique WITHIN a manifest — two manifests in the same passport may legitimately carry colliding constraint ids, and the same sentence may then violate both. Pre-fix, the dedupe false-positived these as duplicates. Spec wording in §3.5 + §7.1 4b updated. - **#120 P2-3 — judge `judgment` `isinstance(str)` guard before set membership.** `_validate_judge_dict` now rejects a non-string judgment (e.g. malformed `{"judgment": [1, 2], "rationale": "..."}`) as `judge_parse_error → audit_tool_failure` via the INV-14 translation boundary instead of bubbling `TypeError("unhashable type: 'list'")` out of the set-membership test. - **#120 P2-4 — retrieve `ref_retrieval_method` `isinstance(str)` guard before set membership.** Symmetric to P2-3 on the retrieval boundary. `_invoke_retrieve` rejects a non-string method as `retrieval_api_error → audit_tool_failure` instead of crashing on set membership. ### Tests - `scripts/test_claim_audit_schema.py`: 3 new tests in `TS9MalformedPassportGuard` (nested string / non-string claim_id / mixed-type indices) + new test class `TSCVDedupeManifestScope` with 2 tests (cross-manifest collision must keep both; within-manifest true duplicate still caught). - `scripts/test_claim_audit_pipeline.py`: 2 new tests in `TP12JudgeFailureAuditToolFailure` (non-string list + dict judgment) + 1 new test in `TP14RetrieveFailureAuditToolFailure` (non-string list method). - Regression baseline: 682 → 690 tests (+8), 0 failures, 0 errors across full `scripts/test_*.py` discovery. ### Design memo A local, gitignored design memo carries the option-1 vs option-2 analysis, CV-INV-4 dedupe key shape rationale, and the release-framing decision. Closes [#119](https://github.com/Imbad0202/academic-research-skills/issues/119). Refs [#120](https://github.com/Imbad0202/academic-research-skills/issues/120) P2-1, P2-2, P2-3, P2-4 (all four R8 findings). --- ## [3.8.0] - 2026-05-16 — L3 Claim-Faithfulness Locator + Audit (v3.7.3 + #103 paired milestone) v3.7.3 + v3.8 close the L3 (claim-faithfulness) gap end-to-end. v3.7.3 ships the locator infrastructure (every citation carries a three-layer anchor so the audit can fetch the cited passage); v3.8 ships the audit pass that consumes those anchors, judges whether the cited source supports the claim, and gate-refuses HIGH-WARN violations at the formatter terminal hard gate. The release also bundles 5 audit-trail-shipped feature PRs accumulated on main since v3.7.0 (#104 / #105 / #108 / #111 / #115). External motivation: Zhao et al. arXiv:2605.07723 (2026-05) — 146,932 hallucinated citations across arXiv / bioRxiv / SSRN / PMC in 2025. ### #103 — v3.8 claim ↔ reference faithfulness audit agent (2026-05-16) **Parent issue:** [#103](https://github.com/Imbad0202/academic-research-skills/issues/103) — closes the L3 (claim-faithfulness) gap left open by v3.7.3 (which closed the locator-channel half). Spec: `docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md` + decision doc `docs/design/2026-05-15-issue-103-claim-alignment-audit-decision.md` (D1-D6 settled). **Why:** Zhao et al. arXiv:2605.07723 (2026-05) shows 146,932 hallucinated citations across arXiv / bioRxiv / SSRN / PMC in 2025; v3.7.3 stopped the "no locator" path but a present-but-wrong claim ↔ source mismatch was still undetected. v3.8 adds a Stage 4→5 audit pass that judges every sampled citation against its retrieved excerpt, emits 5 new passport aggregates, and drives 5 new HIGH-WARN annotation classes through the formatter terminal hard gate. **New components:** - **`claim_ref_alignment_audit_agent`** (1 new agent, `academic-pipeline/agents/`) — opt-in (`ARS_CLAIM_AUDIT=1`, default OFF for v3.8.0) audit agent dispatched between v3.7.1 cite finalizer and formatter hard gate. Takes citations + manifests + corpus + Stage 4 draft sentence stream (full uncited + D4-c filtered subset). - **5 new passport schemas** (`shared/contracts/passport/`): `claim_audit_result`, `claim_intent_manifest`, `claim_drift`, `uncited_assertion`, `constraint_violation`. Cross-field invariants INV-1..INV-18 / M-INV-1..M-INV-4 / U-INV-1..U-INV-4 / D-INV-1..D-INV-4 / CV-INV-1..CV-INV-4 lint-enforced (JSON Schema can't express the conditional matrix relating judgment / audit_status / defect_stage / ref_retrieval_method). - **Runtime pipeline** (`scripts/claim_audit_pipeline.py`) — implements §4 step 1-6 + manifest set-diff (D6 set-of-text semantics). Per-citation judge wrapping (`_invoke_judge` + `_invoke_retrieve` translate transient failures to INV-14 `audit_tool_failure` rows: judge_timeout / judge_api_error / judge_parse_error / cache_corruption / retrieval_api_error / retrieval_timeout / retrieval_network_error). Cache hits re-validated through the same surface. Per-manifest uncited judge calls to prevent MNC id collisions across manifests. - **8-row finalizer matrix** (`scripts/claim_audit_finalizer.py`) — discriminates paywall (LOW-WARN advisory) / fabricated reference (HIGH-WARN gate-refuse) / anchorless (HIGH-WARN defense-in-depth) / audit_tool_failure (MED-WARN advisory) via `ref_retrieval_method` alongside `(judgment, defect_stage)`. - **5 new HIGH-WARN annotation classes** in `formatter_agent` REFUSE list: `[HIGH-WARN-CLAIM-NOT-SUPPORTED]` / `[HIGH-WARN-NEGATIVE-CONSTRAINT-VIOLATION]` / `[HIGH-WARN-FABRICATED-REFERENCE]` / `[HIGH-WARN-CLAIM-AUDIT-ANCHORLESS]` / `[HIGH-WARN-CONSTRAINT-VIOLATION-UNCITED]`. Mirrors v3.7.3 R-L3-1-A asymmetry — `/ars-mark-read` does NOT clear; remediation is fixing the prose. - **"Claim Intent Manifest Emission" sibling section** added to `synthesis_agent` / `draft_writer_agent` / `report_compiler_agent` per v3.6.7 PATTERN PROTECTION pattern. The §3a SHA-pinned blocks stay byte-equivalent to commit `e7e775a0e1b4`. - **Calibration runner** (`scripts/claim_audit_calibration.py` + `scripts/test_claim_audit_calibration.py` + `scripts/fixtures/claim_audit_calibration/gold_set.json`) — 20-tuple gold set (12 alignment + 8 constraint); T-C1 threshold gate (FNR < 0.15 + FPR < 0.10), T-C2 per-class FNR/FPR, T-C3 gold-set shape integrity. Re-run: `PYTHONPATH=. python3 -m unittest scripts.test_claim_audit_calibration -v`. - **2 new lints + 1 new pytest module + 7 new unittest modules wired into CI** (`.github/workflows/spec-consistency.yml`): `check_claim_audit_consistency.py` (38 invariant checks + schema validation), `check_v3_8_annotation_literal_sync.py` (formatter-finalizer literal drift gate). Test suite: 194 unittest tests across the 7 modules. **Review trail (Step 13 dual-track, 2026-05-16):** 8 rounds codex (gpt-5.5 xhigh) + 1 round Gemini 3.1-pro-preview before Gemini quota exhausted. Trajectory R1 4P1+2P2 → R2 0P1+3P2 → R3 0P1+5P2 → R4 2P1+2P2 → R5 0P1+2P2+1P3 → R6 1P1+1P2 → R7 1P1+1P2+1P3 → **R8 0P1+4P2 → ship**. Per `feedback_codex_review_surface_loop_design_phase.md` design-phase P2 noise floor doesn't auto-converge; the user declared ship signal at R8 with all P0/P1 closed and 4 R8 P2 carried over to v3.8.1 ([#120](https://github.com/Imbad0202/academic-research-skills/issues/120)). **Carry-over follow-up issues:** - [#118](https://github.com/Imbad0202/academic-research-skills/issues/118) — uncited path NOT_VIOLATED swallow on judge failure (schema-level decision) - [#119](https://github.com/Imbad0202/academic-research-skills/issues/119) — nested schema-invalid shapes still crash invariant helpers - [#120](https://github.com/Imbad0202/academic-research-skills/issues/120) — 4 R8 P2 findings (CV-INV-4 dedupe scope / invariant walker short-circuit / judgment + method type-check before set membership) **Regression baseline (post-ship):** - pytest: 1356 passed, 3 skipped, 103 subtests (was 1107 pre-#103, +249 tests across schema / pipeline / detector / manifest / finalizer / e2e / calibration / lint coverage) - v3.x lints: 7/7 PASS (v3.6.7 / v3.6.8 ×4 / v3.7.3 / v3.8) - personal-boundary: 0 violations (614 files scanned) - SHA-pinned zero-touch: `shared/sprint_contract.schema.json` 0 lines diff, `shared/contracts/passport/audit_artifact_entry.schema.json` 0 lines diff against main ### #115 — Semantic Scholar client maturity: throttle + outage latch (2026-05-15) **Parent issue:** [#115](https://github.com/Imbad0202/academic-research-skills/issues/115) — follow-up to #105 PR codex round-5 [P2]×2 findings (R5-2 throttle + R5-3 outage latch). Both deferred during #105 ship per architectural-inflection discipline; this entry closes the SS-client maturity gap. **Modified files:** - `scripts/semantic_scholar_client.py` — two additions: - **Throttle** (#115 R5-2): new ctor params `clock` + `min_interval_seconds`. Defaults: 1.0s unauthenticated (1 req/s per protocol), auto-drops to 0.1s when `S2_API_KEY` detected (authenticated 10 req/s tier). Pre-request pacing tracks `_last_request_at`; sleeps `max(0, min_interval - elapsed)` before each call. First request passes through. - **Outage latch** (#115 R5-3): `_latched_unavailable` flag set on `URLError`. Subsequent `lookup()` calls short-circuit with `SemanticScholarUnavailable` without invoking urlopen. New `reset_outage_latch()` method lets long-running tools retry between passport batches. HTTP 5xx does NOT latch (server-side error ≠ transport outage). - `scripts/test_semantic_scholar_client.py` — 9 new tests (5 throttle: first-no-sleep / back-to-back / past-interval / authenticated-tier / override; 3 latch: URLError short-circuits / reset restores / 5xx does not latch; 1 efficiency: 429-retry refreshes throttle anchor). - `scripts/contamination_signals.py` — new `reset_client_outage_latch(client)` helper. Production clients implementing the outage-latch pattern expose `reset_outage_latch()`; mocks may not. Helper invokes when present, no-ops when absent — avoids AttributeError when callers swap clients. 2 new tests. - `scripts/migrate_literature_corpus_to_v3_7_3.py` — `migrate_directory` resets the SS client's outage latch between passports so a transient network blip on one passport doesn't permanently disable lookups for the rest of the directory. Within a single passport the latch still short-circuits to protect a dead service from N retry waves. **Production behavior change:** - `_build_default_ss_client()` API unchanged (`SemanticScholarClient()` no-arg). New throttle is automatic per protocol — no migration tool changes required. - For a 5000-entry unauthenticated migration: same ~1.5hr runtime (already constrained by 1 req/s); now achieves it via deterministic pacing rather than 429-retry exhaustion. - For an authenticated migration (`S2_API_KEY` set): drops to 0.1s/call = ~8min for 5000 entries. - Network outage during large corpus: previously retried every entry independently (up to 30s timeout per entry on the slow path); now the first URLError latches the client and subsequent entries short-circuit until the next batch boundary calls `reset_outage_latch()`. The `migrate_directory` helper does this reset automatically between passports. **Out of scope:** migration tool (`migrate_literature_corpus_to_v3_7_3.py`) — #105 partial-fill / provenance contract correct as shipped. Protocol doc — already correct; this issue is implementation alignment. **Regression:** 472 unittest (+8 #115 tests) + 201 pytest adapters + spec_consistency + preprint_venues all green. ### #105 — v3.7.3 contamination_signals backfill migration tool (2026-05-15) **Parent issue:** [#105](https://github.com/Imbad0202/academic-research-skills/issues/105). Spec anchor: v3.7.3 §3.2 R-L3-2-B (the deferred batch operation; bibliography_agent computes signals at ingest, this tool delivers post-hoc backfill on legacy corpora). Design: `docs/design/2026-05-15-issue-105-contamination-signals-backfill-design.md`. **New files:** - `scripts/contamination_signals.py` — two pure-function resolvers + emission rules + `SemanticScholarClient` protocol. `compute_preprint_signal()` (Signal 1, deterministic year+venue check against 10-server closed list). `compute_ss_unmatched_signal()` (Signal 2, dependency-injected SS client, returns `None` on manual exemption + API degradation per spec). - `scripts/migrate_literature_corpus_to_v3_7_3.py` — CLI tool: `[--dry-run] [--verbose] `. Uses `ruamel.yaml` round-trip to preserve comments + key order + quoting style. Reports `processed / patched / skipped_already_migrated / skipped_insufficient_data` counts. Idempotent. - `scripts/test_contamination_signals.py` — 25 unit tests covering Signal 1 (15 cases: 10 preprint venues × year boundary, non-preprint venue, missing year, missing venue), Signal 2 (6 cases: manual exemption / match / no-match / API degradation × 2 paths / unexpected exception), emission rules (4 cases). - `scripts/test_migrate_literature_corpus_to_v3_7_3.py` — 9 unittest cases covering dry-run, full migration per emission rules, idempotency, insufficient-data skip, empty-corpus passport, directory scan (non-recursive), comment preservation. - `docs/migration/v3.7.3-contamination-signals-backfill.md` — user-facing migration guide (when to run, dry-run workflow, idempotency, SS API rate-limit considerations, what's out of scope). **Modified files:** - `shared/contracts/passport/literature_corpus_entry.schema.json` — purely additive: new optional `contamination_signals_backfilled_at` field (ISO-8601 date-time string). Existing v3.7.3 ingest-time entries (which lack this field) remain valid; pre-v3.7.3 entries (which lack both this field and `contamination_signals`) remain valid. - `scripts/adapters/tests/test_literature_corpus_entry_schema.py` — 3 new tests for the additive field (valid present / absent / non-string rejected). - `requirements-dev.txt` — add `ruamel.yaml>=0.17`. **Open-question resolutions (user-chosen 2026-05-15):** - Q1 API rate-limit handling: backoff-only via existing SS protocol (429 → 2s × 3); no resumable checkpoint (YAGNI per minimal scope) - Q2 schema field naming: scalar `contamination_signals_backfilled_at` ISO-8601 timestamp; strictly additive upgrade path if v3.7.4 needs structured provenance - Q3 multi-passport batch mode: directory-scan only; no `--input-list` (YAGNI) - Q4 YAML library: `ruamel.yaml` round-trip to preserve user-owned passport formatting (memory `feedback_toml_duplicate_table_corruption` spirit) **Spec discipline (per v3.7.3 R-L3-2-B):** - Migration is offline + opt-in: user explicitly invokes; pipeline doesn't auto-trigger - Idempotency keyed on `contamination_signals` presence: first-migration timestamp preserved across re-runs - `obtained_via=manual` exemption preserved at migration time (semantic_scholar_unmatched field omitted, matches the v3.7.3 schema cross-field rule) - API degradation → field omitted (NOT set to False, per "absence ≠ negative confirmation" rule) **Files explicitly NOT touched:** - `deep-research/agents/bibliography_agent.md` — v3.7.3 ingest-time computation frozen - `academic-pipeline/agents/pipeline_orchestrator_agent.md` — finalizer behavior unchanged - Existing `scripts/adapters/*` — adapters produce ingest-time entries; migration is downstream **Regression status:** 1053 #108 baseline + 17 #111 baseline + 25 resolver + 9 migration + 3 schema = 1107 total. All green. No regression on the existing 4 `allOf` cross-field invariants (manual exemption + preprint year=2024 boundary verified by adapter pytest). ### #104 — README motivation: add Zhao et al. corpus-scale evidence anchor (2026-05-15) **Parent issue:** [#104](https://github.com/Imbad0202/academic-research-skills/issues/104). Doc-only — no code changes. Adds a third evidence anchor to the `### Why human-in-the-loop, not full automation?` README section, between the ARS positioning paragraph and the PaperOrchestra paragraph. Closes the gap where v3.7.x trust-and-locator machinery appeared in the codebase without its corpus-scale motivation surfaced in the public-facing README. **Modified files:** - `README.md` — new Zhao et al. paragraph - `README.zh-TW.md` — translated equivalent **Three motivation anchors now read in sequence:** - Lu et al. (Nature 651:914-919) — case-study evidence of autonomous-pipeline failure modes - Zhao et al. (arXiv:2605.07723) — corpus-scale evidence of the citation-faithfulness problem (111M references / 2.5M papers / 146,932 conservative 2025 estimate / mid-2024 inflection / 85.3% bioRxiv-to-PMC persistence) - PaperOrchestra (Song et al., arXiv:2604.05018) — method-level technique source **Discipline (#104 acceptance criteria):** - Statistics verified directly against Zhao et al. abstract (111M / 2.5M / 146,932 / conservative qualifier) + v3.7.3 spec which carries the body-level numbers (85.3% bioRxiv→PMC specificity, mid-2024 inflection) through prior 10-round codex + gemini cross-model review. - No claims that v3.7.x "closes" L3 — only "adds locator infrastructure" / "advisory risk signals". - L3 attributed to ARS terminology, not the paper's. - "Motivated by" not "responds to". ### #111 — slr_lineage emission on systematic-review → academic-paper full handoff (2026-05-15, unreleased) **Parent issue:** [#111](https://github.com/Imbad0202/academic-research-skills/issues/111), follow-up to #108 (PR #110, merged 70c8678) round-8 P2 #1. Design: `docs/design/2026-05-15-issue-111-slr-lineage-emission-design.md`. > Version label `v3.7.4` below is provisional and will be confirmed at the next release sweep per `feedback_version_bump_sweep_checklist.md`. If this work ships as part of v3.7.3 (the in-progress release at writing time), the version stamps in this entry and the prose files below are swept to the final label at release tag. Closes the pipeline-plumbing gap surfaced by #108: `disclosure --policy-anchor=prisma-trAIce` now dispatches automatically when the documented `deep-research systematic-review → academic-paper full → disclosure` path runs, without the user manually supplying `mode=systematic-review` at cold-start. **New files added:** - `scripts/slr_lineage.py` — two pure functions: (a) `resolve_from_stages(stages)` returns `True` iff any stage was produced by `deep-research` in systematic-review mode (bound to the deep-research producer specifically — a non-deep-research stage carrying mode='systematic-review' does NOT trigger SLR lineage); (b) `emit(stages, incoming_slr_lineage)` is the monotonic-OR wrapper the orchestrator calls at every handoff. The OR preserves any signal already persisted on the incoming passport (load-bearing for `resume_from_passport=` sessions whose `state_tracker.stages` is empty — codex round-1 [P2] closure). - `scripts/test_slr_lineage_emission.py` — 17 conformance tests: resolver semantics (7 cases: positive / non-SLR / mid-entry / empty / alias `slr` / non-deep-research / missing-mode), renderer integration (3 cases: pipeline-emitted dispatches without `mode_param` / non-SLR still blocks / pre-#111 cold-start fallback preserved), end-to-end pipeline handoff (2 cases), and monotonic-OR emit semantics (5 cases: resume preserves true / in-session false-to-true / no-evidence false / None incoming / default arg ergonomics). **Modified files:** - `shared/handoff_schemas.md` — Schema 9 Material Passport gains optional top-level `slr_lineage: boolean` row + dedicated "Run-level lineage signal (v3.7.4)" subsection documenting semantics, producer, consumer, backward compat, and G1 boundary note (passport-level vs corpus-entry-level distinction). - `academic-pipeline/agents/pipeline_orchestrator_agent.md` — §4 Transition Management gains a "Run-level lineage emission (v3.7.4+)" step computed at every handoff transition before dispatch. Passport carry-line updated to reference `slr_lineage` from v3.7.4+. **Files explicitly NOT touched (matches #111 §Scope out-of-scope):** - `scripts/policy_anchor_disclosure_referee.py` — #108 referee, contract unchanged - `academic-paper/references/policy_anchor_disclosure_protocol.md` — #108 protocol, unchanged - `academic-paper/references/policy_anchor_table.md` — #108 anchor table, unchanged - `academic-paper/references/disclosure_mode_protocol.md` — already references `slr_lineage` as pipeline-supplied - `shared/contracts/passport/literature_corpus_entry.schema.json` — G1 invariant frozen (corpus entry schema, not passport schema) **G1 boundary clarification:** Decision Doc §4.4 #11 G1 invariant scope is `literature_corpus_entry.schema.json` (corpus entry data schema). Schema 9 Material Passport top-level extensions follow the v3.6.3 (`reset_boundary[]`) / v3.6.4 (`literature_corpus[]`) / v3.6.7 (`audit_artifact[]`) precedent and are permitted per Decision Doc §4.4 #11's "non-renderer code changes for §4.4 concerns are permitted" provision. **Backward compat:** passports written by pre-v3.7.4 runs lack the `slr_lineage` field; renderer treats absence as `false` (cold-start path requiring explicit `mode_param='systematic-review'`). Identical to pre-v3.7.4 behavior. **Regression status:** 1053-baseline frozen (no #108 contract drift); +17 new tests cover this issue's acceptance criteria #1-#3 plus codex round-1 [P2] (monotonic-OR emit across resume). ### #108 — AI disclosure policy-anchor renderer (2026-05-14, audit-trail-shipped) **Parent docs:** Decision Doc (`docs/design/2026-05-14-ai-disclosure-schema-decision.md`, PR #109, merged commit 20ed72d) + implementation spec (`docs/design/2026-05-14-ai-disclosure-impl-spec.md`). **Migration note (G1 + G6 invariants):** **no migration required**. Decision Doc §2.1 G1 invariant: no `ai_disclosure` field is added to `shared/contracts/passport/literature_corpus_entry.schema.json`. Decision Doc §3 G6: no deprecation horizon — legacy entries (which by §1 fact-check do not carry any AI-disclosure field today) stay byte-equivalent. The implementation extends the runtime renderer path, not the data schema. **New files added:** - `academic-paper/references/policy_anchor_table.md` — 4-anchor (PRISMA-trAIce / ICMJE / Nature / IEEE) × 16-field source-of-truth reference table carrying verbatim policy quotes lifted from discovery doc §4.3-4.6 (PR #107, commit 299c4b6) + per-anchor renderer rules. - `academic-paper/references/policy_anchor_disclosure_protocol.md` — LLM-prose runtime protocol for the new `--policy-anchor=` track: 7-section flow covering inputs / G10 7-row precedence table / per-anchor render flows / auto-promotion forbiddance / venue-anchor conflict resolution / three-state completeness flag / 11-concern resolution map. - `shared/policy_data/nature_policy.md` — canonical Nature substantive policy source; both the policy-anchor track and the v3.2 venue track cross-reference this path for the G4 dedup invariant. - `scripts/check_policy_anchor_table.py` + `scripts/test_check_policy_anchor_table.py` — anchor table structural lint with 13 mutation tests + Nature dedup guard wired into the main lint command. - `scripts/check_policy_anchor_protocol.py` + `scripts/test_check_policy_anchor_protocol.py` — protocol doc lint with 12 mutation tests covering §4.3 8 invariants + §4.4 11 concerns + G10 7-row precedence table + auto-promotion forbiddance + anchor inventory closed-enum. - `scripts/policy_anchor_disclosure_referee.py` + `scripts/test_policy_anchor_disclosure.py` — executable specification (referee) of §3 G10 7-row decision table + 8 invariant predicates; 61 conformance tests covering every (input × expected output) combination + forbidden-path negative fixtures. **Modified files:** - `academic-paper/references/disclosure_mode_protocol.md` — `--policy-anchor=` track added in parallel to v3.2 `--venue=` track. Phase 1 dispatch becomes selector-aware (step 1a / step 1b venue / step 1c anchor). Venue-only flow unchanged; anchor flow delegates Phase 3+4 to `policy_anchor_disclosure_protocol.md`. Concern #7 venue+anchor conflict resolution enforced. - `academic-paper/references/venue_disclosure_policies.md` — Nature entry gains derivation note + dedup pointer to `shared/policy_data/nature_policy.md`. v3.2 venue rendering content unchanged (derived view, manual sync to canonical source until future refactor). - `.github/workflows/spec-consistency.yml` — 5 new CI steps wiring the new validators and conformance test suite into the existing spec-consistency job. **§4.4 11 open concerns resolved** (4 user-chosen, 7 inline; full table in impl spec §3): 1. Track-selection lookup: explicit `slr_lineage` input from pipeline orchestrator (user-chosen). 2. Tool identity collection: auto-detect from session metadata (mirror v3.2 Phase 4). 3. Prompt scope: per-(tool × task) tuple per PRISMA M6.a. 4. IEEE section locator: free-form list with recommended IMRaD exemplars. 5. Nature image metadata: hybrid output channel (annotation block + suggested inline patches) (user-chosen). 6. UNCERTAIN per-facet finalization: USED-full + per-facet annotation alongside still-UNCERTAIN (user-chosen). 7. Venue+anchor conflict: reject conflicting selectors with explicit error. 8. Three-state completeness flag: full computation logic encoded in §6 of protocol doc. 9. Test set scope: 86 new tests covering 8 invariants + 10 concerns × {positive, negative}. 10. `ai_used:true` substantive-content gate: force v3.2 categorization flow (user-chosen). 11. G1 invariant scope: data layer untouched; non-renderer pipeline plumbing permitted. **Known follow-up (out of #108 scope):** the academic-pipeline orchestrator does not yet emit `slr_lineage` on the documented `systematic-review → academic-paper full` handoff. Authors targeting `--policy-anchor=prisma-trAIce` must supply `mode=systematic-review` manually until that plumbing lands in a separate PR (touches `academic-pipeline/` + `shared/handoff_schemas.md`, outside §4.1 items 1-5 NO-CHANGE boundary). **Regression status:** 967 baseline + 86 new tests = 1053 passing / 3 skipped / 0 failed. Public-repo boundary clean. Eight rounds of codex gpt-5.5 xhigh review (R1 4 P2 → R8 2 P2); shipped audit-trail-complete per user decision rather than pushing past Decision Doc 11-round high water mark. R8 P2 #1 captured as the known follow-up above. ### v3.7.3 — claim faithfulness locator + contaminated-source advisory (2026-05-12, in progress) **External motivation:** Zhao, Wang, Stuart, De Vaan, Ginsparg, Yin "LLM hallucinations in the wild: Large-scale evidence from non-existent citations" (arXiv:2605.07723, 2026-05). Corpus-scale audit of 111M references across 2.5M papers across arXiv / bioRxiv / SSRN / PMC finds 146,932 hallucinated citations estimated for 2025 alone, with the inflection point at mid-2024, 85.3% of preprint hallucinations surviving into the published record, and Google Scholar increasingly indexing citation-only entries. The paper names the L3 (claim faithfulness) gap explicitly: *"real citations deployed to support claims the cited references do not actually make ... remains an open challenge for which reliable detection methods remain under active development."* v3.7.3 closes the locator-channel half of that gap (anchor infrastructure for future L3 audit) and surfaces two contamination signals (preprint post-LLM-inflection + Semantic Scholar unmatched) as advisory cite-time markers. **L3-1 — Three-Layer Citation Emission (claim faithfulness locator):** - `deep-research/agents/synthesis_agent.md`, `academic-paper/agents/draft_writer_agent.md`, `deep-research/agents/report_compiler_agent.md` gain `## Three-Layer Citation Emission (v3.7.3)` H2 section that extends v3.7.1 Two-Layer with a third hidden marker: `` where `` ∈ `{quote, page, section, paragraph, none}`. Production-mandatory locator rule (R-L3-1-A) requires `` ≠ `none` for every visible citation; emitting `none` triggers finalizer MED-WARN-NO-LOCATOR (gate-refused). Quote anchors capped at 25 words by whitespace split (R-L3-1-B). Anchor values come from corpus context only — no frontmatter reads (R-L3-1-C, inherits v3.6.7 partial-inversion discipline). - `academic-pipeline/agents/pipeline_orchestrator_agent.md` gains a `## Cite-Time Provenance Finalizer — v3.7.3 extension` H2 section: 4-cell matrix becomes 5-cell along a new precedence-zero locator-presence axis. NO-LOCATOR resolution: `[UNVERIFIED CITATION — NO QUOTE OR PAGE LOCATOR]`. - `academic-paper/agents/formatter_agent.md` gains a `## Cite-Time Provenance Hard Gate (v3.7.1 + v3.7.3)` section formalizing the terminal hard-gate refusal across all three v3.7.x severity tiers (HIGH-WARN-NO-ORIGINAL, MED-WARN-NOT-CROSS-CHECKED, MED-WARN-NO-LOCATOR). **L3-2 — Contaminated-source advisory signals:** - `shared/contracts/passport/literature_corpus_entry.schema.json` adds optional `contamination_signals: { preprint_post_llm_inflection, semantic_scholar_unmatched }` object. Both sub-fields optional within the object; both default to absent (signals not computed). `additionalProperties: false` enforced on the sub-object. Backward compat: entries without the field stay valid. - `deep-research/agents/bibliography_agent.md` gains `## Contamination Signal Computation (v3.7.3)` section. Signal 1 (`preprint_post_llm_inflection`): `year >= 2024 AND venue ∈ {arXiv, bioRxiv, medRxiv, SSRN, Research Square, Preprints.org}`. Signal 2 (`semantic_scholar_unmatched`): existing Semantic Scholar API protocol returns no match by DOI or title; exempted when `obtained_via: manual`; omitted (not `false`) on API degradation. - Pipeline finalizer (in pipeline_orchestrator) annotates `ok` / `LOW-WARN` markers with `CONTAMINATED-PREPRINT` / `CONTAMINATED-UNMATCHED` / `CONTAMINATED-PREPRINT+UNMATCHED` suffix per `contamination_signals` state. Annotations are **advisory only** — they do NOT change the gate decision (v3.5 Collaboration Depth Observer precedent). **Lint + tests:** - New `scripts/check_v3_7_3_three_layer_citation.py` static lint: every `` must be followed by ``; `quote` values ≤25 words; orphan anchors rejected. - New `scripts/test_check_v3_7_3_three_layer_citation.py`: 14 tests covering positive (5 kinds × passing cases, contamination-suffix marker, LOW-WARN-resolved marker, multi-citation) + negative (bare ref, orphan anchor, invalid kind, 26-word quote). - New 6 contamination_signals tests in `scripts/adapters/tests/test_literature_corpus_entry_schema.py`: absence / empty / both-false / both-true / unknown-subfield-rejected / non-boolean-rejected. - New `V373ExtensionLineBudgetTest` in `scripts/test_v3_6_7_phase_6_6.py`: 60-line budget for `## Cite-Time Provenance Finalizer — v3.7.3 extension` block; existing Phase 6.6 +60 v3.6.7 budget test updated to subtract both v3.7.1 Step 3b AND v3.7.3 extension lines. **Regression status (final, post round-10 convergence):** 967 tests pass, 3 skipped, 0 failed (42 new tests across rounds 1-10 fixes; pre-review baseline was 925). v3.6.7 + v3.6.8 + v3.7.1 + v3.7.2 lints all PASS unmodified. v3.6.7 PATTERN PROTECTION blocks remain byte-equivalent (SHA gate v2 unchanged). Material Passport literature_corpus_entry schema backward compatible (new contamination_signals field optional; cross-field rules only fire when explicitly set). New v3.7.3 lint wired into spec-consistency.yml CI workflow per F18. **Cross-model review closure (2026-05-12, 11 rounds total — 10 codex + 1 gemini cross-model):** | Round | Reviewer | Findings | Closures | |---|---|---|---| | 1 (initial) | Codex | 0 P1 / 2 P2 | F3 (untracked artifacts → closed at commit), F4 (NO-LOCATOR acknowledgment contradiction → removed `/ars-mark-read` promise from formatter+finalizer+spec Q5) | | 1 (initial) | Gemini 3.1-pro-preview | 2 P1 / 2 P2 / 1 P3 | F1 (hyphen-encode → 3 prompts + lint + 3 tests), F2 (whitespace/newline tolerance → finalizer clarification + 4 tests), F5 (year<2024 schema cross-field → allOf + 4 tests), F6 (venue list 6 → 10 added ChemRxiv / EarthArXiv / OSF Preprints / TechRxiv), F7 (fenced code block isolation → helper + 4 tests) | | 2 | Codex | 0 P1 / 2 P2 | F8 (lint regex widened to {0,2} suffix tokens → 3 tests), F9 (empty non-`none` anchor value rejection → 5 tests) | | 3 | Codex | 0 P1 / 2 P2 | F10 (premature HTML comment terminator sentinel scan → 3 tests), F11 (schema manual-entry exemption → 4 tests) | | 4 | Codex | 0 P1 / 1 P2 / 1 P3 | F12 (orphan_pattern lookbehind removed → 3 tests), F13 (schema venue list description sync 6 → 10) | | 5 | Codex | 0 P1 / 1 P2 | F14 (malformed ref broad-scan detector → 4 tests) | | 6 | Codex | 0 P1 / 1 P2 | F15 (prompt-vs-lint alignment on `--` rule → 2 tests; prompts loosened to match lint's narrower contract) | | 7 | Codex | 0 P1 / 3 P2 | F16 (finalizer status-suffix-tolerant for revision-loop reruns), F17 (standalone deep-research self-gate), F18 (CI workflow wires v3.7.3 lint into spec-consistency.yml) | | 8 | Codex | 0 P1 / 3 P2 | F19 (decode value before empty check → 3 tests), F20 (formatter raw `anchor:none` gate), F21 (F17 self-gate scoped to standalone mode only via prompt mode-detection) | | 9 | Codex | 0 P1 / 1 P2 | F22 (self-gate also rejects bare refs without anchor — parity with pipeline finalizer's precedence-zero "no anchor = anchor=none" rule) | | **10 (final)** | **Codex** | **0 findings** | **Convergence achieved.** | - **No cross-finding overlap across reviewers.** Codex and Gemini found complementary defect classes — Codex caught contract gaps + regex completeness + architectural integration; Gemini caught HTML comment parsing edge cases + cross-field schema rules + venue completeness. This is the canonical value split documented in `feedback_codex_workflow_consolidated.md`. - **Cascade pattern:** each round's closure introduced no new defects in its OWN scope, but interactions with other v3.7.3 surfaces surfaced new layers — F19 was an F9 layer (encoded-whitespace bypass after the F9 raw-value fix), F21 was a F17 regression (self-gate ran in pipeline mode and interfered with finalizer), F22 was an F17+F21 boundary (only catching explicit `none` markers missed bare-ref legacy form). The 10-round convergence trajectory is consistent with the v3.6.8 18-round implementation precedent and `feedback_complex_spec_review_inventory_pattern.md`. - **F23+ not yet observed.** Round 10 returned no findings on the 9th amended branch state, providing the convergence signal. Future codex challenge mode (adversarial scope) may surface architecturally deeper gaps; tracked separately as a v3.7.4+ concern. **Out of v3.7.3 scope (tracked as follow-up issues):** - v3.7.4 retrieval-side hardening: OpenAlex + Crossref triangulation as second contamination signal (Vector 2 currently single-source via Semantic Scholar only). - v3.8 L3 full audit: `claim_ref_alignment_audit_agent` running LLM-as-judge over (claim, ref full-text) pairs. v3.7.3 anchors are the input; v3.8 verifies anchor content faithfulness. - AI disclosure schema split (per-stage: drafting / editing / **reference suggestion** / data analysis) — Zhao et al. Fig. 1l correlates AI-writing-signature with hallucination rate. - Public README motivation update citing arXiv:2605.07723. - Migration tool for legacy `literature_corpus[]` entries lacking `contamination_signals`. Spec: `docs/design/2026-05-12-ars-v3.7.3-claim-faithfulness-and-contaminated-source-spec.md`. ### Backlog — gbrain harness borrow analysis (2026-05-10, post codex review) Source: 2026-05-10 analysis of `garrytan/gbrain` (14.2k★ agent harness for OpenClaw/Hermes), with codex cross-model review same day. Two candidates surfaced; they have different risk profiles and are tracked separately. **Candidate A — Shared `shared/_invariants.md` cross-skill rules file** (gbrain pattern P3). Status: backlog, low-risk. ARS cross-cutting rules are scattered today: Iron Rules in adapter overview, hedging contract in `protected_hedging_phrases.md`, citation precedence in agents' frontmatter, integrity gates referenced from multiple SKILL.md. When a rule evolves (e.g. v3.6.5 corpus protocol Iron Rules), secondary mentions drift. Shape if adopted: - `shared/_invariants.md` enumerating **positive invariants only** (no rejected-reasoning column; that was the contamination vector in the 2026-05-10 anti-pattern-table evaluation) - File stays short, normative, and example-free — additional examples turn invariants into demonstrations and re-introduce few-shot drift - Each SKILL.md references it via a stronger convention than `## See Also` (which reads as optional reading); proposed wording at adoption time - Frontmatter `validated_against: ` enables a stale-reference grep job on minor bumps. **The grep job detects version drift only — it does NOT validate semantic compliance.** Semantic checks remain a human / codex review responsibility. **Candidate B — Declarative `shared/_review_pairs.yaml` cross-model review config** (gbrain pattern P6). Status: **needs design spike before becoming a real candidate**, higher-risk. ARS cross-model review is currently invoked imperatively: `ARS_CROSS_MODEL=1` env flag + manual codex review per phase. A declarative `(deliverable_kind, reviewer_model, dimensions, when_to_invoke)` map could improve reproducibility for Stage 2.5 / 4.5 integrity gates and Phase 6 in-pair evaluator review. Three open problems before this is shippable: 1. **Refusal-routing semantics conflict.** gbrain's chain (primary → DeepSeek → Qwen → Groq, silent switch) routes past refusal; ARS treats reviewer disagreement as signal. Borrowing the YAML format without resolving this imports the wrong invariant. Likely answer is "borrow the declarative-pairing shape, drop the refusal-routing chain entirely." 2. **Embedding governance in config.** A YAML that decides "this deliverable triggers this reviewer with these dimensions" is workflow policy. Wrong shape locks in a bad routing decision across all phases. Needs a usage survey of existing manual invocations before designing the schema. 3. **Lower confidence than Candidate A.** ARS already has review phases and cross-model invocation working manually; the missing piece is reproducibility, not the capability. If manual invocation isn't causing missed reviews or inconsistent reviews in practice, this should drop too. Rejected from same gbrain analysis: P1 RESOLVER.md dispatcher (10 slash commands serve dispatch), P4 trust boundary (research tool, no untrusted caller class), P5 pain-triggered subagent routing (covered in user CLAUDE.md, repo-level not relevant). **P2 friction protocol** is a soft reject — codex review pointed out a first-class friction CLI captures pain at the moment of pain, which 5+ round codex review at deliverable-time does not. Re-examine if ARS skill development surfaces recurring author-time pain that retrospective review doesn't capture. Meta-lesson from this analysis: "we already do something adjacent" is weaker than it sounds as a reject reason. The test is whether the existing mechanism captures the same signal at the same time with the same enforcement strength. ### Added (v3.6.7 Step 6 Phase 6.8 — Step 8 evaluation case) - **17 micro-fixtures + 1 chapter-level integration fixture** under `tests/fixtures/v3_6_7_pattern_eval/` exercising the 17 numbered downstream -agent patterns (A1–A5, B1–B5, C1–C3, D1–D4) per spec §7. Each micro fixture: `manifest.json` (`fixture_kind: "micro"`) + `upstream_context/` (`passport_snippet.yaml` + `prior_artifacts/`) + `bad_run/` + `good_run/` with `deliverable.md`, `expected_audit_findings.yaml`, `expected_orchestrator_action.yaml`. Integration fixture under `integration/chapter_level_run/` exercises A3+C2+D4+C1 across 3-round MATERIAL escalation → ship_with_known_residue acknowledgement per §7.3. - **`scripts/check_pattern_eval_manifest.py`** — fixture_kind discriminator routing micro (§7.2) vs integration (§7.3) JSON Schema 2020-12 manifest schemas; `audit_verdict.schema.json` validation on every `expected_audit_findings.yaml`; path-safety rejects absolute paths and `..` segments; coverage cross-check enforces 17/17 numbered IDs covered (with hard-fail on unknown directory names per §7.5). - **`scripts/test_pattern_eval_runtime.py`** — 112-test parametrized harness reading expected verdicts as synthesized output and asserting against expected orchestrator action. Per-pattern parametrized tests (BAD signal + GOOD passes + run_id F1 regex + BAD/GOOD uniqueness); integration state runner driving §7.3 5-step procedure (load verdicts → drive §5.6 → verify pipeline state per round → feed escalation user_response → verify final passport state); Path A re-verification axis (≥6 A7 happy-path legs at rounds 2+3); finding-id lineage carry-forward per audit-template Section 6; per-phase synthetic injections (24 of 26 PHASE_TO_PASSPORT_MUTATION rows validated for "none" / "appended"); A1.5 supersession-preflight axis tests. - **`scripts/test_run_codex_audit_e2e.py`** — Phase 6.1 deferred end-to-end dispatch test (Linux Bash 4+ only; macOS stock Bash 3.2 self-skips). Mocks codex CLI via PATH-prefix shim emitting canonical Phase 2 JSONL stream. Validates wrapper produces 4 contract files + 3 diagnostic files; proposal entry validates against `audit_artifact_entry.schema.json --mode proposal` (Pattern C3 defense — `verified_at`/`verified_by` absent); `--dry-run` writes nothing; `--round=2` without `--previous-findings` rejected with `EX_USAGE`. - **`.github/workflows/spec-consistency.yml`** — 4 new CI steps: Phase 6.8 manifest validation, pattern-eval-unit (micro fixtures + phase inventory + synthetic non-supersession), pattern-eval-integration (integration fixture + synthetic supersession), Phase 6.1 wrapper E2E (Linux runner only). - **`docs/design/TODO-l-doc-1-18-patterns-prose-retirement.md`** — files L-doc-1 follow-up enumerating 8 retirement locations for the docs-only PR retiring "18 patterns" prose to "17 patterns" per §9.2. - **Spec amendments** at `docs/design/2026-04-30-ars-v3.6.7-step-6-orchestrator -hooks-spec.md`: §7.4 success criterion 1 prose updated for C2 MINOR special case + D2 PASS convergence-policy assertion; §7.4 phase example updated `escalation` → `B11`; §7.6 deployment note explaining named-step CI deployment (vs literal "two separate jobs"); §9.2 L-doc-1 row points at the TODO file; §7.3 example manifest snippet updated to F-101/F-103. ### Notes - **11 codex review rounds converged to 0 findings**. Cumulative 24 findings closed (4 P1 + 18 P2 + 2 P3) across rounds 1-10. - 135 Phase 6.8-specific tests; total repo regression 742 pytest + 251 unittest = 993 green + 3 skipped (macOS Bash 3.2 wrapper E2E gate). - v3.6.7 Step 6 + Step 8 now structurally complete: prompt-level pattern protection (Step 1+2) + version sweep (Step 7) + runtime audit-artifact gate (Step 6 §1-§11 + Phases 6.1-6.7) + synthetic evaluation case (Phase 6.8) deliver the §10 ship-quality target. ## [3.7.0] - 2026-05-05 > **Claude Code plugin packaging.** ARS now installs in one line on Claude Code > CLI / VS Code / JetBrains via `/plugin marketplace add Imbad0202/academic-research-skills` > + `/plugin install academic-research-skills`. The traditional > `git clone + symlink to ~/.claude/skills/` flow continues to work — both > tracks are first-class. ### Added - **Plugin manifest + marketplace metadata** (Phase 1, PR #68). `.claude-plugin/plugin.json` declares the suite. `.claude-plugin/marketplace.json` registers the plugin so a single GitHub-hosted endpoint serves both the marketplace listing and the plugin source. `skills/` directory carries relative symlinks to the four existing skill directories so the plugin loader auto-discovers them without moving repo layout. - **10 slash commands** at `commands/ars-*.md` (Phase 2.1, PR #69) mapping `MODE_REGISTRY.md` entries to `/ars-` triggers. Model routing pinned in each command's frontmatter — `opus` for `full` and `revision-coach` (architectural / review-interpretation depth), `sonnet` for the other 8. No Haiku per `feedback_no_haiku.md`. - **3 plugin-shipped agents** at `agents/*_agent.md` (Phase 2.1, PR #69) as relative symlinks to the v3.6.7-hardened downstream agents in `deep-research/agents/`: `synthesis_agent`, `research_architect_agent`, `report_compiler_agent`. Underscore filenames preserved to match `scripts/check_v3_6_7_pattern_protection.py` hard-pinned paths and the INV-3 manifest-confined Clause 1 invariant. Symlinks (not copies) preserve a single source of truth and prevent the Pattern C3 attack surface that v3.6.7 §6 inversion sweep + INV-1/2/3 lint closes. - **`model: inherit`** added to those three source agent frontmatters (PR #69 R1 codex finding). Inherit chosen over pinning `sonnet` so an Opus session running the full pipeline keeps Opus agents (instead of being capped) while the user's existing PreToolUse `warn-agent-no-model.sh` hook gates Haiku at the dispatch boundary. - **SessionStart announce hook** at `hooks/hooks.json` + `scripts/announce-ars-loaded.sh` (Phase 2.2, PR #70). When the plugin loads, the hook injects `additionalContext` listing the 10 slash commands, the 3 plugin agents, and a token-budget pointer into the LLM's first turn. `startup` and `clear` source values get the full announce; `resume` and `compact` get a one-line ack to avoid burning context on every resume. Bash 3.2 compatible — runs on macOS stock `/bin/bash` with no `brew install bash` requirement. `${CLAUDE_PLUGIN_ROOT}` quoted for install paths containing spaces. - **`docs/PERFORMANCE.md` + `.zh-TW.md`** subsection "v3.7.0 Plugin agents and model routing" explaining `model: inherit` semantics and the current 3-agent scope boundary. - **`docs/ARCHITECTURE.md`** Evolution Timeline extended with v3.6.7 / v3.6.8 / v3.7.0 entries. - **README + README.zh-TW** version badge bumped to v3.7.0; Pipeline section heading bumped to v3.7; CHANGELOG entry added. ### Deferred (future release) - **SubagentStop → `run_codex_audit.sh` codex audit hook** (Phase 2.2 scope reduction). Two compounding reasons: (a) wrong invoker class — `run_codex_audit.sh` lines 4–7 forbid same-session in-LLM invocation (Pattern C3 attack surface), and the original PostToolUse Write|Edit matcher would fire from inside the producing session; (b) contract gap — the SubagentStop hook payload carries no stage/deliverable info, so a wrapper would have to half-infer those required arguments. Real audit-hook integration deferred to a future release when ARS gains a stage/deliverable propagation contract. See `docs/design/2026-04-30-ars-v3.7.0-plugin-packaging-roadmap.md` Update note 2026-05-05 (Phase 2.2 scope reduction). ### Changed - `academic-pipeline/SKILL.md` frontmatter `version: "3.7.0"` + H1 + Version Info table. - `MODE_REGISTRY.md` Last updated bumped to `v3.7.0 (2026-05-05)`. - `.claude/CLAUDE.md` Skills Overview row + Suite version footer bumped to 3.7.0. - `scripts/check_spec_consistency.py` lint pins (Suite version, README badge, MODE_REGISTRY heading, CHANGELOG section heading) bumped to v3.7.0. ### Unchanged The four skill directories, all 25 modes, agent prompts, schema files, and lint contracts. Plugin packaging only adds new top-level surface (`commands/`, `agents/`, `hooks/`, `.claude-plugin/`, `skills/` symlink dir, three plugin-agent `model: inherit` frontmatter additions). Existing 4.3k clone-install users see no breaking change. ### Codex review chain 8 inline iterative rounds + 3 fresh PR-level rounds across the three PRs (#68 / #69 / #70), all converging to 0 P0/P1/P2 findings before merge. The Phase 2.2 fresh PR review caught one P2 (unquoted `${CLAUDE_PLUGIN_ROOT}` breaking install paths with spaces) that the inline rounds missed — confirms the value of separating implementation review (inline) from contract / install-time review (fresh). Reference: `feedback_codex_review_vs_resume_audit_scope.md`. ## [3.6.8] - 2026-05-03 > **Naming note**: this release ships the **v3.6.6 generator-evaluator contract** > spec (`docs/design/2026-04-27-ars-v3.6.6-generator-evaluator-contract-design.md`) > and its implementation. The v3.6.6 work landed after v3.6.7 due to project > sequencing; the design doc retains the v3.6.6 internal naming for the > contract gate version (`writer_full` / `evaluator_full` mode, Schema 13.1, > `pre_commitment_artifacts` + `disagreement_handling` schema fields), while > the suite release is tagged v3.6.8 to keep the CHANGELOG monotonic. ### Added - **Schema 13.1 generator-evaluator contract gate** for `academic-paper full` mode (`shared/sprint_contract.schema.json`, design doc §3): two new `mode` enum values (`writer_full` + `evaluator_full`); two new optional top-level fields (`pre_commitment_artifacts` writer-only with `acceptance_criteria_paraphrase.minimum_dimensions`; `disagreement_handling` evaluator-only with `paraphrase_minimum_dimensions` + `scoring_plan` + `pre_commitment_check_protocol` + `disagreement_resolution`); 12 `allOf` branches enforcing reviewer- / writer- / evaluator-conditional gates (existing 2 + 10 new per design doc §3.5 table). - **Two new shipped contract templates**: `shared/contracts/writer/full.json` (writer dimensions D1 section_completeness / D2 citation_density / D3 argument_blueprint_fidelity / D4 total_word_count / D5 per_section_word_count / D6 acknowledged_limitations / D7 register_consistency; F-conditions F1/F4/F2/F3/F0; no `scoring_plan`) and `shared/contracts/evaluator/full.json` (evaluator dimensions D1 originality / D2 methodological_rigor / D3 evidence_sufficiency / D4 argument_coherence / D5 writing_quality; F-conditions F1/F2/F3/F6/F4/F5/F0; full `scoring_plan` + `disagreement_handling`). Templates already shipped on the spec branch as design-time artefacts since 2026-04-28; this release promotes them to live status atomically with the Schema 13.1 upgrade. - **Two-phase orchestration inside `academic-paper full` mode** (design doc §5): Phase 4 splits into Phase 4a paper-blind writer pre-commitment + Phase 4b paper-visible drafting + self-scoring. Phase 6 splits into Phase 6a paper-blind evaluator pre-commitment + Phase 6b paper-visible scoring + decision. Phase-numbered `` / `` data delimiters mirror the v3.6.2 reviewer pattern. Lint counts: writer 3+4 / evaluator 5+5 / reviewer 5+6 (reviewer surfaces remain zero-touch per §3.6). `[GENERATOR-PHASE-ABORTED]` abort tag with 5% / three-month operational monitor. - **`academic-paper/SKILL.md` `## v3.6.6 Generator-Evaluator Contract Protocol` orchestration block** (101 lines): four-call structure with system-vs-user content discipline, schema-vs-runtime emission distinction, per-phase lint, abort handling, two valid Stage 3 entry paths (standard F0/F4 + exceptional F5), cross-session resume scope. Plus a new `## Known limitations` section carrying the graceful-degradation forward note (v3.6.7 candidate) + the cross-session resume `pre_commitment_history[]` forward note (v3.6.7+ candidate) + in-pair Phase 6 evaluator vs external `academic-paper-reviewer` tech debt. - **`academic-paper/agents/draft_writer_agent.md` + `peer_reviewer_agent.md`** each gain a verbatim `## v3.6.6 Generator-Evaluator Contract Protocol` section with the system-prompt sub-sections for Phase 4a/4b (writer) and Phase 6a/6b (evaluator). The orchestrator includes the relevant sub-section verbatim in the system prompt for the corresponding call; user content carries contract JSON, paper metadata, delimiter blocks, and upstream artefacts per the SKILL.md discipline. - **`scripts/check_sprint_contract.py` SC-* mode-gating audit** (per §7.1 implementation requirement): SC-5 (measurement_procedure canonical outputs) and SC-11 (panel_size sanity) now mode-gated to `mode.startswith("reviewer_")` so they do not noise on clean writer / evaluator templates. SC-9 (paraphrase_minimum_dimensions exceeds dim count) extended across all three mode families: reviewer reads `mp.paraphrase_minimum_dimensions`, writer reads `pre_commitment_artifacts.acceptance_criteria_paraphrase.minimum_dimensions`, evaluator reads `disagreement_handling.paraphrase_minimum_dimensions`. Mode-agnostic warnings (SC-1 baseline lag, SC-2 single dimension, SC-3 no mandatory, SC-4 orphan dim ref, SC-7 conflicting actions, SC-10 unreferenced mandatory/high) unchanged. - **17 new validator tests** (54 → 71 total): 4 writer/evaluator template positive tests; 5 schema-branch negative tests covering branches 11 / 12 / 4 / 5 / 6 hard-fail (cross-mode field leakage intentionally NOT a v3.6.6 hard-fail per §7.1 R1 settled — v3.7.x `not`-clause hardening is the long-term fix); 2 §3.6 reviewer regression tests (`test_existing_reviewer_contracts_still_valid_under_13_1` + `test_byte_equivalent_validation_for_reviewer_contracts`); 6 SC-5/SC-9/SC-11 mode-gating tests. - **`scripts/check_v3_6_6_ab_manifest.py`** (new) implements the §7.5 manifest CI lint: schema-shape checks per §6.2 (top-level required fields with declared types; per-paper required fields; paper_id uniqueness; aggregate role counts 6+1; paper-A paper_type families 3 × 2; paper-A required judge_output_baseline; paper-C must-have known_failure_mode + failure_evidence; paper-C must-not-have judge / metrics fields); path-existence checks (mode-conditional + populated-optional); reverse-scan against fixture-orphans; exit-1-on-malformed-YAML mirrors `check_sprint_contract.py` convention. - **`.github/workflows/spec-consistency.yml`** extends the "Validate sprint contract templates" step to iterate writer + evaluator template directories alongside the existing reviewer loop, and adds a new "Validate v3.6.6 A/B fixture manifest" step running the new manifest CI lint script as an additional step inside the existing `spec-consistency` job. - **`tests/fixtures/v3.6.6-ab/` A/B evidence fixture stub** (30 files): manifest.yaml + README.md + 6 paper-A inputs/baseline + 1 paper-C inputs/baseline + Stage 3 reviewer excerpt + 6 codex-judge baseline placeholders. `manifest_lint_mode: spec_branch`, `fixture_version: 0.1.0`. Each placeholder explains the expected populated content; real fixture data (existing deep-research synthesis reports for paper-A; v3.6.5 session log + Stage 3 reviewer excerpt for paper-C; codex gpt-5.5 + xhigh judge runs against paper-A baseline) populates in follow-up commits before the v3.6.6 implementation work fully completes. - **`academic-paper-reviewer/references/sprint_contract_protocol.md` cross-reference** noting Schema 13.1 since v3.6.6 + pointing readers at `academic-paper/SKILL.md` + design doc §5 for the parallel generator-evaluator protocol. The reviewer protocol itself is byte-equivalent across v3.6.2 → v3.6.8 (zero-touch promise per §3.6). ### Changed - **Suite version**: v3.6.7 → v3.6.8 (per the naming note above; design doc retains v3.6.6 for the contract gate version). - **`academic-pipeline` skill version** bumped from v3.6.7 to v3.6.8 in the `.claude/CLAUDE.md` Skills Overview table. ### Deferred - **Real fixture data populate** for `tests/fixtures/v3.6.6-ab/` (30 placeholders → real paper-A inputs + baseline + paper-C session log + codex judge runs) lands in follow-up commits. - **Treatment runs** (writer Phase 4a/4b + evaluator Phase 6a/6b on the seven fixtures), **codex judge against treatment**, and **metrics computation + summary.md** require actual `academic-paper full` invocations + Semantic Scholar API + codex CLI runs; deferred to follow-up commits before the fixture-completeness work concludes. - **manifest_lint_mode flip** from `spec_branch` to `implementation_pr` co-lands with the treatment population in the same atomic merge state per §6.5 invariant 3. - **ROADMAP §3.6.4 description correction** per design doc §9.3 ("Extend v3.6.2 sprint contract pattern to the existing `academic-paper` writer/evaluator pair via contract-gated phase splits and Schema 13.1 conditional gates. No new agent files; existing `draft_writer_agent` and `peer_reviewer_agent` gain per-phase sub-section instructions") lands in the private ROADMAP.md (gitignored, maintained outside this public repo), not in this repo PR. ## [3.6.7] - 2026-04-30 ### Added - **Downstream-agent pattern protection layer** (`docs/design/2026-04-29-ars-v3.6.7-downstream-agent-pattern-protection-spec.md`). Hardens three downstream agents against 17 hallucination/drift patterns documented in the spec: `synthesis_agent` (A1–A5 narrative-side), the survey-designer mode of `research_architect_agent` (B1–B5 instrument-side), and the abstract-only mode of `report_compiler_agent` (C1–C3 publication- side), plus four cross-cutting patterns (D1–D4). Patterns observed in production output across multiple chapter-length runs. - **Four reference files in `shared/references/`** carrying the operational contracts that protection clauses cite: - `irb_terminology_glossary.md` — anonymity vs confidentiality vs de-identification vs pseudonymization (B1). - `psychometric_terminology_glossary.md` — true reverse-coded vs contrast item, with construct-equivalence rule (B2). - `protected_hedging_phrases.md` — five-rule contract for upstream-marked hedge protocol (conservative inclusion, anchor every entry, no duplicates, verbatim preservation, conflict reporting) (C1). - `word_count_conventions.md` — whitespace-split standard (`body.split()`), 3–5% buffer below hard cap, publisher conventions (C1). - **Cross-model audit prompt template** at `shared/templates/codex_audit_multifile_template.md` — seven audit dimensions (cross-ref, hallucination, primary-source integrity, internal coherence, instrument quality, Round-N framing, COI adequacy) plus a mandatory three-part Section 4(f) check for `report_compiler_agent` bundles (whitespace-split cap-minus-buffer, protected-hedge verbatim, abstract no less hedged than body — failure of any sub-check is P1). - **Static lint** at `scripts/check_v3_6_7_pattern_protection.py` enforcing protection-clause presence and obligation-phrase shape across the reference files, audit template, and three downstream agent prompts. Per-regex `allow_prohibition` flag scopes the prohibition exemption so prohibition-style obligations (`DO NOT simulate`, `must not claim audit-passed state`, `does not paraphrase`) do not leak the exemption to assertion-style obligations on the same Check. Span-restricted exemption rejects a second prohibition elsewhere in the bullet. Modal/advisory weakener coverage: `may`, `should`, `can`, `will`, `would`, `ought to`, `ideally`, `preferably`, `We recommend that`, `is/are recommended`, `is/are allowed`, `is/are permitted`, plus exception qualifiers (`except`, `unless`, `save when`). - **Mutation test suite** at `scripts/test_check_v3_6_7_pattern_protection.py` with 29 tests preserving codex review evidence (R2–R6). Future checker regressions surface in CI rather than only in ad-hoc mutation runs. - **CI wiring** in `.github/workflows/spec-consistency.yml` runs both the static lint and the mutation suite on every push and pull request. ### Changed - **`deep-research/agents/synthesis_agent.md`** carries a `PATTERN PROTECTION (v3.6.7)` block with five clauses covering effect-inventory cross-section consistency self-check, pending-verification hedge wrap, one-line anchor justification, verbatim phrase boundary on quotes, and the prohibition on declarative claims about un-provided documents (with conditional-language fallback). - **`deep-research/agents/research_architect_agent.md`** survey-designer mode carries a `PATTERN PROTECTION (v3.6.7)` block with five clauses covering IRB terminology pass-through, reverse-coded construct- equivalence justification, event-anchored retrospective default (calendar-anchored only when sample shares a common event date), neutral-balanced item phrasing with chapter argument vocabulary forbidden, and primary-source list enumerate-fully (no subsetting, no over-setting, no scope cross-contamination). - **`deep-research/agents/report_compiler_agent.md`** abstract-only mode carries a `PATTERN PROTECTION (v3.6.7)` block with three clauses covering whitespace-split word budget plus 3–5% buffer with budget- protected hedges, explicit-temporal-bounds reflexivity disclosure (year range / past-tense disambiguating verb / "former" prefix; deictic phrases forbidden), and the anti-fake-audit guard (DO NOT simulate any audit step; DO NOT claim to have run codex/external review; output metadata must not claim audit-passed state). ### Notes - v3.6.7 ships in two stages. **Step 1 + Step 2** (this entry) include the four reference files, the audit template, the static lint, the mutation test suite, the CI wiring, and the three agent-prompt protection blocks. **Step 6** (orchestrator hooks for automatic per-agent audit and anti-fake-audit guard wiring) and **Step 8** (synthetic evaluation case demonstrating all 17 patterns triggered + protected) ship in a follow-up PR. Step 6 is cross-agent runtime work that warrants its own design discussion and is intentionally decoupled from this prompt-and-lint PR. - Codex review history: seven rounds of `gpt-5.5` + `xhigh` cross-model review reached SHIP-OK with zero P1 + P2 findings. R1 closed ten Step-1 findings; R2 closed four cascade gaps plus the per-Check `allow_prohibition` leak; R3 closed three P2 findings (span-restricted exemption, token→regex with imperative anchoring, `except/unless/ save when` weakeners); R4 closed three P2 findings (modal verb scope expansion, §6 sub-clause coverage, lint→CI wiring); R5 closed one P2 plus one P3 (`should/can/permitted` modals and the mutation test suite); R6 closed one P2 (`will/would/ought to/ideally/preferably/ We-recommend-that` weakeners) and explicitly deferred orchestrator runtime hooks to the Step 6 follow-up PR. R7 surfaced only one P3 add-counter signal (`try to / generally / where relevant` weakeners), which is non-blocking polish. - ARS pipeline ship-quality target updates from "each agent produces a clean v1" to "end-to-end deliverable set passes independent xhigh cross-model audit at 0 P1 + P2 finding within three rounds" (per spec §10). ## [3.6.5.2] - 2026-04-27 ### Changed - **`docs/SETUP.md` Method 4 (claude.ai) recommendation revised**. Method 4b (Project + GitHub integration) is now presented first as the recommended claude.ai path, since it brings the repository into Project knowledge for reading and citation without losing fidelity. Method 4a (Custom Skill upload) is now explicitly marked as **not recommended for this suite**, with a rationale paragraph covering two compounding reasons: - ARS depends on Claude Code-only orchestration features. Each skill drives 12-13 specialised agents through Claude Code's Task / subagent tooling and Material Passport file handoffs that resume across sessions. claude.ai Custom Skills do support multi-file packages with `scripts/` and code execution per Anthropic's documentation, but the Anthropic- documented scope of the claude.ai Custom Skill runtime does not include Claude Code's Task / subagent control surface or cross-session Material Passport handoffs. The recommendation is forward-looking based on those documented assumptions; we have not run a live upload to characterise the actual surfacing in claude.ai. - Trimming the four `description` fields below claude.ai's 200-character cap would weaken Claude Code and Cowork routing on the platforms the suite was actually built for. The Agent Skills specification and Claude Code Skills documentation both allow up to 1,024 characters; only claude.ai's upload UI enforces 200. Trading Claude Code and Cowork routing precision for partial functionality on the limited claude.ai path was judged not worth it. - **Method 4a install commands kept in place** for users who decide to try it anyway, framed as "if you want to try this path despite the limitations" rather than as a recommended flow. The upload UI's expected rejection on description-too-long is documented as deliberate, not an oversight to fix later. - **`docs/SETUP.zh-TW.md`** mirrors the English changes end-to-end. ### Notes - Doc-only patch. No `SKILL.md` (frontmatter or body), no agent file, no schema, no script, no test, no workflow, and no version bump in any skill changed in this patch. The four current `description` fields stay at their Claude Code-native lengths (440-842 characters) so routing on Claude Code and Cowork remains intact. - This patch is a scope change from the v3.6.5.2 originally forecast in the v3.6.5.1 SETUP doc. The earlier plan was a description trim; on review, the trim direction was abandoned because it would have damaged Claude Code and Cowork routing to unblock a path that delivers an untested partial fit anyway. The v3.6.5.1 SETUP text's forward-promise of a description trim is removed here. - Issue [#44](https://github.com/Imbad0202/academic-research-skills/issues/44) receives a single consolidated reply on this PR's merge, summarising both v3.6.5.1 (SETUP doc rewrite) and v3.6.5.2 (Method 4a recommendation), and closes there. ## [3.6.5.1] - 2026-04-27 ### Fixed - **`docs/SETUP.md` Method 3 install paths** — Option A (symlink) and Option B (copy) now install each of the four skill folders separately into `~/.claude/skills//`, matching the `//SKILL.md` discovery convention. The previous text installed the whole repo under `~/.claude/skills/academic-research-skills/`, which buried the four `SKILL.md` files one level too deep for Cowork / Claude Code discovery. - **`docs/SETUP.md` Method 4 (claude.ai) restructured** — split into Method 4a (Custom Skill upload via Settings → Capabilities → Skills, the standard claude.ai Skill install path) and Method 4b (Project + GitHub integration, fallback knowledge mode and not a Skill install). The previous text framed GitHub integration as a Skill install path, which conflated content retrieval with skill execution. Method 4a documents the current 200-character `description` cap blocker (this entry originally forecast a description trim in v3.6.5.2; see the v3.6.5.2 entry above for the actual decision — Method 4a is documented as not recommended for this suite, and descriptions remain at their Claude Code-native lengths). - **Method 3 prerequisites** — expanded from one sentence to a full prerequisites subsection covering Claude Desktop version, internet connectivity, Cowork process model, folder permissions, paid plan, and Team/Enterprise org-admin controls. - **Method 4 prerequisites** — split per sub-method. 4a documents zip structure + description cap surfacing as upload-time errors; 4b documents GitHub authentication via the Anthropic connector, private-repo App authorization, and Team/Enterprise owner-level connector enablement. - **Cowork UI terminology** — replaced "Cowork tab" / "working directory" with current Cowork UI labels: mode selector (Chat / Cowork), Tasks view, "Use an existing folder" in the left navigation panel, and Cowork Project as the canonical term. - **Skill invocation framing** — clarified that Claude uses each skill's `description` for relevance routing rather than literal trigger-phrase matching, and documented the Cowork `/` command palette and `+` capability picker as explicit invocation surfaces. - **Method 4 directory table** — added the `scripts/` row (required for Material Passport `literature_corpus[]` adapters and schema validators) and refreshed the project-capacity guidance against current Anthropic Project file limits (per-file 30 MB; file count is not artificially capped at 200). - **`docs/SETUP.zh-TW.md`** — mirrored the English rewrite end-to-end so Traditional Chinese readers see the same structure and content for Methods 1-4. - **`QUICKSTART.md` Step 1** — install commands aligned with the new Method 3 four-symlink approach. ### Notes - Doc-only patch. No skill content (`SKILL.md`), no agent file, no schema, no script, and no test changed in this patch. - Issue [#44](https://github.com/Imbad0202/academic-research-skills/issues/44) (philpav) reports SETUP problems on Cowork and claude.ai. v3.6.5.1 fixes the SETUP doc; this entry originally forecast a `SKILL.md` description-length fix in v3.6.5.2, but v3.6.5.2 instead documents Method 4a as not recommended for this suite (see the v3.6.5.2 entry above for the actual decision). Issue #44 receives a single consolidated reply and closes on v3.6.5.2 ship. ## [3.6.5] - 2026-04-27 ### Added - Material Passport `literature_corpus[]` consumer integration in Phase 1 (deep-research/bibliography_agent + academic-paper/literature_strategist_agent). Corpus-first, search-fills-gap flow with PRE-SCREENED reproducibility block. Reproducibility for systematic-review use is preserved through Iron Rule 1 same-criteria parity plus Step 2 case C (standard external search runs even when corpus fully covers RQ subtopics). - `academic-pipeline/references/literature_corpus_consumers.md` — consumer protocol reference with four Iron Rules (Same criteria / No silent skip / No corpus mutation / Graceful fallback on parse failure) and per-consumer reading instructions. - `scripts/check_corpus_consumer_protocol.py` — CI lint enforcing nine protocol invariants with manifest-driven consumer list and stub-block opt-out. - `scripts/corpus_consumer_manifest.json` — supported-consumer manifest. ### Changed - `shared/handoff_schemas.md` Schema 9 — retired the v3.6.4 "Consumer-side integration deferred to v3.6.5+" caveat; replaced with backpointer to the consumer protocol. - `deep-research/SKILL.md` 2.9.1 → 2.9.2 — bibliography_agent corpus-first flow (also syncs Version Info footer that lagged at 2.9.0). - `academic-paper/SKILL.md` 3.1.0 → 3.1.1 — literature_strategist_agent corpus-first flow. - `academic-pipeline/SKILL.md` 3.6.4 → 3.6.5 — suite version invariant. - `.claude/CLAUDE.md`, `MODE_REGISTRY.md`, `README.md`, `README.zh-TW.md`, `scripts/check_spec_consistency.py` updated for the version bump (suite version, badge, tag, changelog heading). ### Notes - Consumer integration is presence-based: auto-engages when passport carries a non-empty `literature_corpus[]` and parses cleanly. Parse failures fall back to external-DB-only flow with a `[CORPUS PARSE FAILURE]` surface. No new env flag introduced. - Schema is unchanged from v3.6.4. Existing user adapters work without modification. - `citation_compliance_agent` corpus integration deferred to v3.6.6+. - `source_pointer` is not dereferenced by consumers; URI resolution remains a future `source_verification_agent` concern. ## [3.6.4] - 2026-04-25 ### Added - **Material Passport `literature_corpus[]` input port**. Schema 9 gains an optional `literature_corpus[]` field defined by `shared/contracts/passport/literature_corpus_entry.schema.json`. Each entry carries `citation_key`, CSL-JSON `authors`, `year`, `title`, and a `source_pointer` back to the user's own KB. `abstract` and `user_notes` are private optional fields with copyright caveats. - **Adapter contract** (`academic-pipeline/references/adapters/overview.md`): language-neutral specification for producing literature_corpus entries from user-owned corpus sources. Covers fail-soft entry-level error handling, mandatory `rejection_log.yaml` output, deterministic ordering (sort by `citation_key` / `source`), and extension points for user-written adapters. - **Three reference Python adapters** (`scripts/adapters/`): `folder_scan.py` (filesystem of PDFs), `zotero.py` (Better BibTeX JSON export), `obsidian.py` (vault frontmatter, BibTeX-style or literature-note convention). Each ships with pytest tests, fixtures, and golden expected outputs. - **Rejection log contract** (`shared/contracts/passport/rejection_log.schema.json`). Always emitted; empty when no rejections; closed enum of categorical reason values. - **CI lint + pytest job**: `scripts/check_literature_corpus_schema.py` (schema + adapter example validation), `scripts/sync_adapter_docs.py --check` (schema→docs drift detector with auto-regen mode), and a new `.github/workflows/pytest.yml` running `scripts/adapters/tests/` on path-filtered triggers. - `_common.ensure_unique_citekey(key, existing)` helper for adapters whose source already supplies a citekey (zotero, obsidian frontmatter), with sanitization to satisfy the schema pattern and a/b/...zz alpha-suffix collision disambiguation. - `_common.path_to_file_uri(path)` helper that delegates to `Path.as_uri()` so spaces and reserved characters in filenames are properly percent-encoded. ### Changed - `academic-pipeline/references/passport_as_reset_boundary.md`: "deferred to v3.6.4, PR-B" placeholders replaced with forward references to `adapters/overview.md` and `literature_corpus_entry.schema.json`. - `shared/handoff_schemas.md`: Schema 9 optional fields table adds `literature_corpus`; new "Literature Corpus Input Port (v3.6.4)" subsection appended after Reset Boundary Extension. - `academic-pipeline/SKILL.md` bumped 3.6.3 → 3.6.4 (suite version invariant). Other skills retain independent semver. - `.claude/CLAUDE.md`, `MODE_REGISTRY.md`, `README.md`, `README.zh-TW.md`, `scripts/check_spec_consistency.py` updated for the version bump (suite version, badge, tag, changelog heading). ### Not changed (explicit non-goals) - No ARS agent consumes `literature_corpus[]` yet. Consumer-side integration is deferred to v3.6.5+. v3.6.4 defines the input port only. - No PDF parsing, no text extraction, no live API clients, no authenticated library crawling. The reference adapters read filenames or local export files and never make network calls. ## [3.6.3] - 2026-04-23 ### Added - **Opt-in passport reset boundary** via `ARS_PASSPORT_RESET=1`. Every FULL checkpoint becomes a context-reset boundary when the flag is set. `systematic-review` mode with the flag ON makes reset mandatory; other modes treat reset as the flag-gated default. - **`resume_from_passport=` mode** in `academic-pipeline`. Lets users resume a pipeline run in a fresh Claude Code session from the Material Passport ledger alone. - **Schema 9 `reset_boundary[]`** optional append-only field with two entry kinds (`boundary`, `resume`). Entry shape in `shared/contracts/passport/reset_ledger_entry.schema.json` (oneOf split with `kind` discriminator). Hash computed via JSON Canonical Form + SHA-256 with `"000000000000"` placeholder for self-reference safety. Optional `pending_decision` field handles MANDATORY branch choices (Stage 3 reject/restructure/abort, Stage 5 finalization) that survive the reset boundary. - **Protocol doc:** `academic-pipeline/references/passport_as_reset_boundary.md` (authoritative; every file mentioning `ARS_PASSPORT_RESET` must co-locate a reference). - **CI lint:** `scripts/check_passport_reset_contract.py` + unittest suite. Wired into `.github/workflows/spec-consistency.yml`. - **`docs/PERFORMANCE.md` + `docs/PERFORMANCE.zh-TW.md`** long-running-session subsection documenting when reset beats continuation, passport file-location convention, and empirical-measurement disclaimer. ### Changed - `academic-pipeline/agents/pipeline_orchestrator_agent.md` adds §"Passport Reset Boundary (v3.6.3+)" and §"Resume Mode: `resume_from_passport`". FULL Checkpoint Template includes conditional reset-handoff tag slot. - `academic-pipeline/references/pipeline_state_machine.md` documents `awaiting_resume` transitions derived from the ledger (no out-of-band state). - `academic-pipeline/SKILL.md` adds `resume_from_passport` to the mode table and bumps version 3.6.2 → 3.6.3. - `shared/handoff_schemas.md` Schema 9 gains `reset_boundary` row + "Reset Boundary Extension (v3.6.3)" subsection with full YAML example showing both kinds. ### Changed (post-P1 fixes) - `pending_decision.options[]` now carries per-branch routing (`{value, next_stage, next_mode}`); `value` uniqueness within one options array is enforced by CI lint (`scripts/check_passport_reset_contract.py`). The matched option's `next_stage` supersedes the boundary entry's advisory `next` field. `next` MAY be `null` when all branches terminate or no sensible default exists. - Exclusive advisory lock (POSIX `fcntl.flock LOCK_EX`, bounded timeout not exceeding 60 s, 30 s recommended) is required for the resume read-check-append sequence. Non-POSIX implementations MUST refuse to resume rather than degrade silently. ### Notes - **Flag OFF is the default.** Pre-v3.6.3 behavior is preserved byte-for-byte when `ARS_PASSPORT_RESET` is unset or `=0`. - Out of scope (deferred to v3.6.4): `examples/adapters/{folder_scan, zotero, obsidian}/` reference adapters and the `literature_corpus` entry shape on Schema 9. - No breaking changes. No existing mode behavior changes when the flag is OFF. ## [3.6.2] - 2026-04-23 ### Added - **Sprint Contract (Schema 13) — reviewer hard gate.** `shared/sprint_contract.schema.json` defines machine-checkable acceptance criteria (`panel_size`, `acceptance_dimensions`, `failure_conditions` with `severity` + `cross_reviewer_quantifier`, `measurement_procedure`, optional `override_ladder`, bounded `agent_amendments`). Validator `scripts/check_sprint_contract.py` (schema validation + `check_structural_invariants()` hard check + nine soft warnings SC-1..SC-11 with SC-6 documented as dead path and SC-8 promoted to hard check). Two templates ship: `shared/contracts/reviewer/full.json` (panel 5) and `shared/contracts/reviewer/methodology_focus.json` (panel 2). Reviewer orchestration reshaped into paper-content-blind Phase 1 + paper-visible Phase 2 hard gate. Synthesizer runs three-step mechanical protocol (build matrix → evaluate with quantifier → resolve precedence). See `docs/design/2026-04-23-ars-v3.6.2-sprint-contract-design.md`. - **Token cost note.** Reviewer total calls under sprint contract = `2 × panel_size`. For `reviewer_full`: 5 → 10 calls. Phase 1 input is metadata-only and output short, so real token bound is well below 2x. ### Changed - **`academic-paper-reviewer` v1.8.1 → v1.9.0.** Five reviewer agent markdown files (EIC + methodology + domain + perspective + DA) gain Phase 1/2 protocol sections; `editorial_synthesizer_agent.md` gains the three-step synthesizer protocol + forbidden-operations list. - **Harness retirement notes folded in.** The prior `[Unreleased]` harness-retirement pass (Task A per `project_ars_v3.6_execution_order.md`) ships with this release — 7 negative-framing blocks rewritten to positive / split form across 7 files, no behaviour change: - `academic-paper/agents/socratic_mentor_agent.md` — Core Principles items 1, 6 (F-001) - `deep-research/agents/socratic_mentor_agent.md` — Quality Standards items 2, 3, 4 (F-002) - `academic-paper/agents/draft_writer_agent.md` — quick style check, paragraph variation, colloquialisms, transition-word usage (F-003, 4 spots) - `academic-pipeline/agents/pipeline_orchestrator_agent.md` — **split** "Prohibited Actions" (9 items, all negative) into "Scope (delegate, don't perform)" (items 1-6, positive delegation) + "Hard boundaries (never violate)" (items 7-9, kept negative as intentional safety directives for silent-failure modes: fabrication, skipped checkpoints, skipped integrity gates) (F-004) - `academic-pipeline/agents/collaboration_depth_agent.md` — Agent-specific boundaries 4 bullets (F-005) - `academic-pipeline/SKILL.md` — single-line UX guidance (F-006) - `academic-paper/references/academic_writing_style.md` — §4 Formality 3 items (F-007, discovered during apply) ### Notes - `reviewer_re_review`, `reviewer_calibration`, `reviewer_guided` are reserved in the Schema 13 `mode` enum but ship without contract templates in v3.6.2. Those modes continue pre-v3.6.2 behaviour until a follow-up patch adds their templates. - `reviewer_quick` is intentionally excluded from the Schema 13 `mode` enum (Q3-A' boundary). - CI gate: `validate-sprint-contracts` step in `.github/workflows/spec-consistency.yml` runs the full unit test suite and validates every template under `shared/contracts/reviewer/*.json` against the current ARS version. - Kept-as-debt from harness retirement: ~50 anti-hallucination references across `deep-research/`, `academic-paper/references/anti_leakage_protocol.md`, `academic-pipeline/references/ai_research_failure_modes.md`, `shared/agents/compliance_agent.md`, `shared/compliance_checkpoint_protocol.md` — load-bearing integrity architecture (Lu 2026 7-mode; S2 API Tier-0; `[MATERIAL GAP]` taxonomy). Not retired under the iron rule clause for silent-failure domains. ## [3.5.1] - 2026-04-22 ### Added - **Opt-in Socratic reading-check probe.** When `ARS_SOCRATIC_READING_PROBE=1` is set, the Socratic Mentor fires a one-time honesty probe during goal-oriented sessions where the user has cited a specific paper. The probe asks the user to paraphrase one passage. Decline is logged without penalty. Outcome is recorded in the Research Plan Summary and flows into the Stage 6 AI Self-Reflection Report when the pipeline continues. Default OFF. Roadmap slot: v3.7.3. See `deep-research/agents/socratic_mentor_agent.md` §"Optional Reading Probe Layer". ### Changed - `deep-research/SKILL.md`, `deep-research/references/socratic_mode_protocol.md`, `academic-pipeline/references/process_summary_protocol.md` — aligned text updates for the new probe section. No behaviour change when the env var is unset. ### Version - Suite: 3.5.0 → 3.5.1 (patch; opt-in, default OFF, no breaking change) - `deep-research` skill: 2.9.0 → 2.9.1 - `academic-pipeline` skill: 3.5.0 → 3.5.1 (tracks suite version per `check_version_consistency.py` invariant) ## [3.5.0] - 2026-04-21 ### Added - `shared/collaboration_depth_rubric.md` v1.0 — canonical 4-dimension rubric (Delegation Intensity, Cognitive Vigilance, Cognitive Reallocation, Zone Classification). Based on Wang, S., & Zhang, H. (2026). "Pedagogical partnerships with generative AI in higher education: how dual cognitive pathways paradoxically enable transformative learning." *International Journal of Educational Technology in Higher Education*, 23:11. DOI 10.1186/s41239-026-00585-x. Licensed CC-BY-NC 4.0. - `academic-pipeline/agents/collaboration_depth_agent.md` — observer agent (Agent Team grows 3 → 4). Invoked at every FULL/SLIM checkpoint and at pipeline completion; scores user-AI collaboration pattern against the canonical rubric. **Advisory only — never blocks progression.** Frontmatter declares `blocking: false`, `measures: collaboration_depth`, `rubric_ref: shared/collaboration_depth_rubric.md`. - `scripts/check_collaboration_depth_rubric.py` + `scripts/test_check_collaboration_depth_rubric.py` — new lint enforces: (1) rubric file exists; (2) rubric cites Wang & Zhang 2026 with DOI; (3) `rubric_version` frontmatter field; (4) four canonical dimension headings; (5)/(6) any agent claiming `measures: collaboration_depth` references the canonical rubric path and declares `blocking: false`; (7)/(8) orchestrator and SKILL.md mention observer with non-blocking semantics. 10 unit tests, all green. - `academic-pipeline/references/changelog.md` row v2.8. - `academic-pipeline/references/reinforcement_content.md` row for FULL/SLIM checkpoint — IRON RULE: observer is advisory only, never blocks, never a leaderboard. ### Changed - `academic-pipeline/SKILL.md` — version bump `3.3.0 → 3.4.0`. Agent Team table grows to 4 rows. New "Collaboration Depth Observer" section with explicit non-blocking guarantees and distinction from integrity verification and Stage 6 self-reflection. Reference Files table adds rubric entry. - `academic-pipeline/agents/pipeline_orchestrator_agent.md` — checkpoint Steps flow amended: after `state_tracker` update the orchestrator invokes `collaboration_depth_agent` on the just-completed stage's dialogue range (FULL/SLIM only; MANDATORY integrity gates explicitly skip) and injects its output into checkpoint templates as a named "Collaboration Depth" section. FULL checkpoint template expanded with the observer block; SLIM template gains a one-line compact observer summary; MANDATORY template unchanged (integrity gates never dilute). New "Collaboration Depth Observer" subsection under §3 Checkpoint Management covers invocation, cross-model behaviour, short-stage guard, and non-blocking IRON RULE. - `academic-pipeline/agents/state_tracker_agent.md` — Write Access Control adds `collaboration_depth_agent` (append-only `collaboration_depth_history[]`). New `dialogue_log_ref` turn-range pointer per stage; new `collaboration_depth_history[]` root-level array; new `append_observer_report()` function (only function that writes the history; preconditions block any attempt to turn observer output into a blocking condition). - `scripts/_skill_lint.py` — new shared `split_frontmatter(text) -> (dict|None, str)` lenient helper, reused by the new lint. - Suite version bumped to `3.5.0` across `README.md`, `README.zh-TW.md`, `MODE_REGISTRY.md`, `.claude/CLAUDE.md`; new `### v3.5.0 (2026-04-21)` section in both READMEs; new `## v3.5 Key Additions` block in `.claude/CLAUDE.md`. - `scripts/check_spec_consistency.py` — README version expectations bumped to `v3.5.0`; `MODE_REGISTRY.md` last-updated expectation updated; `.claude/CLAUDE.md` suite version expectation updated. New embedded-changelog regression checks for `### v3.5.0 (2026-04-21)` entries. ### Notes - MANDATORY integrity checkpoints (Stages 2.5, 4.5) are **not** instrumented by the observer. The observer never appears in the "Flagged" line of any checkpoint. `blocked_by: collaboration_depth_agent` is never a legal state. The orchestrator's numbered Step 3 explicitly branches on checkpoint_type. - Cross-model behaviour (`ARS_CROSS_MODEL`): observer runs on both models; dimension disagreement > 2 points is flagged explicitly, never silently averaged. `ARS_CROSS_MODEL_SAMPLE_INTERVAL` escape hatch documented. - Short-stage guard: if the completed stage has fewer than 5 user turns, a static `insufficient_evidence` block is injected and the full-model observer call is skipped. - Credit: Wang & Zhang (2026) introduced the dual-pathway SEM and three-zone (Zone 1 / Zone 2 / Zone 3) framework that anchors the rubric's dimension operationalisation and synthesis rule. ## [3.4.0] - 2026-04-20 ### Added - `shared/agents/compliance_agent.md` — single mode-aware agent for PRISMA-trAIce + RAISE compliance. Dispatches on `compliance_mode ∈ {systematic_review, primary_research, other_evidence_synthesis}`. See design spec `docs/design/2026-04-20-v3.4-prisma-trAIce-raise-readcheck-design.md`. - `shared/prisma_trAIce_protocol.md` — verbatim 17-item snapshot from `cqh4046/PRISMA-trAIce` (2025-12-10) + per-item ARS check procedure + 4-tier behaviour table. Citation: Holst et al. 2025, JMIR AI, doi:10.2196/80247. - `shared/raise_framework.md` — 4 principles (human oversight / transparency / reproducibility / fit-for-purpose) + 8-role matrix + mandatory scope disclaimer. Citation: Thomas et al. 2025, NIHR ESG Best Practice Working Group, 17 July 2025. - `shared/compliance_checkpoint_protocol.md` — Stage 2.5 / 4.5 dual-gate behaviour spec, decision precedence, override ladder, fail-loop integration, boundary behaviour for non-pipeline invocation. - `shared/compliance_report.schema.json` — Schema 12 validator (Draft 2020-12). - `examples/compliance/fixture_sr_full_compliant.yaml`, `fixture_sr_missing_M4.yaml`, `fixture_primary_raise_weak.yaml` — regression fixtures + user reference templates. - `scripts/check_compliance_report.py` + tests — Schema 12 CLI validator. - `scripts/validate_compliance_fixtures.py` + tests — YAML→JSON fixture loop used by CI. - `scripts/check_prisma_trAIce_freshness.py` + tests — non-blocking upstream-drift warning (180-day threshold). - `.github/workflows/freshness-check.yml` — weekly cron (Monday 09:00 UTC) + path-filtered push trigger for freshness check. - `docs/PERFORMANCE.md` + `.zh-TW.md`: new "Long-running session management" section + v3.4.0 token-cost deltas. ### Changed - `shared/handoff_schemas.md`: Schema 12 pointer + Material Passport `compliance_history[]` (append-only audit trail). - `academic-pipeline/SKILL.md` (v3.2.2 → v3.3.0): Stage 2.5 / 4.5 extended with compliance payload; checkpoint dashboard gains compliance row. - `deep-research/SKILL.md` (v2.8.1 → v2.9.0): `systematic-review` mode now triggers `compliance_agent` at both gates. - `academic-paper/SKILL.md` (v3.0.2 → v3.1.0): `full` mode adds pre-finalize RAISE principles-only check (warn-only). `disclosure` mode unchanged and complementary. - `.github/workflows/spec-consistency.yml`: added compliance validator + unit test runner steps. - `scripts/check_spec_consistency.py`: version pins bumped. - `README.md`, `README.zh-TW.md`, `.claude/CLAUDE.md`, `MODE_REGISTRY.md`: suite version → 3.4.0. ### Notes - Calibration philosophy: compliance_agent ships with transparent reporting, **no hard FNR/FPR threshold**. This is self-consistent with ARS's v3.3.2 `task_type: open-ended` truth-in-advertising annotation — publishing a hard gate would contradict the "not a benchmark task" declaration. - Compliance Mandatory failures in SR mode are blocking, but the 3-round override ladder preserves human-in-the-loop authority. Overrides auto-inject `disclosure_addendum` into the final manuscript — no detection evasion. - The v3.2 Failure Mode Checklist and the v3.4.0 compliance agent run in parallel at the same gates. Their scopes are non-overlapping: failure-mode checks research validity; compliance checks reporting transparency. - Internal numbering: compliance_report is Schema 12 (not 10). Schema 10 is Style Profile (v2.7+); Schema 11 is R&R Traceability Matrix. The plan's initial Schema 10 assignment was corrected mid-branch before Task 9. ## [3.3.6] - 2026-04-15 ### Added - `docs/ARCHITECTURE.md` — single source of truth for pipeline structure (flow, stage × dimension matrix, data-access flow, skill dependency graph, quality gates, modes). Merged into main via PR #18. - `docs/SETUP.md` + `docs/SETUP.zh-TW.md` — prerequisites, API keys, Pandoc / tectonic setup, cross-model verification (`ARS_CROSS_MODEL`), and four installation methods. - `docs/PERFORMANCE.md` + `docs/PERFORMANCE.zh-TW.md` — per-mode token budgets, full-pipeline cost estimate, and recommended Claude Code settings (Agent Team, Ralph Loop, Skip Permissions). ### Changed - `README.md` and `README.zh-TW.md` streamlined: removed the ASCII pipeline diagram and the 16-point key-feature list (superseded by `docs/ARCHITECTURE.md`). Setup, performance, and installation sections relocated to `docs/`. Skill Details now anchors version numbers and routes readers to ARCHITECTURE.md §3 for per-agent rosters. - `scripts/check_spec_consistency.py` — bumped README version expectations to `v3.3.6`; DOCX contract expectations (both EN and zh-TW) moved from READMEs to the new `docs/SETUP.*` docs; added `check_setup_docs()` step. - Suite version bumped to `3.3.6` across `README.md`, `README.zh-TW.md`, `.claude/CLAUDE.md`, and `MODE_REGISTRY.md`. ### Notes - No functional change to any skill. Pure documentation reorganization. ## [3.3.5] - 2026-04-15 ### Added - `shared/benchmark_report.schema.json` — JSON Schema (draft-2020-12) defining required fields for ARS benchmark reports. Catches the "n=2 author-conducted baseline" failure mode from Anthropic's automated-w2s-researcher paper. - `shared/benchmark_report_pattern.md` — narrative hub doc explaining the schema. - `scripts/check_benchmark_report.py` + tests — validator with self-scored and small-sample warnings. - `examples/benchmark_report_template.json` — fillable template. - `repro_lock` optional sub-block added to Material Passport (Schema 9 in `shared/handoff_schemas.md`). Configuration lockfile; NOT a deterministic replay guarantee. - `shared/artifact_reproducibility_pattern.md` — hub doc with mandatory "not a replay guarantee" disclaimer section and required `stochasticity_declaration` field. - `scripts/check_repro_lock.py` + tests — passport validator. - `examples/passport_with_repro_lock.yaml` — example. - `requirements-dev.txt` — formal Python dev dep manifest (pyyaml + jsonschema). ### Changed - `.github/workflows/spec-consistency.yml` installs via `pip install -r requirements-dev.txt` instead of ad-hoc `pip install`. - `academic-pipeline/references/reproducibility_audit.md` cross-links to new artifact-reproducibility pattern. ## [3.3.4] - 2026-04-15 ### Fixed - Embedded changelog sections in `README.md` and `README.zh-TW.md` now include the missing `v3.3.3` and `v3.3.2` summaries, so the README history matches the published releases. - `scripts/check_spec_consistency.py` now verifies that the README changelog summaries include the latest release entries, so future drift fails CI. ### Changed - Suite version bumped to `3.3.4` across release-facing docs after the README changelog sync patch release. ## [3.3.3] - 2026-04-15 ### Fixed - `scripts/_skill_lint.py` now rejects SKILL frontmatter that is missing a closing `---` fence instead of silently treating the rest of the file as YAML. - `scripts/_skill_lint.py` now reports a readable error when frontmatter parses as valid YAML but not as a mapping object, instead of crashing with `AttributeError`. - Broken showcase link for the post-publication audit report corrected in both `README.md` and `README.zh-TW.md`. - `scripts/check_spec_consistency.py` now validates README relative Markdown links so future dead links fail CI. ### Changed - DOCX generation contract aligned across README, `academic-paper/SKILL.md`, `academic-paper/agents/formatter_agent.md`, `academic-pipeline/SKILL.md`, and `academic-pipeline/agents/pipeline_orchestrator_agent.md`: direct `.docx` output is Pandoc-dependent, with Markdown + conversion instructions as the fallback. - Added regression tests covering missing closing fences and non-mapping YAML frontmatter in both lint test suites. - Suite version bumped to `3.3.3` across release-facing docs; `academic-paper` patch-bumped to `3.0.2` and `academic-pipeline` patch-bumped to `3.2.2`. ## [3.3.2] - 2026-04-15 ### Added - `metadata.data_access_level` field on every top-level SKILL.md. Three-tier vocabulary (`raw` | `redacted` | `verified_only`) declaring what kind of data each skill may consume. Inspired by the three-tier isolation pattern in Anthropic's automated-w2s-researcher (2026). - `deep-research` = `raw` - `academic-paper` = `redacted` - `academic-paper-reviewer` = `verified_only` - `academic-pipeline` = `verified_only` - `scripts/check_data_access_level.py` lint script with unit tests; wired into `.github/workflows/spec-consistency.yml`. - Pointer section in `shared/handoff_schemas.md` documenting the vocabulary for future skill authors. - `metadata.task_type` field on every top-level SKILL.md. Two-value vocabulary (`open-ended` | `outcome-gradable`) declaring whether the task has a scalar ground-truth metric. All current ARS skills are `open-ended` — the field is a truth-in-advertising signal that ARS targets domain-judgment work, not benchmark tasks. - `scripts/check_task_type.py` lint script with 4 unit tests; wired into the same CI workflow. - Pointer section in `shared/handoff_schemas.md` for the `task_type` vocabulary. - `shared/ground_truth_isolation_pattern.md` — narrative pattern doc explaining the three-layer model behind `data_access_level` and `task_type`. Cross-references existing protocols (S2 verification, anti-leakage, integrity gates, calibration mode). Linked from `handoff_schemas.md` and `CONTRIBUTING.md`. ### Changed - Per-skill `metadata.version` patch-bumped on all 4 SKILL.md files; `last_updated` refreshed to 2026-04-15. - Suite version bumped to 3.3.2 across `README.md`, `README.zh-TW.md`, and `.claude/CLAUDE.md`. ## [3.3.1] - 2026-04-14 ### Fixed - Public contract drift across `README.md`, `README.zh-TW.md`, `.claude/CLAUDE.md`, `MODE_REGISTRY.md`, and the affected `SKILL.md` files - Cross-model wording now matches the implemented scope: integrity sample verification and independent DA critique are shipped; sixth-reviewer peer review remains planned - `academic-pipeline` checkpoint docs now state that SLIM checkpoints still wait for explicit user confirmation - `academic-pipeline` integrity gate docs now consistently state that Stage 2.5 and Stage 4.5 cannot be skipped - `academic-paper/SKILL.md` mode-count heading and `academic-paper-reviewer/SKILL.md` Version Info block ### Added - `scripts/check_spec_consistency.py` to catch mode-count, version-block, and forbidden-claim drift - `.github/workflows/spec-consistency.yml` to run the consistency check on pushes and pull requests ## [3.3] - 2026-04-09 ### Added — PaperOrchestra-inspired enhancements Integrates techniques from Song et al. (2026, *arXiv:2604.05018*) "PaperOrchestra: A Multi-Agent Framework for Automated AI Research Paper Writing." - **Semantic Scholar API Verification** (deep-research, academic-pipeline): Tier 0 programmatic reference verification via S2 API. Title search with Levenshtein >= 0.70 matching. DOI mismatch detection for Compound Deception Pattern #5. Bibliography deduplication via S2 IDs. Graceful degradation if API unavailable. - New file: `deep-research/references/semantic_scholar_api_protocol.md` - Modified: `source_verification_agent`, `bibliography_agent`, `integrity_verification_agent` - **Anti-Leakage Protocol** (academic-paper, deep-research): Knowledge Isolation Directive prioritizes session materials over LLM parametric memory for factual content. Flags `[MATERIAL GAP]` for missing content instead of silently filling from memory. Reduces Mode 5/6 failure risk. - New file: `academic-paper/references/anti_leakage_protocol.md` - Modified: `draft_writer_agent`, `report_compiler_agent` - **VLM Figure Verification** (academic-paper): Optional closed-loop verification of rendered figures using vision-capable LLM. 10-point checklist covering data accuracy, APA 7.0 compliance, and visual quality. Max 2 refinement iterations. - New file: `academic-paper/references/vlm_figure_verification.md` - Modified: `visualization_agent` - **Score Trajectory Protocol** (academic-pipeline): Per-dimension rubric score delta tracking across revision rounds. Detects regressions (delta < -3) and triggers mandatory checkpoint. Extends v3.2 early-stopping with dimension-level granularity. - New file: `academic-pipeline/references/score_trajectory_protocol.md` - Modified: `integrity_review_protocol.md`, `handoff_schemas.md` (Schema 5) - **Stage 2 Parallelization Directive** (academic-pipeline): Visualization and argument building can run in parallel after outline completion. - **Handoff Schema Updates** (shared): `semantic_scholar_id` field added to Bibliography source object. `score_trajectory` structure added to Integrity Report schema. **Version bumps**: deep-research v2.8, academic-paper v3.0, academic-pipeline v3.2 ## [3.2] - 2026-04-09 ### Added — Lu 2026 integration Integrates insights from Lu et al. (2026, *Nature* 651:914-919) — the first end-to-end autonomous AI research system to pass blind peer review. - **AI Research Failure Mode Checklist** (academic-pipeline): 7-mode taxonomy extending the existing 5-type citation hallucination taxonomy. Covers implementation-bug blindness, hallucinated experimental results, shortcut reliance, bug-as-insight, methodology fabrication, and pipeline-level frame-lock. Runs at Stage 2.5 and 4.5 with mandatory blocking behaviour. Reported at Stage 6 in the Failure Mode Audit Log subsection of the AI Self-Reflection Report. - New file: `academic-pipeline/references/ai_research_failure_modes.md` - **Reviewer Calibration Mode** (academic-paper-reviewer v1.8): opt-in mode that measures FNR / FPR / balanced accuracy / AUC against a user-supplied gold-standard set of 5-20 papers. Uses 5x ensembling with fresh context per run. Cross-model verification default-on. Session-scoped confidence disclosure. - New file: `academic-paper-reviewer/references/calibration_mode_protocol.md` - **Disclosure Mode** (academic-paper v2.9): venue-specific AI-usage disclosure statement generator. v1 database covers ICLR, NeurIPS, Nature, Science, ACL, EMNLP. Unknown venues halt and prompt user to paste policy. - New files: `academic-paper/references/disclosure_mode_protocol.md`, `academic-paper/references/venue_disclosure_policies.md` - **Fidelity-Originality Mode Spectrum** (all skills): classifies all modes on a fidelity–originality axis per Lu 2026 Fig 1c. Quick Mode Selection Guides updated with Spectrum column. - New file: `shared/mode_spectrum.md` - **Early-Stopping Criterion** (academic-pipeline v3.1): convergence check (delta < 3 points + no P0) suggests stopping revision loop. Budget transparency estimate at pipeline start. - **README Positioning Update**: "Why human-in-the-loop, not full automation?" section citing Lu 2026 as external evidence for ARS's design thesis. Both EN and zh-TW updated. ### Changed - `.claude/CLAUDE.md`: synced all skill versions and mode lists to reality (deep-research v2.7, academic-paper v2.9, academic-paper-reviewer v1.8, academic-pipeline v3.1) - `quality_rubrics.md`: added "Known error profile" preamble explaining rubric scores are ordinally but not cardinally interpretable without calibration **Version bumps**: academic-paper v2.9, academic-paper-reviewer v1.8, academic-pipeline v3.1 ## [3.1.1] - 2026-04-09 ### Added - **Information Systems — Senior Scholars' Basket of 11** (extending the *Basket of 8* added in v2.9): *Decision Support Systems*, *Information & Management*, *Information and Organization* — completing the AIS College of Senior Scholars' official list of premier IS journals - Section heading updated from "Information Systems (Basket of 8)" to "Information Systems (Senior Scholars' Basket of 11)" in `academic-paper-reviewer/references/top_journals_by_field.md` - Original IS Basket of 8 proposed and drafted by [@mchesbro1](https://github.com/mchesbro1) — [Issue #5](https://github.com/Imbad0202/academic-research-skills/issues/5). Extended to Basket of 11 by [@cloudenochcsis](https://github.com/cloudenochcsis) — [Issue #7](https://github.com/Imbad0202/academic-research-skills/issues/7), [PR #8](https://github.com/Imbad0202/academic-research-skills/pull/8). Source: [AIS Senior Scholars' List of Premier Journals](https://aisnet.org/research/seniorscholarsbasket/) ## [2.9.1] - 2026-04-03 ### Added - `status` and `related_skills` metadata to all 4 SKILL.md frontmatters - Enables skill discovery tools and cross-skill navigation for users with multiple skills installed - `deep-research` ↔ `academic-paper` ↔ `academic-paper-reviewer` ↔ `academic-pipeline` ## [2.9] - 2026-03-27 ### Added - **Style Calibration** — learn the author's writing voice from past papers (optional, intake Step 10) - **Writing Quality Check** — checklist catching overused AI-typical patterns (renamed from AI Writing Lint) - Information Systems Basket of 8 journals added to academic-paper reference list - Copilot philosophy tagline to README EN + zh-TW - Substack guide articles to both READMEs ### Fixed - Skill Details section version numbers and agent descriptions updated - /simplify review — stale refs, lint sweep efficiency, schema fields - Removed last v4.0 reference in CHANGELOG ## [2.8] - 2026-03-22 ### Added - **SCR Loop Phase 1** — State-Challenge-Reflect mechanism integrated into Socratic Mentor Agent - Commitment gates at layer/chapter transitions (collect user predictions before presenting evidence) - Certainty-triggered contradiction (probes high-confidence statements with counterpoints) - Adaptive intensity (tracks commitment accuracy, adjusts challenge frequency) - Self-calibration signal (S5) for convergence detection - SCR Switch — users can disable/re-enable predictions mid-dialogue - `deep-research/agents/socratic_mentor_agent.md` — SCR Protocol section with commitment gates, divergence reveal, and adaptive intensity - `deep-research/references/socratic_questioning_framework.md` — SCR Overlay Protocol mapping SCR phases to Socratic functions - `academic-paper/agents/socratic_mentor_agent.md` — Chapter-level SCR Protocol with per-chapter commitment questions and cross-chapter pattern tracking ## [2.7.3] - 2026-03-10 ### Fixed - Version badge corrected in both EN and zh-TW READMEs ## [2.7.2] - 2026-03-10 ### Added - Version, license, and sponsor badges to README - zh-TW README badges ## [2.7.1] - 2026-03-10 ### Fixed - Buy Me a Coffee username corrected ## [2.7] - 2026-03-09 ### Added - Integrity Verification v2.0: Anti-Hallucination Overhaul - Full academic research skills suite (4 skills, 116 files) - Deep Research v2.3 — 13-agent research team with 7 modes - Academic Paper v2.4 — 12-agent paper writing with LaTeX hardening - Academic Paper Reviewer v1.4 — Multi-perspective peer review with quality rubrics - Academic Pipeline v2.6 — 10-stage orchestrator with integrity verification