compute set-domain/check-domain: region-aware selection, guidance, status

Merged
TO
3mo ago
+803-33

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, --group required:
    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. --group for 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's default fallback 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, injected DomainApi seam): preflight lookup, explicit group on POST/GET/DELETE, ambiguity refused before any write, --json passthrough, 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