The asker rename (d29e1426) let main's subject, which names no asker, take only a build and a run, so a holder refused the
checker's check asks ("the kind check is not taken on main's subject"), and seven of the checker's eight integration tests
failed. Until main's subject goes it also takes the checker's check and recheck; a step there is still refused.
Builds: design 47 G A-184, §543
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
151 lines
5.8 KiB
Go
151 lines
5.8 KiB
Go
package steprun
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
buildrun "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildrun"
|
|
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
|
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
|
)
|
|
|
|
// **A holder refuses a kind its asker may not ask** (design 47 G §543, A-163, A-170). An ask rides
|
|
// `mesh.seat.<seat>.accept.<queue>.<asker>`, the asker the asking seat's own name, and its kind stays in its body. The
|
|
// holder takes the asker from the subject and checks the kind against the callers the seat's definition lists for it (read
|
|
// from the seat list); on main's subject, which names no asker, it takes only a build and a run.
|
|
|
|
// KindOf is the word the seat definition gives an ask's kind: run for a run or a compose, else the ask's own kind.
|
|
func KindOf(ask sdkstep.StepAsk) string {
|
|
if ask.Run != nil || ask.Compose != nil {
|
|
return "run"
|
|
}
|
|
return ask.Kind
|
|
}
|
|
|
|
// CallersFromDefinitions is a Puller.Callers: the seats the definition of language's build seat lists as callers of an
|
|
// accepted kind, from the seat list's definitions (seatlist.Reader.Definitions). found is false when the definition or the
|
|
// kind is not held yet. A caller entry is `seat:<name>`.
|
|
func CallersFromDefinitions(language string, definitions func() map[string]seatdef.Definition) func(kind string) (callers []string, found bool) {
|
|
return func(kind string) ([]string, bool) {
|
|
seat, err := sdkstep.BuildSeat(language)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
def, ok := definitions()[seat]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
var out []string
|
|
found := false
|
|
for _, m := range def.Accepts {
|
|
if m.Name != kind {
|
|
continue
|
|
}
|
|
found = true
|
|
if m.Callers != nil {
|
|
for _, e := range m.Callers.List {
|
|
if name, ok := strings.CutPrefix(e, "seat:"); ok {
|
|
out = append(out, name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out, found
|
|
}
|
|
}
|
|
|
|
// askerRefusal is why an ask must not be taken from its subject's asker: "" when it may be, and judged false when the seat
|
|
// list does not say yet (the ask is given back, not refused).
|
|
func (p *Puller) askerRefusal(msg *Pulled, ask sdkstep.StepAsk) (why string, judged bool) {
|
|
if msg.Subject == "" {
|
|
return "", true // a source that gives no subject (a test's)
|
|
}
|
|
_, _, asker, err := sdkstep.ParseAskSubjectBy(msg.Subject)
|
|
if err != nil {
|
|
return fmt.Sprintf("the ask came on %s, which is no accept subject of this seat", msg.Subject), true
|
|
}
|
|
kind := KindOf(ask)
|
|
if asker == "" {
|
|
// Main's subject takes a build, a run and, until it goes, the checker's check and recheck (design 47 G A-184, §543).
|
|
if (ask.Build != nil && ask.Kind == sdkstep.AskDelivery) || kind == "run" || kind == sdkstep.AskCheck || kind == sdkstep.AskRecheck {
|
|
return "", true
|
|
}
|
|
return fmt.Sprintf("the kind %s is not taken on main's subject, which takes only a build, a run and the checker's check and recheck (asker: none named)", kind), true
|
|
}
|
|
if p.Callers == nil {
|
|
return "", true
|
|
}
|
|
callers, found := p.Callers(kind)
|
|
if !found {
|
|
return "", false
|
|
}
|
|
if !slices.Contains(callers, asker) {
|
|
return fmt.Sprintf("the kind %s is not one %s may ask: its callers are %s", kind, asker, strings.Join(callers, ", ")), true
|
|
}
|
|
return "", true
|
|
}
|
|
|
|
// refuseAsker ends an ask whose asker may not ask its kind: its step key says failed with the reason, naming the kind and the
|
|
// asker, step-ended says failed, and the message is dropped.
|
|
func (p *Puller) refuseAsker(msg *Pulled, ask sdkstep.StepAsk, why string) {
|
|
key, err := sdkstep.AskStepKey(keyAsker(msg), p.Node, ask.ID)
|
|
if err == nil {
|
|
at := now().UTC()
|
|
step := sdkstep.Step{ID: ask.ID, Tool: ask.Kind + " ask", Node: p.Node, State: sdkstep.StepFailed, Started: at, Ended: at,
|
|
Reason: "error: " + why, Owner: buildrun.Owner, Run: int(msg.Delivered)}
|
|
err = writeEnd(p.Put, key, step)
|
|
}
|
|
if err != nil {
|
|
p.logf("the ask %s of the wrong asker cannot be answered on %s, so it is given back: %v", ask.ID, p.Node, err)
|
|
_ = msg.Nak()
|
|
return
|
|
}
|
|
p.logf("the ask %s is refused on %s: %s", ask.ID, p.Node, why)
|
|
if ask.Step == nil {
|
|
p.sayEnded(msg, ask, sdkstep.OutcomeFailed, why, nil, "")
|
|
} else if end, err := sdkstep.EndOf(ask, p.Node, sdkstep.OutcomeFailed); err == nil {
|
|
// A pipeline step's end names its pipeline, step, commit and fingerprint (its own form, §602).
|
|
end.Said, end.At, end.Asker = why, now().UTC(), askerOf(msg, ask)
|
|
if body, err := sdkstep.EncodeStepEnded(end); err == nil {
|
|
p.say("step-ended", json.RawMessage(body), "step-ended."+ask.ID)
|
|
}
|
|
}
|
|
_ = msg.Term()
|
|
}
|
|
|
|
// giveBack hands an ask back to be judged again a little later, once the seat list says more.
|
|
func (p *Puller) giveBack(msg *Pulled, ask sdkstep.StepAsk) {
|
|
p.logf("the ask %s is given back on %s: the seat list holds no definition of its kind yet", ask.ID, p.Node)
|
|
if msg.NakAfter != nil && msg.NakAfter(DefaultPullWait) == nil {
|
|
return
|
|
}
|
|
_ = msg.Nak()
|
|
}
|
|
|
|
// SeatListCallers is a Puller.Callers that follows the seat list: it starts the reader once the runtime serves
|
|
// (seatlist.Start through the runtime's own way in) and answers from its definitions, found false until it has read them.
|
|
// A reader that cannot start is said and the asks are given back until it can (design 47 G §543, A-163).
|
|
func SeatListCallers(language string, logf func(format string, args ...any)) func(kind string) ([]string, bool) {
|
|
var reader atomic.Pointer[seatlist.Reader]
|
|
go func() {
|
|
<-stdio.Served()
|
|
r, err := seatlist.Start(seatlist.Config{Bus: seatlist.StdioBus(), Bootstrap: seatlist.Bootstrapped()})
|
|
if err != nil && logf != nil {
|
|
logf("following the seat list: %v", err)
|
|
}
|
|
reader.Store(r)
|
|
}()
|
|
return func(kind string) ([]string, bool) {
|
|
r := reader.Load()
|
|
if r == nil {
|
|
return nil, false
|
|
}
|
|
return CallersFromDefinitions(language, r.Definitions)(kind)
|
|
}
|
|
}
|