compute set-domain/check-domain: region-aware selection, guidance, status
What
Users see their service's default URL; they can bind a custom domain (Cloudflare-terminated) and that domain routes to the region the service lives in (single region per service today; anycast later). This PR makes the three domain verbs say so, and never guess which service a hostname binds to. Only src/commands/compute.ts (domain section) + two test files; no command/flag surface change (set-domain <host> / check-domain <host> / remove-domain <host>, --branch --group --json unchanged).
Selection — only where genuinely ambiguous
- No
--group, exactly one compute service in the branch → binds to it. - Several → refuses (non-interactive) and lists each with region and default URL,
--grouprequired:this project has 3 compute services; pass --group to choose which one serves app.customer.com: api us-east https://insta-main-api-1a2b.compute.instacloud.com (running) web us-west https://insta-main-web-3c4d.compute.instacloud.com (running) worker eu-central (no HTTP endpoint — worker, cannot serve a domain) - Workers (
port: 0) are shown but unbindable, also when named with--group.--groupfor an unknown name lists what exists. The refusal happens before any write reaches the platform. - All three verbs resolve the target this way and send
group: <service name>explicitly (the platform'sdefaultfallback is never relied on).
Guidance after set-domain — from the platform's records, never a template
app.customer.com -> api (us-east)
add these DNS records at your DNS provider:
CNAME app.customer.com -> cname.instacloud-dns.com
TXT _insta-verify.app.customer.com -> insta-verify=tok123
then: insta compute check-domain app.customer.com --group api
No records from the platform → says so explicitly and prints no value. Generated follow-up commands carry the resolved --group and the invoked --branch, so a printed command cannot die on the ambiguity error above.
check-domain — every stage, and only claiming what is confirmed
app.customer.com -> api (us-east)
ownership verified (TXT found)
cname ok (points at cname.instacloud-dns.com)
certificate active (edge TLS issued)
resolves to prod-use1-origin.instacloud-dns.com (us-east router) ok
serving https://app.customer.com
Pending/mismatch/unchecked stages say what to do. Ownership and CNAME stages are drawn even when the platform sends no such record (an omitted stage reads as "not required"); for Fly, which issues no ownership TXT, that stage is n/a rather than an unclearable blocker.
serving is claimed only when every stage agrees — the provider says configured and nothing is outstanding and the routing target is confirmed. So a configured: true beside a missing record reports the disagreement, and an error state blocks with or without a reason. resolves to has these shapes, none of which read as serving: region has no edge origin configured (origin: ""), Cloudflare holds another region's origin ("attached elsewhere — remove it there first"), Cloudflare does not hold the hostname yet, and a plane daemon that reports no routing target → UNCONFIRMED. The one case that does not block is a provider with no origin concept at all (Fly: no ssl, no per-hostname origin) — it says so and its own verdict stands.
--json is the platform body verbatim (includes service, region, ssl, dns[].status, origin/edgeOrigin/originOk when present). An answer omitting dns entirely is read as no records.
Bound elsewhere (platform 409)
Worded as release-then-rebind — there is no "move": owner named and in this project → the exact insta compute remove-domain <host> --group <owner> [--branch …]; owner named but not a service here → held by a deleted service, operator must release; owner not named (today's plane) → generic release instruction.
An older platform that answers without service/region still gets them in the human output from the service row the CLI resolved.
Tests (negative-controlled: all fail with src/ stashed, all pass with the change)
test/compute-domain-region.test.ts(31, pure functions): selection incl. worker/sole-worker/unknown-group; guidance verbatim and the no-records message; the follow-up command carrying group + branch; every check-domain stage incl. unchecked and no-record-drawn cases; the routing shapes above; configured-but-blocked; error with and without a reason; Fly-backed row; not-added; the three conflict messages.test/compute-domain-flow.test.ts(10, injectedDomainApiseam): preflight lookup, explicitgroupon POST/GET/DELETE, ambiguity refused before any write,--jsonpassthrough, both 409 shapes, a non-409 passing through unreworded.
Gates
npm run typecheck clean; npm test 42 files / 554 tests passed. (The previously reported setup-agent/ensure-skills failures did not reproduce in this worktree.)
Not in this PR
skills/insta/cli-reference.md (separate insta-skills repo) still carries the Fly-era wording; no flag changed, so no surface entry changes — a wording refresh can follow.
🤖 Generated with Claude Code