Files
Edward Cheng-I Wu 5f107c7bad v3.7.1 Step 0+1: SHA byte-equivalence gate + D1 trust-chain frontmatter schema (#76)
* feat(v3.7.1 Step 0): inversion manifest + SHA byte-equivalence lint

Spec: docs/design/2026-04-30-ars-v3.6.8-trust-provenance-and-drift-transparency-spec.md
      § Step 0 (round-1 codex F-004 amend)

Establishes v3.7.1's own inversion manifest separate from the v3.6.7
frozen scope, plus a byte-equivalence SHA gate that asserts the v3.6.7
PATTERN PROTECTION blocks in synthesis_agent / research_architect_agent /
report_compiler_agent stay byte-identical to the v3.6.7 base commit
(derived via `git log -1 -- scripts/v3_6_7_inversion_manifest.json`).

Why: Step 3a will add a "Two-Layer Citation Emission" prompt block to
those three agents OUTSIDE their v3.6.7-tagged blocks. The SHA gate keeps
that boundary enforceable from PR-1 onward, so any accidental edit inside
the v3.6.7 block is caught at CI time.

How:
- scripts/v3_6_8_inversion_manifest.json — empty entries for now;
  Step 3a will populate with two-layer-citation invariants.
- scripts/check_v3_6_8_pattern_protection.py — reuses v3.6.7 lint's
  heading-based extractor for byte-equivalent block isolation; resolves
  default branch via `git symbolic-ref refs/remotes/origin/HEAD` →
  $GITHUB_DEFAULT_BRANCH → hard-fail; handles shallow CI clones via
  `git fetch --unshallow`; computes SHA-256 with raw-bytes-minus-BOM.
- .github/workflows/spec-consistency.yml — adds `fetch-depth: 0` to
  checkout (so `git log -1` and `git show <commit>:<path>` see history)
  + two new lint steps right after the v3.6.7 mutation tests.
- scripts/test_check_v3_6_8_pattern_protection.py — 12 tests pinning
  the contract: happy path + intra-block mutation (FAIL) + extra-block
  H2 append directly after EOF newline (PASS) + same with extra blank
  line (FAIL — pins the Step 3a contributor rule) + manifest shape
  validation + boundary error cases.

Verification (local):
- baseline: spec-consistency / v3.6.7 protection (12 inv) /
  v3.6.7 mutation (58/58) / audit-artifact-consistency — all green.
- new: SHA gate happy-path PASS; 12/12 mutation tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(v3.7.1 Step 1): D1 frontmatter schema split + trust-chain lint

Spec: docs/design/2026-04-30-ars-v3.6.8-trust-provenance-and-drift-transparency-spec.md
      § 3.1 D1 — Frontmatter trust-strength conflation
      § Step 1 — Frontmatter schema split

Splits the previously-single `verified` field into seven orthogonal
trust-chain fields on `literature_corpus_entry.schema.json` so downstream
consumers can distinguish source acquisition, source verification against
the original artifact, and description provenance. The user-set human-read
signal stays user-owned in the §3.6 peer file (NOT entry-stored) per
round-1 codex F-005 amend; the entry schema remains
`additionalProperties: false`.

Schema (additive — backward-compatible with v3.6.4 fixtures):
- 7 new optional properties: source_acquired, source_acquisition_date,
  source_acquisition_path, source_verified_against_original,
  source_verification_method, description_source, description_last_audit.
- 2 new `allOf` branches enforcing spec firm rules #1 and #2:
    #1: source_verified_against_original=true ⇒ source_acquired=true AND
        source_verification_method ∈ {codex_audit, manual_grep, vision_check}
        (round-2 R2-007 amend: 'none' enumerated but FORBIDDEN here)
    #2: source_acquired=false ⇒ description_last_audit ∈ {null, "none"}
- Firm rule #3 (no literal human_read_source / human_read_at on entry)
  is automatically caught by the existing `additionalProperties: false`.

Agent prompts:
- deep-research/agents/bibliography_agent.md — adds "Trust-Chain
  Frontmatter Discipline (v3.7.1+)" H2 with field semantics, three firm
  rules, and a refusal-on-uncertain rule (do not infer verification from
  derivative-bibliography agreement; emit verified=false when in doubt).
- academic-paper/agents/literature_strategist_agent.md — same H2 framed
  for the downstream consumer perspective: read-only, surface
  inconsistencies rather than paper over them.

Lint + tests:
- scripts/check_v3_6_8_frontmatter_trust_schema.py — defense-in-depth
  over the schema; produces friendly spec-cited error messages when
  scanning passports / single entries / bare lists.
- scripts/test_check_v3_6_8_frontmatter_trust_schema.py — 20 tests:
  schema self-consistency + 7-field presence + additionalProperties
  preserved + positive/negative for each firm rule + payload-shape
  coverage + v3.6.4 fixture regression.
- .github/workflows/spec-consistency.yml — wires lint + pytest into CI.

Verification (local):
- All 4 baselines green: spec-consistency, v3.6.7 protection (12 inv),
  v3.6.7 mutation (58/58), audit-artifact-consistency.
- v3.6.4 backward compat: existing literature_corpus schema lint green;
  corpus-consumer-protocol lint green; all 3 adapter fixtures pass new
  trust-schema lint (no entry currently carries trust fields).
- New: 20/20 trust-schema mutation tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 1 cascade): regenerate adapters/overview.md + escape union type pipe

