relay-node: node-scoped workspace credentials (sh_wk_) through Control's broker

Merged
AG
10h ago
+2163-120

Acceptance side of the node-scoped workspace credential (sh_wk_). Contract: relay-control main 1e55273 (#83) and its docs/relay-node-credential.md; the issuing side is layered-control's.

What changes

  • An sh_wk_ key is a workspace credential bound to one spawn node name. Its workspaces.json entry (and project pin) carries control_url / controlUrl beside base_url and is refused without a bare https one. A node key is never accepted from an environment variable.
  • One routing helper sends the node's workspace-key calls (workspace identity, node lookup and enrolment, agent lookup) to <control_url>/relay-node/v1; the SDK transport refuses to send an sh_wk_ key anywhere else, so other calls are unavailable and the credential never reaches Relaycast. The report-back requester asks with the enrolled node token.
  • Broker errors: 401 token_invalid rejects the credential; 403 scope_denied is unavailable and rejects nothing; 403 node_mismatch stops the spawn node with a log line naming the configured node; 502 relaycast_unavailable is retried as an outage; Relaycast's own answers pass through as today.
  • An rk_ / ar_ws_ to sh_wk_ change on the same workspace is a rotation that keeps the enrolled node.
  • Workspace-key env vars are stripped from every spawned child (claude, grok, opencode, managed sessions); sh_wk_ joins the request-log and activity redaction lists.

Contract gaps for the issuing side

  1. The broker must answer with Relaycast's envelopes ({"ok":false,"error":{code,message}} / {"ok":true,"data":…}); the node parses nothing else.
  2. Relaycast must accept a node token on GET /v1/actions/spawn/invocations/{id} (report-back).
  3. The token's nodeName must equal the node's configured node_name; a mismatch stops the node.

Open questions

  • Should a passed-through Relaycast 403 mark an sh_wk_ workspace Rejected, or stay unavailable like scope_denied?
  • Confirm an sh_wk_ to sh_wk_ rotation keeps the enrolled node (implemented that way).

Tests

workspace/cli/tests.rs: store entry carries its origins; a workspace key ignores control_url; refused at load without control_url; non-https refused; project pin beside controlUrl; refused from every env var by name. Groups run from the built test binary on the agent host (env -u SSL_CERT_FILE -u SSL_CERT_DIR).

Merge on Khaliq's word via lead-desktop.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.