The assigner's state and verb placement are assignments, with its events, tools, views and every caller; the assigner copies the retired state at its start (issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:13:20 +01:00
parent 369ebd092d
commit 0eafb2fe95
164 changed files with 1699 additions and 1498 deletions
@@ -6,6 +6,7 @@ import (
"context"
"encoding/json"
"errors"
"maps"
"os"
"path/filepath"
"slices"
@@ -19,10 +20,10 @@ import (
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
@@ -114,10 +115,12 @@ func TestTheGrantsPartWhole(t *testing.T) {
t.Fatal(err)
}
manifest(grants.Module, string(own))
pl, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "nats"}, {Module: "mod-c"},
values, _ := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "nats"}, {Module: "mod-c"},
{Module: grants.Module}}})
if _, err := kvs[grants.ReadPlacement].Put(ctx, "placement.a", pl); err != nil {
t.Fatal(err)
for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key
if _, err := kvs[grants.ReadAssignments].Put(ctx, k, values[k]); err != nil {
t.Fatal(err)
}
}
busKey, _ := seal.GenerateKeyPair()
put(grants.ReadHave, "a.node", map[string]any{"v": 1})
@@ -241,7 +244,7 @@ func TestTheGrantsPartWhole(t *testing.T) {
}
until("every first view asked and refused once", func() bool { return refusedAsks.Load() >= int32(len(grants.FirstViews)) })
until("the part saying it waits on the indexer and the assigner", func() bool {
return logged.has("mesh-delivery-indexer.manifests") && logged.has("mesh-delivery-assigner.placement") && logged.has("is not answered")
return logged.has("mesh-delivery-indexer.manifests") && logged.has("mesh-delivery-assigner.assignments") && logged.has("is not answered")
})
if _, err := kvs[grants.Module+"."+grants.StateMemberships].Get(ctx, "a.mod-c"); err == nil {
t.Error("a membership composed before the owners answered its first views")
+1 -1
View File
@@ -1,5 +1,5 @@
// Command mesh-grants is the grants part (novox/hq ADR 0547 clauses 5 to 8, issue 1945): a process on its own bus
// credential that watches placement, the assigner's wants, the indexer's manifests, the builder's builds, mesh-config's
// credential that watches assignments, the assigner's wants, the indexer's manifests, the builder's builds, mesh-config's
// resolved values and the node agents' haves, and on every change composes each assignment's membership, the bus's user
// list and each manifest's buckets, logs and streams with the bus composer the controller uses (mesh-sdk buscompose).
//
+5 -5
View File
@@ -175,7 +175,7 @@ func toolsOf(p *grants.Part) []stdio.Tool {
}},
{Name: grants.ToolUserList, Description: "The bus's user list (ADR 0547 clauses 6 and 8): the list the grants part " +
"composed (digest, revision, users), what the bus node says it loaded (source, revision, digest, users, error), and how " +
"they differ: users added, users dropped (and of those, gone from placement), each user's grant differences, the " +
"they differ: users added, users dropped (and of those, gone from assignments), each user's grant differences, the " +
"assignments left out, every input the bus does not carry, named; and per user whether the vault holds its hash " +
"(mesh-vault.bus-accounts, ADR 0547 clause 9) and how its line would differ from the loaded one (same, adds, left-out " +
"for want of a credential, loses-login), never a hash. Read-only.",
@@ -196,7 +196,7 @@ func toolsOf(p *grants.Part) []stdio.Tool {
Input: schema(map[string]string{"module": "the module"}, "module"),
Run: func(a map[string]any) (any, error) { return p.AssertOne(grants.Answer(a, "module")) }},
{Name: grants.ToolAccounts, Description: "The bus accounts the mesh wants (novox/hq ADR 0561 §2, §9): every account the vault " +
"mints that placement composes (a module's, a process's, a machine's provisioner client's), as the grants part last " +
"mints that assignments composes (a module's, a process's, a machine's provisioner client's), as the grants part last " +
"wrote it to its state accounts, which the vault alone reads: user, kind, node, module, process, the bus's address and " +
"fingerprint, a module's claims with the verbs each seat promises, and when the key was written; one user's or all; " +
"what the last pass wrote, deleted or kept; and the durable consumers the accounts need as the last pass found them " +
@@ -232,15 +232,15 @@ func toolsOf(p *grants.Part) []stdio.Tool {
return p.AcceptHashes(stdio.CallerOf(a), users, grants.Answer(a, "why"))
}},
{Name: grants.ToolFirstViews, Description: "The grants part's first views of the owners' states (novox/hq ADR 0615 §4 to §6, " +
"ADR 0622): the assigner's placement and wants, the indexer's manifests, modules and verdicts, each with its owner, tool " +
"ADR 0622): the assigner's assignments and wants, the indexer's manifests, modules and verdicts, each with its owner, tool " +
"and the revision its picture holds, when it asked and when it was answered, how many asks and hand acts, and whether " +
"its condition is raised, as the part's process last kept them. Read-only.",
Run: func(map[string]any) (any, error) { return p.FirstViewsOf() }},
{Name: grants.ToolFirstViewAgain, Description: "Ask one of the grants part's first views once more now (placement, wants, " +
{Name: grants.ToolFirstViewAgain, Description: "Ask one of the grants part's first views once more now (assignments, wants, " +
"manifests, modules or verdicts; none for all), by a person's act with why, recorded as a hand act: the step after an " +
"owner's state was written again from its sources (an empty answer at revision 0, condition " +
"mesh-grants.first-view-empty). Asks the part's process, which alone follows them; grants_first_views shows the answer.",
Input: schema(map[string]string{"state": "placement, wants, manifests, modules or verdicts; none for all", "why": "why, kept in the hand act"}, "why"),
Input: schema(map[string]string{"state": "assignments, wants, manifests, modules or verdicts; none for all", "why": "why, kept in the hand act"}, "why"),
Run: func(a map[string]any) (any, error) {
return p.AskFirstViewAgain(stdio.CallerOf(a), grants.Answer(a, "state"), grants.Answer(a, "why"), recordHandAct)
}},
+4 -4
View File
@@ -10,9 +10,9 @@ require (
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
@@ -47,7 +47,7 @@ require (
require (
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
+90 -92
View File
@@ -22,17 +22,17 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
@@ -339,12 +339,9 @@ func (b *fakeBus) refuse(module, raw, problem string) {
Problems: []string{problem}})
}
// assign places module on node in the seat's placement, the one place the part reads it (ADR 0549 §5; issue 774).
// assign places module on node in the seat's assignments, the one place the part reads it (ADR 0549 §5; issue 774).
func (b *fakeBus) assign(node, module string) {
var pl meshmoduleassigner.Placement
if raw, ok := b.states[grants.ReadPlacement]["placement."+node]; ok {
pl, _ = meshmoduleassignerclient.DecodePlacement(raw)
}
pl := b.assignmentsOf(node)
pl.Node = node
for _, m := range pl.Modules {
if m.Module == module {
@@ -352,25 +349,37 @@ func (b *fakeBus) assign(node, module string) {
}
}
pl.Modules = append(pl.Modules, meshmoduleassigner.PlacedModule{Module: module})
raw, err := meshmoduleassignerclient.EncodePlacement(pl)
b.setAssignments(pl)
}
// unassign takes module off node in the seat's assignments.
func (b *fakeBus) unassign(node, module string) {
pl := b.assignmentsOf(node)
pl.Modules = slices.DeleteFunc(pl.Modules, func(m meshmoduleassigner.PlacedModule) bool { return m.Module == module })
b.setAssignments(pl)
}
// assignmentsOf is what the seat's state holds of node, assembled from its keys.
func (b *fakeBus) assignmentsOf(node string) meshmoduleassigner.Assignments {
got, _ := meshmoduleassignerclient.AssignmentsOf(b.states[grants.ReadAssignments])
return got[node]
}
// setAssignments holds a node's assignments in the seat's state as its keys: a key per module, and the node key.
func (b *fakeBus) setAssignments(pl meshmoduleassigner.Assignments) {
if b.states[grants.ReadAssignments] == nil {
b.states[grants.ReadAssignments] = map[string][]byte{}
}
for key := range b.states[grants.ReadAssignments] {
if strings.HasPrefix(key, pl.Node+".") {
delete(b.states[grants.ReadAssignments], key)
}
}
values, err := meshmoduleassignerclient.AssignmentsValues(pl)
if err != nil {
panic(err)
}
if b.states[grants.ReadPlacement] == nil {
b.states[grants.ReadPlacement] = map[string][]byte{}
}
b.states[grants.ReadPlacement]["placement."+node] = raw
}
// unassign takes module off node in the seat's placement.
func (b *fakeBus) unassign(node, module string) {
pl, err := meshmoduleassignerclient.DecodePlacement(b.states[grants.ReadPlacement]["placement."+node])
if err != nil {
return
}
pl.Modules = slices.DeleteFunc(pl.Modules, func(m meshmoduleassigner.PlacedModule) bool { return m.Module == module })
raw, _ := meshmoduleassignerclient.EncodePlacement(pl)
b.states[grants.ReadPlacement]["placement."+node] = raw
maps.Copy(b.states[grants.ReadAssignments], values)
}
const (
@@ -576,8 +585,8 @@ func TestGrants(t *testing.T) {
key, _ := builds.BuilderBuildKey("mod-x", fp)
b.put(grants.ReadBuilds, key, map[string]any{"v": keys.Version, "state": "done", "manifest": digest})
}, []string{"a.mod-x", "a.nats", "b.mod-y", "node.a", "node.b"}, "people"},
{"placement refused is a gap; the machines come from the haves", func(b *fakeBus) { b.refused[grants.ReadPlacement] = true },
[]string{"node.a", "node.b"}, grants.ReadPlacement},
{"assignments refused is a gap; the machines come from the haves", func(b *fakeBus) { b.refused[grants.ReadAssignments] = true },
[]string{"node.a", "node.b"}, grants.ReadAssignments},
} {
t.Run(row.name, func(t *testing.T) {
b := fixture()
@@ -644,7 +653,7 @@ func TestGrants(t *testing.T) {
}
})
}
// ADR 0567 §3, §9: the seat mesh-placement's candidates are read by every node agent (mesh-sdk NodeComposeReads)
// ADR 0567 §3, §9: the seat mesh-module-assigner's candidates are read by every node agent (mesh-sdk NodeComposeReads)
// and by a module whose manifest reads them (the view), written by their holder, the assigner, and read by no other.
cands := fixture()
cands.manifestOf("mesh-delivery-assigner", `{"module":"mesh-delivery-assigner","version":"1","own-secrets":{"broker":"/var/lib/as/broker"},"state":[{"name":"candidates","history":5}]}`)
@@ -655,12 +664,12 @@ func TestGrants(t *testing.T) {
}
var readers []string
for _, u := range shadowOf(t, cands) {
if slices.Contains(u.Publish, "$JS.API.DIRECT.GET.KV_"+buscompose.PlacementCandidatesBucket+".>") {
if slices.Contains(u.Publish, "$JS.API.DIRECT.GET.KV_"+buscompose.AssignmentsCandidatesBucket+".>") {
readers = append(readers, u.User)
}
}
if want := []string{"a.mesh-delivery-assigner", "a.mesh-delivery-view", "node.a", "node.b"}; !slices.Equal(readers, want) {
t.Errorf("the placement candidates are read by %v, want the node agents, the view and their holder %v", readers, want)
t.Errorf("the assignments candidates are read by %v, want the node agents, the view and their holder %v", readers, want)
}
b := fixture()
users := shadowOf(t, b)
@@ -880,7 +889,7 @@ func TestGrants(t *testing.T) {
recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{}))
grants.WithRootFree(&recs, b)
kept := false
for _, k := range buscompose.PlacementsOf(recs, recs.Interchangeable).Kinds {
for _, k := range buscompose.AssignmentsOf(recs, recs.Interchangeable).Kinds {
kept = kept || slices.Contains(k.Capabilities, "verified-sender")
}
if kept != row.keeps {
@@ -1134,7 +1143,7 @@ func TestGrants(t *testing.T) {
{"equal", shadow, nil, nil, nil, nil, true, nil, nil, 0},
{"one user the bus lacks", shadow[1:], []string{shadow[0].User}, nil, nil, nil, false, nil, []string{shadow[0].User}, 0},
{"one loaded user still placed", append(slices.Clone(shadow), grants.User{User: "a.mod-x.worker"}), nil, []string{"a.mod-x.worker"}, nil, nil, false, nil, []string{"a.mod-x.worker"}, 0},
{"one loaded user placement no longer names", append(slices.Clone(shadow), grants.User{User: "b.gone"}), nil, []string{"b.gone"}, []string{"b.gone"}, nil, true, nil, nil, 0},
{"one loaded user assignments no longer names", append(slices.Clone(shadow), grants.User{User: "b.gone"}), nil, []string{"b.gone"}, []string{"b.gone"}, nil, true, nil, nil, 0},
{"grants differ", changed, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1},
{"only a right added", fewer, nil, nil, nil, []string{"a.mod-x"}, false, nil, nil, 1},
{"a deny added refuses", readable, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1},
@@ -1196,9 +1205,9 @@ func TestGrants(t *testing.T) {
}
})
// 1126 (ADR 0549 §1, §3): once the catalogue places the nodes, a seat held once for the mesh is held where placement
// says, not on every machine running a module that claims it; what placement adds is composed beside the assignments.
t.Run("a mesh seat is held where placement says", func(t *testing.T) {
// 1126 (ADR 0549 §1, §3): once the catalogue places the nodes, a seat held once for the mesh is held where assignments
// says, not on every machine running a module that claims it; what assignments adds is composed beside the assignments.
t.Run("a mesh seat is held where assignments says", func(t *testing.T) {
store := catalogue.Manifest{Module: "store", Version: "1",
DefinesSeats: []catalogue.SeatDeclaration{{Name: "store-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "databases"}}}},
Claims: []catalogue.Claim{{Name: "store-seat", Scope: catalogue.ScopeMesh}}}
@@ -1212,51 +1221,41 @@ func TestGrants(t *testing.T) {
return n
}
if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 {
t.Fatalf("before placement the seat is held on a %d and b %d times, want both", holds(recs, "a"), holds(recs, "b"))
t.Fatalf("before assignments the seat is held on a %d and b %d times, want both", holds(recs, "a"), holds(recs, "b"))
}
in.Placed = map[string]meshmoduleassigner.Placement{
in.Placed = map[string]meshmoduleassigner.Assignments{
"a": {Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}}, Holds: []meshmoduleassigner.Holding{{Claim: "store-seat", Scope: "mesh", Module: "store"}}},
"b": {Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}}}}
if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 0 {
t.Errorf("placed, the seat is held on a %d and b %d times, want a alone", holds(recs, "a"), holds(recs, "b"))
}
b := fixture()
pl, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}})
b.states[grants.ReadPlacement] = map[string][]byte{"placement.b": pl}
b.setAssignments(meshmoduleassigner.Assignments{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}})
read := grants.ReadInputs(b, &grants.Manifests{})
if !slices.Contains(read.Assigned["b"], "mod-x") || read.Placed["b"].Node != "b" || slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == "seat holdings" }) {
t.Errorf("read: assigned %v, placed %v, gaps %v", read.Assigned, read.Placed, read.Gaps)
}
// Research 072 row 37: a node key and a key per module, the legacy mirror left out, read the same.
perModule, _ := meshmoduleassignerclient.PlacementValues(meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}})
delete(perModule, "placement.b")
nb := fixture()
nb.states[grants.ReadPlacement] = perModule
if got := grants.ReadInputs(nb, &grants.Manifests{}); !slices.Equal(meshmoduleassignerclient.Assigned(got.Placed["b"]), []string{"mod-y", "mod-x"}) ||
slices.ContainsFunc(got.Gaps, func(g grants.Gap) bool { return strings.HasPrefix(g.Input, grants.ReadPlacement) }) {
t.Errorf("read of the keys per module: placed %v, gaps %v", got.Placed, got.Gaps)
}
b.refused[grants.ReadPlacement] = true
// The placement alone places (issue 774, 774/S29): not read, nothing is placed, and it is a failed read.
if read := grants.ReadInputs(b, &grants.Manifests{}); !slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadPlacement && g.Failed }) ||
b.refused[grants.ReadAssignments] = true
// The assignments alone places (issue 774, 774/S29): not read, nothing is placed, and it is a failed read.
if read := grants.ReadInputs(b, &grants.Manifests{}); !slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadAssignments && g.Failed }) ||
len(read.Assigned) != 0 {
t.Errorf("placement not on the bus is no failed read, or something is placed: %v, %v", read.Gaps, read.Assigned)
t.Errorf("assignments not on the bus is no failed read, or something is placed: %v, %v", read.Gaps, read.Assigned)
}
// Issue 1945, the user list of 2026-10-27 09:21: a placement's holds are the holders on record, not every holder.
// A mesh seat no placement holds keeps the module claiming it as its holder, wherever placement puts it.
// Issue 1945, the user list of 2026-10-27 09:21: the assignments' holds are the holders on record, not every holder.
// A mesh seat no assignments holds keeps the module claiming it as its holder, wherever assignments puts it.
in.Manifests["other"] = catalogue.Manifest{Module: "other", Version: "1", Claims: []catalogue.Claim{{Name: "other-seat", Scope: catalogue.ScopeMesh}},
DefinesSeats: []catalogue.SeatDeclaration{{Name: "other-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "v"}}}}}
in.Assigned["b"] = append(in.Assigned["b"], "other")
in.Placed["b"] = meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}}}
in.Placed["b"] = meshmoduleassigner.Assignments{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}}}
if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 {
t.Errorf("a seat no placement holds is held on b %d times, want once (and store-seat on a alone: a %d)", holds(recs, "b"), holds(recs, "a"))
t.Errorf("a seat no assignments holds is held on b %d times, want once (and store-seat on a alone: a %d)", holds(recs, "b"), holds(recs, "a"))
}
in.Placed["b"] = meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}},
in.Placed["b"] = meshmoduleassigner.Assignments{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}},
Holds: []meshmoduleassigner.Holding{{Claim: "other-seat", Scope: "mesh", Module: "someone-else"}}}
if recs, _ := grants.Records(in); holds(recs, "b") != 0 {
t.Errorf("a seat placement holds by another module is still held by other on b")
t.Errorf("a seat assignments holds by another module is still held by other on b")
}
// The live shapes: the seed's novox placement (mesh-catalog 0c61bad, its twelve holdings, without
// The live shapes: the seed's novox assignments (mesh-catalog 0c61bad, its twelve holdings, without
// anthropic-licence-manager), the licence manager's manifest (mesh-catalog c05aa5e6), and the subjects the bus
// grants novox.node-tools for its seat (nats_users, 2026-10-27 19:40Z).
file := func(name string, v any) {
@@ -1268,9 +1267,9 @@ func TestGrants(t *testing.T) {
t.Fatal(err)
}
}
var seed meshmoduleassigner.Placement
var seed meshmoduleassigner.Assignments
var live struct{ Users []grants.User }
file("placement-novox-0c61bad.json", &seed)
file("assignments-novox-0c61bad.json", &seed)
file("node-tools-novox-licence-subjects.json", &live)
licence, _ := os.ReadFile(filepath.Join("testdata", "claude-licence-manager.json"))
n := newFakeBus()
@@ -1285,14 +1284,13 @@ func TestGrants(t *testing.T) {
n.assign("novox", "claude-licence-manager")
n.manifestOf("node-tools", `{"module":"node-tools","version":"1","tool-runner":true}`) // the machine's tool runner, which carries the seat
n.assign("novox", "node-tools")
// The live placement names mesh-host; with its manifest's state the runner once carried it and the whole list was
// The live assignments names mesh-host; with its manifest's state the runner once carried it and the whole list was
// refused as a second writer of the engine's health (issue 774, 2026-10-30): the shadow below now composes, with the states the
// engine's manifest declares today (health, findings, data, packages): the findings state, written by each node's engine
// alone (ADR 0636 §1), does not bring the refusal back, and the runner is granted none of them.
n.manifestOf(buscompose.EngineModule, `{"module":"mesh-host","version":"1","consumes":["restic.backed-up"],"state":[{"name":"health"},{"name":"findings"},{"name":"data"},{"name":"packages"}]}`)
n.put(grants.ReadHave, "novox.node", map[string]any{"v": 1})
pl, _ = meshmoduleassignerclient.EncodePlacement(seed)
n.states[grants.ReadPlacement] = map[string][]byte{"placement.novox": pl}
n.setAssignments(seed)
recs, _ := grants.Records(grants.ReadInputs(n, &grants.Manifests{}))
var tools []buscompose.ProcessPrincipal
for _, d := range recs.Assigned["novox"] {
@@ -1305,7 +1303,7 @@ func TestGrants(t *testing.T) {
t.Errorf("the bus module's principals = %+v, want the keeper keeping and nats-tools carrying both removals", tools)
}
if m := grants.Memberships(recs)["novox.claude-licence-manager"]; len(m.Membership.Seats) == 0 {
t.Errorf("the licence manager's membership under the seeded placement carries no seats: %+v", m.Membership)
t.Errorf("the licence manager's membership under the seeded assignments carries no seats: %+v", m.Membership)
}
var runtime grants.User
for _, u := range shadowOf(t, n) {
@@ -1318,7 +1316,7 @@ func TestGrants(t *testing.T) {
}
for _, s := range live.Users[0].Subscribe {
if strings.Contains(s, ".call.") && !slices.Contains(runtime.Subscribe, s) {
t.Errorf("under the seeded placement novox.node-tools is not granted %s, which the bus grants it today", s)
t.Errorf("under the seeded assignments novox.node-tools is not granted %s, which the bus grants it today", s)
}
}
})
@@ -1448,7 +1446,7 @@ func TestGrants(t *testing.T) {
moved := p.Compose()
if !moved.Written || moved.Revision != 2 || !slices.Equal(moved.Published, []string{"a.mod-x", "b.mod-x"}) || len(moved.NotPublished) != 0 || moved.UserList.Compare.Equal() ||
!slices.Equal(moved.UserList.Compare.Added, []string{"b.mod-x"}) {
t.Errorf("a composition after placement moved = %+v", moved)
t.Errorf("a composition after assignments moved = %+v", moved)
}
got, _ = p.UserList()
ul, _ := got.(map[string]any)
@@ -1483,10 +1481,10 @@ func TestGrants(t *testing.T) {
var said []oc.Condition
p.Say = func(c oc.Condition) error { said = append(said, c); return nil }
// settled is the placement of the lists agreeing, every hash held, and the loaded list as edit leaves it.
// settled is the assignments of the lists agreeing, every hash held, and the loaded list as edit leaves it.
settled := func(edit func(modX *grants.User)) func() {
return func() {
b.states[grants.ReadPlacement] = nil
b.states[grants.ReadAssignments] = nil
b.assign("a", "nats")
b.assign("a", "mod-x")
b.assign("b", "mod-y")
@@ -1981,11 +1979,11 @@ func TestGrants(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() { f.Run(ctx); close(done) }()
waitOpened(t, w, grants.ReadPlacement)
waitOpened(t, w, grants.ReadAssignments)
if r := f.Refused(); r[grants.ReadModules] == "" {
waitFor(t, func() bool { return f.Refused()[grants.ReadModules] != "" })
}
w.change(grants.ReadPlacement, "placement.a")
w.change(grants.ReadAssignments, "a.node")
waitOpened(t, w, grants.ReadModules)
waitFor(t, func() bool { return len(f.Refused()) == 0 })
select {
@@ -2199,11 +2197,11 @@ func TestGrants(t *testing.T) {
}
})
// 774/S29/AC1 (ADR 0549 §5, ADR 0571 §10): the memberships composed from the placement alone, with no read of the
// controller, are the ones the union of placement and assignments gave on the same input: the fixture's (two nodes,
// 774/S29/AC1 (ADR 0549 §5, ADR 0571 §10): the memberships composed from the assignments alone, with no read of the
// controller, are the ones the union of assignments and assignments gave on the same input: the fixture's (two nodes,
// the bus and mod-x on a, mod-y on b, as placed on the live mesh's own shape of nats beside its modules), whose
// assignments named the same modules. Neither the controller's assignments nor its condition events are read.
t.Run("placement alone places, with the memberships the union gave", func(t *testing.T) {
t.Run("assignments alone places, with the memberships the union gave", func(t *testing.T) {
b := fixture()
p := partOn(b)
p.Compose()
@@ -2211,7 +2209,7 @@ func TestGrants(t *testing.T) {
for key := range want {
raw, _, found, _ := b.ownGet(grants.StateMemberships, key)
if !found {
t.Fatalf("no membership of %s from the placement", key)
t.Fatalf("no membership of %s from the assignments", key)
}
want[key] = string(raw)
}
@@ -2379,9 +2377,9 @@ func TestGrants(t *testing.T) {
b.manifestOf("mesh-delivery-indexer", `{"module":"mesh-delivery-indexer","version":"1","own-secrets":{"broker":"/var/lib/i/broker"},`+
`"emits":["views.manifests","views.modules"],"streams":[{"name":"indexer-views","events":["views.*"],"max-age-days":7,"max-mib":256}]}`)
b.manifestOf("mesh-delivery-assigner", `{"module":"mesh-delivery-assigner","version":"1","own-secrets":{"broker":"/var/lib/as/broker"},`+
`"emits":["views.placement","views.seat-holders"],"streams":[{"name":"assigner-views","events":["views.*"],"max-age-days":7,"max-mib":256}]}`)
`"emits":["views.assignments","views.seat-holders"],"streams":[{"name":"assigner-views","events":["views.*"],"max-age-days":7,"max-mib":256}]}`)
b.manifestOf("node-tools", `{"module":"node-tools","version":"1","tool-runner":true,"own-secrets":{"broker":"/var/lib/nt/broker"},`+
`"consumes":["mesh-delivery-assigner.views.placement","mesh-delivery-assigner.views.seat-holders","mesh-delivery-indexer.views.manifests",`+
`"consumes":["mesh-delivery-assigner.views.assignments","mesh-delivery-assigner.views.seat-holders","mesh-delivery-indexer.views.manifests",`+
`"mesh-delivery-indexer.views.modules"],"state":[{"name":"findings","history":5}]}`)
for _, m := range []string{"mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools"} {
b.assign("a", m)
@@ -2422,7 +2420,7 @@ func TestGrants(t *testing.T) {
if fv.Tool != tool {
continue
}
if empty.Load() && fv.State == "placement" {
if empty.Load() && fv.State == "assignments" {
return views.Answer{Revision: 0, Created: created, Values: map[string]json.RawMessage{}}, nil
}
values := map[string]json.RawMessage{}
@@ -2484,37 +2482,37 @@ func TestGrants(t *testing.T) {
if !raised(grants.FirstViewUnanswered+".mesh-delivery-indexer.verdicts", occ.StateRaised) {
t.Fatalf("the refused raise was not said again at the pass: %+v", said)
}
if !slices.ContainsFunc(before.UserList.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadPlacement && g.Failed }) {
t.Fatalf("placement answered empty at revision 0 was not a failed read: %+v", before.UserList.Gaps)
if !slices.ContainsFunc(before.UserList.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadAssignments && g.Failed }) {
t.Fatalf("assignments answered empty at revision 0 was not a failed read: %+v", before.UserList.Gaps)
}
if !raised(grants.FirstViewEmpty+".mesh-delivery-assigner.placement", occ.StateRaised) {
if !raised(grants.FirstViewEmpty+".mesh-delivery-assigner.assignments", occ.StateRaised) {
t.Fatalf("no first-view-empty: %+v", said)
}
empty.Store(false)
down.Store(false)
if _, err := fv.Again(ctx, "placement", "the assigner composed its wants again"); err != nil {
if _, err := fv.Again(ctx, "assignments", "the assigner composed its wants again"); err != nil {
t.Fatal(err)
}
waitFor(t, func() bool { return fv.Followers[grants.ReadPlacement].Status().Revision == 7 })
waitFor(t, func() bool { return fv.Followers[grants.ReadAssignments].Status().Revision == 7 })
waitFor(t, func() bool {
return raised(grants.FirstViewUnanswered+".mesh-delivery-indexer.verdicts", occ.StateCleared)
})
after := p.Compose()
if slices.ContainsFunc(after.UserList.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadPlacement }) ||
!raised(grants.FirstViewEmpty+".mesh-delivery-assigner.placement", occ.StateCleared) {
if slices.ContainsFunc(after.UserList.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadAssignments }) ||
!raised(grants.FirstViewEmpty+".mesh-delivery-assigner.assignments", occ.StateCleared) {
t.Fatalf("after an answer above 0: gaps %+v, said %+v", after.UserList.Gaps, said)
}
if after.UserList.Users != before.UserList.Users && before.UserList.Users > after.UserList.Users {
t.Errorf("the empty first view dropped users: %d then %d", before.UserList.Users, after.UserList.Users)
}
// An event of the owner's views family changes the picture with no ask.
asksBefore := fv.Followers[grants.ReadPlacement].Status().Asks
asksBefore := fv.Followers[grants.ReadAssignments].Status().Asks
raw, _ := json.Marshal(map[string]any{"v": 1, "node": "c"})
if gap, why := fv.Followers[grants.ReadPlacement].Apply(views.Event{V: 1, Owner: grants.AssignerModule, State: "placement",
Key: "placement.c", Revision: 8, Created: created, Value: raw}); gap {
if gap, why := fv.Followers[grants.ReadAssignments].Apply(views.Event{V: 1, Owner: grants.AssignerModule, State: "assignments",
Key: "c.node", Revision: 8, Created: created, Value: raw}); gap {
t.Fatalf("a next revision was a gap: %s", why)
}
if _, found, _ := fv.Get(grants.ReadPlacement, "placement.c"); !found || fv.Followers[grants.ReadPlacement].Status().Asks != asksBefore {
if _, found, _ := fv.Get(grants.ReadAssignments, "c.node"); !found || fv.Followers[grants.ReadAssignments].Status().Asks != asksBefore {
t.Error("the event did not reach the picture, or asked")
}
// The verdicts follow the indexer's manifest-accepted and manifest-refused at the revision each carries (ADR 0622 §2):
@@ -2577,9 +2575,9 @@ func TestGrants(t *testing.T) {
})
// 774/AC4 (novox/hq ADR 0561 §2): every account the vault mints that the records compose is written to the state
// accounts once, with the bus's address and a module's claims, read by the vault alone; a key is deleted when placement
// accounts once, with the bus's address and a module's claims, read by the vault alone; a key is deleted when assignments
// no longer names its user, and never in a pass whose reads failed.
t.Run("wanted accounts: written once each, read by the vault alone, retired only when placement drops them", func(t *testing.T) {
t.Run("wanted accounts: written once each, read by the vault alone, retired only when assignments drops them", func(t *testing.T) {
b := fixture()
b.loaded = shadowOf(t, b)
p := partOn(b)
@@ -2613,7 +2611,7 @@ func TestGrants(t *testing.T) {
if !slices.Equal(readers, []string{"mesh-vault"}) || buscompose.ReaderMay("mesh-delivery-view", buscompose.GrantsAccounts) {
t.Errorf("the wanted accounts are read by %v", readers)
}
// A read that fails retires nothing; once read, the user placement dropped is retired.
// A read that fails retires nothing; once read, the user assignments dropped is retired.
b.unassign("b", "mod-y")
b.refused[grants.ReadResolved] = true
if res := p.Compose(); res.Accounts == nil || len(res.Accounts.Deleted) != 0 || b.own[grants.StateAccounts]["b.mod-y"] == nil {
@@ -2621,7 +2619,7 @@ func TestGrants(t *testing.T) {
}
delete(b.refused, grants.ReadResolved)
if res := p.Compose(); res.Accounts == nil || !slices.Equal(res.Accounts.Deleted, []string{"b.mod-y"}) {
t.Fatalf("the user placement dropped is not retired: %+v", res.Accounts)
t.Fatalf("the user assignments dropped is not retired: %+v", res.Accounts)
}
// The tool reads them, one and all, each with when it was written.
one, err := p.Accounts("a.nats")
@@ -8,7 +8,7 @@ import (
// **The accounts the grants part wants, and the one shape a held account takes** (novox/hq ADR 0561 §1, §2; issue 774).
//
// The bus composer derives every user from placement, the manifests and the wants. For each account the mesh mints (a
// The bus composer derives every user from assignments, the manifests and the wants. For each account the mesh mints (a
// module's, a module's process's, a machine's provisioner client's), the grants part writes one key of its state
// `accounts`, keyed by the bus user, holding the account's fields that are not secret: what the controller's credential
// carries today beside the password. The vault watches that state, mints the user it holds no value for, and keeps the
@@ -12,8 +12,8 @@ import (
"time"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
@@ -57,17 +57,17 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
t.Run("every node's compositions are read by their named readers alone and written by their node", everyNodesCompositions)
t.Run("a module's ledger is written by its ledger account alone", aLedgerIsWrittenByItsLedgerAccountAlone)
t.Run("the bus module's process alone keeps every module's bus state and serves the broker's verbs", theBusModulesProcessKeepsBusState)
// ADR 0567 §3, §9: a node reads every key of the seat's placement and its candidates (it composes with the whole
// ADR 0567 §3, §9: a node reads every key of the seat's assignments and its candidates (it composes with the whole
// proposal laid over), and none of its open proposals; it writes neither.
reads := strings.Join(NodeComposeReads("laptop"), " ")
for bucket, granted := range map[string]bool{PlacementBucket: true, PlacementCandidatesBucket: true,
PlacementProposalsBucket: false} {
for bucket, granted := range map[string]bool{AssignmentsBucket: true, AssignmentsCandidatesBucket: true,
AssignmentsProposalsBucket: false} {
if strings.Contains(reads, "$JS.API.DIRECT.GET.KV_"+bucket+".>") != granted || strings.Contains(reads, "$KV."+bucket+".laptop") {
t.Errorf("a node is granted %s: %v, want %v", bucket, !granted, granted)
}
}
// It hears the creation of those two buckets, and of no other.
if got := NodeComposeAdvisories(); len(got) != 2 || got[0] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_placement" ||
if got := NodeComposeAdvisories(); len(got) != 2 || got[0] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_assignments" ||
got[1] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_candidates" {
t.Errorf("a node hears the creation of %v", got)
}
@@ -513,11 +513,11 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
vault := ClaimedSeat{Seat: "mesh-vault", Scope: "mesh", Serves: []string{"get", "accept", "secret-ask", "issued",
"rotate", "move-in", "node-key", "own", "move-asks"}}
controller := ClaimedSeat{Seat: "mesh-controller", Scope: "mesh", Serves: strings.Split("tools,calls,status,nodes,"+
"node,placement-export,modules,seats,builds,plans,delivery-plan,delivery-order,delivery-check,deliver,"+
"node,assignments-export,modules,seats,builds,plans,delivery-plan,delivery-order,delivery-check,deliver,"+
"delivery-stop,delivery-backups-again,delivery-walks,plan,assign,unassign,pin,unpin,push,rotate,give,issued,"+
"secret-move,secret-ask,issue,token,settings,command,queue,cancel,clear,replay,try,kill,pause,resume,hand-act,"+
"drill,warranted,hand-acts,durations,conditions,healers,doctor,upgrade,bus,retire,cleanup,judge-directories,"+
"root-free,dead-letters,data,build,artifacts,held,collect,move-placement,move-config,mirrors,images", ",")}
"root-free,dead-letters,data,build,artifacts,held,collect,move-assignments,move-config,mirrors,images", ",")}
at := "nats://10.0.0.1:4222"
wanted := map[string]struct {
w WantedAccount
@@ -790,7 +790,7 @@ func healthChecks(t *testing.T) {
}
// ADR 0571 clause 1: a read naming another module's log is issued to the reader as a log it reads, never writes.
if m := MembershipFor("a", Declared{Module: IndexerModule, Reads: []string{builds.BuildLogRead, "x.y"}},
Placements{Logs: map[string]bool{builds.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes ||
Assignments{Logs: map[string]bool{builds.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes ||
m.Logs[0].Stream != "LOG_mesh-delivery-builder_build-log" || m.Logs[0].Name != builds.BuildLogRead {
t.Errorf("the indexer's membership issues the logs %+v", m.Logs)
}
@@ -873,7 +873,7 @@ func healthChecks(t *testing.T) {
{"a flat event of a node seat held by a module is said flat", Declared{Module: "pause", Holds: []Seat{{Name: "node-power",
Scope: "node", Emits: []string{"slept"}}}}, "mesh.seat.node-power.event.slept", true},
} {
m := MembershipFor("a", c.d, Placements{})
m := MembershipFor("a", c.d, Assignments{})
listed := m.SeatTraffic != nil && slices.ContainsFunc(m.SeatTraffic.Publish, func(p string) bool { return p == c.subject })
perms, err := PermissionsFor(Principal{Kind: KindNodeTools, Node: "a", Carries: []Declared{c.d}})
if err != nil {
@@ -1019,7 +1019,7 @@ func everyNodesCompositions(t *testing.T) {
!MayPublish(runner, "$JS.API.DIRECT.GET.KV_composed-two.$KV.composed-two.two.node") {
t.Errorf("the runtime carrying the provisioner cannot read every node's compositions")
}
m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, Placements{})
m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, Assignments{})
if !slices.ContainsFunc(m.State, func(s StateIssued) bool {
return s.Name == ComposedRead && s.PerNode && s.Bucket == ComposedBucketPrefix && !s.Writes
}) {
@@ -22,7 +22,7 @@ func theGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) {
Streams: []ModuleStream{{Module: "mesh-delivery-builder", Name: "BUILDASKS", Events: []string{"ask"}}}},
{Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}},
{Module: "node-tools", ToolRunner: true},
// The node-engine's manifest as a placement names it (issue 774, met twice on 2026-10-30): no account of its
// The node-engine's manifest as assignments names it (issue 774, met twice on 2026-10-30): no account of its
// own, so the runner would carry it, and its state is the node's to write alone.
{Module: EngineModule, NoAccount: true, Consumes: []string{"restic.backed-up"},
State: []Bucket{{Module: EngineModule, Name: "health"}, {Module: EngineModule, Name: EngineActsState}}},
@@ -119,7 +119,7 @@ func ComposedFor(f UserListFixture) (string, []string, error) {
// MembershipsFor is every membership a fixture's records issue, by `<node>.<module>`, as the composer makes them.
func MembershipsFor(f UserListFixture) map[string]Membership {
where := PlacementsOf(f.Records, f.Records.Interchangeable)
where := AssignmentsOf(f.Records, f.Records.Interchangeable)
out := map[string]Membership{}
for node, ds := range f.Records.Assigned {
for _, d := range ds {
@@ -15,8 +15,8 @@ import (
// grant is the same composition read the other way. The shape issued today is the shape the mesh
// already had, so nothing moves when a membership first arrives; only who decides it moves.
// Placements is where every module runs, for deciding which instance answers for the module.
type Placements struct {
// Assignments is where every module runs, for deciding which instance answers for the module.
type Assignments struct {
// Nodes is each module's machines.
Nodes map[string][]string
// Interchangeable is each module whose definition says its instances are the same anywhere,
@@ -33,14 +33,14 @@ type Placements struct {
// plain subject: when it is the only instance, or when the definition says instances are
// interchangeable. A stateful module on two machines gets only its machines' subjects, so a call
// that names none reaches nothing rather than the wrong store.
func (p Placements) AnswersForTheModule(module string) bool {
func (p Assignments) AnswersForTheModule(module string) bool {
return len(p.Nodes[module]) <= 1 || p.Interchangeable[module]
}
// MembershipFor composes one assignment's membership from what it declared and where everything
// runs. The subjects are the ones PermissionsFor grants, derived here once more only until the
// grant itself is read from the membership — which is the next step, not this one.
func MembershipFor(node string, d Declared, where Placements) Membership {
func MembershipFor(node string, d Declared, where Assignments) Membership {
own := "mesh.mod." + d.Module
m := Membership{
Node: node, Module: d.Module,
@@ -101,9 +101,9 @@ func MembershipFor(node string, d Declared, where Placements) Membership {
return m
}
// PlacementsOf reads where everything runs from the records the bus's accounts are composed from.
func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
p := Placements{Nodes: map[string][]string{}, Interchangeable: interchangeable, Logs: DeclaredLogs(r)}
// AssignmentsOf reads where everything runs from the records the bus's accounts are composed from.
func AssignmentsOf(r Records, interchangeable map[string]bool) Assignments {
p := Assignments{Nodes: map[string][]string{}, Interchangeable: interchangeable, Logs: DeclaredLogs(r)}
for node, declared := range r.Assigned {
for _, d := range declared {
p.Nodes[d.Module] = append(p.Nodes[d.Module], node)
@@ -151,7 +151,7 @@ func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
// routerSeat is the seat the router of asks holds (novox/hq ADR 0259 §3).
const routerSeat = "operator-channel"
// placedCapabilities is what a kind's claim promises, as far as its placement lets the router believe it
// placedCapabilities is what a kind's claim promises, as far as its assignments lets the router believe it
// (novox/hq ADR 0259 §8): `verified-sender` only from a holder that runs as an account of its own, on a bus
// account of its own — never one the machine's runtime carries as the operator's account — and only while
// its machine and the router's were root-free when composed (rootFree; the review of 2026-10-09, H3). The
@@ -15,20 +15,20 @@ const (
// EnrolmentModule is the module holding the seat mesh-node-enroller.
EnrolmentModule = "mesh-enrolment"
// PlacementBucket and SeatHoldersBucket are the assigner's states placement and seat-holders (reads:
// mesh-delivery-assigner.placement; the seat-holders state is read as a named reader), PlacementRead the address of the first.
PlacementBucket = AssignerModule + "_placement"
PlacementRead = AssignerModule + ".placement"
// AssignmentsBucket and SeatHoldersBucket are the assigner's states assignments and seat-holders (reads:
// mesh-delivery-assigner.assignments; the seat-holders state is read as a named reader), AssignmentsRead the address of the first.
AssignmentsBucket = AssignerModule + "_assignments"
AssignmentsRead = AssignerModule + ".assignments"
SeatHoldersRead = AssignerModule + ".seat-holders"
SeatHoldersBucket = AssignerModule + "_seat-holders"
AssignerWantsBucket = AssignerModule + "_wants"
PlacementCandidatesBucket = AssignerModule + "_candidates"
PlacementProposalsBucket = AssignerModule + "_placement-proposals"
CandidatesBucket = IndexerModule + "_candidates"
ModulesBucket = IndexerModule + "_modules"
ManifestsBucket = IndexerModule + "_manifests"
VerdictsBucket = IndexerModule + "_verdicts"
AssignerWantsBucket = AssignerModule + "_wants"
AssignmentsCandidatesBucket = AssignerModule + "_candidates"
AssignmentsProposalsBucket = AssignerModule + "_assignments-proposals"
CandidatesBucket = IndexerModule + "_candidates"
ModulesBucket = IndexerModule + "_modules"
ManifestsBucket = IndexerModule + "_manifests"
VerdictsBucket = IndexerModule + "_verdicts"
EnrolmentsState = "enrolments"
EnrolmentsBucket = EnrolmentModule + "_" + EnrolmentsState
@@ -32,7 +32,7 @@ func namesAreTheManifests(t *testing.T) {
{"reads", HaveBucket[:strings.Index(HaveBucket, "_")] + ".have", HaveBucket},
{"reads", NodeLoginsRead, NodeLoginsBucket},
{"reads", EnrolmentsRead, EnrolmentsBucket},
{"invokes", PlacementRead, PlacementBucket},
{"invokes", AssignmentsRead, AssignmentsBucket},
{"invokes", AssignerModule + ".wants", AssignerWantsBucket},
{"invokes", IndexerModule + ".modules", ModulesBucket},
{"invokes", IndexerModule + ".manifests", ManifestsBucket},
@@ -740,7 +740,7 @@ func PermissionsFor(p Principal) (Permissions, error) {
// its own modules reads the other nodes' public records, which its firewall and resolver need (ADR 0547 §2). Read
// only: each node still writes its own keys alone.
pub = append(pub, readGrants(HaveBucket, "*."+HaveNodeWord)...)
// And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-placement's placement and
// And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-module-assigner's assignments and
// candidates, the indexer's modules and manifests, the builder's builds (the manifest of a build its want names), and mesh-config's resolved values of
// its own node alone. Read only.
pub = append(pub, NodeComposeReads(p.Node)...)
@@ -779,7 +779,7 @@ func PermissionsFor(p Principal) (Permissions, error) {
}
sub = append(sub, subject)
}
// And the creation of the seat mesh-placement's two states it composes from (ADR 0567 §9), so a node agent started
// And the creation of the seat mesh-module-assigner's two states it composes from (ADR 0567 §9), so a node agent started
// before they exist binds each when the bus says it was made, with no timer; no other bucket's.
sub = append(sub, NodeComposeAdvisories()...)
// And, while the vault serves it, the vault's move-in in its own name and nobody else's (issue 676): the
@@ -1535,7 +1535,7 @@ func CreateConsumerGrants(consumers []ModuleConsumer) []string {
}
// GrantsModule is the one module whose principal publishes the modules' memberships (novox/hq ADR 0547 §5): the grants
// part, which composes them from the manifests and placement. It defines no stream and is granted no other subject here.
// part, which composes them from the manifests and assignments. It defines no stream and is granted no other subject here.
const GrantsModule = "mesh-grants"
// The grants part's state memberships (novox/hq ADR 0571 clause 7): one key per assignment, `<node>.<module>`, its
@@ -31,22 +31,22 @@ const ProvisionerSecret = "provisioner-broker"
var ConfigResolvedBucket = BucketName("mesh-config", "resolved")
// ProvisionerReads are the domains' buckets a provisioner client reads its own machine's keys of, each keyed
// `<node>.…` (research 051): placement, the controller's assignments mirror (`<node>.<module>`), whose value carries
// `<node>.…` (research 051): assignments, the controller's assignments mirror (`<node>.<module>`), whose value carries
// the provisioner's plan of a module handed to it (inventory.Provisioning, design 57 P5); and config,
// mesh-config's resolved values; and the seat mesh-provisioner's plans of its machine, `<node>.<module>` (novox/hq ADR
// 0570 clause 2). **Named one by one**, as the controller's asks are: a domain that moves to the
// provisioner adds its bucket here, and a bucket not named is one the client cannot read.
var ProvisionerReads = []string{ConfigResolvedBucket}
// ProvisionerReadsWhole are the buckets a provisioner client reads every key of: the seat mesh-placement's placement,
// ProvisionerReadsWhole are the buckets a provisioner client reads every key of: the seat mesh-module-assigner's assignments,
// where a provider's consumers are placed on their own machines, which the provider machine's client reads to fill
// ${consumers:<provision>} with each one's credential (novox/hq ADR 0373, issue 679; ADR 0567, issue 1126: the
// controller's assignments mirror it read before is gone). It holds placement, never a secret's value; the client
// controller's assignments mirror it read before is gone). It holds assignments, never a secret's value; the client
// writes none of it. **The seat's plans are read whole for the same
// reason** (2026-10-30 recovery, issue 774): the client V3 watches every machine's plans to bind a provider's
// consumers, and granted only its own machine's keys it read none. ADR 0570 clause 3 has providers act on the seat's
// provision keys instead; once the client does, plans go back to its own machine's keys.
var ProvisionerReadsWhole = []string{PlacementBucket, ProvisionerPlansBucket}
var ProvisionerReadsWhole = []string{AssignmentsBucket, ProvisionerPlansBucket}
// BoundState is a providing module's state of entries, one per consumer (mesh-sdk go/provider, issue 678): a
// consumer's machine's client reads its own keys of it, `<node>.<module>.<provision>[.<local>]`, to fill
@@ -334,24 +334,24 @@ func stateNames(buckets []Bucket) []string {
const ResolvedBucket = "mesh-config_resolved"
// NodeComposeReads is what a node's agent reads to compose its own modules (novox/hq ADR 0547 §2, ADR 0567 §3, §9,
// issue 1126): every key of the seat mesh-placement's placement and candidates (both the assigner's states), of the
// issue 1126): every key of the seat mesh-module-assigner's assignments and candidates (both the assigner's states), of the
// indexer's modules and manifests and of the builder's builds, and of mesh-config's resolved values only its own node's,
// `<node>.>`. Every candidate key, not only `*.<node>`: a node answering its candidate composes with the whole
// proposal laid over (the seed places every node at once, and a binding crosses nodes), and nothing in a placement is
// proposal laid over (the seed places every node at once, and a binding crosses nodes), and nothing in assignments is
// secret. Reads only; the node is checked as every subject token is by the caller's principal.
func NodeComposeReads(node string) []string {
var out []string
for _, bucket := range []string{PlacementBucket, PlacementCandidatesBucket, ModulesBucket,
for _, bucket := range []string{AssignmentsBucket, AssignmentsCandidatesBucket, ModulesBucket,
ManifestsBucket, builds.BuilderBuildsBucket} {
out = append(out, readGrants(bucket, "")...)
}
return append(out, readGrants(ResolvedBucket, node+".>")...)
}
// NodeComposeAdvisories are the stream-created advisories of the seat mesh-placement's placement and candidates
// NodeComposeAdvisories are the stream-created advisories of the seat mesh-module-assigner's assignments and candidates
// (ADR 0567 §9): what a node agent hears to bind each once it is made. Those two and no wider.
func NodeComposeAdvisories() []string {
return []string{StreamCreatedAdvisory(PlacementBucket), StreamCreatedAdvisory(PlacementCandidatesBucket)}
return []string{StreamCreatedAdvisory(AssignmentsBucket), StreamCreatedAdvisory(AssignmentsCandidatesBucket)}
}
// ServedReadGrants is the read of each key of mesh-config's resolved values named, `<node>.<module>.<key>`
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
"rows": [
{
"name": "module",
"held": "{\"url\":\"nats://10.0.0.1:4222\",\"fingerprint\":\"ab:cd\",\"node\":\"laptop\",\"module\":\"cap-bot\",\"user\":\"laptop.cap-bot\",\"password\":\"pw-module\",\"claims\":[{\"seat\":\"mesh-vault\",\"scope\":\"mesh\",\"serves\":[\"get\",\"accept\",\"secret-ask\",\"issued\",\"rotate\",\"move-in\",\"node-key\",\"own\",\"move-asks\"]},{\"seat\":\"node-service-manager\",\"scope\":\"node\",\"serves\":[\"status\"]},{\"seat\":\"mesh-controller\",\"scope\":\"mesh\",\"serves\":[\"tools\",\"calls\",\"status\",\"nodes\",\"node\",\"placement-export\",\"modules\",\"seats\",\"builds\",\"plans\",\"delivery-plan\",\"delivery-order\",\"delivery-check\",\"deliver\",\"delivery-stop\",\"delivery-backups-again\",\"delivery-walks\",\"plan\",\"assign\",\"unassign\",\"pin\",\"unpin\",\"push\",\"rotate\",\"give\",\"issued\",\"secret-move\",\"secret-ask\",\"issue\",\"token\",\"settings\",\"command\",\"queue\",\"cancel\",\"clear\",\"replay\",\"try\",\"kill\",\"pause\",\"resume\",\"hand-act\",\"drill\",\"warranted\",\"hand-acts\",\"durations\",\"conditions\",\"healers\",\"doctor\",\"upgrade\",\"bus\",\"retire\",\"cleanup\",\"judge-directories\",\"root-free\",\"dead-letters\",\"data\",\"build\",\"artifacts\",\"held\",\"collect\",\"move-placement\",\"move-config\",\"mirrors\",\"images\"]},{\"seat\":\"no-such-seat\",\"scope\":\"node\"}]}"
"held": "{\"url\":\"nats://10.0.0.1:4222\",\"fingerprint\":\"ab:cd\",\"node\":\"laptop\",\"module\":\"cap-bot\",\"user\":\"laptop.cap-bot\",\"password\":\"pw-module\",\"claims\":[{\"seat\":\"mesh-vault\",\"scope\":\"mesh\",\"serves\":[\"get\",\"accept\",\"secret-ask\",\"issued\",\"rotate\",\"move-in\",\"node-key\",\"own\",\"move-asks\"]},{\"seat\":\"node-service-manager\",\"scope\":\"node\",\"serves\":[\"status\"]},{\"seat\":\"mesh-controller\",\"scope\":\"mesh\",\"serves\":[\"tools\",\"calls\",\"status\",\"nodes\",\"node\",\"assignments-export\",\"modules\",\"seats\",\"builds\",\"plans\",\"delivery-plan\",\"delivery-order\",\"delivery-check\",\"deliver\",\"delivery-stop\",\"delivery-backups-again\",\"delivery-walks\",\"plan\",\"assign\",\"unassign\",\"pin\",\"unpin\",\"push\",\"rotate\",\"give\",\"issued\",\"secret-move\",\"secret-ask\",\"issue\",\"token\",\"settings\",\"command\",\"queue\",\"cancel\",\"clear\",\"replay\",\"try\",\"kill\",\"pause\",\"resume\",\"hand-act\",\"drill\",\"warranted\",\"hand-acts\",\"durations\",\"conditions\",\"healers\",\"doctor\",\"upgrade\",\"bus\",\"retire\",\"cleanup\",\"judge-directories\",\"root-free\",\"dead-letters\",\"data\",\"build\",\"artifacts\",\"held\",\"collect\",\"move-assignments\",\"move-config\",\"mirrors\",\"images\"]},{\"seat\":\"no-such-seat\",\"scope\":\"node\"}]}"
},
{
"name": "module-no-claims-no-fingerprint",
File diff suppressed because one or more lines are too long
@@ -75,7 +75,7 @@ type Declared struct {
// 0495 clause 3, issue 1790): granted nothing, kept to be said.
ReadsRefused []string
// ReadLogs are, of its reads, those naming another module's log (`<module>.<log>`, novox/hq ADR 0571 clause 1): filled
// by Users and PlacementsOf from the logs the modules placed anywhere declare, never by a caller.
// by Users and AssignmentsOf from the logs the modules placed anywhere declare, never by a caller.
ReadLogs []string
// KeyedReads are keys of other modules' state it reads, each one key alone: what a seat's holder is
// granted for the pieces the modules beside it offer (novox/hq ADR 0260).
@@ -231,7 +231,7 @@ func Users(r Records) ([]Principal, error) {
}
}
for _, d := range r.Assigned[node] {
// **The node-engine's manifest is no placed module's** (novox/hq ADR 0563 §2, §3; issue 774): where a placement
// **The node-engine's manifest is no placed module's** (novox/hq ADR 0563 §2, §3; issue 774): where assignments
// names it, it adds no user, no process and nothing the runner carries; its grants are the node's account's.
if !Composed(d) {
continue
@@ -541,7 +541,7 @@ func boundBucketsOf(r Records) []string {
}
// Composed says a placed module is composed as one: its own user, its processes and what the runner carries of it.
// The node-engine's manifest (EngineModule) is not, though a placement may name it: each node's engine speaks as the
// The node-engine's manifest (EngineModule) is not, though assignments may name it: each node's engine speaks as the
// node's account, which alone writes the engine's state under its own key (engineGrants, novox/hq ADR 0563 §2, §3), and
// Records.Engine creates that state. Granted to another account it is a second writer, and the writers table refuses
// the whole list (issue 774: met for mesh-host_acts and then mesh-host_health, 2026-10-30).
@@ -12,10 +12,10 @@ import (
)
// **The accounts the mesh wants, said to the vault** (novox/hq ADR 0561 §2, issue 774). The bus composer derives every
// user from placement, the manifests and the wants; for each account the vault mints (a module's, a process's, a
// user from assignments, the manifests and the wants; for each account the vault mints (a module's, a process's, a
// machine's provisioner client's), the part writes one key of its state `accounts`, keyed by the bus user, with the
// fields the controller's credential carries beside the password. A key is written only when it differs, and deleted
// only when placement no longer names its user and every input of the pass was read: a refused read never retires an
// only when assignments no longer names its user and every input of the pass was read: a refused read never retires an
// account. The vault alone reads the state (buscompose.NamedReaders); it mints each user it holds no value for.
// StateAccounts is the part's state of wanted accounts.
@@ -119,7 +119,7 @@ func (p *Part) Accounts(user string) (any, error) {
}
if !found {
if user != "" {
return nil, fmt.Errorf("no account %s is wanted: placement composes no module, process or provisioner client of that user", user)
return nil, fmt.Errorf("no account %s is wanted: assignments composes no module, process or provisioner client of that user", user)
}
continue
}
@@ -149,7 +149,7 @@ type AccountsWritten struct {
Wanted int `json:"wanted"`
Written []string `json:"written,omitempty"`
Deleted []string `json:"deleted,omitempty"`
// Kept are keys placement no longer names that this pass did not delete, with why.
// Kept are keys assignments no longer names that this pass did not delete, with why.
Kept []string `json:"kept,omitempty"`
}
@@ -198,7 +198,7 @@ func (p *Part) writeAccounts(res *Result, in Inputs, recs buscompose.Records) {
}
switch {
case placed(user):
done.Kept = append(done.Kept, user+": placement still names it (composed with no account this pass)")
done.Kept = append(done.Kept, user+": assignments still names it (composed with no account this pass)")
continue
case len(failed) > 0:
done.Kept = append(done.Kept, user+": a read of this pass failed, so no account is retired")
@@ -21,7 +21,7 @@ import (
)
// **The grants part's first views** (novox/hq ADR 0615 §1, §4 to §6, ADR 0621, ADR 0622; design 25 § 12.2; DLV-F4r,
// DLV-R1). The part reads the assigner's placement and wants and the indexer's manifests, modules and verdicts no longer
// DLV-R1). The part reads the assigner's assignments and wants and the indexer's manifests, modules and verdicts no longer
// from their buckets, but by each owner's first-view tool, asked once at its start, then from the owner's views family
// alone (each event one changed key with its revision), asking once more only on a gap in revisions, never on a timer.
// The verdicts alone are followed on the seat mesh-catalog's manifest-accepted and manifest-refused, which carry the revision
@@ -48,10 +48,10 @@ const (
IndexerModule = "mesh-delivery-indexer"
)
// FirstViews are the five (ADR 0615 §3 as ADR 0622 §1 amends it: the grants part's placement, wants, manifests,
// FirstViews are the five (ADR 0615 §3 as ADR 0622 §1 amends it: the grants part's assignments, wants, manifests,
// modules and verdicts).
var FirstViews = []FirstView{
{Owner: AssignerModule, Tool: AssignerModule + ".placement", State: "placement", Read: ReadPlacement},
{Owner: AssignerModule, Tool: AssignerModule + ".assignments", State: "assignments", Read: ReadAssignments},
{Owner: AssignerModule, Tool: AssignerModule + ".wants", State: "wants", Read: ReadWants},
{Owner: IndexerModule, Tool: IndexerModule + ".manifests", State: "manifests", Read: ReadManifests},
{Owner: IndexerModule, Tool: IndexerModule + ".modules", State: "modules", Read: ReadModules},
@@ -244,7 +244,7 @@ func (v *Views) Statuses() []views.Status {
return out
}
// Again asks the first view of one state (its state name, `placement`, or its tool) once more, or every one when state is
// Again asks the first view of one state (its state name, `assignments`, or its tool) once more, or every one when state is
// empty, each on a goroutine of its own; it answers which it asked, or refuses a state that is none of the five.
func (v *Views) Again(ctx context.Context, state, why string) ([]string, error) {
var asked []string
@@ -257,7 +257,7 @@ func (v *Views) Again(ctx context.Context, state, why string) ([]string, error)
asked = append(asked, fv.Tool)
}
if len(asked) == 0 {
return nil, fmt.Errorf("%q is no first view of the grants part: one of placement, wants, manifests, modules, verdicts, or none for all", state)
return nil, fmt.Errorf("%q is no first view of the grants part: one of assignments, wants, manifests, modules, verdicts, or none for all", state)
}
return asked, nil
}
@@ -459,7 +459,7 @@ func (p *Part) AskFirstViewAgain(caller, state, why string, record func(what, wh
}
}
if which == "" {
return nil, fmt.Errorf("%q is no first view of the grants part: one of placement, wants, manifests, modules, verdicts, or none for all", state)
return nil, fmt.Errorf("%q is no first view of the grants part: one of assignments, wants, manifests, modules, verdicts, or none for all", state)
}
}
if strings.TrimSpace(why) == "" {
@@ -15,13 +15,13 @@ type Watcher interface {
Watch(state string, changed func(key string, value []byte), ended func(error)) error
}
// Followed are the states whose change composes again: placement, the indexer's manifests by module, the resolved
// Followed are the states whose change composes again: assignments, the indexer's manifests by module, the resolved
// config values, the node agents' haves (a machine's first have, and the bus node's loaded list) and the assigner's
// wants (the manifest each machine is wanted to run), and the vault's hashes of the bus accounts.
//
// And the seat mesh-enrolment's keys (ADR 0587 §4, §5): a key at live composes enrol.<node>, and its end, by the holder or by
// the bus's expiry, is a change of the key that takes the user out; no event is needed beside the watch.
var Followed = []string{ReadModules, ReadVerdicts, ReadResolved, ReadHave, ReadWants, ReadAccounts, ReadPlacement, ReadEnrolments,
var Followed = []string{ReadModules, ReadVerdicts, ReadResolved, ReadHave, ReadWants, ReadAccounts, ReadAssignments, ReadEnrolments,
ReadNodeLogins}
// Follower composes again on every change it hears, never on a timer. It never stops for a refusal: a watch it cannot
@@ -34,7 +34,7 @@ type GrantsWritten struct {
Deleted []string `json:"deleted,omitempty"`
// Kept says why a pass wrote and deleted nothing, or left a module out.
Kept []string `json:"kept,omitempty"`
// Missing is each module the placement or an assignment puts on a machine that has no grant after the pass,
// Missing is each module the assignments or an assignment puts on a machine that has no grant after the pass,
// `<module>: <why>`: the assigner's sixth rule wants none of it (MissingGrants). Never passed over in silence: once
// the part's grants are the ones read, it is the condition mesh-grants.grant-missing.
Missing []string `json:"missing,omitempty"`
+23 -23
View File
@@ -19,15 +19,15 @@ import (
"mesh-grants/internal/buscompose"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
@@ -47,9 +47,9 @@ const (
ReadBuilds = "mesh-delivery-builder.builds"
// ReadAccounts is the vault's bcrypt hashes of the bus accounts it holds, by bus user (ADR 0547 clause 9).
ReadAccounts = "mesh-vault.bus-accounts"
// ReadPlacement is the seat mesh-placement's placement (ADR 0567 §1, §9): each node's modules and the seats
// ReadAssignments is the seat mesh-module-assigner's assignments (ADR 0567 §1, §9): each node's modules and the seats
// they hold.
ReadPlacement = buscompose.PlacementRead
ReadAssignments = buscompose.AssignmentsRead
// ReadEnrolments is the seat mesh-enrolment's open enrolments, read as a named reader (ADR 0587 §2, §4): a key at
// live or redeeming composes the user enrol.<node>.
ReadEnrolments = buscompose.EnrolmentsRead
@@ -58,13 +58,13 @@ const (
ReadNodeLogins = buscompose.NodeLoginsRead
)
// Reads is every state the part reads, in the manifest's order. Not the controller's assignments: the seat's placement
// Reads is every state the part reads, in the manifest's order. Not the controller's assignments: the seat's assignments
// alone places modules (novox/hq ADR 0549 §5, ADR 0571 §10; issue 774).
var Reads = []string{ReadResolved, ReadHave, ReadBuilds, ReadAccounts, ReadEnrolments, ReadNodeLogins}
// ViewReads are the owners' states the part reads by their first views and views families, never their buckets
// (firstviews.go, ADR 0615): each read in Reads' place.
var ViewReads = []string{ReadModules, ReadManifests, ReadVerdicts, ReadWants, ReadPlacement}
var ViewReads = []string{ReadModules, ReadManifests, ReadVerdicts, ReadWants, ReadAssignments}
// The bus module's tools the part calls, as its manifest's invokes name them: read-only, each.
const (
@@ -128,13 +128,13 @@ type resolved struct {
// Inputs is what the shadow composes from, read from the bus.
type Inputs struct {
// Nodes is every machine seen: in a node agent's have or the placement.
// Nodes is every machine seen: in a node agent's have or the assignments.
Nodes []string
// Assigned is what the seat's placement puts on each machine, sorted (issue 774: never the controller's assignments).
// Assigned is what the seat's assignments puts on each machine, sorted (issue 774: never the controller's assignments).
Assigned map[string][]string
// Placed is each node's placement as the seat mesh-placement holds it (ADR 0567 §1); empty before any is read.
// Where it is, a seat held once for the mesh is held where placement says, not wherever a module claiming it runs.
Placed map[string]meshmoduleassigner.Placement
// Placed is each node's assignments as the seat mesh-module-assigner holds it (ADR 0567 §1); empty before any is read.
// Where it is, a seat held once for the mesh is held where assignments says, not wherever a module claiming it runs.
Placed map[string]meshmoduleassigner.Assignments
// Manifests is each module's manifest at the indexer's trunk, by module.
Manifests map[string]catalogue.Manifest
// Bound is, by module, the streams of each bus-state entry the bus module said it created and has not dropped, from
@@ -318,33 +318,33 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
NoFingerprint: map[string]string{}, Running: map[string]catalogue.Manifest{},
Values: map[string]any{}, Gaps: append([]Gap(nil), standingGaps...)}
nodes := map[string]bool{}
// **The seat's placement alone places modules** (novox/hq ADR 0549 §5, ADR 0567 §9; issue 774): the controller's
// **The seat's assignments alone places modules** (novox/hq ADR 0549 §5, ADR 0567 §9; issue 774): the controller's
// assignments are read no more, and the seats held once for the mesh are held where it says. Not read is a failed
// read: nothing is placed, so the list composed past it is not sealed.
if keys, err := r.Keys(ReadPlacement); err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement, Why: "refused or absent: nothing is placed (" + err.Error() + ")", Failed: true})
if keys, err := r.Keys(ReadAssignments); err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadAssignments, Why: "refused or absent: nothing is placed (" + err.Error() + ")", Failed: true})
} else {
// Each node's keys in either form, assembled by the seat's one reader (meshmoduleassignerclient.PlacementsOf; research 072
// row 37): its node key and a key per module, or its legacy key `placement.<node>`.
// Each node's keys in either form, assembled by the seat's one reader (meshmoduleassignerclient.AssignmentsOf; research 072
// row 37): its node key and a key per module, or its legacy key `assignments.<node>`.
values := map[string][]byte{}
for _, k := range keys {
raw, found, err := r.Get(ReadPlacement, k)
raw, found, err := r.Get(ReadAssignments, k)
if err != nil || !found {
continue
}
if _, _, _, err := meshmoduleassignerclient.ParseKey(k); err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement + " " + k, Why: "not a placement key, so it places nothing"})
in.Gaps = append(in.Gaps, Gap{Input: ReadAssignments + " " + k, Why: "not an assignments key, so it places nothing"})
continue
}
values[k] = raw
}
placed, err := meshmoduleassignerclient.PlacementsOf(values)
placed, err := meshmoduleassignerclient.AssignmentsOf(values)
if err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement, Why: "a key does not read, so its node is composed from what does: " + err.Error()})
in.Gaps = append(in.Gaps, Gap{Input: ReadAssignments, Why: "a key does not read, so its node is composed from what does: " + err.Error()})
}
for node, p := range placed {
if in.Placed == nil {
in.Placed = map[string]meshmoduleassigner.Placement{}
in.Placed = map[string]meshmoduleassigner.Assignments{}
}
in.Placed[node] = p
}
@@ -372,7 +372,7 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
nodes[node] = true
}
}
in.Gaps = append(in.Gaps, Gap{Input: "machines", Why: "read from the node agents' haves and the placement: a machine with neither is not seen"})
in.Gaps = append(in.Gaps, Gap{Input: "machines", Why: "read from the node agents' haves and the assignments: a machine with neither is not seen"})
for n := range nodes {
in.Nodes = append(in.Nodes, n)
}
@@ -40,7 +40,7 @@ type Membership struct {
// Memberships composes every assignment's membership from the records (buscompose.MembershipFor, as the controller's
// parked composition pass, mesh-controller 003eee8a, issued them), by `<node>.<module>`. Pure.
func Memberships(r buscompose.Records) map[string]Membership {
where := buscompose.PlacementsOf(r, r.Interchangeable)
where := buscompose.AssignmentsOf(r, r.Interchangeable)
out := map[string]Membership{}
for node, declared := range r.Assigned {
for _, d := range declared {
@@ -7,7 +7,7 @@ import (
"slices"
"strings"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"mesh-grants/internal/buscompose"
)
@@ -408,14 +408,14 @@ func hearsOf(m catalogue.Manifest, declared map[string]catalogue.Manifest, node
return out, touches, onEvents
}
// heldWherePlaced is a module's holds on a node as the catalogue's placement says them (ADR 0549 §1, §3). A placement's
// heldWherePlaced is a module's holds on a node as the catalogue's assignments says them (ADR 0549 §1, §3). The assignments'
// holds are the holders on record, as catalogue.World's Holdings are: a seat held once for the mesh that some node's
// placement holds is held only where a placement names it (this node's naming this module), so a module claiming it on
// two machines holds it on the one placement names, not on both. A seat no placement holds is held by derivation, by the
// module claiming it wherever it is placed, as it was before placement: the seeded placements hold only the seats the
// assignments holds is held only where assignments names it (this node's naming this module), so a module claiming it on
// two machines holds it on the one assignments names, not on both. A seat no assignments holds is held by derivation, by the
// module claiming it wherever it is placed, as it was before assignments: the seeded assignments hold only the seats the
// controller had on record (twelve on the control-node), and treating their holds as every holder left nine seats with
// no holder in the user list of 2026-10-27 09:21 (issue 1945). A seat of every node is held wherever the module runs.
func heldWherePlaced(holds []buscompose.Seat, placed map[string]meshmoduleassigner.Placement, node, module string) []buscompose.Seat {
func heldWherePlaced(holds []buscompose.Seat, placed map[string]meshmoduleassigner.Assignments, node, module string) []buscompose.Seat {
same := func(x meshmoduleassigner.Holding, h buscompose.Seat) bool {
return (x.Scope == "" || x.Scope == catalogue.ScopeMesh) && sameSeat(x.Claim, h.Name) && x.Kind == h.Kind
}
@@ -239,7 +239,7 @@ type Comparison struct {
Shadow int `json:"shadow"`
Loaded int `json:"loaded"`
// Added are users in the shadow list the loaded list lacks (one left out for want of a credential among them);
// Dropped are loaded users the shadow list lacks; Gone are those of Dropped whose machine or module placement no
// Dropped are loaded users the shadow list lacks; Gone are those of Dropped whose machine or module assignments no
// longer names, which both lists agree are gone.
Added []string `json:"added"`
Dropped []string `json:"dropped"`
@@ -277,7 +277,7 @@ func (c Comparison) Differing() []string {
func (d UserDiff) responsesLost() bool { return strings.HasPrefix(d.Responses, "shadow false") }
// Removing names every user that keeps the lists apart other than by a right the shadow list adds, each with how: a user
// the shadow list adds, a loaded user it drops that placement still names, a publish or subscribe right a loaded user
// the shadow list adds, a loaded user it drops that assignments still names, a publish or subscribe right a loaded user
// would lose, or an allowance of responses lost (ADR 0562 §2). Empty when the lists agree or differ only by added rights.
func (c Comparison) Removing() []string {
var out []string
@@ -286,7 +286,7 @@ func (c Comparison) Removing() []string {
}
for _, u := range c.Dropped {
if !slices.Contains(c.Gone, u) {
out = append(out, u+" (dropped, and placement still names it)")
out = append(out, u+" (dropped, and assignments still names it)")
}
}
for _, d := range c.Differ {
@@ -331,7 +331,7 @@ func (c Comparison) AddedRights() []UserDiff {
return out
}
// Compare compares the shadow list with the loaded one. placed says whether placement still names a loaded user's
// Compare compares the shadow list with the loaded one. placed says whether assignments still names a loaded user's
// machine and module; nil names every one. Pure.
func Compare(shadow, loaded []User, placed func(user string) bool) Comparison {
c := Comparison{Shadow: len(shadow), Loaded: len(loaded), Added: []string{}, Dropped: []string{}, Differ: []UserDiff{}}
@@ -386,7 +386,7 @@ func minus(a, b []string) []string {
return out
}
// Placed says whether placement still names a user's machine and module: a machine's users (`node.<n>`,
// Placed says whether assignments still names a user's machine and module: a machine's users (`node.<n>`,
// `provisioner.<n>`) while the machine is seen, a module's or process's (`<n>.<module>[.<process>]`) while the module is
// assigned there. The controller, enrolment users and persons are never judged gone from here.
func Placed(in Inputs) func(user string) bool {
@@ -447,7 +447,7 @@ func (c Comparison) Excusing(f func(user, subject string, added, subscribe bool)
var seatSubject = regexp.MustCompile(`^mesh\.seat\.([^.]+)\.(?:call|tool)\.([^.]+)(?:\.[^.]+)?$`)
var seatService = regexp.MustCompile(`^\$SRV\.(?:INFO|PING|STATS)\.([^.]+)(?:\.>)?$`)
// ExcusedBy is the excuse of a comparison until placement lives in the catalogue (the operator, 2026-10-26): a verb the
// ExcusedBy is the excuse of a comparison until assignments lives in the catalogue (the operator, 2026-10-26): a verb the
// seat catalogue retired on purpose, which the shadow no longer grants, is no difference; and what the shadow grants more
// for a mesh seat that is claimed on several machines (the standing gap seat holdings: it composes the seat as held on each)
// is named and not counted, for a user on a machine that claims the seat and only as a subscription (what it serves), since
+2 -2
View File
@@ -17,7 +17,7 @@
"mesh-broker.created",
"mesh-broker.dropped",
"mesh-broker.recreated",
"mesh-delivery-assigner.views.placement",
"mesh-delivery-assigner.views.assignments",
"mesh-delivery-assigner.views.wants",
"mesh-delivery-indexer.views.manifests",
"mesh-delivery-indexer.views.modules",
@@ -34,7 +34,7 @@
"nats.nats_user_can",
"nats.nats_users",
"seat:node-engine.root-free",
"mesh-delivery-assigner.placement",
"mesh-delivery-assigner.assignments",
"mesh-delivery-assigner.wants",
"mesh-delivery-indexer.manifests",
"mesh-delivery-indexer.modules",
+48 -48
View File
@@ -47,8 +47,8 @@
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_manifests",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_modules",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-teller_answers",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-watchman_presence",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_node-engine_have"
@@ -118,36 +118,36 @@
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.*.node",
"$JS.API.CONSUMER.DELETE.KV_mesh-config_resolved.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.>",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.novox.keycloak.issuer",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.novox.mailu.domain",
"$JS.API.DIRECT.GET.KV_mesh-delivery-builder_builds.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_modules.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.*.node",
"$JS.API.STREAM.INFO.KV_mesh-config_resolved",
"$JS.API.STREAM.INFO.KV_mesh-delivery-builder_builds",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_manifests",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_modules",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement-candidates",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.FC.KV_mesh-config_resolved.>",
"$JS.FC.KV_mesh-delivery-builder_builds.>",
"$JS.FC.KV_mesh-delivery-indexer_manifests.>",
"$JS.FC.KV_mesh-delivery-indexer_modules.>",
"$JS.FC.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_placement.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments.>",
"mesh.seat.mesh-vault.call.own.node~ace"
],
"subscribe_added": [
@@ -165,15 +165,15 @@
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.*.node",
"$JS.API.CONSUMER.DELETE.KV_mesh-config_resolved.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.bucket",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.org",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.g14.>",
@@ -182,21 +182,21 @@
"$JS.API.DIRECT.GET.KV_mesh-delivery-builder_builds.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_modules.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.*.node",
"$JS.API.STREAM.INFO.KV_mesh-config_resolved",
"$JS.API.STREAM.INFO.KV_mesh-delivery-builder_builds",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_manifests",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_modules",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement-candidates",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.FC.KV_mesh-config_resolved.>",
"$JS.FC.KV_mesh-delivery-builder_builds.>",
"$JS.FC.KV_mesh-delivery-indexer_manifests.>",
"$JS.FC.KV_mesh-delivery-indexer_modules.>",
"$JS.FC.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_placement.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments.>",
"mesh.seat.mesh-vault.call.own.node~g14"
],
"subscribe_added": [
@@ -212,36 +212,36 @@
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.*.node",
"$JS.API.CONSUMER.DELETE.KV_mesh-config_resolved.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.bucket",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.org",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.novox.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-builder_builds.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_modules.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.*.node",
"$JS.API.STREAM.INFO.KV_mesh-config_resolved",
"$JS.API.STREAM.INFO.KV_mesh-delivery-builder_builds",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_manifests",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_modules",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement-candidates",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.FC.KV_mesh-config_resolved.>",
"$JS.FC.KV_mesh-delivery-builder_builds.>",
"$JS.FC.KV_mesh-delivery-indexer_manifests.>",
"$JS.FC.KV_mesh-delivery-indexer_modules.>",
"$JS.FC.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_placement.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments.>",
"mesh.seat.mesh-vault.call.own.node~novox"
],
"subscribe_added": [
@@ -259,15 +259,15 @@
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.*.node",
"$JS.API.CONSUMER.DELETE.KV_mesh-config_resolved.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-builder_builds.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_modules.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_placement.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.bucket",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.ace.influxdb.org",
"$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.novox.keycloak.issuer",
@@ -276,21 +276,21 @@
"$JS.API.DIRECT.GET.KV_mesh-delivery-builder_builds.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_manifests.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_modules.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_placement.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_assignments.>",
"$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.*.node",
"$JS.API.STREAM.INFO.KV_mesh-config_resolved",
"$JS.API.STREAM.INFO.KV_mesh-delivery-builder_builds",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_manifests",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_modules",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_placement-candidates",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments",
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.FC.KV_mesh-config_resolved.>",
"$JS.FC.KV_mesh-delivery-builder_builds.>",
"$JS.FC.KV_mesh-delivery-indexer_manifests.>",
"$JS.FC.KV_mesh-delivery-indexer_modules.>",
"$JS.FC.KV_mesh-delivery-indexer_placement-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_placement.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments-candidates.>",
"$JS.FC.KV_mesh-delivery-indexer_assignments.>",
"mesh.seat.mesh-vault.call.own.node~shanks"
],
"subscribe_added": [
@@ -335,7 +335,7 @@
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-controller_assignments",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-controller_declared-config",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-controller_predefined",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement"
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments"
],
"user": "novox.mesh-config"
},
@@ -384,8 +384,8 @@
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_manifests",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_modules",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_takeins",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_taken"
],
@@ -423,7 +423,7 @@
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-builder_builds",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_manifests",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_modules",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-grants_assertions",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-grants_memberships",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-grants_takeover",
@@ -578,8 +578,8 @@
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-gate_source",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_modules",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_placement-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_assignments-candidates",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-indexer_taken",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-teller_answers",
"$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-view_answers",