L3: a provider's have counts only from a build that offers the provision; the gate takes a merged message with no trees (ADR 0603 §5, issue 1367) #603

Merged
mesh-tools merged 12 commits from feat/l3b-bus into main 2026-11-03 20:12:49 +00:00
22 changed files with 161 additions and 41 deletions
@@ -5,6 +5,8 @@ import (
"strings"
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// The holder, end to end over fakes (novox/hq ADR 0239 *How it is checked*, cut by ADR 0539 §17): a pull request's head
@@ -238,6 +240,17 @@ func TestAMergedGroupEndsMergedWithSourceMainMoved(t *testing.T) {
if w.source.main == nil || w.source.main.Heads["novox/a"] != strings.Repeat("p", 12) || w.source.main.Heads["novox/b"] != strings.Repeat("q", 12) {
t.Fatalf("source.main is %+v, wanted both merges", w.source.main)
}
// A merge that leaves no module.json carries no trees (issue 1367): the gate reads it, and refuses nothing.
msg := sdk.Forge{Kind: sdk.ForgeMerged, Repository: "novox/a", Number: 9, Head: strings.Repeat("e", 40),
Merge: strings.Repeat("f", 40), Root: "r"}
body, err := sdk.EncodeForge(msg)
if err != nil {
t.Fatalf("a merged message with no trees is refused: %v", err)
}
w.h.Forged("mesh-delivery-forge-bridge.novox.a.merged", body)
if len(w.h.refusals) != 0 {
t.Errorf("the gate refused a merged message with no trees: %v", w.h.refusals)
}
}
// ADR 0419 §2, issue 978: a description's `after:` line is not read, and says so once on its pull request. The
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-delivery-gate
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
)
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-provisioner-client
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
golang.org/x/sys v0.48.0
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
@@ -7,6 +7,7 @@
"scope": "node"
}
],
"core-credentials": true,
"own-secrets": {
"provisioner-broker": "${dir:state}/broker"
},
@@ -68,7 +68,10 @@ const (
ModulesRead = delivery.IndexerModule + ".modules"
ManifestsRead = delivery.IndexerModule + ".manifests"
HaveRead = "node-engine.have"
ComposedRead = "mesh-host.composed"
// BuildsRead is the builder's builds, `<module>.<fingerprint part>`: the key of the build a provider runs names the
// digest of the manifest it was built from (novox/hq ADR 0603 clause 5).
BuildsRead = delivery.BuilderModule + ".builds"
ComposedRead = "mesh-host.composed"
)
// BoundReads are the providers' entries this module's manifest reads, `<module>.bound`, whose changes it hears. Which
@@ -819,6 +822,14 @@ func (h *Holder) haveOutcome(p *provisions.Provision) {
case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed:
p.State, p.Why = provisions.Failed, orSaid(have.Detail, where+" failed")
case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP):
// A provider's have counts as its outcome only from a build whose manifest offers the provision (ADR 0603 clause 5).
// Refused only when that manifest is known and does not offer it: a have with no builder key (an old-form build, an
// adopted module) or an unreadable manifest keeps the outcome it had.
if offers, known := h.buildOffers(p.Provider.Module, have.FP, p.Provision); known && !offers {
p.State, p.Why = provisions.Asked, "waits for "+where+" to run a build that offers "+p.Provision+": the build it runs ("+
have.FP+") does not"
return
}
p.State, p.Why = provisions.Provided, ""
default:
p.State, p.Why = provisions.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+
@@ -826,6 +837,56 @@ func (h *Holder) haveOutcome(p *provisions.Provision) {
}
}
// buildOffers says whether the build of a module at a fingerprint, as the builder keyed it, was made from a manifest that
// offers the provision; known is false when the builder holds no such key, the key names no manifest, or the indexer holds
// no such manifest (the caller then does not refuse).
func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, known bool) {
key, err := delivery.BuilderBuildKey(module, fingerprint)
if err != nil {
return false, false
}
be, err := h.Store.Get(BuildsRead, key)
if err != nil || be == nil {
return false, false
}
b, err := delivery.DecodeBuilderBuild(be.Value)
if err != nil || b.Manifest == "" {
return false, false
}
mk, err := delivery.ManifestsKey(b.Manifest)
if err != nil {
return false, false
}
me, err := h.Store.Get(ManifestsRead, mk)
if err != nil || me == nil {
return false, false
}
man, err := delivery.DecodeManifest(me.Value)
if err != nil {
return false, false
}
var decl struct {
Provides []json.RawMessage `json:"provides"`
}
if json.Unmarshal([]byte(man.JSON), &decl) != nil {
return false, false
}
for _, raw := range decl.Provides {
var name string
if json.Unmarshal(raw, &name) != nil {
var full struct {
Name string `json:"name"`
}
_ = json.Unmarshal(raw, &full)
name = full.Name
}
if name == provision {
return true, true
}
}
return false, true
}
// keepsEntries says whether a provider's outcome for one provision is its entries (its manifest at the trunk declares the
// state `bound`) rather than its own have. When any offer of the provider says `answered-by: helper`, only those offers
// are answered by its entries: a module running the helper for one provision and making another some other way (gitea:
@@ -224,6 +224,34 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
t.Fatal(err)
}
settled(HaveRead, node+".postgres")
// The running build's manifest, as the builder keyed it: one that does not offer the provision keeps the key asked
// (ADR 0603 clause 5), one that does lets the have count.
buildsKey := mustKey(delivery.BuilderBuildKey("postgres", fpsOf["postgres"]))
offered := func(provides string) {
raw := `{"module":"postgres","version":"1","provides":` + provides + `}`
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "postgres", JSON: raw})
if err := s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value)); err != nil {
t.Fatal(err)
}
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
if err != nil {
t.Fatal(err)
}
if err := s.Put(BuildsRead, buildsKey, json.RawMessage(build)); err != nil {
t.Fatal(err)
}
}
offered(`["other"]`)
for i := 0; i < 2; i++ {
if _, err := h.ComposeNode(ctx, node); err != nil {
t.Fatal(err)
}
}
if p := state(key); p.State != provisions.Asked {
t.Fatalf("a build not offering the provision turned it %s", p.State)
}
offered(`["postgres-database"]`)
for i := 0; i < 2; i++ { // the credential judged first, then what depends on it
if _, err := h.ComposeNode(ctx, node); err != nil {
t.Fatal(err)
@@ -189,6 +189,28 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
t.Errorf("the provider's have %s: %+v, want %s", row.have, p, row.state)
}
}
// A have counts as the provider's outcome only from a build whose manifest offers the provision (ADR 0603 clause 5):
// refused when the build's manifest is known and does not offer it, provided when it does.
bfp := "src2:" + strings.Repeat("bb", 32)
buildKey := mustKey(delivery.BuilderBuildKey("systemd-resolved", bfp))
for _, row := range []struct{ name, provides, state, why string }{
{"a build whose manifest does not offer it", `["other"]`, provisions.Asked, "offers split-dns"},
{"a build whose manifest offers it", `[{"name":"split-dns"}]`, provisions.Provided, ""},
} {
raw := `{"module":"systemd-resolved","version":"1","provides":` + row.provides + `}`
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "systemd-resolved", JSON: raw})
_ = m.s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value))
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
if err != nil {
t.Fatal(err)
}
_ = m.s.Put(BuildsRead, buildKey, json.RawMessage(build))
m.compose(t, "g14")
if p := m.state(t, split); p.State != row.state || !strings.Contains(p.Why, row.why) {
t.Errorf("%s: %+v, want %s", row.name, p, row.state)
}
}
if e, _ := m.s.Get(PlansState, "g14.gitea"); e == nil {
t.Error("the plan is not written")
}
@@ -8,8 +8,6 @@ import (
"strings"
"sync"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// memStore is the bus as mesh-provisioner reaches it, in memory: the states it keeps.
@@ -326,13 +324,15 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
t.Errorf("the manifest lists %s, which is not served", tool)
}
}
for _, r := range append(slices.Clone(WatchedReads), ManifestsRead) {
// The builds read serves only haveOutcome's offer check (ADR 0603 clause 5: which manifest the build a provider runs
// was made from) and makes no plan, so ADR 0593 §8 ("it composes nothing") holds.
for _, r := range append(slices.Clone(WatchedReads), ManifestsRead, BuildsRead) {
if !slices.Contains(m.Reads, r) {
t.Errorf("the manifest does not read %s, which the holder reads", r)
}
}
// It composes nothing (ADR 0593 §8): no build, and no config value, is read to make a plan.
for _, r := range []string{delivery.BuilderModule + ".builds", "mesh-config.resolved"} {
// It composes nothing (ADR 0593 §8): no config value is read to make a plan.
for _, r := range []string{"mesh-config.resolved"} {
if slices.Contains(m.Reads, r) {
t.Errorf("the manifest reads %s, which only a composer reads", r)
}
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-provisioner
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
github.com/nats-io/nats.go v1.54.0
)
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+2
View File
@@ -20,6 +20,7 @@
"mesh-delivery-indexer.modules",
"mesh-delivery-indexer.manifests",
"node-engine.have",
"mesh-delivery-builder.builds",
"mesh-host.composed",
"gitea.bound",
"keycloak.bound",
@@ -45,6 +46,7 @@
"plans",
"refused"
],
"core-credentials": true,
"own-secrets": {
"broker": "${dir:state}/broker"
},
@@ -24,10 +24,6 @@ import (
"time"
)
// CoreModules are the core's modules whose own secrets the controller carries sealed by the vault: the same fixed
// list as the controller's (ADR 0349 §16). The node-engine and the controller have no own secrets of their own here.
var CoreModules = []string{"mesh-provisioner", "mesh-provisioner-client", "nats"}
// nodeKeyFile is a machine's sealing key as its engine stated it.
type nodeKeyFile struct {
Node string `json:"node"`
@@ -14,7 +14,6 @@ import (
"encoding/pem"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
@@ -56,13 +55,6 @@ func clientsBroker(sealingKey string) MoveOf {
Generation: "g-1", SealingKey: sealingKey}
}
// The list is the controller's (mesh-controller's core list, ADR 0349 §16): named here so a change to either is seen.
func TestTheCoreIsTheBusTheProvisionerAndItsClient(t *testing.T) {
if want := []string{"mesh-provisioner", "mesh-provisioner-client", "nats"}; !reflect.DeepEqual(CoreModules, want) {
t.Fatalf("the core's modules are %v, want %v", CoreModules, want)
}
}
// A node-engine states its machine's sealing key in its own name at the move, and only its own machine's.
func TestAMoveRecordsTheSealingKeyItsNodeEngineStates(t *testing.T) {
v := newVault(t, nil)
+4 -1
View File
@@ -292,7 +292,10 @@ func (v *Vault) StateNodeKey(caller, key string) error {
}
for _, r := range all {
parts := strings.Split(r.Name, "/")
if len(parts) == 4 && parts[0] == "own" && parts[1] == node && slices.Contains(CoreModules, parts[2]) {
// Every own secret of the machine is said changed: which modules carry theirs sealed by the vault is what their
// manifests say (core-credentials), read by the node-engine, which composes again and reseals those alone; the vault
// keeps no list of modules (issue 774, the operator 2026-11-03).
if len(parts) == 4 && parts[0] == "own" && parts[1] == node {
v.announce(Changed{Name: r.Name, Generation: r.Generation, At: v.Now().UTC(), By: caller})
}
}
@@ -2,7 +2,7 @@ package main
// Issue 786 (ADR 0372): a machine's node-engine states its own sealing key to the vault, in its own name, at start and
// when it changes, whether or not it ever moved a secret in. The caller is the bus user the runtime proved; a changed
// key (or a first one) raises `secret-changed` for the machine's core secrets, the same key says nothing.
// key (or a first one) raises `secret-changed` for the machine's own secrets, the same key says nothing.
import (
"git.novox.be/novox/mesh-sdk/go/seal"
@@ -76,7 +76,8 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
t.Error("a key was recorded for g14 by ace's engine")
}
got := changedNames(v.bus)[before:]
want := []string{"own/ace/mesh-provisioner-client/provisioner-broker", "own/ace/nats/broker"}
// Every own secret of the machine: which carry the vault's box their manifests say, read by the node-engine (issue 774).
want := []string{"own/ace/mesh-provisioner-client/provisioner-broker", "own/ace/nats/broker", "own/ace/postgres/superuser-password"}
if strings.Join(got, ",") != strings.Join(want, ",") {
t.Errorf("secret-changed was raised for %v, want %v", got, want)
}
@@ -85,16 +86,16 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
if _, err := tool.runAs("node.ace", map[string]any{"key": pub}); err != nil {
t.Fatal(err)
}
if n := len(changedNames(v.bus)); n != before+2 {
t.Errorf("the same key raised %d more", n-before-2)
if n := len(changedNames(v.bus)); n != before+3 {
t.Errorf("the same key raised %d more", n-before-3)
}
// A different key raises them again.
other, _ := aSealingKey(t)
if _, err := tool.runAs("node.ace", map[string]any{"key": other}); err != nil {
t.Fatal(err)
}
if n := len(changedNames(v.bus)); n != before+4 {
t.Errorf("a changed key raised %d, want 2", n-before-2)
if n := len(changedNames(v.bus)); n != before+6 {
t.Errorf("a changed key raised %d, want 3", n-before-3)
}
if got, _, _ := v.Store.NodeKey("ace"); got != other {
t.Error("the changed key was not recorded")
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-vault
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
golang.org/x/crypto v0.57.0
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1 h1:T66tg9T/1hHg6KN/bDScHPT41IHkxLAGGO8VJiAOlb8=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+1 -1
View File
@@ -3,7 +3,7 @@ module nats-tools
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
github.com/klauspost/compress v1.20.0
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+2 -1
View File
@@ -3,7 +3,7 @@
"version": "1",
"upgrade": {
"policy": "record",
"why": "the bus: replaced only as a planned step a person starts (`bus upgrade`), its streams snapshotted first and checked after (hq ADR 0236); the controller holds this whatever is said here"
"why": "the bus keeps the streams of the whole mesh: each move waits for a person's yes, which the operator's Approve of the release that carries its build gives, and its node backs its streams up before it applies (hq ADR 0236, ADR 0601)"
},
"provides": [
{
@@ -21,6 +21,7 @@
}
],
"bus-users": "/var/lib/nats-module/conf/accounts.conf",
"core-credentials": true,
"own-secrets": {
"broker": "${dir:mesh-state}/broker",
"broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"