Commit Graph
48 Commits
Author SHA1 Message Date
jschoubben dbcba161d5 M-6 fix: the carry-over carries each live lease with its remaining lifetime (issue 1409)
mesh/check checking dbcba161d57c on d9f2b7bcfa07
mesh/delivery-group group feat/39-licence-switch merged: every member is merged
mesh/delivery merged: source.main moved to novox/mesh-catalog@d9f2b7bcfa07
The lead's answer B44: a lease the bundle before held in today's database
and still live at the run is written to `leases` under the bus's key form
with what is left of its lifetime, so no refresh races it whatever the
delivery's order; an expired one is counted read and not written.

Builds: design 39 bus-and-switch §4, §12, B44

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:07:09 +01:00
jschoubben c8e321c8e1 M-11 fix: a reading takes its licence's lease, and a grant the vault does not answer is a failed reading (issue 1409)
Review round 1, finding 5: the reading reads the grant under the licence's
lease (another run holding it reads nothing that round), and a grant the
vault refuses writes readError, keeps the percentages and goes on to the
next licence, so the conditions are still said after the round.

Review round 1, finding 6: these answers were built by earlier commits that
did not name them: B26, B27, B28 and B29 by M-6 (9cc62818), B30 by M-9
(39912119), B35 by M-11 (6cf98386); B23 and B24 by V-1 (e85cf002) and M-7
(5ca3876b), named again in b8e8be61; B31 and B32 in 36d9d739.

Builds: design 39 bus-and-switch §4, §16, §24, §31, B23, B24, B26, B27, B28, B29, B30, B34, B35

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:04:07 +01:00
jschoubben b8e8be619d M-15 fix: resync hands the vault each access token built from its grant (issue 1409)
Review round 1, finding 4: the vault hands a licence's access token only to
a machine a binding names, so licences_resync builds it from the grant (the
key itself for an API key) instead of reading it back.

Builds: design 39 bus-and-switch §15, §17, §42, B23, B24

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:02:54 +01:00
jschoubben cc8a354f75 M-3 fix: an adopted licence says where it came from, and a newer login keeps its reading (issue 1409)
Review round 1, finding 3: adoptedFrom is the node a login came from, or
where an API key came from; a newer login of the same account keeps the
latest reading and generation the key holds.

Builds: design 39 bus-and-switch §2, §34, §46

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:02:14 +01:00
jschoubben 6a57bb7218 M-6 fix: each carried binding is said as binding.changed (issue 1409)
Review round 1, finding 2: a reader's first view taken while the carry-over
runs is brought up to date by the event said after each key is written.

Builds: design 39 bus-and-switch §12, §21

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:01:40 +01:00
jschoubben a8131809b2 M-17: the manager's README says what it keeps on the bus and how it switches (issue 1409)
Builds: design 39 bus-and-switch §52, §1 to §13, §21, §23 to §46

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:39:21 +01:00
jschoubben b8ce072c9f M-16: the manager whole on a real bus and today's database on a real postgres (issue 1409)
The built binary played as its runtime plays it, its kept data in a real
nats-server's buckets and streams, today's rows in a real postgres: a start
stopped at a table says carry-over-failed and acts on nothing, the next
carries every row and a further one writes nothing; an unreachable database
calls no vendor; a reading at the threshold moves four consumers in one act
and no-room is raised and cleared on the operator channel's subject, read
through its client; a new login is adopted, listed and a target.

Builds: design 39 bus-and-switch §9, §11, §12, §13, §23, §26, §28, §29, §46, B41

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:37:54 +01:00
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
jschoubben 9e545658a5 M-14: the manager's conditions, kept in its state and said on the operator channel (issue 1409)
Each condition the manager holds is written in its state conditions with its
body and since, then said on the operator channel's condition accept under
claude-licence-manager, scope module; it is said again only when its words
change, and said cleared, its key deleted, once it no longer holds. At its
start, after the carry-over's run, it judges every kind anew: raised again
each it holds, cleared each kept key it no longer holds. The kinds:
carry-over-failed, settings.<setting>.invalid for a pair above
switch_at_pct and for a setting outside its bound, and per licence no-room
when the rule finds no target, near-limit at warn_at_pct with consumers, and
usage-unread once its latest success, or its adoption when none, is older
than usage_stale_minutes. Headlines name counts and short names, cut at 60.

