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

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:13:20 +01:00
parent 369ebd092d
commit 0eafb2fe95
164 changed files with 1699 additions and 1498 deletions
@@ -8,7 +8,7 @@ import (
// **The accounts the grants part wants, and the one shape a held account takes** (novox/hq ADR 0561 §1, §2; issue 774).
//
// The bus composer derives every user from placement, the manifests and the wants. For each account the mesh mints (a
// The bus composer derives every user from assignments, the manifests and the wants. For each account the mesh mints (a
// module's, a module's process's, a machine's provisioner client's), the grants part writes one key of its state
// `accounts`, keyed by the bus user, holding the account's fields that are not secret: what the controller's credential
// carries today beside the password. The vault watches that state, mints the user it holds no value for, and keeps the
@@ -12,8 +12,8 @@ import (
"time"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
@@ -57,17 +57,17 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
t.Run("every node's compositions are read by their named readers alone and written by their node", everyNodesCompositions)
t.Run("a module's ledger is written by its ledger account alone", aLedgerIsWrittenByItsLedgerAccountAlone)
t.Run("the bus module's process alone keeps every module's bus state and serves the broker's verbs", theBusModulesProcessKeepsBusState)
// ADR 0567 §3, §9: a node reads every key of the seat's placement and its candidates (it composes with the whole
// ADR 0567 §3, §9: a node reads every key of the seat's assignments and its candidates (it composes with the whole
// proposal laid over), and none of its open proposals; it writes neither.
reads := strings.Join(NodeComposeReads("laptop"), " ")
for bucket, granted := range map[string]bool{PlacementBucket: true, PlacementCandidatesBucket: true,
PlacementProposalsBucket: false} {
for bucket, granted := range map[string]bool{AssignmentsBucket: true, AssignmentsCandidatesBucket: true,
AssignmentsProposalsBucket: false} {
if strings.Contains(reads, "$JS.API.DIRECT.GET.KV_"+bucket+".>") != granted || strings.Contains(reads, "$KV."+bucket+".laptop") {
t.Errorf("a node is granted %s: %v, want %v", bucket, !granted, granted)
}
}
// It hears the creation of those two buckets, and of no other.
if got := NodeComposeAdvisories(); len(got) != 2 || got[0] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_placement" ||
if got := NodeComposeAdvisories(); len(got) != 2 || got[0] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_assignments" ||
got[1] != "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_candidates" {
t.Errorf("a node hears the creation of %v", got)
}
@@ -513,11 +513,11 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
vault := ClaimedSeat{Seat: "mesh-vault", Scope: "mesh", Serves: []string{"get", "accept", "secret-ask", "issued",
"rotate", "move-in", "node-key", "own", "move-asks"}}
controller := ClaimedSeat{Seat: "mesh-controller", Scope: "mesh", Serves: strings.Split("tools,calls,status,nodes,"+
"node,placement-export,modules,seats,builds,plans,delivery-plan,delivery-order,delivery-check,deliver,"+
"node,assignments-export,modules,seats,builds,plans,delivery-plan,delivery-order,delivery-check,deliver,"+
"delivery-stop,delivery-backups-again,delivery-walks,plan,assign,unassign,pin,unpin,push,rotate,give,issued,"+
"secret-move,secret-ask,issue,token,settings,command,queue,cancel,clear,replay,try,kill,pause,resume,hand-act,"+
"drill,warranted,hand-acts,durations,conditions,healers,doctor,upgrade,bus,retire,cleanup,judge-directories,"+
"root-free,dead-letters,data,build,artifacts,held,collect,move-placement,move-config,mirrors,images", ",")}
"root-free,dead-letters,data,build,artifacts,held,collect,move-assignments,move-config,mirrors,images", ",")}
at := "nats://10.0.0.1:4222"
wanted := map[string]struct {
w WantedAccount
@@ -790,7 +790,7 @@ func healthChecks(t *testing.T) {
}
// ADR 0571 clause 1: a read naming another module's log is issued to the reader as a log it reads, never writes.
if m := MembershipFor("a", Declared{Module: IndexerModule, Reads: []string{builds.BuildLogRead, "x.y"}},
Placements{Logs: map[string]bool{builds.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes ||
Assignments{Logs: map[string]bool{builds.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes ||
m.Logs[0].Stream != "LOG_mesh-delivery-builder_build-log" || m.Logs[0].Name != builds.BuildLogRead {
t.Errorf("the indexer's membership issues the logs %+v", m.Logs)
}
@@ -873,7 +873,7 @@ func healthChecks(t *testing.T) {
{"a flat event of a node seat held by a module is said flat", Declared{Module: "pause", Holds: []Seat{{Name: "node-power",
Scope: "node", Emits: []string{"slept"}}}}, "mesh.seat.node-power.event.slept", true},
} {
m := MembershipFor("a", c.d, Placements{})
m := MembershipFor("a", c.d, Assignments{})
listed := m.SeatTraffic != nil && slices.ContainsFunc(m.SeatTraffic.Publish, func(p string) bool { return p == c.subject })
perms, err := PermissionsFor(Principal{Kind: KindNodeTools, Node: "a", Carries: []Declared{c.d}})
if err != nil {
@@ -1019,7 +1019,7 @@ func everyNodesCompositions(t *testing.T) {
!MayPublish(runner, "$JS.API.DIRECT.GET.KV_composed-two.$KV.composed-two.two.node") {
t.Errorf("the runtime carrying the provisioner cannot read every node's compositions")
}
m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, Placements{})
m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, Assignments{})
if !slices.ContainsFunc(m.State, func(s StateIssued) bool {
return s.Name == ComposedRead && s.PerNode && s.Bucket == ComposedBucketPrefix && !s.Writes
}) {
@@ -22,7 +22,7 @@ func theGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) {
Streams: []ModuleStream{{Module: "mesh-delivery-builder", Name: "BUILDASKS", Events: []string{"ask"}}}},
{Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}},
{Module: "node-tools", ToolRunner: true},
// The node-engine's manifest as a placement names it (issue 774, met twice on 2026-10-30): no account of its
// The node-engine's manifest as assignments names it (issue 774, met twice on 2026-10-30): no account of its
// own, so the runner would carry it, and its state is the node's to write alone.
{Module: EngineModule, NoAccount: true, Consumes: []string{"restic.backed-up"},
State: []Bucket{{Module: EngineModule, Name: "health"}, {Module: EngineModule, Name: EngineActsState}}},
@@ -119,7 +119,7 @@ func ComposedFor(f UserListFixture) (string, []string, error) {
// MembershipsFor is every membership a fixture's records issue, by `<node>.<module>`, as the composer makes them.
func MembershipsFor(f UserListFixture) map[string]Membership {
where := PlacementsOf(f.Records, f.Records.Interchangeable)
where := AssignmentsOf(f.Records, f.Records.Interchangeable)
out := map[string]Membership{}
for node, ds := range f.Records.Assigned {
for _, d := range ds {
@@ -15,8 +15,8 @@ import (
// grant is the same composition read the other way. The shape issued today is the shape the mesh
// already had, so nothing moves when a membership first arrives; only who decides it moves.
// Placements is where every module runs, for deciding which instance answers for the module.
type Placements struct {
// Assignments is where every module runs, for deciding which instance answers for the module.
type Assignments struct {
// Nodes is each module's machines.
Nodes map[string][]string
// Interchangeable is each module whose definition says its instances are the same anywhere,
@@ -33,14 +33,14 @@ type Placements struct {
// plain subject: when it is the only instance, or when the definition says instances are
// interchangeable. A stateful module on two machines gets only its machines' subjects, so a call
// that names none reaches nothing rather than the wrong store.
func (p Placements) AnswersForTheModule(module string) bool {
func (p Assignments) AnswersForTheModule(module string) bool {
return len(p.Nodes[module]) <= 1 || p.Interchangeable[module]
}
// MembershipFor composes one assignment's membership from what it declared and where everything
// runs. The subjects are the ones PermissionsFor grants, derived here once more only until the
// grant itself is read from the membership — which is the next step, not this one.
func MembershipFor(node string, d Declared, where Placements) Membership {
func MembershipFor(node string, d Declared, where Assignments) Membership {
own := "mesh.mod." + d.Module
m := Membership{
Node: node, Module: d.Module,
@@ -101,9 +101,9 @@ func MembershipFor(node string, d Declared, where Placements) Membership {
return m
}
// PlacementsOf reads where everything runs from the records the bus's accounts are composed from.
func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
p := Placements{Nodes: map[string][]string{}, Interchangeable: interchangeable, Logs: DeclaredLogs(r)}
// AssignmentsOf reads where everything runs from the records the bus's accounts are composed from.
func AssignmentsOf(r Records, interchangeable map[string]bool) Assignments {
p := Assignments{Nodes: map[string][]string{}, Interchangeable: interchangeable, Logs: DeclaredLogs(r)}
for node, declared := range r.Assigned {
for _, d := range declared {
p.Nodes[d.Module] = append(p.Nodes[d.Module], node)
@@ -151,7 +151,7 @@ func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
// routerSeat is the seat the router of asks holds (novox/hq ADR 0259 §3).
const routerSeat = "operator-channel"
// placedCapabilities is what a kind's claim promises, as far as its placement lets the router believe it
// placedCapabilities is what a kind's claim promises, as far as its assignments lets the router believe it
// (novox/hq ADR 0259 §8): `verified-sender` only from a holder that runs as an account of its own, on a bus
// account of its own — never one the machine's runtime carries as the operator's account — and only while
// its machine and the router's were root-free when composed (rootFree; the review of 2026-10-09, H3). The
@@ -15,20 +15,20 @@ const (
// EnrolmentModule is the module holding the seat mesh-node-enroller.
EnrolmentModule = "mesh-enrolment"
// PlacementBucket and SeatHoldersBucket are the assigner's states placement and seat-holders (reads:
// mesh-delivery-assigner.placement; the seat-holders state is read as a named reader), PlacementRead the address of the first.
PlacementBucket = AssignerModule + "_placement"
PlacementRead = AssignerModule + ".placement"
// AssignmentsBucket and SeatHoldersBucket are the assigner's states assignments and seat-holders (reads:
// mesh-delivery-assigner.assignments; the seat-holders state is read as a named reader), AssignmentsRead the address of the first.
AssignmentsBucket = AssignerModule + "_assignments"
AssignmentsRead = AssignerModule + ".assignments"
SeatHoldersRead = AssignerModule + ".seat-holders"
SeatHoldersBucket = AssignerModule + "_seat-holders"
AssignerWantsBucket = AssignerModule + "_wants"
PlacementCandidatesBucket = AssignerModule + "_candidates"
PlacementProposalsBucket = AssignerModule + "_placement-proposals"
CandidatesBucket = IndexerModule + "_candidates"
ModulesBucket = IndexerModule + "_modules"
ManifestsBucket = IndexerModule + "_manifests"
VerdictsBucket = IndexerModule + "_verdicts"
AssignerWantsBucket = AssignerModule + "_wants"
AssignmentsCandidatesBucket = AssignerModule + "_candidates"
AssignmentsProposalsBucket = AssignerModule + "_assignments-proposals"
CandidatesBucket = IndexerModule + "_candidates"
ModulesBucket = IndexerModule + "_modules"
ManifestsBucket = IndexerModule + "_manifests"
VerdictsBucket = IndexerModule + "_verdicts"
EnrolmentsState = "enrolments"
EnrolmentsBucket = EnrolmentModule + "_" + EnrolmentsState
@@ -32,7 +32,7 @@ func namesAreTheManifests(t *testing.T) {
{"reads", HaveBucket[:strings.Index(HaveBucket, "_")] + ".have", HaveBucket},
{"reads", NodeLoginsRead, NodeLoginsBucket},
{"reads", EnrolmentsRead, EnrolmentsBucket},
{"invokes", PlacementRead, PlacementBucket},
{"invokes", AssignmentsRead, AssignmentsBucket},
{"invokes", AssignerModule + ".wants", AssignerWantsBucket},
{"invokes", IndexerModule + ".modules", ModulesBucket},
{"invokes", IndexerModule + ".manifests", ManifestsBucket},
@@ -740,7 +740,7 @@ func PermissionsFor(p Principal) (Permissions, error) {
// its own modules reads the other nodes' public records, which its firewall and resolver need (ADR 0547 §2). Read
// only: each node still writes its own keys alone.
pub = append(pub, readGrants(HaveBucket, "*."+HaveNodeWord)...)
// And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-placement's placement and
// And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-module-assigner's assignments and
// candidates, the indexer's modules and manifests, the builder's builds (the manifest of a build its want names), and mesh-config's resolved values of
// its own node alone. Read only.
pub = append(pub, NodeComposeReads(p.Node)...)
@@ -779,7 +779,7 @@ func PermissionsFor(p Principal) (Permissions, error) {
}
sub = append(sub, subject)
}
// And the creation of the seat mesh-placement's two states it composes from (ADR 0567 §9), so a node agent started
// And the creation of the seat mesh-module-assigner's two states it composes from (ADR 0567 §9), so a node agent started
// before they exist binds each when the bus says it was made, with no timer; no other bucket's.
sub = append(sub, NodeComposeAdvisories()...)
// And, while the vault serves it, the vault's move-in in its own name and nobody else's (issue 676): the
@@ -1535,7 +1535,7 @@ func CreateConsumerGrants(consumers []ModuleConsumer) []string {
}
// GrantsModule is the one module whose principal publishes the modules' memberships (novox/hq ADR 0547 §5): the grants
// part, which composes them from the manifests and placement. It defines no stream and is granted no other subject here.
// part, which composes them from the manifests and assignments. It defines no stream and is granted no other subject here.
const GrantsModule = "mesh-grants"
// The grants part's state memberships (novox/hq ADR 0571 clause 7): one key per assignment, `<node>.<module>`, its
@@ -31,22 +31,22 @@ const ProvisionerSecret = "provisioner-broker"
var ConfigResolvedBucket = BucketName("mesh-config", "resolved")
// ProvisionerReads are the domains' buckets a provisioner client reads its own machine's keys of, each keyed
// `<node>.…` (research 051): placement, the controller's assignments mirror (`<node>.<module>`), whose value carries
// `<node>.…` (research 051): assignments, the controller's assignments mirror (`<node>.<module>`), whose value carries
// the provisioner's plan of a module handed to it (inventory.Provisioning, design 57 P5); and config,
// mesh-config's resolved values; and the seat mesh-provisioner's plans of its machine, `<node>.<module>` (novox/hq ADR
// 0570 clause 2). **Named one by one**, as the controller's asks are: a domain that moves to the
// provisioner adds its bucket here, and a bucket not named is one the client cannot read.
var ProvisionerReads = []string{ConfigResolvedBucket}
// ProvisionerReadsWhole are the buckets a provisioner client reads every key of: the seat mesh-placement's placement,
// ProvisionerReadsWhole are the buckets a provisioner client reads every key of: the seat mesh-module-assigner's assignments,
// where a provider's consumers are placed on their own machines, which the provider machine's client reads to fill
// ${consumers:<provision>} with each one's credential (novox/hq ADR 0373, issue 679; ADR 0567, issue 1126: the
// controller's assignments mirror it read before is gone). It holds placement, never a secret's value; the client
// controller's assignments mirror it read before is gone). It holds assignments, never a secret's value; the client
// writes none of it. **The seat's plans are read whole for the same
// reason** (2026-10-30 recovery, issue 774): the client V3 watches every machine's plans to bind a provider's
// consumers, and granted only its own machine's keys it read none. ADR 0570 clause 3 has providers act on the seat's
// provision keys instead; once the client does, plans go back to its own machine's keys.
var ProvisionerReadsWhole = []string{PlacementBucket, ProvisionerPlansBucket}
var ProvisionerReadsWhole = []string{AssignmentsBucket, ProvisionerPlansBucket}
// BoundState is a providing module's state of entries, one per consumer (mesh-sdk go/provider, issue 678): a
// consumer's machine's client reads its own keys of it, `<node>.<module>.<provision>[.<local>]`, to fill
@@ -334,24 +334,24 @@ func stateNames(buckets []Bucket) []string {
const ResolvedBucket = "mesh-config_resolved"
// NodeComposeReads is what a node's agent reads to compose its own modules (novox/hq ADR 0547 §2, ADR 0567 §3, §9,
// issue 1126): every key of the seat mesh-placement's placement and candidates (both the assigner's states), of the
// issue 1126): every key of the seat mesh-module-assigner's assignments and candidates (both the assigner's states), of the
// indexer's modules and manifests and of the builder's builds, and of mesh-config's resolved values only its own node's,
// `<node>.>`. Every candidate key, not only `*.<node>`: a node answering its candidate composes with the whole
// proposal laid over (the seed places every node at once, and a binding crosses nodes), and nothing in a placement is
// proposal laid over (the seed places every node at once, and a binding crosses nodes), and nothing in assignments is
// secret. Reads only; the node is checked as every subject token is by the caller's principal.
func NodeComposeReads(node string) []string {
var out []string
for _, bucket := range []string{PlacementBucket, PlacementCandidatesBucket, ModulesBucket,
for _, bucket := range []string{AssignmentsBucket, AssignmentsCandidatesBucket, ModulesBucket,
ManifestsBucket, builds.BuilderBuildsBucket} {
out = append(out, readGrants(bucket, "")...)
}
return append(out, readGrants(ResolvedBucket, node+".>")...)
}
// NodeComposeAdvisories are the stream-created advisories of the seat mesh-placement's placement and candidates
// NodeComposeAdvisories are the stream-created advisories of the seat mesh-module-assigner's assignments and candidates
// (ADR 0567 §9): what a node agent hears to bind each once it is made. Those two and no wider.
func NodeComposeAdvisories() []string {
return []string{StreamCreatedAdvisory(PlacementBucket), StreamCreatedAdvisory(PlacementCandidatesBucket)}
return []string{StreamCreatedAdvisory(AssignmentsBucket), StreamCreatedAdvisory(AssignmentsCandidatesBucket)}
}
// ServedReadGrants is the read of each key of mesh-config's resolved values named, `<node>.<module>.<key>`
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
"rows": [
{
"name": "module",
"held": "{\"url\":\"nats://10.0.0.1:4222\",\"fingerprint\":\"ab:cd\",\"node\":\"laptop\",\"module\":\"cap-bot\",\"user\":\"laptop.cap-bot\",\"password\":\"pw-module\",\"claims\":[{\"seat\":\"mesh-vault\",\"scope\":\"mesh\",\"serves\":[\"get\",\"accept\",\"secret-ask\",\"issued\",\"rotate\",\"move-in\",\"node-key\",\"own\",\"move-asks\"]},{\"seat\":\"node-service-manager\",\"scope\":\"node\",\"serves\":[\"status\"]},{\"seat\":\"mesh-controller\",\"scope\":\"mesh\",\"serves\":[\"tools\",\"calls\",\"status\",\"nodes\",\"node\",\"placement-export\",\"modules\",\"seats\",\"builds\",\"plans\",\"delivery-plan\",\"delivery-order\",\"delivery-check\",\"deliver\",\"delivery-stop\",\"delivery-backups-again\",\"delivery-walks\",\"plan\",\"assign\",\"unassign\",\"pin\",\"unpin\",\"push\",\"rotate\",\"give\",\"issued\",\"secret-move\",\"secret-ask\",\"issue\",\"token\",\"settings\",\"command\",\"queue\",\"cancel\",\"clear\",\"replay\",\"try\",\"kill\",\"pause\",\"resume\",\"hand-act\",\"drill\",\"warranted\",\"hand-acts\",\"durations\",\"conditions\",\"healers\",\"doctor\",\"upgrade\",\"bus\",\"retire\",\"cleanup\",\"judge-directories\",\"root-free\",\"dead-letters\",\"data\",\"build\",\"artifacts\",\"held\",\"collect\",\"move-placement\",\"move-config\",\"mirrors\",\"images\"]},{\"seat\":\"no-such-seat\",\"scope\":\"node\"}]}"
"held": "{\"url\":\"nats://10.0.0.1:4222\",\"fingerprint\":\"ab:cd\",\"node\":\"laptop\",\"module\":\"cap-bot\",\"user\":\"laptop.cap-bot\",\"password\":\"pw-module\",\"claims\":[{\"seat\":\"mesh-vault\",\"scope\":\"mesh\",\"serves\":[\"get\",\"accept\",\"secret-ask\",\"issued\",\"rotate\",\"move-in\",\"node-key\",\"own\",\"move-asks\"]},{\"seat\":\"node-service-manager\",\"scope\":\"node\",\"serves\":[\"status\"]},{\"seat\":\"mesh-controller\",\"scope\":\"mesh\",\"serves\":[\"tools\",\"calls\",\"status\",\"nodes\",\"node\",\"assignments-export\",\"modules\",\"seats\",\"builds\",\"plans\",\"delivery-plan\",\"delivery-order\",\"delivery-check\",\"deliver\",\"delivery-stop\",\"delivery-backups-again\",\"delivery-walks\",\"plan\",\"assign\",\"unassign\",\"pin\",\"unpin\",\"push\",\"rotate\",\"give\",\"issued\",\"secret-move\",\"secret-ask\",\"issue\",\"token\",\"settings\",\"command\",\"queue\",\"cancel\",\"clear\",\"replay\",\"try\",\"kill\",\"pause\",\"resume\",\"hand-act\",\"drill\",\"warranted\",\"hand-acts\",\"durations\",\"conditions\",\"healers\",\"doctor\",\"upgrade\",\"bus\",\"retire\",\"cleanup\",\"judge-directories\",\"root-free\",\"dead-letters\",\"data\",\"build\",\"artifacts\",\"held\",\"collect\",\"move-assignments\",\"move-config\",\"mirrors\",\"images\"]},{\"seat\":\"no-such-seat\",\"scope\":\"node\"}]}"
},
{
"name": "module-no-claims-no-fingerprint",
File diff suppressed because one or more lines are too long
@@ -75,7 +75,7 @@ type Declared struct {
// 0495 clause 3, issue 1790): granted nothing, kept to be said.
ReadsRefused []string
// ReadLogs are, of its reads, those naming another module's log (`<module>.<log>`, novox/hq ADR 0571 clause 1): filled
// by Users and PlacementsOf from the logs the modules placed anywhere declare, never by a caller.
// by Users and AssignmentsOf from the logs the modules placed anywhere declare, never by a caller.
ReadLogs []string
// KeyedReads are keys of other modules' state it reads, each one key alone: what a seat's holder is
// granted for the pieces the modules beside it offer (novox/hq ADR 0260).
@@ -231,7 +231,7 @@ func Users(r Records) ([]Principal, error) {
}
}
for _, d := range r.Assigned[node] {
// **The node-engine's manifest is no placed module's** (novox/hq ADR 0563 §2, §3; issue 774): where a placement
// **The node-engine's manifest is no placed module's** (novox/hq ADR 0563 §2, §3; issue 774): where assignments
// names it, it adds no user, no process and nothing the runner carries; its grants are the node's account's.
if !Composed(d) {
continue
@@ -541,7 +541,7 @@ func boundBucketsOf(r Records) []string {
}
// Composed says a placed module is composed as one: its own user, its processes and what the runner carries of it.
// The node-engine's manifest (EngineModule) is not, though a placement may name it: each node's engine speaks as the
// The node-engine's manifest (EngineModule) is not, though assignments may name it: each node's engine speaks as the
// node's account, which alone writes the engine's state under its own key (engineGrants, novox/hq ADR 0563 §2, §3), and
// Records.Engine creates that state. Granted to another account it is a second writer, and the writers table refuses
// the whole list (issue 774: met for mesh-host_acts and then mesh-host_health, 2026-10-30).