cmux NEXT Cloud: caller-ownership answer on connect_info, bound to the link token (cmux-platform#89 A)
Leo, 2026-10-30, on building the app-server Cloud path now instead of after the demo: "well why can't we just.... do our best now?"
Part of manaflow-ai/cmux-platform#89 (section 1, the caller-ownership answer). This is the Worker half. #27922 wires the app, and a follow-up passes the answer through the Cloud app server's cloud.machine.connect. cmux#27778 stays on HOLD until those land.
What changes
The cmux-next API Worker (backend/apps/api, CloudDO) now says whether the signed-in caller owns a Cloud machine, with the classic attach answer's rule (web/services/vms/vmKind.ts vmKindForCaller):
cloud.machine.connect_infoandcloud.machine.link_tokenresults carryvm_kind: "owner" | "team", computed per request and never stored.owneronly when the caller created the machine (exact user id, never an agent token) and the machine is on the creator's personal team. Every shared-team machine isteam, its creator included. A teammate isteam. Another team is refused withauth.forbiddenbefore any answer (unchanged).- The link token signs the same value as claim
vmk. The owner answer therefore belongs to one dial:verifyLinkTokengives no claims for an expired or replayed token, and refuses avmkthat is neitherownernorteamasmalformed. Tokens minted before this have novmkand grant no owner answer. callerVmKind(entity, row, principal)incloud-connect.tsis the one check. The connect/link audit rows recordvm_kind.- Regenerated:
backend/catalog/cloud-operations.json,cloud-vectors.json(connect_info and link_token cases carryvm_kind),schemas/link-token/vectors.json(new casesowner_claimandbad_vmk), the TS client, the Rustcmux-cloud-wiretypes and the app-host typings.
Why it's here and not in cmux-platform: the brief said cmux-platform, but connect_info, link_token and CloudDO live only in cmux, and cmux-platform has no port of them. docs/port-cloud-worker.md rule 1 says "A fix goes to cmux first and comes along with the port."
Compatibility: the deployed Rust decoders (cmux-link ConnectInfo / LinkTokenGrant and the cloud app server's api::models::ConnectInfo) ignore unknown fields. The generated cmux-cloud-wire types are used only inside that crate. Only the TS reference verifier checks link tokens today.
Piece B (lane/cloud-np-b-owner-exec) keeps CloudDO's mayUse for exec/fs, which is classic's /api/vm/{id}/fs rule (any member of the billing team). The owner answer is a strict subset of it.
Tests
backend/apps/api/test/cloud-owner-answer.test.ts: the owner (connect_info, mint, signed claim, audit), an agent token acting as the creator (team), a shared-team machine for its creator and a teammate (team, also in the teammate's token), another team (refused, also when it claims this team's id), an expired token and a replayed token.
- Red on base (fix files reverted, test kept):
bunx vitest run test/cloud-owner-answer.test.tsā 5 failed, 1 passed (the other-team refusal already held). - Green on head: 6 passed.
- On head, 15 files covering connect, link token, bind, keyset vectors, routes and idle: 55 passed.
bun run catalog:check,tsc -p .(api, protocol),lint:size,gen-cmux-global.ts --checkandscripts/cmux-next/prepush-gate.pyall clean.
Observed / Not run
- Observed: the runs above, on a Linux lane host, before and after merging base.
- Also observed:
cargo test -p cmux-cloud-wire(cloud_vectors decodes the regenerated vectors): ok. - Not run: the full api suite (CI runs it). No staging run yet; that is the issue's "done when", after the app wiring lands.
Changelog
none
risk: high (auth answer). Lands on an independent review receipt at the exact head plus a green compile.
š¤ Generated with Claude Code
Agent session: agent session