cmux-vm: mesh creations, membership read and receiver policy for Native Cloud

TE
5d ago
+4863-299

The VM API Worker gains the three routes the Native Cloud provider in cmux-platform (cmux-platform#350) and the guest receiver (#28947) call. Until now feat-cmux-next served only membership PUT and DELETE.

  • POST /v1/mesh-creations: create a mesh under a required Idempotency-Key. The create-ledger Durable Object journals the key before the upstream allocation, so a retry or a lost reply returns the same mesh and never allocates twice. A different body under the same key is a 409. Optional labels are stored on the mesh, plus a reserved creation-key label. A service key that asks for labels missing any of its own gets a 403, and a replay re-checks the retained labels.
  • GET /v1/meshes/:meshId/vms/:vmId: read one current membership (meshId, vmId, ipv4, attachedAt). The address comes from upstream and is refreshed in the store under the mesh writer lease. The route never attaches anything.
  • GET /v1/meshes/:meshId/vms/:vmId/receiver-policy: the VM's current inbound client identities and service permissions, projected from the mesh ACL with no transport rules. It carries a digest, and its lease is capped at 30 s or the earliest live owner-key expiry. The ACL and inventory are read twice; if anything changes in between, it answers 503.

Authorization: creation takes mesh:write and team admin, like POST /v1/meshes. The two reads take mesh:read plus vm:read and run under the mesh writer lease. Mesh and VM ownership come from tenantOwnsMesh / tenantOwnsVm, which return nothing for another tenant's resource or one missing a service key's labels, so both cases get a 404. mesh-creation-labels.test.ts, mesh-member-read.test.ts and mesh-vm-receiver.test.ts cover the service-label and cross-tenant cases.

Creation key scope (changed from the carried stack)

The stack hashed the raw Idempotency-Key, and its tests had other team credentials replay a key. Review found that a second service key with matching labels, a team API key, a session, or a selected-resource key in the same team could reuse a key and read the first caller's mesh id, CIDR and timestamp. The key is now hashed with the creating actor: ["mesh-create-v2", actorRef(actor), key], meaning the service id, API key id or session user. Only that actor can replay it. A service keeps its creations across secret rotation because its id stays the same. Regression tests cover service vs service, service vs team key, two API keys, API key vs session, and selected-resource replay; each fails on the earlier head.

VM and snapshot creation keys (scopedCreationKey, unchanged here) still share one namespace across a tenant's team credentials, and only service actors are confined. That looks deliberate: vm-creation.ts and snapshot-capture.ts apply the same service-only check at each recovery point, and the CLI recovers and cancels creations by key. A follow-up is filed for whether a team key that holds only vm:write should see another credential's creation.

Carried from

Ported from the stale-based stack by the same login, workers/cmux-vm paths only, one commit here per source commit (each message names its source):

  • #22413 stack (mesh creation): 41cab5080749, a59709dcb2ce, c3471cd33cc1 (squashed into the first commit), cd6a907a3b29, b7f540c81038, 5d522a9b7515, 9a4a35283cb0, 5475178a9662, 012bdf4b4388, a88e13494487, 0a31a8c7793e
  • #22388 (member read): 971417212160, 93f3f9b72422, 455995c73727, ab42c7edb4db
  • #22451 (receiver policy): d79d6da253ec, 25c9807112da, 621dbfb825d6, 40ab4a5ebcfe, d664ada9d9d1, 46717b94f996

Not carried: the old openapi.json, sdk/src/schema.d.ts, the Rust generated*.rs and the bundle budget edits. The first three are regenerated from this tree. The budget is re-measured: 424.4 KiB gzip, up from 417.0, with no new dependency; it is raised to 435 KiB. The one conflict was test/support/harness.ts. It keeps base's "re-record may commit, never uncommit" branch and adds the stack's mesh fault injection. No migration: mesh-creation state lives in the create-ledger DO's storage.

cmux-platform: workers/cmux-vm has not moved to cmux-platform yet, and the platform's Native provider (cmux-platform#350) needs these routes on the deployed VM API now. This carries already-written routes and moves no code.

Verification

  • bun run typecheck, bun run lint, bun run openapi (regenerated), sdk: bun run generate && bun run typecheck, cli/scripts/regenerate.sh, and bun run bundle && bun run bundle:check all passed locally.
  • Worker and PGlite suites, the openapi check and the breaking check run via cmux-ci; results are in a comment.

Observed / Not run

Observed: the local runs above. Not run locally: the vitest suites (they run via cmux-ci on this head), and the Rust CLI compile against the regenerated client.

Changelog

Added: VM API mesh creations, a membership read and a VM receiver policy read for Native Cloud.

Agent session: agent session

🤖 Generated with Claude Code