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:
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user