Merge pull request 'As-is: a module's state, and the agent with its licences; designs 36, 39, 40 implemented' (#365) from design/state-and-licences-as-is into main

This commit is contained in:
2026-10-04 14:33:50 +00:00
6 changed files with 116 additions and 6 deletions
+51
View File
@@ -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 `<module>.<name>`). 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 `<module>_<name>` 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.
@@ -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.
+2
View File
@@ -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
@@ -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
@@ -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
@@ -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