Compare commits

..
Author SHA1 Message Date
jochen 87cb48dee9 Design 36 §4: the console is registered in the exclusive managed tool-server file, because the managed-settings key refuses a non-https URL 2026-10-03 16:13:21 +02:00
jochen 8a5dbaeb98 To-be 40 revised for the tools refactor; the manager starts every exchange (ADR 0183 dated note, designs 36 and 39)
Design 38's WP1-WP4b ran: the node's tool runtime is live on all four machines as the operator
account, tools are bundles given only their declared words, and a bundle has no bus credential.
So the wait on design 38 WP3 is over, the agent module calls nothing and the manager starts every
exchange (key, hand-over, waiting login, reconcile), and the manager's daemon now waits on WP4c's
record instead. Accounts are stated on all four, sudo -n works for each, the agent is installed on
all four; the plan's WP0 shrinks and WP2 gets a configuration-only live proof before any licence.
2026-10-03 16:01:37 +02:00
jochen df78f7bed0 To-be 40: building the operator's agent and its licence manager as work packages
Designs 36 and 39 say what is built; this says in which order and what proves each step, in the
shape to-be 38 gave the operator's machine. Seven packages: the operator states the facts (accounts,
roles, licences); the console provides its endpoint; the licence manager and the agent module are
built and unit-tested in parallel; the manager goes live on the control node; the agent on one
workstation, with the switch and the predecessor's files removed as the proof of the whole; then the
rest of the nodes and the retirement of the two catalogue modules built on the old placement. The
live proofs wait for to-be 38's WP3, because both modules' tools run in the node's tool runtime
(ADR 0175) and a per-module tool container would rebuild what that record retires.
2026-10-03 15:59:58 +02:00
6 changed files with 273 additions and 49 deletions
@@ -152,6 +152,19 @@ the node is bound to, and refuses with a notification otherwise.
| An unservable binding refuses rather than lends | a manager test: a worker bound to a dead licence is answered with a refusal, never another licence's token |
| A switch through the console changes the token on the node and nothing in the answer is a token | a live check on one workstation |
> **The mechanism changed — 2026-10-03, by [ADR 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md).**
> What stands: one manager holding the seat, one rotation source, a token sealed to the receiving
> module's key on request/reply and never an event, the agent module alone writing what the agent
> reads, the identity guard, the host knowing nothing. What moved: the agent module's code is now a
> tools bundle the node's runtime serves ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
> [ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)),
> and a bundle has no bus credential of its own and answers calls rather than making them (ADR 0192's
> consequences). So **the manager starts every exchange**: it asks each bound node's agent module for
> its public key, hands it a token, asks it for a login waiting to be adopted, and reconciles every node
> on a schedule — which is what "the agent module asks the seat for its current token" and "offers the
> grant to the manager" in the decision above now mean in practice. The manager's own process needs a
> bus credential to make those calls, which is the question design 38's WP4c leaves to a record.
## References
- [ADR 0024](0024-model-access-is-a-provision.md), [ADR 0050](0050-model-access-is-vendor-agnostic.md) — the licence as a named thing, the carve-out this moves with the manager
@@ -11,16 +11,6 @@ supersedes-in-part:
# 191. The mesh's resolver holds only the mesh's own names; a public name resolves publicly
> **Progressive insight — 2026-10-03.** The first implementation told the mesh's names from public
> ones by their spelling — a name ending in the mesh suffix — and this record said so: the Decision
> read *"only names under its own suffix"*, and the roster check *"every name the roster carries ends
> in the mesh suffix"*. The mesh needs no such test, nor any per-route name: domains are a node's. A
> node has **one internal domain**, `<node>.internal`, and every route on it is a name under that domain
> (ADR 0151), answered by one wildcard per node; a node has **one or more public domains**, which public
> DNS answers. So the mesh's resolver holds the nodes' internal domains and nothing else, and the roster
> carries the machines and no routed name. Both sentences now say that; what was decided — a public
> name is never given a private answer — is unchanged.
## Context
**[ADR 0066](0066-public-routing-is-name-agnostic.md) published every routed name into internal
@@ -73,8 +63,7 @@ every public name the mesh serves, is forwarded and resolves publicly. Chosen.
## Decision
**The mesh's resolver holds each node's internal domain and nothing else** — `<node>.internal` and
everything under it, at that node's private address. A machine's name,
**The mesh publishes into internal resolution only names under its own suffix.** A machine's name,
and through it every `<label>.<node>.internal`, resolve to that machine's private address. **A public
name is never given a private answer by the mesh**: it resolves through public DNS to the public
address, from members and non-members alike.
@@ -104,8 +93,8 @@ reachability — the lab — certifies its internal names and has no public name
**How each is checked:**
- **The roster:** the controller's tests assert that the roster names the machines and nothing
else — a routed name in it, public or internal, fails the build.
- **The roster:** the controller's catalogue tests assert that every name the roster carries ends in
the mesh suffix — a routed public name in it fails the build.
- **On a machine:** asking the machine's resolver for a public name the mesh serves returns the
public address, and asking it for that route's internal name returns the private one. Asked from a
non-member on a LAN the resolver answers, the first must hold as well.
+7 -4
View File
@@ -425,15 +425,15 @@ the cost of not seeing it is inventing a mechanism that already exists.
### The mesh resolves only its own names; a public name resolves publicly
**The mesh's resolver holds each node's internal domain and nothing else** — `<node>.internal` and
everything under it, so every route's internal name `<label>.<node>.internal` with no line of its own
**The mesh's resolver holds names under the mesh suffix and nothing else** — every machine, and through
it every route's internal name `<label>.<node>.internal`
([ADR 0151](../../02-DECISIONS/0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md)).
**A node's public domains — one or more — are never given a private answer**: it is forwarded and resolves to the
**A public name the mesh serves is never given a private answer**: it is forwarded and resolves to the
public address, from a member and from anything else the resolver answers — a resolver may serve a
machine's LAN, and a phone on that LAN must get the address it can reach
([ADR 0191](../../02-DECISIONS/0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)). Inside the
mesh, a routed service is reached, and certified by the internal authority, under its internal name.
*Checked by the controller's tests — the roster names the machines and no routed name —
*Checked by the controller's catalogue tests — every name the roster carries ends in the mesh suffix —
and on a machine by asking its resolver for a public name the mesh serves: the answer is the public
address.*
@@ -1009,6 +1009,9 @@ The list is worth having in one place, because it is most of the argument:
operator's to move between meshes, but the manifest layer still stores it as a literal — so today
the composition is a per-node override rather than the design. The interpolation that would let a
module carry a label and a node carry the domain, and the mesh join them, does not yet exist.
- **Withdrawing public names from internal resolution.** The roster still publishes every routed
public name at its serving node's private address, which ADR 0191 forbids; until the controller
stops, a resolver that answers a LAN hands that LAN's non-members addresses they cannot reach.
## The hub adopts the predecessor's tunnel
@@ -2,7 +2,7 @@
layer: to-be
status: designed
code: []
updated: 2026-10-02
updated: 2026-10-03
decisions:
- 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
@@ -63,8 +63,9 @@ lists them, and until they go the agent reads stale instructions beside the mesh
## 2. What the module declares and what its code writes
**Declared, applied by the host:** the agent's package (§7); the module's state directory; a facts file
in that directory carrying the node's name, the operator account, the console's endpoint, the module's
settings; the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat.
in that directory carrying the node's name and the console's endpoint, and a settings file carrying the
role and the extra tool servers, merged from the module's settings layers — the bundle is told the two
files' paths, because a bundle's words are paths and constants only (ADR 0192); the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat.
Nothing under the home, nothing under `/etc`.
**Written by the module's code**, from the facts file and the manager's hand-over, whenever either
@@ -111,17 +112,20 @@ the playbooks in the record.
## 4. The console
The module tells the agent where the console is, and the port is the console's to say. **The console
provides a node-scoped provision** — its MCP endpoint on loopback — serving the port the machine gave
it, and the module requires it. A requirement names what the consumer is coupled to
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)); co-location
resolves it; a machine without the console refuses the module by name. [To-be 34](34-the-console.md) is
amended in the same change; issue 192 (open) found the gap.
> **Revised 2026-10-03, building it.** The vendor's managed-settings key for tool servers refuses any
> URL that is not `https://`, including one on loopback, so it cannot carry the console. The module
> owns the vendor's **exclusive** managed tool-server file instead (operator's choice): the console as
> `mesh`, over HTTP on loopback, and every server in the module's `mcp_servers` setting — and no other.
> A server added by hand, a project's own file and a plugin's servers stop loading; claude.ai's
> connectors are kept by a managed setting. A person's own servers move into the setting, for the mesh
> or for one node. Stdio straight to the bus, through the runtime's own client, was weighed and left
> for later: it needs a verb the delivered runtime does not have, and a session holding its own bus
> connection breaks on a credential rotation.
**Other tool servers** a person wants on every machine, or on one, are a declared setting of this module
— mesh layer or node layer — rendered into the same managed key. A module tool, `mcp_configure`,
validates a server and sets the setting through the controller's settings verb, so the list stays
declared state. The agent's own HTTP-only constraint for managed servers applies; a person's local
— mesh layer or node layer — rendered into the same managed file. The person sets them with the
controller's `settings` verb on this module, so the list stays declared state; a tool of this module
cannot set it, because a bundle calls nothing (ADR 0192). The agent's own HTTP-only constraint for managed servers applies; a person's local
command-based servers stay their own, in their own file.
**The entry's name is `mesh`.** The hand-made entry both workstations carry today is named after this
@@ -133,29 +137,34 @@ it is the person's to remove, and until then the agent sees the mesh's tools twi
[ADR 0183](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
decides it; to-be 39 is the manager's half. This module:
- **makes a keypair** in its state the first time it runs and registers the public half with the seat;
- **makes a keypair** in its state the first time it runs, and answers `claude_code_public_key` with the
public half when the manager asks;
- **serves `apply`**: the manager's hand-over, a token sealed to the module's key, with the licence's
name and kind. A rotation of the same licence is applied only if newer within one lineage; a switch is
applied regardless, because across licences the expiries are unrelated. The answer says applied or
refused and why, and never echoes a token;
- **pulls** at start and when its token nears expiry, by the seat's `current` verb, and keeps the last
token when the manager does not answer, saying so;
- **is reconciled, never pulls**: the manager asks every bound node on a schedule and after every
rotation, so a node that was away receives its token when it is back; between visits it keeps the last
token, and `licence_status` says how long it has left;
- **writes** for a subscription licence the credentials file as the operator, access-token-only; for the
API-key licence sets the key-helper in the managed settings to a small program that prints the key
from the module's state, so no file under the home is touched;
- **offers a login to the manager**: when the credentials file changes by a person's login, it reads the
account's identity from the agent's state file and offers the grant to the seat, sealed to the manager's
key, for adoption; the manager decides;
- **holds a login for the manager to collect**: when the credentials file holds a full grant it did not
write — a person logged in — it answers `claude_code_pending_login`, when the manager asks, with the
grant sealed to the key the manager gives in its request and the account's identity read from the
agent's state file; the manager decides, and the next hand-over strips the refresh token;
- **serves `licence_status`**: which licence and kind this node holds, when the token expires, whether
the file matches what was handed over — by fingerprint, never by value.
Switching is the seat's `switch` verb, asked through the console; this module only applies what it is
handed.
handed. *2026-10-03:* every exchange is started by the manager, because a tools bundle answers calls and
has no bus credential to make them ([ADR 0192](../../02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md),
ADR 0183's dated note). The tool names follow the catalogue's `<module>_<verb>` form.
## 6. Scope, settings and the order of assignment
**Every node with an operator account** ([ADR 0181](../../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
None has one today; the operator states them first. **Per node:** the role. **Per mesh or per node:**
All four nodes carry one since 2026-10-03. **Per node:** the role. **Per mesh or per node:**
extra tool servers. **Prerequisite:** the manager holds its seat and has adopted the licences.
**Order:** the manager assigned and a refresh observed; the console's provision in the catalogue; this
@@ -2,7 +2,7 @@
layer: to-be
status: designed
code: []
updated: 2026-10-02
updated: 2026-10-03
decisions:
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
- 02-DECISIONS/0024-model-access-is-a-provision.md
@@ -74,8 +74,9 @@ Carried from the predecessor, where each rule was earned by an incident:
## 4. Handing a token to a node
Every node that runs the agent module registers that module's public key with the seat when it first
runs. From then on:
**The manager starts every exchange** (ADR 0183's dated note of 2026-10-03): the agent module is a
tools bundle, which answers and calls nothing. The manager asks each bound node's module for its public
key the first time and keeps it. From then on:
- **On rotation**, the manager calls `claude-code.apply@<node>` on every node bound to the rotated
licence, with the new token sealed to that node's module key. The module answers *applied*, or
@@ -83,11 +84,12 @@ runs. From then on:
- **On a switch**, the same call with the other licence's token, and the binding is the authority: the
module applies a bind without comparing expiries, because across two licences the numbers are
unrelated.
- **On a pull** — the module starting, or finding its token near expiry — the module calls the seat's
`current` verb for its binding and is answered sealed the same way.
- **On a schedule**, every few minutes, the manager visits each bound node: a node whose token is near
expiry, or that did not answer last time, is handed its current token. A node that was away is
served when it is back, with nothing for it to ask.
- **Never as an event.** What the manager emits names the licence and the outcome and carries no token.
A node whose module has not registered a key cannot be handed a token, and the manager says so by name
A node whose module does not answer for its key cannot be handed a token, and the manager says so by name
rather than falling silent. A node whose module refuses — a wrong identity, a stale grant within one
lineage — is recorded as drift and reported.
@@ -119,9 +121,9 @@ already keeps.
A licence enters the mesh one of two ways, and the token never passes through a prompt, a terminal or an
argument:
- **From a node's login.** A person logs in on a node, as they always have. The agent module there reads
the account's identity from the agent's own state file, and offers the full grant to the seat sealed
to the manager's key. The manager adopts it into the licence the node is bound to **only if the
- **From a node's login.** A person logs in on a node, as they always have. On its next visit the manager
asks that node's module for a waiting login, giving its own public key; the module answers with the
full grant sealed to it and the account's identity read from the agent's own state file. The manager adopts it into the licence the node is bound to **only if the
identity matches** that licence's recorded account; a licence not yet identified is identified by its
first adoption; a mismatch is refused and notified, because the predecessor once filed one account's
grant into another's row this way.
@@ -135,8 +137,8 @@ argument:
**The seat's verbs**, the contract every future holder must serve: `licences` (each with kind,
identity, expiry, failures, who is bound), `bindings`, `bind`, `switch`, `release`, `refresh` (now, one
or all), `usage` (current and history), `adopt`, `register` (a node's module key), `current` (a
consumer's token, sealed, asked by the consumer's module).
or all), `usage` (current and history), `adopt`, and `visit` (reconcile one node now). A node's key and
a consumer's token are not seat verbs: the manager asks the node, by the agent module's own tools.
## 8. Settings
@@ -0,0 +1,208 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-03
decisions:
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
- 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
- 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
- 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md
- 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md
- 02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md
- 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md
---
# 40. Building the operator's agent and its licence manager
**The work of [design 36](36-the-operators-agent-on-a-machine.md) and [design 39](39-the-anthropic-licence-manager.md),
broken into packages that each end at something a person can see run, in the order their
dependencies allow.** The two designs are the authority on *what* is built; this document holds the
packages, their order, their sizes and their proofs, and is wrong the moment it disagrees with them.
It is the shape [design 38](38-building-the-operators-machine.md) gives the operator's machine.
*Revised 2026-10-03, after design 38's WP1–WP4b ran:* the node's tool runtime is live on all four
machines, tools are bundles it serves and each is given only the words its artifact declares, and a
bundle has no bus credential of its own. Three things in the first version of this plan changed with
that: the wait on design 38's WP3 is over; the agent module calls nothing, so the manager starts every
exchange (ADR 0183's dated note); and the manager's own process now waits on a different question,
named under WP4.
## How this is built, and where it is run
**On the live mesh, by the operator's decision** ([ADR 0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md)).
Every package is written with unit tests, committed on one branch per repository
([playbook 07](../../00-META/process/07-feature-branches.md)), and proven on the machines: one
workstation first for the agent, the control node first for the manager, then the rest. A broken agent
module leaves a workstation's agent without the mesh's instructions or with a stale token until the next
push; the person's own files under the home are out of the failure's reach, by
[ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md).
Each package names what proves it. A package that cannot name its proof is divided until it can.
## What exists already, measured
Measured 2026-10-03 on the four machines and in the repositories.
| Piece | Today | Becomes |
|---|---|---|
| the node's tool runtime | live on all four, a host process; **runs as the operator account**; listens for the console on loopback at a port its own code fixes; launches or imports every assigned module's tools bundle and hands each its declared words | serves the agent module's tools; gains one provision for its endpoint (WP1) |
| the operator account | **stated on all four** — the runtime runs as it | read by the agent module from the runtime's own words |
| escalation | passwordless `sudo` for the operator account on all four — a fact about the machines, checked by nobody | how the agent module writes its managed directory under `/etc` |
| the agent itself | installed on all four, at four different versions, all above the one the managed tool-server key needs | declared as the module's package |
| a bundle's words | paths and constants written with `${dir:…}` and `${port:…}` only; a fact the mesh knows reaches a bundle as a file whose path is a word | the agent module's facts file and settings file |
| a bundle calling a tool | **not possible**: a bundle answers calls; it holds no bus credential | the manager starts every exchange |
| a module's own long-running process with a bus credential | **undecided** — design 38's WP4c names it as the question its next record answers | the manager's daemon (WP4) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalogue; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the predecessor's manager and consumer | the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints, cooldowns | ported as logic with its tests |
## The order the work allows
```
WP0 the operator names the licences and each node's role (the live mesh) ── an hour
WP1 the runtime provides its endpoint (mesh-tools) ── small
WP2 the agent module (mesh-catalog) ──┐ WP2 needs WP1;
WP3 the manager's code, built and tested (mesh-catalog) ──┘ WP3 is independent
│
WP2 live: one workstation, configuration only — no licence yet ── the first live proof
│
WP4 the manager live on the control node ── waits on design 38 WP4c's record (a process's bus credential)
WP5 the licence end to end on one workstation
WP6 the rest of the nodes, and the predecessor's remains
```
## WP0 — The operator names the licences and each node's role
*The live mesh. An hour, and it is the operator's.* The accounts are stated already. What remains: the
names of the two subscription licences and the API key; each node's role, as the agent module's setting
on the node layer once the module is registered.
**Proof.** The module's settings list a role for every node; the licences have names.
## WP1 — The runtime provides its endpoint
*mesh-tools. An hour.*
**What changes.** The `node-tools` manifest provides a node-scoped provision, `mcp-endpoint`, serving
the port its code listens on, the way the local model server serves its API
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)). Co-location
resolves it. The runtime's port stays what its code fixes; assigning it is
[issue 192](../../04-ISSUES/192-the-meshs-tools-reach-a-person-only-by-a-registration-made-by-hand/00-report.md)'s
second question and not this package's.
**Proof.** The plan for a workstation carrying a consumer of `mcp-endpoint` shows it bound to the
runtime's port; the controller's tests and the catalogue's checks pass.
## WP2 — The agent module
*mesh-catalog. A day and a half.*
**What is written**, as design 36 says:
1. **The manifest.** The agent's package. A state directory. A facts file in it, rendered by the mesh:
the node's name and the console's address from `mcp-endpoint`. A settings file in it, merged from
the module's settings layers: the node's role and the extra tool servers. A tools bundle whose
words name the two files, the state directory and nothing else. **No file resource under a home or
under `/etc`.**
2. **The renderer**, run whenever the runtime collects the module's tools: from the two files, the
managed settings file (the tool servers under the entry `mesh`, the attribution trailers, the
key-helper for an API-key binding) and the managed instruction file, written under the agent's
managed directory through the account's escalation, only when their content changed.
3. **The keypair**, made once in the state directory; X25519 and an authenticated cipher from the
language's own library, so the bundle carries no dependency.
4. **The tools**: `claude_code_status` (what is rendered, what licence is held, when its token expires,
fingerprints only); `claude_code_render` (render now); `claude_code_public_key`;
`claude_code_apply` (a sealed token, applied only if newer within one lineage unless it is a switch;
the credentials write as the operator, access-token-only, atomic; the key-helper program for an API
key); `claude_code_pending_login` (a full grant found in the credentials file, sealed to the key the
caller gives, with the account's identity).
5. **The documentation**: the six predecessor files and the hand-made console entry a person removes.
**Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else;
it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the
lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
contains a token. The catalogue's checks pass.
**Proof, live, on one workstation, configuration only.** Assign the module; set the node's role; push.
The agent's managed directory holds the two files; everything under the person's agent directory is
byte-identical to before; a new session lists the mesh's tools under `mesh` and answers *which node am
I* from the managed instruction file. `claude_code_status` answers through the console. No licence is
touched: the module writes the credentials file only when it is handed a token.
## WP3 — The manager's code, built and tested
*mesh-catalog. Two to three days.*
**What is written**, as design 39 says: the manifest (the seat and its verbs, a database, a `secret`
for the key the grants are encrypted with, a tools bundle, a process bundle for the daemon, settings
with defaults); the store's migrations; the refresh with its plan, lease, floor and cadence as pure
functions; the vendor client from `anthropic-manager`; adoption from a file and from a node's waiting
login with the identity guard; usage and its threshold; the visit — key, hand-over, waiting login — per
bound node; the seat's verbs.
**Proof, before anything runs live.** Unit tests: two refresh runs started together rotate one grant
once; a mismatching identity is refused; a worker bound to a dead licence is refused and never lent
another; nothing the daemon emits carries a token; a hand-over sealed for one node opens with no other
node's key.
## WP4 — The manager live on the control node
*The live mesh. Half a day.* **Waits on design 38 WP4c's record** — how a module's own long-running
process is given a bus credential and its subscriptions — because the daemon must call the agent module
on every node. Until that record exists, nothing in this plan works around it: no tool container, no
credential copied by hand.
**Order.** Assign the manager on the control node; push. Adopt the API key from a file there. Adopt the
two subscription grants: a login on a workstation carrying the agent module, collected by the manager's
visit. Bind each node's agent to a licence.
**Proof.** Through the console: `anthropic-licence-manager.licences` lists three licences with identity
and expiry; within the cadence the audit shows a rotation and a later expiry; a forced `refresh` is
logged with the vendor's answer.
## WP5 — The licence end to end on one workstation
*The live mesh. Half a day. The proof of the whole.*
**Order.** Record the checksums under the person's agent directory. Bind the workstation to a
subscription licence. Remove the six predecessor files and the hand-made console entry. Start a session.
**Proof.** Everything under the person's agent directory is byte-identical but the credentials file,
which is owned by the operator, readable by nobody else, and names no refresh token. A session makes a
model request. `switch` to the second subscription licence changes the token on the workstation within a
minute, and neither the verb's answer nor either module's log holds a token. Switched to the API key, the
credentials file is left as it was and the agent authenticates through the key-helper. Switched back.
## WP6 — The rest of the nodes, and the predecessor's remains
*The live mesh and mesh-catalog. One day.* The module on every node, the predecessor's files removed on
the second workstation; a login under a licence's account collected and adopted, and one under the wrong
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
designs 36 and 39 set to `implemented` with the as-is written
([playbook 02](../../00-META/process/02-graduation.md)).
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
has no module built on the old placement.
## What is deliberately not here
- **The package repository seat** for a distribution that does not carry the agent's package (design 36
§7). The four machines have the package; a fifth would refuse the module in its package manager's
words.
- **Escalation as a checked fact.** The agent module's write under `/etc` relies on the operator
account's passwordless `sudo`, true on all four and checked by nothing. A machine without it refuses
the render in the tool's own words; making escalation a reported capability is design 38's to decide.
- **An automated switch on exhaustion.** The readings are kept from WP4; the policy is a later record.
- **Workers and the mesh's own sessions as consumers.** The manager knows them from WP3; the consumers do
not exist yet ([to-be 15](15-the-agent-session.md)).
- **Whether a refresh token is single-use.** WP4 may measure it; the design holds either way.
## How this list is kept true
Each package's proof is run when the package is finished and its line here gains the date and the
commit, as design 38 does. A package whose proof fails is not reworded; the failure is recorded under it
and the package stays open. When WP2's live proof runs, design 36 moves to `in-progress` with its owning
repository; when WP5's does, design 39 does too; and when WP6's does, both move to `implemented`, with
the as-is written.