From 1faa63b2d52d6c7b5b0385ee0e637d2060faee5c Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 4 Oct 2026 16:32:29 +0200 Subject: [PATCH] As-is: a module's state and the agent with its licences; designs 36, 39 and 40 implemented What runs since 2026-10-04 and what its first live use showed: refused requests as timeouts, a late machine reading the whole set, the partial secrets guard; the agent module and the licence manager on every machine. --- 03-DESIGN/00-as-is/14-a-modules-state.md | 51 +++++++++++++++++ .../00-as-is/15-the-agent-and-its-licences.md | 57 +++++++++++++++++++ 03-DESIGN/00-as-is/README.md | 2 + .../36-the-operators-agent-on-a-machine.md | 4 +- .../39-the-anthropic-licence-manager.md | 4 +- ...operators-agent-and-its-licence-manager.md | 4 +- 6 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 03-DESIGN/00-as-is/14-a-modules-state.md create mode 100644 03-DESIGN/00-as-is/15-the-agent-and-its-licences.md diff --git a/03-DESIGN/00-as-is/14-a-modules-state.md b/03-DESIGN/00-as-is/14-a-modules-state.md new file mode 100644 index 0000000..1914b1a --- /dev/null +++ b/03-DESIGN/00-as-is/14-a-modules-state.md @@ -0,0 +1,51 @@ +--- +layer: as-is +status: implemented +code: [mesh-controller internal/catalogue/state.go, mesh-controller internal/broker/state.go, mesh-controller cmd/mesh-controller/push.go, mesh-tools node-tools/internal/bus/state.go, mesh-tools node-tools/internal/launch/launch.go, mesh-sdk src/state, mesh-sdk go/state.go] +updated: 2026-10-04 +decisions: + - 02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md +--- + +# A module's state, as it runs + +**A module keeps the current value of something on the bus, and every machine sees it — including one +that joins later.** Since 2026-10-04 a manifest may say `state` (buckets the module owns) and `reads` +(another module's, as `.`). The first two modules to use it are the operator's agent on a +machine and its licence manager; on the day this was written, three buckets existed on the bus. + +## What runs + +- **The controller** creates a key-value bucket `_` for every declared state, from the + catalogue — on every start and, since the first module that declared state found it missing, on every + push before the memberships that name it. A bucket nothing declares any more is reported and kept. + Every bucket carries the mesh's caps: 256 KiB a value, 64 MiB a bucket. +- **The grants**: the machine's runtime is granted, for each bucket a module it carries owns, writing + under the bucket's own subjects and reading; for a bucket it only reads, reading. Measured once built, + with the composed grants loaded into a server: a reader's write is refused by the server. +- **The membership** issued to each assignment lists its buckets by the names the module uses, and + whether it may write. +- **The runtime** answers `mesh/state.get`, `put`, `delete`, `keys` and `watch` on the bundle's channel. + A watch hands the current values — none that is deleted — then every change, each naming the watch it + belongs to; it is answered once the current values are delivered. The runtime refuses, with the + reason, a state the module was not issued, a reader's write, a key the bus cannot hold, and a value + carrying a field named like a credential. +- **The SDKs**: `state(name)` in TypeScript, `stdio.State(name)` in Go (tag `go/v0.1.7` and later). + +## What the first live use showed + +- **A refused request is a timeout, not a refusal.** The bus reloads a machine's grants a moment after + the push that changed them; a bundle that asks in between waits out its deadline. A module that + watches at start therefore watches beside its handshake and asks again until the state answers — the + agent module needed two to seven attempts on its first start on each machine. +- **A late machine reads the whole set.** A server registered for every machine before one machine was + assigned the module reached that machine from the current values at its start. +- **The secrets guard is partial and works for what it covers**: an entry carrying an `Authorization` + header was refused on the live bus. A sealed value is plain text to an inspector, and is not caught. + +## How it is checked + +The controller's catalogue and broker tests (names, grants, memberships, a bucket asserted in place +against a real server); the runtime's tests over a real bus (current values without deletions, refusals, +the TypeScript SDK through the runtime); `module check` names a read whose owner on the shelf keeps no +such state. diff --git a/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md b/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md new file mode 100644 index 0000000..3bf29db --- /dev/null +++ b/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md @@ -0,0 +1,57 @@ +--- +layer: as-is +status: implemented +code: [mesh-catalog modules/claude-code, mesh-catalog modules/claude-licence-manager] +updated: 2026-10-04 +decisions: + - 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md + - 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md + - 02-DECISIONS/0209-a-login-on-a-node-moves-that-node-to-its-account-and-an-api-key-is-added-from-any-node-sealed.md +--- + +# The operator's agent and its licences, as they run + +**Every machine with an operator account runs the agent module, and one licence manager on the control +node keeps the licences.** Both are Go binaries the machine's runtime launches; neither has a container, +a port or a bus credential of its own. Live since 2026-10-04, on all four machines. + +## The agent module on each machine + +- **Writes the agent's managed directory**: the tool servers — the console as `mesh`, plus servers + registered through the module — the mesh's settings, and the instruction file. The tool-server list + is exclusive by the vendor's rule: a server not in it does not load on that machine. +- **Keeps registered tool servers in its state**, one key per registration for every machine or for one; + each machine renders what applies to it. +- **Reports what its machine holds** — the account its agent names, the kind, fingerprints and expiries, + never a token — at start and whenever the credentials file changes. +- **Writes what the machine should hold**: on a newer generation of its binding it asks the seat's + `current`, sealed to its own key, and writes the access token only. No machine holds a refresh token. +- **Hands over a login when asked**, sealed to the manager's key, and **adds an API key** from a file on + its machine the same way, removing the file once the manager has it. + +## The licence manager on the control node + +- **Holds the `anthropic-licence-manager` seat**: `licences`, `bindings`, `bind`, `switch`, `release`, + `refresh`, `usage`, `adopt`, `public-key`, `current`. +- **Learns licences from the reports**: a refresh token it does not hold is adopted by refreshing it, + newest login first, once per account. The machine a login was made on is moved to that login's + account; a machine bound to nothing is bound to the account it reports. +- **Is the only refresher**: every exchange under a lease per licence in its own database, every four + hours and in any case within an hour of expiry; grants are encrypted at rest with a key the vault made. +- **Publishes what each machine should hold** as its `bindings` state, with a generation that grows with + every rotation and switch. + +## On the day it went live + +One subscription account was adopted from the control node's own login on its first start; the other +three machines, logged in to the same account with older logins, were bound to it without their logins +being exchanged. A forced rotation reached all four machines within seconds. Two faults were found and +fixed during the rollout: a machine reporting an already-adopted account later was never bound, and a +seat verb named with an underscore was refused by the builder. + +## How it is checked + +Each module's own tests (the agent's instruction file held byte for byte to the renderer it replaced; the +manager's rules on a store in memory and a stub vendor; its store against a real database); one run of +both binaries under the real runtime with a stub vendor before going live; and live: `licences` lists the +licence with every machine bound, and each machine's `claude_code_status` names it with no login waiting. diff --git a/03-DESIGN/00-as-is/README.md b/03-DESIGN/00-as-is/README.md index ee23f9f..75c8c54 100644 --- a/03-DESIGN/00-as-is/README.md +++ b/03-DESIGN/00-as-is/README.md @@ -22,6 +22,8 @@ Where the two disagree, the implementation wins and the disagreement is stated. | [`11-the-lab.md`](11-the-lab.md) | The lab — the first piece of the new shape that exists, and what it does not yet do | | [`12-the-seats.md`](12-the-seats.md) | The seats the mesh defines, who holds one, and where a seat changes resolution | | [`13-the-console.md`](13-the-console.md) | The mesh's tools on the machine a person sits at, served by a module the mesh assigned there | +| [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed | +| [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences | ## What these documents are not diff --git a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md index 5d877a8..0dd5cde 100644 --- a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md +++ b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md @@ -1,7 +1,7 @@ --- layer: to-be -status: designed -code: [] +status: implemented +code: [mesh-catalog modules/claude-code] updated: 2026-10-04 decisions: - 02-DECISIONS/0209-a-login-on-a-node-moves-that-node-to-its-account-and-an-api-key-is-added-from-any-node-sealed.md diff --git a/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md b/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md index 48967dc..fcde53d 100644 --- a/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md +++ b/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md @@ -1,7 +1,7 @@ --- layer: to-be -status: designed -code: [] +status: implemented +code: [mesh-catalog modules/claude-licence-manager] updated: 2026-10-04 decisions: - 02-DECISIONS/0209-a-login-on-a-node-moves-that-node-to-its-account-and-an-api-key-is-added-from-any-node-sealed.md diff --git a/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md index 399dc05..1abd18f 100644 --- a/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md +++ b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md @@ -1,7 +1,7 @@ --- layer: to-be -status: designed -code: [] +status: implemented +code: [mesh-catalog modules/claude-code, mesh-catalog modules/claude-licence-manager] updated: 2026-10-04 decisions: - 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md