CI's `sync_adapter_docs.py --check` step caught two issues from the
Step 1 schema additions:

1. The 7 new trust-chain properties on literature_corpus_entry.schema.json
   needed to cascade into the auto-generated property table at
   academic-pipeline/references/adapters/overview.md.

2. `description_last_audit: ["string", "null"]` was the first union-typed
   property in the schema. `_short_type` rendered the union as
   `null | string`, but Markdown table cells are pipe-delimited, so the
   row split into extra columns. The fix escapes the separator to `\|`
   so the union renders as a literal pipe inside the cell.

The `_short_type` patch is generic — any future union-typed property
will render correctly without re-escaping at the call site.

Verification: `python3 scripts/sync_adapter_docs.py --check` PASSES;
all PR-1 lints + tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 1 R1): close 2 codex P2 findings on D1 schema

Round-1 codex review (`/codex review --base main`) returned 0 P1 + 2 P2.
Both findings closed in this commit.

P2-1 — Schema rule #2 hole on missing description_last_audit:
The `if/then` allOf branch only constrained `description_last_audit`
when the property was present, so an entry with `source_acquired: false`
that omitted the field passed both schema and lint. Spec § 3.1 firm rule
#2 says REQUIRES, which is strict (the field must be present, value must
be null or "none"). Schema gains `required: ["description_last_audit"]`
under the rule-2 `then` branch; lint mirrors with a friendly
"is missing" diagnostic instead of defaulting `entry.get(...)` to None.
New test pins the missing-field FAIL case.

P2-2 — description_source enum hard-coded v1..v3:
Spec § 3.1 yaml uses `bibliography_v<n>` as a template (any non-negative
integer n). The schema enum hard-coded v1..v3, so a corpus that follows
the documented format with v4+ would fail validation. Switched to a
regex `^(original_pdf|bibliography_v[0-9]+|secondary_summary)$` and added
three tests: arbitrary revision numbers (v0 / v4 / v17 / v999) PASS,
canonical values (original_pdf / secondary_summary / v1) still PASS,
unrelated strings (bib_v1 / bibliography_vX / typos) still FAIL.

Verification (local):
- 24/24 trust-schema tests pass (was 20 before R1 fixes);
- 12/12 SHA gate tests + 58/58 v3.6.7 mutation tests still green;
- All 4 baseline lints + sync_adapter_docs --check + corpus consumer
  protocol all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R2): close codex P2 — anti-self-baseline guard on SHA gate

Round-2 codex review (`/codex review --base main`) returned 0 P1 + 1 P2
that's effectively P1-grade in scope: the SHA gate could self-baseline.

The hole: a PR that mutates `scripts/v3_6_7_inversion_manifest.json` AND
a v3.6.7-tagged PATTERN PROTECTION block in the same commit causes
`git log -1 -- v3_6_7_inversion_manifest.json` to resolve to that very
commit. The base block (`git show <commit>:<file>`) and the HEAD block
(read from worktree) then point at the same modified content, the SHA
hashes match trivially, and the boundary rule the gate exists to enforce
goes silently un-checked. Codex's framing: "the gate hashes modified
content as its own baseline."

