// Adoption: the ONE time an operator's refresh token enters the mesh, and it enters already sealed. // // The refresh token is read here, on the MANAGER NODE, sealed at rest to that node's own key, and // only the sealed envelope leaves this process (novox/hq ADR 0050, Phase C). The control plane stores // that envelope via `licence set-grant` without ever seeing the refresh token in the clear — the same // bound every refresh keeps. This is the counterpart to `refresh/index.js`: adoption seals the first // envelope, refresh opens and re-seals it. // // MESH_ANTHROPIC_REFRESH_TOKEN_FILE the operator's refresh token, read once and never written out // MESH_NODE_SEALING_PUBLIC_FILE the manager node's public sealing key (base64 raw X25519) // MESH_ANTHROPIC_GRANT_OUT where the sealed envelope is written, for `licence set-grant` import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { sealAtRest } from "../atrest.js"; function required(name: string): string { const v = process.env[name]; if (!v) throw new Error(`${name} is not set — adoption needs it`); return v; } const refreshToken = readFileSync(required("MESH_ANTHROPIC_REFRESH_TOKEN_FILE"), "utf8").trim(); if (!refreshToken) throw new Error("[anthropic-manager] there is no refresh token to adopt"); const nodePub = readFileSync(required("MESH_NODE_SEALING_PUBLIC_FILE"), "utf8").trim(); const envelope = sealAtRest(refreshToken, nodePub); const out = required("MESH_ANTHROPIC_GRANT_OUT"); mkdirSync(dirname(out), { recursive: true }); const tmp = `${out}.tmp`; writeFileSync( tmp, JSON.stringify({ token: envelope.token, wrapped_key: envelope.wrappedKey, manager_key: envelope.managerKey }), { mode: 0o600 }, ); renameSync(tmp, out); console.error("[anthropic-manager] sealed the refresh token at rest; only this node's key opens it");