Files
mesh-catalog/modules/claude-code/test/node.test.ts
T
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

200 lines
12 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
apply, grantFor, holdingsOf, keypair, onBinding, onServerChange, pull, registerServer, registered, ServerView, type Paths,
type ServerChange, type ServerState,
} from "../dist/node.js";
import { generateKeyPair, open, seal } from "../dist/seal.js";
const NOW = Date.now();
function node(name = "laptop"): { p: Paths; written: Record<string, string> } {
const root = mkdtempSync(join(tmpdir(), "cc-node-"));
const p = { state: join(root, "state"), facts: join(root, "state", "facts.json"), settings: join(root, "state", "settings.json"), home: join(root, "home"), node: name };
mkdirSync(p.state, { recursive: true });
mkdirSync(join(p.home, ".claude"), { recursive: true });
writeFileSync(p.facts, JSON.stringify({ node: name, console: "http://127.0.0.1:4270/mcp" }));
writeFileSync(p.settings, JSON.stringify({ role: "", mcp_servers: {} }));
return { p, written: {} };
}
const writer = (w: Record<string, string>) => (name: string, content: string) => { w[name] = content; return `${name}: written`; };
const creds = (p: Paths) => JSON.parse(readFileSync(join(p.home, ".claude", ".credentials.json"), "utf8"));
const grantFor_ = (p: Paths, licence: string, token: string, kind: "subscription" | "api-key" = "subscription", identity?: object) => ({
licence, kind, identity,
sealed: seal(kind === "api-key" ? token : JSON.stringify({ accessToken: token, expiresAt: NOW + 3_600_000, refreshTokenExpiresAt: NOW + 86_400_000, subscriptionType: licence }), keypair(p).publicKey),
});
test("a pull asks the seat with this node's key and applies what it answers", async () => {
const { p, written } = node();
let asked: [string, Record<string, unknown>] | null = null;
const r = await pull(p, async (address, args) => { asked = [address, args]; return grantFor_(p, "personal", "at-1"); }, writer(written));
assert.equal(asked![0], "seat:anthropic-licence-manager.current");
assert.equal(asked![1].consumer, "laptop");
assert.match(String(asked![1].public_key), /BEGIN PUBLIC KEY/);
assert.equal(r.applied, true);
assert.equal(creds(p).claudeAiOauth.accessToken, "at-1");
assert.ok(written["managed-mcp.json"]);
});
test("a switch replaces the old licence's grant whole and points the account at the new one", () => {
const { p, written } = node();
writeFileSync(join(p.home, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "old" }, projects: { keep: 1 } }));
apply(p, grantFor_(p, "personal", "at-1"), writer(written));
const r = apply(p, grantFor_(p, "work", "at-2", "subscription", { accountUuid: "new", emailAddress: "w@example.org" }), writer(written));
assert.equal(r.switched, true);
assert.equal(creds(p).claudeAiOauth.accessToken, "at-2");
assert.equal(creds(p).claudeAiOauth.subscriptionType, "work", "the old licence's subscription type survived the switch");
const account = JSON.parse(readFileSync(join(p.home, ".claude.json"), "utf8"));
assert.equal(account.oauthAccount.accountUuid, "new");
assert.deepEqual(account.projects, { keep: 1 });
});
test("switching to the API key adds the key-helper; switching away removes the key and the helper", () => {
const { p, written } = node();
apply(p, grantFor_(p, "api", "sk-key", "api-key"), writer(written));
assert.ok(JSON.parse(written["managed-settings.json"]).apiKeyHelper);
assert.ok(existsSync(join(p.state, "api-key")));
apply(p, grantFor_(p, "personal", "at-1"), writer(written));
assert.ok(!("apiKeyHelper" in JSON.parse(written["managed-settings.json"])));
assert.ok(!existsSync(join(p.state, "api-key")) && !existsSync(join(p.state, "api-key-helper")));
});
test("what a node holds is reported with fingerprints and its account, never a token", () => {
const { p } = node();
writeFileSync(join(p.home, ".claude", ".credentials.json"), JSON.stringify({ claudeAiOauth: { accessToken: "at-secret", refreshToken: "rt-secret", expiresAt: NOW + 1000, refreshTokenExpiresAt: NOW + 9000 } }));
writeFileSync(join(p.home, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "u-1", emailAddress: "a@example.org" } }));
const h = holdingsOf(p);
assert.equal(h.node, "laptop");
assert.equal(h.identity?.accountUuid, "u-1");
assert.equal(h.kind, "subscription");
assert.equal(h.refresh.present, true);
assert.match(String(h.refresh.fingerprint), /^sha256:[0-9a-f]{16}$/);
assert.equal(h.access?.expiresAt, NOW + 1000);
assert.ok(h.changedAt);
const text = JSON.stringify(h);
assert.ok(!text.includes("at-secret") && !text.includes("rt-secret"), "a token is in the report");
assert.ok(!/token|secret|password/i.test(Object.keys(h).join(" ") + " " + Object.keys(h.refresh).join(" ")),
"a field the runtime would refuse is in the report");
});
test("a node with nothing reports nothing held, and the grant answers only a waiting login", () => {
const { p } = node();
const h = holdingsOf(p);
assert.equal(h.kind, null);
assert.equal(h.refresh.present, false);
const manager = generateKeyPair();
assert.equal(grantFor(p, manager.publicKey), null);
writeFileSync(join(p.home, ".claude", ".credentials.json"), JSON.stringify({ claudeAiOauth: { accessToken: "at", refreshToken: "rt-login", expiresAt: NOW } }));
writeFileSync(join(p.home, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "u-9" } }));
const g = grantFor(p, manager.publicKey)!;
assert.equal(g.identity?.accountUuid, "u-9");
assert.equal(JSON.parse(open(g.sealed, manager.privateKey)).refreshToken, "rt-login");
assert.ok(!JSON.stringify(g).includes("rt-login"), "the refresh token crossed in the clear");
});
test("a newer generation in the bindings fetches the token once, by the seat's verb; an equal one asks nothing", async () => {
const { p, written } = node();
const asked: string[] = [];
const seatAsk = async (address: string) => { asked.push(address); return { ...grantFor_(p, "personal", "at-1"), generation: 3 }; };
await onBinding(p, { licence: "personal", kind: "subscription", generation: 3 }, seatAsk, writer(written));
assert.deepEqual(asked, ["seat:anthropic-licence-manager.current"]);
assert.equal(creds(p).claudeAiOauth.accessToken, "at-1");
assert.equal(await onBinding(p, { licence: "personal", kind: "subscription", generation: 3 }, seatAsk, writer(written)), null);
assert.equal(asked.length, 1, "an equal generation asked again");
assert.equal(holdingsOf(p).generation, 3);
});
test("the token a node is handed replaces a login's grant and leaves no refresh token", () => {
const { p, written } = node();
writeFileSync(join(p.home, ".claude", ".credentials.json"), JSON.stringify({ claudeAiOauth: { accessToken: "at-old", refreshToken: "rt-spent", expiresAt: NOW + 7_200_000 } }));
const r = apply(p, grantFor_(p, "personal", "at-new"), writer(written));
assert.equal(r.applied, true);
assert.equal(creds(p).claudeAiOauth.accessToken, "at-new");
assert.equal(creds(p).claudeAiOauth.refreshToken, undefined, "a refresh token survived the hand-over");
assert.equal(holdingsOf(p).refresh.present, false);
});
/** The `servers` state as the bus holds it, shared by every node in a test, with each node's watch. */
function bus() {
const kept = new Map<string, Record<string, unknown>>();
const watchers: ((c: ServerChange) => void)[] = [];
const state: ServerState = {
put: async (key, value) => { kept.set(key, value); watchers.forEach((w) => w({ key, op: "put", value })); return kept.size; },
delete: async (key) => { kept.delete(key); watchers.forEach((w) => w({ key, op: "delete" })); },
keys: async () => [...kept.keys()].sort(),
};
/** A node joining: its view takes the current state, then every change. */
const join = (n: { p: Paths; written: Record<string, string> }) => {
const view = new ServerView(n.p);
for (const [key, value] of kept) onServerChange(view, { key, op: "put", value }, n.p, writer(n.written));
watchers.push((c) => onServerChange(view, c, n.p, writer(n.written)));
return view;
};
return { state, join, kept };
}
test("registering a server here puts it under this node's key, renders it, and asks about the other nodes", async () => {
const n = node();
const b = bus();
const view = b.join(n);
const r = await registerServer(n.p, { name: "search", entry: { type: "http", url: "https://s.example/mcp" } },
b.state, view, writer(n.written), async () => ["laptop", "server", "desktop"]);
assert.equal(r.here, "changed");
assert.match(String(r.also), /server, desktop/);
assert.deepEqual([...b.kept.keys()], ["laptop.search"]);
assert.ok(JSON.parse(n.written["managed-mcp.json"]).mcpServers.search);
});
test("registering for every node reaches the others through their watch, and a node joining later reads it", async () => {
const a = node("laptop"), s = node("server");
const b = bus();
const va = b.join(a);
b.join(s);
await registerServer(a.p, { name: "docs", entry: { type: "stdio", command: "docs-mcp" }, nodes: "all" },
b.state, va, writer(a.written), async () => []);
assert.deepEqual([...b.kept.keys()], ["all.docs"]);
assert.deepEqual(registered(s.p).docs, { type: "stdio", command: "docs-mcp" });
assert.ok(JSON.parse(s.written["managed-mcp.json"]).mcpServers.docs);
// The gap events left: a node assigned after the registration takes the whole current set at start.
const late = node("desktop");
b.join(late);
assert.deepEqual(registered(late.p).docs, { type: "stdio", command: "docs-mcp" });
// Unregistering is a delete, and every node's view drops it.
await registerServer(a.p, { name: "docs", nodes: "all" }, b.state, va, writer(a.written), async () => []);
assert.equal(registered(s.p).docs, undefined);
assert.equal(registered(late.p).docs, undefined);
});
test("a node's own registration overrides the one for every node; other nodes' keys leave this one alone", async () => {
const a = node("laptop"), s = node("server");
const b = bus();
const va = b.join(a);
const vs = b.join(s);
await registerServer(a.p, { name: "x", entry: { type: "http", url: "https://all" }, nodes: "all" }, b.state, va, writer(a.written), async () => []);
await registerServer(a.p, { name: "x", entry: { type: "http", url: "https://laptop" } }, b.state, va, writer(a.written), async () => []);
assert.equal(registered(a.p).x.url, "https://laptop");
assert.equal(registered(s.p).x.url, "https://all");
await registerServer(a.p, { name: "only", entry: { type: "http", url: "https://o" }, nodes: ["server"] }, b.state, va, writer(a.written), async () => []);
assert.equal(registered(a.p).only, undefined);
assert.equal(registered(s.p).only.url, "https://o");
// Unregistering here leaves the every-node one applying, and says so.
const r = await registerServer(a.p, { name: "x" }, b.state, va, writer(a.written), async () => []);
assert.match(String(r.still), /still applies here/);
assert.equal(registered(a.p).x.url, "https://all");
assert.equal(vs.effective().x.url, "https://all");
});
test("a bad entry is refused before anything is put; a repeated change changes nothing", async () => {
const n = node();
const b = bus();
const view = b.join(n);
const r = await registerServer(n.p, { name: "mesh", entry: { type: "http", url: "https://x" } }, b.state, view, writer(n.written), async () => []);
assert.equal(r.registered, false);
assert.equal(b.kept.size, 0);
assert.equal(onServerChange(view, { key: "all.a", op: "put", value: { type: "http", url: "https://a" } }, n.p, writer(n.written)), "registered all.a");
assert.equal(onServerChange(view, { key: "all.a", op: "put", value: { type: "http", url: "https://a" } }, n.p, writer(n.written)), null);
assert.equal(onServerChange(view, { key: "server.b", op: "put", value: { type: "http", url: "https://b" } }, n.p, writer(n.written)), null);
});