feat: accept a finding in the workflow it belongs to, with a stated reason - #75
Merged
Merged
Conversation
…eason
A finding that is genuinely acceptable had nowhere to go. It reported forever,
or the reader learned to ignore it - and this repository proved the point: its
own accepted findings lived in a Dictionary inside a test file, invisible to
anyone reading the workflow they were about.
permissions:
# sentinel:accept GHA002 - deleting a workflow run has no narrower grant
actions: write
In the workflow rather than a separate file of rule/line/reason entries, because
line numbers in such a file drift the moment anyone edits the workflow, the
reason ends up far from what it explains, and the file outlives the code it was
written about. A comment dies with the line it annotates, and a reviewer sees it
appear in the diff beside what it waves away.
Three refusals do the actual work:
- an acceptance with no reason is ignored and the finding still reports. A
bare marker records that someone wanted the finding gone, not that anyone
considered it.
- an acceptance is matched on rule AND line, so accepting one grant cannot
quietly cover a second finding elsewhere in the same file.
- an acceptance that no longer matches a finding is itself reported (GHA012).
A directive that outlived its finding is a claim, sitting in the file, that
somebody considered a problem which is no longer there. Every suppression
list rots; reporting the rot is what stops it.
Nothing disappears. An accepted finding moves into "Reviewed and accepted"
carrying its original severity and the stated reason, and the client separates
a documented requirement from a person's judgement - only the second can be
wrong about the risk.
The Accepted table in RepositoryWorkflowsTests is now empty: prune-runs.yml
states its own acceptance beside the grant. That test also stopped calling the
rules directly, which held this repository to a stricter standard than the tool
applies to anyone else's; it now measures what the product reports, and fails
on a stale or unexplained acceptance of its own.
322 backend tests and 28 frontend tests pass; both stacks build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7HjzX5e8Wv7PKENDo2Uj7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A finding that is genuinely acceptable had nowhere to go. It reported forever, or the reader learned to ignore it.
This repository proved the point: its own accepted findings lived in a
Dictionaryinside a test file — invisible to anyone reading the workflow they were about, and useless to anybody else scanning their own repo.Why in the workflow, not a config file
A separate file of rule/line/reason entries is the obvious design and the wrong one. Line numbers drift the moment anyone edits the workflow, the reason ends up far from what it explains, and the file outlives the code it was written about.
A comment is deleted by the same edit that deletes what it annotates, and a reviewer sees it appear in the diff beside what it waves away.
It also fits the constraints already in place: Sentinel is read-only and anonymous, so there is no account to store a decision against. It has to live in the repo it is already fetching.
Three refusals do the actual work
No reason, no acceptance. A directive with no stated reason is ignored and the finding still reports. A bare marker records that someone wanted the finding gone, not that anyone considered it — the mute button this exists instead of.
Matched on rule and line. Accepting one grant cannot quietly cover a second finding of the same rule elsewhere in the file.
A stale acceptance is itself reported. If someone accepts GHA002 on a line and later fixes the permission, that comment is now a claim sitting in the file that somebody considered a problem which is no longer there. It reads as considered when nothing considered it. Every suppression list accumulates these; reporting the rot is what stops it.
That last rule is this repository's own stated principle, previously enforced only by hand in a test file:
Nothing disappears
An accepted finding moves into Reviewed and accepted carrying its original severity and the stated reason. The summary shows both counts, so a suppressed Critical is quiet, never invisible.
The client separates the two kinds of acceptance, because they are different claims and only one of them can be wrong about the risk:
required by an action— a documented fact, established by the ruleaccepted by author— a person's judgement, with their reasonThe exemption table is now empty
prune-runs.ymlstates its own acceptance beside the grant, soRepositoryWorkflowsTests.Acceptedholds nothing.That test also stopped calling the rules directly. Doing so bypassed the service where acceptance is applied, which held this repository to a stricter standard than the tool applies to anyone else's. It now measures what the product actually reports — and fails on a stale or unexplained acceptance of its own.
Verification
322 backend tests and 28 frontend tests pass; both stacks build. Eight new tests cover the mechanism, each paired with what it must refuse: no reason, wrong rule, wrong line, still-valid, and outlived.
🤖 Generated with Claude Code
https://claude.ai/code/session_01N7HjzX5e8Wv7PKENDo2Uj7
Generated by Claude Code