Remove the controller from health, hand acts, announcements and the provider

The take-over key chose between the controller's conditions and the keeper's; ADR 0590 clause 3
ends that choice, so readers hear the keeper (KeeperSeat) and the self-check (SelfCheckSeat)
alone and the key, its version and its two sources go. The controller's hand-act log, its
show verb, its kept announcements and its assignments mirror's name go too, and the hand-act
recorder's unread LeaseAway. Issue 1126 B4b.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 11:37:49 +01:00
parent 8757567a79
commit 66232896f3
10 changed files with 14 additions and 144 deletions
+2 -5
View File
@@ -24,15 +24,12 @@ const Event = "states"
// account may already publish to and the events stream keeps.
func Subject(module string) string { return "mesh.mod." + module + ".event." + Event }
// SubjectAll is every module's announcement, what the controller consumes.
// SubjectAll is every module's announcement.
const SubjectAll = "mesh.mod.*.event." + Event
// KeptBucket is the controller's bucket of the last announcement of each module, by Key.
const KeptBucket = "mesh-controller_states-announced"
var name = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
// Key is module's key in KeptBucket: the module itself, which must be one plain name, since a module's name is part of
// Key is module's key where the last announcement of each module is kept: the module itself, which must be one plain name, since a module's name is part of
// its buckets' names (the indexer's judge, novox/hq ADR 0578 §1).
func Key(module string) (string, error) {
if !name.MatchString(module) {
-3
View File
@@ -42,9 +42,6 @@ func TestAnAnnouncementRoundTripsSortedAndSaysWhatIsReadable(t *testing.T) {
if Subject("mesh-controller") != "mesh.mod.mesh-controller.event.states" || SubjectAll != "mesh.mod.*.event.states" {
t.Fatalf("the subjects are %q and %q", Subject("mesh-controller"), SubjectAll)
}
if KeptBucket != "mesh-controller_states-announced" {
t.Fatalf("the kept bucket is %q", KeptBucket)
}
if k, err := Key("mesh-delivery-builder"); err != nil || k != "mesh-delivery-builder" {
t.Fatalf("Key = %q, %v", k, err)
}
-3
View File
@@ -14,9 +14,6 @@ import (
// announced that state readable. An owner that has announced nothing yet is read as before, from the reader's `reads`
// (the transition, until every module announces), and is named as not yet announced.
// StatesAnnouncedBucket is the controller's bucket of the last announcement of each module (announce.KeptBucket).
const StatesAnnouncedBucket = announce.KeptBucket
// ReadsAnnouncedBy splits one reader module's `reads` (`<owner>.<name>`) by what each owner announced: granted when the
// owner announced the state readable (by this reader, where it named its readers: announce.State.Readers), when the
// composer names the reader among the state's readers (NamedReaders, ADR 0547 clause 9), or when the owner announced
+3 -11
View File
@@ -21,8 +21,6 @@ const (
WarrantsState = "warrants"
// EventRecorded is said on the seat for every act, with the act as its body.
EventRecorded = "act-recorded"
// ControllerActs is the controller's log as a reader names it, read until its lease ends.
ControllerActs = "mesh-controller.hand-acts"
)
// KeptFor is how long an act is kept.
@@ -63,15 +61,12 @@ type Act struct {
RequestedBy string `json:"requested-by,omitempty"`
// Outcome is what came of an act recorded after it was done.
Outcome string `json:"outcome,omitempty"`
// Log is which log holds it, set when it is read (LogSeat or LogController); never written.
// Log is which log holds it, set when it is read (LogSeat); never written.
Log string `json:"log,omitempty"`
}
// The logs an act may be held in.
const (
LogSeat = Seat
LogController = "mesh-controller"
)
// LogSeat is the log an act is held in: the seat's (the ended controller's log is read from its backup, ADR 0590).
const LogSeat = Seat
// Repair says the act counts toward a cause repeated (S15): not a drill, not an operator's answer, not recorded builds.
func (a Act) Repair() bool {
@@ -99,9 +94,6 @@ type Taken struct {
type Recorder struct {
// Ask calls a seat verb by its address (`seat:<seat>.<verb>`): stdio.Ask through the runtime.
Ask func(key string, body any) (json.RawMessage, error)
// LeaseAway is no longer read: nothing falls back to the controller. Kept so a caller that sets it builds; it goes
// once no module sets it.
LeaseAway func() bool
}
// NoResponder says err is the bus finding nobody to answer: the seat has no holder now.
+1 -2
View File
@@ -31,7 +31,6 @@ func TestAnActIsRecordedInTheLogThatAnswers(t *testing.T) {
name string
seat error
ctrl error
away bool
log string
refused string
asked string
@@ -47,7 +46,7 @@ func TestAnActIsRecordedInTheLogThatAnswers(t *testing.T) {
{name: "a warrant recorded", warrants: true, log: LogSeat, asked: "seat:mesh-hand-acts.warranted"},
} {
var asked []string
r := Recorder{LeaseAway: func() bool { return c.away }, Ask: func(key string, body any) (json.RawMessage, error) {
r := Recorder{Ask: func(key string, body any) (json.RawMessage, error) {
asked = append(asked, key)
if strings.HasPrefix(key, "seat:mesh-hand-acts.") {
return json.RawMessage(`{}`), c.seat
+1 -5
View File
@@ -182,13 +182,9 @@ func (c Condition) Began() time.Time {
return c.Raised
}
// ShowKeeper is the verb that shows more about a condition the keeper says (ADR 0564 Limits 9); a condition carried from
// the controller's bucket keeps the controller's (ShowController) until the lease write.
// ShowKeeper is the verb that shows more about a condition the keeper says (ADR 0564 Limits 9).
func ShowKeeper(key string) string { return "mesh-conditions.conditions key=" + key }
// ShowController is the controller's verb that shows more about a condition it raised.
func ShowController(key string) string { return "mesh-controller.conditions key=" + key }
// SilenceAction is the action that stops a condition's messages for a week, as the keeper offers it (ADR 0564 Limits 9:
// `mesh-conditions.silence`). An acknowledgement (ADR 0234 §8); its cause marks it as an answer.
func SilenceAction(key string) Action {
+1 -32
View File
@@ -165,7 +165,7 @@ func TestTheHealthLayoutsHoldTheKeepersRules(t *testing.T) {
t.Errorf("a finding under the old name read as %+v, %v", old, err)
}
if FindingsBucket(SelfCheckModule) != "mesh-self-check_findings" || Bucket(HealersModule, AttemptsState) != "mesh-healers_attempts" ||
Bucket(KeeperModule, TakeoverState) != "mesh-conditions_takeover" {
KeeperSeat != KeeperModule || SelfCheckSeat != SelfCheckModule {
t.Errorf("the buckets are named %s, %s", FindingsBucket(SelfCheckModule), Bucket(HealersModule, AttemptsState))
}
@@ -174,37 +174,6 @@ func TestTheHealthLayoutsHoldTheKeepersRules(t *testing.T) {
(Tried{At: at, What: "asserted the grants", Outcome: "escalated: budget spent", By: "healer H3"}) {
t.Errorf("an attempt is tried as %+v", tr)
}
// The take-over key (clause 9): absent follows the controller; the keeper's names who chose; anything else refused.
for _, c := range []struct {
raw string
found bool
seat, events string
fails bool
}{
{found: false, seat: "mesh-controller", events: "mesh-controller.doctor-heartbeat"},
{raw: `{"v":1,"follow":"controller","by":"the operator"}`, found: true, seat: "mesh-controller", events: "mesh-controller.doctor-heartbeat"},
{raw: `{"v":1,"follow":"keeper","by":"the operator","ask":"ask-1"}`, found: true, seat: "mesh-conditions", events: "mesh-self-check.checked"},
{raw: `{"v":1,"follow":"keeper"}`, found: true, fails: true},
{raw: `{"v":1,"follow":"both","by":"x"}`, found: true, fails: true},
{raw: `{"v":2,"follow":"keeper","by":"x"}`, found: true, fails: true},
} {
got, err := DecodeTakeover([]byte(c.raw), c.found)
if c.fails != (err != nil) {
t.Errorf("%q: read as %+v, %v", c.raw, got, err)
continue
}
if c.fails {
continue
}
seat, event := got.SelfCheckEvent()
if got.Seat() != c.seat || seat+"."+event != c.events {
t.Errorf("%q follows %s and %s.%s", c.raw, got.Seat(), seat, event)
}
if b, err := EncodeTakeover(got); err != nil || !strings.Contains(string(b), `"v":1`) {
t.Errorf("%q encodes as %s, %v", c.raw, b, err)
}
}
}
// credentialNamed is the first field of a JSON value whose name ends as mesh-tools' credentialEndings do, or "".
+5 -78
View File
@@ -25,10 +25,6 @@ const (
FindingsState = "findings"
// ConditionsState is the keeper's open conditions, one key each, without their looks (clause 3).
ConditionsState = "conditions"
// TakeoverState is the keeper's take-over, one key TakeoverKey (clause 9).
TakeoverState = "takeover"
// TakeoverKey is the take-over state's one key.
TakeoverKey = "switch"
// AttemptsState is the healers' acts begun, each created before it is made (clause 7); WouldState the acts they
// would have taken while the lease names a controller (clause 8), which the keeper never reads.
AttemptsState = "attempts"
@@ -272,79 +268,10 @@ type Would struct {
Why string `json:"why"`
}
// Which source the readers of conditions follow (clause 9).
// The readers of conditions (the router, the grants part and mesh-watcher) hear the keeper's condition events on
// KeeperSeat and the self-check's run as `checked` on SelfCheckSeat: the controller's events and the take-over key that
// chose between the two sources ended with it (novox/hq ADR 0590 clause 3).
const (
FollowController = "controller"
FollowKeeper = "keeper"
KeeperSeat = "mesh-conditions"
SelfCheckSeat = "mesh-self-check"
)
// TakeoverVersion is the version of the take-over key's value.
const TakeoverVersion = 1
// Takeover is the value of the keeper's key `takeover.switch` (clause 9): which source the router, the grants part and
// mesh-watcher hear, the controller's condition events and `doctor-heartbeat`, or the keeper's events and the
// self-check's `checked`. Written by the keeper alone, on the operator's warrant for a take-over and by `hand-back`. An
// absent key is FollowController.
type Takeover struct {
V int `json:"v"`
Follow string `json:"follow"`
At time.Time `json:"at"`
// By is who chose (the operator, as the warrant proves), and Why why; Ask the approve ask it was chosen on.
By string `json:"by"`
Why string `json:"why,omitempty"`
Ask string `json:"ask,omitempty"`
}
// EncodeTakeover is t as JSON at TakeoverVersion; it refuses a source the readers could not follow.
func EncodeTakeover(t Takeover) ([]byte, error) {
t.V = TakeoverVersion
if err := t.check(); err != nil {
return nil, err
}
return json.Marshal(t)
}
// DecodeTakeover reads the key as a getter answered it: an absent key follows the controller.
func DecodeTakeover(data []byte, found bool) (Takeover, error) {
if !found || len(data) == 0 {
return Takeover{V: TakeoverVersion, Follow: FollowController}, nil
}
var t Takeover
if err := json.Unmarshal(data, &t); err != nil {
return Takeover{}, fmt.Errorf("health: the take-over key is not readable: %w", err)
}
if t.V != TakeoverVersion {
return Takeover{}, fmt.Errorf("health: the take-over key is of version %d, and this reads %d", t.V, TakeoverVersion)
}
if err := t.check(); err != nil {
return Takeover{}, err
}
return t, nil
}
func (t Takeover) check() error {
if t.Follow != FollowController && t.Follow != FollowKeeper {
return fmt.Errorf("health: the take-over follows %q, not %s or %s", t.Follow, FollowController, FollowKeeper)
}
if t.Follow == FollowKeeper && strings.TrimSpace(t.By) == "" {
return fmt.Errorf("health: a take-over by the keeper names who chose it")
}
return nil
}
// Seat is the seat a reader hears condition events on while it follows t: mesh-controller or mesh-conditions.
func (t Takeover) Seat() string {
if t.Follow == FollowKeeper {
return "mesh-conditions"
}
return "mesh-controller"
}
// SelfCheckEvent is the seat and event a reader of the self-check's run hears while it follows t: the controller's
// `doctor-heartbeat`, or mesh-self-check's `checked`.
func (t Takeover) SelfCheckEvent() (seat, event string) {
if t.Follow == FollowKeeper {
return "mesh-self-check", EventChecked
}
return "mesh-controller", "doctor-heartbeat"
}
-4
View File
@@ -50,10 +50,6 @@ import (
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// AssignmentsBucket is the controller's assignments mirror, `<node>.<module>`, whose value carries the plan of a
// module handed to the provisioner.
const AssignmentsBucket = "mesh-controller_assignments"
// BucketOf is a providing module's own bucket of entries, one per consumer: `<module>_bound`, the module's state
// `bound`.
func BucketOf(module string) string { return module + "_bound" }
+1 -1
View File
@@ -99,7 +99,7 @@ func TestStateBusMapsTheProvidersBucketsToTheRuntimesStates(t *testing.T) {
rt.send(map[string]any{"jsonrpc": "2.0", "id": d["id"], "result": map[string]any{}})
rt.next()
for _, bucket := range []string{"other_bound", "elsewhere", AssignmentsBucket} {
for _, bucket := range []string{"other_bound", "elsewhere", "mesh-controller_assignments"} {
if err := bus.Watch(context.Background(), bucket, func(string, []byte) {}); err == nil || !strings.Contains(err.Error(), "not a bucket") {
t.Fatalf("a watch of %s gave %v", bucket, err)
}