The control-plane side of the vendor-agnostic model-access refreshable-grant (ADR 0050). A manager node is set for a licence; the operator's refresh token is sealed to that node's public key and stored as ciphertext the control plane cannot open. set-grant accepts the sealed box; the host unseals it on delivery (the ordinary sealed-credential path) and mounts the cleartext only at the manager module's secret path. submit-refresh takes back only the rotated access token (sealed to each consumer holder) plus an opaque re-sealed refresh box — the refresh token is never read in the clear by the control plane, and no node but the manager holds it.
Adapter abstraction keyed by licence.vendor with a static-key and a refreshable-grant shape; KeyFor disambiguates manager (access + refresh) from consumer (access only). Migrations 0001 (edited) + 0003 (incremental). The TS(tweetnacl-sealedbox-js)→Go(box.OpenAnonymous) cross-check fixture is regenerated and TestModuleSealedBoxOpensInGo proves interop.
Proven end to end by the mesh-lab anthropic-bed suite (green).
The control-plane side of the vendor-agnostic model-access refreshable-grant (ADR 0050). A manager node is set for a licence; the operator's refresh token is sealed to that node's public key and stored as ciphertext the control plane cannot open. `set-grant` accepts the sealed box; the host unseals it on delivery (the ordinary sealed-credential path) and mounts the cleartext only at the manager module's secret path. `submit-refresh` takes back only the rotated access token (sealed to each consumer holder) plus an opaque re-sealed refresh box — the refresh token is never read in the clear by the control plane, and no node but the manager holds it.
Adapter abstraction keyed by `licence.vendor` with a static-key and a refreshable-grant shape; `KeyFor` disambiguates manager (access + refresh) from consumer (access only). Migrations 0001 (edited) + 0003 (incremental). The TS(tweetnacl-sealedbox-js)→Go(box.OpenAnonymous) cross-check fixture is regenerated and `TestModuleSealedBoxOpensInGo` proves interop.
Proven end to end by the mesh-lab `anthropic-bed` suite (green).
Phase C of model-access (ADR 0050). Refresh above calls an in-process
VendorRefresher, which would open the at-rest envelope inside the control
plane's own process. Anthropic must not: its refresh runs on the manager
node. So add SubmitRefresh, the companion that publishes a refresh a
manager node already performed -- it is given only the new access token in
the clear (sealed per holder, as any accepted key) and an opaque re-sealed
refresh envelope (stored unopened). The refresh token in the clear never
crosses this boundary. The reseal-and-publish half is extracted and shared
with Refresh, so the sealing logic is one implementation.
CLI: licence grant (print the opaque envelope), set-grant (store a
module-produced envelope -- adoption), submit-refresh (access token +
optional rotated envelope). Tests defend that the manager alone opens the
refresh token and the control plane never holds it in the clear.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The refreshable-grant refresh token no longer rides a custom at-rest envelope that a
module opens with a node private key. A module is never given a node's private sealing
key, so that path could not exist -- the gap Phase C hit.
Instead the refresh token is a credential sealed to the MANAGER holder with the same
anonymous box (secrets.Seal / crypto_box_seal) every credential uses, stored as one
sealed blob, and delivered by the existing host-unseal-and-mount: the host opens it with
the node's real key and mounts the cleartext at the manager module's bound path, exactly
as a consumer's db password is delivered.
- refresh_grant now stores { sealed, manager_key }, dropping the AtRest token/wrapped_key
columns; internal/secrets/atrest.go is retired (nothing else used it).
- the licence records its manager as (node, module); KeyFor delivers the refresh token to
the manager holder and the access token to consumers, disambiguated by module so the two
can co-locate. Accept and the reseal skip the manager holder.
- the manager holder is delivered the node's PUBLIC sealing key in its bound facts, so the
module can re-seal a rotated refresh token with no private key of its own; the
declaration tolerates its empty pre-adoption secret rather than refusing.
- SubmitRefresh / set-grant take a sealed blob, never a refresh token in the clear.
The invariant holds unchanged: the control plane never reads the refresh token, and no node
but the manager holds it. A committed cross-language test proves the TypeScript module seal
opens under Go box.OpenAnonymous (the host's Unseal) -- both are NaCl crypto_box_seal.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The manager module's seal now runs over the audited tweetnacl-sealedbox-js (mesh-catalog
anthropic-manager) rather than a hand-transcribed NaCl. Regenerate the fixture's sealed value
from that new seal() over the same node key pair and plaintext, so the fixture is the new
library's output. crypto_box_seal is randomised, so the blob differs; the wire format does
not. TestModuleSealedBoxOpensInGo still opens it under box.OpenAnonymous and recovers the
plaintext, proving TS(tweetnacl-sealedbox-js) to Go interop.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The control-plane side of the vendor-agnostic model-access refreshable-grant (ADR 0050). A manager node is set for a licence; the operator's refresh token is sealed to that node's public key and stored as ciphertext the control plane cannot open.
set-grantaccepts the sealed box; the host unseals it on delivery (the ordinary sealed-credential path) and mounts the cleartext only at the manager module's secret path.submit-refreshtakes back only the rotated access token (sealed to each consumer holder) plus an opaque re-sealed refresh box — the refresh token is never read in the clear by the control plane, and no node but the manager holds it.Adapter abstraction keyed by
licence.vendorwith a static-key and a refreshable-grant shape;KeyFordisambiguates manager (access + refresh) from consumer (access only). Migrations 0001 (edited) + 0003 (incremental). The TS(tweetnacl-sealedbox-js)→Go(box.OpenAnonymous) cross-check fixture is regenerated andTestModuleSealedBoxOpensInGoproves interop.Proven end to end by the mesh-lab
anthropic-bedsuite (green).The refreshable-grant refresh token no longer rides a custom at-rest envelope that a module opens with a node private key. A module is never given a node's private sealing key, so that path could not exist -- the gap Phase C hit. Instead the refresh token is a credential sealed to the MANAGER holder with the same anonymous box (secrets.Seal / crypto_box_seal) every credential uses, stored as one sealed blob, and delivered by the existing host-unseal-and-mount: the host opens it with the node's real key and mounts the cleartext at the manager module's bound path, exactly as a consumer's db password is delivered. - refresh_grant now stores { sealed, manager_key }, dropping the AtRest token/wrapped_key columns; internal/secrets/atrest.go is retired (nothing else used it). - the licence records its manager as (node, module); KeyFor delivers the refresh token to the manager holder and the access token to consumers, disambiguated by module so the two can co-locate. Accept and the reseal skip the manager holder. - the manager holder is delivered the node's PUBLIC sealing key in its bound facts, so the module can re-seal a rotated refresh token with no private key of its own; the declaration tolerates its empty pre-adoption secret rather than refusing. - SubmitRefresh / set-grant take a sealed blob, never a refresh token in the clear. The invariant holds unchanged: the control plane never reads the refresh token, and no node but the manager holds it. A committed cross-language test proves the TypeScript module seal opens under Go box.OpenAnonymous (the host's Unseal) -- both are NaCl crypto_box_seal. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF