Files
mesh-catalog/modules/claude-licence-manager
jschoubben baaf2e56dd M-15: the manager's tools read all it keeps and start by hand all it does (issue 1409)
The licences verb answers each licence's organisation, latest usage with its
reset times, its room or why it is not known, whether it is where a full
licence's consumers move next, when and from where it was adopted and its open
conditions. The usage verb takes since, a limit of at most 2000 and raw. The
module's tools: licences_switches from the audit, licences_read_usage and
licences_switch_now with dry, licences_settings with the level mesh-config
answers (the manifest invokes its config verb), licences_kept over every
bus-state entry with its fill, licences_resync, licences_conditions and
licences_carried with its run, refused once carried. An acting tool waits for
the carry-over.

Builds: design 39 bus-and-switch §34, §35, §37, §38, §39, §40, §41, §42, §43, §44, §47, §20, §11, B9, B18, B40

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:19:22 +01:00
..

claude-licence-manager

Holds the anthropic-licence-manager seat: every Anthropic licence the mesh has, kept alive by one rotation source, and handed to each consumer sealed (novox/hq ADR 0183, ADR 0206, design 39).

How a licence comes to exist

Nothing is configured. Every node running claude-code reports what it holds as that module's holdings state — the account, fingerprints and expiries, never a token. This module reads every report when it starts and watches them:

  1. A report with a refresh token it does not hold is a candidate.
  2. It asks that node's claude_code_grant, giving its public key, and receives the grant sealed to it.
  3. It refreshes it. If the vendor exchanges the token, the grant is this module's — encrypted at rest with the key the vault made for it — and from then on it is the only refresher. If not, the candidate is recorded dead and nothing is adopted.
  4. Several nodes logged in to one account: newest login first; the rest are never exchanged.
  5. A node reporting that account and bound to nothing is bound to it.

Each node is then handed an access token only, so the agent there never refreshes, and a refresh token appearing on a node later can only be a person's login — which wins if it refreshes.

An API key enters through adopt, from a file on this module's node.

What each consumer holds

This module's bindings state: one key per consumer (a node's name) with the licence, its kind and a generation that grows with every rotation and switch. claude-code watches its own key and, on a newer generation, asks current with its public key.

The seat's verbs

licences, bindings, bind, switch, release, refresh, usage, adopt, current — through the console as anthropic-licence-manager.<verb>. No answer carries a token.

Settings

settings.json in the state directory: cadence_minutes (240), floor_minutes (60), failures_to_notify (3), cooldown_hours (24), refresh_warn_days (3).

Events

licence.adopted, licence.refused, licence.failing, usage.read — none carries a secret.

Code and tests

Go, one binary (cmd/claude-licence-manager): the seat's verbs and the daemon in one launched bundle, prepare as the run-once preparation step. The sealed box is claude-code's own format, byte for byte — the two modules carry the same seal.go — and a test opens one sealed by the TypeScript agent module the Go one replaced, so the format is the one already on the machines.

go test ./...
# the store against a real postgres:
docker run -d --rm --name licmgr-pg -e POSTGRES_PASSWORD=t -p 15498:5432 postgres:16-alpine
MESH_TEST_POSTGRES=postgres://postgres:t@127.0.0.1:15498/postgres go test ./...

Files it does not know

Each directory or archive this module declares says how it treats a file it did not write (ADR 0422, ADR 0416).

  • ${dir:state}: everything in it is the program's; the mesh moves nothing out (ADR 0422 §2).