diff --git a/modules/mesh-grants/cmd/mesh-grants/tools.go b/modules/mesh-grants/cmd/mesh-grants/tools.go index 2cd1cd3..1b70ab8 100644 --- a/modules/mesh-grants/cmd/mesh-grants/tools.go +++ b/modules/mesh-grants/cmd/mesh-grants/tools.go @@ -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 " + diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index b059dd1..4aa9212 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -1771,6 +1771,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") { diff --git a/modules/mesh-grants/internal/grants/accounts.go b/modules/mesh-grants/internal/grants/accounts.go index 236c321..bf806a8 100644 --- a/modules/mesh-grants/internal/grants/accounts.go +++ b/modules/mesh-grants/internal/grants/accounts.go @@ -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 }