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