Merge coordinator: reference implementation + design questions (policy-gated protected-ref merges)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Context
`VISION_PROJECTS.md` lists the **merge coordinator** among the designed-but-unbuilt forge roles: the agent that "runs the merge train, requests human sign-off." We've been self-hosting a relay as the system of record for our org's repos and needed that role now, so we built a reference implementation and would like to converge it with wherever the project wants this to go: [`buzz-merge-coordinator` on our fork](https://github.com/happyvertical/buzz/tree/feat/merge-coordinator/crates/buzz-merge-coordinator).
## Shape
One small binary (no daemon state, no unsafe, ~600 lines + 15 predicate tests) that gates a protected-ref fast-forward on three fail-closed predicates, all bound to the **exact head OID**:
1. **Approvals** — kind:46030 grants / 46031 denies with tags `["t","git-merge"] ["r",] ["x",]`; N distinct signers qualifying by bound-channel role (default: admin) or allowlist; any qualifying veto refuses.
2. **CI evidence** — kind:1630 events (`["t",] ["l","success"|"failure"]` + the same `r`/`x` binding); latest attestation per required context from listed attestor pubkeys must be success.
3. **External disposition** (optional) — `POST {repo_id, head_oid, dst_ref}` to a configured URL must return `{"allow":true}`; the seam for an external tracker to hold merge-authority state without the coordinator knowing anything about it.
Then it pushes `oid:ref` over smart HTTP with its own identity (a bound-channel admin), posts kind:1631 + a human-readable decision to the bound channel, and refusals name the failed predicate. The relay's transport protections (`push:` role floor, `no-force-push`) stay as the independent layer underneath — we've been running that combination live.
## Design questions for maintainers
- **Kind/tag conventions**: we reused `KIND_APPROVAL_GRANT/DENY` (46030/46031) with a `t=git-merge` scope, and 1630 for CI results per the VISION merge-flow sketch, with single-letter tags (`r`/`x`/`t`/`l`) so evidence is server-side filterable with standard filters. Is that the vocabulary you'd want, or is a dedicated kind range planned for review approvals / CI attestations?
- **Placement**: workspace crate (as in the branch) vs folding the logic into the relay behind `require-approval` transport enforcement? We see value in both existing — transport counting as defense-in-depth, the coordinator for the richer predicates (CI, disposition, vetoes).
- **Client reuse**: the crate carries a self-contained ~150-line NIP-98 client because `buzz-cli`'s `BuzzClient` is private; if a shared client were extracted into `buzz-sdk`, that module disappears.
Happy to adapt the branch to your conventions and open a PR — or treat this as field input if you'd rather build it differently.
Contributor guide
Assessment
This issue has not been assessed yet.