From d791052f772aab297a56dcd8df4981660e40281f Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 31 Oct 2026 10:02:38 +0100 Subject: [PATCH] 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) --- .../assignment_accounts_test.go | 48 ++++- cmd/mesh-controller/issued_to_vault.go | 5 + cmd/mesh-controller/issued_to_vault_test.go | 17 +- cmd/mesh-controller/modules.go | 172 +++++++++++++----- .../process_issue_integration_test.go | 41 ++--- cmd/mesh-controller/vault_secrets_test.go | 6 + go.mod | 2 +- go.sum | 4 + testdata/beside/CAPTURED | 2 +- 9 files changed, 222 insertions(+), 75 deletions(-) diff --git a/cmd/mesh-controller/assignment_accounts_test.go b/cmd/mesh-controller/assignment_accounts_test.go index 0dd9cbc2..646da68f 100644 --- a/cmd/mesh-controller/assignment_accounts_test.go +++ b/cmd/mesh-controller/assignment_accounts_test.go @@ -372,7 +372,7 @@ func TestTheAccountsAModuleSpeaksWithOnANode(t *testing.T) { t.Error("sealed no password") } return c.sealErr - }) + }, func() error { acts.did("after"); return nil }) if (err != nil) != (c.sealErr != nil) { 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 // 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). diff --git a/cmd/mesh-controller/issued_to_vault.go b/cmd/mesh-controller/issued_to_vault.go index 41703413..fb59b127 100644 --- a/cmd/mesh-controller/issued_to_vault.go +++ b/cmd/mesh-controller/issued_to_vault.go @@ -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 // 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. +// +// **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. var vaultAskerForIssue = func(ctx context.Context) vaultAsker { return theVault(ctx) } diff --git a/cmd/mesh-controller/issued_to_vault_test.go b/cmd/mesh-controller/issued_to_vault_test.go index 50068d32..38edc1cc 100644 --- a/cmd/mesh-controller/issued_to_vault_test.go +++ b/cmd/mesh-controller/issued_to_vault_test.go @@ -55,19 +55,24 @@ func TestModuleIssueHandsACoveredAccountToTheVaultAndOnlyThat(t *testing.T) { issue(core, "first-core-password") issue(moved, "first-moved-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 { 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() - for _, want := range []string{`"module":"nats"`, `"module":"moved-bot"`, `"name":"broker"`, - `"name":"` + broker.ProvisionerSecret + `"`, `"node":"laptop"`} { + client := broker.Principal{Kind: broker.KindProvisioner, Node: "laptop", Module: "nats"}.Username() + for _, want := range []string{`"module":"nats"`, `"module":"moved-bot"`, `"name":"broker"`, `"node":"laptop"`, + `"verb":"issue-account"`, `"user":"` + client + `"`} { if !strings.Contains(said, want) { t.Errorf("the vault was told %s, without %s", said, want) } } - if n := strings.Count(said, `"origin":"issued"`); n != 3 { - t.Errorf("%d accounts were handed with origin issued, want 3 (the core's two and the moved module's): %s", n, said) + if n := strings.Count(said, `"origin":"issued"`); n != 2 { + 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") { 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 { 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) + } } diff --git a/cmd/mesh-controller/modules.go b/cmd/mesh-controller/modules.go index 1b2a5140..3f1b0c9f 100644 --- a/cmd/mesh-controller/modules.go +++ b/cmd/mesh-controller/modules.go @@ -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 // 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. -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) + 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 { password, err := accounts.MintBusPassword(ctx, u) if err != nil { @@ -1062,6 +1075,63 @@ func issueAccount(ctx context.Context, accounts accountIssuer, u inventory.BusUs 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. // // **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, Module: m.Module}, func(password string) error { return issueWith(ctx, inv, m, node, busAddress, known, reachable, user, password) - }); err != nil { + }, func() error { return ensureModuleConsumer(m, node, busAddress) }); err != nil { 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}, func(password string) error { 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. @@ -1135,30 +1205,40 @@ func sealProcess(ctx context.Context, inv *inventory.Inventory, m catalogue.Mani if err := handIssuedToVault(ctx, inv, m, node, name, string(held)); err != nil { return err } - if len(pr.Queues) > 0 { - if busAddress == "" { - 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) - } 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 "+ - "%s's process %s's work-queue consumer: %w", m.Module, pr.Process, err) - } - defer js.Close() - who := broker.Principal{Kind: broker.KindProcess, Node: node, Module: m.Module, Process: pr.Process} - for _, q := range pr.Queues { - if err := js.EnsureConsumer(broker.QueueConsumer(who, q)); err != nil { - return err - } - } - } + 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 == "" { + 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) + return nil + } + js, err := broker.Dial(busAddress) + if err != nil { + return fmt.Errorf("the mesh cannot reach the bus to create %s's process %s's work-queue consumer: %w", + m.Module, pr.Process, err) + } + defer js.Close() + who := broker.Principal{Kind: broker.KindProcess, Node: node, Module: m.Module, Process: pr.Process} + for _, q := range pr.Queues { + if err := js.EnsureConsumer(broker.QueueConsumer(who, q)); err != nil { + return err + } + } + return nil +} + // heldProcessAccount is a process's account, in the provisioner client's form (novox/hq ADR 0436 §2): // {url, fingerprint, node, module, user, password} — never claims, since a process holds no seat (§3). func heldProcessAccount(known broker.Broker, reachable, node, module, user, password string) ([]byte, error) { @@ -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, Module: m.Module}, func(password string) error { 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. @@ -1263,28 +1343,8 @@ func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manife if err := handIssuedToVault(ctx, inv, m, node, "broker", string(held)); err != nil { return err } - - // 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 - } - } + if err := ensureModuleConsumer(m, node, busAddress); err != nil { + return err } fmt.Printf("bus user %s minted for %s, scoped to what it emits and consumes\n", user, m.Module) @@ -1294,6 +1354,30 @@ func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manife 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 // say to be worth anything later. // diff --git a/cmd/mesh-controller/process_issue_integration_test.go b/cmd/mesh-controller/process_issue_integration_test.go index be013ced..a319e2e7 100644 --- a/cmd/mesh-controller/process_issue_integration_test.go +++ b/cmd/mesh-controller/process_issue_integration_test.go @@ -19,17 +19,16 @@ import ( // // 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 -// round 1 finding 1: no own secret `broker` at all, its tools served by the tool runner), and issuing it -// again is the path every bus account takes (`rollout mint`'s KindProcess case, and a re-run of `module -// issue`): it rotates the password, and the old hash no longer matches. +// round 1 finding 1: no own secret `broker` at all, its tools served by the tool runner), as long as no vault is +// installed (genesis). Issue 774: once a vault is installed, the controller mints nothing: the issue is forwarded +// 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) { open := aMesh(t) ctx := t.Context() inv := open.inventory - theVaultIsInstalled(t, open) 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) vault := newStandInVault(t) @@ -58,30 +57,24 @@ func TestModuleIssueMintsAndDeliversAProcesssAccount(t *testing.T) { if err != nil || !held || hash1 == "" { t.Fatalf("the process's account was not minted: held %v, %v", held, err) } - - // 1181/AC1: accepted with the provisioner client's JSON, and handed to the vault whole — the url and - // the fingerprint being exactly what issueProcess was given above, not a guess at what it computes. - 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) - } + // 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 !strings.Contains(vault.said(), `"name":"broker-send"`) { - t.Errorf("the vault was not told the secret's name broker-send: %s", vault.said()) + if said := vault.said(); said != "" { + t.Errorf("the vault was asked before it was installed: %s", 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 774: with the vault installed, forwarded; nothing minted or rotated here. + theVaultIsInstalled(t, open) issue() hash2, held, err := inv.BusUserHash(ctx, user) - if err != nil || !held { - t.Fatalf("the process's account is gone after a re-issue: held %v, %v", held, err) + if err != nil || !held || hash1 != hash2 { + t.Fatalf("the controller minted again with a vault installed (held %v, %v)", held, err) } - if hash1 == hash2 { - t.Fatal("issuing the process's account again did not rotate its password") + if said := vault.said(); !strings.Contains(said, `"verb":"issue-account"`) || !strings.Contains(said, `"user":"`+user+`"`) { + t.Errorf("the vault was not asked to issue %s: %s", user, said) } } diff --git a/cmd/mesh-controller/vault_secrets_test.go b/cmd/mesh-controller/vault_secrets_test.go index e89183b1..1daa2a4d 100644 --- a/cmd/mesh-controller/vault_secrets_test.go +++ b/cmd/mesh-controller/vault_secrets_test.go @@ -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 controller, which carries no key and no sealed value. 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": if v.refuse != nil { return nil, v.refuse diff --git a/go.mod b/go.mod index f29fb348..ab449f93 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-controller go 1.26.0 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/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/go.sum b/go.sum index 0b60d1bd..4a5eb8ad 100644 --- a/go.sum +++ b/go.sum @@ -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-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.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/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/testdata/beside/CAPTURED b/testdata/beside/CAPTURED index 9fb61a4d..15ee0af4 100644 --- a/testdata/beside/CAPTURED +++ b/testdata/beside/CAPTURED @@ -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-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: