A-297: the runner's step ask id takes the usual ask id form, so the holders accept it
The runner's step ask id was `step-<24 hex>`; every build seat holder's CheckAskID refuses it (base 36 milliseconds and six hex digits), so no placed step could be accepted. AskID now opens with base 36 of the pipeline's start time in milliseconds and ends with six hex digits of the sha256 of the pipeline, the artifact and the step: the same placement makes the same id at every start. A rerun uses its own time. Builds: design 47 G A-297, A-232, A-111 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -42,7 +42,7 @@ require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/minio/highwayhash v1.0.4 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.8.2 // indirect
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -53,10 +54,21 @@ var moduleSteps = []string{seat.StepResultStepComposition, seat.StepResultStepRe
|
||||
// kindImage is the form of an acting rule's `on` naming an artifact kind (§324).
|
||||
const kindImage = "kind: image"
|
||||
|
||||
// AskID is a step's ask id, the same for the same pipeline, artifact and step, so a start asked again asks nothing new.
|
||||
func AskID(pipeline, artifact, step string) string {
|
||||
// AskID is a step's ask id in the usual ask id form the holders accept (buildstep.CheckAskID): base 36 of started in
|
||||
// milliseconds, then six hex digits of the sha256 of the pipeline, the artifact and the step, so the same placement makes
|
||||
// the same id at every start and the bus dedupes on it; started is the pipeline's start time, or a rerun's own (design 47
|
||||
// G A-297).
|
||||
func AskID(started time.Time, pipeline, artifact, step string) string {
|
||||
sum := sha256.Sum256([]byte(pipeline + "\n" + artifact + "\n" + step))
|
||||
return "step-" + hex.EncodeToString(sum[:12])
|
||||
return strconv.FormatInt(started.UnixMilli(), 36) + hex.EncodeToString(sum[:3])
|
||||
}
|
||||
|
||||
// startOf is a pipeline's start time, the time its step ask ids open with (A-297); now for a pipeline that records none.
|
||||
func startOf(p seat.Pipeline, now time.Time) time.Time {
|
||||
if p.Started != nil {
|
||||
return *p.Started
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
// Place places a pipeline's steps (design 47 G §21, §23, §323; A-91): only the steps the module's registration policy
|
||||
@@ -74,7 +86,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog
|
||||
// The build step is every pipeline's first, required whatever the policy names, asked of the seat mesh-builds for the
|
||||
// module's own entry only (A-99, A-97): a group's other members are built by their own pipelines.
|
||||
build := seat.StepResult{Step: seat.StepResultStepBuild, Status: seat.StepResultStatusRequired, Fingerprint: p.Fingerprint,
|
||||
Seat: builds.Seat, Outcome: seat.StepResultOutcomeRunning, Started: &now, Ask: AskID(p.Pipeline, "", seat.StepResultStepBuild)}
|
||||
Seat: builds.Seat, Outcome: seat.StepResultOutcomeRunning, Started: &now, Ask: AskID(startOf(p, now), p.Pipeline, "", seat.StepResultStepBuild)}
|
||||
steps = append(steps, build)
|
||||
asks = append(asks, Asked{Seat: builds.Seat, ID: build.Ask, Ask: builds.StepRun{Pipeline: p.Pipeline, Step: builds.StepRunStepBuild, Branch: p.Branch,
|
||||
Revision: p.Revision, Source: p.Source, Entries: []builds.BuildEntry{{Module: p.Module, Commit: p.Commit, Fingerprint: p.Fingerprint}}}})
|
||||
@@ -102,7 +114,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog
|
||||
continue
|
||||
}
|
||||
s.Seat, s.Outcome, s.Started = bs, seat.StepResultOutcomeRunning, &now
|
||||
s.Ask = AskID(p.Pipeline, a.Ref, step)
|
||||
s.Ask = AskID(startOf(p, now), p.Pipeline, a.Ref, step)
|
||||
steps = append(steps, s)
|
||||
ask := Asked{Seat: bs, ID: s.Ask, Ask: stepAsk(p, entry, a, step, s.Ask, asker, now)}
|
||||
if a.Package != nil {
|
||||
@@ -124,7 +136,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog
|
||||
// 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)
|
||||
s.Ask = AskID(startOf(p, now), 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}})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package pipeline
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshpipelinerunner"
|
||||
wants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
nodebuildgo "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
||||
)
|
||||
|
||||
func stepSeatOf(t *testing.T, name string) StepSeat {
|
||||
@@ -81,6 +83,12 @@ func TestStartPlacesTheStepsThePolicyNamesAndAsksEachBuildSeat(t *testing.T) {
|
||||
if len(asks.asks) != 7 {
|
||||
t.Fatalf("asked %d steps, want the build, the five running build steps and the composition", len(asks.asks))
|
||||
}
|
||||
// A-297: every step's ask id is the usual ask id form the holders accept, opened by the pipeline's start time.
|
||||
for i, id := range asks.ids {
|
||||
if err := buildstep.CheckAskID(id); err != nil || !strings.HasPrefix(id, strconv.FormatInt(p.Started.UnixMilli(), 36)) {
|
||||
t.Errorf("ask %d's id %q is not the usual form opened by the pipeline's start: %v", i, id, err)
|
||||
}
|
||||
}
|
||||
// A-99: the build is every pipeline's first step, required whatever the policy names, asked of mesh-builds for the
|
||||
// module's own entry only.
|
||||
b, ok := asks.asks[0].(builds.StepRun)
|
||||
@@ -181,10 +189,11 @@ func TestCompositionIsAskedOfTheAssignerAndItsEndIsTaken(t *testing.T) {
|
||||
// placed under; an ended step, an ended pipeline and another branch's pipeline are asked of no one (design 47 G §88).
|
||||
func TestResumeAsksTheStepsPlacedAndNotEnded(t *testing.T) {
|
||||
reg, entries := placeFixture()
|
||||
started := time.Unix(1, 0) // A-297: a step's ask id opens with its pipeline's start, so resuming later places it under the same id
|
||||
placed := func(branch string, source int64) seat.Pipeline {
|
||||
p := seat.Pipeline{Pipeline: Key(branch, source, "app"), Branch: branch, Source: source, Module: "app", Commit: "c1", Fingerprint: "src2:aa",
|
||||
Repository: "o/app", Outcome: seat.PipelineOutcomeRunning}
|
||||
p.Steps, _ = Place(p, reg, entries, time.Unix(1, 0), "the-runner")
|
||||
Repository: "o/app", Outcome: seat.PipelineOutcomeRunning, Started: &started}
|
||||
p.Steps, _ = Place(p, reg, entries, started, "the-runner")
|
||||
return p
|
||||
}
|
||||
main4, main5, beta6 := placed("main", 4), placed("main", 5), placed("beta", 6)
|
||||
|
||||
@@ -325,12 +325,12 @@ func (v Verbs) askOf(p seat.Pipeline, s seat.StepResult, caller string, now time
|
||||
return nil, fmt.Errorf("the catalog half of %s is not held yet: the step is asked when it is", p.Branch)
|
||||
}
|
||||
_, placed := Place(p, reg, half.Entries, now, v.Starter.Name)
|
||||
base := AskID(p.Pipeline, s.Artifact, s.Step)
|
||||
base := AskID(startOf(p, now), p.Pipeline, s.Artifact, s.Step)
|
||||
for _, a := range placed {
|
||||
if a.ID != base {
|
||||
continue
|
||||
}
|
||||
a.ID = AskID(p.Pipeline, s.Artifact, s.Step+"\nrerun\n"+now.Format(time.RFC3339Nano))
|
||||
a.ID = AskID(now, p.Pipeline, s.Artifact, s.Step)
|
||||
switch b := a.Ask.(type) {
|
||||
case nodebuildgo.StepAsk:
|
||||
b.ID = a.ID
|
||||
|
||||
Reference in New Issue
Block a user