// Composing the bus's own configuration. // // An account is *composed*, never called for: the controller writes accounts, users and // per-subject permissions into one file the host keeps current, and the server reloads it in // place (novox/hq ADR 0106 — never through a management API; design 25 §4). // // Everything here is pure. Given the principals, it returns the file's text — so the whole of the // mesh's authority model is testable as strings, with no server. // // **Permissions are per subject, so a module's own name is the server's to enforce.** ADR 0042 // reserves a module's origin — it publishes only under its own name — and here that is a refusal // rather than something a library promises. package buscompose import ( "regexp" "time" ) // A Kind is what a principal is, which decides the shape of its authority rather than its // contents: a module's comes from its declaration, a host's from its node, and the controller's // and the enrolment user's are fixed. type Kind string const ( KindModule Kind = "module" KindNode Kind = "node" KindEnrolment Kind = "enrolment" // KindPerson is somebody reaching the mesh's tools from a workstation (design 25 §7). Its // authority is a list of tools and nothing else — not control, not declarations, not builds, // and no ability to answer anything, because a person asks. KindPerson Kind = "person" // KindNodeTools is a machine's tool runtime (novox/hq ADR 0175, to-be 38): one process per // node, on the host side, serving every assigned module's tools and every held seat's verbs. // Its authority is the union of what the modules it carries would each have had for their // tools — and nothing of what they consume, because tools are what it runs, not reactions. KindNodeTools Kind = "node-tools" // KindProvisioner is a machine's provisioner client (novox/hq ADR 0349 clauses 6 and 11, design 57 P3): a // service of the module holding NodeProvisionerSeat, outside the tool runner, under an account of its own so // the vault can tell it from an agent. Its authority is provisionerGrants and nothing else. KindProvisioner Kind = "provisioner" // KindProcess is one process of a module that declares bus (novox/hq ADR 0436 §1, §2): a service of that // module, outside the tool runner and outside the module's own account, under a bus account scoped to // exactly what its bus says — never root, never the operator account, and never able to answer a tool // call or a seat's verb (§3). One per process and machine. KindProcess Kind = "process" ) // Seat is a role on the bus as a principal relates to it: the subjects it accepts, and those it // emits (novox/hq ADR 0118, design 29 §5). type Seat struct { Name string // Scope is where the seat has one holder. A node-scoped seat's tool carries the node in its // subject, because one subject reaching six machines' holders is not an address // (novox/hq ADR 0132, design 33 §4). Empty reads as mesh. Scope string Accepts []string Emits []string Serves []string Versions []string // protocol versions served beside the current one; empty for v1 only // Kinded says the seat is a kinded bench (novox/hq ADR 0234 §2, ADR 0259 §3): each holder claims one // kind, and its verbs' subjects carry it. Kind is the kind this principal's claim names, for a seat it // holds. Kinded bool Kind string // ByCaller are the accepts and emits whose last token names the calling module (ADR 0259 §3). ByCaller []string // Proofs are the seat's proof verbs: core request and reply, never on a stream (ADR 0259 §3). Proofs []string // Records are the holder's buckets, by their full name, each user reads under its own name. Records []string // Capabilities are what this principal's claim of a kinded bench promises (ADR 0234 §2). Capabilities []string // DeclaredBy is the module that declares the seat. On a kinded bench it alone submits work to a kind // and answers its proofs (novox/hq ADR 0259 §8): the router, not any user or watcher of the bench. DeclaredBy string // PulledByKind says each accepted kind is a worker of its own on the seat's queue, filtered to that kind, which // every holder pulls in the order Accepts lists them: the build seats' (novox/hq ADR 0525 §2, ADR 0536 §4). PulledByKind bool } // A Principal is one user of the bus. Its permissions are derived from what it declares and // nothing else (novox/hq ADR 0043), over the three namespaces of design 29 §2: its own, the seats // it holds, and the seats it uses. type Principal struct { Kind Kind Node string Module string // Process is the process resource's id, for a KindProcess principal alone (novox/hq ADR 0436 §2): // Username and inbox are ... Process string // FilesFor are the nodes whose declaration-file buckets (novox/hq ADR 0486 §2) this principal may remove, for the bus // module's tools removing the ended controller's leftovers alone (ADR 0590 clause 6): every node of the mesh, one // bucket each. FilesFor []string Emits []string Consumes []string Serves []string Holds []Seat Uses []Seat // Watches are seats whose events this principal consumes. Separate from Consumes because a // role's event lives under the seat's namespace and not a module's, and this package cannot tell // a seat's name from a module's by looking at it — whoever resolved the declaration can, and // does (novox/hq ADR 0121). // // **Found by a consumer reading nothing.** The catalogue consumes the build machine's outcome; // with that name read as a module's, its subscription pointed at `mesh.mod.mesh-build-machine.…`, // a namespace no such module owns. Every service started and the graph stayed empty. Watches []Seat // Carries are the modules whose tools this principal serves, for a KindNodeTools principal // (novox/hq ADR 0175): every module assigned to its node, as each declares itself. Its // serving authority is the union of theirs — each module's own tool namespace and each held // seat's verbs on this node — derived from the same declarations the modules' own principals // are, so the runtime can serve nothing a module could not have served for itself. Carries []Declared // Invokes are the tools this principal may call, as `.`; a single `*` is every // tool. A person's whole authority (design 25 §7), and a module's only if its manifest says so // (novox/hq ADR 0152) — the console's does, and nothing else's. // // **A list, not a role.** A person is not a module and holds no seat: nothing is addressed // to them, nothing is delivered to them, and they have no durable consumer to acknowledge. // What they have is permission to ask. A module that invokes gains exactly the same // permission and nothing beside it. Invokes []string // AsksIssued are, for the vault's holder, the tools it asks in its own name for a credential a provider's software // issued, each `.` (novox/hq ADR 0305, issue 723, vaultCalls): on the subject that names its caller, // so the tool knows the vault asks. AsksIssued []string // AsksTheDesk says this principal is the vault's holder, which asks the desks' secret prompt in its own name // (novox/hq ADR 0349 clause 9, issue 723): `node-launcher.secret` on any machine, on the subject that names // its caller, and nothing else of that seat. With AsksIssued on the same principal, the vault's whole reach // outside its own seat: it asks each issuing provider's tool in its own name (clause 10). AsksTheDesk bool // State is the local names of the state this principal's module keeps, and Reads the state of // others it reads as `.` (novox/hq ADR 0201): a bucket each, kept by the owner's // instances and read by whoever declares it. State []string Reads []string // PerMachine is the local names of its state keyed by machine, reaching the key named for Node and // no other; KeyedReads the keys of others' state it reads one key at a time (novox/hq ADR 0260). PerMachine []string KeyedReads []KeyedRead // Logs is the local names of the logs this principal's module keeps (novox/hq ADR 0297). Logs []string // ReadLogs are, of Reads, those naming another module's log (novox/hq ADR 0571 clause 1). ReadLogs []string // Streams are the streams this principal's module owns, and Hears the module streams it hears with the // subjects it was granted (novox/hq ADR 0363). Streams []ModuleStream Hears []Heard // Queues are its own work-queue streams and the one process each is the consumer of (novox/hq ADR 0436 // §1, §2, §6); for a KindProcess principal, its own alone. Queues []QueueOwner // Asks says a KindProcess principal reads the records of its module's own asks (novox/hq ADR 0436 §1), // on whichever seat of Uses keeps them (novox/hq ADR 0259 §3) — never set for any other kind. Asks bool // SnapshotsTheBus is the bus's own module, the one holding mesh-broker (novox/hq ADR 0235). Its // whole authority is BusSnapshotGrants: it copies the streams for the night's backup and nothing // else — not its declarations, which the node's tool runtime serves for it. SnapshotsTheBus bool // Removes are, for a process or tools bundle account whose bus declares them, what it may remove from the bus // (novox/hq ADR 0564 Limits 4): its whole authority is then BusRemovalGrants of them. Removes []string // DeadLetterSources are, for the watchman, the streams a dead letter it keeps may come from, by name // (deadLetterSourcesOf, novox/hq ADR 0564 clause 5): never a bucket's. DeadLetterSources []string // Checks are the tools a machine principal's node-engine asks as a declared health check, each // `.` (novox/hq ADR 0240, to-be 48 §3): asked of the instance on its own machine and // nowhere else. Checks []string // MovesIntoTheVault is a machine principal whose node-engine may ask the vault's `move-in` in its own name (design // 57's V3, issue 676): set while the vault's holder serves it, so the engine can hand in the values sealed to its // machine, which nothing else can open. MovesIntoTheVault bool // StatesItsKeyToTheVault is a machine principal whose node-engine may ask the vault's `node-key` in its own name // (issue 786, novox/hq ADR 0372): set while the vault's holder serves it. That one verb, and nothing else new. StatesItsKeyToTheVault bool // AsksItsOwnSecrets is a machine principal whose node-engine may ask the vault's `own` in its own name (novox/hq // ADR 0547 §2, ADR 0550 §5, issue 1126): its modules' own secrets sealed to its key, for the compositions it makes // itself. Set while the vault's holder serves it. AsksItsOwnSecrets bool // AsksTheVaultToSign is a machine principal whose node-engine may ask the vault's `sign` in its own name, read the // vault's public signing halves (`signers`) and the controller's lease key (novox/hq ADR 0550 §2): its own node's // certificate, the keys it trusts, and the lease write that drops the controller's key. Set while the vault's holder // serves `sign`. AsksTheVaultToSign bool // ServedReads are, for a machine principal, the keys of mesh-config's resolved values that other nodes' modules serve // from (`..`, catalogue.ServedSettings): what every consumer is told, and so what a node composing // its own modules reads of a provider elsewhere (issue 1126), each key alone. Sorted, none twice. ServedReads []string // FollowsGrantsUserList is the machine principal of the node holding mesh-broker: its node-engine reads the grants // part's sealed user list, one key and an object by name (novox/hq ADR 0547 clause 7, GrantsUserListFollows). FollowsGrantsUserList bool // BoundBuckets are the providers' buckets of entries, `_bound`, a provisioner client reads its own // machine's keys of (mesh-sdk go/provider, issue 679): every bucket named bound of a module the mesh places. BoundBuckets []string // CreatesStreams are the streams the grants part's principal alone may create (novox/hq ADR 0547 §5; the operator, // 2026-10-26 08:54): the stream of every bucket, log and stream a manifest of a module placed anywhere declares, // by name. Created only, never updated or deleted: a new module state is on the bus when the grants part hears its // manifest, not at the controller's next start (issue 1790). CreatesStreams []string // CreatesConsumers are the durable consumers the grants part's principal alone may create beside the controller // (novox/hq ADR 0561 §6): each placed module's on EVENTS and each process's on its work queue (AccountConsumers), by // stream and name. Created only, never updated or deleted. CreatesConsumers []ModuleConsumer // Login is a node principal's login when it is not `node.`: its second login, `node.--b`, composed beside or // in place of the first as the vault's node-logins says (novox/hq ADR 0583 §1, issue 774). Each login is granted // the node's permissions and the node's inbox `_INBOX.node.`; what it calls, it calls in its own name, so the // vault tells the login that confirms a rotation from the one it replaces (§3). Login string // RotatesItsLogin is a machine principal whose node-engine reads its own node's key of the vault's node-logins, // and no other key, and confirms a switch with node-account-confirm in its own name (novox/hq ADR 0583 §1 insight, // §3). Set while the vault's holder serves rotate-node. RotatesItsLogin bool // Redeems is an enrolment principal that may call the seat mesh-enrolment's redeem in its own name, and nothing else // of it (novox/hq ADR 0587 §4): set while the seat's holder serves redeem. LeaseHandedAway drops the controller's // enrolment subject from it, which is served no more from the lease write (§6). Redeems bool LeaseHandedAway bool // VaultEnrols says the vault's holder serves an enrolment verb (VerbsTheEnrolmentAsksTheVault): every principal but // the module mesh-enrolment and its processes is then denied them, however wide its grant (ADR 0587 §1). VaultEnrols bool // PasswordHash is the bcrypt hash the mesh minted. The plaintext is sealed to the principal // and never appears here: this file is written to a node's disk and read by a server, and a // secret that can be read from a configuration file is a secret with a wider blast radius // than the one it protects (novox/hq design 29 §10). PasswordHash string } // enrolmentPrefix is the space every enrolling node's user and inbox live under, so the one place the // controller may answer an enrolment is derived from the same constant the user is named from. const enrolmentPrefix = "enrol" // safeSubject refuses anything that would change the meaning of a subject rather than sit inside // one. A name carrying a dot would silently widen a permission by adding a token; a name carrying // `>` or `*` would widen it to a wildcard, which is the whole authority model gone. var safeSubject = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) // Username is how a principal is named to the server. The node is part of it, so the same module // on two machines holds two users, each sealed to its own — the rule management.go already // applies, kept. func (p Principal) Username() string { switch p.Kind { case KindPerson: return "person." + p.Module case KindModule, KindNodeTools: // The runtime is named exactly as the module it stands for would have been: the mesh // issues its credential through the same path a module's takes (`module issue`), and // that path knows the node and the module, not the kind. return p.Node + "." + p.Module case KindProcess: // One account per process and machine (novox/hq ADR 0436 §2): `..`, // never the module's own name alone, which is a different account with a different secret. return p.Node + "." + p.Module + "." + p.Process case KindNode: if p.Login != "" { return p.Login } return "node." + p.Node case KindProvisioner: // Named for the machine, as a node is: one client per machine, whichever module runs it, and never // `.`, which is the module's own account or the tool runner's. return "provisioner." + p.Node case KindEnrolment: // Per token, not one shared user. **The inbox is the reason**: with a single `enrolment` // user every machine enrolling at once could read every other's answer, and an answer // carries that node's credentials sealed to it. Design 25 §6 says the inbox a token // derives, and a permission belongs to a user, so the user is per token. // // Named after the node, which **is** the token's id: a token is issued for a node record, // the mesh holds one live claim per record, and the node's name is the one identifier both // sides already have before anything else is agreed. It is also exactly what the other // transport does, where the account is named after the node and the secret is its password. return enrolmentPrefix + "." + p.Node } return "" } // inbox is a principal's own reply space. No user is ever granted a bare `_INBOX.>` (design 25 // §4): with one account, inbox privacy is the permission list or it is nothing, so each user's // inbox is derived from its own identity and its permissions name that prefix and no other. // // A node's two logins share the node's inbox (novox/hq ADR 0583 §1): its answers reach it whichever login it dials as. func (p Principal) inbox() string { if p.Kind == KindNode { return "_INBOX.node." + p.Node + ".>" } return "_INBOX." + p.Username() + ".>" } // ResponseTTL is how long the bus lets a principal answer a request it received. Its one answer has // to come inside this, and a seat's holder answers within link.AnswerWithin — inside it by design. // **A broker reloading its user list forgets every answer it was about to permit**, whatever this // says (novox/hq issue 265): a call that is still running when the list reloads has its answer // refused, which is why a holder answers before it does what can reload it. const ResponseTTL = time.Minute // 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. const GrantsModule = "mesh-grants" // The grants part's state memberships (novox/hq ADR 0571 clause 7): one key per assignment, `.`, its // membership (Membership) bare, as the assignments stream carries it, written only when it differs. A module's runtime // reads its own key, the tool runner its node's; the grants part alone writes it. Both sides go through // membership.EncodeGrantsMembership and membership.DecodeGrantsMembership (or BareGrantsMembership for a reader with a // type of its own), never json of a type of their own: the grants part once wrote its wrapper here and every reader // served nothing (issue 774). The decoder still reads that wrapper, for the transition. const ( GrantsMembershipsState = "memberships" GrantsMembershipsBucket = GrantsModule + "_" + GrantsMembershipsState ) // MembershipKey is an assignment's key in the grants part's memberships: `.`. func MembershipKey(node, module string) string { return node + "." + module }