Builds: design 39 bus-and-switch §9, §11, §29, §30, §31, §33, B17, B34, B36, B37, B38, B39

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:13:05 +01:00
jschoubben 4e10ab7b2e M-13: a licence at its threshold moves every consumer, in one act (issue 1409)
After each reading of a licence with consumers whose session or weekly
window is at or above switch_at_pct, the manager moves every consumer bound
to it, when switch_auto is on and the settings are valid, to the other
licence with known room whose windows are both below room_below_pct and the
most room, the first by name on a tie. Each consumer moves as the switch verb
does, by usage, and says binding.changed; the act says one licence.switched
and one audit entry switched with the window, its percentage and reset and
the target's room, and is told to the operator; the switch verb is not told.
By hand the rule runs with switch_auto off, and dry answers the moves alone.

Builds: design 39 bus-and-switch §26, §27, §28, §32, §33, B22

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:00:19 +01:00
jschoubben 954e3afe56 M-12: the switch's settings, read again at every reading (issue 1409)
The settings gain switch_auto, switch_at_pct, room_below_pct, warn_at_pct,
usage_minutes and usage_stale_minutes with the manifest's defaults and
bounds, a value outside its bound keeping the default. The settings file is
read again at every reading, each changed value written in the audit under
settings, and the usage timer follows usage_minutes. A room or warning above
switch_at_pct is found as an invalid pair naming both values.

Builds: design 39 bus-and-switch §33, §24

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:57:38 +01:00
jschoubben 6cf98386e9 M-11: each reading keeps its three reset times, and a licence's room (issue 1409)
The usage reading flattens the session, weekly and sonnet windows each with
its reset time, keeps it beside the licence with when it was read, and says
it as usage.read; a failed answer keeps the last percentages and writes the
vendor's status and reason. A licence's room is 100 less the higher of its
session and weekly windows, known only for a subscription read within
usage_stale_minutes whose refresh is healthy; an API key's is never known.

Builds: design 39 bus-and-switch §24, §25, §48, §2

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:55:22 +01:00
jschoubben bd65b023bd M-10: the manager hears what each node holds as holdings-changed (issue 1409)
The manager consumes the agent module's holdings-changed, keeping each report
as its node's latest and considering them at once, in place of reading the
agent module's state; once it hears them, at every start after its
carry-over, it says holdings.asked with its time and who asks, so every node
says what it holds again.

Builds: design 39 bus-and-switch §23, §46, B12, B22

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:53:10 +01:00
jschoubben b63e8ae58a M-8: the bindings verb answers each licence's key (issue 1409)
Each binding the verb bindings answers carries its licence's key beside the
§3 fields, as binding.changed does, so the vault's first view matches a
token's secret name to the binding without the key form of its own.

