CommandBus queues typed, panel-signed (`cmd`) commands per node in
cluster_commands: FIFO by seq, dedupe_key for desired state, expiry,
pruned by cron:cluster. New ops: `commands` (long-poll after the node's
high-water, held up to 20 s; frozen while quarantined) and `ack` (own
commands only; raises cluster_nodes.cmd_seq).
For nodes with the COMMANDS flow on (toggled on Cluster Nodes),
ClusterRoute sends NodeRpc::request() as a signed node.rpc and waits for
its ack, and NodeRpc::broadcast() / SignalDispatcher::kill() queue
commands; other nodes and every LB keep the legacy transport.
On the node, `console.php cluster:exec` checks a command again against
the panel key the agent pinned and runs node.rpc through the legacy /api
handlers (InternalApiController::runCommand, NodeRpc::ACTIONS only) or
conn.kill_worker. Root actions stay on the signals table for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcNdxHieZuUh3EnMUKt7zf
The agent compares it with its own and says hello again to fetch a newer
transport policy, so a change of MAIN URLs reaches every node within one
heartbeat (Phase 3 groundwork for MAIN endpoint changes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcNdxHieZuUh3EnMUKt7zf
An active node with intact keys gets a new epoch without SSH:
GET challenge?cn=, then a node-signed POST token_rekey (epoch 0, no MAC)
whose body is SEALed to the panel box key under the request context.
MAIN checks the signature before recording anything, then the nonce,
state=active, a once-a-minute limit, the single-use challenge
(NonceStore::consume, atomic via a used marker), the instance_id
attestation (a mismatch quarantines) and the licence (the mint). The
reply is panel-signed `pre` and bound to the node and request nonce.
TokenService::rekey drops every other epoch row.
The install data now carries panel_box_pub; the test fake gets a real
box key, openSealed and licence-gated granting signatures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcNdxHieZuUh3EnMUKt7zf
ClusterApi serves health, challenge, enrol_complete, token_refresh, hello
and heartbeat. Nothing is written before the MAC (and, for token ops, the
node signature) verifies; refusals are panel-signed and bound to the
request nonce. Token refresh is idempotent per agent ephemeral key, so a
node holds at most two valid epochs. ClusterApiTest runs every flow against
a fake of the extension's token half and, opt-in, a real xcvm_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Es8UhyPS8MNjuYTagqfGWi