// Package link is the control plane's side of the connection nodes hold open. // // novox/hq ADR 0002: nodes communicate over a message broker, not over HTTP. One exchange, and // the control plane is the single consumer behind it — ADR 0006 makes that a property worth // having rather than an accident, because two consumers sharing a queue silently split the // traffic between them, each receiving half of what it expects. That has happened here before. package link // Exchange is where nodes publish everything they have to say. const Exchange = "mesh" // ControlQueue is what the control plane consumes. One queue, one consumer. const ControlQueue = "control" // Routing keys. A node may publish these; it may not publish anything else, because its broker // account is scoped to this exchange and its own queue. const ( KeyEnrol = "enrol" KeyReport = "report" KeyAlive = "alive" ) // QueueFor is the queue a node consumes from — the only one it may read. func QueueFor(node string) string { return "node." + node } // EnrolRequest is what a joining node says. // // It arrives on a connection the broker has already authenticated, because the account was // created when the token was issued and the token's secret is its password. So this message is // not how a node gets in — it is what it says once it is in. type EnrolRequest struct { // Node is what this machine believes it is called. Checked against the token, never trusted. Node string `json:"node"` // Secret is the one-time right to join. The account password and this are the same string, // which is deliberate: the broker proves somebody holds the token, and this proves the same // thing to the control plane without the control plane having to ask the broker who connected. Secret string `json:"secret"` // PublicKey is what the mesh will believe from now on. The node generated it; the private // half has never left that machine (novox/hq ADR 0004). PublicKey []byte `json:"public_key"` // OverlayKey is the public half of this node's key on the private network — a different key // from PublicKey, and the mesh only ever sees this half. OverlayKey string `json:"overlay_key,omitempty"` // SealingKey is the public half of the key this node's secrets are sealed to. A third key, // and the reasoning is the same one twice over: the mesh must be able to send this node // something nothing else can read, and it must never be able to read it either. SealingKey string `json:"sealing_key,omitempty"` // ServingKey is the public half of the key this node serves TLS with on its internal name. // The mesh signs a certificate binding it; the private half never leaves the machine, so // there is nothing to seal and a copy of what the mesh holds certifies nothing new. ServingKey string `json:"serving_key,omitempty"` // Profile is what this machine can be asked to do. The control plane cannot decide what a // node should run without it, so it arrives with enrolment rather than being asked for after. Profile map[string]any `json:"profile,omitempty"` } // Signed is a declaration and the signature over it. // // The signature is over Declaration exactly as it will arrive, bytes unchanged — a node verifies // what it received rather than what it re-encoded, because any difference in key order or spacing // would break a signature over the same meaning. type Signed struct { Declaration []byte `json:"declaration"` Signature []byte `json:"signature"` } // Alive is a node saying nothing except that it is there. // // How long a node has been out of touch is a fact only the mesh can hold — nobody else is // watching — and without it a node running last month's assignments looks exactly like one that // is current. type Alive struct { Node string `json:"node"` } // Report is what a node states after applying. It states; the owning context writes. type Report struct { Node string `json:"node"` Applied []string `json:"applied,omitempty"` Failed map[string]string `json:"failed,omitempty"` Refused string `json:"refused,omitempty"` // Carried are the machine's ports held by what that host raised from its own bundle. // // **The half the mesh cannot know** (novox/hq ADR 0038). The substrate is not a module — a // node raises it before any mesh exists — so without being told, the mesh assigns a module a // port the store or the broker already holds, and hears about it from a container runtime. // // The node states and this context writes, which is the shape of every message here. Carried []int `json:"carried,omitempty"` } // EnrolReply is what the mesh says back. type EnrolReply struct { // Accepted says whether the node is now known. Accepted bool `json:"accepted"` // Node is the name the mesh has for this machine, which settles any disagreement: the token // was issued for a node record, and that record's name wins over what the machine called // itself. Node string `json:"node,omitempty"` // Queue is where this node listens from now on. Queue string `json:"queue,omitempty"` // Password is this node's own broker account from now on, replacing the token's secret. // A credential that lives for as long as the node should not be the same string as one that // was meant to be used once. Password string `json:"password,omitempty"` // Fingerprint and Signer are what the node keeps so it can reconnect and keep verifying // without a person and a new token. Fingerprint string `json:"fingerprint,omitempty"` Signer []byte `json:"signer,omitempty"` Broker string `json:"broker,omitempty"` // Refusal says why not, in words for a person. Deliberately the same for every reason a // token can fail — unknown, spent, expired — so that guessing learns nothing. Refusal string `json:"refusal,omitempty"` }