mesh/merge-gate checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/repo-check checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/delivery-group group feat/owned-paths checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Every directory and archive resource carries `unknown`: move-out for a home folder whose program writes nothing there (the node-engine, delivered in mesh-host #117, moves the rest to ~/.local/state/mesh-moved/<module>/ with a list), module for ~/.ssh and ~/.claude, program elsewhere; `knows` keeps what a program or the operator relies on (powerlevel10k's compiled files, i3's scripts and the asus/g14 drop-ins, dunst's slack rule, betterbird's records, node-env's forge credential, issue 1033). Two shared folders are no longer declared. Each module's README says it. checks/owned holds every manifest to it; checks/sshdir stays while main's merge check runs it (issue 1035). Tests that pin a manifest follow the new key only. Reviewed in three rounds.
62 lines
2.9 KiB
Markdown
62 lines
2.9 KiB
Markdown
# 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).
|