RN3: the runner asks the seat mesh-wants for the composition step and hears its step-ended
Place puts composition running, asked of mesh-wants at its accepted kind step with the pipeline's commit, fingerprint, revision and source; the hearer takes mesh-wants' step-ended as the build seats' ends. Builds: design 47 G §22, §24 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
builds "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshpipelinerunner"
|
||||
wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
nodebuildimage "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
||||
nodebuildnode "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
||||
@@ -25,6 +26,7 @@ type StepAsker interface {
|
||||
// acceptStep is where each build seat is sent its step, from its contract.
|
||||
var acceptStep = map[string]string{
|
||||
builds.Seat: builds.AcceptSubjectStep,
|
||||
wants.Seat: wants.AcceptSubjectStep,
|
||||
nodebuildgo.Seat: nodebuildgo.AcceptSubjectCheckStep,
|
||||
nodebuildnode.Seat: nodebuildnode.AcceptSubjectCheckStep,
|
||||
nodebuildimage.Seat: nodebuildimage.AcceptSubjectCheckStep,
|
||||
@@ -106,6 +108,14 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog
|
||||
if status == seat.StepResultStatusBypass {
|
||||
s.Outcome, s.Why = seat.StepResultOutcomeBypassed, "bypassed by the module's policy"
|
||||
}
|
||||
// Composition is the assigner's, an accepted kind step of the seat mesh-wants (§22): it is asked with the others,
|
||||
// running at once, and its end is heard from that seat as the build seats' are. Review and approval wait.
|
||||
if step == seat.StepResultStepComposition && status != seat.StepResultStatusBypass {
|
||||
s.Seat, s.Outcome, s.Started, s.Fingerprint = wants.Seat, seat.StepResultOutcomeRunning, &now, p.Fingerprint
|
||||
s.Ask = AskID(p.Pipeline, "", step)
|
||||
asks = append(asks, Asked{Seat: wants.Seat, ID: s.Ask, Ask: wants.StepRun{Branch: p.Branch, Commit: p.Commit, Fingerprint: p.Fingerprint,
|
||||
Module: p.Module, Pipeline: p.Pipeline, Revision: p.Revision, Source: p.Source, Step: wants.StepRunStepComposition}})
|
||||
}
|
||||
steps = append(steps, s)
|
||||
}
|
||||
return steps, asks
|
||||
|
||||
@@ -4,13 +4,26 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
builds "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshpipelinerunner"
|
||||
wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
)
|
||||
|
||||
func stepSeatOf(t *testing.T, name string) StepSeat {
|
||||
t.Helper()
|
||||
for _, bs := range StepSeats {
|
||||
if bs.Seat == name {
|
||||
return bs
|
||||
}
|
||||
}
|
||||
t.Fatalf("no step seat %s", name)
|
||||
return StepSeat{}
|
||||
}
|
||||
|
||||
type stepAsks struct {
|
||||
at []string
|
||||
ids []string
|
||||
@@ -105,10 +118,7 @@ func TestTheBuildHoldsTheApprovalWhateverThePolicyNames(t *testing.T) {
|
||||
// The seat mesh-builds' end of the build step is taken as the others are, held to its one entry's module, commit and
|
||||
// fingerprint; an end naming two entries is refused (A-97, A-99).
|
||||
func TestTheBuildStepsEndIsTakenOnItsOneEntry(t *testing.T) {
|
||||
bs := StepSeats[len(StepSeats)-1]
|
||||
if bs.Seat != builds.Seat {
|
||||
t.Fatalf("the last step seat is %s, want mesh-builds", bs.Seat)
|
||||
}
|
||||
bs := stepSeatOf(t, builds.Seat)
|
||||
h, store, _, _, _, _ := fixture()
|
||||
e := store[pkey]
|
||||
e.p.Steps = append([]seat.StepResult{{Step: seat.StepResultStepBuild, Status: seat.StepResultStatusRequired, Outcome: seat.StepResultOutcomeRunning,
|
||||
@@ -127,3 +137,38 @@ func TestTheBuildStepsEndIsTakenOnItsOneEntry(t *testing.T) {
|
||||
t.Error("an end of two entries is refused")
|
||||
}
|
||||
}
|
||||
|
||||
// Composition is asked of the seat mesh-wants with the other steps, running at once, and its end is heard from that seat
|
||||
// (design 47 G §22, §24): a pipeline whose policy names it places it running with its own ask, a bypassed one asks no one.
|
||||
func TestCompositionIsAskedOfTheAssignerAndItsEndIsTaken(t *testing.T) {
|
||||
reg, entries := placeFixture()
|
||||
reg.Policy = catalog.Policy{Steps: catalog.StepStatuses{Lint: "bypass", Unit: "bypass", Composition: "required", Approval: "required"}}
|
||||
p := seat.Pipeline{Pipeline: "main.4.app", Branch: "main", Module: "app", Commit: "c1", Fingerprint: "src2:aa", Revision: 7, Source: 4}
|
||||
steps, asked := Place(p, reg, entries, time.Unix(1, 0), "the-runner")
|
||||
var comp seat.StepResult
|
||||
for _, s := range steps {
|
||||
if s.Step == seat.StepResultStepComposition {
|
||||
comp = s
|
||||
}
|
||||
}
|
||||
if comp.Outcome != seat.StepResultOutcomeRunning || comp.Seat != wants.Seat || comp.Ask == "" {
|
||||
t.Fatalf("composition = %+v, want running, asked of mesh-wants", comp)
|
||||
}
|
||||
var got *Asked
|
||||
for i := range asked {
|
||||
if asked[i].Seat == wants.Seat {
|
||||
got = &asked[i]
|
||||
}
|
||||
}
|
||||
r, ok := got.Ask.(wants.StepRun)
|
||||
if got == nil || !ok || got.ID != comp.Ask || acceptStep[wants.Seat] != wants.AcceptSubjectStep || r.Step != "composition" ||
|
||||
r.Commit != "c1" || r.Fingerprint != "src2:aa" || r.Pipeline != "main.4.app" || r.Revision != 7 || r.Source != 4 || r.Module != "app" {
|
||||
t.Fatalf("the composition ask = %+v", got)
|
||||
}
|
||||
// Its end, from the seat's own subject, is taken like the others, a node's warning becoming the step's why.
|
||||
bs := stepSeatOf(t, wants.Seat)
|
||||
ev, err := bs.Decode([]byte(`{"id":"` + comp.Ask + `","pipeline":"main.5.app","step":"composition","module":"app","commit":"c1","fingerprint":"src2:aa","node":"n1","outcome":"passed","warnings":["n2 asleep"]}`))
|
||||
if err != nil || ev.Said != "n2 asleep" || ev.Outcome != "passed" || ev.Module != "app" {
|
||||
t.Fatalf("decoded end = %+v, %v", ev, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
builds "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshpipelinerunner"
|
||||
wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
nodebuildimage "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
||||
nodebuildnode "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
||||
@@ -33,6 +34,23 @@ var StepSeats = []StepSeat{
|
||||
{Seat: nodebuildimage.Seat, Prefix: strings.TrimSuffix(nodebuildimage.SubjectStepEndedAll, "*")},
|
||||
// The seat mesh-builds answers the build step of a candidate, one entry named by its fingerprint (A-97, A-99).
|
||||
{Seat: builds.Seat, Prefix: strings.TrimSuffix(builds.SubjectStepEndedAll, "*"), Decode: decodeBuildEnded},
|
||||
// The seat mesh-wants answers the composition step (§22, §24).
|
||||
{Seat: wants.Seat, Prefix: strings.TrimSuffix(wants.SubjectStepEndedAll, "*"), Decode: decodeWantsEnded},
|
||||
}
|
||||
|
||||
// decodeWantsEnded reads the composition step's end of the seat mesh-wants as the form the hearer holds each end to; a
|
||||
// warning of a node asleep (a pass with a warning, ADR 0593 §9) is the step's `why` when the end says nothing else.
|
||||
func decodeWantsEnded(raw []byte) (StepEnded, error) {
|
||||
var w wants.StepEnded
|
||||
if err := json.Unmarshal(raw, &w); err != nil {
|
||||
return StepEnded{}, err
|
||||
}
|
||||
said := w.Said
|
||||
if said == "" && len(w.Warnings) > 0 {
|
||||
said = strings.Join(w.Warnings, "; ")
|
||||
}
|
||||
return StepEnded{V: w.V, ID: w.ID, Pipeline: w.Pipeline, Step: w.Step, Module: w.Module, Commit: w.Commit, Fingerprint: w.Fingerprint,
|
||||
Node: w.Node, Outcome: w.Outcome, Said: said, Rerun: w.Rerun, By: w.By, Started: w.Started, Ended: w.Ended}, nil
|
||||
}
|
||||
|
||||
// decodeBuildEnded reads the build step's end of the seat mesh-builds as the form the hearer holds each end to: its one
|
||||
|
||||
Reference in New Issue
Block a user