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:
@@ -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,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).
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
}},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
+1
-1
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user