Author SHA1 Message Date
mesh-admin c82e933268 Merge pull request 'Genesis: the first user list lets the controller hear the forge's merges' (#38) from fix/genesis-follows-the-forge into main 2026-09-28 01:13:18 +00:00
jschoubben e212da6bd2 Genesis: the first user list lets the controller hear the forge's merges 2026-09-28 03:13:16 +02:00
mesh-admin 673912ccdc Merge pull request 'A host adopts a delivered membership at start, not only after a declaration' (#37) from feat/a-host-adopts-a-membership-at-start into main 2026-09-28 00:40:27 +00:00
jschoubben 68a193d6f0 A host adopts a delivered membership at start, not only after a declaration
The rescue path: an operator writes the membership file by hand on a machine no
bus can reach — rotated while it still held the old password — and restarts the
host. Same file, same check as the declaration path.
2026-09-28 02:21:56 +02:00
jschoubben 4fba884d47 Merge pull request 'Genesis: the first user list lets the controller hear its consumers, and the account has JetStream' (#36) from fix/genesis-accounts-hear-and-have-jetstream into main 2026-09-27 23:47:41 +00:00
jschoubben 15f0dabf32 Genesis: the first user list lets the controller hear its consumers, and the account has JetStream
Two things the controller's own composition now derives and the installer's
carried list did not: the delivery subjects of the controller's consumers, and
JetStream enabled on the account — without which the first bound consumer is
refused. Found live on a mesh moved rather than raised; a fresh genesis would
have met both at first start.
2026-09-28 01:46:14 +02:00
jschoubben 4ef41ad5a0 Merge pull request 'A host logs in as node.<name> and hears answers on its own inbox' (#35) from fix/a-host-uses-its-own-inbox into main 2026-09-27 23:18:30 +00:00
jschoubben d749de989c A host hears the server's answers on its own inbox
The mesh grants a machine exactly its own inbox prefix; the client used the
default one and was refused a subscription to it.
2026-09-28 01:16:23 +02:00
jschoubben 14e64844df A host logs in to the new bus as node.<name>
The mesh names a machine's bus user node.<name>; the host dialled with the bare
name and every machine was refused the first time it reached the server:
"authentication error - User". Same string as the composed user list, or nothing
connects.
2026-09-28 01:13:52 +02:00
jschoubben d82fc9a121 Merge pull request 'A machine moves to the bus its declaration tells it to' (#34) from feat/a-machine-moves-to-the-bus-it-is-told into main 2026-09-27 22:09:08 +00:00
3 changed files with 15 additions and 4 deletions
+7 -2
View File
@@ -962,9 +962,14 @@ func runLink(ctx context.Context, opts options) error {
return nil // asked to stop while waiting
}
fmt.Printf("node %s, linking to %s\n", mine.Node, mine.Membership.Broker)
// **A membership delivered while this host was not running is adopted before the first dial.**
// The ordinary path is a declaration, read after it applies; the rescue path is an operator
// writing the file by hand on a machine no bus can reach — rotated while it held the old
// password, say — and restarting the host (design 28, task 5.2). Same file, same check.
say := func(line string) { fmt.Println(line) }
adoptDeliveredMembership(identity.Path(opts.state), &mine, say)
fmt.Printf("node %s, linking to %s\n", mine.Node, mine.Membership.Broker)
// One scheduler for the life of the process, re-established from each applied declaration
// (novox/hq ADR 0053). It fires scheduled steps on their cadence, surviving across applies and
+1 -1
View File
@@ -152,7 +152,7 @@
"type": "file",
"path": "/var/lib/mesh-bus-conf/accounts.conf",
"mode": "0600",
"content": "// The first user list, carried by the installer because at genesis there is no mesh to\n// compose one. A bootstrap credential, rotated with the store's and replaced by the\n// controller's own composition from its first start onward.\naccounts {\n MESH {\n users = [\n { user: \"controller\", password: \"$2a$10$AHqJgOifIVbU41KmATiMhuXFs8xa7Wl2HuN4UVBCXdN2jIQzjqApy\", permissions: {\n publish: { allow: [\"$JS.API.>\", \"$JS.ACK.CONTROL.controller.>\", \"$JS.ACK.EVENTS.controller.>\", \"_INBOX.enrol.>\", \"mesh.control.>\", \"mesh.node.>\", \"mesh.seat.mesh-build-machine.accept.>\"] }\n subscribe: { allow: [\"$JS.API.>\", \"_INBOX.controller.>\", \"mesh.control.>\", \"mesh.mod.mesh-catalog.event.catching-up\", \"mesh.mod.mesh-catalog.event.upgraded\", \"mesh.seat.mesh-build-machine.event.built\"] }\n allow_responses: { max: 1, ttl: \"1m\" }\n } }\n ]\n }\n}\n"
"content": "// The first user list, carried by the installer because at genesis there is no mesh to\n// compose one. A bootstrap credential, rotated with the store's and replaced by the\n// controller's own composition from its first start onward.\naccounts {\n MESH {\n jetstream: enabled\n users = [\n { user: \"controller\", password: \"$2a$10$AHqJgOifIVbU41KmATiMhuXFs8xa7Wl2HuN4UVBCXdN2jIQzjqApy\", permissions: {\n publish: { allow: [\"$JS.API.>\", \"$JS.ACK.CONTROL.controller.>\", \"$JS.ACK.EVENTS.controller.>\", \"_INBOX.enrol.>\", \"mesh.control.>\", \"mesh.node.>\", \"mesh.seat.mesh-build-machine.accept.>\"] }\n subscribe: { allow: [\"$JS.API.>\", \"_DELIVER.controller\", \"_DELIVER.controller.>\", \"_INBOX.controller.>\", \"mesh.control.>\", \"mesh.mod.mesh-catalog.event.catching-up\", \"mesh.mod.mesh-catalog.event.upgraded\", \"mesh.mod.gitea.event.pull.merged\", \"mesh.seat.mesh-build-machine.event.built\"] }\n allow_responses: { max: 1, ttl: \"1m\" }\n } }\n ]\n }\n}\n"
},
{
"id": "broker",
+7 -1
View File
@@ -70,7 +70,13 @@ func dialNats(ctx context.Context, m Membership, timeout time.Duration) (Link, e
}
opts := []nats.Option{
nats.Secure(config),
nats.UserInfo(m.Node, m.Password),
// The mesh names a machine's bus user "node.<name>" (the controller's principal scheme), and
// the server refused the bare name the first time a machine dialled it: "authentication
// error - User". The same string the mesh composed into the user list, or nothing connects.
nats.UserInfo("node."+m.Node, m.Password),
// Replies to what this client asks the server arrive on its inbox, and the mesh grants a
// machine exactly its own: the same prefix the user list was composed with.
nats.CustomInboxPrefix("_INBOX.node." + m.Node),
nats.Name("mesh-host/" + m.Node),
nats.Timeout(timeout),
// A node that has silently lost its route notices, rather than holding a connection the