From a9aa6d29ead10e61bba7901bc233c318b9a8e9a7 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 13:21:18 +0100 Subject: [PATCH] =?UTF-8?q?The=20assigner=20moves=20the=20bus=20module=20b?= =?UTF-8?q?y=20its=20rules=20as=20any=20module,=20with=20no=20bar=20and=20?= =?UTF-8?q?no=20snapshot=20of=20its=20own,=20and=20a=20person=20may=20choo?= =?UTF-8?q?se=20its=20policy=20as=20any=20module's:=20its=20node=20backs?= =?UTF-8?q?=20its=20streams=20up=20before=20it=20applies=20(ADR=200601=20?= =?UTF-8?q?=C2=A71,=20=C2=A72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cmd/mesh-delivery-assigner/pass_test.go | 8 ++-- .../mesh-delivery-assigner/seat_tools_test.go | 8 +++- .../cmd/mesh-delivery-assigner/wants_tools.go | 8 +--- .../internal/assign/asks.go | 8 ---- .../internal/assign/policy.go | 2 +- .../internal/assign/wants.go | 48 ++++--------------- .../internal/assign/wants_test.go | 33 ++++--------- .../mesh-delivery-indexer/judge/rules_test.go | 2 +- 8 files changed, 31 insertions(+), 86 deletions(-) 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 df5b9dc9..a527a890 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 @@ -414,10 +414,11 @@ func TestAModuleOnNeitherListIsRaisedOnceAndClearedOnce(t *testing.T) { if last := logged[len(logged)-1]; last != "screen-lock on g14 has a want again" { t.Errorf("the clear said %q", last) } - // 1945 (the bus bar): a module that leaves neither list with no want is cleared in words saying so, never "has a want again". - bus := map[string]neitherOne{"novox.nats": {node: "novox", module: "nats", holding: assign.Holding{Bus: true, Words: "the bus moves only by the operator's put"}}} + // 1945: a module that leaves neither list with no want is cleared in words saying so, never "has a want again". The bus is + // on neither list by a rule as any module (ADR 0601 §1), nothing for the operator to do. + bus := map[string]neitherOne{"novox.nats": {node: "novox", module: "nats", holding: assign.Holding{Rule: delivery.RuleGrant, Words: "no credential"}}} p.sayNeither(bus, nil, nil) - if c := said[len(said)-1]; c.State != asks.StateRaised || !strings.HasPrefix(c.Explanation, asks.NeedsYou) || !strings.Contains(c.Explanation, "the bus moves only by the operator's put") { + if c := said[len(said)-1]; c.State != asks.StateRaised || !strings.HasPrefix(c.Explanation, asks.NothingToDo) || !strings.Contains(c.Explanation, "rule 6 holds its first want back: no credential") { t.Errorf("the bus's neither condition = %+v", c) } p.sayNeither(map[string]neitherOne{}, nil, map[string]string{"novox.nats": "it is held, and its held condition says so"}) @@ -474,7 +475,6 @@ func testThePassGathersTheModulesOnNeitherList(t *testing.T) { {nil, assign.Outcome{Rules: map[string]assign.Holding{"shanks": {Rule: delivery.RuleGrant, Words: "no credential"}}}, "rofi on shanks gets no want: rule 6: no credential"}, {nil, wanted, "rofi on shanks has a want"}, {map[string]delivery.Want{"shanks": wanted.Wants["shanks"]}, assign.Outcome{}, ""}, - {nil, assign.Outcome{Rules: map[string]assign.Holding{"shanks": {Bus: true, Words: "the bus moves only by the operator's put"}}}, "rofi on shanks gets no want: the bus moves only by the operator's put"}, } { logged = nil p.sayNoWant("rofi", []string{"shanks"}, c.before, c.out) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go index 046dceb8..fd93f700 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go @@ -471,8 +471,12 @@ func wantsVerbs(t *testing.T) { len(said) != 1 || !strings.HasPrefix(said[0], "policy-changed policy-changed.web.") { t.Errorf("chosen %+v, asked %v, said %v", c, asked, said) } - if _, err := w.doUpgrade(map[string]any{"module": "nats", "policy": "roll", "why": "x"}, "op"); err == nil { - t.Error("the bus was given a policy") + // A person may choose the bus's policy as any module's (ADR 0601 §1: no part treats the module that provides the bus apart). + if _, err := w.doUpgrade(map[string]any{"module": "nats", "policy": "roll", "why": "x"}, "op"); err != nil { + t.Errorf("a person's policy for the bus was refused: %v", err) + } + if _, err := w.doUpgrade(map[string]any{"module": "nats", "policy": "default", "why": "back to the module"}, "op"); err != nil { + t.Fatal(err) } if _, err := w.doUpgrade(map[string]any{"module": "web", "policy": "roll"}, "op"); err == nil { t.Error("a policy without why was set") diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/wants_tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/wants_tools.go index 4749b289..d0c4092c 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/wants_tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/wants_tools.go @@ -197,18 +197,14 @@ func (s *wantsTools) doUpgrade(args map[string]any, by string) (any, error) { } // setPolicy writes a person's choice of a module's policy, or deletes it for default, records the act in the hand-act log -// and says policy-changed. The bus is never rolled: a choice for it is refused. +// and says policy-changed. The bus's choice is a person's as any module's (ADR 0601 §1). func (s *wantsTools) setPolicy(module, policy, why, by string, manifests map[string]catalogue.Manifest) (any, error) { if module == "" { return nil, errors.New("a policy is set for one module: name it") } - m, ok := manifests[module] - if !ok { + if _, ok := manifests[module]; !ok { return nil, fmt.Errorf("the indexer holds no manifest of %s", module) } - if catalogue.ProvidesBus(m) { - return nil, fmt.Errorf("%s is the bus, which is never rolled whoever says so (novox/hq ADR 0236): its step is the put on the operator's warrant", module) - } if why == "" { return nil, errors.New("a person's policy says why") } diff --git a/modules/mesh-delivery-assigner/internal/assign/asks.go b/modules/mesh-delivery-assigner/internal/assign/asks.go index b8125bee..70651e77 100644 --- a/modules/mesh-delivery-assigner/internal/assign/asks.go +++ b/modules/mesh-delivery-assigner/internal/assign/asks.go @@ -119,14 +119,6 @@ func NeitherCondition(node, module string, h Holding, raised bool) asks.Conditio if n := len([]rune(headline)); n > asks.HeadlineLength { headline = string([]rune(headline)[:asks.HeadlineLength]) } - if h.Bus { - needs := fmt.Sprintf("put %s on %s with assigner_put", module, node) - return asks.Condition{Key: key, Severity: asks.SeverityUrgent, Subject: subject, State: asks.StateRaised, - Summary: headline, Headline: headline, Needs: needs, - Explanation: fmt.Sprintf("%s%s. %s is assigned to %s, but the node holds no want for it and the node's own composition does not "+ - "hold it either, so nothing applies it there. The assigner never moves the bus: %s. This clears once the node has a want for it.", - asks.NeedsYou, needs, module, node, h.Words)} - } return asks.Condition{Key: key, Severity: asks.SeverityUrgent, Subject: subject, State: asks.StateRaised, Summary: headline, Headline: headline, Explanation: fmt.Sprintf("%s %s is assigned to %s, but the node holds no want for it and the node's own composition does not "+ diff --git a/modules/mesh-delivery-assigner/internal/assign/policy.go b/modules/mesh-delivery-assigner/internal/assign/policy.go index c3d94566..fab4630b 100644 --- a/modules/mesh-delivery-assigner/internal/assign/policy.go +++ b/modules/mesh-delivery-assigner/internal/assign/policy.go @@ -16,7 +16,7 @@ import ( // A person's upgrade policy is the assigner's (novox/hq ADR 0565 §7, §8, issue 1953 D4, D5): kept in its state `policies`, one // key per module, set and taken back through `mesh-wants.upgrade`. Every reader takes one derivation, mesh-sdk's -// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus never rolled. +// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus as any module (ADR 0601 §1). // PoliciesBucket is the assigner's state `policies`. var PoliciesBucket = delivery.AssignerModule + "_" + delivery.AssignerPoliciesState diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index 3859e68d..629014e9 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -268,29 +268,18 @@ type RecordNeed struct { Words string } -// Holding is a rule holding a node back, and why. Bus marks the bar on the bus instead of one of the seven rules (Rule is then -// 0): the bus moves only by the operator's put (ADR 0539 §15, ADR 0546 §8), never by the assigner (issue 1945: nats on novox, -// running an old-form build, got no want and the pass said nothing). +// Holding is a rule holding a node back, and why. The bus module is held back by the rules alone, as every module (ADR 0601 §1, +// §2): no bar of its own. type Holding struct { Rule int Words string - Bus bool } -// Reason is the holding in words: the rule and its words, or for the bus bar its words alone. +// Reason is the holding in words: the rule and its words. func (h Holding) Reason() string { - if h.Bus { - return h.Words - } return fmt.Sprintf("rule %d: %s", h.Rule, h.Words) } -// 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 (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("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 // before (wants, by node); lastHealthy is, by node, the newest fingerprint other than m.FP the node ran healthy. A failed // health check of m.FP on a node holds it: every node wanting m.FP is set back to its last healthy fingerprint, but a @@ -425,14 +414,6 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st if need, wait := at.mustAsk(node, asked, hadLive, ran, running.FP, running.Composed == "", d); wait { if need != nil { out.Record = append(out.Record, *need) - } else if prev, barred := at.busBar(asked, hadLive, running.FP); barred { - // The bus is barred: no want moves it but the operator's put. Said, never silent (issue 1945): its holding is named, - // and a node that runs it with no live want has it on neither list, so its condition stays raised. - h := Holding{Bus: true, Words: BusBarWords(node, prev, at.FP)} - out.Rules[node] = h - if !hadLive && running.Phase != delivery.PhaseRemoved && !held { - out.Neither[node] = h - } } continue } @@ -659,10 +640,10 @@ func short(fp string) string { return part[:12] } -// mustAsk says whether the move to the composition d on node waits: for the operator's yes (need names the ask) or, for the bus, -// for the operator's hand (ADR 0539 §14, §15). A module the node never ran (it has no want and no have), a move judged neither, a module that is not of -// policy record and a want that stays are written as any want; so is a revision of the bus module's composition at the -// same fingerprint judged neither (a user list only, §7). +// mustAsk says whether the move to the composition d on node waits for the operator's yes (need names the ask, ADR 0539 §14). A +// module the node never ran (it has no want and no have), a move judged neither, a module that is not of policy record and a want +// that stays are written as any want. The bus module is no exception: it moves by the rules (ADR 0601 §1), its node backing up its +// streams before it applies (§2); the assigner asks no snapshot for it. func (m Module) mustAsk(node string, before delivery.Want, had, ran bool, running string, haveNamesNoComposition bool, d delivery.ComposedDigest) (*RecordNeed, bool) { if (!had && !ran) || before.Removed || (before.FP == m.FP && before.Composed == d.Digest) { return nil, false @@ -674,9 +655,6 @@ func (m Module) mustAsk(node string, before delivery.Want, had, ran bool, runnin if !had && ran && running == m.FP && haveNamesNoComposition && d.Judgement == delivery.JudgedUnchanged { return nil, false } - if _, barred := m.busBar(before, had, running); barred { - return nil, true // the bus is never moved by the assigner; the operator writes its want (§15) - } if !m.recorded(d) || d.Judgement == delivery.JudgedNeither || d.Judgement == delivery.JudgedUnchanged { return nil, false } @@ -686,17 +664,7 @@ func (m Module) mustAsk(node string, before delivery.Want, had, ran bool, runnin return &RecordNeed{Node: node, Digest: d.Digest, Judgement: d.Judgement, Words: d.Words}, true } -// busBar says whether m is the bus and the move would take it off the fingerprint it ran before, prev: its want's, or with no -// readable want the one its have names (ADR 0539 §15 bars a new one). -func (m Module) busBar(before delivery.Want, had bool, running string) (prev string, barred bool) { - prev = before.FP - if !had { - prev = running - } - return prev, catalogue.ProvidesBus(m.Manifest) && prev != m.FP -} - -// recorded says whether the module is of policy record: a person's choice at the assigner over everything but the bus (ADR +// recorded says whether the module is of policy record: a person's choice at the assigner over the manifest's word (ADR // 0565 §7, catalogue.Upgrade); for a module with none, as the controller holds it beside the composition's digest until the // lease write (ADR 0539 §14); on a composition made by hand, which carries none, as the manifest derives it. func (m Module) recorded(d delivery.ComposedDigest) bool { diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index 35af5f8a..95d4eee3 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -639,11 +639,13 @@ func TestAMoveJudgedNeitherOrNeverRunOrNotRecordedIsWrittenAtOnce(t *testing.T) // 1906/D4 (ADR 0539 §7, §15): the bus module's new fingerprint is never written by the assigner, but a revision of its // composition at the same fingerprint that only changes the user list (judged neither) is written at once. -func TestTheBusIsNotMovedButItsUserListRevisionIsWrittenAtOnce(t *testing.T) { +func TestTheBusMovesByTheRulesAndItsUserListRevisionIsWrittenAtOnce(t *testing.T) { m, n, _ := movingTo(delivery.JudgedNeither) m.Manifest.Provides = []catalogue.Offer{{Name: "mesh-bus"}} - if out := Wants(m, n, wantedOld(), nil); out.Wants["ace"].FP == fNew { - t.Errorf("the bus moved to a new fingerprint: %+v", out.Wants["ace"]) + // ADR 0601 §1, §2: the bus module's want is written by the rules, as any module's, and no snapshot is asked for it: its node + // backs its streams up before it applies. + if out := Wants(m, n, wantedOld(), nil); out.Wants["ace"].FP != fNew || len(out.Record) != 0 || len(out.Neither) != 0 { + t.Errorf("the bus did not move by the rules: %+v", out) } running := map[string]delivery.Want{"ace": {FP: fNew, Composed: dig(fOld)}, "g14": {FP: fNew, Composed: dig(fOld)}} out := Wants(m, n, running, nil) @@ -696,23 +698,6 @@ func TestTheBusIsNotMovedButItsUserListRevisionIsWrittenAtOnce(t *testing.T) { t.Errorf("a held bus did not take its user-list revision as a put: %+v", w) } m.Puts = nil - // 1945 (the bus bar made loud): a node that runs the bus at an old-form build and holds no want for it gets none, since only - // the operator's put moves the bus, and the pass says so: a holding naming the bar, and the node on neither list so its - // condition stays raised. A node with a live want keeps it and is not on neither list. - m.Composed = composedEverywhere - n.Haves["ace"]["mail"] = delivery.Have{FP: "b-1700000000-oldform", Phase: delivery.PhaseRunning, Health: delivery.HealthOK} - out = Wants(m, n, map[string]delivery.Want{"g14": {FP: fOld, Composed: dig(fOld)}}, nil) - if w, wrote := out.Wants["ace"]; wrote { - 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 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"]) - } - if _, on := out.Neither["g14"]; on || out.Wants["g14"].FP == fNew { - t.Errorf("g14 has a live want: neither %+v, its want %+v", out.Neither, out.Wants["g14"]) - } stepsOfAPut(t) } @@ -889,8 +874,8 @@ func TestTheRecordPolicyIsTheOneTheControllerHolds(t *testing.T) { } } -// Review round 1, item 2 (ADR 0539 §15): a node that runs the bus with no readable want compares the new fingerprint with the one -// it runs, from its have: the same fingerprint is not a new one (written by the rules), another is barred. +// A node that runs the bus with no readable want gets its want by the rules, at the fingerprint it runs or at a new one alike: the +// bus has no bar of its own (ADR 0601 §1). func TestTheBusWithNoWantIsComparedWithWhatItRuns(t *testing.T) { m, n, _ := movingTo(delivery.JudgedNeither) m.Manifest.Provides = []catalogue.Offer{{Name: "mesh-bus"}} @@ -899,8 +884,8 @@ func TestTheBusWithNoWantIsComparedWithWhatItRuns(t *testing.T) { t.Errorf("the bus runs F with no want and was not given one: %+v %v", w, ok) } n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} - if w, ok := Wants(m, n, nil, nil).Wants["ace"]; ok { - t.Errorf("the bus was moved to a new fingerprint with no want: %+v", w) + if w, ok := Wants(m, n, nil, nil).Wants["ace"]; !ok || w.FP != fNew { + t.Errorf("the bus running an older fingerprint with no want was not given its want by the rules: %+v %v", w, ok) } } diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index 1c14098a..6e873e02 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -5188,7 +5188,7 @@ func TestWhereAModulesUpgradePolicyComesFrom(t *testing.T) { t.Errorf("%s: %s from %s, want %s from %s", c.name, policy, from, c.policy, c.from) } // ADR 0565 clause 7 (issue 1953 L5-S3): a person's choice wins over each source; none leaves the derivation as it - // was. The assigner's upgrade verb refuses a choice for the bus (wants_tools.go setPolicy). + // was. The bus is no exception (ADR 0601 §1). person := &catalogue.PersonPolicy{V: catalogue.PersonPolicyVersion, Module: c.m.Module, Policy: catalogue.PolicyTogether, Why: "one zone", By: "person.jochen"} policy, from, _, by := catalogue.Upgrade(c.m, person) wantPolicy, wantFrom, wantBy := catalogue.PolicyTogether, catalogue.FromPerson, "person.jochen"