The grants part grants on them, the tool runner appends and snapshots on them and the bus module creates the history over them, so the three read one contract (the lead's ruling on issue 1966 B1; ADR 0597 §8, §9).
352 lines
20 KiB
Go
352 lines
20 KiB
Go
// 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"
|
|
// KindLedger is one module assignment's ledger credential (novox/hq ADR 0597 §9, design 25 § 12.4): held by that
|
|
// machine's tool runner, never by the module's own code, and the only account that publishes on that module's ledger
|
|
// histories and snapshots. One per module assignment, whose module asks for a ledger in `requires`.
|
|
KindLedger Kind = "ledger"
|
|
)
|
|
|
|
// LedgerHistorySubject and LedgerSnapshotSubject are the subject prefixes of one ledger's history and of its instances'
|
|
// snapshots (novox/hq ADR 0597 §1, §8, §9): an instance's subject is the prefix, ".", and the instance's path with each
|
|
// "/" written "." (`decision/0290` is `….decision.0290`). One contract: the grants part grants a ledger account its
|
|
// publish on them, the tool runner appends and snapshots on them, and the bus module creates the history's stream over
|
|
// them.
|
|
func LedgerHistorySubject(module, local string) string {
|
|
return "mesh.ledger." + module + "." + local + ".history"
|
|
}
|
|
|
|
// LedgerSnapshotSubject is the subject prefix of one ledger's instances' snapshots (LedgerHistorySubject).
|
|
func LedgerSnapshotSubject(module, local string) string {
|
|
return "mesh.ledger." + module + "." + local + ".snapshot"
|
|
}
|
|
|
|
// 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 <node>.<module>.<process id>.
|
|
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 `<module>.<tool>`; 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 `<module>.<tool>` (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 `<module>.<name>` (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
|
|
// `<module>.<tool>` (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 (`<node>.<module>.<key>`, 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, `<module>_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.<n>`: its second login, `node.<n>--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.<n>`; 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): `<machine>.<module>.<process id>`,
|
|
// 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 KindLedger:
|
|
// `ledger.<machine>.<module>`: never `<node>.<module>`, which is the module's own account or the tool runner's,
|
|
// so the module's code never holds it.
|
|
return "ledger." + p.Node + "." + p.Module
|
|
case KindProvisioner:
|
|
// Named for the machine, as a node is: one client per machine, whichever module runs it, and never
|
|
// `<node>.<module>`, 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, `<node>.<module>`, 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: `<node>.<module>`.
|
|
func MembershipKey(node, module string) string { return node + "." + module }
|