Files
Edward Cheng-I Wu 305884b252 feat(reviewer): #610 formal AR<n> arithmetic-receipt grammar for the methodology seat (#644)
* feat(reviewer): #610 formal AR<n> arithmetic-receipt grammar for the methodology seat

Adds the mandatory ## Arithmetic Receipts section to the methodology
sprint Phase 2 card: one AR<n> receipt per attempted recomputation under
the four bounded procedures, or a no_recomputable_statistics attestation.
Grammar forces the two adjudicated baseline drop points (unstated-tail
both-values display for p_from_test_statistic; rounding_interval +
nearest_achievable for GRIM/GRIMMER) plus df_identity naming, closed
enums, and bidirectional mismatch<->W<n> linkage.

- canonical source: new methodology-receipt fragment, spliced into
  scoring-phase2 by the sync checker (shared text single-sourced),
  content lock re-pinned; slot table row 'scoring + receipts'
- methodology_reviewer_agent.md: composed Phase 2 mirror + Step 4a item 8
- check_phase_conformance.py: methodology-only RECEIPT-* gate,
  RECEIPT-SECTION-FORBIDDEN on other seats (58 new mutation tests)
- statistical_reporting_standards.md §8: four procedure summaries,
  three new arithmetic red-flag rows, covered/reporting-only/
  direct-threshold/not_computable classification
- sprint_contract_protocol.md §5.1 + receipt diagnostics rows
- epistemic-status declaration leads every new surface: receipts prove
  auditability, never arithmetic truth; human adjudication decides

Phase 1, other seats, DA/synthesis grammars, fixtures, and
dispatch_e4_panel.py are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016oZptTA3LpqeXWSxynzSz9

* docs(reviewer): #610 receipt attestation honest-claim wording (round-1 partial)

