From 7d46e48b26a6fa09fc379a15803d2eaa40b98ef7 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 4 Oct 2026 04:03:50 +0200 Subject: [PATCH] The account's environment and the login shell are the mesh's seats (hq ADR 0203, 0204) node-environment says which module writes the account's environment; node-login-shell replaces the module-declared login-shell, so a second shell claims it rather than declaring a rival, and execute is the mesh's contract. login-shell is refused as a module's seat name. Seeded into a live store by the existing additive seeding. --- internal/catalogue/seats.go | 25 +++++++++++++++++++++++++ internal/catalogue/seats_declared.go | 13 +++++++++++++ internal/catalogue/seats_test.go | 9 +++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/internal/catalogue/seats.go b/internal/catalogue/seats.go index 536f9ec..cec63fc 100644 --- a/internal/catalogue/seats.go +++ b/internal/catalogue/seats.go @@ -150,6 +150,17 @@ var defaultSeats = []Seat{ // served by the node tools runtime (ADR 0175). {Name: "node-service-manager", Scope: ScopeNode, Decision: "novox/hq ADR 0177", Serves: serviceManagerVerbs()}, + // The operator account's environment (novox/hq ADR 0203): one module per machine writes it, and + // every module contributes to it. No verbs — the seat says who places the environment's files, + // and their path is its protocol: a shell sources ~/.config/mesh/environment.sh without knowing + // which module wrote it. + {Name: EnvironmentSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0203"}, + // The login shell (novox/hq ADR 0204, replacing the module-declared `login-shell` of ADR 0176): + // the mesh's, so a second shell module claims the seat rather than declaring a second one, and + // the seat exists whether or not zsh's definition is registered. `execute` is the contract any + // node may call; the holder places every module's shell code in its slots. + {Name: LoginShellSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0204", + Serves: loginShellVerbs()}, // Deferred (novox/hq ADR 0121): renaming to mesh-private-network is a scope + server/client // model change, not a rename, so it stays until that is built. {Name: "the-private-network", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, @@ -456,3 +467,17 @@ func serviceManagerVerbs() []Verb { Input: scoped(map[string]string{"unit": unit["unit"], "lines": "how many lines from the end (default 100)"}, []string{"unit"})}, } } + +// loginShellVerbs is the contract every holder of node-login-shell serves (novox/hq ADR 0176, ADR +// 0204): one command, run the way the operator's own terminal would run it, bounded below the +// runtime's thirty-second call limit so a hung command answers rather than times the caller out. +func loginShellVerbs() []Verb { + return []Verb{ + {Name: "execute", Description: "Run one command on this machine as the operator account, in a " + + "non-interactive login shell in its home; answers with what it printed and how it exited.", + Input: schema(map[string]string{ + "command": "the command line, as you would type it", + "timeout_seconds": "give up after this long, at most 25 (default 20)", + }, []string{"command"})}, + } +} diff --git a/internal/catalogue/seats_declared.go b/internal/catalogue/seats_declared.go index 4afb221..5fb89bd 100644 --- a/internal/catalogue/seats_declared.go +++ b/internal/catalogue/seats_declared.go @@ -25,6 +25,10 @@ import ( // and nothing to keep in step when a mesh seat is added. const meshSeatPrefix = "mesh-" +// retiredLoginShell is the one name outside the prefix a module may not declare: the login shell's, +// from when a module declared it (novox/hq ADR 0176), before it became the mesh's (ADR 0204). +const retiredLoginShell = "login-shell" + // A SeatDeclaration is a role a module offers on the bus: what may be sent to it, what it says, // and what it answers. A caller declares that it uses the *seat*, never the module, so the // implementation can be replaced under it. @@ -88,6 +92,15 @@ func declaredSeatProblems(m Manifest) []string { "seats (novox/hq ADR 0118)", m.Module, s.Name, meshSeatPrefix+"*")) continue } + if s.Name == retiredLoginShell { + // The name ADR 0176 gave the login shell when the zsh module declared it. The seat is + // the mesh's now, so a module declaring the old name would be a second login shell + // beside it, with a protocol of its own (novox/hq ADR 0204). + problems = append(problems, fmt.Sprintf( + "%s declares a seat named %q; the login shell is the mesh's own seat %s, which a shell "+ + "module claims and none declares (novox/hq ADR 0204)", m.Module, s.Name, LoginShellSeat)) + continue + } if seen[s.Name] { problems = append(problems, fmt.Sprintf( "%s declares the seat %q twice", m.Module, s.Name)) diff --git a/internal/catalogue/seats_test.go b/internal/catalogue/seats_test.go index 22f3db7..31d8baa 100644 --- a/internal/catalogue/seats_test.go +++ b/internal/catalogue/seats_test.go @@ -44,10 +44,11 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { delivered[s.Delivers] = s.Name } } - // Seventeen since node-build-agent (novox/hq ADR 0190) — sixteen once the retired - // mesh-build-machine row goes, when no registered manifest claims it any more. - if len(Seats()) != 17 { - t.Errorf("the mesh defines %d seats rather than 17; the set is closed, so a change here is "+ + // Nineteen since node-environment and node-login-shell (novox/hq ADR 0203, ADR 0204), after + // node-build-agent made seventeen (ADR 0190) — eighteen once the retired mesh-build-machine row + // goes, when no registered manifest claims it any more. + if len(Seats()) != 19 { + t.Errorf("the mesh defines %d seats rather than 19; the set is closed, so a change here is "+ "a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames()) } }