This file is the single source of truth for firm-rule wording that is otherwise duplicated verbatim across agent prompts. Each block below is the canonical text; the agent prompts listed under "Mirrored in" carry a byte-identical copy (modulo the agent's self-reference noun, where noted). scripts/check_firm_rules_sync.py fails CI if any mirror drifts from the canonical block here.
Why this file exists: the contamination R-L3-2-* wording provably drifted between v3.9.0 (contamination-only) and v3.9.4 (contamination-AND-temporal) drafts before being single-sourced here. Manual 5×-duplication is not enough; the sync lint pins it.
Related mechanisms: the Phase Boundary enforcement-status sentence (factual status prose, not a behavioral firm rule) is single-sourced as CANONICAL_ENFORCEMENT inside scripts/check_v3_9_2_phase_boundary.py (#491) rather than here — same defrift goal, separate mechanism co-located with its only consumer.
ID namespaces (do NOT confuse — they were overloaded until v3.10 PR-A):
R-L3-2-* = contamination advisory rules (origin: v3.7.3 spec §3.2 L3-2; extended v3.9.0 §3.3). Original holder of the R-L3-2 ID.R-CIM-* = Claim Intent Manifest emission rules (origin: #103 claim-alignment spec; the agent prompts borrowed R-L3-2-A/B/C until v3.10 PR-A renamed them to remove the collision).R-L3-1-* = per-citation locator gate (v3.7.3 §3.1). Not mirrored here.Canonical wording note: R-L3-2-A carries the v3.10 PR-B broad form (default-advisory + opt-in strict extension across contamination AND temporal namespaces; temporal strict not yet wired). This block is the single source of truth for the wording; the contamination mirrors below are intentionally by-ID references, not full-block copies (see the "Mirrored in (contamination rules)" note).
strict value, a user-enabled strict terminal policy may promote that namespace's specified signals to non-acknowledgeable terminal blockers. v3.10 accepts a strict value for contamination_triangulation only; temporal_integrity accepts advisory only (no temporal strict path exists yet). This follows the v3.5 Collaboration Depth Observer + v3.6.8 LOW-WARN precedent: hard-gating contamination by default would amount to refusing to cite mid-2024+ preprints en masse, which is too coarse — so the terminal promotion is opt-in (off by default), scoped to the namespace's accepted-strict signals, and surfaced via the §formatter terminal gate, never silently.*_unmatched fields that are present. Absent fields are excluded from the count and do not default to either true or false. k_max reflects how many lookups were successfully run; the (k, k_max) pair together determines the annotation tier.primary_location.source.type and Crossref's type fields, even when returned by the APIs for matched entries, MUST NOT be used to derive any classification (venue_type, scope category, hard-block eligibility). The k=3 case makes those classifications structurally unavailable; including them in any classification logic creates fake precision.severity=HIGH-BLOCK token inside a <!--ref:...--> marker — it is NOT a per-suffix refusal entry, so the advisory suffix table and pass-through allowlist stay unchanged when a strict policy promotes a signal. The formatter is STAMP-CHECK ONLY: it compares each marker's policy_hash against the passport's current terminal_policies (freshness guard) and never re-runs policy logic; the finalizer is the sole policy evaluator.Mirrored in (contamination rules):
academic-paper/agents/formatter_agent.md — R-L3-2-A + R-L3-2-E (in the contamination pass-through paragraph).deep-research/references/crossref_api_protocol.md — R-L3-2-A (user-discretion reference).deep-research/references/openalex_api_protocol.md — R-L3-2-A reference.academic-pipeline/agents/pipeline_orchestrator_agent.md — R-L3-2-C / R-L3-2-D / R-L3-2-E (finalizer logic).deep-research/agents/bibliography_agent.md — R-L3-2-B (ingest-time computation).These mirrors are intentionally by-ID prose references, not full-block copies (e.g. crossref's "the user retains discretion per R-L3-2-A", the formatter's "advisory per ... R-L3-2-A + R-L3-2-E"). The wording lives in exactly one place — the canonical block above — so the single-source goal (D3) is met without duplicating the full rule text into five files. The v3.10 PR-B reword therefore changes ONLY the canonical block, not the mirrors. Because the mirrors are by-ID references, the sync lint does NOT wording-check them; it (1) ID-guards the contamination side (no contamination context reuses an
R-CIM-*ID, no claim-manifest surface reuses anR-L3-2-*ID), and (2) contradiction-guards the contamination mirrors against phrasing that would contradict the broad reword — a by-ID reference's surrounding prose MUST NOT assert an unqualified "advisory only" / "never block" / "cannot block" / "must not block" / "non-blocking" claim, since a strict terminal policy can now block (seecheck_firm_rules_sync.pycontradiction guard). (Wording-sync IS enforced for theR-CIM-*blocks below, whose mirrors ARE full-block copies.)
Renamed from
R-L3-2-A/B/Cin v3.10 PR-A to remove the ID collision with the contamination rules above. The three writing-stage agents emit a claim intent manifest; the only difference between mirrors is the agent's self-reference noun ("synthesis agent" / "compiler" / "writer"), which the sync lint normalizes before comparison.
claim_drifts[] entry with drift_kind=EMITTED_NOT_INTENDED downstream — that detection is the design intent (drift is surfaced, not silenced). The manifest is the pre-commitment artifact the audit diffs against; rewriting it mid-draft would hide the signal.claim_ref_alignment_audit_agent.md. Mirrors the v3.6.7 partial-inversion discipline: narrative-side emits, audit-side reads.claim_text, intended_evidence_kind, planned_refs, and any negative_constraints[].rule values from the corpus + prompt context already provided. You MUST NOT read entry frontmatter to discover candidate claims — the same partial-inversion rule that gates anchor selection in v3.7.3 R-L3-1-C. The orchestrator allocates a fresh manifest_id per invocation (M-INV-4); never copy a manifest_id from a sibling manifest.Mirrored in (claim-manifest rules):
deep-research/agents/synthesis_agent.md (<AGENT> = "synthesis agent")deep-research/agents/report_compiler_agent.md (<AGENT> = "compiler"; R-CIM-B carries an extra standalone-mode sentence — see prompt)academic-paper/agents/draft_writer_agent.md (<AGENT> = "writer")shared/contracts/passport/claim_intent_manifest.schema.json — references R-CIM-A by ID in the claims field description.scripts/check_firm_rules_sync.py additionally asserts:
R-L3-2-A/B/C token appears inside any ## Claim Intent Manifest Emission prompt section or inside claim_intent_manifest.schema.json.R-CIM-A/B/C token appears inside any contamination context (formatter pass-through paragraph, crossref/openalex protocol, finalizer logic, contamination specs).This pins the v3.10 PR-A disambiguation against regression.