Skip to content

ci: no-new-decay gate for line-number citations - #1168

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/citation-decay-gate
Sep 4, 2026
Merged

ci: no-new-decay gate for line-number citations#1168
AdaWorldAPI merged 1 commit into
mainfrom
claude/citation-decay-gate

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Two files, no code touched. Second of two CI gates. Pairs with #1167.

The defect it catches. A citation to <file>:NNN into an append-only prepended file decays on every new entry, with no edit on either side. Two measured instances today: a plan cited EPIPHANIES.md:438 for a claim, and by ratification day :438 had become the heading of the ruling that retracted it — a citation to a removal reads identically to a citation to the thing removed. The same plan cited :13401 where the target was :13615.

How it decides. Recovers an anchor near each citation (board id › backticked symbol › quoted phrase; nearest wins) and asks whether it is still within ±3 lines of the cited line. Exits non-zero only on confirmed decay. No recoverable anchor, an unresolvable path, or a bare basename is UNVERIFIABLE — reported, never failed, and never guessed, because a guessed path manufactures verdicts.

Three orchestrator corrections, each from a test that failed rather than from review

1. Scoped to changed LINES, not changed files — and file scoping was measured insufficient. EPIPHANIES.md alone carries 10 pre-existing decays, and the board-hygiene rule means nearly every PR touches it. A file-scoped gate would have failed almost every PR for decay it did not introduce. That is the fires-on-everything defect, and shipping it would have contradicted this session's own advice against the untracked-plan check.

2. The citation regex could not match ANY dot-prefixed path. Its lookbehind rejected a preceding dot, so every .claude/... citation — most of the paths this workspace cites — was invisible to the scanner. Found because a probe citation was silently ignored, not by reading the regex.

3. Fails closed when the diff cannot be computed, rather than falling back to checking every line.

Verified on real committed diffs

case expected got
added line, wrong number fire exit 1, naming file, target and anchor
added line, correct number silent exit 0
untouched file with 10 known decays silent exit 0

The third case is what makes this a no-new-decay gate rather than a backlog alarm; the second is what proves it isn't simply rejecting all new citations.

The honest ceiling

Repo-wide today: 124 confirmed decays, 2153 unverifiable of 2314 citations. The 93% unverifiable has one cause — the house style cites bare basenames (counterfactual.rs:432, EPIPHANIES.md:355) — so real coverage is the ~7% that carry a path. That is a ceiling, not a tuning knob, and the 124-item backlog is a separate deliberate pass.

The failure message names the real fix: replace the line number with a heading-text or D-id anchor, because correcting the number re-arms the same bomb on the next prepend.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCfrD5y19cvFc4AoyydXYv


Generated by Claude Code

A citation to `<file>:NNN` into an append-only PREPENDED file decays on
every new entry, with no edit on either side. Two measured instances
today: a plan cited EPIPHANIES.md:438 for a claim, and by ratification
day :438 had become the heading of the ruling that RETRACTED it -- a
citation to a removal reads identically to a citation to the thing
removed. The same plan cited :13401 where the target was :13615.

The gate recovers an anchor near each citation (board id, backticked
symbol, quoted phrase; nearest wins) and asks whether it is still within
+-3 lines of the cited line. Exits non-zero ONLY on confirmed decay. No
recoverable anchor, an unresolvable path, or a bare basename is
UNVERIFIABLE: reported, never failed, and never guessed -- a guessed
path would manufacture verdicts.

Three orchestrator corrections to the worker's draft, each from a test
that failed rather than from review:

1. Scoped to changed LINES, not changed files. File scoping is not
   enough and it was measured: EPIPHANIES.md alone carries 10
   pre-existing decays and nearly every PR touches it under the
   board-hygiene rule, so a file-scoped gate would have failed almost
   every PR for decay it did not introduce. That is the fires-on-
   everything defect, and shipping it would have contradicted this
   session's own advice against the untracked-plan check.

2. The citation regex could not match ANY dot-prefixed path. Its
   lookbehind rejected a preceding dot, so every `.claude/...` citation
   -- most of the paths this workspace cites -- was invisible to the
   scanner. Found because a probe citation was silently ignored.

3. Fails closed when the diff cannot be computed, rather than falling
   back to checking every line.

Verified on real committed diffs, all three cases:
- added line, wrong number -> EXIT 1, names file, target and anchor
- added line, correct number -> EXIT 0
- untouched file with 10 known decays -> EXIT 0

Measured backlog on main: 124 confirmed decays, 2153 unverifiable of
2314 citations. The 93% unverifiable is one cause -- the house style
cites bare basenames -- so today's real coverage is the ~7% carrying a
path. That is an honest ceiling, not a tuning knob, and the backlog is a
separate deliberate pass.

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

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 87 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Essentials

Run ID: fbfc133d-d785-4479-a3a5-62f6eda7957d

📥 Commits

Reviewing files that changed from the base of the PR and between f30e300 and 4496e6c.

📒 Files selected for processing (2)
  • .claude/tools/citation_decay.py
  • .github/workflows/citation-decay.yml

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_49dbccc0-1186-42db-b5be-3cdd5993b8c8)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4496e6c143

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +51 to +53
# board-hygiene rule means nearly every PR touches it. `--added-lines-only`
# restricts the verdict to lines this PR actually added or modified,
# which is what makes it a NO-NEW-DECAY gate. The backlog is a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check citations affected by target-file shifts