Fix (per spec round-4 R4-002 single-source-of-truth + new round-2 P2 closure):
add an anti-self-baseline guard at the front of `check_byte_equivalence`.
The guard reads `v3_6_7_inversion_manifest.json` bytes at HEAD and at
`merge-base $GITHUB_BASE_REF / origin/<default-branch> HEAD`, and refuses
to run the SHA gate if the bytes differ — instructing the contributor to
land manifest amendments in a separate PR (under a v3.7+ amendment
process) so the next gate run sees the new manifest as its baseline.

Edge cases handled:
- Manifest absent at PR base → "manifest creation is not a v3.7.1-PR action"
- Manifest absent at HEAD but present at base → "deletion is not a
  v3.7.1-PR action"
- Manifest unchanged → guard passes, fall through to `git log -1` derivation
- No PR base detectable (local detached state) → guard treated as advisory;
  CI on the canonical repo catches the attack

Verification (local):
- New `test_anti_self_baseline_guard_rejects_manifest_mutation_in_pr`:
  mutate manifest in worktree → lint refuses with the round-2 message.
- Pre-existing 12 SHA gate tests stay green; one (manifest-deletion test)
  refined to accept either the new guard message or the legacy loader
  error, since the guard now catches deletion earlier with a more precise
  message.
- All 4 baseline lints + sync_adapter_docs + corpus consumer protocol +
  literature corpus schema + 95 tests across the three v3.6.x suites
  green.

Spec round-9 amendment will document the anti-self-baseline invariant
in §388 acceptance criteria; that's a separate spec PR. This commit lands
the runtime enforcement first (per `feedback_codex_iterative_spec_review_to_zero.md`
discipline — close findings before round-3, regardless of where the spec
text catches up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R3): close codex P2 — include heading prefix in SHA range

Round-3 codex review caught a v3.6.7-extractor inheritance bug. Spec § 388
canonical range says "start at the LINE containing
`## PATTERN PROTECTION (v3.6.7)`", so the `## ` heading prefix is part
of the hashed bytes. But the v3.6.7 lint's `_extract_block` finds the
marker via case-insensitive substring search — it returns a slice
starting at `PATTERN...`, dropping the heading prefix.

That's harmless for v3.6.7's own invariant greps (they search keywords
inside the block; prefix doesn't matter), but the v3.7.1 SHA gate
inherited the same start position and would have accepted a
`## PATTERN PROTECTION (v3.6.7)` → `### PATTERN PROTECTION (v3.6.7)`
mutation as byte-equivalent. Spec wording would say the gate had
silently approved a heading-level demotion.

Fix: `_extract_block_bytes` now wraps the v3.6.7 extractor and walks
the start position backward to the start of the marker's line, so the
heading prefix bytes are inside the hashed range. End position and
termination logic untouched — byte-equivalence to the v3.6.7 extractor
is preserved everywhere except the prefix.

Two new tests:
- `test_heading_prefix_mutation_is_caught`: mutate `##` → `###` on
  synthesis_agent.md → SHA gate FAILS (was previously a silent pass).
- `test_extractor_includes_heading_prefix_bytes`: unit-level check that
  the extractor returns different bytes for H2 vs H3 input and that
  the prefix bytes are literally in the returned slice.

This closes round-3's only finding (0 P1, 1 P2). The base-commit hash
in CI updates because the hash range now includes the previously-elided
prefix bytes — `[v3.7.1 SHA gate]` PASS lines show new sha256 prefixes
on all 3 protected files, but the comparison itself stays trivially
equal at the new commit.

Verification (local):
- 15/15 SHA gate tests + 24/24 trust schema tests + 58/58 v3.6.7
  mutation tests = 97 total green.
- All 4 baselines + sync_adapter_docs + corpus consumer protocol +
  literature corpus schema all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R4): close codex P2 — history-scan in anti-self-baseline guard

Round-4 codex review found a touch-and-revert bypass of the round-2
guard. The byte-only check at HEAD vs base lets the following pattern
through:

  commit A: modify v3.6.7 manifest + modify v3.6.7 protected block
  commit B: revert manifest to original bytes (block edit stays)

→ HEAD-vs-base manifest BYTES are equal (round-2 guard sees nothing wrong)
→ `git log -1 -- manifest` still resolves to commit B as the SHA gate's
  baseline
→ `git show B:<protected>` returns the modified block content
→ HEAD reads the same modified block
→ SHA(B's modified) == SHA(HEAD's modified) → gate trivially passes

