Repository navigation
feat(actions): typed merge/ready/CI/PR/push actions gated by multi_bot switches - #6
Merged
Merged
Conversation
… and publishing switchable multi_bot.policy carries validated switches into the run request and the packaged policy documents them. Public defaults stay conservative (model_class independence, unknown author holds, no draft readying, no CI trigger, no publishing, no fix PRs); an instance opts in per key. Unknown keys or wrong types fail closed before launch. The switches loosen no hold, lock, exclude_writes or permission-ceiling rule. Two tests in test_multibot_resources.py already fail on this branch before the change under Windows checkouts (line endings, path comparison); not touched here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G793TMH12ptWX1f6PYcZU
…t switches api.request can only be scoped by endpoint prefix; "repos/<owner>/" would allow every write in every repository, and draft-ready needs GraphQL. Add six narrow actions instead: pr.merge (fresh read, exact sha, default base, not a draft, mergeable_state clean, configured merge account, sha-bound PUT, merged readback), pr.ready (GraphQL markPullRequestReadyForReview at the exact draft head), actions.dispatch (active workflow only), actions.rerun (completed, non-fork run of the exact head), pr.create (bot-prefixed branch onto the default branch) and git.push_branch (no force, bot prefix, never the default branch, ls-remote readback). They are in the default ceiling but never enabled by default, and each also needs its multi_bot switch, so a permission alone opens nothing. The MULTI-BOT run contract now reports effective_enabled. New `act` command for one action through the existing preview/apply/expect-plan contract. The policy prompt routes merges, ready, CI triggers and fix PRs through these actions and treats a job waiting for a (self-hosted) runner as HOLD, never green. Prompt and JSON resources are pinned to LF so the bound policy hash no longer depends on core.autocrlf. Ticket: T-20261017-353754430 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4ZiDnWpjFvgsu8AjdPgbN
lukisch
pushed a commit
that referenced
this pull request
Oct 17, 2026
…t switches (#6) (#7) * feat(multibot): make review independence, draft readiness, CI trigger and publishing switchable multi_bot.policy carries validated switches into the run request and the packaged policy documents them. Public defaults stay conservative (model_class independence, unknown author holds, no draft readying, no CI trigger, no publishing, no fix PRs); an instance opts in per key. Unknown keys or wrong types fail closed before launch. The switches loosen no hold, lock, exclude_writes or permission-ceiling rule. Two tests in test_multibot_resources.py already fail on this branch before the change under Windows checkouts (line endings, path comparison); not touched here. Claude-Session: https://claude.ai/code/session_013G793TMH12ptWX1f6PYcZU * feat(actions): typed merge/ready/CI/PR/push actions gated by multi_bot switches api.request can only be scoped by endpoint prefix; "repos/<owner>/" would allow every write in every repository, and draft-ready needs GraphQL. Add six narrow actions instead: pr.merge (fresh read, exact sha, default base, not a draft, mergeable_state clean, configured merge account, sha-bound PUT, merged readback), pr.ready (GraphQL markPullRequestReadyForReview at the exact draft head), actions.dispatch (active workflow only), actions.rerun (completed, non-fork run of the exact head), pr.create (bot-prefixed branch onto the default branch) and git.push_branch (no force, bot prefix, never the default branch, ls-remote readback). They are in the default ceiling but never enabled by default, and each also needs its multi_bot switch, so a permission alone opens nothing. The MULTI-BOT run contract now reports effective_enabled. New `act` command for one action through the existing preview/apply/expect-plan contract. The policy prompt routes merges, ready, CI triggers and fix PRs through these actions and treats a job waiting for a (self-hosted) runner as HOLD, never green. Prompt and JSON resources are pinned to LF so the bound policy hash no longer depends on core.autocrlf. Ticket: T-20261017-353754430 Claude-Session: https://claude.ai/code/session_01C4ZiDnWpjFvgsu8AjdPgbN --------- Co-authored-by: Lukas Geiger <lukas@um-bruch.org> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
Problem: the core knows three write actions (
issue.add_labels,issue.comment,api.request). Merging, marking a draft ready, triggering CI and opening fix PRs therefore stay impossible even when an instance setsmulti_bot.merge_enabledand the policy switches. Scopingapi.requestby endpoint prefix is far too coarse for merges (repos/<owner>/allows every write in every repository), draft-ready exists only in GraphQL, and a branch push is not an API call at all.Change:
typed_actions.py:pr.merge,pr.ready,actions.dispatch,actions.rerun,pr.create,git.push_branch. Each re-reads the target before writing, binds to the exact reviewedshawhere that applies, and reads the result back (the readback goes into the receipt).pr.merge: open, exact head, default base (not stacked), not a draft,mergeable_state == "clean"(so an admin token cannot bypass required checks), configured merge account;PUT …/mergewithsha;mergedread back.pr.ready: exact draft head, default base, GraphQLmarkPullRequestReadyForReview, readback.actions.rerun: completed run of the exact head and not from a fork.actions.dispatch: active workflows only.pr.create/git.push_branch: bot branch prefix (typed_actions.push_branch_prefix, defaultbot/), never the default branch, no force or+refspec, origin must be the target repository,ls-remotereadback.enabled. Each kind also needs itsmulti_botswitch (merge_enabled,policy.draft_*,policy.trigger_missing_ci,policy.open_fix_prs), so a permission alone opens nothing. The MULTI-BOT run contract reportseffective_enabled.act <kind> --params '{…}'that uses the existing preview →--apply --expect-plancontract..gitattributespins the prompt and JSON resources to LF, so the bound policy hash no longer depends oncore.autocrlf.Stacking: based on
feat/multibot-resources(#5) plus the previously unpushed commit539b0d0(policy switches), which this change depends on. Merge only after #5.Tests (Windows, Python 3.12, outside OneDrive):
ruff check .andruff format --check .clean;pytest -q→ 190 passed, 2 skipped, 5 failed. The 5 failures are the existingtests/test_multibot_windows.pystarter tests, which also fail on the base branch on Windows (the namespace stub is shadowed by the installed package; see the review on #5). CI runs only on Ubuntu, where they are skipped. 16 new tests intests/test_typed_actions.pycover: default denial, switch gating, static parameter checks, sha-bound merge with readback, refusal on moved head / blocked / unknown / stacked / draft / closed / foreign identity / missing readback, GraphQL ready, fork/foreign-head/running rerun refusal, inactive workflow, PR base, push without force and with origin check, default-branch protection,effective_enabled, CLI preview → apply.Live read-only check:
GhCliTransport.request("POST", "graphql", …)with a read query works throughgh api. A preview against a real instance layer correctly deniespr.merge(not enabled there).Not included: activating the actions in any instance configuration. An independent review is required before that (author: this session).
Ticket: T-20261017-353754430
🤖 Generated with Claude Code
https://claude.ai/code/session_01C4ZiDnWpjFvgsu8AjdPgbN