When a PR prepends content to EPIPHANIES.md—the exact motivating scenario—existing references in other files decay even though their citing lines remain unchanged. Supplying only changed files and then restricting findings to added or modified citation lines means none of those references are checked, so the job succeeds despite newly introducing decay. Compare citation verdicts between the merge base and HEAD, or otherwise include unchanged citations whose resolved targets shifted, while excluding the pre-existing backlog.

Useful? React with 👍 / 👎.

Comment on lines +55 to +56
FILES=$(git diff --name-only --diff-filter=d "$BASE"...HEAD \
-- '.claude/plans/*.md' '.claude/board/*.md' || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate failures from the file-list diff

When this initial git diff fails, such as when the merge base is unavailable, || true converts the error into an empty FILES value and the following branch exits successfully. This bypasses the script's explicit fail-closed handling in added_lines, allowing the gate to pass without checking anything; preserve the command's failure or handle its status separately from a legitimately empty diff.

Useful? React with 👍 / 👎.

@AdaWorldAPI
AdaWorldAPI merged commit c5e8444 into main Sep 4, 2026
3 checks passed
AdaWorldAPI added a commit that referenced this pull request Sep 4, 2026
…d the gate (#1169)

Both PRs added a deliverable and merged with no board entry in-commit,
which is the retroactive-hygiene anti-pattern the Mandatory Board-Hygiene
Rule names by that name. Recording it rather than quietly backfilling,
because the subject of both PRs is making exactly this class of lapse
mechanical rather than remembered.

Carries the measured numbers that justify each gate's scope: the
append-only gate's four verification levels including a real fire test,
and the citation backlog (2314 / 37 OK / 124 DECAYED / 2153
UNVERIFIABLE) that forced line-scoping over file-scoping.


Claude-Session: https://claude.ai/code/session_01DCfrD5y19cvFc4AoyydXYv

Co-authored-by: Claude <noreply@anthropic.com>
AdaWorldAPI added a commit that referenced this pull request Sep 4, 2026
Records the three review findings on the two board gates, and the
uncomfortable half: #1167, #1168 and #1170 each shipped without their
board hygiene in-commit -- three times running, by the session whose
entire subject was making board discipline mechanical rather than
remembered. #1169 recorded the first two; this records the third and
its own lateness.

The two defects are the same shape as the failure each gate exists to
prevent: a substitution that preserves the count (IDEAS.md swapped out
of an eight-tuple that stayed eight long), and a scope that excludes
the case it was built for (added-lines filtering, blind to an
EPIPHANIES.md prepend by construction).

Three sub-findings kept: a count is not a set; a self-test can assert
nothing while printing a number; a green CI tick is not evidence the
gate ran -- the job log was pulled instead, precisely because the
finding was a gate reporting success on the case it exists to catch.

The argument for mechanical checks does not rest on the author being
careless. Over one session the same author, holding the rule in mind,
writing the tooling for the rule, broke it three times and caught it
zero times.

Both gates run green on this commit; the supersession index
regenerates byte-identical (the entry cites no D-ids).


Claude-Session: https://claude.ai/code/session_01DCfrD5y19cvFc4AoyydXYv

Co-authored-by: Claude <noreply@anthropic.com>
AdaWorldAPI pushed a commit that referenced this pull request Sep 4, 2026
…he lint name

Merging origin/main brought in the new `citation-decay` gate (#1168/#1170),
which fired on exactly one new citation — mine:

  TECH_DEBT.md -> crates/sigker/src/signature.rs:133
  anchor=symbol:"needs_range_loop" (anchor absent within +-3 lines)

The gate is right and the finding is a real defect, not a false positive.
`needs_range_loop` is clippy's LINT NAME; it appears nowhere in the cited
source. The gate extracts the backticked token nearest the citation as the
anchor, so the lint name won that race and then could not be found at the line
it supposedly addressed. A citation whose anchor is absent from its target is
unverifiable by construction — which is the whole point of the gate.

Re-anchored on `for flat in 0..len`, which is the actual text at
signature.rs:133, and the lint name is now named in prose instead. The entry
also records why, so the next reader does not "fix" it back.

Local gate runs after the merge, all green:
- citation_decay.py --self-test: passed (both halves)
- citation_decay.py --since <base>: 0 new decays (148 pre-existing backlog,
  unchanged and not failing)
- append_only_gate.py --self-test: passed, 7 cases
- append_only_gate.py origin/main: no protected file shrank, 9 checked
  (TECH_DEBT 4161 -> 4212, +51)
- supersession_index.py: regenerated after the merge, byte-identical
- plan_dids.py: no added plans, nothing to check
- cargo test --manifest-path crates/sigker/Cargo.toml: 62 passed, 0 failed
- cargo fmt --check: clean

Also merges origin/main (f30e300..ac9148f) to clear the merge conflict that
made this PR un-mergeable. The merge itself was clean; EPIPHANIES.md
auto-merged, both sides having prepended.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WkNBjHc2e3zuyz9i8qJEv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants