ADR 0206: a node reports the grant it holds; the manager adopts a licence by refreshing it

The operator's flow: clients publish what their credentials file holds, the
manager takes in a licence it does not own and rotates it from then on. The
token itself cannot be published (design 32 §10, ADR 0201), so a node reports
fingerprints and identity as state and hands the grant over only when the
manager asks; adopting is refreshing, newest login first; bindings with a
generation replace the rotated/switched events. Designs 36 and 39 and to-be 40
amended; a pointer note on ADR 0183.
This commit is contained in:
jochen
2026-10-04 11:58:46 +02:00
parent f6668d76d6
commit 82fa5f79ea
6 changed files with 243 additions and 64 deletions
@@ -166,6 +166,16 @@ the node is bound to, and refuses with a notification otherwise.
> "offers the grant to the manager" in the decision above now mean in practice. The agent module could
> ask through its runtime; it does not need to.
> **The mechanism changed — 2026-10-04, by [ADR 0206](0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md).**
> What stands: the manager holding the seat, one rotation source, the grants encrypted in its store, 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, bindings as a person's act. What moved: the dated note
> above — the manager no longer starts every exchange. Each node reports what it holds as state, without
> the secret; the manager asks a node for its grant only when a report shows one it does not hold, adopts
> a licence by refreshing it rather than into a licence configured beforehand, and keeps what each
> consumer should hold as state, from which the node fetches its token by request. The rotation and switch
> events are gone.
## 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
@@ -0,0 +1,144 @@
---
topic: what runs on it
status: accepted
date: 2026-10-04
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
---
# 206. A node reports the Anthropic grant it holds; the licence manager adopts a licence by refreshing it, and what each node should hold is the manager's state
## Context
[ADR 0183](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
made the licence manager a module holding the `anthropic-licence-manager` seat: one rotation source, the
long-lived grants in its own store, a short-lived token handed to a node sealed on request/reply, the
agent module alone writing what the agent reads. How the manager *learns* a licence, and who starts each
exchange, it left to a later shape, and three texts have since disagreed: ADR 0183 has a node register
its key and the manager adopt a login only into a licence the node is already bound to; its dated note
of 2026-10-03 has the manager start every exchange and visit every node on a schedule; the agent module
as built asks the seat for its token when an event says to, and pushes a login to the seat.
**The operator settled it on 2026-10-04, in the operator's own words:** the manager must hold the active refresh token;
whichever node a login happened on holds the latest one; every client publishes what its credentials
file holds, the manager sees a licence it does not own yet and takes it into its store, and from then on
rotates it and distributes the access token. A manager launched for the first time holds no licence and
accepts what the clients report. Several nodes report the same account — today the nodes are all logged in
to one personal account — and before the manager adopts a grant it must know the refresh token still
works.
Two facts bound how that is built:
- **A refresh token cannot be published.** Anything published on the bus is kept, and a secret never
enters a stream, sealed or not ([design 32](../03-DESIGN/01-to-be/32-what-a-module-declares.md) §10).
A module's state is a stream too, and the runtime refuses a value carrying a field named like a
credential ([ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md);
refused live on 2026-10-04 for an `Authorization` header).
- **A refresh token can only be checked by using it.** No endpoint answers "is this refresh token
valid" without exchanging it, and an exchange is presumed to rotate it (ADR 0183: the predecessor
lost a licence to a reused one). Checking and adopting are therefore one act, and whoever checks
becomes the token's only live holder.
Since ADR 0201 the bus has the shape this needs: **state** every node sees, including one that joins
later or a manager that starts later, read whole on start and then watched.
## Considered Options
1. **Each node publishes its credentials file, the token included.** What the operator described,
literally. Rejected for the token only: it would sit in a stream every principal that reads the
bucket can read, for as long as the bucket keeps it, and the runtime refuses it anyway.
2. **The manager visits every node on a schedule and collects a waiting login** (ADR 0183's dated
note). Rejected: the manager must know every node in advance and poll it, a node that joins later
waits for the next visit, and "what does each node hold" lives nowhere anyone can read.
3. **Each node reports what it holds as state, without the secret; the manager asks for the secret
only when the report shows a grant it does not hold, and adopts by refreshing.** Chosen: the
operator's flow, with the one part that cannot be on the bus moved onto request/reply.
## Decision
**1. Every agent module reports what its node holds, as its own state.** One key per node in the
module's `holdings` state: the account's identity as the agent's own state file names it (account id,
address, organisation), the kind, the refresh token's **fingerprint** and whether one is present at
all, the access token's fingerprint and expiry, the licence it was last handed, and when the credentials
file last changed. Written when the module starts — a node already logged in when the module is first
assigned reports at once — and again whenever the credentials file changes. **No token, ever**: a
fingerprint names a token without being one.
**2. A licence is an account, and the manager learns it from the reports.** The manager reads every
node's `holdings` at start and watches them. A report carrying a refresh token whose fingerprint the
manager does not hold is a **candidate**: for an account it has no licence for yet, a new licence; for
one it has, a login made since. A manager launched for the first time holds no licence and treats
every report as a candidate. An API key still enters only through the seat's `adopt` verb, from a file
on the manager's node.
**3. The secret travels only when asked for.** For a candidate, the manager calls that node's agent
module on request/reply, giving its own public key, and is answered with the grant sealed to that key
(ADR 0183's channel, unchanged).
**4. Adopting is refreshing.** The manager exchanges the candidate's refresh token at the vendor's
endpoint under its lease for that account. If the exchange succeeds, the grant it got back is the
licence's, stored encrypted, and the manager is from then on its only rotation source. If it fails, the
candidate is recorded dead, nothing is adopted, and the report says so. **Several nodes, one account:**
candidates for one account are tried newest login first; the first that refreshes is adopted, and the
manager does not exchange the others.
**5. A node holds an access token only, so the latest login wins.** A node bound to an adopted licence
is handed the access token and nothing else, and the agent module writes the credentials file without a
refresh token — so the agent on the node can never refresh it, and two refreshers never hold one grant.
A refresh token appearing in a node's file afterwards can therefore only be a person's login there; its
report makes it a candidate, and if it refreshes it replaces the licence's grant. That is the operator's
"whichever node a login happened on holds the latest one", made mechanical.
**6. What each consumer should hold is the manager's state.** One key per consumer in the manager's
`bindings` state: the licence, its kind, and a **generation** that increases with every rotation and
every switch. The agent module watches its own key; when the generation is newer than the one it
applied, it asks the seat's `current` verb for the token, sending its public key, and is answered sealed
(request/reply). A node that was away reads its key when it is back and asks once. The `licence.rotated`
and `licence.switched` events go: what they announced is now the state itself, and a node needs the
latest, not the history.
**7. A first binding follows the login.** When the manager adopts a licence from a node's report, a
node with no binding yet whose report names that account is bound to it. Every later change is a
person's act through `bind`, `switch` and `release`, as ADR 0183 says.
**8. The identity guard stands, on two sources.** The account a grant is filed under is the identity
the node read from the agent's own state. Where the vendor's answer to the refresh names the account,
the manager compares the two and refuses a mismatch with a notification; whether it names it is
measured when the manager is built, and the record of which source decided is kept in the audit.
## Consequences
- The manager needs no configuration to start: launched on a mesh whose nodes are logged in, it adopts
every account they hold, one licence each, from the newest login that still refreshes.
- Every node's holding is readable by anyone allowed to read the state — the console, an agent, the
operator — without a token in sight, which is what `licence_status` on each node answered one at a
time.
- **What got harder:** adoption consumes the refresh token the node held. On a node whose grant was
adopted, the agent's own copy is dead from that moment; until the manager hands it an access token
(decision 6), the agent keeps the access token it already had, which lives hours. And a node whose
file still holds a refresh token after adoption — it was not handed one yet — is a second holder of a
dead grant, not a live one, so the rotation-source rule holds.
- A candidate whose refresh fails is not retried by the manager: a dead refresh token does not come
back. A person logs in again, and the new report is a new candidate.
- Nothing in the reports is secret, but they do say which account each node uses; readers of the state
are declared in manifests like any other.
## How it is checked
| Rule | Checked by |
|---|---|
| No report carries a token | the runtime refuses a credential-named field (ADR 0201's test); the agent module's test: a report built from a full credentials file holds fingerprints and identity only |
| A node already logged in reports at start | the agent module's test: with a credentials file present and unchanged, starting writes its `holdings` key |
| A candidate is adopted only by a successful refresh, newest login first, once per account | the manager's tests against a stub vendor: two reports for one account, the newer refreshes and is adopted, the older is never exchanged; a failing refresh adopts nothing and records the candidate dead |
| A node is handed an access token only | the agent module's test: the file it writes after a hand-over holds no refresh token |
| A newer generation is fetched once, by request | the agent module's test: a `bindings` change with a newer generation asks `current` once; an equal one asks nothing |
| No event carries a token, and none announces a rotation any more | the manager's test of everything it publishes |
| Live | the manager launched with no licence on a mesh whose four nodes are logged in to one account adopts one licence, binds the four nodes, and each node's file then holds an access token and no refresh token |
## References
- [ADR 0183](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) — the manager, its seat and its channel, which this extends
- [ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md) — module state, and the refusal of a secret in it
- [design 32](../03-DESIGN/01-to-be/32-what-a-module-declares.md) §10 — no secret in a stream
- [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md), [to-be 39](../03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md) — the two modules, amended by this record
+1
View File
@@ -304,6 +304,7 @@ python3 00-META/checks/index.py fail if stale
- **0203** — [The account's environment is one module's, and every module contributes to it](0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md)
- **0204** — [A module contributes shell code to the login shell in named slots, and the login shell is the mesh's seat](0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md)
- **0205** — [Software the distribution does not package ships as a pinned archive of the module's own](0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md)
- **0206** — [A node reports the Anthropic grant it holds; the licence manager adopts a licence by refreshing it, and what each node should hold is the manager's state](0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)
### How it is built
@@ -2,8 +2,9 @@
layer: to-be
status: designed
code: []
updated: 2026-10-03
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
- 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/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
@@ -139,31 +140,32 @@ it is the person's to remove, and until then the agent sees the mesh's tools twi
## 5. The licence: the consumer side
[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:
decides it and [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md) says how it moves; to-be 39 is the manager's half. This module:
- **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;
- **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;
- **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
- **makes a keypair** in its state the first time it runs, and sends the public half with every request
that is answered sealed;
- **reports what the node holds**, as its own `holdings` state, one key for this node: the account's
identity read from the agent's state file, the kind, the refresh token's fingerprint and whether one is
present, the access token's fingerprint and expiry, the licence and generation it last applied, when the
credentials file last changed. Written at start — a node already logged in reports at once — and on every
change of the file. Never a token: the runtime refuses one anyway;
- **hands over a grant only when asked**: `claude_code_grant` answers the manager, which gives its public
key, with the full grant in the credentials file sealed to that key — the one time a refresh token
leaves the node, for the manager to adopt by refreshing it;
- **watches the manager's `bindings` state** for this node, and when the generation is newer than the one
it applied, asks the seat's `current` verb for the token, sealed to its own key. A rotation of the same
licence is applied only if newer within one lineage; a switch is applied regardless;
- **writes** for a subscription licence the credentials file as the operator, **access-token-only** — so
the agent here never refreshes, and a refresh token appearing later is a person's login, reported like
any change; 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;
- **serves `claude_code_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. *2026-10-03:* every exchange is started by the manager, by the operator's direction (ADR 0183's dated
note); this module is a bundle the node's runtime launches over stdio and answers what it is asked
([ADR 0193](../../02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md)). The tool names follow the catalogue's `<module>_<verb>` form.
Switching is the seat's `switch` verb, asked through the console; this module only applies what the
state says it should hold. *2026-10-04:* this replaces the manager's visits of 2026-10-03 (ADR 0183's dated
note): the node reports, the manager asks for a secret only when a report shows one it does not hold, and
a token is fetched by request when the state says it changed.
## 6. Scope, settings and the order of assignment
@@ -2,8 +2,9 @@
layer: to-be
status: designed
code: []
updated: 2026-10-03
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
- 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
- 02-DECISIONS/0050-model-access-is-vendor-agnostic.md
@@ -74,24 +75,22 @@ Carried from the predecessor, where each rule was earned by an incident:
## 4. Handing a token to a node
**The manager starts every exchange** (ADR 0183's dated note of 2026-10-03), by `mesh/ask` through the
runtime that launched it ([ADR 0198](../../02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md)). The manager asks each bound node's module for its public
key the first time and keeps it. From then on:
*Amended 2026-10-04 by [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)*, replacing the manager's visits: **what each consumer
should hold is the manager's state, and the token is fetched when it changes.**
- **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
*refused* and why, and the manager records it.
- **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 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.
- **The manager keeps a `bindings` state**, one key per consumer: the licence, its kind, and a
**generation** that increases with every rotation and every switch. Nothing in it is secret.
- **The agent module on each node watches its own key.** When the generation is newer than the one it
applied, it asks the seat's `current` verb, sending its public key, and is answered with the token
sealed to it — request/reply, never an event. A node that was away reads its key when it is back and
asks once; a manager that is down leaves every node on its last token, which lives hours.
- **On a switch** the agent applies the new licence's token without comparing expiries, because across
two licences the numbers are unrelated; within one licence it applies only a newer grant.
- **No event announces a rotation or a switch.** What they announced is the state itself, and a node
needs the latest, not the history. What the manager still emits names an outcome and carries no token.
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.
A consumer that never asks is visible: its own report (§6) names the licence and generation it holds,
and a node behind its binding is drift the manager reports.
## 5. Who gets which licence
@@ -118,27 +117,46 @@ already keeps.
## 6. Adopting a grant
A licence enters the mesh one of two ways, and the token never passes through a prompt, a terminal or an
argument:
*Amended 2026-10-04 by [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)*: a licence is an account, learned from what the nodes
report, and adopted by refreshing it.
- **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.
- **Every node reports what it holds**, as the agent module's `holdings` state, one key per node: the
account's identity read from the agent's own state file, the kind, the refresh token's fingerprint and
whether one is present, the access token's fingerprint and expiry, the licence and generation it was
last handed, when the credentials file last changed. Written when the module starts — a node already
logged in reports at once — and on every change. Never a token.
- **The manager reads every report at start and watches them.** A report with a refresh token whose
fingerprint the manager does not hold is a candidate: a new licence for an account it has none for, a
login made since for one it has. A manager launched for the first time holds no licence and takes every
report as a candidate.
- **The secret is asked for, never published.** For a candidate the manager calls that node's agent
module, giving its own public key, and is answered with the grant sealed to it.
- **Adopting is refreshing.** The manager exchanges the candidate's refresh token under its lease for
that account; success makes the returned grant the licence's and the manager its only rotation source;
failure records the candidate dead and adopts nothing. Candidates for one account are tried newest login
first, and the first that refreshes ends the search — the others are never exchanged.
- **The latest login wins.** A bound node is handed an access token only and its file holds no refresh
token, so a refresh token appearing there later is a person's login; its report makes it a candidate,
and if it refreshes it replaces the licence's grant.
- **A first binding follows the login**: a node with no binding whose report names the adopted account
is bound to it. Every later change is `bind`, `switch` or `release`.
- **The identity guard** files a grant under the identity the node read; where the vendor's refresh
answer names the account too, a mismatch is refused and notified. Which source decided is audited.
- **An API key** is delivered to the manager by the operator through the seat's `adopt` verb from a file
on the manager's node, never as an argument.
## 7. What it emits and serves
**Events**, no secret in any: `licence.rotated`, `licence.switched`, `licence.adopted`,
`licence.failing`, `licence.refused`, `usage.read` — the audit logger records them all.
**Events**, no secret in any: `licence.adopted`, `licence.failing`, `licence.refused`, `usage.read` —
the audit logger records them all. *2026-10-04 (ADR 0206):* `licence.rotated` and `licence.switched` are
gone; a rotation or a switch is a new generation in the `bindings` state.
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads.
**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`, 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.
or all), `usage` (current and history), `adopt`, and `current` (a consumer's token, sealed to the key the
consumer sends — ADR 0206). The manager asks a node for a candidate grant by the agent module's own tool.
## 8. Settings
@@ -2,8 +2,9 @@
layer: to-be
status: designed
code: []
updated: 2026-10-03
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
- 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
@@ -116,12 +117,13 @@ runtime's port; the controller's tests and the catalogue's checks pass.
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).
4. **The tools and the state** (*2026-10-04, ADR 0206*): `claude_code_status` (what is rendered, what
licence is held, when its token expires, fingerprints only); `claude_code_render` (render now);
`claude_code_grant` (the full grant in the credentials file, sealed to the key the manager gives).
The `holdings` state, written at start and on every change of the credentials file; a watch of the
manager's `bindings` key for this node, which asks the seat's `current` on a newer generation and
applies the sealed answer — 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.
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;
@@ -143,8 +145,9 @@ touched: the module writes the credentials file only when it is handed a token.
for the key the grants are encrypted with, a tools bundle and a long-running bundle for the daemon, both launched by the runtime, 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.
login with the identity guard; usage and its threshold; the seat's verbs. *2026-10-04 (ADR 0206):* in place
of the visit, the watch of every node's `holdings`, adoption of a candidate by refreshing it (newest login
first, once per account), the `bindings` state with a generation per consumer, and `current`.
**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
@@ -158,9 +161,10 @@ node's key.
yet live on the control node when this package starts, this package waits for 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.
**Order.** Assign the manager on the control node; push. It reads every node's `holdings` and adopts each
account the nodes are logged in to, by refreshing the newest login's grant (ADR 0206); each node with no
binding is bound to the account it reported. Adopt the API key from a file there. A second subscription
account enters by a login on a workstation carrying the agent module.
**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