Billing Worker:
- trials + trial_claims (migration 0005): 2 sandbox hours per new team, no card, no Stripe.
- POST /v1/billing/trial grants it (idempotent, any member); grantTrial/trialOffer exported for
the setup session. offers.trial on GET entitlements only when this caller could still claim it.
- Abuse limits: once per team, Orca user, normalized verified email, GitHub user (token claim)
and GitHub owner (POST /v1/internal/trial/github from the binding; a reused identity revokes),
disposable-domain blocklist, 3 grants per IP per day. Keys stored as SHA-256 hashes.
- Entitlement trial {includedSeconds, usedSeconds, remainingSeconds, exhausted}; metered from
usage_events, concurrency 1, never overage; trial_used_up (suspendRunning) / trial_unavailable.
- Any subscription ends the trial; unused trial hours lapse.
- e2e: trial path (grant, no Stripe customer, concurrency, metering to used up, once per user,
plan ends it) with cleanup of the run's abuse keys.
Desktop: trial_used_up / trial_unavailable classify as 'plan'; copy in billing, phone and CLI;
"Trial" badge, TrialPlanSummary in Plan & billing and the plans page strip, account menu row
"Trial · {{count}} sandbox hours left" (Choose plan for owners/admins); billingClaimTrial IPC
and claimOrcaCloudTrial() for the composer.
- Overage stays $1.00 per sandbox hour (ORCA_CLOUD_OVERAGE_CENTS_PER_HOUR) and is now billed by
the minute: a per-minute metered TEST price (unit_amount_decimal = rate / 60 to 12 decimals,
transform_quantity divide_by 60, round up), created the same way by stripe-test-setup.ts.
capSeconds rounds the cap down to whole minutes and spentCents mirrors the invoice, so the
cap math matches Stripe. The meter still counts seconds.
- Opting in re-prices an overage item still on an older price (e.g. $0.75 / h) instead of adding
a second item on the same meter, and removes any extra stale one.
- Alerts at 75%, 90% and 100% of included hours (warnAtPercent).
- New top-level entitlement.suspendRunning: payment grace expired, or at the limit (out of hours
with overage off, or at the spend cap). The start check and usage answers carry it, so the
control plane puts running sandboxes to sleep (snapshot kept, never deleted). Payment fields and
/v1/internal/suspensions stay payment-only, so no data-retention clock starts at a cap.
- Copy (desktop billing panel, plan card, build sheet, phone, CLI): "billed by the minute" after
the rate; at the limit "Running sandboxes go to sleep and new ones can't start. Nothing is
deleted, and your files are safe."
- workspace destroy -> delete (destroy kept as a hidden alias)
- glossary states (Starting/Running/Asleep/Waking/Couldn't start/Archived/Deleting); list columns
ID NAME STATE REPO BRANCH LAST ACTIVE; raw statuses and codes only under --json
- error copy: resume_failed wake, 409 busy, out of sandbox hours vs spend cap, team owner/admin,
Try again in N seconds; no VM/organization wording; whoami prints team:
- --size dropped from workspace create and worktree create --on cloud; show prints
Sandbox size from ORCA_CLOUD_SANDBOX_SPEC
- orca cloud routine list | run <id-or-name> [--wait]
- control plane: public workspace view adds repoBranch and lastActivityAt
W10 wake-visibility P1s. stderr '- …' lines (NDJSON phase events with
--progress json; --json defaults to none; stdout keeps only the result):
- wake: 'Waking <ws>...', one 'Still waking (Ns)…' line at 10 s,
'<name> is awake (took Ns).', or, when the API answers still waking
after its wait, '<name> is still waking. Check with: …' (exit 0, status
waking kept in the result)
- sleep: 'Putting <ws> to sleep...' / '<name> is asleep (took Ns).'
- worktree create --on cloud: 'Still starting (Ns)…' at 10 s and
'Workspace <id> is running (took Ns).'
Lifecycle handler moved to cloud-lifecycle.ts; shared helper cloud-progress.ts.
Turning in-workspace CLI access off revokes every workspace token, so the
in-VM CLI sees 401, not 403 vm_cli_disabled. Say so and point at the
setting first (verified on a real VM).
Workspace tokens tell the in-VM orca cloud CLI to call publicOrigin(env), but
that host was only reachable when EGRESS_EXTRA_HOSTS listed it (previews did,
the dev Worker did not), so every in-VM call failed with 'fetch failed'.
extraEgressHostsFor(env) now always adds the public API host. The CLI's
cloud_unreachable hint inside a VM names the egress allowlist.
Private build. Talks HTTPS to the Orca Cloud public API, never to the Orca
runtime, so it works inside a VM with no desktop app. Credentials:
ORCA_CLOUD_TOKEN (+ ORCA_CLOUD_API_URL) -> ~/.config/orca-cloud/credentials.json
(written at boot in VMs when vmCliEnabled, or by orca cloud login, 0600).
--json prints { ok, schemaVersion: 1, result }; API failures map to stable
cloud_* codes with next steps (vm_cli_disabled, own_workspace, scopes, 401).