diff --git a/modules/mesh-grants/cmd/mesh-grants/integration_test.go b/modules/mesh-grants/cmd/mesh-grants/integration_test.go index e8ea4d6..d1703b8 100644 --- a/modules/mesh-grants/cmd/mesh-grants/integration_test.go +++ b/modules/mesh-grants/cmd/mesh-grants/integration_test.go @@ -187,7 +187,7 @@ func TestTheGrantsPartWhole(t *testing.T) { if n := said.Load(); n != 0 { t.Errorf("%d memberships were said on their subject", n) } - for _, bucket := range []string{grants.Module + "_lease", grants.Module + "_takeover"} { + for _, bucket := range []string{"mesh-controller_lease", grants.Module + "_lease", grants.Module + "_takeover"} { if _, err := js.KeyValue(ctx, bucket); !errors.Is(err, jetstream.ErrBucketNotFound) { t.Errorf("the bucket %s: %v, want none", bucket, err) } diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 82aaed5..bc3d903 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -23,7 +23,6 @@ import ( "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/membership" "git.novox.be/novox/mesh-sdk/go/seal" @@ -725,44 +724,24 @@ func TestGrants(t *testing.T) { waitFor(t, func() bool { return wb.count(grants.CallUsers) > 0 && handle != nil }) passes := func() int { return wb.count(grants.CallUsers) } before := passes() - handle(grants.EventConditionRaised, []byte(`{"kind":"disk-full","key":"machine.a.disk-full"}`)) + handle(grants.EventKeeperRaised, []byte(`{"kind":"disk-full","key":"machine.a.disk-full"}`)) time.Sleep(100 * time.Millisecond) if passes() != before { t.Errorf("a condition of another kind woke a pass") } - // 1952/G1 (ADR 0564 §9): the keeper's events are not heard while the take-over key follows the controller - handle("mesh.seat.mesh-conditions.event.condition-raised", []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) - time.Sleep(100 * time.Millisecond) - if passes() != before || wf.Follows() != "mesh-controller" { - t.Errorf("the keeper's event woke a pass while the part follows %s", wf.Follows()) - } - handle(grants.EventConditionCleared, []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) - waitFor(t, func() bool { return passes() > before }) - // and from the take-over only the keeper's are heard, never both - watcher := wf.Watch.(*fakeWatcher) - waitFor(t, func() bool { - watcher.mu.Lock() - defer watcher.mu.Unlock() - return watcher.changes[grants.ReadTakeover] != nil - }) - watcher.mu.Lock() - toKeeper := watcher.changes[grants.ReadTakeover] - watcher.mu.Unlock() - toKeeper(health.TakeoverKey, []byte(`{"v":1,"follow":"keeper","by":"operator","at":"2026-10-29T20:00:00Z"}`)) - if wf.Follows() != health.KeeperModule { - t.Fatalf("the part follows %s after the take-over", wf.Follows()) - } - before = passes() + // 1126/B7b (ADR 0590 §3): only the keeper's events are heard, with no take-over key read; the controller's never. handle("mesh.seat.mesh-controller.event.condition-raised", []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) time.Sleep(100 * time.Millisecond) if passes() != before { - t.Errorf("the controller's event woke a pass after the take-over") + t.Errorf("the controller's event woke a pass") } - handle(grants.EventKeeperRaised, []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) + handle("mesh.seat.mesh-conditions.event.condition-raised", []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) waitFor(t, func() bool { return passes() > before }) - toKeeper(health.TakeoverKey, nil) // handed back: the key deleted follows the controller - if wf.Follows() != "mesh-controller" { - t.Errorf("the part follows %s after the hand-back", wf.Follows()) + before = passes() + handle(grants.EventKeeperCleared, []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) + waitFor(t, func() bool { return passes() > before }) + if slices.Contains(grants.Followed, "mesh-conditions.takeover") || slices.Contains(grants.Reads, "mesh-conditions.takeover") { + t.Errorf("the part still reads the keeper's take-over key: %v", grants.Reads) } wcancel() <-wdone @@ -2042,7 +2021,7 @@ func TestGrants(t *testing.T) { t.Errorf("memberships %v", got) } for _, r := range grants.Reads { - if strings.HasPrefix(r, "mesh-controller.") && r != grants.ReadLease { + if strings.HasPrefix(r, "mesh-controller.") { t.Errorf("the part reads %s", r) } } diff --git a/modules/mesh-grants/internal/grants/consumers.go b/modules/mesh-grants/internal/grants/consumers.go index 896dff1..ca7ce75 100644 --- a/modules/mesh-grants/internal/grants/consumers.go +++ b/modules/mesh-grants/internal/grants/consumers.go @@ -19,13 +19,6 @@ import ( // alone creates them, with the configuration the bus composer derives (buscompose.AccountConsumers), only where the bus // lacks one; one the bus holds is never changed or deleted by a pass, a difference is only said. -// ReadLease is the controller's lease as the part reads it (buscompose.NamedReaders names the vault and the part), and -// LeaseKey its one key. -const ( - ReadLease = buscompose.ControllerLease - LeaseKey = buscompose.ControllerLeaseKey -) - // ConsumerCreator creates one durable consumer the bus does not hold: the part's process alone, whose account is granted // the create of each account's consumer by stream and name. One the bus holds answers ErrHeld. type ConsumerCreator interface { @@ -88,9 +81,8 @@ func heldConsumers(c Caller, stream string) (map[string]heldConsumer, error) { return out, nil } -// ConsumersOn is why the part creates and matches the account consumers with no lease read: the controller ended by its -// unassignment, never by the lease write, so its lease names no holder for good and the part is their only maker -// (issue 1126; mesh-sdk's buscompose.LeaseHandedAway answers false for an absent key, so it is no longer asked here). +// ConsumersOn is why the part creates and matches the account consumers: the controller ended by its unassignment, so +// the part is their only maker (novox/hq ADR 0590, issue 1126). const ConsumersOn = "the grants part makes and matches the account consumers: the controller ended by its unassignment (issue 1126)" // consumers compares the account consumers the records compose with those the bus holds, and creates the missing ones diff --git a/modules/mesh-grants/internal/grants/follow.go b/modules/mesh-grants/internal/grants/follow.go index 5ecef55..dfaef64 100644 --- a/modules/mesh-grants/internal/grants/follow.go +++ b/modules/mesh-grants/internal/grants/follow.go @@ -6,9 +6,7 @@ import ( "strings" "sync" - "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" ) // Watcher watches another part's state: changed hears each change after what stands, ended hears the watch end. A watch @@ -23,8 +21,8 @@ type Watcher interface { // // And the seat mesh-enrolment's keys (ADR 0587 §4, §5): a key at live composes enrol., and its end, by the holder or by // the bus's expiry, is a change of the key that takes the user out; no event is needed beside the watch. -var Followed = []string{ReadModules, ReadResolved, ReadHave, ReadWants, ReadAccounts, ReadPlacement, ReadLease, ReadTakeover, - ReadEnrolments, ReadNodeLogins} +var Followed = []string{ReadModules, ReadResolved, ReadHave, ReadWants, ReadAccounts, ReadPlacement, ReadEnrolments, + ReadNodeLogins} // Follower composes again on every change it hears, never on a timer. It never stops for a refusal: a watch it cannot // open is said, composed without, and opened again at the next wake (any change it does hear, or a reconnect). @@ -34,8 +32,8 @@ type Follower struct { Logf func(string, ...any) // Asked watches the part's own ask to compose now (a tool's compose-now), waking at each; nil watches none. Asked func(wake func()) error - // Hear hears the controller seat's condition events and the vault's account-issued (Consumes) and hands each, by event - // name, with its body, to handle; nil hears none. A condition of the kind root-not-free raised or cleared wakes a pass: + // Hear hears the keeper's condition events and the vault's account-issued (Consumes) and hands each, by event name, + // with its body, to handle; nil hears none. A condition of the kind root-not-free raised or cleared wakes a pass: // a machine's root-free word changed; and so does an account the vault minted (ADR 0561 §1), whose hash the list carries. Hear func(handle func(event string, body []byte)) error @@ -52,21 +50,6 @@ type Follower struct { held string notAsserted string refusedMans string - // leaseOn is whether the lease was handed away at its last change heard, once leaseSeen. - leaseOn, leaseSeen bool - // follows is the seat whose condition events the part hears, as the take-over key names it: the controller's until - // the operator's take-over (an absent key, or one not readable, follows the controller). - follows string -} - -// Follows is the seat whose condition events the part hears now. -func (f *Follower) Follows() string { - f.mu.Lock() - defer f.mu.Unlock() - if f.follows == "" { - return "mesh-controller" - } - return f.follows } func (f *Follower) init() { @@ -106,35 +89,6 @@ func (f *Follower) logf(format string, args ...any) { // heard says whether a change composes again: every change but a have's, and of the haves a machine's first and the // bus module's loaded list moving. func (f *Follower) heard(state, key string, value []byte) bool { - if state == ReadTakeover { - // Which seat's condition events are heard: no pass, since nothing composed reads it. - if key != health.TakeoverKey { - return false - } - t, err := health.DecodeTakeover(value, len(value) > 0) - if err != nil { - f.logf("the take-over key cannot be read (%v): the controller's condition events are heard", err) - t = health.Takeover{Follow: health.FollowController} - } - f.mu.Lock() - was := f.follows - f.follows = t.Seat() - f.mu.Unlock() - if was != t.Seat() { - f.logf("root-free words are heard from %s's condition events (the take-over key follows %s)", t.Seat(), t.Follow) - } - return false - } - if state == ReadLease { - // Renewed every few seconds by its holder: only the write that hands it away, or back, composes again (ADR 0561 §6). - // Once handed away nobody renews it, so any change is heard: it may be the lease altered (ADR 0582 clause 4). - on, _ := buscompose.LeaseHandedAway(value, len(value) > 0, nil) - f.mu.Lock() - defer f.mu.Unlock() - was, seen := f.leaseOn, f.leaseSeen - f.leaseOn, f.leaseSeen = on, true - return !seen || was != on || on - } if state != ReadHave { return true } @@ -179,16 +133,13 @@ func (f *Follower) open() { f.Wake() return } - // A condition event of the seat the take-over key does not name is not heard (ADR 0564 §9). - if from := HeardFrom(event); from != "" && from != f.Follows() { - return - } - if IsRootNotFree(body) { + // A condition event is heard from the keeper alone (ADR 0590 §3). + if FromKeeper(event) && IsRootNotFree(body) { f.Wake() } }) if err != nil { - f.logf("the controller's condition events cannot be heard yet (%v): tried again at the next change", err) + f.logf("the keeper's condition events cannot be heard yet (%v): tried again at the next change", err) } else { f.heard_ = true } diff --git a/modules/mesh-grants/internal/grants/inputs.go b/modules/mesh-grants/internal/grants/inputs.go index aa3f313..035ffb2 100644 --- a/modules/mesh-grants/internal/grants/inputs.go +++ b/modules/mesh-grants/internal/grants/inputs.go @@ -45,7 +45,7 @@ const ( // Reads is every state the part reads, in the manifest's order. Not the controller's assignments: the seat's placement // alone places modules (novox/hq ADR 0549 §5, ADR 0571 §10; issue 774). var Reads = []string{ReadModules, ReadManifests, ReadResolved, ReadHave, ReadWants, ReadBuilds, - ReadAccounts, ReadPlacement, ReadLease, ReadTakeover, ReadEnrolments, ReadNodeLogins} + ReadAccounts, ReadPlacement, ReadEnrolments, ReadNodeLogins} // The bus module's tools the part calls, as its manifest's invokes name them: read-only, each. const ( diff --git a/modules/mesh-grants/internal/grants/rootfree.go b/modules/mesh-grants/internal/grants/rootfree.go index 9d2aa5e..ab714f8 100644 --- a/modules/mesh-grants/internal/grants/rootfree.go +++ b/modules/mesh-grants/internal/grants/rootfree.go @@ -18,30 +18,20 @@ import ( const CallRootFree = "seat:node-engine.root-free" // The condition events the part hears (never a timer): a machine's root-free word changes with the raising and the -// clearing of its `root-not-free` condition. They are the controller seat's until the operator's take-over, and the -// condition keeper's (mesh-conditions) from it: the part hears only the seat the take-over key names, never both (novox/hq -// ADR 0564 §9, issue 1952 G1). +// clearing of its `root-not-free` condition. They are the condition keeper's (mesh-conditions) alone, the one keeper +// since the controller ended by its unassignment (novox/hq ADR 0590 §3, which ends ADR 0564 §9's selection by the +// take-over key). const ( - EventConditionRaised = "mesh-controller.condition-raised" - EventConditionCleared = "mesh-controller.condition-cleared" - EventKeeperRaised = health.KeeperModule + ".condition-raised" - EventKeeperCleared = health.KeeperModule + ".condition-cleared" + EventKeeperRaised = health.KeeperModule + ".condition-raised" + EventKeeperCleared = health.KeeperModule + ".condition-cleared" // KindRootNotFree is the condition kind whose events wake a pass. KindRootNotFree = "root-not-free" ) -// ReadTakeover is the condition keeper's take-over key's state (`switch`): which seat's condition events the part hears. -const ReadTakeover = health.KeeperModule + "." + health.TakeoverState - -// HeardFrom is the seat a condition event heard comes from, by its name (`.`) or its bus subject -// (`mesh.seat..event.`); empty for any other. -func HeardFrom(event string) string { - for _, seat := range []string{health.KeeperModule, "mesh-controller"} { - if strings.HasPrefix(event, seat+".") || strings.HasPrefix(event, "mesh.seat."+seat+".event.") { - return seat - } - } - return "" +// FromKeeper says a condition event heard is the keeper's, by its name (`.`) or its bus subject +// (`mesh.seat..event.`). +func FromKeeper(event string) bool { + return strings.HasPrefix(event, health.KeeperModule+".") || strings.HasPrefix(event, "mesh.seat."+health.KeeperModule+".event.") } // EventAccountIssued is the vault's word that it minted an account (novox/hq ADR 0561 §1): its hash is in the vault's @@ -49,8 +39,6 @@ func HeardFrom(event string) string { const EventAccountIssued = "mesh-vault." + buscompose.AccountIssuedEvent // Consumes is every event the part hears, as its manifest's consumes name them. -// -// Not the controller's condition events: the keeper's alone (novox/hq ADR 0564 §9, issue 774). var Consumes = []string{EventKeeperRaised, EventKeeperCleared, EventAccountIssued} // IsAccountIssued says an event heard is the vault's account-issued, by its name or its bus subject. @@ -58,11 +46,6 @@ func IsAccountIssued(event string) bool { return event == EventAccountIssued || event == buscompose.AccountIssuedSubject } -// EventSubject is the bus subject of a controller seat event named `mesh-controller.`. -func EventSubject(event string) string { - return "mesh.seat.mesh-controller.event." + strings.TrimPrefix(event, "mesh-controller.") -} - // IsRootNotFree says a condition event's body names the `root-not-free` kind (its `kind`, or the last part of its // `key`, `..`). An unreadable body wakes a pass too: a pass is cheap, a missed change is not. func IsRootNotFree(body []byte) bool { diff --git a/modules/mesh-grants/module.json b/modules/mesh-grants/module.json index 65a49b9..11e2149 100644 --- a/modules/mesh-grants/module.json +++ b/modules/mesh-grants/module.json @@ -11,8 +11,6 @@ "mesh-delivery-assigner.placement", "node-engine.have", "mesh-vault.bus-accounts", - "mesh-controller.lease", - "mesh-conditions.takeover", "mesh-enrolment.enrolments", "mesh-vault.node-logins" ],