mirror of
https://github.com/Imbad0202/academic-research-skills.git
synced 2026-09-14 13:51:17 +08:00
305884b252
* 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 (:) 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>
177 lines
5.6 KiB
Python
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
|