Codex's framing: "the guard needs to reject any manifest touch in
merge-base..HEAD, not just final content changes."

Fix: insert a `git log --format=%H merge-base..HEAD -- manifest` history
scan as the FIRST check in `_v3_6_7_manifest_unchanged_in_pr`. Any commit
that touches the manifest in the PR range — whether the touch is
ultimately retained at HEAD or reverted later — produces a non-empty
log output and the guard rejects with a list of the offending SHAs.

The round-2 byte-comparison stays as a defense-in-depth backstop in case
the path-filtered `git log` ever under-reports (corrupted history,
filter bug). The "missing at HEAD / missing at base" branches are kept
since they catch a class the history scan can't (manifest re-created
fresh in this PR).

New test:
- `test_anti_self_baseline_guard_history_scan_called` patches `_run_git`
  to inject a synthetic non-empty `git log` result, asserts the guard
  rejects with the round-2+round-4 closure message.

Pre-existing tests:
- `test_anti_self_baseline_guard_rejects_manifest_mutation_in_pr`
  refined to accept either the byte-mismatch message OR the touched-by
  message, since worktree mutations now fall through to the byte
  backstop (history is unchanged, only worktree differs).

Verification (local):
- 16/16 SHA gate tests + 24/24 trust schema tests + 58/58 v3.6.7
  mutation tests = 98 total green.
- All 4 baselines + sync_adapter_docs + corpus consumer protocol all green.

Spec round-9 amendment will document the history-scan invariant
alongside the round-2 byte-equality check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R4 followup): pin GITHUB_BASE_REF in guard tests

CI on `push` event runs revealed that the round-2 + round-4 anti-self-
baseline guard tests assumed PR context. Without `GITHUB_BASE_REF` set
(present on `pull_request` events, absent on `push`), the guard's
`_detect_pr_base_ref` returns None and falls through to advisory mode
("local detached state"), so the tests' "guard MUST reject" assertions
were not exercised on push runs.

Fix: both guard tests now `monkeypatch.setenv("GITHUB_BASE_REF", "main")`
to force the guard onto its real reject path regardless of trigger
event. The `pull_request` event run was already green; this fix unblocks
the matching `push` event run on the same commit.

No production-code change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 1 R6): close codex P2 — strict 'none' literal in firm rule #2

Round-6 codex review caught a spec-vs-impl wording widening I introduced
without authority. Spec § 3.1 firm rule #2 (line 120) reads:

    `source_acquired: false` REQUIRES `description_last_audit: none`

That is the literal sentinel string "none". Spec § 3.1 yaml at line 111
lists the value vocabulary as `<round_id> | none` — there is no null
alternative.

R1 closure had widened this to "null OR 'none'" by mistake. The schema
`then` branch carried `oneOf: [null, "none"]`; the lint allowed both.
That meant an entry with `source_acquired: false` and
`description_last_audit: null` passed CI even though the spec demands
the sentinel.

Fix:
- Schema rule-#2 then-branch: `{type: "string", const: "none"}` only.
- Lint rule-#2 check: only literal "none" passes; null is rejected
  with a "literal sentinel string" diagnostic citing spec § 3.1 line 111.
- Top-level `description_last_audit` field type stays `[string, null]`
  — null is still legal when source_acquired=true and the entry hasn't
  been audited yet. The tightening is scoped to the rule-#2 case only.

Tests:
- `test_rule2_acquired_false_with_audit_null_passes` flipped to
  `..._fails` with positive assertion that schema rejects + lint
  surfaces the literal-only message. Old name removed (it asserted
  the now-incorrect lenient behaviour).
- All other pre-existing tests still green; the `verified_false_does_not_constrain_method`
  test fixture already used `description_last_audit: "none"` (string),
  so it stays compatible.

Verification (local):
- 24/24 trust schema + 16/16 SHA gate + 58/58 v3.6.7 mutation = 98 green.
- All 4 baselines + sync_adapter_docs + corpus consumer + corpus schema green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 1 R7): close codex P2 — agent prompts cascade for R6 strict-'none'

Round-7 codex review caught a cascade gap from R6. R6 tightened the
schema and lint to require literal "none" (rejecting null) when
source_acquired=false, but two agent prompts (bibliography_agent +
literature_strategist_agent) still told agents "MUST be null or 'none'".
An agent following its own prompt for an unacquired source could emit
description_last_audit: null and produce a passport that fails the new
CI lint — the documented agent contract diverged from the enforced
contract.

