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

204 lines
6.8 KiB
Python

#!/usr/bin/env python3
"""Defrift lint for the #610 arithmetic-receipt closed enums.
The receipt grammar spells its four closed enums in three places: the
`methodology-receipt` canonical prompt fragment (what the reviewer is told),
the conformance checker's frozensets (what is enforced), and the #610 spec §4
(what was decided). This lint parses the backtick enum lists out of the
fragment and the spec and asserts set equality against the checker constants,
so a value added or renamed on one surface fails CI until every surface moves
in the same commit. The spec does not define a `tail_convention` enum, so that
comparison is fragment-vs-checker only.
Usage: python scripts/check_receipt_enum_sync.py [--root REPO_ROOT]
Exit 0 in sync, 1 drift, 2 missing file or unparseable surface.
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import check_phase_conformance as phase # noqa: E402
import check_reviewer_sprint_prompt_sync as sync # noqa: E402
CANONICAL_REL = sync.CANONICAL_REL
SPEC_REL = "docs/design/2026-08-02-610-statistical-recompute-baseline-spec.md"
_BACKTICK_NAME_RE = re.compile(r"`([a-z0-9_]+)`")
_ANGLE_ENUM_RES = {
"procedure_id": re.compile(r"`procedure_id: <([^>]+)>`"),
"status": re.compile(r"`status: <([^>]+)>`"),
"tail_convention": re.compile(r"`tail_convention: <([^>]+)>`"),
}
_FRAGMENT_REASONS_RE = re.compile(
r"closed v1 enum: (?P<names>.*?)\. Every other status"
)
CHECKER_ENUMS = {
"procedure_id": frozenset(phase._RECEIPT_PROCEDURES),
"status": frozenset(phase._RECEIPT_STATUSES),
"not_computable_reason": frozenset(phase._NOT_COMPUTABLE_REASONS),
"tail_convention": frozenset(phase._TAIL_CONVENTIONS),
}
def _read(root: Path, rel: str) -> str:
path = root / rel
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
print(f"ERROR: required file unreadable: {rel}: {exc}",
file=sys.stderr)
raise SystemExit(2)
def _fragment_enums(fragment: str, errors: list[str]) -> dict[str, set[str]]:
enums: dict[str, set[str]] = {}
for name, pattern in _ANGLE_ENUM_RES.items():
matches = pattern.findall(fragment)
if len(matches) != 1:
errors.append(
f"fragment: expected exactly one `{name}: <...>` enum "
f"spelling, found {len(matches)}"
)
continue
enums[name] = {value.strip() for value in matches[0].split("|")}
reasons = _FRAGMENT_REASONS_RE.search(fragment)
if reasons is None:
errors.append(
"fragment: the closed v1 not_computable_reason sentence "
"('closed v1 enum: ... . Every other status') was not found"
)
else:
enums["not_computable_reason"] = set(
_BACKTICK_NAME_RE.findall(reasons.group("names"))
)
return enums
def _spec_section4_lines(
spec_lines: list[str], errors: list[str]
) -> list[str]:
"""The lines of spec §4 only, so a worked example or historical block
elsewhere in the document can never satisfy — or mask drift in — the
normative enum definitions (#610 round-2)."""
starts = [
index for index, line in enumerate(spec_lines)
if line.startswith("## 4. ")
]
if len(starts) != 1:
errors.append(
f"spec: expected exactly one '## 4. ' heading, found "
f"{len(starts)}"
)
return []
end = next(
(
index for index in range(starts[0] + 1, len(spec_lines))
if spec_lines[index].startswith("## ")
),
len(spec_lines),
)
return spec_lines[starts[0]:end]
def _spec_table_cell_enum(
spec_lines: list[str], field: str, errors: list[str]
) -> set[str] | None:
rows = [
line for line in spec_lines
if line.startswith(f"| `{field}` |")
]
if len(rows) != 1:
errors.append(
f"spec §4: expected exactly one `| `{field}` |` table row, "
f"found {len(rows)}"
)
return None
cells = rows[0].split("|")
return set(_BACKTICK_NAME_RE.findall(cells[2]))
def _spec_reason_enum(spec_lines: list[str], errors: list[str]) -> set[str]:
starts = [
index for index, line in enumerate(spec_lines)
if "closed v1 enum" in line
]
if len(starts) != 1:
errors.append(
"spec §4: expected exactly one 'closed v1 enum' sentence in "
f"§4, found {len(starts)}"
)
return set()
names: set[str] = set()
for line in spec_lines[starts[0] + 1:]:
if match := re.fullmatch(r"- `([a-z0-9_]+)`", line.strip()):
names.add(match.group(1))
elif line.strip() and names:
break
return names
def check(root: Path) -> list[str]:
errors: list[str] = []
canonical_text = _read(root, CANONICAL_REL)
fragments = {
match.group("name"): match.group("body")
for match in sync.MARKER_RE.finditer(canonical_text)
}
fragment = fragments.get("methodology-receipt")
if fragment is None:
print("ERROR: methodology-receipt fragment missing from "
f"{CANONICAL_REL}", file=sys.stderr)
raise SystemExit(2)
spec_lines = _spec_section4_lines(
_read(root, SPEC_REL).splitlines(), errors
)
fragment_enums = _fragment_enums(fragment, errors)
spec_enums = {
"procedure_id": _spec_table_cell_enum(
spec_lines, "procedure_id", errors
),
"status": _spec_table_cell_enum(spec_lines, "status", errors),
"not_computable_reason": _spec_reason_enum(spec_lines, errors),
}
for name, checker_values in CHECKER_ENUMS.items():
for surface, values in (
("fragment", fragment_enums.get(name)),
("spec §4", spec_enums.get(name)),
):
if values is None:
continue # tail_convention has no spec enum; parse errors
# for the other absences are already recorded above.
if values != checker_values:
missing = sorted(checker_values - values)
extra = sorted(values - checker_values)
errors.append(
f"{name}: {surface} enum drifted from the checker "
f"frozenset (missing={missing}, extra={extra})"
)
return errors
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--root", type=Path,
default=Path(__file__).resolve().parents[1],
)
args = parser.parse_args(argv)
errors = check(args.root)
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
if errors:
return 1
print("RECEIPT-ENUM-SYNC: PASS (4 enums, 3 surfaces)")
return 0
if __name__ == "__main__":
sys.exit(main())