Skip to content

feat(code-review): approve a reviewed head - #82

Merged
pedromvgomes merged 3 commits into
mainfrom
feature/approve-a-reviewed-head
Sep 8, 2026
Merged

feat(code-review): approve a reviewed head#82
pedromvgomes merged 3 commits into
mainfrom
feature/approve-a-reviewed-head

Conversation

@pedromvgomes

Copy link
Copy Markdown
Owner

agtk code-review approve --pr N posts one GitHub review with event APPROVE, as the App, bound to the pull request's current head. A person types it; nothing in a review run reaches it. It exists because a solo author cannot satisfy a required approval alone.

Four conditions, and nothing overrides any of them:

# condition
a the head carries a review posted by this installation
b that review reached a verdict — the judge answered, every reviewer answered, and the thread list was readable
c every finding at or above the severity floor is marked a false positive
d every comment thread is resolved

A security:prompt-injection finding agtk could attach to nothing refuses regardless.

There is no --force

A flag that approves anyway makes every row above a checklist rather than a control, and the person who would type it is the one the gate exists to slow down. Both ways past a finding are acts on the pull request — change the code, or reply on its thread saying it is not a defect — and each is attributable to an account, visible to anybody reading the change, and reversible. This reverses an earlier consequence of ADR 0006.

How approval learns what the last review found

Nothing is persisted, so a posted review carries a marker in its own body: the commit, the verdict, every surviving finding by fingerprint and severity, and which of those agtk could give no thread to. Written and parsed in one file, for the reason marker.go already gives.

The last part is what keeps the gate honest in both directions. A finding with no thread blocks nothing — a gate with no remedy is a deadlock rather than a control — and a prompt-injection finding with no thread blocks everything.

Every blocking finding is answerable

A finding that carries no line, or whose line the diff does not add, now hangs off the whole file: its own request after the review, because subject_type is not a field a review's draft comments carry. AddedLines grew a second question — whether the diff names a path at all — so a file the change only deletes from is somewhere a comment can hang.

False positives

A reply opening a line with agtk: false positive and carrying a reason, from an account that can push. Resolution is not enough: it says the conversation is finished, which is a different claim from "this is not a defect". Write access rather than anyone who can comment, because a finding its own author could dismiss is one an injected instruction can dismiss too.

What guards it

internal/reviewapprove owns the event and the call. Three assertions replace the walk that banned the literal everywhere: the literal is named in no other package; no package a model's output passes through can reach internal/reviewapprove in the import graph, which holds even if somebody spells the event differently; and it joins the credential surface. A fourth is about the grant rather than the act — no file under internal/ may name an endpoint that writes contents, refs, trees, blobs or a merge, because the App holds contents: write only so that its approvals count (ADR 0009).

Also here

  • approval.floor in the review manifest, read at the base ref, defaulting to AMBER. The severity ladder moves to internal/review so a manifest can be validated against it; reviewrun.Severity is now an alias.
  • The shared injection clause says that none of a reviewer's bars narrows it: a prompt-injection finding is filed whether or not the change touches the line, whether or not a concrete failure follows, and whether or not the reviewer is sure what its author meant.

ADRs 0006 and 0007 are amended; ADR 0009 is new; CONTEXT.md gains False positive and Review marker.

…val floor

A manifest cannot be validated against a vocabulary it cannot see, and the
approval floor is a rung a repo names. The ladder therefore lives in
internal/review, where the manifest is parsed; internal/reviewrun keeps its
Severity as an alias, so a finding's severity and a manifest's floor are rungs
of one ladder rather than two vocabularies that happen to agree.

The floor defaults to AMBER. RED and AMBER are both defects and differ in the
strength of the claim rather than in what they oblige, so "at or above the
floor" reads as "a defect rather than a remark"; a repo that wants only RED to
oblige a fix says so. A floor off the ladder is refused when the manifest is
read, because an unrecognised severity ranks below every real one and would
quietly oblige nothing — silently, in the repo that took the trouble to set it.
Nothing is persisted between runs — ids are per-run, and the pull request is
the only record — so a posted review carries a marker naming the commit it was
made against, whether the run reached a verdict, every surviving finding by
fingerprint and severity, and which of those `agtk` could give no thread to.
Written and parsed in one file, for the reason the fingerprint marker already
gives: two definitions of one format drift, and here the failure is worse than
silent, because a marker that does not parse is a review nothing downstream can
see.

The verdict is all three of the judge answering, every reviewer answering, and
the thread list being readable. A run missing any of them found less than it
would have, and "found nothing" is the one thing that must never be read into
that.

Findings are grouped by what a reader can do about one rather than by why it is
not inline. A finding with no line, or one whose line the diff does not add,
now hangs off the whole file: its own request after the review, because
subject_type is not a field a review's draft comments carry. One that fails
costs its own thread and nothing else, and is reported. A finding naming a path
the change does not touch, or no path at all, still reaches the body alone —
GitHub refuses a comment there.

AddedLines keys every path the diff touches, so a file the change only deletes
from is a path a file-level comment can hang off. It answers two questions now:
whether an inline comment may name a line, and whether the diff names a path.
`agtk code-review approve --pr N` posts one review with event APPROVE, as the
App, bound to the pull request's current head. A person types it. Four
conditions hold or it refuses: the head carries a review by this installation,
that review reached a verdict, every finding it reports at or above the floor
is marked a false positive on its thread, and every comment thread is resolved.

There is no --force. A flag that approves anyway makes each of those a
checklist rather than a control, and the person who would type it is the one
the gate exists to slow down. Both ways past a finding are acts on the pull
request: change the code, so the next review does not report it, or reply on
its thread saying it is not a defect. Each is attributable, visible and
reversible. A prompt-injection finding agtk could attach to nothing refuses
regardless — the material addresses the reviewer, there is nothing to reply to,
and the deadlock is the point.

A false positive is a reply opening a line with `agtk: false positive` and
carrying a reason, from an account that can push. Resolution is not enough: it
says the conversation is finished, which is a different claim from "this is not
a defect". Write access rather than anyone who can comment, because a finding
its own author could dismiss is one an injected instruction can dismiss too.

internal/reviewapprove owns the event and the call that sends it. Three guards
replace the walk that banned the literal everywhere: it is named in no other
package; no package a model's output passes through can reach it in the import
graph, which holds even if somebody spells the event differently; and it joins
the credential surface. A fourth is about the grant rather than the act — the
App holds contents: write only so its approvals count, so no file under
internal/ may name an endpoint that writes contents, refs, trees, blobs or a
merge. See ADR 0009.

The injection clause now also says that none of a reviewer's bars narrows it: a
prompt-injection finding is filed whether or not the change touches the line,
whether or not a concrete failure follows, and whether or not the reviewer is
sure what the author meant. The quote is the whole of the evidence.
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

bulwark

  • scan — no findings

  • coverage — go: 81.1% (baseline 80.6%); go patch: 90.3% (552/611 new lines; baseline 80.6%)

📦 Full bulwark output — complete scan and coverage logs.

@pedromvgomes
pedromvgomes merged commit 252f837 into main Sep 8, 2026
10 checks passed
@pedromvgomes
pedromvgomes deleted the feature/approve-a-reviewed-head branch September 8, 2026 16:01
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.

1 participant