Run mesh-grants' handed behaviour without the lease (issue 1126, A1)
The controller ended by its unassignment and its lease key expired, so every gate on "the lease is handed away" stayed shut for good: no account consumer was created or matched, grant-missing was never raised, the enrolment users kept the controller's subject, memberships were still said on their subject and the sealed key never said LeaseHanded. The handed branch now always runs, and a hand-back is gone. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -118,8 +118,8 @@ func toolsOf(p *grants.Part) []stdio.Tool {
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
return p.TakeOver(stdio.CallerOf(a), grants.Answer(a, "why"))
|
||||
}},
|
||||
{Name: grants.ToolHandBack, Description: "Ask the operator to switch the bus node back to the controller's list (ADR 0547 clause 8), " +
|
||||
"by their yes on their channel; in shadow there is nothing to hand back, and it says so. Every act is recorded.",
|
||||
{Name: grants.ToolHandBack, Description: "Gone: the controller ended by its unassignment (issue 1126), so nothing writes its user list and the grants " +
|
||||
"part's list is the only one; it says so and asks nothing (ADR 0547 §11).",
|
||||
Input: schema(map[string]string{"why": "why, kept with the act"}, "why"),
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
return p.HandBack(stdio.CallerOf(a), grants.Answer(a, "why"))
|
||||
@@ -129,15 +129,14 @@ func toolsOf(p *grants.Part) []stdio.Tool {
|
||||
"wrote it to its state accounts, which the vault alone reads: user, kind, node, module, process, the bus's address and " +
|
||||
"fingerprint, a module's claims with the verbs each seat promises, and when the key was written; one user's or all; " +
|
||||
"what the last pass wrote, deleted or kept; and the durable consumers the accounts need as the last pass found them " +
|
||||
"(missing, differing and why, created; whether the lease is handed away). Never a password. Read-only: " +
|
||||
"(missing, differing and why, created). Never a password. Read-only: " +
|
||||
"grants_compose_now writes them again.",
|
||||
Input: schema(map[string]string{"user": "one bus user, e.g. ace.mesh-build-go"}),
|
||||
Run: func(a map[string]any) (any, error) { return p.Accounts(grants.Answer(a, "user")) }},
|
||||
{Name: grants.ToolConsumerMatch, Description: "Bring one durable consumer an account needs (a module's on EVENTS, a process's on " +
|
||||
"its work queue; novox/hq ADR 0561 §6) to the configuration the bus composer composes, by hand: the recovery when " +
|
||||
"the condition mesh-grants.consumer-differs names it. Asks the part's process, which alone may, and keeps where the " +
|
||||
"consumer delivers from. Refused while the controller holds its lease (the consumers are the controller's then) and " +
|
||||
"for a consumer the records do not compose. grants_user_list's last pass says what became of it.",
|
||||
"consumer delivers from. Refused for a consumer the records do not compose. grants_user_list's last pass says what became of it.",
|
||||
Input: schema(map[string]string{"stream": "the stream, e.g. EVENTS", "consumer": "the consumer, e.g. ace_mesh-build-go"}, "stream", "consumer"),
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
return p.MatchConsumer(grants.Answer(a, "stream"), grants.Answer(a, "consumer"))
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -540,7 +539,7 @@ func TestGrants(t *testing.T) {
|
||||
gap string
|
||||
}{
|
||||
{"every record on the bus", func(*fakeBus) {},
|
||||
[]string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "enrolment tokens"},
|
||||
[]string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "people"},
|
||||
{"a manifest missing is a gap and its user is left out", func(b *fakeBus) { delete(b.states[grants.ReadModules], "mod-y") },
|
||||
[]string{"a.mod-x", "a.nats", "controller", "node.a", "node.b"}, "manifest of mod-y"},
|
||||
{"a want's built manifest is composed, not the trunk's; the list's records carry both (issue 774)", func(b *fakeBus) {
|
||||
@@ -551,7 +550,7 @@ func TestGrants(t *testing.T) {
|
||||
b.put(grants.ReadWants, "want.a.mod-x", delivery.Want{V: delivery.Version, FP: fp, Composed: "sha256:" + strings.Repeat("ef", 32)})
|
||||
key, _ := delivery.BuilderBuildKey("mod-x", fp)
|
||||
b.put(grants.ReadBuilds, key, map[string]any{"v": delivery.Version, "state": "done", "manifest": digest})
|
||||
}, []string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "enrolment tokens"},
|
||||
}, []string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "people"},
|
||||
{"placement refused is a gap; the machines come from the haves", func(b *fakeBus) { b.refused[grants.ReadPlacement] = true },
|
||||
[]string{"controller", "node.a", "node.b"}, grants.ReadPlacement},
|
||||
} {
|
||||
@@ -1316,7 +1315,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
// Written in shadow, the bus node told to follow the controller's list; every user left out while the vault holds
|
||||
// no hash; a tool's part (no object bucket) writes none.
|
||||
if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowController || g.Revision != 1 || g.Node != "a" ||
|
||||
if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowController || !g.LeaseHanded || g.Revision != 1 || g.Node != "a" ||
|
||||
g.Users != 0 || len(g.LeftOut) != 6 || len(objs.objects) != 1 || !strings.Contains(open(g), "accounts {") {
|
||||
t.Errorf("the sealed list in shadow = %+v, %d objects", g, len(objs.objects))
|
||||
}
|
||||
@@ -1328,14 +1327,15 @@ func TestGrants(t *testing.T) {
|
||||
} else if _, _, had := tp.Sealed(); had {
|
||||
t.Error("a tool's part wrote the sealed list")
|
||||
}
|
||||
if len(first.Published) != 3 || len(b.publishes) != 3 {
|
||||
t.Errorf("the first composition published %v, want the three assignments' memberships", first.Published)
|
||||
// Written to the state alone, never said on their subject, with no lease key (1126/A1).
|
||||
if len(first.Published) != 3 || first.MembershipsWritten != 3 || len(b.publishes) != 0 {
|
||||
t.Errorf("the first composition published %v (%d on the subject), want the three assignments' memberships in the state alone", first.Published, len(b.publishes))
|
||||
}
|
||||
// The second pass says each membership unchanged (published at the first); the third writes nothing at all.
|
||||
if again := p.Compose(); again.Written || again.Revision != 1 || len(again.Published) != 0 || again.AssertionsWritten != 0 {
|
||||
t.Errorf("a composition with no input changed wrote or published: %+v", again)
|
||||
}
|
||||
if third := p.Compose(); third.Written || third.MembershipsWritten != 0 || len(third.Published) != 0 || len(b.publishes) != 3 {
|
||||
if third := p.Compose(); third.Written || third.MembershipsWritten != 0 || len(third.Published) != 0 || len(b.publishes) != 0 {
|
||||
t.Errorf("a membership equal to the one last published was published again, or a state written: %+v", third)
|
||||
}
|
||||
// mod-x placed on b too: b.mod-x is added and its first membership published; a.mod-x's loses the module's plain
|
||||
@@ -1402,8 +1402,6 @@ func TestGrants(t *testing.T) {
|
||||
var out any
|
||||
if act == "take-over" {
|
||||
out, err = p.TakeOver("shanks.node-tools", "a row")
|
||||
} else {
|
||||
out, err = p.HandBack("shanks.node-tools", "a row")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", act, err)
|
||||
@@ -1452,7 +1450,6 @@ func TestGrants(t *testing.T) {
|
||||
}, []string{"a.mod-x: 1 read"}},
|
||||
{"a yes with only added rights and every hash held records the switch, the ask naming them", "take-over", yes, settled(lacks), nil, nil, grants.TookOver, "recorded",
|
||||
nil, []string{"adds 1 right to 1 user and takes none away", "a.mod-x: 1 read"}},
|
||||
{"a yes to hand back records the switch back", "hand-back", yes, func() {}, nil, nil, grants.Shadow, "recorded", nil, nil},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
row.setup()
|
||||
@@ -1522,8 +1519,8 @@ func TestGrants(t *testing.T) {
|
||||
// The key the bus node follows turns with the switch at once, and the state says switched only once the bus
|
||||
// node loaded the grants part's written list.
|
||||
g, _, _ := p.Sealed()
|
||||
if want := map[string]string{grants.TookOver: delivery.FollowGrants, grants.Shadow: delivery.FollowController}[p.SwitchState()]; g.Follow != want {
|
||||
t.Errorf("the key says follow %s with the switch %s", g.Follow, p.SwitchState())
|
||||
if want := map[string]string{grants.TookOver: delivery.FollowGrants, grants.Shadow: delivery.FollowController}[p.SwitchState()]; g.Follow != want || !g.LeaseHanded {
|
||||
t.Errorf("the key says follow %s, lease handed %v, with the switch %s and no lease key (1126/A1/AC2)", g.Follow, g.LeaseHanded, p.SwitchState())
|
||||
}
|
||||
effect := func() string { st, _ := p.TakeOverState(); return fmt.Sprint(st.(map[string]any)["effect"]) }
|
||||
if row.outcome == "recorded" && row.act == "take-over" {
|
||||
@@ -1663,10 +1660,11 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("hand-back in shadow is refused at once, with nothing asked", func(t *testing.T) {
|
||||
// 1126/A1: the controller ended by its unassignment, so nothing writes its list and a hand-back is gone.
|
||||
t.Run("hand-back is gone at once, with nothing asked", func(t *testing.T) {
|
||||
n := len(fa.asks)
|
||||
if _, err := p.HandBack("shanks.node-tools", "a row"); err == nil || !strings.Contains(err.Error(), "nothing to hand back") {
|
||||
t.Errorf("hand-back in shadow = %v", err)
|
||||
if _, err := p.HandBack("shanks.node-tools", "a row"); err == nil || !strings.HasPrefix(err.Error(), "gone") {
|
||||
t.Errorf("hand-back = %v", err)
|
||||
}
|
||||
p.Compose()
|
||||
if len(fa.asks) != n {
|
||||
@@ -1674,6 +1672,8 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
t.Run("an ask that can stay open is closed every way and can be asked again", func(t *testing.T) {
|
||||
// The rows above end taken over and a hand-back is gone (1126/A1): asked again from a part in shadow.
|
||||
delete(b.own[grants.StateTakeover], grants.KeySwitch)
|
||||
// A warrant the process missed is caught up from the router's record, whatever its outcome.
|
||||
id := asked("take-over")
|
||||
p.Compose()
|
||||
@@ -1843,11 +1843,10 @@ func TestGrants(t *testing.T) {
|
||||
if row.names == "" && row.last != nil && !strings.Contains(string(state), "10.0.0.1") {
|
||||
t.Errorf("the private network of the last membership is not carried: %s", state)
|
||||
}
|
||||
// Written to the state alone, never said on its subject (1126/A1).
|
||||
got, err := membership.DecodeGrantsMembership(state)
|
||||
var sent membership.Membership
|
||||
_ = json.Unmarshal(b.stream["mesh.assignment."+key], &sent)
|
||||
if err != nil || !reflect.DeepEqual(got, sent) || bytes.Contains(state, []byte(`"membership"`)) {
|
||||
t.Errorf("the state is not the bare membership published (%v): %s", err, state)
|
||||
if err != nil || got.Node != "a" || got.Module != "mod-x" || bytes.Contains(state, []byte(`"membership"`)) || len(b.stream) != 0 {
|
||||
t.Errorf("the state is not the bare membership published, or its subject said (%v, %d said): %s", err, len(b.stream), state)
|
||||
}
|
||||
if !strings.Contains(pass, row.names) {
|
||||
t.Errorf("the pass does not name %q: %s", row.names, pass)
|
||||
@@ -1866,23 +1865,6 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
// The assignments stream removed (nats_stream_remove at the lease write) and its subject refused: every membership
|
||||
// is still written to the state, where each runtime reads it, and the refusal is said (issue 774).
|
||||
t.Run("the stream gone and its subject refused: memberships still written, the refusal said", func(t *testing.T) {
|
||||
b := fixture()
|
||||
b.loaded = shadowOf(t, b)
|
||||
b.refusePub = true
|
||||
p := partOn(b)
|
||||
refused := p.Compose()
|
||||
if !slices.Equal(refused.Published, []string{"a.mod-x", "a.nats", "b.mod-y"}) || refused.MembershipsWritten != 3 ||
|
||||
!strings.Contains(strings.Join(refused.Errors, "\n"), "Permissions Violation") {
|
||||
t.Errorf("with the stream gone = %+v: every membership written, the refusal said", refused)
|
||||
}
|
||||
b.refusePub = false
|
||||
if next := p.Compose(); len(next.Published) != 0 || next.MembershipsWritten != 0 {
|
||||
t.Errorf("the next event published %v again", next.Published)
|
||||
}
|
||||
})
|
||||
t.Run("a tool answers while a pass waits on the bus", func(t *testing.T) {
|
||||
b := fixture()
|
||||
b.loaded = shadowOf(t, b)
|
||||
@@ -2251,7 +2233,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"})
|
||||
// With no lease key (1126/A1).
|
||||
// The private network the mesh provides is placed with no fingerprint by construction: never a missing grant.
|
||||
b.assign("b", "mesh-wireguard")
|
||||
fp := "src2:d4afe7a1abab5ccdbef6d8629b2245006a2aec60290f430f7b4756e2930ed60c"
|
||||
@@ -2422,7 +2404,6 @@ func TestGrants(t *testing.T) {
|
||||
b := fixture()
|
||||
b.manifestOf("mesh-enrolment", enrolmentManifest)
|
||||
b.assign("a", "mesh-enrolment")
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"})
|
||||
issued := time.Date(2026, 11, 2, 1, 0, 0, 0, time.UTC)
|
||||
for node, stage := range map[string]string{"lab": delivery.EnrolmentLive, "pi": delivery.EnrolmentAsked} {
|
||||
raw, err := delivery.EncodeEnrolment(delivery.Enrolment{Node: node, Address: "10.0.0.7", Why: "joins",
|
||||
@@ -2545,27 +2526,21 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// 774/AC6 (novox/hq ADR 0561 §6): the durable consumers an account needs, with the controller's configuration, are
|
||||
// compared in shadow while the controller holds the lease, and created, only where the bus lacks them, from the write
|
||||
// that hands the lease away; one the bus holds is never changed.
|
||||
t.Run("account consumers: compared while the controller holds the lease, created once it is handed away", func(t *testing.T) {
|
||||
// 774/AC6 (novox/hq ADR 0561 §6), 1126/A1/AC1: the durable consumers an account needs, with the composed
|
||||
// configuration, are created where the bus lacks them with no lease key at all (the controller ended by its
|
||||
// unassignment, never by the lease write); one the bus holds is never changed.
|
||||
t.Run("account consumers: created where the bus lacks them, with no lease key", func(t *testing.T) {
|
||||
const cManifest = `{"module":"mod-c","version":"1","own-secrets":{"broker":"/var/lib/c/broker"},"consumes":["mod-y.done"]}`
|
||||
b := fixture()
|
||||
b.manifestOf("mod-c", cManifest)
|
||||
b.assign("b", "mod-c")
|
||||
b.loaded = shadowOf(t, b)
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"instance": "novox/1/2026-10-27", "epoch": 57})
|
||||
p := partOn(b)
|
||||
p.Consumers = b
|
||||
res := p.Compose()
|
||||
if res.Consumers == nil || res.Consumers.On || !slices.Contains(res.Consumers.Missing, "EVENTS.b_mod-c") || len(b.consumerCreates) != 0 {
|
||||
t.Fatalf("while the controller holds the lease: %+v, created %v", res.Consumers, b.consumerCreates)
|
||||
}
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"})
|
||||
b.consumers = map[string][]map[string]any{"EVENTS": {{"name": "b_other", "filters": []string{"x"}, "max_deliver": 5}}}
|
||||
res = p.Compose()
|
||||
if !res.Consumers.On || !slices.Equal(res.Consumers.Created, []string{"EVENTS.b_mod-c"}) {
|
||||
t.Fatalf("once handed away: %+v", res.Consumers)
|
||||
res := p.Compose()
|
||||
if res.Consumers == nil || !slices.Equal(res.Consumers.Created, []string{"EVENTS.b_mod-c"}) {
|
||||
t.Fatalf("with no lease key: %+v", res.Consumers)
|
||||
}
|
||||
var made map[string]any
|
||||
for _, c := range b.consumers["EVENTS"] {
|
||||
@@ -2581,7 +2556,7 @@ func TestGrants(t *testing.T) {
|
||||
t.Errorf("a consumer the bus holds was asked again: %+v, %v", res.Consumers, b.consumerCreates)
|
||||
}
|
||||
// A held consumer that differs is never changed by a pass: it is raised as a condition naming it, and brought to
|
||||
// the composed configuration only by hand (grants_consumer_match), never while the controller holds the lease.
|
||||
// the composed configuration only by hand (grants_consumer_match), with no lease key too.
|
||||
var said []asks.Condition
|
||||
p.Say = func(c asks.Condition) error {
|
||||
if c.Key != grants.GrantMissingCondition && c.Key != grants.RefusedCondition { // their own rows
|
||||
@@ -2596,11 +2571,6 @@ func TestGrants(t *testing.T) {
|
||||
!strings.Contains(said[0].Explanation, "EVENTS.b_mod-c") {
|
||||
t.Fatalf("a differing consumer: %+v, said %+v", res.Consumers, said)
|
||||
}
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"instance": "novox/2/2026-10-27", "epoch": 58})
|
||||
if _, err := p.MatchConsumer("EVENTS", "b_mod-c"); err == nil || !strings.Contains(err.Error(), "controller holds the lease") {
|
||||
t.Errorf("matched by hand under the controller's lease: %v", err)
|
||||
}
|
||||
b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"})
|
||||
if _, err := p.MatchConsumer("EVENTS", "b_nothing"); err == nil {
|
||||
t.Error("a consumer the records do not compose was asked to match")
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ type ConsumerCreator interface {
|
||||
|
||||
// ConsumersState is what one pass found and did of the account consumers.
|
||||
type ConsumersState struct {
|
||||
// On says the lease is handed away, so the part creates what the bus lacks; Why says why it is on or off.
|
||||
// On says the part creates what the bus lacks: always, since the controller ended by its unassignment (issue 1126);
|
||||
// Why says so.
|
||||
On bool `json:"on"`
|
||||
Why string `json:"why"`
|
||||
// Wanted is how many the records compose; Missing those the bus lacks (`<stream>.<name>`), Differ those it holds
|
||||
@@ -88,14 +89,13 @@ func heldConsumers(c Caller, stream string) (map[string]heldConsumer, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// leaseHandedAway reads the lease as the part's reads give it.
|
||||
func leaseHandedAway(r Reader) (bool, string) {
|
||||
raw, found, err := r.Get(ReadLease, LeaseKey)
|
||||
return buscompose.LeaseHandedAway(raw, found, err)
|
||||
}
|
||||
// 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).
|
||||
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
|
||||
// once the lease is handed away and the part holds a creator.
|
||||
// where the part holds a creator.
|
||||
func (p *Part) consumers(res *Result, recs buscompose.Records, match string) {
|
||||
users, err := buscompose.Users(recs)
|
||||
if err != nil {
|
||||
@@ -103,7 +103,7 @@ func (p *Part) consumers(res *Result, recs buscompose.Records, match string) {
|
||||
return
|
||||
}
|
||||
st := &ConsumersState{}
|
||||
st.On, st.Why = leaseHandedAway(p.Read)
|
||||
st.On, st.Why = true, ConsumersOn
|
||||
res.Consumers = st
|
||||
wanted := buscompose.AccountConsumers(users)
|
||||
st.Wanted = len(wanted)
|
||||
@@ -154,9 +154,8 @@ func (p *Part) consumers(res *Result, recs buscompose.Records, match string) {
|
||||
}
|
||||
|
||||
// sayDiffer raises the warning condition mesh-grants.consumer-differs while an account consumer the part owns (from the
|
||||
// lease write) differs from its composed configuration, naming each with the fields that differ, and clears it when they
|
||||
// match. Said once per set of differences, again when it changes. Before the lease write the consumers are the
|
||||
// controller's, and a difference is only said in the pass.
|
||||
// controller's end) differs from its composed configuration, naming each with the fields that differ, and clears it when
|
||||
// they match. Said once per set of differences, again when it changes.
|
||||
func (p *Part) sayDiffer(st *ConsumersState) {
|
||||
if p.Say == nil {
|
||||
return
|
||||
@@ -190,15 +189,12 @@ func (p *Part) sayDiffer(st *ConsumersState) {
|
||||
const ConsumerDiffersCondition = "mesh-grants.consumer-differs"
|
||||
|
||||
// MatchConsumer is the tool consumer-match (ADR 0561 §6, §9): it asks the part's process to bring one account consumer
|
||||
// the records compose to its composed configuration, keeping where it delivers from. Refused while the controller holds
|
||||
// the lease (the consumers are the controller's then), and for a consumer the records do not compose.
|
||||
// the records compose to its composed configuration, keeping where it delivers from. Refused for a
|
||||
// consumer the records do not compose.
|
||||
func (p *Part) MatchConsumer(stream, name string) (any, error) {
|
||||
if stream == "" || name == "" {
|
||||
return nil, errors.New("consumer-match names a stream and a consumer")
|
||||
}
|
||||
if on, why := leaseHandedAway(p.Read); !on {
|
||||
return nil, fmt.Errorf("not asked: %s; the controller makes and matches the consumers while it holds the lease", why)
|
||||
}
|
||||
in := ReadInputs(p.Read, &p.cache)
|
||||
recs, _ := Records(in)
|
||||
users, err := buscompose.Users(recs)
|
||||
|
||||
@@ -314,15 +314,15 @@ func orSaid(s, none string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// sayMissing raises mesh-grants.grant-missing while a placed module has no grant, once the controller's lease is handed
|
||||
// away (the part's grants are then the ones the assigner reads), and clears it when every one has one; in shadow the
|
||||
// pass and the tool say them. Said once per set, again when it changes, and always at the first pass after a start.
|
||||
// sayMissing raises mesh-grants.grant-missing while a placed module has no grant (the part's grants are the ones the
|
||||
// assigner reads since the controller's end, issue 1126), and clears it when every one has one. Said once per set, again
|
||||
// when it changes, and always at the first pass after a start.
|
||||
func (p *Part) sayMissing(missing []string) {
|
||||
if p.Say == nil {
|
||||
return
|
||||
}
|
||||
why := ""
|
||||
if on, _ := leaseHandedAway(p.Read); on && len(missing) > 0 {
|
||||
if len(missing) > 0 {
|
||||
why = strings.Join(missing, "; ")
|
||||
}
|
||||
p.holdMu.Lock()
|
||||
|
||||
@@ -81,8 +81,6 @@ type Gap struct {
|
||||
|
||||
// Standing gaps: what the controller composes from its own store, which no bus value carries in this milestone.
|
||||
var standingGaps = []Gap{
|
||||
{Input: "enrolment tokens", Why: "while the controller holds the lease, its live enrolment tokens are in its store alone: " +
|
||||
"an enrolment user is composed only from the seat mesh-enrolment's keys"},
|
||||
{Input: "people", Why: "the people and the tools they may invoke are in the controller's store alone: no person user is composed"},
|
||||
{Input: "seat holdings", Why: "who holds each seat held once for the mesh is in the controller's store alone: a mesh seat claimed on two machines is composed as held on both"},
|
||||
{Input: "announced states", Why: "the controller does not announce its states-announced readable, so every read is composed from `reads`, as when announcements cannot be read"},
|
||||
@@ -131,9 +129,6 @@ type Inputs struct {
|
||||
// NodeLogins is each node's key of the vault's node-logins (ReadNodeLogins, ADR 0583 §1); a node absent logs in as
|
||||
// node.<n> alone.
|
||||
NodeLogins map[string]delivery.NodeLogin
|
||||
// LeaseAway says the controller's lease is handed away, as ReadInputs read it: an enrolment user then enrols through
|
||||
// the seat alone (ADR 0587 §6).
|
||||
LeaseAway bool
|
||||
// Refused is every manifest the indexer holds that the part cannot read or mesh-sdk's reader refuses, assigned or
|
||||
// not, each with why: its module counts as placed nowhere, so it is said loudly, never passed over (issue 1945).
|
||||
Refused []Gap
|
||||
@@ -280,7 +275,6 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
|
||||
in.Placed[node] = p
|
||||
}
|
||||
}
|
||||
in.LeaseAway, _ = leaseHandedAway(r)
|
||||
for node, p := range in.Placed {
|
||||
nodes[node] = true
|
||||
for _, m := range p.Assigned() {
|
||||
@@ -448,12 +442,8 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
|
||||
in.Gaps = append(in.Gaps, Gap{Input: "wanted manifests", Why: "no build record names the manifest at the wanted fingerprint, so the trunk's is used: " + strings.Join(fromTrunk, ", ")})
|
||||
}
|
||||
in.Hashes = readHashes(r, &in.Gaps)
|
||||
// The controller's tokens stand as a gap while it holds the lease; from the write the seat's keys are every token.
|
||||
var enrolmentsRead bool
|
||||
in.Enrolling, enrolmentsRead = readEnrolling(r, &in.Gaps)
|
||||
if enrolmentsRead && in.LeaseAway {
|
||||
in.Gaps = slices.DeleteFunc(in.Gaps, func(g Gap) bool { return g.Input == "enrolment tokens" })
|
||||
}
|
||||
// The seat mesh-enrolment's keys are every token since the controller's end (issue 1126).
|
||||
in.Enrolling, _ = readEnrolling(r, &in.Gaps)
|
||||
in.NodeLogins = readNodeLogins(r, &in.Gaps)
|
||||
for _, raw := range each(r, ReadResolved, &in.Gaps) {
|
||||
var v resolved
|
||||
|
||||
@@ -337,10 +337,8 @@ func (p *Part) Compose() Result {
|
||||
members = append(members, m.Membership)
|
||||
}
|
||||
seated := holdings.ByMemberships(members...)
|
||||
// From the lease write the memberships are written to the state alone and no longer said on their subject, which
|
||||
// nothing reads any more (novox/hq ADR 0571 clause 10, issue 774).
|
||||
notice, _ := leaseHandedAway(p.Read)
|
||||
notice = !notice
|
||||
// The memberships are written to the state alone and never said on their subject, which nothing reads any more
|
||||
// (novox/hq ADR 0571 clause 10, issue 774; unconditional since the controller's end, issue 1126).
|
||||
for _, key := range sortedKeys(ms) {
|
||||
// The last membership is the state's own (ADR 0571 clause 7), never the assignments stream's, which the bus
|
||||
// module's tools remove at the lease write (research 072 row 33): a last read there would freeze every membership
|
||||
@@ -360,11 +358,6 @@ func (p *Part) Compose() Result {
|
||||
m.Published, now = "not published (tried again at the next event): its own "+StateMemberships+" cannot be written", false
|
||||
} else {
|
||||
res.MembershipsWritten++
|
||||
if notice {
|
||||
if err := p.Publish.PublishMembership(m.Node, m.Module, body); err != nil {
|
||||
res.Errors = append(res.Errors, fmt.Sprintf("the membership of %s is written, its notice on its subject not said: %v", key, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// One unchanged is written bare once where the state still holds the wrapper written before 2026-10-30.
|
||||
|
||||
@@ -39,7 +39,7 @@ func records(in Inputs, union bool) (buscompose.Records, []Gap) {
|
||||
// The enrolment users, from the seat mesh-enrolment's keys, and from the lease write through its redeem alone (ADR 0587
|
||||
// §4, §6).
|
||||
out := buscompose.Records{Assigned: map[string][]buscompose.Declared{}, People: map[string][]string{},
|
||||
Interchangeable: map[string]bool{}, Enrolling: slices.Clone(in.Enrolling), LeaseHandedAway: in.LeaseAway,
|
||||
Interchangeable: map[string]bool{}, Enrolling: slices.Clone(in.Enrolling), LeaseHandedAway: true,
|
||||
// And each node's logins (ADR 0583 §1): both while it rotates.
|
||||
NodeLogins: in.NodeLogins}
|
||||
values := func(provider, setting string) []any {
|
||||
|
||||
@@ -95,8 +95,9 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped
|
||||
cur, _, had := p.Sealed()
|
||||
next := cur
|
||||
next.Follow = followOf(p.SwitchState())
|
||||
// The bus node follows this list alone once the lease is handed (ADR 0571 clause 8, ADR 0582 clause 3).
|
||||
next.LeaseHanded = p.leaseHanded()
|
||||
// The bus node follows this list alone (ADR 0571 clause 8): always since the controller ended by its unassignment,
|
||||
// with no lease written (issue 1126).
|
||||
next.LeaseHanded = true
|
||||
key := ""
|
||||
if in.Loaded != nil {
|
||||
key = in.Loaded.Key
|
||||
|
||||
@@ -132,14 +132,11 @@ func (p *Part) SwitchState() string {
|
||||
// the refusals of Decide. Asking again while an ask is open is refused.
|
||||
func (p *Part) TakeOver(caller, why string) (any, error) { return p.request(ActTakeOver, caller, why) }
|
||||
|
||||
// HandBack is the tool: it asks the operator to switch the bus node back to the controller's list. Once the lease is
|
||||
// handed to the grants part it is answered as gone: the grants part's list is the only one (ADR 0547 §11).
|
||||
// HandBack is the tool: it is answered as gone. The controller ended by its unassignment (issue 1126), so nothing
|
||||
// writes its list and the grants part's list is the only one (ADR 0547 §11).
|
||||
func (p *Part) HandBack(caller, why string) (any, error) {
|
||||
if p.leaseHanded() {
|
||||
return nil, errors.New("gone: the controller's lease is handed to mesh-grants, so the grants part's list is the only " +
|
||||
"one and the user list is not handed back (ADR 0547 §11); lease-hand-back gives the lease back first")
|
||||
}
|
||||
return p.request(ActHandBack, caller, why)
|
||||
return nil, errors.New("gone: the controller ended by its unassignment (issue 1126), so nothing writes its user list; " +
|
||||
"the grants part's list is the only one and the user list is not handed back (ADR 0547 §11)")
|
||||
}
|
||||
|
||||
// nonce is four hex digits that keep two asks of one second apart.
|
||||
@@ -546,13 +543,7 @@ func (p *Part) recordWarrant(ask string) {
|
||||
if p.Call == nil {
|
||||
return
|
||||
}
|
||||
rec := handacts.Recorder{Ask: p.Call.Call, LeaseAway: func() bool {
|
||||
if p.Read == nil {
|
||||
return false
|
||||
}
|
||||
away, _ := leaseHandedAway(p.Read)
|
||||
return away
|
||||
}}
|
||||
rec := handacts.Recorder{Ask: p.Call.Call}
|
||||
taken, err := rec.RecordWarrant(Module, ask)
|
||||
if p.Logf == nil {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user