ADR-0045: palantir/policy-bot, required through a repository ruleset, decides which agent PRs merge
Status: Accepted Date: 2026-09-27 Deciders: Smana Related Spec: SP3 dark factory §5
Context
main requires 0 approving reviews (a solo maintainer cannot approve his own PRs), so nothing in
GitHub stops an agent PR from merging once CI is green. D4 lets policy-defined low-risk classes
merge on their own and requires a human for the rest. That boundary must be enforced at merge time,
by code the agents cannot change, and must fail towards more review.
Decision Drivers
- Merge authority read from
main, never from the PR under evaluation - Path, size, author, contributor and branch predicates; “no rule matched” must block
- Review invalidation on push and forged statuses already handled
- A cluster torn down routinely must not block every merge
Considered Options
Option 1: policy-bot, its status required by a ruleset with policy-bot’s App as expected source
Pros:
- Reads the policy from the target branch; every predicate the classes need;
errorwhen all rules skip - The expected source makes the status unforgeable by
statuses: write; a ruleset bypass keeps the owner and Renovate working when policy-bot is down
Cons:
- A public webhook; the check is absent while the cluster is down
Option 2: Required reviews, or rulesets with CODEOWNERS alone
Pros:
- Native
Cons:
- A solo maintainer cannot approve his own PRs; no size, author or contributor predicates; no fail-closed “unmatched”
Option 3: A custom Actions check, Prow/tide, Mergify or Kodiak
Pros:
- Anything we write; rich SaaS rules; label-driven automation
Cons:
- Re-implements invalidation and forgery handling; a whole Prow; SaaS-first (D2); Kodiak has no path predicates and its label auto-approve is the labels-as-trust trap
Decision Outcome
Chosen option: “Option 1”
Rationale: policy-bot is the only maintained OSS engine that evaluates the policy from the target branch with every predicate the classes need, and a ruleset with an expected source makes its status the unforgeable input GitHub’s native auto-merge waits for.
Consequences
Positive
- An agent PR touching a gate path shows
errorand cannot merge, even with an approval - Every control fails towards more human review
Negative
- policy-bot holds
statuses: writeand is the most sensitive component: its own namespace, store and default-deny policy, a digest-pinned image, HMAC webhooks - While the cluster is down, agent PRs wait; the owner and Renovate bypass
- Arming and reverts need a second App,
ogenki-agent-merger, the only App that bypasses theagent-mergeruleset onmainand the revert branches; its key, held by the factory alone, is the one that can merge. The factory’s own App stays comment-and-label only
Neutral
- Classes are promoted from shadow to live only by a human-authored PR, on evidence
Implementation Notes
tooling/base/policy-bot/ (namespace merge-gate), .policy.yml, .github/rulesets/agent-merge-gate.json
applied by scripts/ops/github/agent-merge-gate-ruleset.sh, .github/rulesets/agent-merge.json applied by
scripts/ops/github/agent-merge-ruleset.sh, scripts/ci/check-policy-gate-coverage.sh.