Build against mesh-sdk without the controller (issue 1126)

mesh-sdk's group B member renames the grant to Grant, drops the
controller principal, the signer and the lease fields from the user
list key, and the keeper's and config's take-over verbs. The grants
part, the assigner and the view use the new names; the judge's copies of
the health and config seats follow; the tests' fixtures lose the
controller's user, and the refused principal is one removing what the
closed set does not name, the controller's mirror being gone.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 11:46:12 +01:00
parent 2049b1a370
commit 8d4c13427a
16 changed files with 93 additions and 124 deletions
@@ -167,7 +167,7 @@ func (p *passer) wants(ctx context.Context) {
return p.w.composed(node, name, fp) // a placed node's own: the digest its own have names (ADR 0571 §6)
},
GrantedAt: func(fp string) bool {
key, err := delivery.ControllerGrantKey(name, fp)
key, err := delivery.PartGrantKey(name, fp)
return err == nil && p.w.has(grants, key)
},
Restored: func(node string) bool {
@@ -180,13 +180,13 @@ func (p *passer) wants(ctx context.Context) {
}}
if fp != "" {
held, _ := delivery.HeldKey(name, fp)
grant, _ := delivery.ControllerGrantKey(name, fp)
grant, _ := delivery.PartGrantKey(name, fp)
hold, _ := delivery.HoldAskKey(name, fp)
m.Released = p.answeredWith(hold, "release")
m.Kept = p.answeredWith(hold, "keep")
m.Built, m.Held, m.Granted = p.w.built(name, fp), p.w.has(delivery.AssignerWantsBucket, held) && !m.Released, p.w.has(grants, grant)
if raw := p.w.value(grants, grant); raw != nil {
if g, err := delivery.DecodeControllerGrant(raw); err == nil {
if g, err := delivery.DecodeGrant(raw); err == nil {
m.GrantedOn = func(node string) bool { return assign.GrantNamesNode(g, node, name) }
}
}
@@ -293,8 +293,8 @@ func putOfTheBus(t *testing.T) {
default:
}
composes(t, p.w, "novox", digest)
gkey, _ := delivery.ControllerGrantKey("nats", fp)
g, _ := delivery.EncodeControllerGrant(delivery.ControllerGrant{})
gkey, _ := delivery.PartGrantKey("nats", fp)
g, _ := delivery.EncodeGrant(delivery.Grant{})
world(delivery.PartGrantsBucket, gkey, g)
got := step()
akey, _ := delivery.RecordAskKey("novox", "nats", digest)
@@ -226,9 +226,9 @@ func (p *passer) composedFor(node, module, fp string) (*delivery.ComposedDigest,
out = &d
}
granted := false
if gkey, err := delivery.ControllerGrantKey(module, fp); err == nil {
if gkey, err := delivery.PartGrantKey(module, fp); err == nil {
if raw := p.w.value(p.w.grantsBucket(), gkey); raw != nil {
if g, err := delivery.DecodeControllerGrant(raw); err == nil {
if g, err := delivery.DecodeGrant(raw); err == nil {
granted = g.NamesNode(node, module)
}
}
@@ -127,25 +127,22 @@ func theWorldCountsTheValuesAKeyTakes(t *testing.T) {
if got := w.changed(delivery.HaveBucket, "gone"); got != 0 {
t.Errorf("changed = %d for a key never seen", got)
}
// ADR 0571 clause 9: grants are read from the grants part's bucket, and the controller's own grants count for nothing.
// ADR 0571 clause 9: grants are read from the grants part's bucket, the one bucket of grants (ADR 0590).
fp := delivery.SourcePrefix2 + strings.Repeat("d", 64)
gkey, _ := delivery.ControllerGrantKey("web", fp)
grant, _ := delivery.EncodeControllerGrant(delivery.ControllerGrant{Accounts: []string{"ace.web"}})
gkey, _ := delivery.PartGrantKey("web", fp)
grant, _ := delivery.EncodeGrant(delivery.Grant{Accounts: []string{"ace.web"}})
for _, row := range []struct {
name string
inPart, inController bool
granted bool
name string
inPart bool
granted bool
}{
{"the part's grant", true, false, true},
{"only the controller's grant", false, true, false},
{"the part's grant", true, true},
{"no grant", false, false},
} {
p := newPasser(buckets{}, time.Now())
if row.inPart {
p.w.put(delivery.PartGrantsBucket, gkey, grant)
}
if row.inController {
p.w.put(delivery.ControllerGrantsBucket, gkey, grant)
}
if got := p.w.grantsBucket(); got != delivery.PartGrantsBucket {
t.Errorf("%s: grants are read from %s, want the part's", row.name, got)
}
@@ -733,8 +733,8 @@ func (m Module) userListRevision(node string, before delivery.Want, had bool, n
return w, true
}
// GrantNamesNode says whether a grant of module names its accounts on node (mesh-sdk's ControllerGrant.NamesNode, which a
// GrantNamesNode says whether a grant of module names its accounts on node (mesh-sdk's Grant.NamesNode, which a
// put reads by too).
func GrantNamesNode(g delivery.ControllerGrant, node, module string) bool {
func GrantNamesNode(g delivery.Grant, node, module string) bool {
return g.NamesNode(node, module)
}
@@ -293,7 +293,7 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
// Rule 6 per node: a grant written for the module's other nodes does not grant a node whose account it does not name
// (issue 1945, live on 2026-10-25 at 20:04: ace's mesh-build-go was wanted with no account on the bus).
m, n := ready()
grant := delivery.ControllerGrant{Accounts: []string{"ace.mail", "ace.mail.send"}}
grant := delivery.Grant{Accounts: []string{"ace.mail", "ace.mail.send"}}
m.GrantedOn = func(node string) bool { return GrantNamesNode(grant, node, "mail") }
out := Wants(m, n, wantedOld(), nil)
if w := out.Wants["ace"]; w.FP != fNew {
@@ -312,7 +312,7 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
{[]string{"g14.mailer"}, "g14", "mail", false},
{[]string{"ace.mail"}, "g14", "mail", false},
} {
if got := GrantNamesNode(delivery.ControllerGrant{Accounts: c.accounts}, c.node, c.module); got != c.named {
if got := GrantNamesNode(delivery.Grant{Accounts: c.accounts}, c.node, c.module); got != c.named {
t.Errorf("%v names %s of %s: %v, want %v", c.accounts, c.module, c.node, got, c.named)
}
}
+4 -30
View File
@@ -7370,21 +7370,8 @@ var defaultSeats = append([]catalogue.Seat{
Serves: []catalogue.Verb{
{Name: "config", Optional: true, Description: "Set, clear, list or read the history of config values: " +
"mesh config or node config for a predefined key, or a module's own value on one node, each set " +
"with why, validated, and naming each module on each node whose value moves. A trusted value only " +
"from the controller's terminal.",
"with why, validated, and naming each module on each node whose value moves.",
Input: configInput()},
// The switch of config to its own sources (novox/hq ADR 0565 §4): optional until every mesh-config serves it.
{Name: "compare", Optional: true, Description: "Every difference between config's own sources and the " +
"controller's records, each with its reason: a module's declared config against the controller's mirror, " +
"the indexer's predefined list against the mirror's, and each open pull request's config candidate against " +
"what the controller told. The take-over waits for none.",
Input: schema(map[string]string{}, nil)},
{Name: "take-over", Optional: true, Description: "Ask the operator's Approve to switch config to its own " +
"sources (the indexer); refused while compare shows a difference.",
Input: schema(map[string]string{"why": "why, in a sentence"}, []string{"why"})},
{Name: "hand-back", Optional: true, Description: "Switch config back to the controller's mirrors, until the " +
"controller hands its lease away; refused after it.",
Input: schema(map[string]string{"why": "why, in a sentence"}, []string{"why"})},
}},
// The provisioner's server (novox/hq ADR 0349 clause 3, design 57 P4): its holder keeps the registry of which
// domain answers which kind of placeholder, and the buckets mesh-provisioner_needs, _applied and _collect,
@@ -7967,7 +7954,7 @@ func conditionsVerbs() []catalogue.Verb {
{Name: "conditions", Description: "What is wrong with the mesh now: every open condition, urgent first, then oldest, " +
"raised from the observers' findings (the watchman, the self-check, the healers, the assigner) and cleared when " +
"the finding goes, never by hand. Given a key, that one whole with its evidence; with history, every transition " +
"lately, the controller's before the take-over included.",
"lately.",
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, mesh, delivery or module",
@@ -7984,17 +7971,6 @@ func conditionsVerbs() []catalogue.Verb {
"why": "why, required and recorded",
"cause": "the cause in a word (the condition's kind when absent)",
}, []string{"key", "for", "why"})},
{Name: "compare", Description: "The keeper's open conditions against the controller's: every key open in one and not " +
"the other, or open in both with another severity, each with its reason; the kinds that end with the controller " +
"and the keys held while their module is delivered listed apart and not counted. The take-over waits for it empty.",
Input: schema(map[string]string{}, nil)},
{Name: "take-over", Description: "Ask the operator's Approve to switch who the router, the grants part and " +
"mesh-watcher hear from the controller to the keeper; refused while compare shows a difference. On the warrant " +
"the keeper writes its take-over key.",
Input: schema(map[string]string{"why": "why now, shown to the operator"}, []string{"why"})},
{Name: "hand-back", Description: "Switch the readers back to the controller's conditions, until the controller's " +
"lease is handed away; refused after it.",
Input: schema(map[string]string{"why": "why, kept with the switch"}, []string{"why"})},
{Name: "resync", Description: "Read every observer's findings again and raise and clear what differs from the open " +
"conditions: the recovery of a keeper that missed a change.",
Input: schema(map[string]string{}, nil)},
@@ -8016,8 +7992,7 @@ func watchdogsVerbs() []catalogue.Verb {
Input: schema(map[string]string{"machine": "one machine"}, nil)},
{Name: "dead-letters", Description: "Every message a consumer on the bus gave up on, 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 deliver or drop, a hand act with why, taken only once the controller's lease is handed away; " +
"before, refused naming the controller's verb.",
"whole. With deliver or drop, a hand act with why.",
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 <stream>.<consumer>: only what that one gave up on",
@@ -8052,8 +8027,7 @@ func selfCheckVerbs() []catalogue.Verb {
func healersVerbs() []catalogue.Verb {
return []catalogue.Verb{
{Name: "healers", Description: "The healers: each registered response to one kind of condition, its repair, its " +
"budget and what happens when it is spent; every act taken lately with its outcome, the acts it would have " +
"taken while the controller's lease names a controller, and the mesh-wide brake.",
"budget and what happens when it is spent; every act taken lately with its outcome, and the mesh-wide brake.",
Input: schema(map[string]string{"days": "how many days of acts back (default 7)"}, nil)},
{Name: "heal", Description: "Take one healer's act for one open condition now, within its budget and the brake.",
Input: schema(map[string]string{
@@ -4994,12 +4994,12 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
// The four health seats (novox/hq ADR 0564 clause 1): at mesh scope, each held once, each naming ADR 0564, with the
// verbs and events the record names; the events keep the controller's names (clause 3).
for _, c := range []struct{ seat, verbs, emits string }{
{catalogue.ConditionsSeat, "conditions,silence,compare,take-over,hand-back,resync", "condition-raised,condition-changed,condition-cleared"},
{catalogue.ConditionsSeat, "conditions,silence,resync", "condition-raised,condition-changed,condition-cleared"},
{catalogue.WatchdogsSeat, "signals,stuck,presence,dead-letters,resync", ""},
{catalogue.SelfCheckSeat, "doctor,probes", "checked"},
{catalogue.HealersSeat, "healers,heal,drill", "healer-acted"},
// And mesh-config's switch (ADR 0565 §4), beside its config.
{"mesh-config", "config,compare,take-over,hand-back", ""},
// And mesh-config's config; its switch went with the controller (ADR 0590 clause 3).
{"mesh-config", "config", ""},
// And ADR 0565 clause 1's two.
{catalogue.WantsSeat, "upgrade,bus", "policy-changed"},
{catalogue.HandActsSeat, "hand-act,hand-acts,warranted", "act-recorded"},
@@ -219,8 +219,7 @@ func Tools(r view.Reader, u view.UsageReader) []stdio.Tool {
{Name: view.TrustsReadTool,
Description: "What every node trusts a mesh signature from, in one call (novox/hq ADR 0550 §2): each node's record's trusts " +
"(from node-engine_have <node>.node), whether it names the vault's signing key as mesh-vault publishes it in its " +
"state signers and whether it still names the controller's, the nodes that do not name the vault's yet (not_yet), " +
"and every: true once all do, the gate before the controller's lease is handed away. Read-only.",
"state signers, the nodes that do not name the vault's yet (not_yet), and every: true once all do. Read-only.",
Input: map[string]any{"type": "object", "properties": map[string]any{}},
Run: func(map[string]any) (any, error) {
return r.Trusts()
@@ -757,15 +757,14 @@ func (r Reader) Haves(node, module string) (KeptAnswer, error) {
}
// TrustsReadTool reads what every node trusts a mesh signature from, in one call (novox/hq ADR 0550 §2; design 47's L3
// s6b): the gate before the lease write is that every node's record names the vault's key.
// s6b): whether every node's record names the vault's key.
const TrustsReadTool = "node_trusts"
// NodeTrusts is what one node's record says it trusts: its lines (delivery.Trusts) and whether they name the vault's key
// the vault publishes and the controller's key.
// the vault publishes.
type NodeTrusts struct {
Trusts []string `json:"trusts"`
TrustsVault bool `json:"trusts_vault"`
TrustsController bool `json:"trusts_controller"`
Trusts []string `json:"trusts"`
TrustsVault bool `json:"trusts_vault"`
}
// TrustsAnswer is node_trusts' answer: the vault's key as it publishes it, each node's trusts, the nodes whose record does
@@ -824,9 +823,6 @@ func (r Reader) Trusts() (TrustsAnswer, error) {
if h.Record != nil {
t.Trusts = h.Record.Trusts
}
for _, line := range t.Trusts {
t.TrustsController = t.TrustsController || strings.HasPrefix(line, delivery.SignerController+" key ")
}
t.TrustsVault = out.VaultKey != "" && delivery.TrustsKey(t.Trusts, delivery.SignerVault, out.VaultKey)
out.Nodes[node] = t
if !t.TrustsVault {
@@ -182,7 +182,7 @@ func TestTheGrantsPartWhole(t *testing.T) {
return false
}
g, err := delivery.DecodeGrantsUserList(e.Value())
return err == nil && g.Follow == delivery.FollowGrants && g.LeaseHanded
return err == nil && g.Follow == delivery.FollowGrants
})
if n := said.Load(); n != 0 {
t.Errorf("%d memberships were said on their subject", n)
+46 -43
View File
@@ -390,7 +390,7 @@ func fixture() *fakeBus {
b.put(grants.ReadHave, "a.node", map[string]any{"v": 1})
b.put(grants.ReadHave, "b.node", map[string]any{"v": 1})
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 6}})
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5}})
return b
}
@@ -523,9 +523,9 @@ 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"}, "people"},
[]string{"a.mod-x", "a.nats", "b.mod-y", "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"},
[]string{"a.mod-x", "a.nats", "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) {
built := strings.Replace(xManifest, `{"name":"kept","history":3}`, `{"name":"newer"}`, 1)
digest := delivery.ManifestDigest([]byte(built))
@@ -534,9 +534,9 @@ 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"}, "people"},
}, []string{"a.mod-x", "a.nats", "b.mod-y", "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},
[]string{"node.a", "node.b"}, grants.ReadPlacement},
} {
t.Run(row.name, func(t *testing.T) {
b := fixture()
@@ -1099,7 +1099,7 @@ func TestGrants(t *testing.T) {
// 1952/S5 (ADR 0564 Limits 4): the bus module's tools bundle declares its own account and what it may remove.
n.manifestOf("nats", strings.Replace(strings.TrimSuffix(natsManifest, "}"), `"broker":"/var/lib/nats/broker"`, `"broker":"/var/lib/nats/broker","broker-nats-tools":"${dir:tools-secret}/broker-nats-tools"`, 1)+
`,"resources":[{"id":"tools-secret","type":"directory","path":"/var/lib/mesh/nats-tools","mode":"0711"}],"build":{"artifacts":[{"name":"tools","kind":"bundle","language":"go",`+
`"system":"arch","from":"cmd/nats-tools","binary":"nats-tools","loads":["nats-tools"],"bus":{"removes":["DEAD_LETTER_NOTICES.controller","mesh-host_acts"]}}]}}`)
`"system":"arch","from":"cmd/nats-tools","binary":"nats-tools","loads":["nats-tools"],"bus":{"removes":["controller-leftovers","mesh-host_acts"]}}]}}`)
n.manifestOf("claude-licence-manager", string(licence))
n.assign("novox", "nats")
n.assign("novox", "claude-licence-manager")
@@ -1118,7 +1118,7 @@ func TestGrants(t *testing.T) {
tools = d.Processes
}
}
if len(tools) != 1 || tools[0].Process != "nats-tools" || !slices.Equal(tools[0].Removes, []string{"DEAD_LETTER_NOTICES.controller", "mesh-host_acts"}) {
if len(tools) != 1 || tools[0].Process != "nats-tools" || !slices.Equal(tools[0].Removes, []string{"controller-leftovers", "mesh-host_acts"}) {
t.Errorf("the bus module's principals = %+v, want nats-tools carrying both removals", tools)
}
if m := grants.Memberships(recs)["novox.claude-licence-manager"]; len(m.Membership.Seats) == 0 {
@@ -1208,7 +1208,7 @@ func TestGrants(t *testing.T) {
w.Key = busKey.PublicKey
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &w})
}
loadedBefore := delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 6}
loadedBefore := delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5}
withKey(loadedBefore)
objs := &fakeObjects{}
p.Objects = objs
@@ -1229,13 +1229,13 @@ func TestGrants(t *testing.T) {
t.Errorf("user-list before any composition = %v", got)
}
first := p.Compose()
if !first.Written || first.Revision != 1 || !first.UserList.Compare.Equal() || first.UserList.Users != 6 {
if !first.Written || first.Revision != 1 || !first.UserList.Compare.Equal() || first.UserList.Users != 5 {
t.Fatalf("first composition = %+v", first)
}
// The bus node follows the part's list alone (issue 1126): a first list that would leave out every loaded user
// while the vault holds no hash is held, nothing sealed, the key saying follow grants; a tool's part (no object
// bucket) writes none.
if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowGrants || !g.LeaseHanded || g.Digest != "" ||
if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowGrants || g.Digest != "" ||
len(objs.objects) != 0 || first.Sealed == nil || !strings.Contains(first.Sealed.Why, "would be left out") {
t.Errorf("the first sealed list = %+v, %d objects, %+v", g, len(objs.objects), first.Sealed)
}
@@ -1322,8 +1322,8 @@ func TestGrants(t *testing.T) {
settled(lacks)()
p.Compose()
g, _, _ := p.Sealed()
if g.Follow != delivery.FollowGrants || !g.LeaseHanded {
t.Errorf("the key says follow %s, lease handed %v; want grants with no lease key (1126/A1/AC2)", g.Follow, g.LeaseHanded)
if g.Follow != delivery.FollowGrants {
t.Errorf("the key says follow %s, want grants (1126/A1/AC2)", g.Follow)
}
if users := strings.Count(open(g), "{ user: "); users != g.Users || g.Users == 0 || len(g.LeftOut) != 0 {
t.Errorf("the sealed list holds %d users, the key says %d, left out %v", users, g.Users, g.LeftOut)
@@ -1823,7 +1823,7 @@ func TestGrants(t *testing.T) {
if res.Grants == nil || !slices.Equal(held(), []string{key("mod-x"), key("nats")}) || len(res.Grants.Written) != 2 {
t.Fatalf("grants of the hashed modules: held %v, %+v", held(), res.Grants)
}
if g, err := delivery.DecodeControllerGrant(b.own[grants.StateGrants][key("mod-x")]); err != nil || !slices.Equal(g.Accounts, []string{"a.mod-x"}) {
if g, err := delivery.DecodeGrant(b.own[grants.StateGrants][key("mod-x")]); err != nil || !slices.Equal(g.Accounts, []string{"a.mod-x"}) {
t.Errorf("the grant of mod-x is %+v, %v", g, err)
}
said := func() int {
@@ -1871,7 +1871,7 @@ func TestGrants(t *testing.T) {
}
loads := func(source, digest string) {
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: source, Revision: 8, Wanted: digest, Digest: digest, Users: 6, Key: busKey.PublicKey,
UserList: &delivery.UserListWritten{Source: source, Revision: 8, Wanted: digest, Digest: digest, Users: 5, Key: busKey.PublicKey,
Follows: delivery.FollowGrants}})
}
loads("a.nats.src2", "sha256:aa")
@@ -1976,10 +1976,9 @@ func TestGrants(t *testing.T) {
}
})
// ADR 0571 §13's test for this repository (issue 774, 774/S24/AC1), replacing the guard of ADR 0549 §6 and its list
// ../assignments-readers: at phase write no module.json here depends on mesh-controller beyond the lease and the three
// kept to the write (mesh-sdk catalogue.ManifestRefusals, the judge grants_lease_check runs).
t.Run("no module of this repository depends on the controller beyond what the lease write keeps", func(t *testing.T) {
// ADR 0590 clause 5's test for this repository (issue 1126): no module.json here names mesh-controller, the lease
// read and the builder's consume of artifacts-gone included (mesh-sdk catalogue.ManifestRefusals).
t.Run("no module of this repository names the controller", func(t *testing.T) {
paths, _ := filepath.Glob("../*/module.json")
manifests := map[string]catalogue.Manifest{}
for _, path := range paths {
@@ -1996,7 +1995,7 @@ func TestGrants(t *testing.T) {
if len(manifests) < 8 {
t.Fatalf("read %d manifests, want every module of the repository", len(manifests))
}
for _, why := range catalogue.ManifestRefusals(manifests, catalogue.PhaseWrite) {
for _, why := range catalogue.ManifestRefusals(manifests) {
t.Error(why)
}
})
@@ -2318,7 +2317,7 @@ func TestGrants(t *testing.T) {
t.Fatal(err)
}
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}})
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}})
p := partOn(b)
p.Objects = &fakeObjects{}
var said []asks.Condition
@@ -2400,7 +2399,7 @@ func TestGrants(t *testing.T) {
t.Fatal(err)
}
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}})
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}})
p := partOn(b)
p.Objects = &fakeObjects{}
var digests []string
@@ -2434,7 +2433,7 @@ func TestGrants(t *testing.T) {
// The loaded list's permissions are read back from the keys when the bus module's tools cannot say them.
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: delivery.GrantsUserListSource, Follows: delivery.FollowGrants, Revision: 8,
Digest: digests[2], Users: 6, Key: busKey.PublicKey}})
Digest: digests[2], Users: 5, Key: busKey.PublicKey}})
b.refuseUsers = true
if res := p.Compose(); slices.ContainsFunc(res.Grants.Waits, func(w string) bool { return strings.Contains(w, "unknown") }) {
t.Errorf("the kept permissions of the loaded list were not read: %v", res.Grants.Waits)
@@ -2446,23 +2445,27 @@ func TestGrants(t *testing.T) {
// written, and the refusal is raised by name; one the list in force does not carry stays out, said too.
t.Run("a refused principal keeps its grants of the list in force; the rest of the list is written", func(t *testing.T) {
b := fixture()
const ctl = `{"module":"mesh-controller","version":"1","own-secrets":{"broker":"/var/lib/c/broker"}}`
refusedCtl := strings.Replace(ctl, `}}`, `},"state":[{"name":"declared-config"}]}`, 1)
b.manifestOf("mesh-controller", ctl)
b.assign("b", "mesh-controller")
// A tools bundle's account whose bus removes what the closed set does not name is refused (issue 1126: the
// controller's mirror as a second writer, which this row used, went with the controller).
const ctl = `{"module":"mod-r","version":"1","own-secrets":{"broker":"/var/lib/r/broker","broker-mod-r-tools":"${dir:tools-secret}/broker-mod-r-tools"},` +
`"resources":[{"id":"tools-secret","type":"directory","path":"/var/lib/mesh/mod-r-tools","mode":"0711"}],"build":{"artifacts":[{"name":"tools",` +
`"kind":"bundle","language":"go","system":"arch","from":"cmd/mod-r-tools","binary":"mod-r-tools","loads":["mod-r-tools"],"bus":{}}]}}`
refusedCtl := strings.Replace(ctl, `"bus":{}`, `"bus":{"removes":["EVENTS.x"]}`, 1)
b.manifestOf("mod-r", ctl)
b.assign("b", "mod-r")
hash := func(user, h string, gen int) {
b.put(grants.ReadAccounts, user, map[string]any{"user": user, "hash": h, "generation": gen, "fingerprint": fmt.Sprintf("sha256:%d", gen)})
}
for _, u := range shadowOf(t, b) {
hash(u.User, "$2a$10$first", 1)
}
hash("a.mesh-controller", "$2a$10$first", 1)
hash("a.mod-r.mod-r-tools", "$2a$10$first", 1)
busKey, err := seal.GenerateKeyPair()
if err != nil {
t.Fatal(err)
}
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}})
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}})
p := partOn(b)
objs := &fakeObjects{}
p.Objects = objs
@@ -2494,39 +2497,39 @@ func TestGrants(t *testing.T) {
if inForce.Digest == "" {
t.Fatal("no list written after take-over")
}
entryBefore := entryOf(inForce, "b.mesh-controller")
ctlKey := grants.SealedUserKey(inForce.Digest, "b.mesh-controller")
entryBefore := entryOf(inForce, "b.mod-r.mod-r-tools")
ctlKey := grants.SealedUserKey(inForce.Digest, "b.mod-r.mod-r-tools")
before, _, ok, _ := b.ownGet(grants.StateUserList, ctlKey)
if !ok {
t.Fatal("the list in force keeps no grants of b.mesh-controller")
t.Fatal("the list in force keeps no grants of b.mod-r.mod-r-tools")
}
// b.mesh-controller now declares the controller's mirror as its own state: refused as a second writer. a.mod-x's hash
// changes in the same pass, and a.mesh-controller, refused the same way, is placed new.
b.manifestOf("mesh-controller", refusedCtl)
b.assign("a", "mesh-controller")
// b.mod-r.mod-r-tools now removes what the set does not name: refused. a.mod-x's hash
// changes in the same pass, and a.mod-r.mod-r-tools, refused the same way, is placed new.
b.manifestOf("mod-r", refusedCtl)
b.assign("a", "mod-r")
hash("a.mod-x", "$2a$10$second", 2)
res := p.Compose()
now, _, _ := p.Sealed()
if now.Digest == inForce.Digest || slices.ContainsFunc(res.Errors, func(e string) bool { return strings.Contains(e, "composed whole") }) {
t.Fatalf("a refused principal froze the list: sealed %+v, errors %v", res.Sealed, res.Errors)
}
after, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "b.mesh-controller"))
after, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "b.mod-r.mod-r-tools"))
if !ok || string(after) != string(before) {
t.Errorf("b.mesh-controller did not keep its grants of the list in force: %s, was %s", after, before)
t.Errorf("b.mod-r.mod-r-tools did not keep its grants of the list in force: %s, was %s", after, before)
}
// The kept user is written as the composer writes one: the bus reads the same entry it loaded.
if got := entryOf(now, "b.mesh-controller"); entryBefore == "" || got != entryBefore {
if got := entryOf(now, "b.mod-r.mod-r-tools"); entryBefore == "" || got != entryBefore {
t.Errorf("the kept entry is written otherwise than the list in force holds it:\n%s\nwas\n%s", got, entryBefore)
}
if entryOf(now, "a.mesh-controller") != "" {
if entryOf(now, "a.mod-r.mod-r-tools") != "" {
t.Error("a refused principal the list in force did not carry is in the sealed list")
}
if _, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "a.mesh-controller")); ok {
if _, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "a.mod-r.mod-r-tools")); ok {
t.Error("a refused principal the list in force did not carry is in the list")
}
if len(res.PrincipalsRefused) != 2 || len(said) == 0 || said[len(said)-1].State != asks.StateRaised ||
!strings.Contains(said[len(said)-1].Summary, "b.mesh-controller (keeps its grants of the list in force)") ||
!strings.Contains(said[len(said)-1].Summary, "a.mesh-controller (left out of the list") {
!strings.Contains(said[len(said)-1].Summary, "b.mod-r.mod-r-tools (keeps its grants of the list in force)") ||
!strings.Contains(said[len(said)-1].Summary, "a.mod-r.mod-r-tools (left out of the list") {
t.Fatalf("the refusals were not said by name: %v, said %+v", res.PrincipalsRefused, said)
}
// Composing again changes nothing and says nothing new; once the manifest composes, the warning is cleared.
@@ -2534,7 +2537,7 @@ func TestGrants(t *testing.T) {
if p.Compose(); len(said) != n {
t.Errorf("an unchanged refusal was said again: %+v", said[n:])
}
b.manifestOf("mesh-controller", ctl)
b.manifestOf("mod-r", ctl)
if res := p.Compose(); len(res.PrincipalsRefused) != 0 || said[len(said)-1].State != asks.StateCleared {
t.Errorf("the refusal was not cleared once it composes: %v, said %+v", res.PrincipalsRefused, said)
}
@@ -266,7 +266,7 @@ const GrantMissingCondition = "mesh-grants.grant-missing"
// in the vault (drop). **The fingerprint is the indexer's, never a commit's** (issue 1126: link2pay, snake, f1-circuits
// and txt-game, whose source commit has not moved since a build of the old form, have a done key of the new form at it,
// and that is the fingerprint granted). Pure.
func MissingGrants(in Inputs, want map[string]delivery.ControllerGrant, drop []string, skip map[string]bool) []string {
func MissingGrants(in Inputs, want map[string]delivery.Grant, drop []string, skip map[string]bool) []string {
placed := map[string]bool{}
for _, ms := range in.Assigned {
for _, m := range ms {
@@ -347,7 +347,7 @@ func (p *Part) sayMissing(missing []string) {
// GrantsOf is the grant of every module at the fingerprint the indexer's trunk names for it, by key, and the keys that
// have none (drop): see the comment above. A module in skip (its manifest was refused) is neither. Pure.
func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[string]delivery.ControllerGrant, []string, error) {
func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[string]delivery.Grant, []string, error) {
principals, err := buscompose.Users(recs)
if err != nil {
return nil, nil, err
@@ -363,7 +363,7 @@ func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[str
}
accounts[p.Module] = append(accounts[p.Module], p.Username())
}
out := map[string]delivery.ControllerGrant{}
out := map[string]delivery.Grant{}
var drop []string
for module, fp := range in.Fingerprints {
if skip[module] || !strings.HasPrefix(fp, delivery.SourcePrefix2) {
@@ -379,7 +379,7 @@ func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[str
continue
}
sort.Strings(names)
out[key] = delivery.ControllerGrant{Accounts: names}
out[key] = delivery.Grant{Accounts: names}
}
sort.Strings(drop)
return out, drop, nil
@@ -426,7 +426,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp
}
for _, key := range sortedKeys(want) {
g := want[key]
data, err := delivery.EncodeControllerGrant(g)
data, err := delivery.EncodeGrant(g)
if err != nil {
res.Errors = append(res.Errors, fmt.Sprintf("the grant %s cannot be encoded: %v", key, err))
continue
@@ -435,7 +435,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp
res.Errors = append(res.Errors, fmt.Sprintf("its own %s %s cannot be read: %v", StateGrants, key, err))
continue
} else if found {
if was, err := delivery.DecodeControllerGrant(raw); err == nil && delivery.SameControllerGrant(was, withVersion(g)) {
if was, err := delivery.DecodeGrant(raw); err == nil && delivery.SameGrant(was, withVersion(g)) {
continue
}
}
@@ -463,7 +463,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp
}
}
func withVersion(g delivery.ControllerGrant) delivery.ControllerGrant {
func withVersion(g delivery.Grant) delivery.Grant {
g.V = delivery.Version
return g
}
@@ -38,7 +38,7 @@ func records(in Inputs, union bool) (buscompose.Records, []Gap) {
}
// The enrolment users, from the seat mesh-enrolment's keys, enrolling 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: true,
Interchangeable: map[string]bool{}, Enrolling: slices.Clone(in.Enrolling),
// And each node's logins (ADR 0583 §1): both while it rotates.
NodeLogins: in.NodeLogins}
values := func(provider, setting string) []any {
@@ -87,7 +87,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped
next := cur
// The bus node follows this list alone (ADR 0571 clause 8): always since the controller ended by its unassignment
// (novox/hq ADR 0590, issue 1126).
next.Follow, next.LeaseHanded = delivery.FollowGrants, true
next.Follow = delivery.FollowGrants
key := ""
if in.Loaded != nil {
key = in.Loaded.Key
+6 -6
View File
@@ -35,14 +35,14 @@ const WritesGrants = "the grants part writes grants.<module>.<fp> into its state
// Grants answers the tool grants: the grants the part wrote, one module's or all.
func (p *Part) Grants(module string) (any, error) {
out := map[string]any{"writes": WritesGrants}
read := func(get func() ([]string, func(string) ([]byte, bool, error), error)) (map[string]delivery.ControllerGrant, error) {
read := func(get func() ([]string, func(string) ([]byte, bool, error), error)) (map[string]delivery.Grant, error) {
keys, one, err := get()
if err != nil {
return nil, err
}
m := map[string]delivery.ControllerGrant{}
m := map[string]delivery.Grant{}
for _, k := range keys {
mod, _, err := delivery.ParseControllerGrantKey(k)
mod, _, err := delivery.ParsePartGrantKey(k)
if err != nil || (module != "" && mod != module) {
continue
}
@@ -53,7 +53,7 @@ func (p *Part) Grants(module string) (any, error) {
if !found {
continue
}
g, err := delivery.DecodeControllerGrant(raw)
g, err := delivery.DecodeGrant(raw)
if err != nil {
return nil, fmt.Errorf("%s: %w", k, err)
}
@@ -73,10 +73,10 @@ func (p *Part) Grants(module string) (any, error) {
Module string `json:"module"`
Accounts []string `json:"accounts,omitempty"`
}
list := func(m map[string]delivery.ControllerGrant) []grant {
list := func(m map[string]delivery.Grant) []grant {
out := []grant{}
for _, k := range sortedKeys(m) {
mod, _, _ := delivery.ParseControllerGrantKey(k)
mod, _, _ := delivery.ParsePartGrantKey(k)
out = append(out, grant{Key: k, Module: mod, Accounts: m[k].Accounts})
}
return out