Files
mesh-delivery/modules/mesh-delivery-forge-bridge/internal/bridge/acts.go
T
jschoubben efca168c55
mesh/repo-check head efca168c5522 merged into trunk 0e372c87f1df beside mesh-catalog#457@896bd883 +1 more — pass: its merge-check.sh passed
mesh/merge-gate head efca168c5522 merged into trunk 0e372c87f1df — pass: group feat/r4e composed: every machine composes with the change as it did without …
mesh/delivery covered after its stop: a later walk that contains it was delivered: plan-1792707174627079192 (novox/mesh-delivery at b714eab9 into main, d…
mesh/delivery-group group feat/r4e closed: a member was closed before the group merged
mesh/check failed: unit: ..test: node:internal/modules/esm/resolve:989 | efca168c5522 on b714eab995a5 | unit 2092 run, 0 kept, 3 skipped | integration…
Stop the forge bridge performing mesh/check: the forge module writes it (R2-1)
R4e round 2 finding R2-1, ADR 0521: nothing in the delivery plan orders the
forge module before the bridge, and the forge module's durable consumer keeps
its deliver policy when its filters change, so a check.status emitted before
it consumed the event was lost. The forge module now watches the checker's
owed state and writes mesh/check itself, once per revision.

The bridge emits no check.status, declares no emits, and neither reads the
checker's owed nor hears its stream; OwedAct still refuses mesh/check. The
integration test shows the bridge calls no tool and publishes nothing of its
own when the checker owes mesh/check.
2026-10-22 21:40:09 +02:00

159 lines
6.2 KiB
Go

package bridge
import (
"fmt"
"slices"
"strings"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// Call is a call of a forge tool: the tool by its manifest key and its arguments.
type Call struct {
Tool string
Args map[string]any
}
// Act is one thing the bridge does on the forge for a key of the gate: the act's name (an owed key's act, `status=<context>`,
// `view`, `note`, `comment.<seq>`, or `merge`), the pull request and head it is on (Number 0 for a commit no pull
// request names) and the forge call that performs it.
type Act struct {
Name string
Repository string
Number int
Head string
Call Call
}
// The forge's tools, by manifest key; their arguments are the ones of mesh-catalog modules/gitea: delivery.ts
// commitStatusTool (owner, repo, sha, context, state, description, target_url), tools/index.ts gitea_delivery_view
// (owner, repo, number, body), gitea_note_append (owner, repo, sha, ref, line), gitea_add_comment (owner, repo,
// number, body), merge.ts gitea_merge_pull_request (owner, repo, number, head, approved_heads).
const (
commitStatusTool = "gitea.gitea_commit_status"
deliveryViewTool = "gitea.gitea_delivery_view"
noteAppendTool = "gitea.gitea_note_append"
addCommentTool = "gitea.gitea_add_comment"
mergePullTool = "gitea.gitea_merge_pull_request"
)
// MergeAct is the name of the act of merging a pull request.
const MergeAct = "merge"
func ownerRepo(repository string) (owner, repo string) {
owner, repo, _ = strings.Cut(repository, "/")
return owner, repo
}
// OwedAct turns an owed key and its value (mesh-sdk go/delivery OwedKey, EncodeOwed) into the forge call that performs
// it. A key that is no owed key, a value that is no owed act, a value whose act is not the key's, and a view or a comment
// on no pull request are errors, each saying why.
func OwedAct(key string, value []byte) (Act, error) {
repository, number, head, name, err := delivery.ParseOwedKey(key)
if err != nil {
return Act{}, err
}
o, err := delivery.DecodeOwed(value)
if err != nil {
return Act{}, fmt.Errorf("the owed act %s: %w", key, err)
}
kind, context, err := delivery.ParseOwedAct(name)
if err != nil {
return Act{}, err
}
if o.Act != kind {
return Act{}, fmt.Errorf("the owed act %s says %q, not the %q its key names", key, o.Act, kind)
}
owner, repo := ownerRepo(repository)
a := Act{Name: name, Repository: repository, Number: number, Head: head}
needsPull := func() error {
if number <= 0 {
return fmt.Errorf("the owed %s %s is on a commit no pull request names, and it needs the pull request", kind, key)
}
return nil
}
switch kind {
case delivery.OwedStatus:
if o.Context != context {
return Act{}, fmt.Errorf("the owed status %s says the context %q, not the %q its key names", key, o.Context, context)
}
if o.Context == delivery.StatusContextCheck {
// No tool writes mesh/check (R4e finding K1), and the bridge performs no act of it: the forge module writes it from
// the checker's state mesh-delivery-checker.owed, read by a watch (ADR 0521, finding R2-1).
return Act{}, fmt.Errorf("the owed status %s is %s, which the bridge does not perform: the forge module writes it from mesh-delivery-checker.owed",
key, delivery.StatusContextCheck)
}
a.Call = Call{Tool: commitStatusTool, Args: map[string]any{"owner": owner, "repo": repo, "sha": head, "context": o.Context,
"state": o.State, "description": o.Text, "target_url": o.Target}}
case delivery.OwedView:
if err := needsPull(); err != nil {
return Act{}, err
}
a.Call = Call{Tool: deliveryViewTool, Args: map[string]any{"owner": owner, "repo": repo, "number": number, "body": o.Text}}
case delivery.OwedNote:
a.Call = Call{Tool: noteAppendTool, Args: map[string]any{"owner": owner, "repo": repo, "sha": head, "ref": o.Ref, "line": o.Text}}
case delivery.OwedComment:
if err := needsPull(); err != nil {
return Act{}, err
}
a.Call = Call{Tool: addCommentTool, Args: map[string]any{"owner": owner, "repo": repo, "number": number, "body": o.Text}}
}
return a, nil
}
// MergeActs turns the value of a merges key (mesh-sdk go/delivery EncodeMerge) into the merges to perform: one for each
// pull request in the state merging, in the order the value names them, at the head it expects and with the approved
// heads of every member of the group, `<owner>/<repo>@<full sha>` sorted (the merge tool's approved_heads). A pull
// request merged or refused already gets none. A value with no approved heads is an error: the forge's merge tool
// needs them for a group.
func MergeActs(value []byte) ([]Act, error) {
m, err := delivery.DecodeMerge(value)
if err != nil {
return nil, err
}
var handed []delivery.MergePull
for _, p := range m.Pulls {
if p.State == delivery.MergeMerging {
handed = append(handed, p)
}
}
if len(handed) == 0 {
return nil, nil
}
if len(m.Approved) == 0 {
return nil, fmt.Errorf("the merge of the group %s names no approved heads, so the forge's merge tool has nothing to merge at", m.Group)
}
approved := make([]string, 0, len(m.Approved))
for key, sha := range m.Approved {
repository, _, ok := strings.Cut(key, "@")
if !ok {
return nil, fmt.Errorf("the approved head %q of the group %s is not <repository>@<number>", key, m.Group)
}
approved = append(approved, repository+"@"+sha)
}
slices.Sort(approved)
var acts []Act
for _, p := range handed {
owner, repo := ownerRepo(p.Repository)
acts = append(acts, Act{Name: MergeAct, Repository: p.Repository, Number: p.Number, Head: p.Head.Head,
Call: Call{Tool: mergePullTool, Args: map[string]any{"owner": owner, "repo": repo, "number": p.Number, "head": p.Head.Head,
"approved_heads": approved}}})
}
return acts, nil
}
// RefusedMessage is the refused message the bridge writes on FORGE when the forge refuses an act: the act, the pull
// request and head it was on, and the forge's own words.
func RefusedMessage(a Act, words string) (Message, error) {
event, err := delivery.ForgeEvent(a.Repository, delivery.ForgeRefused)
if err != nil {
return Message{}, err
}
body, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeRefused, Repository: a.Repository, Number: a.Number,
Head: a.Head, Act: a.Name, Words: words})
if err != nil {
return Message{}, err
}
return Message{Event: event, Data: body}, nil
}