Let failed join the seat optional, and let a seeded row carry both changes
mesh/delivery-group group feat/journal-window-on-the-seat delivering: 0 of 2 delivered
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

failed was required, so the controller refused the systemd module running
today and the module serving it was refused by the controller running
today: neither could land first. It is now optional (Verb.Optional, #117).

Two things kept either change from reaching a mesh whose seat rows already
exist: re-seeding added a verb but never an argument to one, and the
console refuses an argument the row does not name, so the journal window
would stay unreachable; and the optional mark is never stored, so a verb
seeded into a row came back required. A row's verb now gains the arguments
the binary names, and the working set takes the optional mark from the
compiled seat, which also keeps mesh-delivery's checks optional once seeded.
This commit is contained in:
jochen
2026-10-07 20:05:01 +02:00
parent d851573793
commit 13b6fc6d97
5 changed files with 199 additions and 4 deletions
+49
View File
@@ -126,6 +126,16 @@ func (i *Inventory) widenProtocol(ctx context.Context, s catalogue.Seat) error {
if s.Name == catalogue.ControllerSeatName && !sameVerb(row.Serves[at], v) {
row.Serves[at] = v
changed = true
continue
}
// **Any other seat's verb gains the arguments the binary names and the row lacks** — additive,
// as the rest of the protocol is. The console refuses an argument the row does not name (issue
// 244), so a holder taught a new argument (the service manager's journal window) would be
// unreachable through it while the row kept the older schema. Nothing the row has is removed or
// made required; the description is the binary's, since it describes the arguments added.
if widened, ok := widenInput(row.Serves[at], v); ok {
row.Serves[at] = widened
changed = true
}
}
if !changed {
@@ -296,6 +306,45 @@ func (i *Inventory) Holdings(ctx context.Context) ([]catalogue.Held, error) {
return out, rows.Err()
}
// widenInput is the row's verb with every input property the binary's names and the row's lacks, and
// the binary's description; and whether anything was added.
func widenInput(row, binary catalogue.Verb) (catalogue.Verb, bool) {
want, _ := binary.Input["properties"].(map[string]any)
if len(want) == 0 {
return row, false
}
have, _ := row.Input["properties"].(map[string]any)
added := map[string]any{}
for name, p := range want {
if _, kept := have[name]; !kept {
added[name] = p
}
}
if len(added) == 0 {
return row, false
}
input := map[string]any{}
for k, v := range row.Input {
input[k] = v
}
if input["type"] == nil {
input["type"] = "object"
}
props := map[string]any{}
for k, v := range have {
props[k] = v
}
for k, v := range added {
props[k] = v
}
input["properties"] = props
row.Input = input
if binary.Description != "" {
row.Description = binary.Description
}
return row, true
}
// sameVerb is whether two definitions of a verb say the same, read as the row stores them.
func sameVerb(a, b catalogue.Verb) bool {
ja, errA := json.Marshal(a)
+71
View File
@@ -156,3 +156,74 @@ func TestTheControllersVerbsInTheRowAreTheBinarys(t *testing.T) {
t.Fatal("a verb this binary adds was not added")
}
}
// **A seat's verb gains the arguments a newer binary names** (the service manager's journal window,
// 2026-10-07): the console refuses an argument the row does not name, so a row seeded before them would
// keep the holder's new arguments unreachable. Added, never removed — an argument only the row has stays,
// and nothing becomes required — and a verb the binary adds optional is optional in the working set read back.
func TestASeatsVerbGainsTheArgumentsTheBinaryNames(t *testing.T) {
inv := ForTest(t)
ctx := t.Context()
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
old := `[{"name":"journal","description":"The last lines of one unit's journal.","input":{"type":"object",
"properties":{"unit":{"type":"string"},"lines":{"type":"string"},"scope":{"type":"string"},"kept":{"type":"string"}},
"required":["unit"]}}]`
if _, err := inv.store.Pool().Exec(ctx, `update seat set serves = $1 where name = $2`,
[]byte(old), catalogue.ServiceManagerSeat); err != nil {
t.Fatal(err)
}
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
seats, err := inv.Seats(ctx)
if err != nil {
t.Fatal(err)
}
verbs := map[string]catalogue.Verb{}
for _, s := range seats {
if s.Name == catalogue.ServiceManagerSeat {
for _, v := range s.Serves {
verbs[v.Name] = v
}
}
}
props, _ := verbs["journal"].Input["properties"].(map[string]any)
for _, arg := range []string{"since", "until", "match", "priority", "unit", "lines", "scope", "kept"} {
if _, has := props[arg]; !has {
t.Errorf("journal in the row does not take %s: %v", arg, props)
}
}
if req, _ := verbs["journal"].Input["required"].([]any); len(req) != 1 || req[0] != "unit" {
t.Errorf("what journal requires changed: %v", verbs["journal"].Input["required"])
}
if _, added := verbs["failed"]; !added {
t.Fatal("failed was not added to the row")
}
// The optional mark is not stored; the working set read from the rows takes it from the compiled seat,
// so today's holder, which does not serve failed, still holds the seat.
catalogue.UseSeats(seats)
defer catalogue.UseSeats(catalogue.DefaultSeats())
live, _ := catalogue.SeatNamed(catalogue.ServiceManagerSeat)
holder := catalogue.Manifest{Module: "systemd", Version: "1", Claims: []catalogue.Claim{{Name: catalogue.ServiceManagerSeat,
Scope: catalogue.ScopeNode, Serves: []string{"units", "status", "start", "stop", "restart", "enable", "disable", "journal"}}}}
if err := catalogue.CanHold(holder, live); err != nil {
t.Fatalf("the seat read from the store refuses today's holder: %v", err)
}
// Seeding again changes nothing more.
before := verbs["journal"]
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
after, _ := inv.Seats(ctx)
for _, s := range after {
if s.Name == catalogue.ServiceManagerSeat {
for _, v := range s.Serves {
if v.Name == "journal" && !sameVerb(v, before) {
t.Fatalf("re-seeding changed journal again: %+v", v)
}
}
}
}
}