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.
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:
- Approvals — kind:46030 grants / 46031 denies with tags
["t","git-merge"] ["r",<repo_id>] ["x",<head_oid>]; N distinct signers qualifying by bound-channel role (default: admin) or allowlist; any qualifying veto refuses.
- CI evidence — kind:1630 events (
["t",<context>] ["l","success"|"failure"] + the same r/x binding); latest attestation per required context from listed attestor pubkeys must be success.
- 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.
Context
VISION_PROJECTS.mdlists 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-coordinatoron our fork.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:
["t","git-merge"] ["r",<repo_id>] ["x",<head_oid>]; N distinct signers qualifying by bound-channel role (default: admin) or allowlist; any qualifying veto refuses.["t",<context>] ["l","success"|"failure"]+ the samer/xbinding); latest attestation per required context from listed attestor pubkeys must be success.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:refover 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_APPROVAL_GRANT/DENY(46030/46031) with at=git-mergescope, 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?require-approvaltransport enforcement? We see value in both existing — transport counting as defense-in-depth, the coordinator for the richer predicates (CI, disposition, vetoes).buzz-cli'sBuzzClientis private; if a shared client were extracted intobuzz-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.