Every check composes with the composer built at the node-engine member's head, else at its trunk, and a change's manifests carry the check's own judge's verdicts (issue 1921, milestone 47/G D12, D13)
The parked composition-at-running path is folded as the lead's ruling Q3: one path, so no check composes with the running engines' own code (ADR 0656 §5). The sandbox request no longer drops a change's manifest verdict: it carries the check's judge's, or none, which the composer reads as accepted (ADR 0578 §3). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -260,6 +260,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
Bound: answerBound,
|
||||
Sandbox: seatSandbox{asker: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}},
|
||||
Verdicts: verdicts.all,
|
||||
// The node-engine's trunk head: a group with no node-engine member composes with the composer built there (ADR 0656 §5).
|
||||
Composer: func() string { return trunks.Heads()[check.NodeEngineRepository] },
|
||||
}
|
||||
// The tries (ADR 0501 §5) are judged by the same judge, held by the same deliveries.
|
||||
tryer := &check.Tryer{Tries: kvStore{buckets[check.TriesBucket]}, Bound: checkBound, NotTaken: stepBound, Logf: logger.Printf,
|
||||
|
||||
@@ -31,7 +31,7 @@ const NodeEngineModule = nodeEngineModule
|
||||
|
||||
const (
|
||||
catalogueRepository = "novox/mesh-catalog"
|
||||
nodeEngineRepo = "novox/mesh-host"
|
||||
nodeEngineRepo = NodeEngineRepository
|
||||
nodeEngineModule = "mesh-host"
|
||||
// networkingRepository holds route-proxy, whose manifest the node-engine's proofs of a composition read.
|
||||
networkingRepository = "novox/mesh-networking"
|
||||
@@ -213,3 +213,6 @@ func besideAtTrunk(c *Case, refs []BesideRef, into map[string]string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NodeEngineRepository is the node-engine's repository, whose trunk head a check without its member composes at.
|
||||
const NodeEngineRepository = "novox/mesh-host"
|
||||
|
||||
@@ -50,6 +50,9 @@ type Case struct {
|
||||
Seats []json.RawMessage
|
||||
// Try narrows a try's check (its level, its tests); nil for a pull request's.
|
||||
Try *TryOptions
|
||||
// Judged are the check's own judge's verdicts on the manifests the change brings, by module, as the seat mesh-catalog's
|
||||
// verdict value (judgeManifests): the composer's request carries them in place of the running indexer's (ADR 0578 §2).
|
||||
Judged map[string]json.RawMessage
|
||||
// Tagged are the group's tagged versions of its Go packages the check heard (design 51 *Tagged versions*), nil for none:
|
||||
// a composer built at the node-engine member's head is built against them, as the check's other Go builds are.
|
||||
Tagged buildstep.PackageVersions
|
||||
|
||||
@@ -50,7 +50,10 @@ type EngineCompositions struct {
|
||||
Placed func() ([]string, error)
|
||||
// Bound is the answer bound (the setting compose-answer-bound); zero is nec.AnswerBound.
|
||||
Bound time.Duration
|
||||
// Composer is the commit of the node-engine the mesh runs, named as what composed (ADR 0548 §6's running commit).
|
||||
// Composer is the node-engine's trunk head: a check whose group has no node-engine member is composed by the composer
|
||||
// built there, so every check composes with an acting part built from the change's heads or their trunk, never with
|
||||
// the running engines' own code (ADR 0656 §5; the lead's ruling Q3 on milestone 47/G). Nil, or an empty head, leaves
|
||||
// such a check composed by the running engines, said.
|
||||
Composer func() string
|
||||
// Sandbox runs the composer of a node-engine member's head; nil leaves such a group judged by the running engines,
|
||||
// said in a warning.
|
||||
@@ -67,6 +70,14 @@ type EngineCompositions struct {
|
||||
}
|
||||
|
||||
// composerHead is the node-engine member's head of a case, or empty.
|
||||
// atWord names where a composer was built: the member's head or the node-engine's trunk.
|
||||
func atWord(member bool) string {
|
||||
if member {
|
||||
return "head"
|
||||
}
|
||||
return "trunk"
|
||||
}
|
||||
|
||||
func composerHead(c *Case) string {
|
||||
if m := c.member(nodeEngineRepo); m != nil {
|
||||
return m.Head
|
||||
@@ -92,7 +103,11 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (rules.Compose
|
||||
if bound <= 0 {
|
||||
bound = nec.AnswerBound
|
||||
}
|
||||
head := composerHead(c)
|
||||
// One composition path (ADR 0656 §5): the composer built at the node-engine member's head, else at its trunk.
|
||||
head, member := composerHead(c), true
|
||||
if head == "" && e.Composer != nil {
|
||||
head, member = e.Composer(), false
|
||||
}
|
||||
sandboxed := head != "" && e.Sandbox != nil
|
||||
answers := map[string]ne.ComposeCandidateAnswer{}
|
||||
silent := map[string]string{}
|
||||
@@ -111,7 +126,7 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (rules.Compose
|
||||
// the head.
|
||||
if composed, serr := e.inSandbox(ctx, c, head, node, a); serr != nil {
|
||||
a = ne.ComposeCandidateAnswer{V: a.V, Node: node, Candidate: a.Candidate, Verdict: nec.Refused,
|
||||
Refused: "the composer at the node-engine's head " + head + " could not compose it: " + serr.Error()}
|
||||
Refused: "the composer at the node-engine's " + atWord(member) + " " + head + " could not compose it: " + serr.Error()}
|
||||
} else {
|
||||
a = composed
|
||||
}
|
||||
@@ -133,12 +148,13 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (rules.Compose
|
||||
composerName := "node-engine"
|
||||
if sandboxed {
|
||||
composerName = head
|
||||
} else if e.Composer != nil && e.Composer() != "" {
|
||||
composerName = e.Composer()
|
||||
}
|
||||
v := rules.Composed{Group: c.Group, Heads: c.HeadsHash, Composer: composerName, Nodes: nodes, Answers: answers, Silent: silent}
|
||||
if head != "" && e.Sandbox == nil {
|
||||
switch {
|
||||
case member && head != "" && e.Sandbox == nil:
|
||||
v.Warnings = append(v.Warnings, "the group changes the node-engine's composition code and was composed by the running engines")
|
||||
case !sandboxed && e.Sandbox != nil && len(nodes) > 0:
|
||||
v.Warnings = append(v.Warnings, "composed by the running engines: no composer at the node-engine's trunk is known to build")
|
||||
}
|
||||
v.Took = time.Since(began).Milliseconds()
|
||||
return v, nil
|
||||
@@ -174,9 +190,14 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s
|
||||
verdicts[name] = raw
|
||||
}
|
||||
}
|
||||
// A manifest the change brings is the check's own judge's (ADR 0578 §2, §3): its verdict there when the judge gave one,
|
||||
// else none, which the composer reads as accepted; never the running indexer's (F5 of milestone 47/G).
|
||||
for _, p := range c.Pulls {
|
||||
for name := range p.Fps {
|
||||
delete(verdicts, name) // a manifest the change brings is the checker's own judge's (ADR 0578 §2)
|
||||
delete(verdicts, name)
|
||||
if raw, ok := c.Judged[name]; ok {
|
||||
verdicts[name] = raw
|
||||
}
|
||||
}
|
||||
}
|
||||
// The answer carries its input as the object the seat's contract names, decoded with its numbers as written
|
||||
|
||||
@@ -131,7 +131,8 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
if took := time.Since(began); took > 2*time.Second {
|
||||
t.Errorf("the nodes were not asked in parallel within the bound: %s", took)
|
||||
}
|
||||
if v.State != meshdeliverycheckerclient.CompositionPass || v.Sdk != strings.Repeat("7", 40) || len(v.Machines) != 1 {
|
||||
// No sandbox: the running engines composed, named so, though the node-engine's trunk head is known.
|
||||
if v.State != meshdeliverycheckerclient.CompositionPass || v.Sdk != "node-engine" || len(v.Machines) != 1 {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
for _, node := range []string{"ace", "g14", "laptop"} {
|
||||
@@ -229,6 +230,64 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// ADR 0578 §2, §3, ADR 0656 §5 (milestone 47/G D12, D13): with no node-engine member, every node is composed by the
|
||||
// composer built at the node-engine's trunk over the input its engine answers, and a manifest the change brings carries
|
||||
// the check's own judge's verdict, not the running indexer's: one the running indexer refused and the judge accepted
|
||||
// composes, and the verdict names the trunk's composer.
|
||||
t.Run("a change's manifest the check's judge accepted is composed by the composer at the node-engine's trunk", func(t *testing.T) {
|
||||
p := pullOf(repoA, 7, head1)
|
||||
p.Modules = []string{"mesh-build-go"}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-build-go": {Fp2: "src2:" + strings.Repeat("f", 64), Manifest: strings.Repeat("d", 64)}}
|
||||
brings := caseOf(t, p)
|
||||
brings.Judged = map[string]json.RawMessage{"mesh-build-go": json.RawMessage(`{"module":"mesh-build-go","state":"accepted"}`)}
|
||||
trunkHead := strings.Repeat("7", 40)
|
||||
withInput := composed("anchor")
|
||||
withInput.Input = map[string]any{"nodes": []any{map[string]any{"name": "anchor"}}}
|
||||
e := &fakeEngines{answers: map[string]ne.ComposeCandidateAnswer{"anchor": withInput}}
|
||||
sb := &fakeSandbox{v: func(node string) composer.Verdict {
|
||||
return composer.Verdict{V: composer.Version, Cases: []composer.CaseVerdict{{Name: "change",
|
||||
Machines: map[string]composer.Machine{node: {Composes: true}}}}}
|
||||
}}
|
||||
indexer := map[string]meshcatalog.VerdictsByDigestValue{"mesh-build-go": {Module: "mesh-build-go", State: "refused", Problems: []string{"serves no verb input"}}, "mail": {Module: "mail", State: "accepted"}}
|
||||
ec := EngineCompositions{Asker: e, Placed: func() ([]string, error) { return []string{"anchor"}, nil }, Bound: time.Second,
|
||||
Sandbox: sb, Composer: func() string { return trunkHead }, Published: published{},
|
||||
Verdicts: func() map[string]meshcatalog.VerdictsByDigestValue { return indexer }}
|
||||
raw, err := ec.Compose(context.Background(), brings)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := rules.JudgeEngines(raw); v.State != meshdeliverycheckerclient.CompositionPass || v.Sdk != trunkHead {
|
||||
t.Fatalf("verdict = %+v, want a pass composed at the node-engine's trunk %s", v, trunkHead)
|
||||
}
|
||||
if len(sb.jobs) != 1 || sb.jobs[0].Fetch.Commit != trunkHead || sb.jobs[0].Fetch.Repository != nodeEngineRepo {
|
||||
t.Fatalf("jobs %+v: want one job at the node-engine's trunk", sb.jobs)
|
||||
}
|
||||
var req composer.Request
|
||||
_ = json.Unmarshal(sb.jobs[0].Request, &req)
|
||||
if got := string(req.Cases[0].Verdicts["mesh-build-go"]); got != string(brings.Judged["mesh-build-go"]) || len(req.Cases[0].Verdicts) != 2 {
|
||||
t.Errorf("the request's verdicts %s: want the check's judge's for mesh-build-go and the indexer's for mail", req.Cases[0].Verdicts)
|
||||
}
|
||||
// A manifest the judge gave no verdict on carries none, which the composer reads as accepted (ADR 0578 §3).
|
||||
brings.Judged = nil
|
||||
if _, err := ec.Compose(context.Background(), brings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var none composer.Request
|
||||
_ = json.Unmarshal(sb.jobs[len(sb.jobs)-1].Request, &none)
|
||||
if _, kept := none.Cases[0].Verdicts["mesh-build-go"]; kept {
|
||||
t.Errorf("the running indexer's verdict on the change's manifest was kept: %s", none.Cases[0].Verdicts)
|
||||
}
|
||||
// A refusal the trunk's composer makes stays the node-engine's own.
|
||||
sb.v = func(node string) composer.Verdict {
|
||||
return composer.Verdict{V: composer.Version, Cases: []composer.CaseVerdict{{Name: "change",
|
||||
Machines: map[string]composer.Machine{node: {Composes: false, Reason: "a required value is unset"}}}}}
|
||||
}
|
||||
raw, _ = ec.Compose(context.Background(), brings)
|
||||
if v := rules.JudgeEngines(raw); v.State != meshdeliverycheckerclient.CompositionFail || !strings.Contains(v.Words, "a required value is unset") {
|
||||
t.Errorf("a refusal the trunk's composer makes: %+v", v)
|
||||
}
|
||||
})
|
||||
|
||||
// Review finding 4: assignments that cannot be read is no mesh with no node: the composition is not made, said.
|
||||
t.Run("an unreadable assignments makes no composition", func(t *testing.T) {
|
||||
ec := EngineCompositions{Asker: &fakeEngines{}, Placed: func() ([]string, error) { return nil, errors.New("no such bucket") }}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
@@ -140,6 +141,7 @@ func (st *state) judgeManifests(ctx context.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st.judgedVerdicts(cv, at)
|
||||
m := rules.Manifests{Touched: touched, At: at, Change: cv, Trunk: trunkCase}
|
||||
if !st.indexerOf(ctx, req, touched, cv, at, &m) {
|
||||
return
|
||||
@@ -368,3 +370,29 @@ func (st *state) knownPackages(ctx context.Context) []string {
|
||||
func (st *state) trunkJudgeVersions() buildstep.PackageVersions {
|
||||
return packagesFor(st.j.Published, &Case{}, nil, DeliveryRepository)
|
||||
}
|
||||
|
||||
// judgedVerdicts puts the check's own judge's verdict on each manifest the change brings on the case, as the seat
|
||||
// mesh-catalog's verdict value, for the composer's request (ADR 0578 §2, §3; F5 of milestone 47/G): accepted, or refused
|
||||
// with its words; a manifest the judge gave no verdict on carries none, which the composer reads as accepted.
|
||||
func (st *state) judgedVerdicts(cv composer.CaseVerdict, at string) {
|
||||
for _, p := range st.c.Pulls {
|
||||
for name, fp := range p.Fps {
|
||||
read, ok := cv.Manifests[name]
|
||||
if fp.Manifest == "" || !ok || (!read.Accepted && read.Refused == "") {
|
||||
continue
|
||||
}
|
||||
v := meshcatalog.VerdictsByDigestValue{Module: name, State: meshcatalogclient.VerdictAccepted, Judge: at}
|
||||
if !read.Accepted {
|
||||
v.State, v.Problems = meshcatalogclient.VerdictRefused, []string{read.Refused}
|
||||
}
|
||||
raw, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if st.c.Judged == nil {
|
||||
st.c.Judged = map[string]json.RawMessage{}
|
||||
}
|
||||
st.c.Judged[name] = raw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user