checker_status names the builds a running check waits on (ADR 0603 §10)
Worked out from the builder's builds state at each read, stored nowhere: of each running check, the candidate's builds that are neither done nor failed. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -244,7 +244,9 @@ func Tools(r check.Reader) []stdio.Tool {
|
||||
"build seats' streams, whose backlog the delivery view shows (novox/hq ADR 0525). held_by_turn names the candidates whose check an approved " +
|
||||
"merge's turn holds off (novox/hq ADR 0548 §3), with the repository and the merge that waits; the gate's turns tool reads every turn and its " +
|
||||
"hand-now tool steers one. dead_letters says whether the checker hears the asks the build seats give up on (issue 734), and why not: " +
|
||||
"deaf, a try or check whose ask is given up waits until its bound. Read-only.",
|
||||
"deaf, a try or check whose ask is given up waits until its bound. builds_not_ended names, of each running check, the builder's builds of " +
|
||||
"its candidate's changed modules that are neither done nor failed: the check waits on them before it composes, within its build-wait-bound " +
|
||||
"(novox/hq ADR 0603 §10); a failed one fails the check: the builder's builder_build_again builds it again, then mesh-delivery.recheck asks the check again. Read-only.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Run: func(map[string]any) (any, error) { return r.Status() }},
|
||||
}
|
||||
@@ -283,7 +285,7 @@ func main() {
|
||||
StepStates: map[string]check.StateRead{"mesh-build-go": read(stdio.State("mesh-build-go.steps")), "mesh-build-node": read(stdio.State("mesh-build-node.steps"))},
|
||||
HaveState: read(stdio.State("node-engine.have")), StepBound: stepBound(),
|
||||
TurnState: read(stdio.State("mesh-delivery-gate.turns")), HearingState: read(stdio.State(buildrun.HearingState)),
|
||||
AskState: read(stdio.State(check.AsksState))}
|
||||
AskState: read(stdio.State(check.AsksState)), BuildState: read(stdio.State("mesh-delivery-builder.builds"))}
|
||||
r.Runs = runsOf{read(stdio.State("mesh-delivery-indexer.modules"))} // the node-engine at the commit the mesh runs, as the checker reads it
|
||||
tries := stdio.State(check.TriesState)
|
||||
source := stdio.State("mesh-delivery-gate.source")
|
||||
|
||||
@@ -3,6 +3,7 @@ package check
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -47,6 +48,9 @@ type Reader struct {
|
||||
Runs interface{ CommitOf(module string) string }
|
||||
// HearingState is the checker's state hearing: whether it hears DEAD_LETTERS (issue 734).
|
||||
HearingState StateRead
|
||||
// BuildState is the builder's builds (mesh-delivery-builder.builds): the builds of a running check's candidate that have
|
||||
// not ended, which the check waits on before it composes (ADR 0603 §10); without it none is named.
|
||||
BuildState StateRead
|
||||
// AskState is the checker's state asks: the asks each running check put (AsksState), which checker_ask_again reads.
|
||||
AskState StateRead
|
||||
Now func() time.Time
|
||||
@@ -344,6 +348,10 @@ type Status struct {
|
||||
// Lost are the candidates whose verdict is running while no step of theirs can still run and their newest step ended, by
|
||||
// group, with that step: nothing will judge them (the lost check of 2026-10-28); checker_ask_again asks one again.
|
||||
Lost map[string]string `json:"lost"`
|
||||
// BuildsNotEnded are, of each candidate whose check runs, the builder's builds of its changed modules that are neither
|
||||
// done nor failed, by group: `<module> <state>`, sorted (no key yet is `not begun`). A check waits on them before it
|
||||
// composes (ADR 0603 §10), but for the modules its line names as composed without them; builder_build_again builds a failed one again.
|
||||
BuildsNotEnded map[string]string `json:"builds_not_ended,omitempty"`
|
||||
// DeadLetters says whether the checker hears the asks the build seats give up on (issue 734): deaf, a try or check
|
||||
// whose ask is given up waits until its bound.
|
||||
DeadLetters DeadLettersHeard `json:"dead_letters"`
|
||||
@@ -500,6 +508,12 @@ func (r Reader) Status() (Status, error) {
|
||||
if why := read.lostWhy(c); why != "" {
|
||||
st.Lost[c.Group] = why
|
||||
}
|
||||
if words := r.buildsNotEnded(c); words != "" {
|
||||
if st.BuildsNotEnded == nil {
|
||||
st.BuildsNotEnded = map[string]string{}
|
||||
}
|
||||
st.BuildsNotEnded[c.Group] = words
|
||||
}
|
||||
case IsNodesFailure(v):
|
||||
st.Waiting[KindNodes] = append(st.Waiting[KindNodes], c.Group)
|
||||
case IsUntrunked(v):
|
||||
@@ -663,3 +677,38 @@ func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// buildsNotEnded are the builder's builds of the candidate's changed modules that are neither done nor failed, as
|
||||
// Status.BuildsNotEnded says them; empty when none, or when the builder's builds are not readable here.
|
||||
func (r Reader) buildsNotEnded(c delivery.Candidate) string {
|
||||
if r.BuildState.Get == nil {
|
||||
return ""
|
||||
}
|
||||
var out []string
|
||||
for _, p := range c.Pulls {
|
||||
for module, cm := range p.Fps {
|
||||
if cm.FP2 == "" || cm.Manifest == "" {
|
||||
continue
|
||||
}
|
||||
key, err := delivery.BuilderBuildKey(module, cm.FP2)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
raw, err := r.BuildState.Get(key)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
state := "not begun"
|
||||
if len(raw) > 0 {
|
||||
b, err := delivery.DecodeBuilderBuild(raw)
|
||||
if err != nil || b.State == delivery.BuildDone || b.State == delivery.BuildFailed {
|
||||
continue
|
||||
}
|
||||
state = b.State
|
||||
}
|
||||
out = append(out, module+" "+state)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return strings.Join(slices.Compact(out), ", ")
|
||||
}
|
||||
|
||||
@@ -147,10 +147,24 @@ func TestTheStatusToolReadsRunningSteps(t *testing.T) {
|
||||
passed, _ := delivery.EncodeStep(delivery.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: delivery.StepPassed})
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
ckey, _ := delivery.CandidatesKey("feat/x")
|
||||
// ADR 0603 §10: feat/y's check runs and waits on the builds of its changed modules: web building, mail not begun; api
|
||||
// is done and named nowhere.
|
||||
fp, man := "src2:"+strings.Repeat("f", 64), strings.Repeat("e", 64)
|
||||
py := pullOf(repoA, 8, head2)
|
||||
py.Modules, py.Fps = []string{"api", "mail", "web"}, map[string]delivery.CandidateModule{"web": {FP2: fp, Manifest: man}, "mail": {FP2: fp, Manifest: man}, "api": {FP2: fp, Manifest: man}}
|
||||
candY := candidateOf(t, "feat/y", py)
|
||||
ykey, _ := delivery.CandidatesKey("feat/y")
|
||||
runningV, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/y", Heads: "0123456789ab", State: delivery.VerdictRunning})
|
||||
builds := fakeState{}
|
||||
for module, state := range map[string]string{"web": delivery.BuildBuilding, "api": delivery.BuildDone} {
|
||||
k, _ := delivery.BuilderBuildKey(module, fp)
|
||||
builds[k], _ = json.Marshal(delivery.BuilderBuild{V: 1, State: state})
|
||||
}
|
||||
rd := Reader{
|
||||
StepStates: map[string]StateRead{"mesh-build-go": fakeState{"shanks.abc1": running, "ace.abc2": passed}.read()},
|
||||
CandidateState: fakeState{ckey: cand}.read(),
|
||||
VerdictState: fakeState{}.read(),
|
||||
CandidateState: fakeState{ckey: cand, ykey: candY}.read(),
|
||||
VerdictState: fakeState{keyOf(t, "feat/y", candY): runningV}.read(),
|
||||
BuildState: builds.read(),
|
||||
HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(),
|
||||
HearingState: fakeState{"dead-letters": []byte(`{"heard":false,"streams":["SEAT_NODE_BUILD_GO"],` +
|
||||
`"why":"DEAD_LETTERS is not heard, so an ask a build seat gives up on is waited for until its bound: SEAT_NODE_BUILD_GO: permissions violation",` +
|
||||
@@ -167,6 +181,9 @@ func TestTheStatusToolReadsRunningSteps(t *testing.T) {
|
||||
if len(st.Unjudged) != 1 || st.Unjudged[0] != "feat/x" {
|
||||
t.Errorf("unjudged = %v", st.Unjudged)
|
||||
}
|
||||
if got := st.BuildsNotEnded; len(got) != 1 || got["feat/y"] != "mail not begun, web building" {
|
||||
t.Errorf("builds not ended = %v, want feat/y's mail not begun and web building", got)
|
||||
}
|
||||
// Issue 734, review round 1: a checker that does not hear DEAD_LETTERS says so in its status, with why.
|
||||
if st.DeadLetters.Heard || !strings.Contains(st.DeadLetters.Why, "permissions violation") {
|
||||
t.Errorf("dead letters = %+v, want not heard, saying why", st.DeadLetters)
|
||||
|
||||
Reference in New Issue
Block a user