cmux NEXT Cloud: owner answer on the app-server link (cmux-platform#89 C1)

Merged
TE
6d ago
+150-7

Part C1 of manaflow-ai/cmux-platform#89 (the app side of the caller-ownership answer). Leo, 2026-10-30, on building the app-server cloud path now instead of after the Oct 31 demo: "well why can't we just.... do our best now?"

What

On the Cloud app-server link, every app-started chat and every folder-trust call on a Worker machine was refused with agent_session.host_unverified ("the Cloud API did not say this machine is yours"). The cause: AgentTabs+Wiring.swift built the owner closure only from the legacy link (CloudMachineSession.link), which is nil on an app-server session, so AgentSessionAttachClient.ownerAnswer always refused (cmux#27778 review, comment 6514080989).

This PR reads the owner answer on the app link, with the same rules as the legacy link:

  • CloudConnectOpResolver decodes vm_kind from the Cloud app server's cloud.machine.connect answer. Only the exact value owner (the classic attach answer's value) sets CloudLinkSocket.callerOwnsVM. An absent, empty, differently cased or padded value, or any other kind, does not.
  • CloudLinkSession.ownerLink is the current connect's id while that link is up (ready or attached) and its answer said owner, else nil. The attach client reads it before it connects and again as it sends, so the answer never outlives its link: a newer connect gets a new id, and a down or revoked change, a pause (end) or a second endpoint call ends it.
  • CloudMachineSession.ownerLink answers from whichever link the session has (legacy or app link). AgentTabs+Wiring uses it.

Until the server side sends vm_kind (piece A: cmux#27940 on the Worker and cmux#27943 on the Cloud app server), nothing changes: the answer stays nil and the start is refused as before. Nothing here grants owner privileges without that exact answer.

Risk

risk:high (an owner/auth decision). Ownership stays a server answer, so the client can't widen it: no answer, or any value other than owner, still refuses. The legacy path keeps the same closure.

Tests

CloudAppLinkOwnerAnswerTests (CmuxNextAppTests), committed red first (2671109afe3) before the fix:

  • only an exact owner value marks the connect as owned (absent, "", "Owner", " owner", "teammate", "team", "guest" do not);
  • the answer holds only while its own connect is up: a new connect gets a new id; a down, a revoke and a pause end it;
  • a connect whose answer doesn't say owner gives no answer;
  • an app-link CloudMachineSession answers for its own link.

Observed / Not run

  • Observed: scripts/cmux-next/prepush-gate.py: no new violations (app-size warning only, +8 App lines).
  • Not run yet: the Swift suite on the fleet (this lane VM can't run it). The CI results at the head go in a comment.
  • Not run: an end-to-end start on a Worker machine. That needs piece A's server answer.

Changelog

none (the app-server link is a Debug-only path today)

No capture: no UI change.

šŸ¤– Generated with Claude Code

Agent session: agent session