Adjudication of codex round-1 P1-1: the attestation is declaration-only;
the checker cannot verify manuscript applicability (extraction surface is
explicitly out of #610 scope, spec §2). Prompt now states the boundary;
canonical + mirror edited together, content lock re-pinned. Remaining
round-1 fixes tracked in the PR checklist comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016oZptTA3LpqeXWSxynzSz9

* fix(reviewer): #610 receipt gate round-1 adjudicated fixes (fence-transparent read, decoration-agnostic aborts, enum defrift lint)

Implements the 10-item adjudicated fix list from PR #644 dual-track round 1:

1. Both-tails rule requires VALUES: each tail label needs a digit in its own
   ;-segment of derived_value_or_range, NFKC-folded, hyphen variants folded.
2. Attestation honesty: checker prints the declaration-only
   [RECEIPT-ATTESTATION] advisory; CHANGELOG / protocol 5.1 / statistical 8
   aligned to mandatory-declaration-adjudicated-honesty wording.
3. Fence-aware receipt parsing via _lines_with_fence_state: fenced receipt
   content is READ (no #637-family false abort) and a fenced AR block beside
   an unfenced attestation is SEEN and aborts; both directions pinned.
4. Decoration-agnostic field detection (_is_dissent_field_shaped
   construction) for receipt fields and the Arithmetic Receipt
   back-reference; decorated machine lines abort loudly.
5. Back-reference value must be exactly AR<n> to EOL/next pipe.
6. ## Arithmetic Receipts must be the card's final H2 (all three surfaces
   agree on "final section").
7. RECEIPT_SPLICE_ANCHOR bytes join canonical_digest; lock re-pinned.
8. New check_receipt_enum_sync.py pins the four closed enums across
   fragment / checker frozensets / spec 4 (CI + manifest wired, 91 entries).
9. GRIMMER direct tests (spec 5.3 worked case).
10. P3 batch: conditional-field loop driven from _PROCEDURE_FIELDS,
    CHANGELOG counts corrected (91 conformance / 7 sync mutation tests),
    8.5 inclusion-criterion sentence, prompt "vanish" overstatement fixed,
    H2 case-sensitivity documented.

Verification: full spec-consistency lint battery green locally; 91-entry
pytest manifest green (498 conformance + 32 sync + 9 enum-lint tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

* fix(reviewer): #610 receipt gate round-2 adjudicated fixes (comment visibility, preamble guard, cell/entity detection)

Implements the adjudicated round-2 dual-track findings (codex 3P1+5P2,
security 2P1+4P2+3P3, converging on the HTML-comment channel):

- Shared _lines_with_hidden_state walker (mirrors _raw_dissent_span's
  comment/paragraph bookkeeping): commented-out machine lines, attestation
  lines, AR headings, and back-references abort; commented prose tolerated.
- Preamble guard: canonical machine lines outside every ### AR<n>
  subsection (other than the attestation) abort instead of parking
  un-inspected past the enum/linkage gates.
- Shape detection runs per GFM cell on non-canonical lines and
  HTML-unescapes entity colons; CJK cell prose pinned tolerated.
- Backref declared-vs-parsed per-cell equality: a malformed declaration
  cannot ride beside a canonical one.
- _lines_with_fence_state yields display form: indented-fence content
  dedented per CommonMark (false abort removed, hiding direction pinned).
- Tail labels gain letter boundaries; a label needs only to share its
  ;-segment with a digit (either order). Fused-label and zh-TW-label
  behaviors pinned as declared boundaries. Segment scan now single-pass.
- Receipt field bold is balanced-or-absent (half-bold aborts, both ways).
- Terminal rule counts unfenced 1-3-space-indented ## lines as headings.
- Enum lint spec parse scoped to §4 with pre-§4 decoy mutation test.
- Fragment/mirror/§5.1 name the tolerated decorations and the abort
  surface, warn against field-name-leading prose; lock re-pinned.
- Declared-boundary comment on check_receipt_section_forbidden.

Conformance suite 407 → 522 collected; enum lint 11; 91-entry manifest and
spec-consistency battery green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

* fix(reviewer): #610 receipt gate round-3 adjudicated fixes (comment-free zone, inline spans, display-fold order)

Implements the adjudicated round-3 dual-track findings (codex 2P1+3P2+1P3,
security 0P1+2P2+1P3 with a 9/9 round-2 closure sweep; three items shared):

- The receipt section is a comment-free zone: any unfenced <!--/--> aborts,
  closing the paragraph-inline `prose <!--` laundering channel the block
  visibility model deliberately cannot read. Supersedes the round-2
  commented-prose tolerance (machine section, no legitimate comment use);
  fenced occurrences stay literal and exempt. Fragment/mirror instruct it.
- The Review Body backref walk tracks paragraph-inline comment spans with
  an ordered token scan; a back-reference inside a live span aborts and is
  never credited. Spans die at paragraph boundaries (blank line, heading),
  pinned both directions.
- _display_fold: HTML-unescape BEFORE the NFKC fold, so fullwidth-colon
  entities (&#xFF1A;) decode+fold to `:` for fields and backrefs.
- _cell_split_form: link destinations, inline code spans, and escaped
  pipes are blanked before GFM cell splitting — a pipe-bearing URL in
  receipt-section prose no longer false-aborts; the later-cell attack
  still does.
- code_indented state (paragraph-aware) in _lines_with_hidden_state: an
  indented-code back-reference aborts instead of earning linkage credit; a
  4-space paragraph-continuation backref stays live.
- Tail-rule wording unified to the enforced either-order segment rule
  (§5.1 + [RECEIPT-TAILS] diagnostic); shape-scan abort message
  neutralized (no false "commented-out" claim on partially-rendering
  opener lines).

Conformance suite 522 → 533 collected; fragment lock re-pinned; 91-entry
manifest and focused lints green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

* fix(reviewer): #610 receipt gate round-4 adjudicated fixes (display-form parity, CommonMark code spans, span edges)

Implements the adjudicated round-4 dual-track findings (codex 2P1+2P2,
security 1P1+1P2, four distinct items over two root causes; both tracks
6/6 CLOSED on round-3):

- Display-form parity (round-3 regression): canonical backref parsing and
  the declaration count run on the same _cell_split_form display line, so
  a code-span declaration can never be credited over the rendered field
  beside it.
- _blank_code_spans: equal-run CommonMark code-span scanner replaces the
  unequal-delimiter regex, restoring the later-cell attack abort and the
  legitimate equal-tick tolerance.
- _strip_inline_comment_spans yields the rendered remainder per line:
  content behind an opener is stripped (opener-line pipe trick dies),
  content after a closing --> is parsed live (rendered backref credited,
  spurious one aborts).
- Indented-code lines no longer open a paragraph, so line 2 of a code
  block is still code; code spans are blanked before comment-token
  scanning; any ATX heading level ends an inline span.

Conformance suite 533 → 543 collected; canonical lock unchanged; 91-entry
manifest and focused lints green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

* fix(reviewer): #610 receipt gate round-5 fix (hidden declaration beside visible content aborts)

Closes the last open dual-track finding (codex round-5 P2; security track
already MERGE-READY at 8119c7f with 0 P1/P2):
_strip_inline_comment_spans now returns the hidden text alongside the
rendered remainder, and a back-reference declaration inside the hidden
span aborts even when the line also carries visible content — silently
dropping it re-opened the hidden-declaration channel one
partial-visibility case at a time. Hidden prose beside a visible
declaration stays harmless; the round-4 opener-line pin now aborts at the
hiding site (paragraph-inline diagnostic) instead of downstream.

Conformance suite 543 → 545 collected; canonical lock unchanged; 91-entry
manifest green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

* fix(reviewer): #610 receipt gate round-6 fix (per-span hidden-segment checks)

Closes the single codex round-6 P2: hidden inline-comment segments are
kept one entry per span instead of joined, so a harmless prose span can
no longer prefix-shield a declaration hidden in the next span, and
fragments across spans can no longer synthesize a phantom declaration
into a false abort. Both directions pinned.

Conformance suite 545 → 547 collected; canonical lock unchanged; 91-entry
manifest green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QRa5VKhLqmcdYPGZiXVm

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 22:01:49 +08:00

177 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Mutation tests for the #610 receipt closed-enum defrift lint."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
CHECKER = REPO_ROOT / "scripts" / "check_receipt_enum_sync.py"
CANONICAL = "academic-paper-reviewer/references/reviewer_sprint_prompt_source.md"
SPEC = "docs/design/2026-08-02-610-statistical-recompute-baseline-spec.md"
def _run(root: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(CHECKER), "--root", str(root)],
capture_output=True,
text=True,
)
@pytest.fixture()
def tree(tmp_path: Path) -> Path:
for rel in (CANONICAL, SPEC):
dst = tmp_path / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(REPO_ROOT / rel, dst)
return tmp_path
def _mutate(root: Path, rel: str, old: str, new: str) -> None:
path = root / rel
text = path.read_text(encoding="utf-8")
assert old in text, f"anchor missing in {rel}: {old!r}"
path.write_text(text.replace(old, new, 1), encoding="utf-8")
def test_clean_tree_passes(tree: Path) -> None:
result = _run(tree)
assert result.returncode == 0, result.stderr
assert "RECEIPT-ENUM-SYNC: PASS" in result.stdout
def test_fragment_procedure_enum_drift_fails(tree: Path) -> None:
_mutate(
tree,
CANONICAL,
"`procedure_id: <p_from_test_statistic|grim|grimmer|n_from_df>`",
"`procedure_id: <p_from_test_statistic|grim|grimmer|n_from_df"
"|effect_size_consistency>`",
)
result = _run(tree)
assert result.returncode == 1
assert "procedure_id: fragment enum drifted" in result.stderr
def test_fragment_status_enum_drift_fails(tree: Path) -> None:
_mutate(
tree,
CANONICAL,
"`status: <consistent|mismatch|not_computable|not_applicable>`",
"`status: <consistent|mismatch|not_computable>`",
)
result = _run(tree)
assert result.returncode == 1
assert "status: fragment enum drifted" in result.stderr
def test_fragment_tail_enum_drift_fails(tree: Path) -> None:
_mutate(
tree,
CANONICAL,
"`tail_convention: <two-tailed|one-tailed|upper-tail|unstated>`",
"`tail_convention: <two-tailed|one-tailed|unstated>`",
)
result = _run(tree)
assert result.returncode == 1
assert "tail_convention: fragment enum drifted" in result.stderr
def test_fragment_reason_enum_drift_fails(tree: Path) -> None:
_mutate(
tree,
CANONICAL,
"`reachability_not_completed`. Every other status",
"`reachability_incomplete`. Every other status",
)
result = _run(tree)
assert result.returncode == 1
assert "not_computable_reason: fragment enum drifted" in result.stderr
def test_spec_procedure_row_drift_fails(tree: Path) -> None:
_mutate(
tree,
SPEC,
"`p_from_test_statistic`, `grim`, `grimmer`, or `n_from_df` |",
"`p_from_test_statistic`, `grim`, or `n_from_df` |",
)
result = _run(tree)
assert result.returncode == 1
assert "procedure_id: spec §4 enum drifted" in result.stderr
def test_spec_reason_bullet_drift_fails(tree: Path) -> None:
_mutate(tree, SPEC, "- `tail_ambiguous`\n", "")
result = _run(tree)
assert result.returncode == 1
assert "not_computable_reason: spec §4 enum drifted" in result.stderr
def test_fragment_missing_enum_spelling_fails(tree: Path) -> None:
_mutate(
tree,
CANONICAL,
"`status: <consistent|mismatch|not_computable|not_applicable>`",
"`status: any of the closed statuses`",
)
result = _run(tree)
assert result.returncode == 1
assert "expected exactly one" in result.stderr
def test_pre_section4_decoy_cannot_mask_spec_drift(tree: Path) -> None:
# #610 round-2: a complete historical example placed BEFORE §4 must not
# satisfy the parse while the real §4 drifts.
decoy = (
"\nHistorical example (non-normative): the closed v1 enum: was\n\n"
"- `missing_reported_value`\n"
"- `test_family_ambiguous`\n"
"- `tail_ambiguous`\n"
"- `nonstandard_p_procedure`\n"
"- `inequality_unresolvable`\n"
"- `rounding_rule_ambiguous`\n"
"- `rounding_boundary_ambiguous`\n"
"- `scale_granularity_unknown`\n"
"- `scale_support_unknown`\n"
"- `analytic_n_ambiguous`\n"
"- `aggregation_or_weighting_unknown`\n"
"- `sd_convention_unknown`\n"
"- `mean_grim_inconsistent`\n"
"- `df_identity_ambiguous`\n"
"- `model_correction_or_pooling`\n"
"- `reachability_not_completed`\n\n"
)
_mutate(tree, SPEC, "- `tail_ambiguous`\n- `nonstandard", "- `nonstandard")
_mutate(
tree, SPEC,
"## 4. Prospective arithmetic receipt contract",
decoy + "## 4. Prospective arithmetic receipt contract",
)
result = _run(tree)
assert result.returncode == 1
assert "not_computable_reason: spec §4 enum drifted" in result.stderr
def test_second_enum_sentence_inside_section4_fails(tree: Path) -> None:
_mutate(
tree, SPEC,
"Required invariants:",
"A second closed v1 enum mention inside §4.\n\nRequired invariants:",
)
result = _run(tree)
assert result.returncode == 1
assert "expected exactly one 'closed v1 enum' sentence" in result.stderr
def test_missing_spec_file_is_invocation_error(tree: Path) -> None:
(tree / SPEC).unlink()
result = _run(tree)
assert result.returncode == 2
assert "required file unreadable" in result.stderr