Skip to content

fix: permission-gate cancel, memory acks, and command supersession (#13)#49

Merged
BunsDev merged 1 commit into
mainfrom
fix/issue-13-command-gates-durable
Jul 7, 2026
Merged

fix: permission-gate cancel, memory acks, and command supersession (#13)#49
BunsDev merged 1 commit into
mainfrom
fix/issue-13-command-gates-durable

Conversation

@BunsDev

@BunsDev BunsDev commented Jul 7, 2026

Copy link
Copy Markdown
Member

Re-lands the privilege fix from fix/issue-13-command-gates (written pre-durable-store) adapted to the SQLite queue that landed with #2.

The holes (all real on main)

The #13 command protocol gated work commands (fix/review/retry) behind the worker's write-access check — but three lanes acted before any permission check:

  1. @cody cancel tombstoned queued reviews at the route — a drive-by commenter could cancel a maintainer's queued review
  2. @cody remember / forget acknowledgements replied ungated
  3. A command-initiated review superseded older queued reviews at durable insert — an unauthorized commenter could displace legitimate queued work just by asking

The fix

  • cancel rides a new adapter-only TaskKind::CancelReviews: the worker verifies the commander's write access first, then tombstones via store.cancel_queued and acknowledges with the count; below-write commanders get Status: declined. No Check Run, no session — same adapter-only footprint as replies.
  • Memory acknowledgements carry the commander and decline below write. Status answers and clarification replies stay ungated (read-only).
  • Insert-time supersession is now auto-review-only (commander IS NULL); command reviews supersede older queued reviews post-gate via supersede_queued_except, which spares the commanding task itself.

Verification (151 tests, all green)

  • Route: cancel produces a gated CancelReviews task and mutates nothing at the route; remember carries the commander
  • Worker (wiremock): drive-by cancel → declined, queued review untouched; maintainer cancel → tombstoned + "Cancelled 1 queued review(s)" ack, no Check Run; drive-by memory ack → declined, ungated text never leaks
  • Store: command review does not tombstone at insert; post-gate supersession spares the current task
  • clippy -D warnings clean · python gates green · demo 21/21 through the real binary

Credit: original analysis and approach from the fix/issue-13-command-gates branch; this PR supersedes it (that branch predates the durable queue and no longer applies).

…ommand supersession (#13)

Drive-by commenters could steer queued work: the cancel command tombstoned
queued reviews at the route (pre-gate), memory acknowledgements replied
ungated, and a command review superseded queued reviews at durable insert —
all before anyone checked the commander's write access.

Now every mutating or acknowledging command lane passes the worker's write
gate first:

- cancel rides a new adapter-only CancelReviews task; the tombstone happens
  in the worker after the gate, and below-write commanders get the decline
  body instead
- remember/forget acknowledgements carry the commander and decline below
  write; status and clarification replies stay ungated
- insert-time supersession is auto-review-only: command reviews supersede
  older queued reviews post-gate via supersede_queued_except, sparing the
  commanding task itself

Re-lands the intent of fix/issue-13-command-gates (pre-durable-store)
adapted to the SQLite queue from #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Val Alexander <bunsthedev@gmail.com>
Copilot AI review requested due to automatic review settings July 7, 2026 04:37
@BunsDev BunsDev merged commit 34d4d23 into main Jul 7, 2026
2 checks passed
@BunsDev BunsDev deleted the fix/issue-13-command-gates-durable branch July 7, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens the maintainer-command permission model (issue #13) in the GitHub App adapter by ensuring that “adapter-only” command surfaces (cancel + memory acknowledgements) cannot mutate queued work or reveal gated acknowledgement text until the worker has verified the commander has write access.

Changes:

  • Routes @… cancel into a new adapter-only TaskKind::CancelReviews so queued-review tombstoning happens only in the worker post-permission-gate (not in the webhook route).
  • Gates memory acknowledgement CommandReply tasks by carrying the commander through to the worker and declining below-write without leaking the ungated acknowledgement body.
  • Restricts insert-time review supersession to auto-triggered reviews (no commander) and adds post-gate supersession for authorized command reviews via supersede_queued_except.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/worker/src/lib.rs Executes CancelReviews and commander-carrying CommandReply tasks adapter-side with permission gating; adds post-gate review supersession and new worker tests.
crates/worker/src/brief.rs Adds safe-fallback briefing mappings for CancelReviews (though it should not normally reach briefing).
crates/webhook/src/routes.rs Stops route-side cancel tombstoning; emits CancelReviews tasks and gates remember/forget acks by carrying the commander.
crates/store/src/lib.rs Adds supersede_queued_except and makes insert-time supersession auto-review-only; adds store tests for command-gate behavior.
crates/github/src/{lib.rs,tasks.rs} Extends task kind enum and surface naming to include CancelReviews.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/worker/src/lib.rs
Comment on lines +1183 to +1199
async fn commander_below_write(
api_base_url: &str,
orchestration: &str,
task: &Task,
) -> Result<bool> {
let Some(commander) = &task.commander else {
return Ok(false);
};
let permission = repo::get_collaborator_permission_with_base_url(
api_base_url,
orchestration,
&task.repo_owner,
&task.repo_name,
commander,
)
.await?;
Ok(!matches!(permission.as_str(), "admin" | "maintain" | "write"))
Comment thread crates/worker/src/lib.rs
Comment on lines +2617 to +2626
fn permission_mock(login: &str, permission: &str) -> Mock {
Mock::given(method("GET"))
.and(path(format!(
"/repos/OpenCoven/demo/collaborators/{login}/permission"
)))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({ "permission": permission })),
)
}
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