From 82952070777e7c74c3cc29f2d744f5a90ddd2984 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 2 Nov 2026 11:20:28 +0100 Subject: [PATCH] Remove the controller's seat, the lease verbs and placement's adopt and hand-back; refuse any manifest naming the controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The controller ended by its unassignment (ADR 0590), so its seat, the grants seat's lease write and hand-back and placement's take-over from it serve nothing. The dependence check becomes strict (clause 5): a read of the lease, a claim, a require or the module itself is refused like any other declaration. held and artifacts-gone are the artifact sweep seat's (ADR 0571 §2). Issue 1126 B1, B2. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- go/buscompose/buscompose_test.go | 5 +- go/buscompose/health_grants.go | 6 - go/catalogue/controller_dependence.go | 187 ++------ go/catalogue/controller_dependence_test.go | 121 ++--- go/catalogue/delivery_seat_test.go | 1 - go/catalogue/grants_seat.go | 53 --- go/catalogue/grants_seat_test.go | 68 --- go/catalogue/placement_seat.go | 12 +- go/catalogue/replaces_test.go | 1 - go/catalogue/seats.go | 34 -- go/catalogue/verbs.go | 515 --------------------- go/catalogue/verbs_r4g_test.go | 31 -- go/catalogue/verbs_r6_test.go | 64 --- go/catalogue/verbs_test.go | 18 - go/delivery/held.go | 20 +- go/delivery/held_test.go | 32 +- 16 files changed, 104 insertions(+), 1064 deletions(-) delete mode 100644 go/catalogue/grants_seat.go delete mode 100644 go/catalogue/grants_seat_test.go delete mode 100644 go/catalogue/verbs_r4g_test.go diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index b896812..af04c90 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -1102,10 +1102,7 @@ func healthChecks(t *testing.T) { // It hears its watchdogs' words on core subjects (Limits 3, 11). for _, subject := range []string{"mesh.mod.power.event.sleeping", "mesh.control.g14.tools-alive", "mesh.seat.mesh-self-check.event.checked", "mesh.mod.keycloak.event.provisioner.failing", - "mesh.mod.nats.event.bus.slow-consumer", "prov.applied.g14.keycloak", - // ADR 0582 clauses 4, 6 (issue 774, 774/S22): the lease write's events, and ASSIGNMENTS deleted. - "mesh.seat.mesh-grants.event.lease-altered", "mesh.seat.mesh-grants.event.removal-failed", - "mesh.seat.mesh-grants.event.lease-handed", "$JS.EVENT.ADVISORY.STREAM.DELETED.ASSIGNMENTS"} { + "mesh.mod.nats.event.bus.slow-consumer", "prov.applied.g14.keycloak"} { if !MaySubscribe(perms, subject) { t.Errorf("the watchman may not hear %s", subject) } diff --git a/go/buscompose/health_grants.go b/go/buscompose/health_grants.go index 925b248..98960f7 100644 --- a/go/buscompose/health_grants.go +++ b/go/buscompose/health_grants.go @@ -129,12 +129,6 @@ func WatchmanHears() []string { "mesh.mod.nats.event.bus.connection-closed", ProvAppliedSubjects, deadLetterPrefix + ">", - // And the lease write's (ADR 0582 clauses 4, 6; issue 774): the seat mesh-grants' three events, and the stream - // ASSIGNMENTS deleted, which with the controller's consumer deleted clears a removal that failed. - "mesh.seat." + catalogue.GrantsSeat + ".event." + catalogue.GrantsEventLeaseHanded, - "mesh.seat." + catalogue.GrantsSeat + ".event." + catalogue.GrantsEventLeaseAltered, - "mesh.seat." + catalogue.GrantsSeat + ".event." + catalogue.GrantsEventRemovalFailed, - "$JS.EVENT.ADVISORY.STREAM.DELETED." + AssignmentsStream, } } diff --git a/go/catalogue/controller_dependence.go b/go/catalogue/controller_dependence.go index 298b8ba..afcb9cf 100644 --- a/go/catalogue/controller_dependence.go +++ b/go/catalogue/controller_dependence.go @@ -7,118 +7,62 @@ import ( "strings" ) -// **What still depends on the controller, and the lease write's check** (novox/hq ADR 0571 §13, ADR 0582 clause 2, -// issue 774): every declaration of a manifest other than the controller's on the controller (a read of one of its -// states, an invoke of one of its verbs or tools, a consume or a wait on one of its events, a use of its seat), judged -// against the named, temporary list of what another accepted record keeps until the lease write. The grants part's -// grants_lease_check runs it over every manifest the indexer holds; ManifestRefusals runs it over a repository's -// manifests, in the test ADR 0571 §13 names in mesh-catalog, mesh-delivery and mesh-tools. A read of the lease key is no -// dependency: it names the lease's holder, whoever that is, and parts read it to learn that the write happened. +// **Nothing may name the controller** (novox/hq ADR 0590 clause 5, issue 1126): the controller ended by its unassignment +// on 2026-11-02 and its code left the mesh, so a manifest that reads one of its states (its lease too), invokes one of its +// verbs or tools, consumes or waits on one of its events, uses, claims or requires it, or is it, depends on nothing that +// answers. ManifestRefusals runs the check over a repository's manifests, in the test each repository of manifests keeps +// (mesh-catalog, mesh-delivery, mesh-tools); a later decision that places a controller again must first lift it. -// ControllerLease is the controller's lease as a reader names it (`.`): no dependency. -const ControllerLease = ControllerSeatName + ".lease" +// EndedControllerName is the name the ended controller had, as its module and as its seat: no manifest may name it. +const EndedControllerName = "mesh-controller" -// The kinds of a dependency on the controller, as a manifest declares them. +// The kinds of a declaration that names the controller. const ( - DependsRead = "read" - DependsInvoke = "invoke" - DependsConsume = "consume" - DependsUses = "uses" - DependsWaits = "waits" + DependsRead = "read" + DependsInvoke = "invoke" + DependsConsume = "consume" + DependsUses = "uses" + DependsWaits = "waits" + DependsClaims = "claims" + DependsRequires = "requires" + DependsIs = "is" ) -// The phases of the lease write's check (ADR 0571 §13): before the lease write's own delivery the whole named list is -// allowed; that delivery drops every entry but the three kept to the write itself, and its check runs with those alone. -const ( - PhaseBefore = "before-delivery" - PhaseWrite = "write" -) - -// Dependency is one declaration of a manifest other than the controller's on the controller: a read of one of its -// states, an invoke of one of its verbs or tools, a consume or a wait on one of its events, or a send to its seat. +// Dependency is one declaration of a manifest that names the controller. type Dependency struct { Module string `json:"module"` // By is the account declaring it when it is not the module's own: `process ` or `tools `. By string `json:"by,omitempty"` Kind string `json:"kind"` What string `json:"what"` - // Kept is the record that keeps it until the lease write, from the named list; empty when none does. - Kept string `json:"kept,omitempty"` - // ToTheWrite says it is one of the three kept to the write itself, which the lease write's own delivery keeps. - ToTheWrite bool `json:"to_the_write,omitempty"` -} - -// KeptDependency is one entry of the named, temporary list ADR 0571 §13 allows: a read, invoke or consume another -// accepted record keeps until the lease write, with that record. ToTheWrite marks the three kept to the write itself. -type KeptDependency struct { - Module, Kind, What, Record string - ToTheWrite bool -} - -// KeepList is the named list. Each entry names the clause that keeps it; a dependency not on it fails the check. It -// holds the three kept to the write itself and nothing else: the lease write's own delivery dropped every other entry -// (ADR 0571 §13; issue 774), so its check runs with these alone in either phase. The third, the bus node agent's -// following of the controller's user list (ADR 0571 §8, ADR 0547 §8), is no manifest's declaration but the -// node-engine's own, so no manifest matches it; it stays named here so the list reads as the record has it. -var KeepList = []KeptDependency{ - {Module: "mesh-delivery-builder", Kind: DependsConsume, What: "mesh-controller.artifacts-gone", Record: "ADR 0571 §4, §13", ToTheWrite: true}, - {Module: "artifact-store-tools", Kind: DependsInvoke, What: "seat:mesh-controller.collect", Record: "ADR 0571 §4, §13", ToTheWrite: true}, - {Module: "node-engine", Kind: DependsRead, What: "the controller's user list of the bus module's composition", Record: "ADR 0571 §8, §13; ADR 0547 §8", ToTheWrite: true}, -} - -// EndedBy names, for a declaration no entry keeps, the record that already ended it, so a refusal says where it moved. -// The controller's `grants` is one: ADR 0571 §9 has no manifest read it from L7's delivery, which changes ADR 0567 §10 -// and ADR 0547 §5's shadow compare, so neither the assigner's nor the grants part's read of it is kept. -var EndedBy = map[string]string{ - DependsRead + " mesh-controller.grants": "ADR 0571 §9: no manifest reads the controller's grants; the grants part's key is the one read", - DependsInvoke + " seat:mesh-controller.hand-act": "ADR 0565 §11: every recorder calls mesh-hand-acts", - DependsInvoke + " seat:mesh-controller.warranted": "ADR 0565 §11: every recorder calls mesh-hand-acts", - DependsInvoke + " seat:mesh-controller.root-free": "ADR 0571 §11: each machine's engine answers root-free (seat:node-engine.root-free@machine)", - DependsInvoke + " seat:mesh-controller.held": "ADR 0571 §2: held is the seat mesh-artifact-sweep's", -} - -// keptFor is the list's entry for one dependency, or nil. -func keptFor(d Dependency) *KeptDependency { - for i, k := range KeepList { - if k.Module == d.Module && k.Kind == d.Kind && k.What == d.What { - return &KeepList[i] - } - } - return nil } // onController says whether a name `.` is the controller's. -func onController(name string) bool { return strings.HasPrefix(name, ControllerSeatName+".") } +func onController(name string) bool { return strings.HasPrefix(name, EndedControllerName+".") } // invokesController says whether an invoke names a verb of the controller's seat or a tool of its module. `*` names none: -// it is the console's grant to every tool, judged by no record as a dependency. +// it is the console's grant to every tool. func invokesController(invoke string) bool { if seat, _, ok := SeatInvoke(invoke); ok { - return seat == ControllerSeatName + return seat == EndedControllerName } return onController(invoke) } -// Dependencies are every declaration on the controller in the manifests given, by module, each with the list's entry -// that keeps it. The controller's own manifest is not judged, nor a read of its lease key: it names the lease's holder, -// whoever that is, and parts read it to learn that the write happened (ADR 0571 §13). +// Dependencies are every declaration naming the controller in the manifests given, by module. func Dependencies(manifests map[string]Manifest) []Dependency { var out []Dependency add := func(module, by, kind, what string) { - d := Dependency{Module: module, By: by, Kind: kind, What: what} - if k := keptFor(d); k != nil { - d.Kept, d.ToTheWrite = k.Record, k.ToTheWrite - } - if !slices.Contains(out, d) { + if d := (Dependency{Module: module, By: by, Kind: kind, What: what}); !slices.Contains(out, d) { out = append(out, d) } } for name, m := range manifests { - if name == ControllerSeatName || m.Module == ControllerSeatName { - continue + if name == EndedControllerName || m.Module == EndedControllerName { + add(name, "", DependsIs, EndedControllerName) } for _, r := range m.Reads { - if onController(r) && r != ControllerLease { + if onController(r) { add(name, "", DependsRead, r) } } @@ -138,10 +82,20 @@ func Dependencies(manifests map[string]Manifest) []Dependency { } } for _, u := range m.Uses { - if u == ControllerSeatName { + if u == EndedControllerName { add(name, "", DependsUses, u) } } + for _, c := range m.Claims { + if c.Name == EndedControllerName { + add(name, "", DependsClaims, c.Name) + } + } + for _, r := range m.Requires { + if r == EndedControllerName || onController(r) { + add(name, "", DependsRequires, r) + } + } for _, a := range m.BusAccounts() { by := "process " + a.ID if a.Tools { @@ -170,69 +124,20 @@ func Dependencies(manifests map[string]Manifest) []Dependency { return out } -// Verdict is the lease write's check in one phase. -type Verdict struct { - Phase string `json:"phase"` - Passes bool `json:"passes"` - Reasons []string `json:"reasons"` -} - -// Coverage is how much of the mesh's manifests the dependencies were derived from: the manifests read, the modules the -// indexer holds (-1 when its keys could not be listed, which Unread then names), and each manifest that could not be -// read. -type Coverage struct { - Read, Indexed int - Unread []Unread -} - -// Unread is one manifest, or one listing of them, that could not be read, and why. -type Unread struct { - Input string `json:"input"` - Why string `json:"why"` -} - -// Judge runs the lease write's check over the dependencies (ADR 0571 §13) in one phase: before the lease write's own -// delivery, every dependency must be on the named list; at the write, only the three kept to the write itself may stay. -// It passes only over every module the indexer holds: no manifest read, fewer read than the indexer holds, or one that -// could not be read fails it, since what was not read could hide a dependency. A lease already handed away leaves -// nothing to write. -func Judge(deps []Dependency, phase string, cov Coverage, handedAway bool, leaseSays string) Verdict { - v := Verdict{Phase: phase, Reasons: []string{}} - if handedAway { - v.Reasons = append(v.Reasons, "there is nothing to write: "+leaseSays) +// ManifestRefusals is the check over a repository's manifests, by module name: one reason per declaration naming the +// controller, none when it passes. None given fails it, since nothing read can hide a declaration. +func ManifestRefusals(manifests map[string]Manifest) []string { + if len(manifests) == 0 { + return []string{"no manifest was read: with nothing read, nothing naming the controller can be judged absent"} } - switch { - case cov.Read == 0: - v.Reasons = append(v.Reasons, fmt.Sprintf("no manifest was read from the indexer (it holds %d modules): with nothing read, nothing on the controller can be judged absent", max(cov.Indexed, 0))) - case cov.Indexed >= 0 && cov.Read < cov.Indexed: - v.Reasons = append(v.Reasons, fmt.Sprintf("only %d of the %d modules the indexer holds had their manifest read, so what the others declare on the controller is not known", cov.Read, cov.Indexed)) - } - for _, d := range deps { + var out []string + for _, d := range Dependencies(manifests) { who := d.Module if d.By != "" { who += " (" + d.By + ")" } - switch { - case d.Kept == "": - why := fmt.Sprintf("%s declares %s %s, which no record keeps until the lease write (ADR 0571 §13)", who, d.Kind, d.What) - if ended := EndedBy[d.Kind+" "+d.What]; ended != "" { - why += "; ended by " + ended - } - v.Reasons = append(v.Reasons, why) - case phase == PhaseWrite && !d.ToTheWrite: - v.Reasons = append(v.Reasons, fmt.Sprintf("%s declares %s %s, kept by %s only until the lease write's own delivery, which drops it", who, d.Kind, d.What, d.Kept)) - } + out = append(out, fmt.Sprintf("%s declares %s %s: the controller ended by its unassignment and nothing may name it "+ + "(ADR 0590 clause 5)", who, d.Kind, d.What)) } - for _, g := range cov.Unread { - v.Reasons = append(v.Reasons, fmt.Sprintf("%s could not be read (%s), so what it declares on the controller is not known", g.Input, g.Why)) - } - v.Passes = len(v.Reasons) == 0 - return v -} - -// ManifestRefusals is the lease write's check over a repository's manifests, by module name, in one phase (PhaseBefore -// or PhaseWrite): each reason the check gives, none when it passes. Every manifest given counts as read: the caller reads -// them from its own tree, and none given fails it. -func ManifestRefusals(manifests map[string]Manifest, phase string) []string { - return Judge(Dependencies(manifests), phase, Coverage{Read: len(manifests), Indexed: -1}, false, "").Reasons + return out } diff --git a/go/catalogue/controller_dependence_test.go b/go/catalogue/controller_dependence_test.go index 410e27f..e6d2ce3 100644 --- a/go/catalogue/controller_dependence_test.go +++ b/go/catalogue/controller_dependence_test.go @@ -6,98 +6,53 @@ import ( "testing" ) -// Issue 774 (ADR 0571 §13, ADR 0582 clause 2): the lease write's check judges every manifest's dependency on the -// controller against the named list, at the write (the three kept to it alone) and before its delivery. Moved with the -// judge from mesh-grants' leasecheck_test.go (TestTheLeaseCheckJudgesEveryDependencyOnTheController's manifest rows and -// TestTheKeepListNamesItsRecordsAndThreeToTheWrite), whose manifests were mesh-grants' fixtures; the conditions row is -// mesh-conditions' manifest at mesh-catalog ce3bedd5 (module.json:31-32). -func TestTheLeaseWritesCheckJudgesEveryDependencyOnTheController(t *testing.T) { - toTheWrite := 0 - for _, k := range KeepList { - if !strings.HasPrefix(k.Record, "ADR ") || k.Module == "" || k.What == "" { - t.Errorf("entry %+v names no record, module or declaration", k) - } - if k.ToTheWrite { - toTheWrite++ +// Issue 1126 B1/AC1, B2/AC1 (ADR 0590 clause 5): the seat set holds no mesh-controller, and the dependence check refuses +// every manifest that reads (the lease too), invokes, consumes, waits on, uses, claims, requires or is mesh-controller. +func TestNothingMayNameTheController(t *testing.T) { + for _, s := range DefaultSeats() { + if s.Name == EndedControllerName { + t.Errorf("the seat set still holds %s", s.Name) } } - if toTheWrite != 3 || len(KeepList) != 3 { - t.Errorf("%d entries, %d kept to the write itself: ADR 0571 §13 keeps the three alone from the write's delivery", len(KeepList), toTheWrite) - } - - type want struct { - deps []string // " ", kept by the record or "unkept" - write string // a reason the check at the write names, or "" when it passes - before string // a reason the check before the delivery names, or "" when it passes + if _, ok := SeatNamed(EndedControllerName); ok { + t.Errorf("SeatNamed still finds %s", EndedControllerName) } for _, row := range []struct { - name string - manifests map[string]string - want want + name, manifest string + want []string // " " }{ - // 774/S4/AC2. - {"a read of the lease alone is no dependency, and the controller's own manifest is not judged", - map[string]string{"mod-l": `{"module":"mod-l","version":"1","reads":["mesh-controller.lease"]}`, - "mesh-controller": `{"module":"mesh-controller","version":"1","invokes":["seat:mesh-controller.status"]}`}, - want{}}, - // 774/S4/AC1, 774/S30/AC1: the keeper's read of the controller's conditions put back is refused in either phase, - // since the lease write's delivery dropped its entry (it was kept by ADR 0564 §9 before). - {"the keeper's read of the controller's conditions, put back, is refused", - map[string]string{"mesh-conditions": `{"module":"mesh-conditions","version":"1","reads":["mesh-controller.conditions","mesh-controller.lease"]}`}, - want{deps: []string{"mesh-conditions read mesh-controller.conditions unkept"}, - write: "mesh-conditions declares read mesh-controller.conditions, which no record keeps until the lease write", - before: "mesh-conditions declares read mesh-controller.conditions, which no record keeps until the lease write"}}, - // 774/S30/AC1: the three kept to the write pass in either phase. - {"the artifact sweep's collect is kept to the write itself", - map[string]string{"artifact-store-tools": `{"module":"artifact-store-tools","version":"1","invokes":["seat:mesh-controller.collect"]}`}, - want{deps: []string{"artifact-store-tools invoke seat:mesh-controller.collect kept by ADR 0571 §4, §13"}}}, - {"the builder's consume of artifacts-gone is kept to the write itself", - map[string]string{"mesh-delivery-builder": `{"module":"mesh-delivery-builder","version":"1","consumes":["mesh-controller.artifacts-gone"]}`}, - want{deps: []string{"mesh-delivery-builder consume mesh-controller.artifacts-gone kept by ADR 0571 §4, §13"}}}, - {"a read no record keeps fails both phases, named", - map[string]string{"mod-u": `{"module":"mod-u","version":"1","reads":["mesh-controller.declared-packages"]}`}, - want{deps: []string{"mod-u read mesh-controller.declared-packages unkept"}, - write: "mod-u declares read mesh-controller.declared-packages, which no record keeps until the lease write", - before: "mod-u declares read mesh-controller.declared-packages, which no record keeps until the lease write"}}, - {"a seat verb and a module tool of the controller's, a consume and a wait are each one; every tool (*) is none", - map[string]string{"mod-i": `{"module":"mod-i","version":"1","invokes":["seat:mesh-controller.root-free","mesh-controller.status","*","nats.nats_users"],` + - `"consumes":["mesh-controller.checked","mod-x.thing"],"uses":["mesh-controller"]}`, - "node-tools": `{"module":"node-tools","version":"1","waits":["mesh-controller.checked"]}`}, - want{deps: []string{"mod-i consume mesh-controller.checked unkept", "mod-i invoke mesh-controller.status unkept", - "mod-i invoke seat:mesh-controller.root-free unkept", "mod-i uses mesh-controller unkept", "node-tools waits mesh-controller.checked unkept"}, - write: "mod-i declares invoke seat:mesh-controller.root-free, which no record keeps until the lease write (ADR 0571 §13); ended by ADR 0571 §11", - before: "node-tools declares waits mesh-controller.checked"}}, - {"a process's own invoke of the controller is its module's dependency, named by the process", - map[string]string{"mod-p": `{"module":"mod-p","version":"1","resources":[{"id":"account","type":"user","name":"mod-p"},{"id":"worker","type":"process","run":["./w"],"user":"mod-p",` + - `"bus":{"invokes":["seat:mesh-controller.held"]}}]}`}, - want{deps: []string{"mod-p invoke seat:mesh-controller.held unkept"}, - write: "mod-p (process worker) declares invoke seat:mesh-controller.held", before: "mod-p (process worker)"}}, - {"no manifest fails both phases", map[string]string{}, want{write: "no manifest was read", before: "no manifest was read"}}, + {"a read of the lease", `{"module":"mod-l","version":"1","reads":["mesh-controller.lease"]}`, []string{"read mesh-controller.lease"}}, + {"the controller's own manifest", `{"module":"mesh-controller","version":"1"}`, []string{"is mesh-controller"}}, + {"a claim of its seat", `{"module":"a-controller","version":"1","claims":[{"name":"mesh-controller","scope":"mesh"}]}`, + []string{"claims mesh-controller"}}, + {"each declaration kind; every tool (*) is none", + `{"module":"mod-i","version":"1","invokes":["seat:mesh-controller.root-free","mesh-controller.status","*","nats.nats_users"],` + + `"consumes":["mesh-controller.artifacts-gone","mod-x.thing"],"uses":["mesh-controller"],"waits":["mesh-controller.checked"],` + + `"requires":["mesh-controller"]}`, + []string{"consume mesh-controller.artifacts-gone", "invoke mesh-controller.status", "invoke seat:mesh-controller.root-free", + "requires mesh-controller", "uses mesh-controller", "waits mesh-controller.checked"}}, + {"a process's own invoke", `{"module":"mod-p","version":"1","resources":[{"id":"account","type":"user","name":"mod-p"},` + + `{"id":"worker","type":"process","run":["./w"],"user":"mod-p","bus":{"invokes":["seat:mesh-controller.held"]}}]}`, + []string{"invoke seat:mesh-controller.held"}}, + {"a module on its own seats", `{"module":"mod-ok","version":"1","reads":["mesh-grants.accounts"],"invokes":["*"]}`, nil}, } { - manifests := map[string]Manifest{} - for name, raw := range row.manifests { - m, err := Decode([]byte(raw)) - if err != nil { - t.Fatalf("%s: %s: %v", row.name, name, err) - } - manifests[name] = m + m, err := Decode([]byte(row.manifest)) + if err != nil { + t.Fatalf("%s: %v", row.name, err) } - var deps []string - for _, d := range Dependencies(manifests) { - kept := "unkept" - if d.Kept != "" { - kept = "kept by " + d.Kept - } - deps = append(deps, d.Module+" "+d.Kind+" "+d.What+" "+kept) + var got []string + for _, d := range Dependencies(map[string]Manifest{m.Module: m}) { + got = append(got, d.Kind+" "+d.What) } - if !slices.Equal(deps, row.want.deps) { - t.Errorf("%s: dependencies %q, want %q", row.name, deps, row.want.deps) + if !slices.Equal(got, row.want) { + t.Errorf("%s: dependencies %q, want %q", row.name, got, row.want) } - for phase, reason := range map[string]string{PhaseWrite: row.want.write, PhaseBefore: row.want.before} { - got := ManifestRefusals(manifests, phase) - if (reason == "") != (len(got) == 0) || !slices.ContainsFunc(got, func(r string) bool { return strings.Contains(r, reason) }) && reason != "" { - t.Errorf("%s: at %s refused %q, want %q", row.name, phase, got, reason) - } + refused := ManifestRefusals(map[string]Manifest{m.Module: m}) + if len(refused) != len(row.want) || len(refused) > 0 && !strings.Contains(refused[0], "ADR 0590") { + t.Errorf("%s: refused %q", row.name, refused) } } + if got := ManifestRefusals(map[string]Manifest{}); len(got) != 1 || !strings.Contains(got[0], "no manifest") { + t.Errorf("no manifest given: %q", got) + } } diff --git a/go/catalogue/delivery_seat_test.go b/go/catalogue/delivery_seat_test.go index 0ea1642..046b93d 100644 --- a/go/catalogue/delivery_seat_test.go +++ b/go/catalogue/delivery_seat_test.go @@ -9,7 +9,6 @@ import ( // The delivery seat answers "what did the mesh's checks say of this pull request?" as a verb of its own, // `checks` (novox/hq ADR 0239): read by repository and a pull request's number or a commit. func TestTheDeliverySeatPromisesChecks(t *testing.T) { - t.Run("the seat mesh-grants promises the lease write", grantsSeatPromisesTheLeaseWrite) seat, ok := SeatNamed(DeliverySeat) if !ok { t.Fatal("the delivery seat is not in the set") diff --git a/go/catalogue/grants_seat.go b/go/catalogue/grants_seat.go deleted file mode 100644 index 5d12b55..0000000 --- a/go/catalogue/grants_seat.go +++ /dev/null @@ -1,53 +0,0 @@ -package catalogue - -// **The lease write, a verb of the seat `mesh-grants`** (novox/hq ADR 0582 clause 1, issue 774): the grants part -// holds the seat once for the mesh and, on the operator's warrant, hands the controller's lease to itself and back. -// It serves two verbs and says three events, none carrying a secret: -// -// - lease-handed: the lease names mesh-grants, written on the operator's warrant (clause 3); -// - lease-altered: the lease key or its bucket's expiry differs from what mesh-grants wrote (clause 4); -// - removal-failed: the day-later removal of the controller's consumer or stream failed (clause 6). - -// GrantsSeat is the seat the grants part (mesh-grants) holds. -const GrantsSeat = "mesh-grants" - -// The seat's verbs. -const ( - GrantsVerbLeaseWrite = "lease-write" - GrantsVerbLeaseHandBack = "lease-hand-back" -) - -// The seat's events. -const ( - GrantsEventLeaseHanded = "lease-handed" - GrantsEventLeaseAltered = "lease-altered" - GrantsEventRemovalFailed = "removal-failed" -) - -// grantsDecision is the record that made the seat. -const grantsDecision = "novox/hq ADR 0582" - -// grantsEvents are the seat's events, in the order the write meets them. -func grantsEvents() []string { - return []string{GrantsEventLeaseHanded, GrantsEventLeaseAltered, GrantsEventRemovalFailed} -} - -// grantsVerbs are the seat's verbs (ADR 0582 clauses 1, 2, 3, 5). -func grantsVerbs() []Verb { - return []Verb{ - {Name: GrantsVerbLeaseWrite, Description: "Hand the controller's lease to mesh-grants, kept for good. Refused, " + - "naming the check, until the lease check at phase write passes and every condition it does not judge reads " + - "green. Otherwise the operator is asked once; on the warrant the checks are read again, the controller's " + - "service is stopped, the bus's user list is composed without the controller's write on the lease, the lease's " + - "expiry is removed and the key holder names mesh-grants, said as lease-handed and recorded as a hand act. A no " + - "or an expiry of the ask acts on nothing.", - Input: schema(map[string]string{"why": "why the lease is handed now, shown to the operator and kept"}, - []string{"why"})}, - {Name: GrantsVerbLeaseHandBack, Description: "Give the lease back to the controller, for a day after the write: " + - "asked and warranted the same way, it restores the lease's fifteen-second expiry, deletes the key holder, " + - "gives the controller its rights back once the list is loaded, and starts the controller. Refused once the " + - "day-later removal ran: the controller would start without its consumer and stream.", - Input: schema(map[string]string{"why": "why the lease goes back, shown to the operator and kept"}, - []string{"why"})}, - } -} diff --git a/go/catalogue/grants_seat_test.go b/go/catalogue/grants_seat_test.go deleted file mode 100644 index 62e08b3..0000000 --- a/go/catalogue/grants_seat_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package catalogue - -import ( - "reflect" - "strings" - "testing" -) - -// The seat mesh-grants (novox/hq ADR 0582 clause 1, issue 774, 774/S1/AC2): its holder serves lease-write and -// lease-hand-back, both a condition of holding, and nothing else; the seat says lease-handed, lease-altered and -// removal-failed. A case of TestTheDeliverySeatPromisesChecks, the seat-verb test, within the test budget. -func grantsSeatPromisesTheLeaseWrite(t *testing.T) { - seat, ok := SeatNamed(GrantsSeat) - if !ok { - t.Fatal("the seat mesh-grants is not in the set") - } - if seat.Scope != ScopeMesh || seat.Delivers != "" { - t.Errorf("mesh-grants is at scope %q delivering %q, want mesh and nothing", seat.Scope, seat.Delivers) - } - if got, want := VerbNames(seat.Serves), []string{"lease-write", "lease-hand-back"}; !reflect.DeepEqual(got, want) { - t.Errorf("mesh-grants serves %v, want %v", got, want) - } - if want := []string{"lease-handed", "lease-altered", "removal-failed"}; !reflect.DeepEqual(seat.Emits, want) { - t.Errorf("mesh-grants says %v, want %v", seat.Emits, want) - } - for _, v := range seat.Serves { - if v.Optional { - t.Errorf("%s is optional: the holder ships with the seat, so every verb is a condition of holding", v.Name) - } - props, _ := v.Input["properties"].(map[string]any) - if req, _ := v.Input["required"].([]string); len(props) != 1 || !reflect.DeepEqual(req, []string{"why"}) { - t.Errorf("%s takes %v requiring %v, want only a why", v.Name, props, req) - } - } - - claim := func(serves ...string) Manifest { - return Manifest{Module: "mesh-grants", Claims: []Claim{{Name: GrantsSeat, Scope: ScopeMesh, Serves: serves}}} - } - for _, c := range []struct { - name string - m Manifest - refuse string // empty: held - }{ - {"both verbs", claim("lease-write", "lease-hand-back"), ""}, - {"without the hand-back", claim("lease-write"), "lease-hand-back"}, - {"a verb the seat does not promise", claim("lease-write", "lease-hand-back", "lease-take"), "lease-take"}, - {"at node scope", Manifest{Module: "mesh-grants", Claims: []Claim{{Name: GrantsSeat, Scope: ScopeNode, - Serves: []string{"lease-write", "lease-hand-back"}}}}, "scope"}, - } { - err := CanHold(c.m, seat) - switch { - case c.refuse == "" && err != nil: - t.Errorf("%s: refused: %v", c.name, err) - case c.refuse != "" && (err == nil || !strings.Contains(err.Error(), c.refuse)): - t.Errorf("%s: %v, want a refusal naming %q", c.name, err, c.refuse) - } - } - - // No other seat changed: the set is the one before it with mesh-grants added beside mesh-hand-acts. - var names []string - for _, s := range DefaultSeats() { - names = append(names, s.Name) - } - at := strings.Index(strings.Join(names, " "), HandActsSeat+" "+GrantsSeat+" ") - if at < 0 { - t.Errorf("mesh-grants does not follow mesh-hand-acts in the seat set: %v", names) - } -} diff --git a/go/catalogue/placement_seat.go b/go/catalogue/placement_seat.go index 9bb9f51..4b7af9b 100644 --- a/go/catalogue/placement_seat.go +++ b/go/catalogue/placement_seat.go @@ -12,15 +12,13 @@ package catalogue // PlacementSeat is the seat the assigner holds for placement. const PlacementSeat = "mesh-placement" -// The seat's seven verbs (ADR 0567 §10). +// The seat's five verbs (ADR 0567 §10). const ( PlacementVerbPlacement = "placement" PlacementVerbProposals = "proposals" PlacementVerbPropose = "propose" PlacementVerbWithdraw = "withdraw" PlacementVerbAgain = "again" - PlacementVerbAdopt = "adopt" - PlacementVerbHandBack = "hand-back" ) // placementVerbs are the seat's verbs: what reads all it keeps and does by hand all it does by itself (ADR 0567 §10). @@ -74,13 +72,5 @@ func placementVerbs() []Verb { "stand, and raise its ask again when its verdict passes and the operator's channel does not hold the ask. " + "Nothing more: the recovery of a proposal whose judgement or ask was lost.", Input: schema(map[string]string{"id": "the proposal's id"}, []string{"id"})}, - {Name: PlacementVerbAdopt, Optional: true, Description: "Take placement over from the controller: its " + - "placement-export is proposed whole as the seed, every node's key, which every node must answer as the same. " + - "Its Approve is the take-over; at the warrant the export is read again and a changed one is proposed anew.", - Input: schema(map[string]string{"why": "why, shown to the operator"}, []string{"why"})}, - {Name: PlacementVerbHandBack, Optional: true, Description: "The operator's alone, recorded as a hand act: " + - "delete every placement key, the history kept, so every node returns to the controller's compositions. " + - "Refused after the lease write.", - Input: schema(map[string]string{"why": "why, recorded with the hand act"}, []string{"why"})}, } } diff --git a/go/catalogue/replaces_test.go b/go/catalogue/replaces_test.go index 5815795..2f10676 100644 --- a/go/catalogue/replaces_test.go +++ b/go/catalogue/replaces_test.go @@ -13,7 +13,6 @@ func TestTheVerbsWorkedAroundSayWhatTheyReplace(t *testing.T) { ServiceManagerSeat + ".restart": "systemctl restart", ServiceManagerSeat + ".status": "systemctl status", "node-hostname.add": "edit /etc/hosts", - ControllerSeatName + ".node": "hostnamectl", } for _, s := range DefaultSeats() { for _, v := range s.Serves { diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 727f4c2..4b6b9bc 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -100,37 +100,6 @@ type Receivable struct { // // In the order a person reads it: the mesh's own, then a node's. var defaultSeats = append([]Seat{ - // The control plane states what it did under the seat it holds (novox/hq ADR 0134): a role's - // events belong to the role, so they keep their address while the holder is replaced. No accepts, - // so no work queue is raised for it — only what its holder may say. - // And it serves the mesh's own verbs as the seat's tools (novox/hq ADR 0154): `status`, `push`, - // `assign` and the rest are a role's interface, not a container's, and stay addressable while - // the control plane is replaced. - {Name: ControllerSeatName, Scope: ScopeMesh, Decision: "novox/hq ADR 0079", - // And what is wrong, as it changes, and the self-check's heartbeat (novox/hq to-be 45 §2, §4). - Emits: []string{"applied", "refused", "built-before", - "condition-raised", "condition-changed", "condition-cleared", "doctor-heartbeat", - // A value given by hand, replaced after its module's first good start (novox/hq ADR 0228). - "secret-replaced", - // Every act a healer takes (novox/hq to-be 45 §7). - "healer-acted", - // A build put back after its gate failed (novox/hq ADR 0236, to-be 45 §8). - "rolled-back", - // A pull request's merge check, judged (novox/hq to-be 45 §9). - "checked", - // A walk kept in a new state, and a tier sent to a machine: kept until the catalogue's main consumes them no - // more; nothing emits them (ADR 0539). - "plan-moved", - "sent", - // A key of a record mirror written or removed (novox/hq ADR 0289, design 54 §3; design 57 F3). - "assignment-changed", "declared-config-changed", "predefined-changed", - // A builder's key not taken in because the artifact store does not hold an artifact it names (novox/hq - // issue 1937): the builder builds it again. - "artifacts-gone", - // A bucket of a state this release deleted, deleted at the controller's first start (novox/hq ADR 0539 - // clause 13): the merge window's, the silent sends' and the building plans' hold, each said once. - "bucket-deleted"}, - Serves: ControllerVerbs}, // The store's first verbs (novox/hq ADR 0159): the smallest set that makes the store askable, // served by whichever module holds the seat with tools of these names. {Name: "mesh-store", Scope: ScopeMesh, Delivers: "postgres-database", Decision: "novox/hq ADR 0079", @@ -348,9 +317,6 @@ var defaultSeats = append([]Seat{ // policy and the bus step at the assigner, and the hand-act log. {Name: WantsSeat, Scope: ScopeMesh, Decision: controlRecordsDecision, Emits: []string{"policy-changed"}, Serves: wantsVerbs()}, {Name: HandActsSeat, Scope: ScopeMesh, Decision: controlRecordsDecision, Emits: []string{"act-recorded"}, Serves: handActsVerbs()}, - // The lease write (novox/hq ADR 0582 clause 1, grants_seat.go): the grants part hands the controller's lease to - // itself and back on the operator's warrant. - {Name: GrantsSeat, Scope: ScopeMesh, Decision: grantsDecision, Emits: grantsEvents(), Serves: grantsVerbs()}, // Joining a machine (novox/hq ADR 0587 §1, enrolment_seat.go): held once by the module mesh-enrolment. {Name: EnrolmentSeat, Scope: ScopeMesh, Decision: enrolmentDecision, Emits: EnrolmentEvents, Serves: enrolmentVerbs()}, // The artifact sweep (novox/hq ADR 0571 clause 2, artifact_sweep_seat.go). diff --git a/go/catalogue/verbs.go b/go/catalogue/verbs.go index 9c1bdac..0c3c9f0 100644 --- a/go/catalogue/verbs.go +++ b/go/catalogue/verbs.go @@ -76,521 +76,6 @@ func VerbNames(verbs []Verb) []string { return out } -// RemovedControllerVerbs are the controller seat's verbs release 6 removed (novox/hq ADR 0539, ADR 0429 §8): no -// delivery plan, batch, tier or push exists, so nothing starts, stops, retries, follows or reads one. Each stays in -// ControllerVerbs as a row with no input, so a caller is answered by name with its replacement, given here. -var RemovedControllerVerbs = map[string]string{ - "plans": "the delivery view (nox-delivery): each machine's want and have, read from the assigner's wants and the " + - "node-engine's haves", - "plan": "the node's compositions: mesh-controller_composed holds each module's digest and judgement per node, and " + - "mesh-hand-compose prints one module's composition for one node", - "push": "a want: the assigner writes want.. by its rules, and by hand the operator's credential writes " + - "it, naming a kept composition (ADR 0539 clause 10)", - "deliver": "nothing to start: a merged change reaches each machine by its want", - "delivery-stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)", - "delivery-backups-again": "the assigner's backup ask, answered on the phone or in the delivery view (ADR 0539 clause 16)", - "delivery-walks": "the delivery view (nox-delivery) and the wants, haves and compositions on the bus", - "delivery-order": "no order to keep: each module reaches its machines by its own want, its providers first by rule 5", -} - -// removedInR6 is the row of a verb release 6 removed from the controller's seat: no input, refused by name. -func removedInR6(name string) Verb { - return Verb{Name: name, Description: "Removed in R6 (ADR 0539): refused by name, naming its replacement: " + - RemovedControllerVerbs[name], Input: schema(nil, nil)} -} - -// ControllerSeatName is the seat the control plane holds, whose tools are the mesh's own verbs. -const ControllerSeatName = "mesh-controller" - -// ControllerVerbs are the mesh's own verbs, as the `mesh-controller` seat's tools (novox/hq ADR 0154). -// -// **The same function the command line calls, and nothing the tool adds** (ADR 0035): each of these -// is a command the controller's binary already answers, run by the holder of the seat with the -// arguments below and answered with what the command printed. A verb here is a contract every future -// holder must implement, which is why the list is short and made of what an operator asks weekly. -// Additive within a version (design 33 §7); a verb that would break a caller takes a new version. -var ControllerVerbs = []Verb{ - {Name: "tools", Description: "Every seat's tools, from the mesh's own records: what each role " + - "answers, whether or not its holder is up. The mesh's own verbs are the mesh-controller seat's.", - Input: schema(nil, nil)}, - {Name: "calls", Description: "The calls this controller answered lately and what came of each — " + - "one still running, one that finished after its caller was told it was running, one whose answer " + - "the bus refused — and, given a call's id, its whole answer (novox/hq issue 265). A call that has " + - "not finished within ten seconds answers that it is running, with its id; this is where it ends.", - Input: schema(map[string]string{ - "call": "a call's id, as a running answer or `calls` gives it: that call and its whole answer", - }, nil)}, - {Name: "status", Description: "What is wrong, what is quiet, what is out of date, and which " + - "machines are behind what the mesh would send them.", - Input: schema(nil, nil)}, - {Name: "nodes", Description: "Every machine the mesh knows, with whether it is converged or adopted.", - Input: schema(nil, nil)}, - {Name: "node", Description: "What one machine reported it can do, what it is assigned, and why.", - Input: schema(map[string]string{"node": "the machine's name"}, []string{"node"}), - Replaces: []string{"hostnamectl", "uptime"}}, - {Name: "placement-export", Description: "Each machine's placement as the catalogue's placement file says it (novox/hq ADR 0555 " + - "§6, issue 1126): what a person decided of the machine (endpoint, site, hub, address on the private network, public " + - "domain, accounts, adoption, the tunnel found at adoption) and where its modules run (in order, with host ports, " + - "bindings, config placement layers, pins and holdings), read by the controller's own code from its own store, as JSON. " + - "Read-only; the one verb the controller gains for its exit (the operator, 2026-10-26 19:06:40), read once by the seed " + - "of the placement files.", - Input: schema(map[string]string{"node": "one machine's name; every machine when absent"}, nil)}, - {Name: "modules", Description: "Every module the mesh holds: version, the commit it was built from, " + - "and which machines run it.", - Input: schema(nil, nil)}, - {Name: "seats", Description: "Every seat the mesh defines, what it delivers, and who holds it.", - Input: schema(nil, nil)}, - {Name: "builds", Description: "What has been built lately and what came of it, for every module or for one; " + - "or, given a build's id, everything the build machine said while building it, line by line, from the bus.", - Input: schema(map[string]string{ - "module": "one module's name; every module when absent", - "log": "a build's id (as `builds` lists it): print what the build machine said, line by line", - "limit": "how many builds to list (default 20); not with log", - }, nil)}, - removedInR6("plans"), - // The verbs the delivery's owner asks with (novox/hq ADR 0239): the planner's answer, a group's order and - // composed check, and the start and end of a walk. Callable by a person too; mesh-delivery is who needs them. - {Name: "delivery-plan", Description: "Removed in R4e (ADR 0501 §5 / ADR 0503 §3): refused by name, naming its replacement", - Input: schema(nil, nil)}, - removedInR6("delivery-order"), - {Name: "delivery-check", Description: "Answers that the checker (mesh-delivery-checker) judges a group (novox/hq " + - "ADR 0503 §3); asks nothing. Its inputs stay, since hq's repository check still names it (ADR 0513 §4).", - Input: schema(map[string]string{"group": "the group's id (none for one head checked again)", - "members": "the members, as JSON, as delivery-order takes them", - "ready": "\"true\" when the pull request is marked ready: its check is queued before every other check (novox/hq ADR 0406 §3)"}, - []string{"members"})}, - removedInR6("deliver"), - removedInR6("delivery-stop"), - removedInR6("delivery-backups-again"), - removedInR6("delivery-walks"), - removedInR6("plan"), - {Name: "assign", Description: "Put a module on a machine. Refused with the mesh's own words when it cannot resolve there, " + - "or when a seat its resources are applied through is held by nothing on the machine (novox/hq ADR 0207). " + - "A module not registered yet is looked for (novox/hq issue 325, ADR 0261): while its build runs — a merge " + - "that adds a module asks for one — the assignment is kept as pending and the controller makes it when the " + - "build registers it; `status` lists it, and if the build fails it expires with why and raises a condition. " + - "A module known and not built is said, and build asks for its build; a name with no module and no build " + - "request kept is refused with the closest names.", - Input: schema(map[string]string{"node": "the machine's name", - "module": "the module's name; several comma-separated are judged together", - "build": "\"true\": for a module known and not built (its last build failed, or was never asked), ask " + - "for its build and keep this assignment pending until the build registers it"}, - []string{"node", "module"}, "build")}, - {Name: "unassign", Description: "Take a module off a machine. Refused when it holds a seat a module left there depends on.", - Input: schema(map[string]string{"node": "the machine's name", - "module": "the module's name; several comma-separated are judged together"}, []string{"node", "module"})}, - {Name: "pin", Description: "Tell a machine which provider answers a provision for it — the module, and the node " + - "it runs on, both. Asked for when more than one could answer; the refusal lists them.", - Input: schema(map[string]string{ - "node": "the machine's name", - "provision": "the provision, as the consumer requires it", - "from": "the node the chosen provider runs on", - "module": "the module providing it there", - }, []string{"node", "provision", "from", "module"})}, - {Name: "unpin", Description: "Take that choice back, putting the question to the mesh again.", - Input: schema(map[string]string{"node": "the machine's name", "provision": "the provision"}, []string{"node", "provision"})}, - removedInR6("push"), - {Name: "rotate", Description: "Replace a credential. A pair credential, by provision (and a consuming machine and module, " + - "else every holder): both ends are re-sent together. Or a module's own secret, by machine, module and " + - "name: made anew and the machine sent, so the module starts again on it — for a secret its definition " + - "says it reads at start, whether the mesh made the value or a person gave it (novox/hq ADR 0228); one " + - "an outside party issued, or one the module applies to a backend, is refused with the reason.", - Input: schema(map[string]string{ - "provision": "a pair credential: the provision whose credential to replace", - "consumer": "with provision: only the holder on this machine (optional)", - "node": "an own secret: the machine", - "module": "an own secret: the module; with provision: only this consuming module's credential (optional)", - "secret": "an own secret: its name in the module's definition", - "why": "an own secret: why it is rotated — recorded in the hand-act log (optional)", - "cause": "with why: the cause in a word, the word a second rotation for the same reason uses (optional)", - }, nil)}, - {Name: "give", Description: "Take a module's own secret from the operator at their desk (novox/hq ADR 0259 " + - "§10): the same as secret-ask with the desk named. A prompt that does not show what is typed opens on " + - "the machine named by at, its answer comes back sealed to this call alone, and is sealed to the " + - "module's machine as `secret accept` seals it. The value is never an argument and never in the answer: " + - "the answer says it was taken, or why not. Recorded in the hand-act log as a value given at the desk. " + - "The prompt waits 25 seconds; dismissed or unanswered, nothing changes. Then push the machine.", - Input: schema(map[string]string{ - "node": "the machine the module runs on, which the secret is sealed to", - "module": "the module's name", - "secret": "the own secret's name in the module's definition", - "at": "the machine the operator sits at, where the prompt opens", - }, []string{"node", "module", "secret", "at"})}, - {Name: "issued", Description: "Take the credential a provider's software issued for a provision — a media " + - "server's sign-in token — and keep it as that provision's one credential for every consumer (novox/hq ADR " + - "0305): the controller asks the provider's own tool on its machine, in the controller's own name, for the " + - "value sealed to a key made for this call alone, seals it to every holder and forgets it, and sends their " + - "machines when it changed. Carries no value and answers none; a value every holder already holds is not " + - "sealed again, so asking again restarts nothing. Refused for a provision no offer says its software issues.", - Input: schema(map[string]string{ - "provision": "the provision whose provider's software issues its credential, e.g. plex-api", - }, []string{"provision"})}, - {Name: "secret-move", Description: "Move every secret the controller holds into the vault, as it is (novox/hq " + - "ADR 0349 clause 7, design 57's V3): each machine's node-engine opens what was sealed to it and hands it to " + - "the vault, which takes it from that machine alone, never over a value it holds, and reads it back; the " + - "record of asks at a desk is copied once. No value is rotated, in the arguments or in the answer, and " + - "nothing points at the vault because of it. Answers, per machine, what moved, what was there already and " + - "what did not move and why; a machine not answering is moved when it is next heard from. Run again, it " + - "moves what is left.", - Input: schema(map[string]string{ - "node": "only this machine's secrets (optional; every machine's without it)", - }, nil)}, - {Name: "secret-ask", Description: "Ask the operator for a module's own secret (novox/hq ADR 0277): a prompt " + - "that shows nothing of what is typed opens at the desk — the module's machine, or the one at names — " + - "naming the module, the secret and who asked; the operator types the value there, never on a channel " + - "and never in a verb's argument; the answer comes back sealed to this call alone and is sealed to the " + - "module's machine as `secret accept` seals it. The answer says the value was taken, or why not. " + - "Refused for a secret the mesh issues itself (the bus account, one the mesh may make) and for a module " + - "that runs as an account of its own, whose value is typed at the controller's terminal. Bounded: one " + - "open prompt per secret, three asks an hour. Recorded in the hand-act log, with who asked, and " + - "announced on every channel. Then push the machine.", - Input: schema(map[string]string{ - "node": "the machine the module runs on, which the secret is sealed to", - "module": "the module's name", - "secret": "the own secret's name in the module's definition", - "at": "the machine the operator sits at, where the prompt opens; the module's machine when absent", - }, []string{"node", "module", "secret"})}, - {Name: "issue", Description: "Give a module on a machine its account on the bus: minted, and sealed to the " + - "machine as the module's own secret named broker, read at the next push of that machine. For a module " + - "whose definition declares that secret; refused with the reason otherwise. Issued again, it replaces the account.", - Input: schema(map[string]string{ - "node": "the machine that runs the module", - "module": "the module's name", - }, []string{"node", "module"})}, - {Name: "token", Description: "Refused through a verb since novox/hq ADR 0266: the one-time token a machine " + - "joins with is answered to its caller, and whoever may call a verb includes agents. Issue it at the " + - "controller's terminal: `mesh-controller token issue --new --overlay-key `.", - Input: schema(map[string]string{ - "node": "a machine the mesh already has a record for", - "new": "or the name of a machine to create the record for", - "overlay_key": "the public half of the machine's tunnel key", - "for": "how long it may be used, as a duration (default 1h)", - "adopted": "\"true\" when the machine is in use and joins adopted", - }, nil, "adopted"), - Replaces: []string{"mesh-controller token issue", "wg set"}}, - {Name: "settings", Description: "Read or set what an assignment is configured with: a module's settings for the whole " + - "mesh, or for one machine. Without values or clear, answers the layer as it stands — read it before setting it — " + - "then every value the module gives a default or a layer sets, with where it comes from: the module's default, " + - "the mesh, or the machine (novox/hq ADR 0262); with list \"preferences\", or with no module, every " + - "module's preferences and each machine's value; " + - "with history, the layers it replaced. Setting replaces that layer whole and answers each key it adds (+), " + - "changes (~) and removes (-); a set that would remove a key is refused unless replace says it is meant " + - "(novox/hq ADR 0217). Takes effect at the next push. With clear, removes the layer and the module is back to " + - "what its definition says; a cleared or replaced layer is kept in the history. A trusted setting — " + - "places, accesses, what a provider serves, what a trusted file asks for, the whole layer of a module " + - "with a trusted mergeable file — is refused through this verb (novox/hq issue 339, 340) and PROPOSED " + - "instead (novox/hq ADR 0277): with propose, the values (or clear) are put to the operator on a channel " + - "that proves who answers, with every key and its exact new value, and the layer is set only on their " + - "Approve; the answer names the proposal, its fingerprint and when it expires, and nothing changes until " + - "then. With proposals, every proposal and where each stands; with proposal, one whole. Once move-config has " + - "run, a module in the config form forwards to mesh-config (issue 664): show lists what config lists, set and " + - "clear set and clear each value as the module's own on the machine (none for the whole mesh), placement " + - "staying beside the assignment, and propose proposes each value through mesh-config.", - Input: schema(map[string]string{ - "module": "the module's name; with list, only that module's", - "values": "the settings as a JSON object, for set or propose", - "node": "one machine; the whole mesh when absent", - "clear": "\"true\" to remove the layer instead of setting it, or to propose its removal; not with values", - "replace": "\"true\": with values, the set (or the proposal) is meant to remove the keys the layer had and it does not name", - "history": "\"true\": without values or clear, the layers this one replaced, the latest first", - "list": "\"preferences\": every module's preferences — key, default and why — and the value on each " + - "machine it is assigned to with where it comes from; module and node narrow it (novox/hq ADR 0262)", - "propose": "\"true\": propose the values (or clear) to the operator instead of setting them (novox/hq ADR 0277)", - "proposals": "\"true\": every settings proposal, newest first, and where each stands", - "proposal": "a proposal's id: that proposal whole, its values and the layer it was shown against", - }, nil, "clear", "replace", "history", "propose", "proposals")}, - {Name: "command", Description: "Run one reading command line of the controller's own, as you would type it at " + - "its shell — `node show ace`, `module list`, `plans`, `conditions show ` — and answer what it " + - "printed. The generic verb beside the named ones (novox/hq ADR 0154), and since ADR 0266 it only reads: " + - "status, version, help, seats, healers, hand-acts, durations, collection, images, artifacts, data, builds, " + - "plan, plans (not stop/close/go), doctor (not run), conditions list/show/history, node list/show, " + - "module list, settings show/preferences, retire list, cleanup list, delivery plan/walks, bus, mirrors (not " + - "--record/--confirm). What writes has its named verb; what sets a key, issues a credential or a token, or " + - "accepts, recovers or exports a secret is the controller's terminal's alone.", - Input: schema(map[string]string{ - "command": "the command line, as the controller's binary takes it; quotes group a word with spaces", - }, []string{"command"})}, - // The build queue went with build-agent (novox/hq ADR 0525 §9): asks are the build seats' streams, their backlog the - // streams' own state, shown in the delivery view. The verbs are refused by name for one release, naming that. - {Name: "queue", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: the build seats' backlog in the delivery view", - Input: schema(nil, nil)}, - {Name: "cancel", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, - {Name: "clear", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, - {Name: "replay", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, - // novox/hq issue 686: a builder ran a branch's store tests for the first time in its pull request's merge check. - {Name: "try", Description: "Try a pushed branch or commit before its pull request (novox/hq issue 686): the " + - "checker (mesh-delivery-checker) merges the head onto the repository's trunk and runs its levels on the merge, " + - "as it judges a pull request (ADR 0503 §3), its levels asks on the build seats (ADR 0525 §1); no merge-check.sh is run. Answers at " + - "once with an id; asked again with that id it waits up to 20 s and answers pass, fail or error, how long it " + - "took and the lines naming what failed — or that it is still waiting or running, to ask again. Sets no " + - "status on the forge, registers and builds nothing for delivery. One try of a commit at a time — a second " + - "ask answers the first's id. Only for a repository of the mesh; the ref must be on the forge.", - Input: schema(map[string]string{ - "repository": "the repository on the forge, owner/name; with ref", - "ref": "the branch or commit to try, as pushed to the forge", - "base": "the branch it is merged into (the repository's default branch when none is named)", - "tests": "only the tests this `go test -run` pattern names", - "store-only": "\"true\": only the integration level", - "id": "instead of a repository: the id a try answered, to read what came of it", - }, nil, "store-only"), - Replaces: []string{"sh merge-check.sh", "docker run postgres"}}, - {Name: "kill", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: a build seat's ask ends where it runs", Input: schema(nil, nil)}, - {Name: "pause", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: /node-build-.pause", Input: schema(nil, nil)}, - {Name: "resume", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: /node-build-.resume", Input: schema(nil, nil)}, - // Acts done by hand, and what the bounds are set from (novox/hq to-be 45 §7, Phase 0). - {Name: "hand-act", Description: "Record an act done by hand outside the mesh — a container restarted, a file " + - "edited, a service started on a machine — with why and its cause, in the hand-act log beside the pushes and " + - "plans ended by hand (novox/hq to-be 45 §7). A cause recorded twice in a fortnight is a healer wanted.", - Input: schema(map[string]string{ - "what": "what was done, in a line", - "why": "why it had to be done by hand", - "cause": "the cause in a word, or a condition's kind — the word a second act for the same reason uses", - "condition": "the key of the condition it addressed, if any (optional)", - }, []string{"what", "why", "cause"})}, - {Name: "drill", Description: "Record a drill — something broken on purpose to see the mesh raise and clear it: " + - "a module stopped, a process killed — with what it tests, in the hand-act log. A drill is a person's " + - "deliberate test, never a repair: no healer is wanted for it however often it is run (S15).", - Input: schema(map[string]string{ - "what": "what was done on purpose, in a line", - "why": "what the drill tests", - "condition": "the key of the condition the drill is meant to raise, if any (optional)", - }, []string{"what", "why"})}, - {Name: "warranted", Description: "Record in the hand-act log what a module did on the operator's warrant (novox/hq " + - "ADR 0274, ADR 0259): who chose, through which channel, with which proofs and which answer are read from the " + - "router's own record of that module's ask, never from the caller; recorded once per ask however often it is " + - "asked; the caller names the ask and says nothing else. Refused for an ask still open, ended without a choice, " + - "or not that module's.", - Input: schema(map[string]string{ - "asker": "the module that asked, e.g. claude-code", - "ask": "its ask's id", - }, []string{"asker", "ask"})}, - {Name: "hand-acts", Description: "What was done by hand lately — pushes, plans ended, consumers re-made, acts " + - "recorded — who, why and the cause of each, and which causes repeat: each repeat is a healer the mesh lacks.", - Input: schema(map[string]string{"days": "how many days back (default 14)"}, nil)}, - {Name: "durations", Description: "How long things take, as the controller measured them: a send to its machine's " + - "report (apply), a plan's tier, a build, and each phase of an " + - "ended walk per class (walk-phase, novox/hq ADR 0282) — per machine, repository, module or class/phase, with " + - "median, p90 and max. What the core's bounds are set from (novox/hq to-be 45 Phase 0).", - Input: schema(map[string]string{ - "kind": "one kind: apply, plan-tier, build or walk-phase; every kind when absent", - "days": "how many days back (default 14)", - }, nil)}, - // What is wrong, and the self-check (novox/hq to-be 45 §2, §4). - {Name: "conditions", Description: "What is wrong with the mesh now: every open condition, urgent first, " + - "then oldest — raised by the watchdogs of the signals table, the self-check's probes and the providers' " + - "own words, and cleared when observation says it is resolved, never by hand. Given a key, that one " + - "whole with its evidence; with history, every transition lately; with silence, stop one's messages " + - "for a while — a hand act, which says why (novox/hq to-be 45 §2).", - Input: schema(map[string]string{ - "key": "a condition's key: that one whole; with history, only its transitions", - "scope": "only this scope: machine, plan, call, build, merge, provider, seat, bus, core, probe or mesh", - "severity": "only urgent, or only warning", - "machine": "only those about this machine", - "history": "\"true\": every raising, change, silence and clearing lately, oldest first", - "days": "with history: how many days back (default 7, at most 90)", - "silence": "a condition's key: send no message for it for a while; it stays open and in status", - "for": "with silence: how long — 30m, 4h, 2d; at most 7d", - "why": "with silence: why — required, and recorded in the hand-act log", - "cause": "with silence: the cause in a word (the condition's kind when absent)", - }, nil, "history")}, - // What the healers did (novox/hq to-be 45 §7). - {Name: "healers", Description: "The healers (novox/hq to-be 45 §7): each registered response to one kind " + - "of condition — its repair (the ordinary path again), its budget, what happens when it is spent — every act " + - "they took lately with its outcome, and the mesh-wide brake. A heal is never a hand act; whether it " + - "repaired anything is the condition's clearing to say. Each act is also in its condition's tried.", - Input: schema(map[string]string{"days": "how many days of acts back (default 7)"}, nil)}, - {Name: "doctor", Description: "The self-check (novox/hq to-be 45 §4): the last run's verdict at once — " + - "each probe of the design's live invariants passed, failed or could not run, and how long ago. With " + - "run, a run now; with probes, the registry; with signals, every row of the signals table and the age " + - "of its newest signal. Runs every five minutes on its own; each failure is an open condition.", - Input: schema(map[string]string{ - "run": "\"true\": run every probe now and answer the verdict", - "probes": "\"true\": the registry — what each probe asserts, and the condition it raises", - "signals": "\"true\": the signals table, each row with the age of its newest signal", - "probe": "one probe's id (D14): its verdict alone, with every finding — the verdict shows at most twenty a probe", - }, nil, "run", "probes", "signals")}, - // How a module's new builds reach its machines, and the bus's planned step (novox/hq ADR 0236). - {Name: "upgrade", Description: "Whether each module's new builds wait for a person (novox/hq ADR 0236, ADR 0539 clause 14): " + - "for a module of policy record, the assigner writes no want naming a composition judged to recreate a container or move " + - "data until the operator answers its ask; with where the policy comes from (a person, the module, the bus, its " + - "irreplaceable data, the default) and why. With module, that one; with policy, a person's choice for it — roll-out, " + - "record (with why) or default to take the choice back. The bus is never moved by the assigner (ADR 0539 clause 15).", - Input: schema(map[string]string{ - "module": "one module", - "policy": "with module: roll-out, record or default", - "why": "with policy: why — required for record, kept", - }, nil)}, - {Name: "bus", Description: "The bus as a person's step (novox/hq to-be 45 §8, ADR 0236, ADR 0539 clause 15): what a bus " + - "upgrade would do — the bus's build on each machine against the one the mesh holds — and how the last step went. " + - "With upgrade, check one: a person's act with why, after the streams are snapshotted (snapshot-taken says where, " + - "while the mesh takes none itself), saying first whether it can be reverted; then it answers the want the operator " + - "writes with the operator's credential, want.., recorded — nothing is sent, and the assigner never " + - "moves the bus.", - Input: schema(map[string]string{ - "upgrade": "\"true\": start the bus's upgrade", - "why": "with upgrade: why — required, recorded in the hand-act log", - "cause": "with upgrade: the cause in a word (default bus-upgrade)", - "reversible": "\"true\": with upgrade, the new version can be undone by putting the old one back", - "irreversible": "\"true\": with upgrade, it cannot — your explicit word that it runs anyway", - "snapshot-taken": "with upgrade: where the streams' snapshot you took is", - }, nil, "upgrade", "reversible", "irreversible")}, - // A consumer the mesh stopped asking for: retired, waiting for a person, deleted only by one - // (novox/hq ADR 0230). - {Name: "retire", Description: "A consumer the mesh stops asking for is retired by its provider — access " + - "disabled, data kept — after the same answer in five passes; more than three at once, or more than half " + - "of those held, waits for a person. With no answer, every provider that waits and what it would retire. " + - "With answer approve or reject, a node and a module: retire exactly what that provider waits with, or " + - "keep it active — a hand act, which says why (novox/hq ADR 0230).", - Input: schema(map[string]string{ - "answer": "approve or reject: answer what the provider waits with (needs node, module and why)", - "node": "with answer: the machine the provider runs on", - "module": "with answer: the provider module", - "why": "with answer: why — required, and recorded in the hand-act log", - "cause": "with answer: the cause in a word (retire-waiting when absent)", - }, nil)}, - {Name: "cleanup", Description: "Every consumer a provider holds retired — its age, its size where the " + - "backend knows, and why it was retired — and every module's own data retired on its machine (ADR 0233). " + - "With consumer (and node, module): the provider deletes that one retired consumer — never an active one; " + - "for a module's own retired item, consumer names the item and the machine's backup holder takes a last " + - "restore point of it, then deletes it. With older-than: every retired consumer older than that many " + - "days, listed; deleted only with confirm. Deleting is a hand act, which says why (novox/hq ADR 0230).", - Input: schema(map[string]string{ - "node": "with consumer: the machine the provider runs on", - "module": "with consumer: the provider module", - "consumer": "delete this retired consumer (needs node, module and why)", - "older-than": "delete every consumer retired more than this many days (needs why; lists only without confirm)", - "confirm": "\"true\": with older-than, delete what is listed", - "why": "with consumer or older-than: why — required, and recorded in the hand-act log", - "cause": "with consumer or older-than: the cause in a word (cleanup-waiting when absent)", - }, nil, "confirm")}, - {Name: "judge-directories", Description: "Read-only: which of the directories one machine is declared its " + - "node-engine's walk that follows no link another account could make would refuse (novox/hq issue 358), one line " + - "each: placed, would be refused and why (the link and who could replace it), refused already by the placement " + - "guard, or below a home. The engine judges the declaration it keeps and makes, chmods and chowns nothing.", - Input: schema(map[string]string{ - "node": "the machine's name", - }, []string{"node"})}, - // What a consumer gave up on (novox/hq issue 330): kept in DEAD_LETTERS until a person acts on it. - {Name: "root-free", Description: "Whether each machine named is root-free now (novox/hq ADR 0259 §8): no agent " + - "there can become root without a person. Judged when asked, never from a condition: free only when the machine " + - "names an account its agents run as, its node-engine judged that account unable to become root within the " + - "last 15 minutes, and the login shell's execute is not served there. Anything else, a read that failed " + - "included, is not free and says why. The router asks it before an answer from a channel proving its sender " + - "may approve. Only reads.", - Input: listed(schema(map[string]string{ - "machines": "the machines to judge, by name: a list, or one text separated by commas", - }, []string{"machines"}), "machines")}, - {Name: "dead-letters", Description: "Every message a consumer on the bus gave up on after handing it over " + - "as often as it may, kept in DEAD_LETTERS: whose consumer, the subject, how often it was handed over and " + - "when it was given up, newest first. With id: that one whole, with what it said. With deliver: hand it " + - "again to the consumer that gave it up, and nobody else; with drop: let it go for good. Delivering and " + - "dropping are hand acts, which say why (novox/hq issue 330).", - Input: schema(map[string]string{ - "id": "a dead letter's id, as the list gives it: that one whole, with its message", - "consumer": "a consumer's name, or .: only what that one gave up on", - "limit": "how many to list, newest first (default 50); only when listing", - "deliver": "a dead letter's id: deliver it again to the consumer that gave it up (needs why)", - "drop": "a dead letter's id: drop it for good (needs why)", - "why": "with deliver or drop: why — required, and recorded in the hand-act log", - "cause": "with deliver or drop: the cause in a word (dead-letter when absent)", - }, nil)}, - // The data every machine declares (novox/hq ADR 0233). - {Name: "data", Description: "Every item of data every machine declares, as the self-check last measured it: " + - "its class (irreplaceable, rebuildable, cache), where it is, its size, its newest write, its newest good " + - "backup and the bound on it — and what is retired: kept after its module left the machine, removed only by " + - "`cleanup delete` (novox/hq ADR 0233).", - Input: schema(map[string]string{ - "machine": "one machine (optional)", - "retired": "\"true\": only what is retired", - }, nil, "retired")}, - {Name: "build", Description: "Removed in R4e (ADR 0501 §5 / ADR 0503 §3): refused by name, naming its replacement", - Input: schema(nil, nil)}, - // What the records say the registries may keep, asked on demand (novox/hq ADR 0251, to-be 51). - {Name: "artifacts", Description: "Every artifact the mesh recorded making that lives in its artifact store, " + - "each with its repository, digest, kind (image or archive) and state: kept — and why: a definition names " + - "it, or one of the five most recent builds of a module the mesh holds — or eligible: made, kept for no " + - "reason, not yet let go. Collected ones are counted, and listed only with collected. Reads only " + - "(novox/hq ADR 0251).", - Input: schema(map[string]string{ - "repository": "only this repository's references, as / (the counts stay whole)", - "collected": "\"true\": list the collected references too (they only grow; counted always)", - }, nil, "collected")}, - // Whether the artifact store holds what a builder's done key names, before the builder reuses it (novox/hq issue - // 1937): the store's collector deletes a blob nothing holds, and a key the controller never took in holds nothing. - {Name: "held", Description: "Whether the artifact store holds each reference given, asked with one HEAD each: " + - "held, gone (the store answered 404) or unknown with why. The builder asks it before it reuses a done key, and " + - "builds a key whose artifact is gone again. Reads only: nothing is held or written (novox/hq issue 1937).", - Input: listed(schema(map[string]string{ - "references": "the artifacts' references, artifact-store:////blobs/sha256:: a list, " + - "or one text separated by commas", - }, []string{"references"}), "references")}, - {Name: "collect", Description: "The artifact store's sweep, now: what the mesh made and keeps for no reason " + - "is let go of. Without confirm a dry run — every kept archive asked about with HEADs, what would be let go " + - "listed, nothing held or deleted. With confirm and why: every kept archive held first, then each eligible " + - "artifact let go of, oldest first, and recorded collected — a hand act, recorded with its why. A confirmed " + - "collect lets an index go with the platform manifests no kept index names (read first; any read that " + - "fails lets nothing go); the sweep after a build leaves an eligible index for such a collect (novox/hq ADR " + - "0257). Bounded by " + - "most (500 by default, at most 5000) and 45 seconds; what is left is said. Bytes are reclaimed by the " + - "store's nightly collector. Never touches a digest the mesh did not record (novox/hq ADR 0189, ADR 0251).", - Input: schema(map[string]string{ - "confirm": "\"true\": let go of what is eligible (needs why); without it nothing is done", - "why": "with confirm: why — required, and recorded in the hand-act log", - "most": "let go of at most this many (500 by default, at most 5000)", - }, nil, "confirm")}, - {Name: "move-placement", Description: "The move of placement out of the settings layers, run once after the " + - "delivery that holds it (novox/hq ADR 0348 clause 11, issue 736): every placement key a settings layer still " + - "holds — endpoints, expose, reach and ports, which a layer set before then keeps among its settings — is put " + - "beside the assignment, where places, accesses and networks already are. Without confirm it says which layers " + - "hold which keys and moves nothing. With confirm and why: in one transaction, each such layer is kept whole " + - "in its history first (why: moved to placement), its keys are laid over the placement beside it, and the " + - "layer keeps its other keys, when it was set and who set it — and the act is recorded in the hand-act log. " + - "Answers each layer moved: its machine (empty for the whole mesh), its module and its keys. Nothing a " + - "machine is told changes: composition reads the keys as placement wherever they are. Asked again, it " + - "moves nothing.", - Input: schema(map[string]string{ - "confirm": "\"true\": move them (needs why); without it nothing is moved", - "why": "with confirm: why — required, and recorded in the hand-act log", - }, nil, "confirm")}, - {Name: "move-config", Description: "The move of config: every config value a settings layer holds is set in " + - "mesh-config, run once after the delivery that holds it and after move-placement (novox/hq ADR 0348 §9, " + - "issue 664). A machine's layer becomes the module's own value on that machine; a whole-mesh layer becomes " + - "mesh config for the predefined key its value falls back to where that module alone falls back to it, and " + - "otherwise the module's own value on each machine it is assigned to, laid under that machine's layer; a key " + - "a layer says by its file's name is set under its config key. Without confirm it says each value it would " + - "set, where from, each layer that stays (its module is in the settings form) and anything that refuses the " + - "move, and sets nothing. With confirm and why: each value is set through mesh-config in the controller's " + - "name, every module's resolved values are read back and compared with what its layers lay today, and only " + - "then, in one transaction, is each layer moved kept whole in its history first (why: moved to mesh-config) " + - "and emptied, and the controller switched to read mesh-config where it read the layers — the act recorded " + - "in the hand-act log. Nothing a machine is told changes. Asked again, it sets nothing set already.", - Input: schema(map[string]string{ - "confirm": "\"true\": set the values and switch (needs why); without it nothing is set", - "why": "with confirm: why — required, and recorded in the hand-act log and with each value", - }, nil, "confirm")}, - {Name: "mirrors", Description: "The bases the mesh copied into its artifact store for builds to stand on, as " + - "the records hold them: each with its state — kept because a kept build stood on it, or eligible — and " + - "which build or person recorded it. With record: references of copies no record names (a copy made " + - "before copies were recorded, or by a build that failed before then) to record as the mesh's, so the " + - "sweep keeps or lets go of them like any other; only a repository the mirror writes is taken " + - "(upstream//, or a module's /on-), and each must be in the store. Without " + - "confirm a dry run; with confirm and why, recorded — a hand act. A recorded copy is eligible: the next " + - "sweep lets it go unless a kept build stood on it. Records only: this verb deletes nothing " + - "(novox/hq ADR 0257).", - Input: schema(map[string]string{ - "record": "references to record, artifact-store://@sha256:, separated by spaces or commas", - "confirm": "\"true\": record them (needs why); without it nothing is recorded", - "why": "with confirm: why — required, and recorded with each copy and in the hand-act log", - }, nil, "confirm")}, - {Name: "images", Description: "Every container image one machine's declaration names — the declaration the " + - "mesh would send it now and the one it was last sent — each with the resources naming it: what the " + - "machine keeps when it prunes its images. sent_known is false when the last one sent is not kept with " + - "its images. Reads only (novox/hq ADR 0251).", - Input: schema(map[string]string{ - "node": "the machine's name", - }, []string{"node"})}, -} - // schema is a JSON schema for an object of string properties, which is every argument the verbs // above take. Kept small on purpose: a schema an agent cannot read is a tool it cannot call. The // switches are the properties that are "true" or "false" and nothing else, said in the schema as an diff --git a/go/catalogue/verbs_r4g_test.go b/go/catalogue/verbs_r4g_test.go deleted file mode 100644 index 41e1876..0000000 --- a/go/catalogue/verbs_r4g_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package catalogue - -import ( - "strings" - "testing" -) - -// 1887/A9b: the build queue's verbs are refused by name, naming what replaced them, and the verbs R4e removed are gone. -func TestTheBuildQueueVerbsAreRefusedByName(t *testing.T) { - rows := map[string]Verb{} - for _, v := range ControllerVerbs { - rows[v.Name] = v - } - for _, gone := range []string{"rebuild", "forget-checks"} { - if _, ok := rows[gone]; ok { - t.Errorf("the controller still lists %s", gone) - } - } - for _, refused := range []string{"queue", "cancel", "clear", "kill", "pause", "resume", "replay"} { - v, ok := rows[refused] - if !ok || !strings.HasPrefix(v.Description, "Removed in R4g (ADR 0525 §9): refused by name") { - t.Errorf("%s = %q, want refused by name", refused, v.Description) - } - } - if !strings.Contains(rows["queue"].Description, "delivery view") || !strings.Contains(rows["pause"].Description, "node-build-") { - t.Errorf("queue and pause do not name their replacements: %q, %q", rows["queue"].Description, rows["pause"].Description) - } - if strings.Contains(rows["try"].Description, "build-agent") { - t.Errorf("try still names build-agent: %q", rows["try"].Description) - } -} diff --git a/go/catalogue/verbs_r6_test.go b/go/catalogue/verbs_r6_test.go index 72d8f6f..546d8fe 100644 --- a/go/catalogue/verbs_r6_test.go +++ b/go/catalogue/verbs_r6_test.go @@ -5,58 +5,6 @@ import ( "testing" ) -// 1943/AC4: the controller's seat no longer offers a verb that starts, stops, retries, follows or reads a delivery -// plan, nor a push: each is a row with no input whose description refuses it by name and names its replacement, and -// each is in the removed list the holder answers from. upgrade keeps its policy and drops the plan's backlog. -func TestTheControllerSeatHasNoPlanVerbs(t *testing.T) { - rows := map[string]Verb{} - for _, v := range ControllerVerbs { - rows[v.Name] = v - } - gone := []string{"plans", "plan", "push", "deliver", "delivery-stop", "delivery-backups-again", "delivery-walks", "delivery-order"} - for _, name := range gone { - v, ok := rows[name] - if !ok { - t.Errorf("%s is not in the table, so a caller is not answered by name", name) - continue - } - replacement, removed := RemovedControllerVerbs[name] - if !removed || replacement == "" { - t.Errorf("%s is not in the removed list with its replacement", name) - } - if !strings.HasPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name") || !strings.Contains(v.Description, replacement) { - t.Errorf("%s = %q, want refused by name, naming %q", name, v.Description, replacement) - } - if props, _ := v.Input["properties"].(map[string]any); len(props) > 0 { - t.Errorf("%s still takes %v", name, props) - } - } - props, _ := rows["upgrade"].Input["properties"].(map[string]any) - for _, arg := range []string{"together", "backlog", "release-backlog", "cause"} { - if _, has := props[arg]; has { - t.Errorf("upgrade still takes %s, a plan's", arg) - } - } - for _, arg := range []string{"module", "policy", "why"} { - if _, has := props[arg]; !has { - t.Errorf("upgrade no longer takes %s", arg) - } - } - for _, word := range []string{"rolled", "gate", "backlog", "push"} { - if strings.Contains(rows["upgrade"].Description, word) { - t.Errorf("upgrade's description still speaks of %q: %q", word, rows["upgrade"].Description) - } - } - if !strings.Contains(rows["bus"].Description, "want") { - t.Errorf("bus does not say the operator writes the bus's want: %q", rows["bus"].Description) - } - for name := range RemovedControllerVerbs { - if v := rows[name]; !strings.HasPrefix(v.Description, "Removed in R6") { - t.Errorf("%s is in the removed list and its row says %q", name, v.Description) - } - } -} - // 1943/AC4: the delivery seat keeps the merge's verbs and no longer offers times, what-if, release, stop or // retire-history: each is a row with no input, optional so the holder of before and the holder of release 6 both hold // the seat, refusing it by name; mark-ready, served by nobody since issue 1022, is gone. @@ -101,15 +49,3 @@ func TestTheDeliverySeatHasNoPlanVerbs(t *testing.T) { t.Errorf("the gate of release 5 cannot hold the seat: %v", err) } } - -// 1943 (for 1945/AC14): the controller's seat says a bucket it deleted at its first start as its event, bucket-deleted -// (ADR 0539 clause 13). -func TestTheControllerSaysABucketItDeleted(t *testing.T) { - seat, ok := SeatNamed(ControllerSeatName) - if !ok { - t.Fatal("no controller seat") - } - if !SeatSays(seat.Emits, "bucket-deleted") { - t.Errorf("the controller's seat does not emit bucket-deleted: %v", seat.Emits) - } -} diff --git a/go/catalogue/verbs_test.go b/go/catalogue/verbs_test.go index 9a2b973..da85cec 100644 --- a/go/catalogue/verbs_test.go +++ b/go/catalogue/verbs_test.go @@ -68,21 +68,3 @@ func TestHoldingAMeshSeatRequiresServingItsVerbs(t *testing.T) { t.Fatalf("a holder serving every verb was refused: %v", err) } } - -// The mesh's own verbs are declared in full: an agent cannot call a name without a schema. -func TestEveryControllerVerbIsDescribedWithASchema(t *testing.T) { - seen := map[string]bool{} - for _, v := range ControllerVerbs { - if v.Description == "" || v.Input == nil || v.Input["type"] != "object" { - t.Errorf("%s: no description or no object schema", v.Name) - } - if seen[v.Name] { - t.Errorf("%s declared twice", v.Name) - } - seen[v.Name] = true - } - seat, _ := SeatNamed(ControllerSeatName) - if len(seat.Serves) != len(ControllerVerbs) { - t.Fatalf("the compiled mesh-controller seat serves %d verbs, the table has %d", len(seat.Serves), len(ControllerVerbs)) - } -} diff --git a/go/delivery/held.go b/go/delivery/held.go index 77e243a..3cd8711 100644 --- a/go/delivery/held.go +++ b/go/delivery/held.go @@ -8,19 +8,19 @@ import ( ) // Whether the artifact store still holds what a done key names (novox/hq issue 1937). A build module pushes a bundle or -// an archive as a bare blob (ADR 0498 §1), and the store's nightly collector deletes every blob no manifest names; only -// the controller's take-in and sweep hold one by its manifest. A done key the controller has not taken in can therefore -// name a blob that is gone, and reusing it sends a definition every machine fails on. So the builder asks before it -// reuses a done key, and the controller says when a take-in found a gone blob. +// an archive as a bare blob (ADR 0498 §1), and the store's nightly collector deletes every blob no manifest names. A done +// key can therefore name a blob that is gone, and reusing it sends a definition every machine fails on. So the builder +// asks before it reuses a done key, and the artifact sweep says when it found a gone blob. Both are the seat +// mesh-artifact-sweep's (novox/hq ADR 0571 §2, ADR 0590): the controller that served them first has ended. const ( - // HeldVerb is the controller seat's verb the builder asks, as a module's invokes names it: references in, each + // HeldVerb is the artifact sweep seat's verb the builder asks, as a module's invokes names it: references in, each // answered held or gone. - HeldVerb = "seat:mesh-controller.held" - // ArtifactsGoneEvent is the controller seat's event said when a take-in is refused because the store does not hold - // an artifact the key names: as a consumer names it, `mesh-controller.artifacts-gone`. + HeldVerb = "seat:mesh-artifact-sweep.held" + // ArtifactsGoneEvent is the artifact sweep seat's event said when the store does not hold an artifact a key names: + // as a consumer names it, `mesh-artifact-sweep.artifacts-gone`. ArtifactsGoneEvent = "artifacts-gone" // ArtifactsGoneSubject is where it is published (the seat's event subject). - ArtifactsGoneSubject = "mesh.seat.mesh-controller.event." + ArtifactsGoneEvent + ArtifactsGoneSubject = "mesh.seat.mesh-artifact-sweep.event." + ArtifactsGoneEvent ) // HeldAnswer is the answer of HeldVerb: the references the store holds, and those it answered 404 for. A reference the @@ -69,7 +69,7 @@ func DecodeHeldAnswer(raw []byte, asked []string) (HeldAnswer, error) { return a, nil } -// ArtifactsGone is the body of ArtifactsGoneEvent: the module whose builder's key the controller would not take in, +// ArtifactsGone is the body of ArtifactsGoneEvent: the module whose builder's key names a gone artifact, // the key and its fingerprint, and every reference of it the store does not hold. type ArtifactsGone struct { Event string `json:"event"` diff --git a/go/delivery/held_test.go b/go/delivery/held_test.go index f5b71c4..7b8a0c4 100644 --- a/go/delivery/held_test.go +++ b/go/delivery/held_test.go @@ -1,6 +1,7 @@ package delivery import ( + "slices" "strings" "testing" "time" @@ -55,30 +56,13 @@ func TestArtifactsGoneRoundTrip(t *testing.T) { } } -// The verb and the event are on the controller seat's contract under the names the builder uses. -func TestHeldOnTheControllerSeat(t *testing.T) { - if HeldVerb != "seat:"+catalogue.ControllerSeatName+".held" { - t.Fatalf("HeldVerb %q", HeldVerb) +// The verb and the event are on the artifact sweep seat's contract under the names the builder uses (ADR 0571 §2). +func TestHeldOnTheArtifactSweepSeat(t *testing.T) { + seat, ok := catalogue.SeatNamed(catalogue.ArtifactSweepSeat) + if !ok || HeldVerb != "seat:"+seat.Name+".held" || ArtifactsGoneSubject != "mesh.seat."+seat.Name+".event."+ArtifactsGoneEvent { + t.Fatalf("HeldVerb %q, ArtifactsGoneSubject %q", HeldVerb, ArtifactsGoneSubject) } - found := false - for _, v := range catalogue.ControllerVerbs { - if v.Name == "held" { - found = true - } + if !slices.ContainsFunc(seat.Serves, func(v catalogue.Verb) bool { return v.Name == "held" }) || !slices.Contains(seat.Emits, ArtifactsGoneEvent) { + t.Fatalf("the sweep seat serves %v and emits %v", seat.Serves, seat.Emits) } - if !found { - t.Fatal("the controller seat declares no verb held") - } - for _, s := range catalogue.DefaultSeats() { - if s.Name != catalogue.ControllerSeatName { - continue - } - for _, e := range s.Emits { - if e == ArtifactsGoneEvent { - return - } - } - t.Fatalf("the controller seat emits %v, not %s", s.Emits, ArtifactsGoneEvent) - } - t.Fatal("no controller seat") }