Builds: design 39 bus-and-switch §17, B10, B25 (hq 128236e7 and after, PR #561)

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:32:31 +01:00
jschoubben 3991211993 M-9: every change of a binding is said as binding.changed (issue 1409)
Bind, switch, release, a login's move and a rotation's advance each say one
binding.changed per consumer moved, with the licence's key, after the key of
bindings is written or deleted; a switch through the verbs says one
licence.switched with the from, to, consumers, by and why the audit's
switched entry also holds. licence.bound, released and rotated are said no
more. An adoption advances the consumers already bound before it moves the
login's nodes, so each is said once.

Builds: design 39 bus-and-switch §21, §27 (the verb's switch), §36, B7, B16

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:30:39 +01:00
jschoubben 9cc62818f4 M-6: today's rows carried over once, before the manager does anything else (issue 1409)
At its first start the manager reads the six tables of its database, read
only, and writes them into its kept data and its grants into the vault at
generation 1, then carried.postgres; until then only its read verbs answer.
A second start never opens the database, and a stopped run is done again
whole without doubling a log entry. The store's write code and its postgres
test go: nothing writes the database again.

Builds: design 39 bus-and-switch §1, §8, §11, §12, §13, A3, B14

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:28:14 +01:00
jschoubben 2b604e0dad M-5: the usage and audit logs kept in the bus's own storage
Every reading is appended under its licence's key with the vendor's answer whole (ADR
0054), and every act under its kind, never rewritten; an act whose audit entry the log
does not take fails and says so. The memory store goes: the tests run the bus store over
a bus kept in memory.

Builds: design 39 bus-and-switch §6, §7, B20

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:19:03 +01:00
jschoubben 9770dab09e M-4: leases and offered grants kept in the bus's own storage
A lease is a key created with a two-minute lifetime, which fails while another holds it:
the bus's own compare-and-set keeps one rotation source (ADR 0183 §6). An offered grant's
outcome is kept under its fingerprint in the key form for 90 days, every outcome kept.

Builds: design 39 bus-and-switch §4, §5, B13

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:17:16 +01:00
jschoubben 15094d6e72 M-3: licences and bindings kept in the bus's own storage
The state licences holds one key per licence by its key form, never a secret, with its
latest reading beside it; the state bindings, the same local name the consumers read,
holds each consumer's licence, kind, generation, since, why and by, written by the store
itself. Each consumer counts its own generations, past what its node reported.

Builds: design 39 bus-and-switch §2, §3, B11, B22

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:16:25 +01:00
jschoubben 5ca3876bea M-7: every grant and access token lives in the vault
The manager hands what the vendor answers to licence-put, the grant first and then the
access, at the licence's next generation, and reads a grant with get for one act; it keeps
no token at rest (ADR 0550 §3). The sealing at rest goes; opening stays for the carry-over.

Builds: design 39 bus-and-switch §15, §16, §20, §45, B15

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:13:31 +01:00
jschoubben f29a9ad563 M-8: current answers the licence and its health, never the token
The vault hands every token (ADR 0550 §3); current names the licence, its key, kind,
generation, health and access expiry, and keeps the account's identity for the agent's
account file. bindings takes an optional consumer, answered unpaged.

Builds: design 39 bus-and-switch §18, B7, B10, B19

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:11:09 +01:00
jschoubben 1c03299721 M-2: the manager's manifest asks for its kept data by template
Eight bus-state entries in the typed long form; the old state and the reads of
claude-code.holdings go, and so does the run-once prepare, since nothing writes the
tables again. The database and grant-key stay as the carry-over's read-only source.
The manifest declares its new events, its consumption of holdings-changed, the
operator channel it uses, the verbs it invokes, six settings and its eight tools.

Builds: design 39 bus-and-switch §1, §9, §21, §23, §27, §33, §37-§44, A3, B9, B16

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:01:43 +01:00
jschoubben e995e40519 M-1: a licence's key form, and an adoption whose key is taken is refused
A key of the bus takes fewer characters than a licence's name; the key is the store's
alone, and two names that write the same key would overwrite each other.

Builds: design 39 bus-and-switch §10, B6

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 22:58:52 +01:00
jschoubben fb6bf361e4 Require every mesh package of the group at the floor version (ADR 0573 sections 2 and 4, issue 1966)
A member's go.mod names the oldest version a module accepts; the check builds the group's own heads whatever it names. go.sum lines of mesh packages are left out.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 16:56:39 +00:00
jschoubben d8af436536 Pin mesh-sdk 05b3e2e1b94f and mesh-contracts ae5266e77639, the group's final heads, in every Go module (issue 1966)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:46:38 +01:00
jschoubben 5ab349df6c Pin mesh-sdk e5d89c5eedb5 and mesh-contracts 347a3053580a, the group's heads after review round 2, in every Go module (issue 1966)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:25:28 +01:00
jschoubben 80cdcbfa2f Pin mesh-sdk c54648e74346 and mesh-contracts ec9529022c44, the group's heads, in every Go module (issue 1966)
Every module that imports mesh-sdk requires its group head; each requires the mesh-contracts seats and clients it
imports, and mesh-contracts/go through the SDK's seat list. The placeholder versions are gone.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:43:43 +01:00
jschoubben 68e7a2ddbf Pin mesh-sdk at f02f142, the sdk1 head whose tests no longer leak package globals, so the group's check builds against it
mesh/delivery-group group feat/sdk1 rejected: composed together: the checker's verdict at the members' heads (6b0f5a2e9397): integration: meshbuildgo/cmd/mesh-…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: meshbuildgo/cmd/mesh-build-go… | 68e7a2ddbfb9 on ee75d4327ce5 | judged by the candidate's mesh-build-go at 773f299aed81
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 20:14:16 +01:00
jschoubben 7f4cc9e2ec Pin mesh-sdk at the sdk1 group's head 1c7356c, so the group's check builds every module against the narrowed SDK it was written for
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:29:53 +01:00
jschoubben 7f8701637f Pin mesh-sdk to the group head of issue 1126
mesh/delivery-group group feat/1126-removal rejected: composed together: the checker's verdict at the members' heads (a523066f1791): integration: meshbuildgo/c…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: meshbuildgo/cmd/mesh-build-go… | 7f8701637f9d on 32c4c52261c4 | judged by the candidate's mesh-build-go at b82f7f676986
Every member builds against mesh-sdk abf5eac, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:29:57 +01:00
jschoubben 04379702e5 Pin mesh-sdk to the group head of issue 1126
Every member builds against mesh-sdk f569924, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:29:56 +01:00
jschoubben 70396caeab Pin mesh-sdk to the group head of issue 1126
Every member builds against mesh-sdk 729a86b, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:29:56 +01:00
jschoubben 3e08301b27 Pin every module to the group's mesh-sdk head after the gofmt fix
mesh/delivery-group group feat/judge-once rejected: composed together: the checker's verdict at the members' heads (5c42ba1fb5f3): integration: artifactstoreto…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: artifactstoretools/cmd/artifa… | 3e08301b27ea on 4b16c193f0fb | judged by the candidate's mesh-build-go at 8dd51540f73a
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:30:59 +01:00
jschoubben c4406da4a3 Pin every module to the group's mesh-sdk head after review round 1
mesh/delivery-group group feat/judge-once checking: 6 of 6 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check checking c4406da4a3fe on 4b16c193f0fb | novox/mesh-catalog#585 in group feat/judge-once
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:17:47 +01:00
jschoubben c19edac804 Pin every module that names mesh-sdk at the group's final head, without the judge (ADR 0578 §4)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:50:43 +01:00
mesh-tools 652b00b25b Say on every directory how it treats files it does not know, and check it (issue 1024, ADR 0422)
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.
2026-10-17 16:23:03 +00:00
mesh-tools 0260ae92f6 Feature 57 (config and provisioning): release the catalogue, the reviewed release branch merged with main, on top of main 2026-10-16 09:42:45 +00:00
mesh-admin 1725ffc799 licence manager: a login's adoption is said beside the bindings it made, not first (issue 625)
mesh/merge-gate head 1725ffc799bd merged into trunk 8922c890595b — pass: builds claude-code, claude-licence-manager → ace, g14, novox, shanks; no bus step;…
mesh/repo-check head 1725ffc799bd merged into trunk 8922c890595b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
The bindings a login makes are now announced as licence.bound before the adoption's own event, so the test asks for each event rather than for adoption to come first.
2026-10-13 11:49:30 +00:00
mesh-admin a72eb3dbb2 licence manager: say what moved a binding, why, and who asked (issue 625)
mesh/merge-gate head a72eb3dbb28d merged into trunk 19ecad261cf2 — pass: builds claude-code, claude-licence-manager → ace, g14, novox, shanks; no bus step;…
mesh/repo-check head a72eb3dbb28d merged into trunk 19ecad261cf2 — fail: its merge-check.sh failed: --- FAIL: TestAManagerWithNoLicenceAdoptsTheNewestLogin…
mesh/delivery superseded: a newer head of the same pull request
A switch of g14 back to another licence on 2026-10-13 left no trace: the manager logged no line and sent no event for bind, switch or release, and its audit kept only the verb's name. Every change of what a consumer holds now goes to the journal, the audit and a licence.<what> event with the same fields: consumer, licence before and after, generation, why (the verb or the rule) and by (the caller the runtime proved, or the manager itself). The node is told why in the bindings state and says it when it applies. The audit and binding tables become readable through the store's reader. The SDK moves to the version that proves the caller (issue 365).
2026-10-13 11:45:52 +00:00
jochen c8fb0016f2 Test that a sealed grant hides its words, not two letters
mesh/merge-gate pass: builds claude-code, claude-licence-manager, restic → ace, g14, novox, shanks; no bus step; every machine composes with the change as …
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery stopped: the pull request closed unmerged
The check looked for "rt" in a random base64 ciphertext, which holds it
about one time in seventy, and failed mesh-catalog #141's repo check by
chance.
2026-10-09 10:14:18 +02:00
jochen b57e6e8144 claude-code: write the agent's home as its own account where it has one
On the control node agents are to run as an account that cannot become root
(hq ADR 0266). The runtime keeps running as the operator, so the token, the
identity and the home-scope items go to the agent's home through a helper
started as that account; the licence and the account file follow the move,
what was placed in the operator's home goes once the agent holds the licence,
and the guard refuses every tool of a session running as anyone else,
recording to the journal. An API key is taken only from the agent's home and
only when it is one, here and in the licence manager, so the tool cannot carry
off the runtime's credential. Unchanged where no agent account is named.
2026-10-09 10:14:18 +02:00
jochen b91b4c427d Apply a binding that differs, and never repeat a generation a node passed
A licence store rebuilt after issue 241 counted generations from one again,
and nodes that apply only a higher number discarded the login move and the
rotations unseen. Nodes now apply any binding other than the one applied;
the manager moves its sequence past every generation a node reports. hq
issue 243.
2026-10-05 09:59:19 +02:00
jochen 0dc13965e5 The licence manager's verb is public-key: a seat's verb is lower-case letters, digits and hyphens
public_key failed the builder's manifest check and stopped the manager's
rebuild; claude-code asks the new name.
2026-10-04 15:33:28 +02:00
jochen e8562b58ce The licence manager's seat declares public_key, which claude_code_add_api_key asks (novox/hq ADR 0209) 2026-10-04 15:22:15 +02:00
jochen 4ef4983d0d A login moves its node; an API key is added from any node, sealed (novox/hq ADR 0209)
The manager binds the node a login was adopted from to that login's licence,
switching it if it was bound to another; serves public_key; adopt takes a
key sealed to it. claude-code gains claude_code_add_api_key: read a file on
this node, seal, hand to adopt, remove the file, optionally switch here.
2026-10-04 15:11:54 +02:00
jochen af63f12129 The licence manager binds a node reporting an account it already holds
Found going live: the other nodes report the adopted account with older
logins, which are never candidates, and the first binding was only made at
adoption — so a node reporting afterwards was never bound (ADR 0206 §7).
2026-10-04 12:34:39 +02:00
jochen 306d01d74d claude-code in Go (operator: always Go)
The module is one Go binary the runtime launches: the renderer (its
instruction file held byte for byte to the TypeScript one it replaces),
the credentials and identity files, the licence flow of ADR 0206 and the
MCP servers in state. Keeps the TypeScript module's key files, so a node
moving to it keeps its key. The npm package, its tests and its build go.

Both binaries were run together under the real runtime on a test bus with
postgres and a stub vendor: a login was adopted by one exchange, the node
bound and handed an access token, its file left with no refresh token, and
no token in either state.
2026-10-04 12:27:36 +02:00
jochen 15b2e6b86e The licence manager, in Go, and claude-code's half of ADR 0206
claude-licence-manager holds the anthropic-licence-manager seat: it reads
every node's holdings state, adopts a login it does not hold by refreshing
it (newest first, once per account), keeps each grant alive under a lease,
publishes what each consumer should hold as its bindings state with a
generation, and answers current sealed to the consumer's key. Postgres
store prepared by a run-once step; grants encrypted with the vault's key.

claude-code reports what its node holds (fingerprints and account, never a
token), hands its grant over only when the manager asks, watches its
binding and fetches the token on a newer generation, and writes
access-token-only. Its ask now reads the runtime's answer as a value and
addresses seats as seats.
2026-10-04 12:18:51 +02:00