Per `feedback_cross_model_review_cascade_inconsistency.md`: large
revisions need a second-round codex pass to catch downstream cascade,
exactly the kind R7 surfaced.

Fix:
- bibliography_agent.md (Trust-Chain Discipline H2):
  - Inline yaml comment on description_last_audit clarifies "null only
    when source_acquired=true; rule-#2 case requires literal 'none'".
  - Firm rule #2 prose rewritten to require literal "none", cite spec
    line 120 + line 111 yaml vocabulary, and explain CI lint enforcement.
- literature_strategist_agent.md (Trust-Chain Discipline H2):
  - Same yaml-comment + rewritten firm rule #2.
  - Adds "such entries fail the trust-chain CI lint" guidance so the
    consumer agent surfaces the inconsistency rather than silently
    treating null as acceptable.
- literature_corpus_entry.schema.json field-level description:
  - Clarifies that the field-level `[string, null]` permits both
    broadly, but the rule-#2 then-branch tightens to literal "none"
    only when source_acquired=false. Prevents future readers from
    assuming the field-level wording is the full contract.

No production-code logic change in this commit (it's all prompts +
schema description text); the rule enforcement landed in R6.

Verification (local):
- 98 tests green across all three v3.6.x suites;
- All 4 baselines + sync_adapter_docs (no cascade — descriptions kept
  their first-sentence summary intact) + corpus consumer + corpus
  schema all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R8): close codex P2 — file-level vs block-level BOM stripping

Round-8 codex review caught a normalization scope error introduced by
R3. R3 wrapped the v3.6.7 extractor with line-start backtracking + a
helper called `_normalize_bytes` that stripped a leading UTF-8 BOM from
the EXTRACTED block. Spec § Step 0 says "the FILE's BOM (if any) is
excluded" — the exclusion is FILE-level (byte 0), not block-level.

Because all three current v3.6.7-protected blocks start MID-FILE,
inserting U+FEFF immediately before `## PATTERN PROTECTION (v3.6.7)`
created a block whose extracted slice started with the BOM bytes,
which `_normalize_bytes` then silently stripped, making the HEAD block
hash identical to the base hash even though the canonical byte range
had changed. Real content mutation, gate said PASS.

Codex's framing: "this should only strip a BOM when the block begins
at byte 0 (or normalize the full file before extraction)."

Fix:
- Renamed `_normalize_bytes` → `_strip_file_bom`. Old name kept as a
  module-level alias only for backward compatibility with anything
  that imports it; tests use the new name.
- `_extract_block_bytes` now takes raw `file_bytes: bytes` (was: a
  decoded `str`) and applies `_strip_file_bom` BEFORE extraction.
  This restricts BOM stripping to byte 0 of the file; any BOM that
  appears later (e.g. inserted before a heading as a hidden mutation)
  stays in the hashed range and trips the gate.
- Two call sites in `check_byte_equivalence` updated to pass raw
  bytes; intermediate `.decode("utf-8", errors="replace")` calls
  removed (the function now does the decode internally on
  BOM-stripped bytes).

New tests:
- `test_extractor_strips_only_file_level_bom_not_block_level`: unit-
  level check that file-level BOM (byte 0) IS stripped, but BOM
  before heading (mid-file) is NOT.
- `test_bom_before_heading_attack_caught_by_lint`: end-to-end
  mutation test that injects BOM right before the v3.6.7 marker on
  disk and asserts the lint FAILS with BYTE-EQUIVALENCE FAIL.

Renamed test (signature change, semantics unchanged):
- `test_normalize_strips_bom_only_when_present` → `test_strip_file_bom_only_at_byte_zero`,
  with an added assertion that mid-input BOM bytes are NOT stripped
  (pinning the round-8 invariant at the helper level too).

Verification (local):
- 18/18 SHA gate + 24/24 trust schema + 58/58 v3.6.7 mutation = 100 green.
- All 4 baselines + sync_adapter_docs + corpus consumer + corpus schema green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R9): close codex P3 — anchor marker search to heading line

Round-9 codex review caught a false-fail risk introduced by R3's
substring-based marker search. A v3.7.1 PR may legitimately add prose
BEFORE the protected block that mentions `PATTERN PROTECTION (v3.6.7)`
— for instance, a "Two-Layer Citation Emission" section's introductory
paragraph that explains how new invariants relate to the existing
v3.6.7 PATTERN PROTECTION block. The pre-round-9 substring search would
have matched the prose mention first, hashed the wrong byte range, and
false-failed CI on a valid edit.

