mesh-grants: grants_accounts reads the account consumers' differences in shadow, where no condition is raised (issue 774, ADR 0561 §6, §9)

This commit is contained in:
2026-10-28 21:54:35 +01:00
parent 9b31746cdb
commit 67e3ebf22f
3 changed files with 13 additions and 2 deletions
+4 -2
View File
@@ -120,8 +120,10 @@ func toolsOf(p *grants.Part) []stdio.Tool {
{Name: grants.ToolAccounts, Description: "The bus accounts the mesh wants (novox/hq ADR 0561 §2, §9): every account the vault " +
"mints that placement composes (a module's, a process's, a machine's provisioner client's), as the grants part last " +
"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; and " +
"what the last pass wrote, deleted or kept. Never a password. Read-only: grants_compose_now writes them again.",
"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: " +
"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 " +
+4
View File
@@ -1945,6 +1945,10 @@ func TestGrants(t *testing.T) {
if _, err := p.Accounts("b.mod-y"); err == nil {
t.Error("the tool reads an account no longer wanted")
}
p.Pass()
if all, _ := p.Accounts(""); all.(map[string]any)["consumers"] == nil {
t.Errorf("the tool does not say the account consumers in shadow: %v", all)
}
// With no bus address no account is written, and the pass says why.
bare := partOn(fixture())
if res := bare.Compose(); res.Accounts != nil || !strings.Contains(strings.Join(res.Errors, " "), "no bus address") {
@@ -138,6 +138,11 @@ func (p *Part) Accounts(user string) (any, error) {
if json.Unmarshal(raw, &res) == nil && res.Accounts != nil {
answer["last_pass"] = res.Accounts
}
// And the durable consumers the accounts need, as the last pass found them: missing and differing ones are read
// here in shadow too, where no condition is raised (ADR 0561 §6, §9).
if res.Consumers != nil {
answer["consumers"] = res.Consumers
}
}
return answer, nil
}