cmux-api: PlatformMachines, a service-binding entrypoint for the platform's environment setups (development only)

Merged
TE
9d ago
+456-7

What

A new named Worker entrypoint on cmux-api, PlatformMachines (backend/apps/api/src/platform-machines.ts). The private platform Worker uses it over a Cloudflare service binding to run derived-environment setups on Cloud machines. It has five methods: boot (from the deployment's image or a team snapshot), find, exec, snapshot and destroy. Each one acts for the user and team the caller names, through that team's CloudDO (src/cloud-platform.ts, CloudDO.platformMachine):

  • boot submits cloud.machine.create (or cloud.snapshot.restore) as that person. snapshot submits cloud.snapshot.create, and destroy submits cloud.machine.delete. All three go through the normal submit path, so plan checks, max_active and max_saved, the per-team mutation limit, the provider-call ledger and its repair alarm apply the same way as for a machine the person creates. The idempotency key is derived from the caller's builder or snapshot name, so a retried boot or snapshot reaches the same machine or snapshot. It never creates a second one.
  • exec runs one command as the image's work user (never as root) on a bound, running machine. The audit row records the action, machine, who and exit code, never the command or its stdin.
  • The machine shows up in the team's machine list as "Environment setup". A private platform flag on the row (stripped from the public view) marks it as made by a platform boot.

No public route changes. The default export's fetch is untouched, and the new test checks that no HTTP path reaches the entrypoint.

Auth model (what a binding can do)

  • Who can call. Only a Worker in this Cloudflare account whose wrangler config binds service: "<this worker>", entrypoint: "PlatformMachines". A named entrypoint is not reachable over HTTP, and adding a binding is a deploy to the account. Today the only binder is the platform Worker's development environment (manaflow-ai/cmux-platform, CMUX_MACHINES).
  • Which team. The team the caller names. The caller vouches for the person: the platform verified the person's cmux access token when they derived the environment, and the setup runs later from its Durable Object alarm with no live token. cmux-api still checks on every call that the user may use the team's machines now. That means their personal team, or a current member with team.resources, with a departure heard during the read also refusing (the same rule as automationExec). find and destroy skip that check, so a setup can still find and delete its own builder after the person has left the team; they still reach only machines a platform boot of that same user made.
  • What it reaches. Boot, snapshot and delete are that person's own ops. Their principal is the user in the named team with the identity platform:<user>, so its idempotency keys never meet the person's own. Exec, snapshot and delete reach only machines a platform boot of the same user made. A person's own machines answer cloud.machine.not_found, and another user's platform machine answers auth.forbidden.
  • Why that's safe. A bound caller can do no more than the person could do with their own session: create, snapshot and delete machines within their team's plan. It can also run commands on machines the binding itself made, as the work user. It cannot reach machines the person made themselves, other teams beyond what their membership allows, or anything outside CloudDO.
  • Where it answers. Only where PLATFORM_MACHINES is on. That is the development block of wrangler.jsonc. Staging and production refuse every call with platform.machines_off before any lookup. PLATFORM_MACHINES is now a guarded production var in prod-promote.ts, so turning it on in production needs its own approval.

Why

Leo, 2026-10-28: "whatever's convenient for cloud, like that can be productionized, operationalized, we ought to just put out there"

Derived environments run their setup in cmux-platform (manaflow-ai/cmux-platform #12 to #16). The decision (option b) is that cmux-platform reaches CloudDO through a service binding, so machine boot, exec, snapshot and delete reuse CloudDO's provider contract, plan caps and ledger, and no provider key goes into cmux-platform. The platform side (the Machines layer over this binding, development only) is a separate PR in cmux-platform. It lands after this one.

Risk

  • Low for existing paths. The reducer change only adds the private platform flag when the principal's identity starts with platform:. No other principal has that prefix, and publicMachine strips the flag.
  • The new surface answers in development only.
  • The development CLOUD_ALLOWED_TEAMS gains one team: the personal team of a throwaway development test account used for the derived-environment end-to-end run. It is development-only and gives that team the stub plan, like the other entries.
  • Builders count against the team's max_active while a setup runs, and the snapshots count against max_saved. That is the intent ("plan caps apply exactly as for a person's machine"). The platform does not yet delete a replaced environment snapshot (a follow-up there).

Tests

test/platform-machines.test.ts runs in workerd against the real CloudDO and the fake provider. It covers:

  • one machine per boot name, where a retry makes no second create;
  • find by name;
  • the platform flag kept private;
  • exec refused (retryable) while the machine binds, then run as the work user once it is bound, with the audit row;
  • a person's own machine unreachable;
  • another user refused;
  • snapshot once per name;
  • delete, with a delete of a machine already gone counting as success, a delete still in progress answering retryable, and a delete that failed for good started again under a new key (one per machine revision);
  • a snapshot receipt still answering after its machine is deleted;
  • a member who has left: boot and exec refused, find and destroy still working;
  • a warm boot from the snapshot;
  • a non-member and a team with no plan refused;
  • argument validation;
  • the entrypoint off without PLATFORM_MACHINES;
  • no HTTP path reaching it.

On Linux via cmux-ci test:

  • backend typecheck, lint:size, and test/platform-machines.test.ts, test/cloud-snapshots.test.ts, test/automation-machine.test.ts and test/cloud-do.test.ts at the head (23bbe1a): 4 files, 25/25 passed.
  • The wider cloud suites (21 tests) and prod-promote.test.ts (31 tests) passed earlier in the branch.

Agent session: agent session

šŸ¤– Generated with Claude Code