Forward module issue and an assignment's issue to the vault once one is installed, so the controller mints no bus account and none waits on a node reporting here (issue 774; research 072, the controller NATS-native: gap "vault mints after the lease", the minting piece moves to the vault)

This commit is contained in:
2026-10-31 10:02:39 +01:00
parent 22935b9202
commit d791052f77
9 changed files with 222 additions and 75 deletions
@@ -372,7 +372,7 @@ func TestTheAccountsAModuleSpeaksWithOnANode(t *testing.T) {
t.Error("sealed no password") t.Error("sealed no password")
} }
return c.sealErr return c.sealErr
}) }, func() error { acts.did("after"); return nil })
if (err != nil) != (c.sealErr != nil) { if (err != nil) != (c.sealErr != nil) {
t.Fatalf("error %v, want %v", err, c.sealErr) t.Fatalf("error %v, want %v", err, c.sealErr)
} }
@@ -385,6 +385,52 @@ func TestTheAccountsAModuleSpeaksWithOnANode(t *testing.T) {
}) })
} }
// Issue 774: where a vault is installed the controller mints nothing; `module issue` and an assignment's issue are
// forwarded to the vault's issue-account, and what the account needs beside its value still runs. A refusal fails
// `module issue` and only says itself on an assignment's issue: the vault mints on the want when it hears it.
for _, c := range []struct {
name string
only bool
answer string
refuse error
want string
fails bool
}{
{"module issue: forwarded, minted by the vault", false, `{"minted":true,"account":{"secret":"own/ace/mesh-build-go/broker","generation":1}}`, nil, "after", false},
{"an assignment's issue: forwarded, held already", true, `{"minted":false,"why":"held already"}`, nil, "after", false},
{"an assignment's issue: refused, said", true, "", errors.New("nobody wants it yet"), "after", false},
{"module issue: refused, failed", false, "", errors.New("nobody wants it"), "", true},
} {
t.Run("forwarded to the vault: "+c.name, func(t *testing.T) {
was := vaultIssuer
t.Cleanup(func() { vaultIssuer = was })
var asked []string
vaultIssuer = func(context.Context, accountIssuer) (vaultAsker, bool, error) {
return func(verb string, args map[string]any) (json.RawMessage, error) {
asked = append(asked, verb+" "+fmt.Sprint(args["user"]))
return json.RawMessage(c.answer), c.refuse
}, true, nil
}
acts := &anAccountStore{}
ctx, got := withIssued(t.Context())
if c.only {
ctx = onlyIfAbsent(ctx)
}
err := issueAccount(ctx, acts, inventory.BusUser{Username: "ace.mesh-build-go", Kind: inventory.BusModule},
func(string) error { acts.did("seal"); return nil }, func() error { acts.did("after"); return nil })
if (err != nil) != c.fails || strings.Join(acts.acts, " ") != c.want {
t.Fatalf("error %v, acts %q; want failed %v, acts %q (nothing minted, locked or sealed here)", err,
strings.Join(acts.acts, " "), c.fails, c.want)
}
if strings.Join(asked, ",") != "issue-account ace.mesh-build-go" {
t.Errorf("the vault was asked %v", asked)
}
if minted := len(got.list()) == 1; minted != strings.Contains(c.answer, `"minted":true`) {
t.Errorf("said issued %v", got.list())
}
})
}
// The accounts of a module that no longer resolves on its node are stale, its provisioner client's among them; a // The accounts of a module that no longer resolves on its node are stale, its provisioner client's among them; a
// module still resolved (assigned or required), an account of another kind, and a node whose resolution is unknown // module still resolved (assigned or required), an account of another kind, and a node whose resolution is unknown
// are left alone (issue 1945: ace's mesh-build-go rows from 17:19, marked sealed by migration 0108). // are left alone (issue 1945: ace's mesh-build-go rows from 17:19, marked sealed by migration 0108).
+5
View File
@@ -17,6 +17,11 @@ import (
// the vault alone opens it (ADR 0349 clause 8). The vault keeps it as the module's own secret on that machine and // the vault alone opens it (ADR 0349 clause 8). The vault keeps it as the module's own secret on that machine and
// raises `secret-changed`; a re-issue is the same hand with a new value, which replaces the old in place. An account // raises `secret-changed`; a re-issue is the same hand with a new value, which replaces the old in place. An account
// not covered is minted and kept as it was and handed to nobody. // not covered is minted and kept as it was and handed to nobody.
//
// **Since issue 774 an installed vault mints every account itself** (issueAccount forwards to its issue-account), so
// this hand is reached only where the controller still mints: before a vault is installed (where it hands nothing) and
// `rollout mint`'s re-issue of a module's account onto a new bus. The vault never lets such a hand replace an account it
// minted.
// vaultAskerForIssue is the vault as `module issue` asks it. A test replaces it. // vaultAskerForIssue is the vault as `module issue` asks it. A test replaces it.
var vaultAskerForIssue = func(ctx context.Context) vaultAsker { return theVault(ctx) } var vaultAskerForIssue = func(ctx context.Context) vaultAsker { return theVault(ctx) }
+13 -4
View File
@@ -55,19 +55,24 @@ func TestModuleIssueHandsACoveredAccountToTheVaultAndOnlyThat(t *testing.T) {
issue(core, "first-core-password") issue(core, "first-core-password")
issue(moved, "first-moved-password") issue(moved, "first-moved-password")
issue(plain, "kept-password") issue(plain, "kept-password")
// Issue 774: with the vault installed an issue mints nothing here; it is forwarded to the vault's issue-account.
if err := issueProvisionerClient(ctx, inv, core, "laptop", known, "10.0.0.1:4222"); err != nil { if err := issueProvisionerClient(ctx, inv, core, "laptop", known, "10.0.0.1:4222"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, held, err := inv.ModuleSecretIfIssued(ctx, "laptop", "nats", broker.ProvisionerSecret); err != nil || held {
t.Errorf("the controller kept a value of its own for the client's account (held %v, %v)", held, err)
}
said := vault.said() said := vault.said()
for _, want := range []string{`"module":"nats"`, `"module":"moved-bot"`, `"name":"broker"`, client := broker.Principal{Kind: broker.KindProvisioner, Node: "laptop", Module: "nats"}.Username()
`"name":"` + broker.ProvisionerSecret + `"`, `"node":"laptop"`} { for _, want := range []string{`"module":"nats"`, `"module":"moved-bot"`, `"name":"broker"`, `"node":"laptop"`,
`"verb":"issue-account"`, `"user":"` + client + `"`} {
if !strings.Contains(said, want) { if !strings.Contains(said, want) {
t.Errorf("the vault was told %s, without %s", said, want) t.Errorf("the vault was told %s, without %s", said, want)
} }
} }
if n := strings.Count(said, `"origin":"issued"`); n != 3 { if n := strings.Count(said, `"origin":"issued"`); n != 2 {
t.Errorf("%d accounts were handed with origin issued, want 3 (the core's two and the moved module's): %s", n, said) t.Errorf("%d accounts were handed with origin issued, want 2 (the core's and the moved module's): %s", n, said)
} }
if strings.Contains(said, "kept-bot") { if strings.Contains(said, "kept-bot") {
t.Errorf("an account that is neither core nor moved was handed to the vault: %s", said) t.Errorf("an account that is neither core nor moved was handed to the vault: %s", said)
@@ -156,4 +161,8 @@ func TestModuleIssueBeforeTheVaultIsInstalledKeepsTheValueAndHandsNothing(t *tes
if err := issueWith(ctx, inv, core, "laptop", "", known, "10.0.0.1:4222", "module.nats.laptop", "pw2"); err == nil { if err := issueWith(ctx, inv, core, "laptop", "", known, "10.0.0.1:4222", "module.nats.laptop", "pw2"); err == nil {
t.Error("a vault that is installed and does not answer did not fail the issue") t.Error("a vault that is installed and does not answer did not fail the issue")
} }
if err := issueProvisionerClient(ctx, inv, core, "laptop", known, "10.0.0.1:4222"); err == nil ||
!strings.Contains(err.Error(), "one issuer") {
t.Errorf("`module issue` forwarded to a vault that does not answer: %v", err)
}
} }
+117 -33
View File
@@ -1018,8 +1018,21 @@ const issueLockedFor = 2 * time.Minute
// left ace's mesh-build-go with no credential), under the store's lock on the account, so two issuers never interleave // left ace's mesh-build-go with no credential), under the store's lock on the account, so two issuers never interleave
// their look, mint and seal: the hash the bus holds is always the password sealed. A seal that fails leaves the // their look, mint and seal: the hash the bus holds is always the password sealed. A seal that fails leaves the
// account unsealed, issued again by the next issue. // account unsealed, issued again by the next issue.
func issueAccount(ctx context.Context, accounts accountIssuer, u inventory.BusUser, seal func(password string) error) error { //
// **Where a vault is installed the controller mints nothing** (issue 774, research 072's gap "vault mints after the
// lease"; the controller is deprecated and hands its duties over): the vault is the one issuer of every account the
// grants part wants, minted on that want, so the issue is forwarded to the vault's issue-account and only after runs
// (what the account needs beside its value: its durable consumers). The controller's store keeps no value for it and its
// own user list no hash: no account waits on a node reporting here for the vault to take it. Until a vault is installed
// (genesis, ADR 0113 §15 to §18) the controller mints as before.
func issueAccount(ctx context.Context, accounts accountIssuer, u inventory.BusUser, seal func(password string) error,
after func() error) error {
only, _ := ctx.Value(mintOnlyIfAbsent{}).(bool) only, _ := ctx.Value(mintOnlyIfAbsent{}).(bool)
if ask, installed, err := vaultIssuer(ctx, accounts); err != nil {
return fmt.Errorf("whether the vault is installed cannot be read, so %s is not issued: %w", u.Username, err)
} else if installed {
return forwardToTheVault(ctx, ask, u.Username, only, after)
}
if !only { if !only {
password, err := accounts.MintBusPassword(ctx, u) password, err := accounts.MintBusPassword(ctx, u)
if err != nil { if err != nil {
@@ -1062,6 +1075,63 @@ func issueAccount(ctx context.Context, accounts accountIssuer, u inventory.BusUs
return nil return nil
} }
// vaultIssuer answers the vault to forward an issue to, and whether one is installed (vaultInstalled); a store that is
// not the controller's inventory (a test's) has none. A test replaces it.
var vaultIssuer = func(ctx context.Context, accounts accountIssuer) (vaultAsker, bool, error) {
inv, ok := accounts.(*inventory.Inventory)
if !ok {
return nil, false, nil
}
installed, err := vaultInstalled(ctx, inv)
if err != nil || !installed {
return nil, false, err
}
return vaultAskerForIssue(ctx), true, nil
}
// issuedByTheVault is the vault's answer to issue-account (mesh-vault heldBackOrMinted).
type issuedByTheVault struct {
Minted bool `json:"minted"`
Why string `json:"why"`
Account struct {
Secret string `json:"secret"`
Generation int `json:"generation"`
} `json:"account"`
}
// forwardToTheVault asks the vault to issue user (its verb issue-account) and then runs after. The vault mints only
// what the grants part wants and never anew a value it holds, so asking twice, or with the want's own mint, is harmless.
// An issue on an assignment (only) is not failed by a refusal: the vault mints on the want itself when it is heard, and
// the refusal is said; `module issue` fails with it.
func forwardToTheVault(ctx context.Context, ask vaultAsker, user string, only bool, after func() error) error {
raw, err := ask("issue-account", map[string]any{"user": user})
if err != nil {
if !only {
return fmt.Errorf("the vault is the one issuer of bus accounts and did not issue %s: %w", user, err)
}
fmt.Printf(" %s is the vault's to mint, on the grants part's want of it; issue-account said: %v\n", user, err)
} else {
var got issuedByTheVault
if err := json.Unmarshal(raw, &got); err != nil {
return fmt.Errorf("the vault's answer to issue-account %s does not read: %w", user, err)
}
if got.Minted {
fmt.Printf("bus user %s minted by the vault as %s (generation %d)\n", user, got.Account.Secret, got.Account.Generation)
if g, _ := ctx.Value(issuedNowKey{}).(*issuedNow); g != nil {
g.mu.Lock()
g.users = append(g.users, user)
g.mu.Unlock()
}
} else {
fmt.Printf("bus user %s not minted anew by the vault: %s\n", user, got.Why)
}
}
if after == nil {
return nil
}
return after()
}
// issueOnTheNewBus gives an assigned module its credential on the bus being built. // issueOnTheNewBus gives an assigned module its credential on the bus being built.
// //
// **Three things differ from a management call, and each is the point of the move.** The credential // **Three things differ from a management call, and each is the point of the move.** The credential
@@ -1090,7 +1160,7 @@ func issueOnTheNewBus(ctx context.Context, inv *inventory.Inventory, m catalogue
if err := issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: busKindOf(m.Module), Node: node, if err := issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: busKindOf(m.Module), Node: node,
Module: m.Module}, func(password string) error { Module: m.Module}, func(password string) error {
return issueWith(ctx, inv, m, node, busAddress, known, reachable, user, password) return issueWith(ctx, inv, m, node, busAddress, known, reachable, user, password)
}); err != nil { }, func() error { return ensureModuleConsumer(m, node, busAddress) }); err != nil {
return err return err
} }
} }
@@ -1118,7 +1188,7 @@ func issueProcess(ctx context.Context, inv *inventory.Inventory, m catalogue.Man
return issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: inventory.BusProcess, Node: node, Module: m.Module}, return issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: inventory.BusProcess, Node: node, Module: m.Module},
func(password string) error { func(password string) error {
return sealProcess(ctx, inv, m, pr, node, busAddress, known, reachable, user, password) return sealProcess(ctx, inv, m, pr, node, busAddress, known, reachable, user, password)
}) }, func() error { return ensureProcessQueues(m, pr, node, busAddress) })
} }
// sealProcess seals a process's minted account to its node, hands it to the vault and makes its work-queue consumers. // sealProcess seals a process's minted account to its node, hands it to the vault and makes its work-queue consumers.
@@ -1135,15 +1205,29 @@ func sealProcess(ctx context.Context, inv *inventory.Inventory, m catalogue.Mani
if err := handIssuedToVault(ctx, inv, m, node, name, string(held)); err != nil { if err := handIssuedToVault(ctx, inv, m, node, name, string(held)); err != nil {
return err return err
} }
if len(pr.Queues) > 0 { if err := ensureProcessQueues(m, pr, node, busAddress); err != nil {
return err
}
fmt.Printf("bus user %s minted for %s's process %s, scoped to its bus declaration\n", user, m.Module, pr.Process)
fmt.Printf(" sealed to %s as %s. It arrives with the next push — `push %s` to send it\n", node, name, node)
return nil
}
// ensureProcessQueues makes a process's work-queue consumers where the bus can be reached: what its account needs beside
// its value, whoever minted it.
func ensureProcessQueues(m catalogue.Manifest, pr broker.ProcessPrincipal, node, busAddress string) error {
if len(pr.Queues) == 0 {
return nil
}
if busAddress == "" { if busAddress == "" {
fmt.Printf(" %s's process %s consumes its work queue; its consumer is created when the bus is "+ fmt.Printf(" %s's process %s consumes its work queue; its consumer is created when the bus is "+
"reachable (`push --behind`, then `rollout mint` again is harmless)\n", m.Module, pr.Process) "reachable (`push --behind`, then `rollout mint` again is harmless)\n", m.Module, pr.Process)
} else { return nil
}
js, err := broker.Dial(busAddress) js, err := broker.Dial(busAddress)
if err != nil { if err != nil {
return fmt.Errorf("the credential is minted and the mesh cannot reach the bus to create "+ return fmt.Errorf("the mesh cannot reach the bus to create %s's process %s's work-queue consumer: %w",
"%s's process %s's work-queue consumer: %w", m.Module, pr.Process, err) m.Module, pr.Process, err)
} }
defer js.Close() defer js.Close()
who := broker.Principal{Kind: broker.KindProcess, Node: node, Module: m.Module, Process: pr.Process} who := broker.Principal{Kind: broker.KindProcess, Node: node, Module: m.Module, Process: pr.Process}
@@ -1152,10 +1236,6 @@ func sealProcess(ctx context.Context, inv *inventory.Inventory, m catalogue.Mani
return err return err
} }
} }
}
}
fmt.Printf("bus user %s minted for %s's process %s, scoped to its bus declaration\n", user, m.Module, pr.Process)
fmt.Printf(" sealed to %s as %s. It arrives with the next push — `push %s` to send it\n", node, name, node)
return nil return nil
} }
@@ -1191,7 +1271,7 @@ func issueProvisionerClient(ctx context.Context, inv *inventory.Inventory, m cat
return issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: inventory.BusProvisioner, Node: node, return issueAccount(ctx, inv, inventory.BusUser{Username: user, Kind: inventory.BusProvisioner, Node: node,
Module: m.Module}, func(password string) error { Module: m.Module}, func(password string) error {
return sealProvisionerClient(ctx, inv, m, node, known, reachable, user, password) return sealProvisionerClient(ctx, inv, m, node, known, reachable, user, password)
}) }, nil)
} }
// sealProvisionerClient seals the provisioner client's minted account to its machine and hands it to the vault. // sealProvisionerClient seals the provisioner client's minted account to its machine and hands it to the vault.
@@ -1263,29 +1343,9 @@ func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manife
if err := handIssuedToVault(ctx, inv, m, node, "broker", string(held)); err != nil { if err := handIssuedToVault(ctx, inv, m, node, "broker", string(held)); err != nil {
return err return err
} }
if err := ensureModuleConsumer(m, node, busAddress); err != nil {
// And how it hears what it consumes. Derived from its declaration, and only when it declared
// something: a module that consumes nothing needs no consumer, and creating one would be a
// durable subscription nobody reads.
if consumer, needed := broker.ConsumerFor(broker.Principal{
Kind: broker.KindModule, Node: node, Module: m.Module,
Emits: m.EmitsAll(), Consumes: m.Consumes, Serves: m.Tools,
}); needed {
if busAddress == "" {
fmt.Printf(" %s consumes; its consumer is created when the bus is reachable (`push --behind`, then "+
"`rollout mint` again is harmless)\n", m.Module)
} else {
js, err := broker.Dial(busAddress)
if err != nil {
return fmt.Errorf("the credential is minted and the mesh cannot reach the bus to create "+
"how %s hears what it consumes: %w", m.Module, err)
}
defer js.Close()
if err := js.EnsureConsumer(consumer); err != nil {
return err return err
} }
}
}
fmt.Printf("bus user %s minted for %s, scoped to what it emits and consumes\n", user, m.Module) fmt.Printf("bus user %s minted for %s, scoped to what it emits and consumes\n", user, m.Module)
fmt.Printf(" sealed to %s. It arrives with the next push — `push %s` to send it\n", node, node) fmt.Printf(" sealed to %s. It arrives with the next push — `push %s` to send it\n", node, node)
@@ -1294,6 +1354,30 @@ func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manife
return nil return nil
} }
// ensureModuleConsumer makes how a module hears what it consumes, where the bus can be reached: what its account needs
// beside its value, whoever minted it. Derived from its declaration, and only when it declared something: a module that
// consumes nothing needs no consumer, and creating one would be a durable subscription nobody reads.
func ensureModuleConsumer(m catalogue.Manifest, node, busAddress string) error {
consumer, needed := broker.ConsumerFor(broker.Principal{
Kind: broker.KindModule, Node: node, Module: m.Module,
Emits: m.EmitsAll(), Consumes: m.Consumes, Serves: m.Tools,
})
if !needed {
return nil
}
if busAddress == "" {
fmt.Printf(" %s consumes; its consumer is created when the bus is reachable (`push --behind`, then "+
"`rollout mint` again is harmless)\n", m.Module)
return nil
}
js, err := broker.Dial(busAddress)
if err != nil {
return fmt.Errorf("the mesh cannot reach the bus to create how %s hears what it consumes: %w", m.Module, err)
}
defer js.Close()
return js.EnsureConsumer(consumer)
}
// whereItComesFrom is the provenance a module handed over by hand records, and what a record must // whereItComesFrom is the provenance a module handed over by hand records, and what a record must
// say to be worth anything later. // say to be worth anything later.
// //
@@ -19,17 +19,16 @@ import (
// //
// review round 1, findings 3 and 4 (1181/AC1, AC2): module issue mints and delivers a process's own // review round 1, findings 3 and 4 (1181/AC1, AC2): module issue mints and delivers a process's own
// account against the store — no bus to dial, busAddress "" — for mesh-mail's real manifest shape (review // account against the store — no bus to dial, busAddress "" — for mesh-mail's real manifest shape (review
// round 1 finding 1: no own secret `broker` at all, its tools served by the tool runner), and issuing it // round 1 finding 1: no own secret `broker` at all, its tools served by the tool runner), as long as no vault is
// again is the path every bus account takes (`rollout mint`'s KindProcess case, and a re-run of `module // installed (genesis). Issue 774: once a vault is installed, the controller mints nothing: the issue is forwarded
// issue`): it rotates the password, and the old hash no longer matches. // to the vault's issue-account for the process's user, and the controller's hash and value stay as they were.
func TestModuleIssueMintsAndDeliversAProcesssAccount(t *testing.T) { func TestModuleIssueMintsAndDeliversAProcesssAccount(t *testing.T) {
open := aMesh(t) open := aMesh(t)
ctx := t.Context() ctx := t.Context()
inv := open.inventory inv := open.inventory
theVaultIsInstalled(t, open)
m := mailSenderWithProcess() m := mailSenderWithProcess()
m.Moved = true // so handIssuedToVault's accountIsCovered is true, and the hand-off is exercised whole m.Moved = true
register(t, open, m) register(t, open, m)
vault := newStandInVault(t) vault := newStandInVault(t)
@@ -58,30 +57,24 @@ func TestModuleIssueMintsAndDeliversAProcesssAccount(t *testing.T) {
if err != nil || !held || hash1 == "" { if err != nil || !held || hash1 == "" {
t.Fatalf("the process's account was not minted: held %v, %v", held, err) t.Fatalf("the process's account was not minted: held %v, %v", held, err)
} }
// 1181/AC1: kept in the provisioner client's JSON — the url and the fingerprint being exactly what issueProcess
// was given above, not a guess at what it computes.
sealed, held, err := inv.ModuleSecretIfIssued(ctx, "laptop", "mesh-mail", "broker-send")
if err != nil || !held || sealed == "" {
t.Fatalf("the process's account is not kept as broker-send: held %v, %v", held, err)
}
if said := vault.said(); said != "" {
t.Errorf("the vault was asked before it was installed: %s", said)
}
// 1181/AC1: accepted with the provisioner client's JSON, and handed to the vault whole — the url and // Issue 774: with the vault installed, forwarded; nothing minted or rotated here.
// the fingerprint being exactly what issueProcess was given above, not a guess at what it computes. theVaultIsInstalled(t, open)
taken := vault.taken["accept"]
for _, want := range []string{`"url":"nats://` + reachable + `"`, `"fingerprint":"ab"`, `"node":"laptop"`,
`"module":"mesh-mail"`, `"user":"laptop.mesh-mail.send"`, `"password"`} {
if !strings.Contains(taken, want) {
t.Errorf("the vault was handed %q, without %s", taken, want)
}
}
if !strings.Contains(vault.said(), `"name":"broker-send"`) {
t.Errorf("the vault was not told the secret's name broker-send: %s", vault.said())
}
// The file's own owner (mesh-mail-send, review round 1 finding 7's area) is 1170's mechanism, proven
// generically there and for this fixture in TestAProcessesOwnSecretIsOwnedByItsOwnAccount.
// 1181/AC2: issued again — the path `rollout mint`'s KindProcess case and a re-run of `module issue`
// both take — rotates the password; the old hash no longer matches what is recorded.
issue() issue()
hash2, held, err := inv.BusUserHash(ctx, user) hash2, held, err := inv.BusUserHash(ctx, user)
if err != nil || !held { if err != nil || !held || hash1 != hash2 {
t.Fatalf("the process's account is gone after a re-issue: held %v, %v", held, err) t.Fatalf("the controller minted again with a vault installed (held %v, %v)", held, err)
} }
if hash1 == hash2 { if said := vault.said(); !strings.Contains(said, `"verb":"issue-account"`) || !strings.Contains(said, `"user":"`+user+`"`) {
t.Fatal("issuing the process's account again did not rotate its password") t.Errorf("the vault was not asked to issue %s: %s", user, said)
} }
} }
@@ -49,6 +49,12 @@ func (v *standInVault) ask(verb string, args map[string]any) (json.RawMessage, e
// The vault asks the desk's prompt and the provider's tool itself, in its own name (issue 723): one step from // The vault asks the desk's prompt and the provider's tool itself, in its own name (issue 723): one step from
// the controller, which carries no key and no sealed value. // the controller, which carries no key and no sealed value.
switch verb { switch verb {
case "issue-account":
// The vault is the one issuer once installed (issue 774): the controller forwards, and keeps nothing.
if v.refuse != nil {
return nil, v.refuse
}
return json.Marshal(map[string]any{"minted": true, "account": map[string]any{"user": args["user"], "generation": 1}})
case "secret-ask": case "secret-ask":
if v.refuse != nil { if v.refuse != nil {
return nil, v.refuse return nil, v.refuse
+1 -1
View File
@@ -3,7 +3,7 @@ module github.com/novox/mesh-controller
go 1.26.0 go 1.26.0
require ( require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
github.com/jackc/pgx/v5 v5.10.0 github.com/jackc/pgx/v5 v5.10.0
github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0 github.com/nats-io/nats.go v1.54.0
+4
View File
@@ -2,6 +2,10 @@ git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5 h1:E5stfIeCN6uYg
git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE= git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031083427-de8e82c06f54 h1:xITIabv8ujmwpaEKBiQpkOx6U/ICLTTzX9WWC3DGOPQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031083427-de8e82c06f54/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+1 -1
View File
@@ -9,7 +9,7 @@ a second place to clean besides the catalogue itself.
mesh-catalog f3a57dcc56fb6c198095cafd11d0decff9f720c4 modules/*/module.json, modules/*/files, modules/*/templates, predefined.json mesh-catalog f3a57dcc56fb6c198095cafd11d0decff9f720c4 modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
mesh-sdk 2da0de75c3f0087cff9c2f5ef789ca6dc3c59045 conformance/events/module-event.json mesh-sdk acbf80ad910a3492dd9a970ac17f9b29579c4e56 conformance/events/module-event.json
To move them, from this repository's root, with the two repositories checked out beside it: To move them, from this repository's root, with the two repositories checked out beside it: