Compose where a declaration was sent: secrets, credentials, acceptances, the hub's peers, adoption, assignments and the bus's user list (issue 1945, B2, B6)

Each act that changed what a node runs now writes that node's compositions, and its wants follow by the assigner's
rules; a bus upgrade composes the bus's machines and answers the want the operator writes, since the assigner never
moves the bus (ADR 0539 clauses 5, 7 and 15). A node-engine may ask its own backup holder before a data module moves.
This commit is contained in:
2026-10-24 16:59:06 +02:00
parent 0f8c47842a
commit c40e15268e
17 changed files with 223 additions and 51 deletions
+6 -4
View File
@@ -112,11 +112,13 @@ func acceptForProvision(ctx context.Context, provider, providerModule, provision
if len(accepted.Sealed) == 0 {
return nil
}
if err := sendTo(ctx, open, accepted.Machines); err != nil {
return fmt.Errorf("%s is sealed and could not be sent: %w; `push %s` sends it", provision, err,
strings.Join(accepted.Machines, "`, `push "))
// Composed into each consumer's composition (novox/hq ADR 0539 clause 5): a new revision its want then names.
if _, err := composeModules(ctx, open, accepted.Machines); err != nil {
return fmt.Errorf("%s is sealed and its compositions on %s could not be written: %w", provision,
strings.Join(accepted.Machines, ", "), err)
}
fmt.Printf(" sent %s; each consumer starts again on it\n", strings.Join(accepted.Machines, ", "))
fmt.Printf(" composed for %s; each consumer starts again on it once its want names the new composition\n",
strings.Join(accepted.Machines, ", "))
return nil
}
+18 -5
View File
@@ -171,8 +171,21 @@ func assignWith(ctx context.Context, open *stores, node string, opts assignOptio
answer += "\n but " + catalogue.WrongMachine(u.Module, c, node)
}
}
return answer + fmt.Sprintf("\n run `push %s` to send it", node) +
blockedElsewhere(ctx, open, node), nil
return answer + composedAfterAct(ctx, open, node) + blockedElsewhere(ctx, open, node), nil
}
// composedAfterAct composes the node an assignment or its end changed (novox/hq ADR 0539 clause 5): what the
// answer says of it. The assigner then writes the node's wants by its rules; nothing is sent.
func composedAfterAct(ctx context.Context, open *stores, node string) string {
wrote, err := composeModules(ctx, open, []string{node})
if err != nil {
return fmt.Sprintf("\n but its compositions on %s could not all be written: %v", node, firstLine(err.Error()))
}
if len(wrote[node]) == 0 {
return fmt.Sprintf("\n %s's compositions were already as composed now", node)
}
return fmt.Sprintf("\n composed for %s (%d composition(s) written): its wants follow by the assigner's rules", node,
len(wrote[node]))
}
// seatDependenciesOnAssign is the refusal ADR 0207 makes at assignment, or nothing, with the
@@ -273,8 +286,8 @@ func unassign(ctx context.Context, open *stores, node string, modules ...string)
return "", err
}
}
answer := fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so",
node, strings.Join(modules, ", "), node)
answer := fmt.Sprintf("%s no longer runs %s: its want is written removed by the assigner",
node, strings.Join(modules, ", "))
after := resolvedAfter(ctx, open, shelf, node)
for _, line := range unheldChangeTo(shelf, node, assigned, after.modules, after.unheld) {
answer += "\n " + line
@@ -295,7 +308,7 @@ func unassign(ctx context.Context, open *stores, node string, modules ...string)
for _, line := range keptOnUnassign(ctx, open.inventory, node, modules) {
answer += "\n " + line
}
return answer + blockedElsewhere(ctx, open, node), nil
return answer + composedAfterAct(ctx, open, node) + blockedElsewhere(ctx, open, node), nil
}
// afterAct is the one resolution of a node an act's answer is said from, taken after its write
+3 -3
View File
@@ -205,9 +205,9 @@ func adoptedNodes(nodes []inventory.Node) []string {
// The operator's acts on an adopted node (novox/hq ADR 0100). Called by the command line and the
// command API alike, so a refusal is the same refusal in the same words at both (ADR 0035).
// sendNodes sends the named machines what they should be now. A variable so a test can see what
// an act would send without a broker.
var sendNodes = sendTo
// sendNodes composes the named machines as they should be now (novox/hq ADR 0539 clause 5): each node's want then
// names what changed. A variable so a test can see what an act would compose without a broker.
var sendNodes = composeAndSay
// DefaultFilter is the module converging a node assigns to load the mesh's derived filter.
const DefaultFilter = "nftables"
+76 -6
View File
@@ -10,8 +10,10 @@ import (
"time"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/catalogue"
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-controller/internal/conditions"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
@@ -229,17 +231,85 @@ func busCommand(ctx context.Context, args []string) error {
fmt.Printf("bus upgrade %d: %s %s → %s on %s; streams snapshotted at %s; %s\n", step.ID, b.module, step.From,
short(b.to), strings.Join(moving, ", "), where, map[bool]string{true: "reversible: putting the old build back undoes it",
false: "NOT reversible: the snapshot is the only way back"}[*reversible])
sent, err := sendToEach(withBusStep(ctx), open, moving)
// **Nothing is sent, and the assigner never moves the bus** (novox/hq ADR 0539 clause 15): the bus's machines are
// composed at the build the mesh holds, and the operator writes each machine's want with the operator's credential,
// recorded, naming that composition.
wants, err := busWantsToWrite(withBusStep(ctx), open, b.module, moving)
if err != nil {
_ = inv.EndBusStep(ctx, step.ID, "failed", "the send was refused: "+err.Error())
return fmt.Errorf("the bus's machine could not be sent its new build: %w — nothing was replaced", err)
_ = inv.EndBusStep(ctx, step.ID, "failed", "the bus's composition could not be written: "+err.Error())
return fmt.Errorf("the bus's machines could not be composed at its new build: %w — nothing was replaced", err)
}
fmt.Printf("composed %s at the bus's new build. Write each want below with the operator's credential, and record it "+
"in the hand-act log; `bus-maintenance` is open until the bus answers healthy again — every stream, every durable "+
"consumer, a round trip to the machines (H-bus) — within %s, or the step is said failed with its snapshot as the "+
"way back. `bus` says how it went\n", strings.Join(moving, ", "), busStepBound)
for _, w := range wants {
fmt.Println(" " + w)
}
fmt.Printf("sent %s; `bus-maintenance` is open until the bus answers healthy again — every stream, every durable "+
"consumer, a round trip to the machines (H-bus) — within %s, or the step is said failed with its snapshot "+
"as the way back. `bus` says how it went\n", strings.Join(sent, ", "), busStepBound)
return nil
}
// busWantsToWrite composes the bus's machines and answers, for each, the want the operator writes: its key in the
// assigner's bucket and its value, naming the fingerprint the bus module is registered at and that machine's
// composition of it.
func busWantsToWrite(ctx context.Context, open *stores, module string, machines []string) ([]string, error) {
if _, err := composeModules(ctx, open, machines); err != nil {
return nil, err
}
fps, err := currentFingerprints(ctx, open.inventory)
if err != nil {
return nil, err
}
bus, err := aBus()
if err != nil {
return nil, err
}
defer bus.Close()
js, err := jetstream.New(bus.Conn())
if err != nil {
return nil, err
}
digests, err := js.KeyValue(ctx, sdkdelivery.ControllerComposedBucket)
if err != nil {
return nil, err
}
digestOf := func(node string) (string, error) {
key, err := sdkdelivery.ComposedKey(node, module, fps[module])
if err != nil {
return "", err
}
e, err := digests.Get(ctx, key)
if err != nil {
return "", fmt.Errorf("%s: %w", key, err)
}
v, err := sdkdelivery.DecodeComposedDigest(e.Value())
return v.Digest, err
}
return busWants(module, fps[module], machines, digestOf)
}
// busWants is each machine's want for the bus module, as the operator writes it: `<bucket> <key> <value>`.
func busWants(module, fp string, machines []string, digestOf func(node string) (string, error)) ([]string, error) {
var out []string
for _, node := range machines {
digest, err := digestOf(node)
if err != nil {
return nil, err
}
key, err := sdkdelivery.WantKey(node, module)
if err != nil {
return nil, err
}
value, err := sdkdelivery.EncodeWant(sdkdelivery.Want{FP: fp, Composed: digest,
Words: "the bus moved by a person's step (bus upgrade, ADR 0539 clause 15)"})
if err != nil {
return nil, err
}
out = append(out, fmt.Sprintf("%s %s %s", sdkdelivery.AssignerWantsBucket, key, value))
}
return out, nil
}
// busStatus is `bus`: what an upgrade would do, and the last step.
func busStatus(ctx context.Context) error {
open, err := openStores(ctx)
@@ -0,0 +1,33 @@
package main
import (
"strings"
"testing"
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1945/AC7: a bus upgrade answers, for each machine, the want the operator writes: the assigner's bucket, the
// machine's key for the bus module, and a want naming the bus's fingerprint and that machine's composition of it. It
// sends nothing: nothing it calls publishes on a node's declaration subject (busWants composes no send at all).
func TestABusUpgradeAnswersTheWantTheOperatorWrites(t *testing.T) {
fp := sdkdelivery.SourcePrefix2 + strings.Repeat("b", 64)
digests := map[string]string{"novox": "sha256:" + strings.Repeat("1", 64), "ace": "sha256:" + strings.Repeat("2", 64)}
lines, err := busWants("nats", fp, []string{"ace", "novox"}, func(node string) (string, error) { return digests[node], nil })
if err != nil || len(lines) != 2 {
t.Fatalf("busWants = %v, %v", lines, err)
}
for i, node := range []string{"ace", "novox"} {
parts := strings.SplitN(lines[i], " ", 3)
if len(parts) != 3 || parts[0] != sdkdelivery.AssignerWantsBucket || parts[1] != "want."+node+".nats" {
t.Fatalf("line %q", lines[i])
}
w, err := sdkdelivery.DecodeWant([]byte(parts[2]))
if err != nil || w.FP != fp || w.Composed != digests[node] {
t.Errorf("%s's want = %+v, %v", node, w, err)
}
}
if _, err := busWants("nats", fp, []string{"ace"}, func(string) (string, error) { return "", nil }); err == nil {
t.Errorf("a want naming no composition was answered")
}
}
+6 -4
View File
@@ -62,12 +62,14 @@ func replaceGiven(ctx context.Context, open *stores, report link.Report) {
for _, r := range replaced {
said := SecretReplaced{Node: report.Node, Module: r.Module, Name: r.Name, Given: r.Given.UTC(),
Sent: r.Machines, Why: givenWhy}
log.Printf("replaced %q of %s on %s, given %s, with a value the mesh made: %s; sending %s",
log.Printf("replaced %q of %s on %s, given %s, with a value the mesh made: %s; composing %s",
r.Name, r.Module, report.Node, r.Given.UTC().Format(time.RFC3339), givenWhy, strings.Join(r.Machines, ", "))
if err := sendTo(ctx, open, r.Machines); err != nil {
// Composed (novox/hq ADR 0539 clause 5): the compositions that use it get a new revision, which each node's
// want then names.
if _, err := composeModules(ctx, open, r.Machines); err != nil {
said.Sent, said.Unsent = nil, err.Error()
log.Printf("the new %q of %s is sealed and not yet delivered to %s — the next push carries it: %v",
r.Name, r.Module, strings.Join(r.Machines, ", "), err)
log.Printf("the new %q of %s is sealed and its compositions on %s are not yet written — the next act that "+
"composes them carries it: %v", r.Name, r.Module, strings.Join(r.Machines, ", "), err)
}
stateReplaced(ctx, said)
}
+4 -1
View File
@@ -315,7 +315,10 @@ func secretIssued(ctx context.Context, args []string) error {
return err
})
},
send: func(machines []string) error { return sendTo(ctx, open, machines) },
send: func(machines []string) error {
_, err := composeModules(ctx, open, machines)
return err
},
ofTheVault: filledTheNewWay,
vault: theVault(ctx),
}
+5 -4
View File
@@ -616,10 +616,11 @@ func throughTheTunnel(ctx context.Context, open *stores, node inventory.Node, ke
}
}
}
if err := sendTo(ctx, open, []string{hub.Name}); err != nil {
return nil, "", fmt.Errorf("%s was made a peer of the hub, and the hub could not be sent "+
"it, so the tunnel would not answer — the token is not shown; issue it again once %s "+
"can be pushed: %w", node.Name, hub.Name, err)
// The hub's own composition carries the private network (novox/hq ADR 0539 clause 5), its want then names it.
if _, err := composeModules(ctx, open, []string{hub.Name}); err != nil {
return nil, "", fmt.Errorf("%s was made a peer of the hub, and the hub's composition could not be "+
"written, so the tunnel would not answer — the token is not shown; issue it again once %s "+
"composes: %w", node.Name, hub.Name, err)
}
// An address, not a name — nothing resolves before the machine has joined (novox/hq ADR 0004).
return &token.Tunnel{
+3 -1
View File
@@ -62,7 +62,9 @@ func ownGrantsOf(open *stores) ownGrantsCarrier {
return ownGrantsCarrier{
behind: func(ctx context.Context) (string, bool, error) { return brokerBehind(ctx, open, nil) },
send: func(ctx context.Context, holder string) error {
_, err := sendToEach(ctx, open, []string{holder})
// The user list is a file of the bus module's composition (novox/hq ADR 0539 clause 7): composed, and the
// assigner writes the bus node's want for that revision at once.
_, err := composeModules(ctx, open, []string{holder})
return err
},
}
+3 -3
View File
@@ -435,9 +435,9 @@ func applyPending(ctx context.Context, open *stores, p inventory.PendingAssignme
case strings.Contains(answer, "already runs"):
note = fmt.Sprintf("%s already runs %s: the pending assignment is met", p.Node, p.Module)
default:
// The same as a person's assign: nothing is sent by this act.
note = fmt.Sprintf("%s is assigned %s: build %s registered it (pending since %s); `push %s` sends it",
p.Node, p.Module, by, clock(p.Since), p.Node)
// The same as a person's assign: composed, and its want follows by the assigner's rules.
note = fmt.Sprintf("%s is assigned %s: build %s registered it (pending since %s); composed, its want follows",
p.Node, p.Module, by, clock(p.Since))
}
settled, serr := inv.SettlePending(ctx, p.ID, inventory.PendingApplying, state, note)
if serr != nil {
+10 -11
View File
@@ -142,20 +142,19 @@ func rotateCommand(ctx context.Context, args []string) error {
}
}
// **Both ends, in one send.** There is a window either way — a role's password changes on the
// provider and the file changes on the consumer, and they cannot be simultaneous — so the
// honest thing is to make it as short as the broker allows and to never leave it open across
// a command boundary, where it depends on somebody's memory.
fmt.Printf("\nsending to both ends:\n")
if err := sendTo(ctx, open, machines); err != nil {
// **Both ends, composed together** (novox/hq ADR 0539 clause 5). There is a window either way — a role's
// password changes on the provider and the file changes on the consumer, and they cannot be simultaneous —
// so both ends' compositions are written in one act, never left across a command boundary.
fmt.Printf("\ncomposing both ends:\n")
if _, err := composeModules(ctx, open, machines); err != nil {
return fmt.Errorf(
"%w\n\nThe old credentials are gone from the mesh and the new ones have not arrived. "+
"Nothing on those machines has changed yet, so what is running keeps working "+
"until the provider next applies. Fix the cause and run `push --behind`", err)
"%w\n\nThe old credentials are gone from the mesh and the new compositions are not all written. "+
"Nothing on those machines has changed yet, so what is running keeps working until each "+
"node applies the composition its want names. Fix the cause and rotate again", err)
}
fmt.Printf("\n%d machine(s) told. Until both ends have applied, a consumer whose password "+
"changed cannot authenticate — `status` says who is still behind\n", len(machines))
fmt.Printf("\n%d machine(s) composed. Until both ends have applied what their wants name, a consumer whose "+
"password changed cannot authenticate — the delivery view says each machine's want and have\n", len(machines))
return nil
}
+6 -5
View File
@@ -651,13 +651,14 @@ func rotateKept(ctx context.Context, node, module, name string, why handActFlags
machines = []string{node}
}
if len(machines) == 1 {
fmt.Printf("sending %s, so %s starts again on the new value:\n", node, module)
fmt.Printf("composing %s, so %s starts again on the new value once its want names it:\n", node, module)
} else {
fmt.Printf("shared with every consumer; sending %s together:\n", strings.Join(machines, ", "))
fmt.Printf("shared with every consumer; composing %s together:\n", strings.Join(machines, ", "))
}
if err := sendTo(ctx, open, machines); err != nil {
return fmt.Errorf("%w\n\nThe new value is sealed and not yet delivered; what runs keeps the old "+
"one until the machines next apply. Fix the cause and run `push --behind`", err)
// Composed (novox/hq ADR 0539 clause 5): a new revision of the compositions that use it.
if _, err := composeModules(ctx, open, machines); err != nil {
return fmt.Errorf("%w\n\nThe new value is sealed and its compositions are not all written; what runs "+
"keeps the old one. Fix the cause and move it again", err)
}
return nil
}
+4 -3
View File
@@ -191,11 +191,12 @@ func composeAnnouncedReads(ctx context.Context, open *stores) {
if holder == "" || !behind {
return
}
if _, err := sendToEach(ctx, open, []string{holder}); err != nil {
fmt.Printf("states: the user list the states announced compose could not be sent to %s: %v\n", holder, err)
// A file of the bus module's composition (novox/hq ADR 0539 clause 7), written into the bus node's want at once.
if _, err := composeModules(ctx, open, []string{holder}); err != nil {
fmt.Printf("states: the user list the states announced compose could not be composed for %s: %v\n", holder, err)
return
}
fmt.Printf("states: sent %s, which holds the bus, the user list the states announced compose\n", holder)
fmt.Printf("states: composed for %s, which holds the bus, the user list the states announced compose\n", holder)
}
// notYetAnnounced are the owners some module reads the state of and that have announced nothing yet: what `status`
+17
View File
@@ -96,3 +96,20 @@ func ComposedReads(node string) ([]string, error) {
}
return append(readGrants(bucket, ""), readGrants(delivery.AssignerWantsBucket, "want."+node+".>")...), nil
}
// VerbsTheNodeAgentAsks are the seat verbs a node's node-engine calls of its own machine while it applies a want (ADR
// 0539 clause 16, ADR 0429 §14): its backup holder backs a data module up before it moves, and says when it was. Asked
// of its own machine only, `mesh.seat.<seat>.tool.<verb>.<node>`.
var VerbsTheNodeAgentAsks = []SeatVerb{{Seat: "node-backup", Verb: "now"}, {Seat: "node-backup", Verb: "backed-up"}}
// NodeAgentAsks are the subjects of VerbsTheNodeAgentAsks for one node.
func NodeAgentAsks(node string) ([]string, error) {
if !safeSubject.MatchString(node) {
return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node)
}
var out []string
for _, v := range VerbsTheNodeAgentAsks {
out = append(out, "mesh.seat."+v.Seat+".tool."+v.Verb+"."+node)
}
return out, nil
}
+22
View File
@@ -47,3 +47,25 @@ func TestANodeReadsOnlyItsOwnCompositions(t *testing.T) {
t.Errorf("a node name with a wildcard was granted")
}
}
// 1945 (for issue 1944): a node-engine asks its own machine's backup holder to back a data module up before it moves
// and when it last was, and no other machine's.
func TestANodeAgentAsksItsOwnBackupHolder(t *testing.T) {
asks, err := NodeAgentAsks("ace")
if err != nil || strings.Join(asks, " ") != "mesh.seat.node-backup.tool.now.ace mesh.seat.node-backup.tool.backed-up.ace" {
t.Errorf("NodeAgentAsks(ace) = %v, %v", asks, err)
}
p, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace", PasswordHash: "x"})
if err != nil {
t.Fatal(err)
}
all := strings.Join(p.Publish, " ")
for _, want := range append(asks, "$JS.API.DIRECT.GET.KV_composed-ace.>") {
if !strings.Contains(all, want) {
t.Errorf("a node-engine of ace may not publish %s", want)
}
}
if strings.Contains(all, "node-backup.tool.now.*") || strings.Contains(all, "KV_composed-g14") {
t.Errorf("a node-engine of ace reaches another machine: %v", p.Publish)
}
}
+6
View File
@@ -865,6 +865,12 @@ func PermissionsFor(p Principal) (Permissions, error) {
return Permissions{}, err
}
pub = append(pub, composedReads...)
// And its own machine's backup holder, before a data module moves (ADR 0539 clause 16).
backups, err := NodeAgentAsks(p.Node)
if err != nil {
return Permissions{}, err
}
pub = append(pub, backups...)
// And, while the vault serves it, the vault's move-in in its own name and nobody else's (issue 676): the
// values sealed to this machine, which only it opens, handed in sealed to the vault's key for the call.
if p.MovesIntoTheVault {
+1 -1
View File
@@ -38,7 +38,7 @@ accounts {
subscribe: { allow: ["_INBOX.enrol.one.>"] }
} }
{ user: "node.one", password: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn", permissions: {
publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.CREATE.KV_composed-one.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.one.>", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-one.*.$O.declaration-files-one.C.*", "$JS.API.CONSUMER.DELETE.KV_composed-one.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_wants.>", "$JS.API.CONSUMER.DELETE.KV_node-engine_have.>", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-one.*", "$JS.API.CONSUMER.INFO.NODES.one", "$JS.API.DIRECT.GET.KV_composed-one.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.one.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-one.$O.declaration-files-one.M.*", "$JS.API.STREAM.INFO.KV_composed-one", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_wants", "$JS.API.STREAM.INFO.KV_node-engine_have", "$JS.FC.KV_composed-one.>", "$JS.FC.KV_mesh-delivery-assigner_wants.>", "$JS.FC.KV_node-engine_have.>", "$JS.FC.OBJ_declaration-files-one.>", "$KV.node-engine_have.one.>", "$SRV.PING.node-tools.one", "mesh.control.one.>"] }
publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.CREATE.KV_composed-one.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.one.>", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-one.*.$O.declaration-files-one.C.*", "$JS.API.CONSUMER.DELETE.KV_composed-one.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_wants.>", "$JS.API.CONSUMER.DELETE.KV_node-engine_have.>", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-one.*", "$JS.API.CONSUMER.INFO.NODES.one", "$JS.API.DIRECT.GET.KV_composed-one.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.one.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-one.$O.declaration-files-one.M.*", "$JS.API.STREAM.INFO.KV_composed-one", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_wants", "$JS.API.STREAM.INFO.KV_node-engine_have", "$JS.FC.KV_composed-one.>", "$JS.FC.KV_mesh-delivery-assigner_wants.>", "$JS.FC.KV_node-engine_have.>", "$JS.FC.OBJ_declaration-files-one.>", "$KV.node-engine_have.one.>", "$SRV.PING.node-tools.one", "mesh.control.one.>", "mesh.seat.node-backup.call.backed-up.one.node~one", "mesh.seat.node-backup.call.now.one.node~one", "mesh.seat.node-backup.tool.backed-up.one", "mesh.seat.node-backup.tool.now.one"] }
subscribe: { allow: ["_DELIVER.one", "_DELIVER.one.>", "_INBOX.node.one.>", "mesh.node.one.ask.hand-over", "mesh.node.one.ask.judge-directories", "mesh.node.one.ask.move-secret", "mesh.node.one.ask.report", "mesh.node.one.ask.setuid-search", "mesh.node.one.declare"] }
allow_responses: { max: 1, ttl: "1m" }
} }