GET /v1/internal/accounts/:accountId/subscription adds cancelling / endsAt / periodEnd (additive);
new POST .../subscription/cancel-now (membership-sync token) cancels the team's subscription
immediately (invoice_now: reported overage is billed; prorate false: no refund) but only one
already set to end (cancel_at_period_end or a portal cancel_at); a renewing plan gets 409
not_cancelling. Idempotent; a Stripe failure is a 502 and changes nothing, so auth must not
delete the team. Mirror re-read from Stripe, grants revoked.
The desktop minted billing tokens with an empty body, so auth picked the team from the cloud
profile's active team, which every device of the same user shares: one device switching teams made
the other read (and act on) that team's plan, hours and usage. mintBillingToken now sends {orgId}
for this device's team (auth #761), refuses a token whose org claim is any other team (fail
closed: billing_org_mismatch), and falls back to {} on an older auth (400) with the claim still
checked. Mirrors the sandbox mint (#634).
Billing test: two teams of one owner on one Stripe customer each read only their own plan/hours.
Only the first build and the failed-build retry used autoConfig(); a golden refresh reused the active
build's stored (pre-#660) config, and a manual Rebuild built a default config, so neither got
ORCA_AUTO_INSTALL (and a Rebuild lost the blobless clone). startBuild now derives both from the
environment's kind: every build of an automatic environment, whatever started it, clones blobless and
installs from the lockfile. Tests for create, retry, golden refresh and manual Rebuild.
Stripe's portal on API 2025-09-30.clover cancels by setting cancel_at, not cancel_at_period_end,
so billing never saw the cancellation: no 'Plan ends', no grant end. The mirror now stores
cancel_at (migration 0009); cancel_at_period_end is the 'cancelling' flag for either form; grants
end at cancel_at; entitlements carry cancelAt; the desktop's 'Plan ends' uses it.
Test: two teams of one owner (separate customers), one cancels via cancel_at: its own plan shows
ending on that date, the other is untouched; un-cancelling clears it.
* fix(cloud): validate ORCA_PLATFORM_HOST_SUFFIXES up front; smoke boots use the configured hosts
A malformed suffix now refuses every request (503 platform_hosts_misconfigured,
naming the bad entries) and skips scheduled work, instead of failing each
limited-mode create later. Golden-image and environment-build smoke boots
use the workspace base allowlist, so they follow the configured platform
hosts rather than the built-in *.onorca.dev.
* fix(cloud): narrow the platform-host guard to what compiles an allowlist
A malformed ORCA_PLATFORM_HOST_SUFFIXES no longer takes the Worker down.
GET /v1/health (no auth, for the post-deploy smoke) returns 503
platform_hosts_misconfigured; limited-mode creates, wakes and resumes,
new environment builds and the golden rotation refuse or skip. Reads,
sleep, stop, delete and the reconcile cron keep working; open-mode
creates compile no allowlist and are unaffected.
* fix(cloud): platform_hosts_misconfigured responses carry the code only
/v1/health is public, so the bad entries stay in the config_error log
line; the authenticated refusals return the same bare code.