0033: Merge approval is a Slack reaction from a named approver, read by polling; any rejection wins
- Status: Accepted (backfilled 2026-10-06: records a decision already built)
- Date: 2026-10-06
- Related: 0011 (Orion lands work), 0015 (Orion is the gate under a merge ref)
Contextβ
Orion lands its own work (0011). Some teams want a person to approve a merge first. The approval has to be easy to give from a phone, impossible for Orion to give itself, and readable by a process that has no public endpoint: Orion is a CLI, often on a laptop.
Decisionβ
Orion posts an approval request in the project's Slack channel, mentioning
the approvers, and reads the answer back on each watch pass
(internal/collect/approval.go). The rules:
- Who counts: only people listed in
slack.merge_approvers. Empty means nobody, never everybody: being in a channel is not authority. - What counts: a reaction on the request message (approve:
white_check_mark,heavy_check_mark,+1,shipit,rocket; reject:x,no_entry,no_entry_sign,-1,hand), or a reply in its thread with an approving or rejecting word. Both are accepted because a phone user reacts and a laptop user types. - The bot's own reactions are excluded, so Orion cannot approve its own request.
- A rejection beats every approval.
- Polled, not pushed. Each pass reads the message; there is no listener.
When it applies: with batch integration, a green batch waits for approval
only when slack.merge_approvers names people; otherwise it lands. Without
batch integration, slack.require_approval turns the gate on for each
ticket's pull request; with it off, Orion reports that checks pass and waits
for a person to merge on GitHub.
The same reaction is how a recommendation becomes a decision
(internal/decide): one approval mechanism, not two that drift apart.
Consequencesβ
- Approving is one tap in Slack; nothing new to install or secure.
- An approval arrives on the next pass, not instantly.
- Reading reactions needs the
reactions:readscope, and thread replieschannels:historyorgroups:history.
Alternatives rejectedβ
- A Slack app with a listener (socket mode). A long-running process with tokens to protect, a lot of attack surface for a single-user supervisor.
- Approval on GitHub. Fine for people at a desk, but it does not reach the phone, and it duplicates what branch protection already does.
- Anyone in the channel may approve. Channel membership is not authority.