Codex's framing: "anchor the lookup to the Markdown heading line."

Note: this is a P3 finding (not P2/P1). Per
`feedback_codex_review_vs_resume_audit_scope.md`, P3-only is ship-OK,
but the false-fail risk is real correctness-grade — fixing now keeps
the lint usable for legitimate v3.7.1 work.

Fix:
- Add `import re`.
- Replace the substring `text.lower().find(marker.lower())` with an
  anchored heading regex: `(?im)^[ \t]*#{1,3}[ \t]+<marker>`. The
  `(?m)` MULTILINE flag makes `^` match line starts; `(?i)` matches
  the case-insensitive marker per the v3.6.7 lint convention. NO `\b`
  after the marker (it ends with `)`, a non-word char, so `\b` would
  not match there).

Tests:
- New `test_prose_mention_of_marker_does_not_misanchor_extractor`:
  text containing a prose mention of the marker before the actual
  heading; extractor must return the heading-anchored block, NOT the
  prose mention. Asserts the returned bytes start with `## PATTERN
  PROTECTION (v3.6.7)\\n` and that the prose paragraph's content is
  not swallowed.
- `test_bom_before_heading_attack_caught_by_lint` updated to accept
  either "BYTE-EQUIVALENCE FAIL" or "marker missing at PR HEAD" as
  the rejection diagnostic. After R9, BOM bytes injected before the
  heading break the `^[ \\t]*#{1,3}` shape so the regex misses, and
  the lint falls into the missing-marker branch — still a hard fail,
  just a different message. Both are correct.

Verification (local):
- 19/19 SHA gate (added 1 prose-mention test) + 24/24 trust schema +
  58/58 v3.6.7 mutation = 101 total green.
- All 4 baselines + sync_adapter_docs + corpus consumer + corpus
  schema all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(v3.7.1 Step 0 R10): close codex P2 — independent block-end search

Round-10 codex review caught an incomplete fix from R9. R9 anchored the
block START to a Markdown heading line, but `_extract_block_bytes` was
still calling the v3.6.7 legacy extractor and using `len(block)` from
that result as the slice length. The v3.6.7 extractor uses substring
search — when prose before the protected block mentions
`PATTERN PROTECTION (v3.6.7)`, it returns the slice starting at the
PROSE position. So `len(block)` equalled "prose-to-next-heading", and
adding that length to the heading-anchored line_start produced a
slice covering the WRONG byte range (truncated or extended depending
on relative offsets). CI would falsely fail valid v3.7.1 PRs.

Codex's framing: "the slice length comes from the wrong prose-to-
heading fragment, so the SHA range is truncated/extended."

Fix: stop calling the v3.6.7 legacy extractor entirely. Anchor BOTH
endpoints to heading lines:
- START: `(?im)^[ \t]*#{1,3}[ \t]+<marker>` (round-9, kept).
- END: independent search for the next H1/H2/H3 heading AFTER the
  marker line, or EOF — `(?m)^[ \t]*#{1,3}[ \t]+`. Mirrors the
  v3.6.7 lint's heading-to-next-heading-or-EOF termination semantics
  but starts the END search from the line AFTER the marker, not from
  the `pos = len(marker)` cursor the substring extractor used.

Removed unused import: `_extract_block as _v3_6_7_extract_block` from
`check_v3_6_7_pattern_protection`. The v3.6.8 lint no longer delegates
any extraction work to the v3.6.7 extractor — it computes the canonical
range from scratch using heading-anchored regexes. (The v3.6.7
PROTECTION_BLOCK constant is still imported as the marker text source
of truth.)

New test:
- `test_prose_mention_does_not_truncate_block_range`: prose paragraph
  mentions marker before the real heading; extractor must return the
  block from the heading through the next heading (full body),
  excluding the prose paragraph and the v3.7.1 section's heading.

Verification (local):
- 20/20 SHA gate (added 1 R10 closure test) + 24/24 trust schema +
  58/58 v3.6.7 mutation = 102 total green.
- All 4 baselines + sync_adapter_docs + corpus consumer + corpus
  schema all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 06:19:04 +08:00

4.1 KiB
Executable File