diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go index f609b52..01370a6 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go @@ -264,6 +264,8 @@ func leftNeither(into map[string]string, m assign.Module, before map[string]deli why = "it is held, and its held condition says so" case slices.ContainsFunc(out.Record, func(r assign.RecordNeed) bool { return r.Node == node }): why = "its move waits for the operator's yes on its record ask" + case putOn(m, node): + why = "its put waits on its own steps and the operator's answer" case m.FP == "": why = "the trunk has no build of it" case ruled: @@ -275,6 +277,13 @@ func leftNeither(into map[string]string, m assign.Module, before map[string]deli } } +// putOn says whether an operator's put names module m on node: it owns the node's want through its steps (ADR 0546), so the +// rules write none meanwhile. +func putOn(m assign.Module, node string) bool { + _, put := m.Puts[node] + return put +} + // startClears adds, for the first pass after a start, every node of m that is not on neither list after the pass: a neither // condition a previous process raised for it is cleared. That covers a node with a want and one with none that is not on neither // list, such as a held module (issue 1945: the hold is said on its own, so a neither raised for it before the restart would stay up diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go index 5a59c5c..575224f 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go @@ -398,6 +398,24 @@ func TestThePassGathersTheModulesOnNeitherList(t *testing.T) { if len(got) != 1 || got["g14.screen-lock"].node != "g14" || got["g14.screen-lock"].module != "screen-lock" || got["g14.screen-lock"].holding.Rule != delivery.RuleGrant { t.Errorf("gathered = %+v", got) } + // 1945 (the bus bar): a node off neither list is cleared in words that say why it has no want; an operator's put running + // through its steps owns the want, so it is named, never "the node does not run it". + for _, c := range []struct { + name string + puts map[string]delivery.Put + before map[string]delivery.Want + why string + }{ + {"a live want", nil, map[string]delivery.Want{"novox": {FP: fpTest}}, ""}, + {"a put on its steps", map[string]delivery.Put{"novox": {FP: fpTest, Step: delivery.PutPerson}}, nil, "its put waits on its own steps and the operator's answer"}, + {"nothing", nil, nil, "the node does not run it"}, + } { + left := map[string]string{} + leftNeither(left, assign.Module{Name: "nats", FP: fpTest, Nodes: []string{"novox"}, Puts: c.puts}, c.before, assign.Outcome{}) + if why, there := left["novox.nats"]; !there || why != c.why { + t.Errorf("%s: left %q (%v), want %q", c.name, why, there, c.why) + } + } // 1945 (a first want held back silently): the journal says why a node gets no want when the reason appears or changes, and // when it has a want again: one line per change, not per pass. p := newPasser(buckets{}, time.Date(2026, 10, 25, 21, 30, 0, 0, time.UTC)) diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index 452d958..7bc6885 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -210,9 +210,9 @@ func (h Holding) Reason() string { } // BusBarWords say why the assigner writes no want moving the bus on node: it runs prev, the trunk builds fp, and only the -// operator's put moves it. +// operator's put moves it (ADR 0539 §15, ADR 0546 §8). The words reach the operator, so they name no record. func BusBarWords(node, prev, fp string) string { - return fmt.Sprintf("the bus moves only by the operator's put (ADR 0546 §8): %s runs %s, the trunk builds %s; ask assigner_put", node, short(prev), short(fp)) + return fmt.Sprintf("only your put moves the bus: %s runs %s, the trunk builds %s; put it with assigner_put", node, short(prev), short(fp)) } // Wants is the pass of one module m: for each of its nodes, the want by the six rules, starting from the want written diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index dc18ce0..ea4ca59 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -437,7 +437,7 @@ func TestTheBusIsNotMovedButItsUserListRevisionIsWrittenAtOnce(t *testing.T) { t.Errorf("the bus bar wrote ace's want %+v", w) } h, on := out.Neither["ace"] - if !on || !h.Bus || out.Rules["ace"] != h || !strings.Contains(h.Words, "only by the operator's put") || !strings.Contains(h.Words, "ace runs b-1700000000-oldform") || + if !on || !h.Bus || out.Rules["ace"] != h || !strings.Contains(h.Words, "only your put moves the bus") || !strings.Contains(h.Words, "ace runs b-1700000000-oldform") || !strings.Contains(h.Words, "assigner_put") { t.Errorf("the barred bus on ace: neither %+v (%v), rules %+v", h, on, out.Rules["ace"]) }