Merge feat/modules-name-no-node into bulk-b

This commit is contained in:
jochen
2026-10-22 21:39:02 +02:00
7 changed files with 141 additions and 141 deletions
@@ -159,11 +159,11 @@ func aMesh(t *testing.T) (*Config, *memStore) {
"managed":{"kind":"","default":{},"why":"","type":"object","description":"managed settings","trusted":true,"predefined":"agent-managed-settings"}}}`)
m.mirror(t, DeclaredConfigRead, "old", `{"module":"old","settings":{"workers":{"kind":"preference","default":4,"why":"two per core"}}}`)
m.mirror(t, PredefinedRead, PredefinedMirrorKey, theList)
for _, node := range []string{"g14", "novox"} {
for _, node := range []string{"node-b", "node-c"} {
m.assign(t, node, "dunst")
}
m.assign(t, "g14", "claude-code")
m.assign(t, "g14", "old")
m.assign(t, "node-b", "claude-code")
m.assign(t, "node-b", "old")
clock := time.Date(2026, 10, 14, 19, 0, 0, 0, time.UTC)
c := &Config{Store: m, Now: func() time.Time { clock = clock.Add(time.Second); return clock }, Logf: t.Logf}
if _, err := c.Reconcile(); err != nil {
@@ -179,7 +179,7 @@ func set(t *testing.T, c *Config, call Call) *SetAnswer {
call.Action = "set"
}
if call.Caller == "" {
call.Caller = "g14.node-tools"
call.Caller = "node-b.node-tools"
}
if call.Why == "" {
call.Why = "the operator asked for it"
@@ -204,42 +204,42 @@ func expect(t *testing.T, m *memStore, node string, value, source string) {
// cleared falls back to the one below.
func TestEachOfTheFourSourcesResolvesInTurnAndEachClearedFallsBack(t *testing.T) {
c, m := aMesh(t)
expect(t, m, "g14", "10", SourceDefault)
expect(t, m, "novox", "10", SourceDefault)
expect(t, m, "node-b", "10", SourceDefault)
expect(t, m, "node-c", "10", SourceDefault)
a := set(t, c, Call{Key: "font-size", Value: 12.0})
expect(t, m, "g14", "12", SourceMesh)
expect(t, m, "novox", "12", SourceMesh)
expect(t, m, "node-b", "12", SourceMesh)
expect(t, m, "node-c", "12", SourceMesh)
if len(a.Moved) != 2 || a.Level.Kind != LevelMesh {
t.Errorf("a mesh value moved %+v", a.Moved)
}
a = set(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
expect(t, m, "g14", "14", SourceNode)
expect(t, m, "novox", "12", SourceMesh)
if len(a.Moved) != 1 || a.Moved[0].Node != "g14" || string(a.Moved[0].From) != "12" || string(a.Moved[0].To) != "14" {
t.Errorf("g14's node value moved %+v", a.Moved)
a = set(t, c, Call{Key: "font-size", Value: 14.0, Node: "node-b"})
expect(t, m, "node-b", "14", SourceNode)
expect(t, m, "node-c", "12", SourceMesh)
if len(a.Moved) != 1 || a.Moved[0].Node != "node-b" || string(a.Moved[0].From) != "12" || string(a.Moved[0].To) != "14" {
t.Errorf("node-b's node value moved %+v", a.Moved)
}
a = set(t, c, Call{Key: "font-size", Value: 16.0, Node: "g14", Module: "dunst"})
expect(t, m, "g14", "16", SourceModule)
expect(t, m, "novox", "12", SourceMesh)
a = set(t, c, Call{Key: "font-size", Value: 16.0, Node: "node-b", Module: "dunst"})
expect(t, m, "node-b", "16", SourceModule)
expect(t, m, "node-c", "12", SourceMesh)
if len(a.Moved) != 1 || a.Moved[0].Source != SourceModule {
t.Errorf("dunst's own value moved %+v", a.Moved)
}
// A value set below the module's own moves nothing it overrides.
if a = set(t, c, Call{Key: "font-size", Value: 18.0, Node: "g14"}); len(a.Moved) != 0 {
if a = set(t, c, Call{Key: "font-size", Value: 18.0, Node: "node-b"}); len(a.Moved) != 0 {
t.Errorf("a node value under a module's own moved %+v", a.Moved)
}
set(t, c, Call{Action: "clear", Key: "font-size", Node: "g14", Module: "dunst"})
expect(t, m, "g14", "18", SourceNode)
set(t, c, Call{Action: "clear", Key: "font-size", Node: "g14"})
expect(t, m, "g14", "12", SourceMesh)
set(t, c, Call{Action: "clear", Key: "font-size", Node: "node-b", Module: "dunst"})
expect(t, m, "node-b", "18", SourceNode)
set(t, c, Call{Action: "clear", Key: "font-size", Node: "node-b"})
expect(t, m, "node-b", "12", SourceMesh)
set(t, c, Call{Action: "clear", Key: "font-size"})
expect(t, m, "g14", "10", SourceDefault)
expect(t, m, "novox", "10", SourceDefault)
expect(t, m, "node-b", "10", SourceDefault)
expect(t, m, "node-c", "10", SourceDefault)
}
// **A value naming no predefined key ignores mesh and node config** (ADR 0348 §2): it resolves from its default and
@@ -247,20 +247,20 @@ func TestEachOfTheFourSourcesResolvesInTurnAndEachClearedFallsBack(t *testing.T)
func TestAValueNamingNoPredefinedKeyResolvesFromItsDefaultAndItsOwnValueOnly(t *testing.T) {
c, m := aMesh(t)
set(t, c, Call{Key: "font-size", Value: 12.0})
set(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
if r := m.resolved(t, "g14", "dunst", "width"); string(r.Value) != "300" || r.Source != SourceDefault {
set(t, c, Call{Key: "font-size", Value: 14.0, Node: "node-b"})
if r := m.resolved(t, "node-b", "dunst", "width"); string(r.Value) != "300" || r.Source != SourceDefault {
t.Errorf("width is %s from %s, want its default", r.Value, r.Source)
}
set(t, c, Call{Key: "width", Value: 400.0, Node: "g14", Module: "dunst"})
if r := m.resolved(t, "g14", "dunst", "width"); string(r.Value) != "400" || r.Source != SourceModule {
set(t, c, Call{Key: "width", Value: 400.0, Node: "node-b", Module: "dunst"})
if r := m.resolved(t, "node-b", "dunst", "width"); string(r.Value) != "400" || r.Source != SourceModule {
t.Errorf("width is %s from %s, want dunst's own value", r.Value, r.Source)
}
// A module not yet moved, in the settings form: its default and its own value.
if r := m.resolved(t, "g14", "old", "workers"); string(r.Value) != "4" || r.Source != SourceDefault {
if r := m.resolved(t, "node-b", "old", "workers"); string(r.Value) != "4" || r.Source != SourceDefault {
t.Errorf("old's workers is %s from %s", r.Value, r.Source)
}
set(t, c, Call{Key: "workers", Value: 8.0, Node: "g14", Module: "old"})
if r := m.resolved(t, "g14", "old", "workers"); string(r.Value) != "8" || r.Source != SourceModule {
set(t, c, Call{Key: "workers", Value: 8.0, Node: "node-b", Module: "old"})
if r := m.resolved(t, "node-b", "old", "workers"); string(r.Value) != "8" || r.Source != SourceModule {
t.Errorf("old's workers is %s from %s", r.Value, r.Source)
}
}
@@ -268,15 +268,15 @@ func TestAValueNamingNoPredefinedKeyResolvesFromItsDefaultAndItsOwnValueOnly(t *
// **A required value nothing sets is answered unset** (ADR 0348 §4), and once set, it is the value.
func TestARequiredValueNothingSetsIsAnsweredUnset(t *testing.T) {
c, m := aMesh(t)
r := m.resolved(t, "g14", "dunst", "origin")
r := m.resolved(t, "node-b", "dunst", "origin")
if r.Source != SourceUnset || !r.Required || len(r.Value) != 0 {
t.Errorf("an unset required value resolves as %+v", r)
}
set(t, c, Call{Key: "origin", Value: "home", Node: "g14", Module: "dunst"})
if r = m.resolved(t, "g14", "dunst", "origin"); r.Source != SourceModule || string(r.Value) != `"home"` {
set(t, c, Call{Key: "origin", Value: "home", Node: "node-b", Module: "dunst"})
if r = m.resolved(t, "node-b", "dunst", "origin"); r.Source != SourceModule || string(r.Value) != `"home"` {
t.Errorf("a set required value resolves as %+v", r)
}
if r = m.resolved(t, "novox", "dunst", "origin"); r.Source != SourceUnset {
if r = m.resolved(t, "node-c", "dunst", "origin"); r.Source != SourceUnset {
t.Errorf("another node's required value resolves as %+v", r)
}
}
@@ -291,30 +291,30 @@ func TestTheVerbRefusesWhatADeclarationDoesNotAllow(t *testing.T) {
call Call
want string
}{
{"a key the module does not declare", Call{Key: "colour", Value: "red", Node: "g14", Module: "dunst"},
{"a key the module does not declare", Call{Key: "colour", Value: "red", Node: "node-b", Module: "dunst"},
"it declares: font-size, origin, position, width"},
{"a key not on the predefined list, at the mesh", Call{Key: "width", Value: 400.0},
"the predefined keys are: agent-managed-settings, font-size"},
{"a key not on the predefined list, at a node", Call{Key: "width", Value: 400.0, Node: "g14"}, "is not one"},
{"a key not on the predefined list, at a node", Call{Key: "width", Value: 400.0, Node: "node-b"}, "is not one"},
{"a module value without a node", Call{Key: "width", Value: 400.0, Module: "dunst"},
"no module value for the whole mesh"},
{"a module on a node it is not assigned to", Call{Key: "width", Value: 400.0, Node: "ace", Module: "dunst"},
"it is assigned to: g14, novox"},
{"a module not registered", Call{Key: "width", Value: 400.0, Node: "g14", Module: "nobody"}, "not a registered module"},
{"a module on a node it is not assigned to", Call{Key: "width", Value: 400.0, Node: "node-a", Module: "dunst"},
"it is assigned to: node-b, node-c"},
{"a module not registered", Call{Key: "width", Value: 400.0, Node: "node-b", Module: "nobody"}, "not a registered module"},
{"a value of another type", Call{Key: "font-size", Value: "12"}, `"12" is not a number`},
{"a value below its minimum", Call{Key: "font-size", Value: 2.0, Node: "g14"}, "below the minimum 6"},
{"a value above its maximum", Call{Key: "font-size", Value: 99.0, Node: "g14", Module: "dunst"}, "above the maximum 72"},
{"a value not in its enum", Call{Key: "position", Value: "middle", Node: "g14", Module: "dunst"}, "is not one of"},
{"a value not matching its pattern", Call{Key: "origin", Value: "Home 1", Node: "g14", Module: "dunst"},
{"a value below its minimum", Call{Key: "font-size", Value: 2.0, Node: "node-b"}, "below the minimum 6"},
{"a value above its maximum", Call{Key: "font-size", Value: 99.0, Node: "node-b", Module: "dunst"}, "above the maximum 72"},
{"a value not in its enum", Call{Key: "position", Value: "middle", Node: "node-b", Module: "dunst"}, "is not one of"},
{"a value not matching its pattern", Call{Key: "origin", Value: "Home 1", Node: "node-b", Module: "dunst"},
"does not match the pattern"},
{"a trusted value at the mesh, not from the terminal", Call{Key: "agent-managed-settings",
Value: map[string]any{"a": 1.0}}, "set only at the controller's terminal"},
{"a trusted module value, not from the terminal", Call{Key: "managed", Value: map[string]any{},
Node: "g14", Module: "claude-code"}, "set only at the controller's terminal"},
Node: "node-b", Module: "claude-code"}, "set only at the controller's terminal"},
{"a change without why", Call{Key: "font-size", Value: 12.0, Why: " "}, "say why"},
{"an unproven caller", Call{Key: "font-size", Value: 12.0, Caller: Unproven}, "no proven caller"},
{"by from anyone but the controller", Call{Key: "font-size", Value: 12.0, By: "jochen"}, "only the controller says"},
{"a clear of nothing set", Call{Action: "clear", Key: "font-size", Node: "g14"}, "nothing is set"},
{"a clear of nothing set", Call{Action: "clear", Key: "font-size", Node: "node-b"}, "nothing is set"},
} {
t.Run(tc.name, func(t *testing.T) {
call := tc.call
@@ -322,7 +322,7 @@ func TestTheVerbRefusesWhatADeclarationDoesNotAllow(t *testing.T) {
call.Action = "set"
}
if call.Caller == "" {
call.Caller = "g14.node-tools"
call.Caller = "node-b.node-tools"
}
if call.Why == "" {
call.Why = "a test"
@@ -349,7 +349,7 @@ func TestATrustedValueFromTheTerminalIsSetWithWhoGaveIt(t *testing.T) {
if string(a.Value) != `{"a":1,"b":2}` {
t.Errorf("the value is written as %s", a.Value)
}
r := m.resolved(t, "g14", "claude-code", "managed")
r := m.resolved(t, "node-b", "claude-code", "managed")
if r.Source != SourceMesh || !r.Trusted || string(r.Value) != `{"a":1,"b":2}` {
t.Errorf("claude-code's managed settings resolve as %+v", r)
}
@@ -366,8 +366,8 @@ func TestATrustedValueFromTheTerminalIsSetWithWhoGaveIt(t *testing.T) {
func TestEveryChangeIsKeptWithWhoAndWhyAndRaisesConfigChanged(t *testing.T) {
c, m := aMesh(t)
m.events = nil
set(t, c, Call{Key: "font-size", Value: 12.0, Caller: "novox.node-tools", Why: "bigger on every desk"})
set(t, c, Call{Action: "clear", Key: "font-size", Caller: "g14.node-tools", Why: "back to the default"})
set(t, c, Call{Key: "font-size", Value: 12.0, Caller: "node-c.node-tools", Why: "bigger on every desk"})
set(t, c, Call{Action: "clear", Key: "font-size", Caller: "node-b.node-tools", Why: "back to the default"})
history, err := c.History(Call{})
if err != nil {
@@ -379,22 +379,22 @@ func TestEveryChangeIsKeptWithWhoAndWhyAndRaisesConfigChanged(t *testing.T) {
changes = append(changes, h)
}
}
if len(changes) != 2 || changes[0].By != "novox.node-tools" || changes[0].Why != "bigger on every desk" ||
changes[1].Action != "clear" || changes[1].By != "g14.node-tools" || string(changes[1].Was) != "12" {
if len(changes) != 2 || changes[0].By != "node-c.node-tools" || changes[0].Why != "bigger on every desk" ||
changes[1].Action != "clear" || changes[1].By != "node-b.node-tools" || string(changes[1].Was) != "12" {
t.Fatalf("the history holds %+v", changes)
}
if len(m.handActs) != 2 || !strings.Contains(m.handActs[0], "config set font-size at mesh config to 12, by novox.node-tools | bigger on every desk") {
if len(m.handActs) != 2 || !strings.Contains(m.handActs[0], "config set font-size at mesh config to 12, by node-c.node-tools | bigger on every desk") {
t.Errorf("the hand-act log holds %q", m.handActs)
}
if len(m.events) != 2 || len(m.events[0].Moved) != 2 || m.events[0].Key != "font-size" || m.events[1].Action != "clear" {
t.Errorf("config-changed was raised as %+v", m.events)
}
// Narrowed: g14's dunst sees both, through what they moved; a node that holds no dunst sees neither.
if h, _ := c.History(Call{Module: "dunst", Node: "g14"}); len(h) < 2 {
t.Errorf("dunst on g14's history holds %d changes", len(h))
// Narrowed: node-b's dunst sees both, through what they moved; a node that holds no dunst sees neither.
if h, _ := c.History(Call{Module: "dunst", Node: "node-b"}); len(h) < 2 {
t.Errorf("dunst on node-b's history holds %d changes", len(h))
}
if h, _ := c.History(Call{Node: "ace"}); len(h) != 0 {
t.Errorf("ace's history holds %+v", h)
if h, _ := c.History(Call{Node: "node-a"}); len(h) != 0 {
t.Errorf("node-a's history holds %+v", h)
}
}
@@ -406,7 +406,7 @@ func TestAChangeTheHandActLogRefusesIsSaid(t *testing.T) {
if len(a.Unsaid) != 1 || !strings.Contains(a.Unsaid[0], "hand-act log does not hold it") {
t.Errorf("the answer says %q", a.Unsaid)
}
expect(t, m, "g14", "12", SourceMesh)
expect(t, m, "node-b", "12", SourceMesh)
}
// **The newest list of predefined keys is published, and an older one heard later is not** (ADR 0348 §7).
@@ -447,7 +447,7 @@ func TestTheNewestListOfPredefinedKeysIsPublished(t *testing.T) {
// listing says why until the value is cleared.
func TestAListDroppingAKeyThatHoldsAValueIsWithheldUntilItIsCleared(t *testing.T) {
c, m := aMesh(t)
set(t, c, Call{Key: "font-size", Value: 12.0, Node: "novox"})
set(t, c, Call{Key: "font-size", Value: 12.0, Node: "node-c"})
m.mirror(t, PredefinedRead, PredefinedMirrorKey, `{"keys":{"agent-managed-settings":{"type":"object",
"description":"managed settings","trusted":true}},"commit":"cccccccccc","generation":9}`)
if _, err := c.Reconcile(); err != nil {
@@ -458,11 +458,11 @@ func TestAListDroppingAKeyThatHoldsAValueIsWithheldUntilItIsCleared(t *testing.T
t.Fatal(err)
}
if _, kept := listed.Predefined["font-size"]; !kept ||
!strings.Contains(listed.Withheld, "drops font-size (still set as node config on novox)") {
!strings.Contains(listed.Withheld, "drops font-size (still set as node config on node-c)") {
t.Fatalf("the list was published over a value it drops: withheld %q, published %v", listed.Withheld,
joinKeys(listed.Predefined))
}
set(t, c, Call{Action: "clear", Key: "font-size", Node: "novox"})
set(t, c, Call{Action: "clear", Key: "font-size", Node: "node-c"})
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
@@ -481,19 +481,19 @@ func TestAnAssignmentMovesTheResolvedValuesAndAQuietPassWritesNothing(t *testing
t.Fatalf("a pass over unchanged sources moved %+v (%v)", r.Moved, err)
}
m.events = nil
m.assign(t, "ace", "dunst")
m.assign(t, "node-a", "dunst")
if r, _ = c.Reconcile(); len(r.Moved) != 4 {
t.Errorf("an assignment moved %+v", r.Moved)
}
expect(t, m, "ace", "10", SourceDefault)
expect(t, m, "node-a", "10", SourceDefault)
if len(m.events) != 1 || m.events[0].Action != "resolve" {
t.Errorf("an assignment raised %+v", m.events)
}
_ = m.Delete(AssignmentsRead, "ace.dunst")
_ = m.Delete(AssignmentsRead, "node-a.dunst")
if r, _ = c.Reconcile(); len(r.Moved) != 4 || !r.Moved[0].Gone {
t.Errorf("an unassignment moved %+v", r.Moved)
}
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "dunst", "font-size")); e != nil {
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "dunst", "font-size")); e != nil {
t.Error("an unassigned module keeps its resolved values")
}
}
@@ -523,17 +523,17 @@ func TestTheDeclaredConfigMirrorsTwoShapesReadTheSame(t *testing.T) {
func TestAListingNarrowsByModuleAndNode(t *testing.T) {
c, _ := aMesh(t)
set(t, c, Call{Key: "font-size", Value: 12.0})
set(t, c, Call{Key: "font-size", Value: 14.0, Node: "novox"})
l, err := c.List(Call{Module: "dunst", Node: "g14"})
set(t, c, Call{Key: "font-size", Value: 14.0, Node: "node-c"})
l, err := c.List(Call{Module: "dunst", Node: "node-b"})
if err != nil {
t.Fatal(err)
}
if len(l.Resolved) != 4 || len(l.Set) != 1 || l.Set[0].Level.Kind != LevelMesh || l.Predefined != nil {
t.Errorf("dunst on g14 lists %d resolved and %+v", len(l.Resolved), l.Set)
t.Errorf("dunst on node-b lists %d resolved and %+v", len(l.Resolved), l.Set)
}
l, _ = c.List(Call{Node: "novox"})
l, _ = c.List(Call{Node: "node-c"})
if len(l.Resolved) != 4 || len(l.Set) != 2 {
t.Errorf("novox lists %d resolved and %+v", len(l.Resolved), l.Set)
t.Errorf("node-c lists %d resolved and %+v", len(l.Resolved), l.Set)
}
}
@@ -556,20 +556,20 @@ func TestTheVerbsArgumentsAreReadStrictly(t *testing.T) {
// resolves nothing and takes the module's resolved values away, as a removed key does.
func TestAnEndedAssignmentIsNotAnAssignment(t *testing.T) {
c, m := aMesh(t)
m.assign(t, "ace", "dunst")
m.assign(t, "node-a", "dunst")
if r, err := c.Reconcile(); err != nil || len(r.Moved) != 4 {
t.Fatalf("the assignment moved %+v (%v)", r.Moved, err)
}
for _, ended := range []string{`"ended":"2026-10-18T01:00:00Z"`, `"state":"pending"`} {
m.mirror(t, AssignmentsRead, "ace.dunst", `{"node":"ace","module":"dunst","assigned":"2026-10-14T00:00:00Z",`+ended+`}`)
m.mirror(t, AssignmentsRead, "node-a.dunst", `{"node":"node-a","module":"dunst","assigned":"2026-10-14T00:00:00Z",`+ended+`}`)
r, err := c.Reconcile()
if err != nil || len(r.Moved) != 4 || !r.Moved[0].Gone {
t.Errorf("%s moved %+v (%v)", ended, r.Moved, err)
}
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "dunst", "font-size")); e != nil {
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "dunst", "font-size")); e != nil {
t.Errorf("%s: the module keeps its resolved values", ended)
}
m.assign(t, "ace", "dunst")
m.assign(t, "node-a", "dunst")
if r, _ = c.Reconcile(); len(r.Moved) != 4 {
t.Fatalf("the assignment standing again moved %+v", r.Moved)
}
@@ -579,14 +579,14 @@ func TestAnEndedAssignmentIsNotAnAssignment(t *testing.T) {
// Issue 1098: the fields the controller adds to an assignment change nothing, under the same key.
func TestAnAssignmentCarriesBuildCommitAndRolloutWithoutChangingItsKey(t *testing.T) {
c, m := aMesh(t)
m.mirror(t, AssignmentsRead, "ace.dunst", `{"node":"ace","module":"dunst","assigned":"2026-10-14T00:00:00Z",`+
m.mirror(t, AssignmentsRead, "node-a.dunst", `{"node":"node-a","module":"dunst","assigned":"2026-10-14T00:00:00Z",`+
`"build":"b-1","artifact":{"name":"dunst","version":"1.2"},"commit":"abc123","rollout":"r-9",`+
`"since":"2026-10-18T00:00:00Z","ended":"","state":"","future":{"x":1}}`)
r, err := c.Reconcile()
if err != nil || len(r.Moved) != 4 {
t.Fatalf("an assignment with the new fields moved %+v (%v)", r.Moved, err)
}
expect(t, m, "ace", "10", SourceDefault)
expect(t, m, "node-a", "10", SourceDefault)
if r, _ = c.Reconcile(); len(r.Moved) != 0 {
t.Errorf("a quiet pass moved %+v", r.Moved)
}
@@ -35,7 +35,7 @@ type standIn struct {
func newStandIn() *standIn {
return &standIn{asks: map[string]asks.Ask{}, records: map[string][]byte{}, kinds: []stdio.KindHeld{
{Seat: asks.ChannelSeat, Kind: "telegram", Module: "telegram", Node: "novox",
{Seat: asks.ChannelSeat, Kind: "telegram", Module: "telegram", Node: "node-c",
Capabilities: []string{"choice", "exact-render", "verified-sender"}}}}
}
@@ -123,7 +123,7 @@ func propose(t *testing.T, c *Config, call Call) *ProposeAnswer {
t.Helper()
call.Action = "propose"
if call.Caller == "" {
call.Caller = "g14.node-tools"
call.Caller = "node-b.node-tools"
}
if call.Why == "" {
call.Why = "the agents need the new rule"
@@ -176,7 +176,7 @@ func TestATrustedValueIsProposedAskedUnderItsOwnNameAndSetOnTheOperatorsApprove(
t.Fatalf("a proposal set something: %d events, %d hand-acts", len(m.events), len(m.handActs))
}
if !strings.Contains(a.Router, "took the ask") || a.Proposal == "" || len(a.Reaches) != 1 ||
a.Reaches[0] != "claude-code on g14" {
a.Reaches[0] != "claude-code on node-b" {
t.Errorf("the proposal answered %+v", a)
}
// Asked under mesh-config's own name, on the operator channel.
@@ -234,12 +234,12 @@ func TestATrustedValueIsProposedAskedUnderItsOwnNameAndSetOnTheOperatorsApprove(
}
v, set := storedValue(t, m, "mesh.agent-managed-settings")
if !set || string(v.Value) != `{"permissions":{"deny":["Bash(ssh:*)"]}}` || v.By != "the operator" ||
v.Via != "g14.node-tools" || !strings.Contains(v.Approved, "approved by the operator, as telegram identity 4242, "+
v.Via != "node-b.node-tools" || !strings.Contains(v.Approved, "approved by the operator, as telegram identity 4242, "+
"via telegram (user id verified)") || !strings.Contains(v.Approved, "ask "+a.Proposal) ||
!strings.Contains(v.Approved, "proposed by g14.node-tools") {
!strings.Contains(v.Approved, "proposed by node-b.node-tools") {
t.Fatalf("the value is %+v", v)
}
if res := m.resolved(t, "g14", "claude-code", "managed"); res.Source != SourceMesh ||
if res := m.resolved(t, "node-b", "claude-code", "managed"); res.Source != SourceMesh ||
string(res.Value) != string(v.Value) {
t.Errorf("claude-code's managed settings resolve as %+v", res)
}
@@ -285,14 +285,14 @@ func TestAnApproveIsRefusedWhenTheStoredValueChangedSince(t *testing.T) {
}
// And a clear since: the value it was shown against (a value) is no longer stored.
set(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
a = propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
set(t, c, Call{Action: "clear", Key: "font-size", Node: "g14"})
set(t, c, Call{Key: "font-size", Value: 12.0, Node: "node-b"})
a = propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "node-b"})
set(t, c, Call{Action: "clear", Key: "font-size", Node: "node-b"})
r.choose(t, a.Proposal, OptionApprove, nil)
if said, _ = c.Decide(); len(said) != 1 || !strings.Contains(said[0], "no value set") {
t.Fatalf("the Approve after a clear decided %v", said)
}
if _, isSet := storedValue(t, m, "node.g14.font-size"); isSet {
if _, isSet := storedValue(t, m, "node.node-b.font-size"); isSet {
t.Error("an Approve against a cleared value set it")
}
}
@@ -313,7 +313,7 @@ func TestAnApproveIsRefusedWhenTheProposalChangedAfterItWasAsked(t *testing.T) {
{"the value it was shown against", func(p *Proposal) {
p.Was, p.HadValue = json.RawMessage(`{"x":1}`), true
}, "the value it was shown against"},
{"its level", func(p *Proposal) { p.Level = Level{Kind: LevelNode, Node: "g14"} }, "not the ask the person was shown"},
{"its level", func(p *Proposal) { p.Level = Level{Kind: LevelNode, Node: "node-b"} }, "not the ask the person was shown"},
} {
t.Run(tc.name, func(t *testing.T) {
c, m, r := aProposingMesh(t)
@@ -378,7 +378,7 @@ func TestDeclineExpiryAndTheRoutersRefusalSetNothing(t *testing.T) {
}
// Ended without a choice: the router's word.
a = propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
a = propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "node-b"})
c.HeardEnd(asks.Warrant{Ask: a.Proposal, Asker: Module, Outcome: asks.OutcomeExpired, At: time.Now()})
if p := storedProposal(t, m, a.Proposal); p.State != ProposalEnded || !strings.Contains(p.Outcome, "expired") {
t.Errorf("the expired proposal is %+v", p)
@@ -386,7 +386,7 @@ func TestDeclineExpiryAndTheRoutersRefusalSetNothing(t *testing.T) {
// Ended, and the router never recorded it: discarded once its time is past.
r.noRecord = true
a = propose(t, c, Call{Key: "font-size", Value: 13.0, Node: "novox"})
a = propose(t, c, Call{Key: "font-size", Value: 13.0, Node: "node-c"})
if !strings.Contains(a.Router, "has not said yet") {
t.Errorf("an ask the router has not taken answered %q", a.Router)
}
@@ -402,7 +402,7 @@ func TestDeclineExpiryAndTheRoutersRefusalSetNothing(t *testing.T) {
// Refused by the router at once: said to the proposer.
c2, m2, r2 := aProposingMesh(t)
r2.refuseWith = "no channel can carry an approval"
_, err := c2.Propose(Call{Action: "propose", Key: "font-size", Value: 12.0, Caller: "g14.node-tools", Why: "w"})
_, err := c2.Propose(Call{Action: "propose", Key: "font-size", Value: 12.0, Caller: "node-b.node-tools", Why: "w"})
if err == nil || !strings.Contains(err.Error(), "no channel can carry an approval") {
t.Fatalf("the router's refusal answered %v", err)
}
@@ -426,11 +426,11 @@ func TestAProposalIsJudgedAndRefusedBeforeTheOperatorIsAsked(t *testing.T) {
call Call
want string
}{
{"a key not declared", Call{Key: "colour", Value: "red", Node: "g14", Module: "dunst"}, "it declares:"},
{"a key not declared", Call{Key: "colour", Value: "red", Node: "node-b", Module: "dunst"}, "it declares:"},
{"a value failing validation", Call{Key: "font-size", Value: 99.0}, "above the maximum 72"},
{"a module value without a node", Call{Key: "width", Value: 400.0, Module: "dunst"}, "no module value for the whole mesh"},
{"nothing that would change", Call{Key: "font-size", Value: 12.0}, "already holds exactly 12"},
{"a clear of nothing", Call{Key: "font-size", Clear: true, Node: "g14"}, "nothing to clear"},
{"a clear of nothing", Call{Key: "font-size", Clear: true, Node: "node-b"}, "nothing to clear"},
{"a clear with a value", Call{Key: "font-size", Clear: true, Value: 1.0}, "a clear carries no value"},
{"a secret's shape", Call{Key: "agent-managed-settings", Value: map[string]any{"env": map[string]any{
"API_TOKEN": "ghp_abcdefghijklmnopqrstuvwxyz0123456789"}}}, "leaves the mesh nowhere"},
@@ -442,7 +442,7 @@ func TestAProposalIsJudgedAndRefusedBeforeTheOperatorIsAsked(t *testing.T) {
call := tc.call
call.Action = "propose"
if call.Caller == "" {
call.Caller = "g14.node-tools"
call.Caller = "node-b.node-tools"
}
if call.Why == "" {
call.Why = "a test"
@@ -470,20 +470,20 @@ func TestAProposalIsJudgedAndRefusedBeforeTheOperatorIsAsked(t *testing.T) {
}
r.refuse = nil
first := propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
if _, err := c.Propose(Call{Action: "propose", Key: "font-size", Value: 14.0, Node: "g14", Caller: "g", Why: "w"}); err == nil ||
first := propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "node-b"})
if _, err := c.Propose(Call{Action: "propose", Key: "font-size", Value: 14.0, Node: "node-b", Caller: "g", Why: "w"}); err == nil ||
!strings.Contains(err.Error(), "proposed already, as "+first.Proposal) {
t.Errorf("the same change twice: %v", err)
}
// A newer proposal for the same place replaces the older (ADR 0277 §10), and is not counted twice.
newer := propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "g14"})
newer := propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "node-b"})
if len(newer.Replaced) != 1 || newer.Replaced[0] != first.Proposal ||
storedProposal(t, m, first.Proposal).State != ProposalEnded {
t.Errorf("the newer proposal replaced %v", newer.Replaced)
}
propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "novox"})
propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "node-c"})
propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
if _, err := c.Propose(Call{Action: "propose", Key: "width", Value: 400.0, Node: "g14", Module: "dunst",
if _, err := c.Propose(Call{Action: "propose", Key: "width", Value: 400.0, Node: "node-b", Module: "dunst",
Caller: "g", Why: "w"}); err == nil || !strings.Contains(err.Error(), "3 proposals already wait") {
t.Errorf("a fourth open ask: %v", err)
}
@@ -496,16 +496,16 @@ func TestAProposalIsJudgedAndRefusedBeforeTheOperatorIsAsked(t *testing.T) {
// naming the proposal as the way (ADR 0277 §2).
func TestAClearIsProposedAndATrustedSetNamesTheProposal(t *testing.T) {
c, m, r := aProposingMesh(t)
_, err := c.Change(Call{Action: "set", Key: "agent-managed-settings", Value: map[string]any{}, Caller: "g14.node-tools",
_, err := c.Change(Call{Action: "set", Key: "agent-managed-settings", Value: map[string]any{}, Caller: "node-b.node-tools",
Why: "w"})
if err == nil || !strings.Contains(err.Error(), "propose it instead") {
t.Errorf("a trusted set through the verb: %v", err)
}
set(t, c, Call{Key: "managed", Value: map[string]any{"a": 1.0}, Node: "g14", Module: "claude-code",
set(t, c, Call{Key: "managed", Value: map[string]any{"a": 1.0}, Node: "node-b", Module: "claude-code",
Caller: ControllerCaller, By: "jochen"})
a := propose(t, c, Call{Key: "managed", Clear: true, Node: "g14", Module: "claude-code"})
a := propose(t, c, Call{Key: "managed", Clear: true, Node: "node-b", Module: "claude-code"})
ask := r.asks[a.Proposal]
if !strings.HasPrefix(ask.Headline, "Clear managed for claude-code on g14") ||
if !strings.HasPrefix(ask.Headline, "Clear managed for claude-code on node-b") ||
!strings.Contains(ask.Explanation, `- managed (was: {"a":1})`) {
t.Errorf("the clear asks %q:\n%s", ask.Headline, ask.Explanation)
}
@@ -513,7 +513,7 @@ func TestAClearIsProposedAndATrustedSetNamesTheProposal(t *testing.T) {
if said, _ := c.Decide(); len(said) != 1 || !strings.Contains(said[0], "is cleared") {
t.Fatalf("the clear decided %v", said)
}
if _, isSet := storedValue(t, m, "module.g14.claude-code.managed"); isSet {
if _, isSet := storedValue(t, m, "module.node-b.claude-code.managed"); isSet {
t.Error("an approved clear left the value")
}
h, _ := c.History(Call{Key: "managed"})
@@ -526,7 +526,7 @@ func TestAClearIsProposedAndATrustedSetNamesTheProposal(t *testing.T) {
func TestTheProposalsAreListedAndOneIsReadWhole(t *testing.T) {
c, m, _ := aProposingMesh(t)
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
b := propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
b := propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "node-b"})
rows, err := c.Do(Call{Action: "proposals"})
if err != nil {
t.Fatal(err)
@@ -575,7 +575,7 @@ func TestAnOwnersListOfNamesWithDigitsIsProposed(t *testing.T) {
c, m, r := aProposingMesh(t)
m.mirror(t, DeclaredConfigRead, "mesh-issues", `{"module":"mesh-issues","settings":{
"owners":{"kind":"","default":[],"why":"","type":"list","description":"owners","trusted":true}}}`)
m.assign(t, "novox", "mesh-issues")
m.assign(t, "node-c", "mesh-issues")
raw, err := os.ReadFile(filepath.Join("testdata", "owners-1439.json"))
if err != nil {
t.Fatal(err)
@@ -584,7 +584,7 @@ func TestAnOwnersListOfNamesWithDigitsIsProposed(t *testing.T) {
if err := json.Unmarshal(raw, &owners); err != nil || len(owners) != 135 {
t.Fatalf("testdata holds %d owners (%v), want the 134 defaults plus okioki", len(owners), err)
}
a := propose(t, c, Call{Key: "owners", Value: owners, Node: "novox", Module: "mesh-issues"})
a := propose(t, c, Call{Key: "owners", Value: owners, Node: "node-c", Module: "mesh-issues"})
if a.Proposal == "" || len(r.subjects) != 1 {
t.Fatalf("the owners were not put to the operator: %+v, asked on %v", a, r.subjects)
}
@@ -11,13 +11,13 @@ func TestAModulePulledInHasItsValuesResolvedOnItsNode(t *testing.T) {
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
m.mirror(t, AssignmentsRead, "ace.dunst", `{"node":"ace","module":"dunst","required-by":"desk",`+
m.mirror(t, AssignmentsRead, "node-a.dunst", `{"node":"node-a","module":"dunst","required-by":"desk",`+
`"provisioning":{"templates":{"source":"x","digest":"y"},"files":[]}}`)
if r, err := c.Reconcile(); err != nil || len(r.Moved) != 4 {
t.Fatalf("a pulled-in module's value moved %+v (%v)", r.Moved, err)
}
expect(t, m, "ace", "10", SourceDefault)
_ = m.Delete(AssignmentsRead, "ace.dunst")
expect(t, m, "node-a", "10", SourceDefault)
_ = m.Delete(AssignmentsRead, "node-a.dunst")
if r, _ := c.Reconcile(); len(r.Moved) != 4 || !r.Moved[0].Gone {
t.Errorf("a module no longer pulled in moved %+v", r.Moved)
}
@@ -18,13 +18,13 @@ func n8nPullDeclares() map[string]any {
"description": "the media group", "required": true, "minimum": 1, "maximum": 65535}}}
}
// aHoldingMesh is a proposing mesh with n8n assigned to ace and declaring nothing on the trunk (the case of the
// aHoldingMesh is a proposing mesh with n8n assigned to node-a and declaring nothing on the trunk (the case of the
// issue), and a stand-in forge.
func aHoldingMesh(t *testing.T) (*Config, *memStore, *standIn, *standForge) {
t.Helper()
c, m, r := aProposingMesh(t)
m.mirror(t, DeclaredConfigRead, "n8n", `{"module":"n8n","settings":{}}`)
m.assign(t, "ace", "n8n")
m.assign(t, "node-a", "n8n")
f := &standForge{state: map[string][2]bool{}}
c.Forge = f
if _, err := c.Reconcile(); err != nil {
@@ -60,10 +60,10 @@ func tell(t *testing.T, c *Config, pull, head string, declares map[string]any) *
return a.(*PullDeclaresAnswer)
}
// holdIt proposes media-gid=2000 for n8n on ace held for the pull request and has the operator approve it.
// holdIt proposes media-gid=2000 for n8n on node-a held for the pull request and has the operator approve it.
func holdIt(t *testing.T, c *Config, m *memStore, r *standIn) string {
t.Helper()
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull,
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull,
Why: "n8n's container joins the media group, held for #434"})
r.choose(t, a.Proposal, OptionApprove, nil)
said, err := c.Decide()
@@ -78,7 +78,7 @@ func keptCount(m *memStore) int { k, _ := m.Keys(KeptState); return len(k) }
// 1826/K1: mesh-config takes what a pull request declares from the controller alone, and keeps it per pull request.
func TestAPullRequestsDeclarationsAreTakenFromTheControllerAlone(t *testing.T) {
c, m, _, _ := aHoldingMesh(t)
for _, caller := range []string{"g14.node-tools", Unproven, ""} {
for _, caller := range []string{"node-b.node-tools", Unproven, ""} {
if _, err := c.Do(Call{Action: "pull-declares", Pull: thePull, Head: "abc1234", Declares: n8nPullDeclares(),
Caller: caller}); err == nil || !strings.Contains(err.Error(), "controller") {
t.Errorf("pull-declares from %q: %v", caller, err)
@@ -106,7 +106,7 @@ func TestAPullRequestsDeclarationsAreTakenFromTheControllerAlone(t *testing.T) {
}())
}
// Nothing is resolved from a pull request's declaration.
if r, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); r != nil {
if r, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); r != nil {
t.Errorf("a pull request's declaration was resolved: %s", r.Value)
}
}
@@ -117,11 +117,11 @@ func TestAPullRequestsDeclarationsAreTakenFromTheControllerAlone(t *testing.T) {
func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T) {
c, m, _, _ := aHoldingMesh(t)
try := func(call Call) error {
call.Action, call.Caller, call.Why = "propose", "g14.node-tools", "held for 434"
call.Action, call.Caller, call.Why = "propose", "node-b.node-tools", "held for 434"
_, err := c.Propose(call)
return err
}
base := Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull}
base := Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull}
if err := try(base); err == nil || !strings.Contains(err.Error(), "holds no declaration for "+thePull) {
t.Errorf("without the pull request's declaration: %v", err)
}
@@ -142,7 +142,7 @@ func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T)
"a key not added": {func(c *Call) { c.Key = "other" }, "does not add or change a value"},
"a bad value": {func(c *Call) { c.Value = 0.0 }, "below the minimum"},
"a wrong type": {func(c *Call) { c.Value = "2000" }, "is not a number"},
"another node": {func(c *Call) { c.Node = "g14" }, "is not assigned to g14"},
"another node": {func(c *Call) { c.Node = "node-b" }, "is not assigned to node-b"},
"another pull": {func(c *Call) { c.Pull = "novox/mesh-catalog#435" }, "holds no declaration for"},
"a malformed pull": {func(c *Call) { c.Pull = "434" }, "owner/repository#number"},
} {
@@ -153,8 +153,8 @@ func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T)
}
}
// A set or a clear through the verb takes no pull request.
if _, err := c.Change(Call{Action: "set", Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull,
Caller: "g14.node-tools", Why: "w"}); err == nil || !strings.Contains(err.Error(), "only a proposal") {
if _, err := c.Change(Call{Action: "set", Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull,
Caller: "node-b.node-tools", Why: "w"}); err == nil || !strings.Contains(err.Error(), "only a proposal") {
t.Errorf("a set naming a pull request: %v", err)
}
if keptCount(m) != 0 {
@@ -167,7 +167,7 @@ func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T)
func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "held for #434"})
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "held for #434"})
if len(a.Reaches) != 0 {
t.Errorf("a held value reaches %v", a.Reaches)
}
@@ -175,7 +175,7 @@ func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
if err := ask.Check(c.now()); err != nil {
t.Fatalf("the ask: %v", err)
}
for _, want := range []string{thePull, "merges", "dropped if it closes unmerged", "media-gid", "n8n", "ace"} {
for _, want := range []string{thePull, "merges", "dropped if it closes unmerged", "media-gid", "n8n", "node-a"} {
if !strings.Contains(ask.Explanation, want) {
t.Errorf("the ask does not say %q:\n%s", want, ask.Explanation)
}
@@ -195,10 +195,10 @@ func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
t.Fatalf("decided %v", said)
}
// Kept apart: not a value, not resolved.
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set {
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set {
t.Error("an Approve set the value")
}
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); e != nil {
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); e != nil {
t.Errorf("an Approve resolved the value: %s", e.Value)
}
if keptCount(m) != 1 {
@@ -212,12 +212,12 @@ func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
if len(m.handActs) != 1 || !strings.Contains(m.handActs[0], thePull) {
t.Errorf("hand-acts %q", m.handActs)
}
h, _ := c.History(Call{Module: "n8n", Node: "ace"})
h, _ := c.History(Call{Module: "n8n", Node: "node-a"})
if len(h) != 1 || h[0].Action != ActionHold || h[0].Pull != thePull {
t.Errorf("history %+v", h)
}
// held answers it, at the head it is kept at, to the controller alone.
if _, err := c.Do(Call{Action: "held", Caller: "g14.node-tools", Pulls: []PullAt{{thePull, "abc1234def"}}}); err == nil {
if _, err := c.Do(Call{Action: "held", Caller: "node-b.node-tools", Pulls: []PullAt{{thePull, "abc1234def"}}}); err == nil {
t.Error("held answered a caller that is not the controller")
}
got, err := c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "abc1234def"}}})
@@ -225,7 +225,7 @@ func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
t.Fatal(err)
}
ha := got.(*HeldAnswer)
if len(ha.Held) != 1 || ha.Held[0].Module != "n8n" || ha.Held[0].Node != "ace" || ha.Held[0].Key != "media-gid" ||
if len(ha.Held) != 1 || ha.Held[0].Module != "n8n" || ha.Held[0].Node != "node-a" || ha.Held[0].Key != "media-gid" ||
string(ha.Held[0].Value) != "2000" || !strings.Contains(ha.Held[0].Approved, "approved by the operator") ||
ha.Held[0].Pull != thePull || len(ha.Skipped) != 0 {
t.Errorf("held answered %+v", ha)
@@ -251,7 +251,7 @@ func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
func TestAnApproveOfAHeldValueIsRefusedWhenTheDeclarationChanged(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "w"})
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "w"})
changed := n8nPullDeclares()
changed["n8n"].(map[string]any)["media-gid"].(map[string]any)["maximum"] = 1000.0
tell(t, c, thePull, "def5678abc", changed)
@@ -312,7 +312,7 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
approved := func() string {
raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.ace.n8n.media-gid")
raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.node-a.n8n.media-gid")
var k KeptValue
_ = json.Unmarshal(raw.Value, &k)
return k.Approved
@@ -323,7 +323,7 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes
if len(said) == 0 {
t.Fatal("the merge said nothing")
}
v, set := storedValue(t, m, "module.ace.n8n.media-gid")
v, set := storedValue(t, m, "module.node-a.n8n.media-gid")
if !set || string(v.Value) != "2000" || v.Approved != approved || v.Approved == "" || v.By != "the operator" ||
!strings.Contains(v.Why, thePull) {
t.Fatalf("the merge wrote %+v", v)
@@ -339,7 +339,7 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes
t.Errorf("raised %+v, recorded %q", m.events, m.handActs)
}
// Not resolved before the trunk declares it.
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); e != nil {
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); e != nil {
t.Errorf("resolved before the trunk declares the key: %s", e.Value)
}
m.mirror(t, DeclaredConfigRead, "n8n", `{"module":"n8n","settings":{"media-gid":{"kind":"","default":null,"why":"",`+
@@ -347,10 +347,10 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
if res := m.resolved(t, "ace", "n8n", "media-gid"); string(res.Value) != "2000" || res.Source != SourceModule {
if res := m.resolved(t, "node-a", "n8n", "media-gid"); string(res.Value) != "2000" || res.Source != SourceModule {
t.Errorf("media-gid resolves as %+v", res)
}
l, _ := c.List(Call{Module: "n8n", Node: "ace"})
l, _ := c.List(Call{Module: "n8n", Node: "node-a"})
if len(l.Set) != 1 || l.Set[0].Approved != approved || len(l.Held) != 0 {
t.Errorf("the listing: set %+v held %+v", l.Set, l.Held)
}
@@ -373,7 +373,7 @@ func TestAClosedPullRequestDropsItsHeldValuesAndSaysSo(t *testing.T) {
if len(said) == 0 || !strings.Contains(strings.Join(said, "\n"), "closed unmerged: dropped, nothing was set") {
t.Errorf("said %v", said)
}
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set || keptCount(m) != 0 {
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set || keptCount(m) != 0 {
t.Error("a closed pull request left a value")
}
if len(m.events) != 1 || m.events[0].Action != ActionDrop || m.events[0].Pull != thePull ||
@@ -393,7 +393,7 @@ func TestACloseEventForAMergedPullRequestIsItsMerge(t *testing.T) {
f.state[thePull] = [2]bool{false, true}
f.head = "abc1234def"
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
if v, set := storedValue(t, m, "module.ace.n8n.media-gid"); !set || string(v.Value) != "2000" {
if v, set := storedValue(t, m, "module.node-a.n8n.media-gid"); !set || string(v.Value) != "2000" {
t.Errorf("the merge of a pull request announced closed set %+v", v)
}
}
@@ -415,7 +415,7 @@ func TestTheLookAtTheForgeFindsAMissedMergeOrCloseAndLeavesAnOpenPullRequest(t *
f.head = "abc1234def"
f.state[thePull] = [2]bool{false, true}
c.SweepPulls()
if v, set := storedValue(t, m, "module.ace.n8n.media-gid"); !set || string(v.Value) != "2000" || keptCount(m) != 0 {
if v, set := storedValue(t, m, "module.node-a.n8n.media-gid"); !set || string(v.Value) != "2000" || keptCount(m) != 0 {
t.Errorf("a missed merge: %+v", v)
}
// A missed close.
@@ -454,7 +454,7 @@ func TestTheMergeAppliesOnlyAValueHeldAtTheMergedHead(t *testing.T) {
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: head}, true)
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set || keptCount(m) != 0 ||
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set || keptCount(m) != 0 ||
!strings.Contains(strings.Join(said, "\n"), "dropped, nothing was set") {
t.Errorf("%s: set=%v held=%d said %v", name, set, keptCount(m), said)
}
@@ -471,7 +471,7 @@ func TestALaterHeadWithoutTheDeclarationLeavesNothingToApplyAtTheMerge(t *testin
holdIt(t, c, m, r)
tell(t, c, thePull, "bbbbbbbbbb", map[string]any{})
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "bbbbbbbbbb"}, true)
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set {
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set {
t.Error("a value held for a declaration the merged head lacks was set")
}
}
@@ -1,4 +1,4 @@
Captured answers of the gitea module's `gitea_get_pull_request`, taken 2026-10-22 by fix-1826-catalog@shanks with
Captured answers of the gitea module's `gitea_get_pull_request`, taken 2026-10-22 by fix-1826-catalog@node-d with
`mesh_call gitea.gitea_get_pull_request {"owner":"novox","repo":"mesh-catalog","number":N}` through the mesh MCP server.
- get_pull_request_closed_merged_435.json: pull request 435, closed and merged.
@@ -1,7 +1,7 @@
{
"pull": {
"base": "main",
"body": "Part of design 47's milestone R4c-9 (ADR 0487): the image build module. A container image becomes one more artifact kind the builder builds through a build module, like Go and Node: mesh-build-image builds rootless with buildah, a candidate's check publishes nothing, a trunk build publishes exactly one digest and its build key names the reference. build-agent keeps building and delivering every image until R4g; nothing the operator sees changes. One delivery group on branch `feat/r4c9-image-build` in mesh-catalog and mesh-delivery. After the delivery the lead assigns buildah and mesh-build-image to shanks and ace.\n\nReviewed-by: reviewer-r4c9@shanks at 7b5f7f84acb73aaf8189200548ff69fd9aac04b2",
"body": "Part of design 47's milestone R4c-9 (ADR 0487): the image build module. A container image becomes one more artifact kind the builder builds through a build module, like Go and Node: mesh-build-image builds rootless with buildah, a candidate's check publishes nothing, a trunk build publishes exactly one digest and its build key names the reference. build-agent keeps building and delivering every image until R4g; nothing the operator sees changes. One delivery group on branch `feat/r4c9-image-build` in mesh-catalog and mesh-delivery. After the delivery the lead assigns buildah and mesh-build-image to node-d and node-a.\n\nReviewed-by: reviewer-r4c9@node-d at 7b5f7f84acb73aaf8189200548ff69fd9aac04b2",
"head": "feat/r4c9-image-build",
"head_sha": "7b5f7f84acb73aaf8189200548ff69fd9aac04b2",
"html_url": "http://127.0.0.1:20000/novox/mesh-catalog/pulls/435",
@@ -1,7 +1,7 @@
{
"pull": {
"base": "main",
"body": "Part of design 47's milestone R4d: n8n and the bus run pinned upstream images (ADR 0480).\n\nReviewed-by: reviewer-r4d@shanks at c948f3a61c74257e6e3147effb396d9d6434f491\n",
"body": "Part of design 47's milestone R4d: n8n and the bus run pinned upstream images (ADR 0480).\n\nReviewed-by: reviewer-r4d@node-d at c948f3a61c74257e6e3147effb396d9d6434f491\n",
"head": "feat/r4d",
"head_sha": "c948f3a61c74257e6e3147effb396d9d6434f491",
"html_url": "http://127.0.0.1:20000/novox/mesh-catalog/pulls/434",