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.
* fix(w8): no-desktop Home says Orca Cloud workspaces show up once a desktop is paired (W9 finding; design-squad copy)
* test(w8): empty-state test without an any-typed renderer union
- When the user's default or a local pick isn't on the Orca Cloud image, one muted line under the
picker says so: '{{agent}} isn't available on Orca Cloud yet, so {{fallback}} is selected.' It
goes away once the user picks an image agent.
- Fixtures (dark/light): closed with Orca Cloud, open with the six agents, open with two disabled,
and the fallback line.
- Mobile chips are labelled with the catalog name plus ', selected' on the chosen one.
* fix(w8): cloud-icon rule on the phone: a paired desktop's Orca Cloud workspaces lead with the Cloud glyph + state dot (worktree.ps carries orcaCloud.status), home cloud rows read 'Cloud workspace, …', muted Cloud before the cloud workspace title
* fix(w8): cloud-icon review: glyph label merges into the row (no own focus stop), no filled ring behind the dot (pressed/active fills), newest VM record speaks for a workspace
* fix(w8): cloud-icon review 2: a cleaned-only workspace stays cloud (as on the desktop) with no dot, a live VM beats a cleaned one, cleanup_failed draws no forever-pulsing dot; store-order test
* test(w8): cloud-icon ps status test without a cast
* fix(w8): cloud-icon dot sits mostly outside the glyph's bottom-right (design-squad), clear of the title
- 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."
* fix(w8): DS-41 phone polish: grey backdrop behind white groups in light, light amber at 3:1+, access rows read '{device}, last used {date}' with a 'Removes this phone's access' hint
* fix(w8): DS-41 review: white text on light amber badges (onStatusAmber, 5:1), grey backdrop only behind grouped lists (card screens keep white), 'connected {date}' label when a phone was never used
* feat(w8): DS-28 new cloud workspace: pick the repository from this phone's recents and the team's projects ('Other repository…' for any other), and say 'Sleeps after N minutes idle' (new screen, empty Home)
* copy(w8): DS-28 with the picker, the note says 'Pick none for a blank project'
* fix(w8): DS-28 review: explicit 'No repository (blank project)' row, the field stays while typing an unlisted repo, choosing 'Other repository…' again keeps the text
* fix(w8): DS-28 nit: typing in the field selects 'Other repository…'
Desktop composer with Orca Cloud selected: the picker is enabled and lists the
image agents (minus disabled ones) as terminal rows, defaulting to the user's
default when the image ships it, else claude; a local-only pick is repaired.
Phone cloud picker and composer read the same shared list and default.
* feat(w8): reassure after 10 s of waking (files are safe; a queued prompt sends once awake), under the steps with the timer still running
* fix(w8): wake-slow note: iOS-only announce, prompt sentence only while waking, whole-run reassurance from 20 s, fresh per run
* feat(w8): teammates wake a team workspace from the phone (team route long-poll wake, same access/billing/cap; owner route stays the fallback)
* test(w8): team wake refuses an admin on a private row, never returns the endpoint; phone never falls back on 401/403
* fix(w8): phone forgets cloud workspaces that are gone (missing from the complete registry list, or wake says not_found); never on transient errors
* fix(w8): prune only on the registry's own not_found for that workspace (lists are capped and skip hidden rows; status 404s are owner-only); restore a link whose host removal failed
* perf(w8): host prune is single-flight and remembers a confirmed-present workspace for 5 minutes
* fix(w8): removing a member revokes their phones; role changes move phones too; a phone's stored role never re-syncs keys
* fix(w8): one removal path revokes keys, phones, grants and routines; stale desktop tokens can't re-sync roles or vouch phones; a grant revoked mid-issue stays revoked
* fix(w8): a removed member's unexpired desktop tokens stop working; changes count from auth's time; desktop re-mints and retries enroll on stale_token
* feat(w8): role-aware plan-denial copy on the phone (owners and admins get the fix, members who to ask)
* fix(w8): W10 copy (per-seat team limit, choose a plan) and FE review (3 s abortable role read, Android live region, more copy cases); no casts
* feat(cloud): Orca Cloud workspaces on the phone (W8)
Home gets a Cloud section that lists the team's cloud workspaces straight from
the control plane (phone-signed device-session tokens; no desktop in the path).
A workspace screen wakes the sandbox (W2 long-poll wake), gets this phone its
own revocable orcad device through a W4 sealed grant (opened with the shared
opener on pure-JS primitives for Hermes), pairs directly over E2EE to the stable
sandbox endpoint, and delivers prompts queued on the phone while it was asleep
(agent.launchReplay, deduped by operation id). Create a workspace from the
phone (repo, branch, agent, first prompt, sandbox size), sleep, archive, live
status and spend, per-phone access management with revoke and disconnect, and
team share links (orca://cloud/w/<id>) open on the phone. Copy follows the
Orca Cloud glossary; dark/light, Reduce Motion, VoiceOver announcements.
* fix(w8): design round 2 (honest spend, can't-connect state, queue states, distinct titles, neutral archive, hairline groups)
* fix(w8): FE + W10 round 2 (retry ids, pairing give-up, create idempotency, neutral dots, waking composer, a11y values, salvage storage)
* fix(w8): tell same-named cloud workspaces apart by when they were made
* fix(w8): agent names from the shared table; localized repo placeholder
* fix(w8): round 3 (agent-started card with terminal sign-in path, transport-safe retries, open waits for the connection, single wake timer, created-ago rows)
* fix(w8): FE round 4 (open never blocked on worktree lookup, one connect deadline per run, mid-delivery prompts never stranded, no blank terminal on open session)
* fix(w8): prompts interrupted mid-delivery count as waiting (Open and retry resume them)