cloud receiver: local admission keeps streams up when the control plane is slow
Summary
Phase A of Cloud receiver local admission (design: manaflow-ai/cmuxterm-hq#7144, plans/task-cmux-vm-cloud/receiver-local-admission.md). Leo, 2026-11-04: "of course start it".
Today a slow or failed receiving-policy read closes every stream and peer on a Cloud machine, so Cloud chats show "didn't connect" whenever the control plane is slow (production: receiver policy read failed after 20000 ms; streams and peers closed). With this change:
- The policy view gets an optional
admit_until, at most an hour afterchecked_at(MAX_ADMIT_MS). When it is present, the verifier's authority, each admitted stream's lease and the WireGuard peer set run toadmit_until. The lease (expires_at) only schedules renewals. - A read with no answer keeps the current policy and retries with backoff. No answer means a timeout, a 5xx or 429, the host agent restarting, or a late reply. Open streams stay up, and new streams with valid signed tokens are still admitted locally.
- A refusal closes at once, as before. A refusal is a 403, a 200 for another binding, epoch or generation, changed bound files, a malformed reply, or a socket that is not root's. The host agent's socket now answers
cloud.vm.network_policy.refusedor.unavailable, so the receiver can tell them apart. - A successful read still reconciles every stream at once, so a removed peer or service closes immediately.
- Kill switch: without
admit_until, every failed read closes as before. The platform side (cmux-platform PR,CLOUD_RECEIVER_ADMIT_MS,0= off) decides whether the field is sent. Older images ignore the field.
This builds on #35996 (per-stream drop reasons) and does not touch its files.
Testing
cmux-ci test (fleet Linux):
cargo test -p cmux-link --test native_token_receiver: the newnative_token_receiver_admission_window_holds_a_stream_until_revoked. A stream outlives the lease edge, and a read without its peer revokes it.cargo test -p cmux-host --lib cloud::: 162 passed. New tests:receiver_owner_agent_no_answer_keeps_admitted_streams_and_admits_new_ones(control plane down)receiver_owner_agent_refusal_clears_existing_authority(revocation)receiver_owner_agent_admission_window_comes_from_admit_untilvm_receiver_client_read_says_whether_a_failure_refused_or_went_unansweredvm_receiver_client_admit_until_runs_authority_to_the_admission_end_within_an_hour
cargo test -p cmux-tui --lib link::native: passed, including the newkeeps_policytests (no answer keeps, refusal closes, no admit_until closes, an ended admission keeps nothing).- The generated catalog, the TS client and the Rust wire were regenerated with the protocol scripts.
Risk
Security-sensitive: this widens how long a revoked peer's open stream can last, but only when the VM cannot read policy. The bound is the control-plane-chosen horizon (15 min default), enforced on the VM's monotonic and suspend-inclusive clock. While the control plane is up, the revocation window is unchanged (one lease).
cmux-platform: the admit_until issuer lives in cmux-platform receiver/Policy.ts.
Changelog
none
Agent session: agent session
๐ค Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith ยท Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes Cloud chats showing "didn't connect" whenever the control plane is slow, by keeping receiver streams and peers up through a failed policy read instead of closing them all.
Adds an optional admit_until to the Cloud receiving-policy view โ at most one hour after checked_at. When present, the verifier's authority, each admitted stream's lease, and the WireGuard peer set run to that horizon instead of the shorter renewal lease.
- A read with no answer (timeout, 5xx/429, host agent restarting, late reply) keeps the current policy, retries with backoff, and still admits new signed tokens locally.
- A refusal (403, reply for another binding/epoch/generation, changed bound files, malformed reply, keyset or budget, unauthorized socket) closes at once as before; the host agent's socket now distinguishes
cloud.vm.network_policy.refusedfrom.unavailable. - A successful read still reconciles at once, so a removed peer or service closes immediately.
- Without
admit_until, every failed read closes as before โ the kill switch, decided by the platform side.
Migration
The platform side must send admit_until to enable local admission; older images ignore the field and keep the current behavior.