package api
// NotifyRedTrunk tells a repository's owners that a pipeline failed on
// a check-in of its default branch. The caller (the CI poller, on the
// lease holder) decides the run failed and was not cancelled; this
// decides whether the check-in is on the default branch and who hears.
//
// The owners are the deed holder and whoever owns the default branch,
// once each. It rides the landing family because a red trunk is the
// same question a landing asks — may work merge here — and so the one
// preference a person already has for "trunk wants my attention"
// answers for it too, with no fifth family in the matrix.
//
// The idempotency key is the check-in and the pipeline, not the run: a
// re-run of the same pipeline on the same check-in that fails again is
// the same news.
func (d Deps) NotifyRedTrunk(repo, checkin, pipeline, runID string) {
if d.Repos == nil {
return
}
r, ok := d.Repos.Get(repo)
if !ok || r.Index == nil {
return
}
branch := r.Index.DefaultBranch()
on, err := r.Index.BranchOf([]string{checkin})
if err != nil || on[checkin] != branch {
return
}
var owners []string
if d.Owners != nil {
if o, err := d.Owners.OwnerOf(repo); err == nil {
owners = append(owners, o)
}
}
_, branchOwners := d.protectionOf(repo, branch)
told := map[string]bool{"": true}
short := checkin
if len(short) > 12 {
short = short[:12]
}
for _, who := range append(owners, branchOwners...) {
if told[who] {
continue
}
told[who] = true
d.fireNotify(NotifyEvent{
Workflow: "quarry-landing", To: who,
Payload: map[string]any{
"repo": repo, "from": "ci",
"summary": pipeline + " failed on " + branch + " at " + short,
"link": d.link("/r/" + repo + "/ci/run/" + runID),
},
TxnID: "ci:red:" + repo + ":" + checkin + ":" + pipeline + ":" + who,
})
}
}