Skip to content

chore(domain): stop guiding users to buy or delegate new domains - #378

Merged
CarmenDou merged 1 commit into
mainfrom
chore/pause-domain-buy-guidance
Oct 19, 2026
Merged

CarmenDou merged 1 commit into
mainfrom
chore/pause-domain-buy-guidance

Conversation

@CarmenDou

@CarmenDou CarmenDou commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

The InstaCloud platform has paused new domain orders (POST /orgs/:orgId/domains/orders answers 409 domain_purchase_paused) and new delegation of domains registered elsewhere (POST /orgs/:orgId/zones answers 409 byo_delegation_paused). The CLI still advertised both and printed hints that led straight into those 409s.

This PR stops the CLI from guiding users or agents to buy a domain or to delegate a domain registered elsewhere. Everything that still works is unchanged: domain attach / check / detach, every verb for domains already bought through InstaCloud (list, status, renew, delegate, nameservers, records, transfer), and existing BYO zones (zone list / records / release).

How

  • src/index.ts: domain search, domain buy and domain zone delegate are registered with { hidden: true } (the same mechanism as env and setup), so they drop out of help but still run. Re-enabling is removing the flag.
  • src/index.ts: the domain group description no longer offers buying. The zone group and zone records descriptions now describe existing zones and no longer tell anyone to run or re-run zone delegate.
  • src/commands/domain.ts: removed the printed hints buy one: insta domain buy … (after search), order again: insta domain buy … (canceled order in domain status), (search: insta domain search <keyword>) (empty domain list), start one: insta domain zone delegate <domain> (empty zone list), and the "re-run zone delegate to re-import" advice in zone records and in the zone delegate answer.
  • README.md: the insta domain row no longer lists search or buy.
  • No new commands, so no MCP tool is owed. Not in this PR: skills/insta/cli-reference.md and the MCP insta_search_domains / insta_buy_domain tools still describe buying and BYO delegation.

Verify

Run with Node 20.20.2:

  • npm run typecheck: passes
  • npm test: 106 files, 2245 tests pass
  • npm run build: passes
  • New test in test/help-surface.test.ts: insta domain --help lists neither search nor buy, insta domain zone --help lists exactly list, records, release, neither root nor domain help mentions buying or zone delegate, and domain search --help, domain buy --help, domain zone delegate --help each still resolve to their own usage line.
  • test/domain.test.ts and test/zone.test.ts now assert the removed hints are absent.

🤖 Generated with Claude Code

The platform paused new domain orders (domain_purchase_paused) and new
BYO zone delegation (byo_delegation_paused) over the Cloudflare zone
quota. Hide `domain search`, `domain buy` and `domain zone delegate`
from help, keeping them registered so re-enabling is unhiding, and drop
every printed hint that sends the reader to buy, search or re-run
delegate. Attach/check/detach, bought-domain management and existing
BYO zones are unchanged.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/domain.test.ts">

<violation number="1" location="test/domain.test.ts:233">
P3: Removing this test drops the only coverage for `domainStatus` with `--org`, even though that path is unchanged. Keep a test that checks a canceled status with `--org` queries the named org and prints the canceled line.</violation>
</file>

<file name="test/zone.test.ts">

<violation number="1" location="test/zone.test.ts:112">
P3: The removed `zoneRecords('Byo.Example', { org: 'org9' })` case was the only test of `--org` routing and domain normalization for `zone records`. Keep a call-path assertion for it, such as checking the GET path is `/orgs/org9/zones/byo.example/records`, even though the reminder assertion is gone.</violation>
</file>

<file name="src/index.ts">

<violation number="1" location="src/index.ts:326">
P2: The agent-facing CLI reference still directs agents to this paused command, so hiding it from CLI help does not stop agents following the documented workflow. `AGENTS.md` requires syncing `skills/insta/cli-reference.md`; update that reference with this command change.</violation>
</file>

<file name="src/commands/domain.ts">

<violation number="1" location="src/commands/domain.ts:402">
P2: This removes the only recovery instruction for records the scan misses. `zoneRecords` only reads and prints records, so users cannot correct the managed zone there; retain the add-at-provider and re-run-delegate step before telling them to switch nameservers.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/index.ts
const zone = dom.command('zone').description("Bring-your-own domains served from an InstaCloud-managed zone (nameserver delegation): point the domain's registrar at the pair `zone delegate` answers, and every attach's records — apexes included — are published for you. The alternative stays available: `insta domain attach` alone prints records to paste into your own zone")
zone.command('delegate <domain>').description("Delegate a domain you own elsewhere: builds its managed zone (seeded by a provider record scan — a HEURISTIC, so review `zone records` and add anything missing at your current provider BEFORE switching nameservers; re-running delegate re-imports) and answers the two nameservers to set at your registrar. A domain carrying live MX records is refused — move mail first or keep the records path (org admin; gated: zone.delegate — agent mode gates from a linked project, an unlinked --org call falls under org administration instead)")
const zone = dom.command('zone').description("Bring-your-own domains already delegated to an InstaCloud-managed zone: list them, review their records, or release them. For a hostname under a domain you own elsewhere, `insta domain attach` prints the records to add in your own DNS")
zone.command('delegate <domain>', { hidden: true }).description("Delegate a domain you own elsewhere: builds its managed zone (seeded by a provider record scan — a HEURISTIC, so review `zone records` against your current DNS BEFORE switching nameservers) and answers the two nameservers to set at your registrar. A domain carrying live MX records is refused — move mail first or keep the records path (org admin; gated: zone.delegate — agent mode gates from a linked project, an unlinked --org call falls under org administration instead)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The agent-facing CLI reference still directs agents to this paused command, so hiding it from CLI help does not stop agents following the documented workflow. AGENTS.md requires syncing skills/insta/cli-reference.md; update that reference with this command change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/index.ts, line 326:

<comment>The agent-facing CLI reference still directs agents to this paused command, so hiding it from CLI help does not stop agents following the documented workflow. `AGENTS.md` requires syncing `skills/insta/cli-reference.md`; update that reference with this command change.</comment>

<file context>
@@ -321,14 +322,14 @@ ns.command('reset <domain>').description("Put the zone back on the registrar's o
-const zone = dom.command('zone').description("Bring-your-own domains served from an InstaCloud-managed zone (nameserver delegation): point the domain's registrar at the pair `zone delegate` answers, and every attach's records — apexes included — are published for you. The alternative stays available: `insta domain attach` alone prints records to paste into your own zone")
-zone.command('delegate <domain>').description("Delegate a domain you own elsewhere: builds its managed zone (seeded by a provider record scan — a HEURISTIC, so review `zone records` and add anything missing at your current provider BEFORE switching nameservers; re-running delegate re-imports) and answers the two nameservers to set at your registrar. A domain carrying live MX records is refused — move mail first or keep the records path (org admin; gated: zone.delegate — agent mode gates from a linked project, an unlinked --org call falls under org administration instead)")
+const zone = dom.command('zone').description("Bring-your-own domains already delegated to an InstaCloud-managed zone: list them, review their records, or release them. For a hostname under a domain you own elsewhere, `insta domain attach` prints the records to add in your own DNS")
+zone.command('delegate <domain>', { hidden: true }).description("Delegate a domain you own elsewhere: builds its managed zone (seeded by a provider record scan — a HEURISTIC, so review `zone records` against your current DNS BEFORE switching nameservers) and answers the two nameservers to set at your registrar. A domain carrying live MX records is refused — move mail first or keep the records path (org admin; gated: zone.delegate — agent mode gates from a linked project, an unlinked --org call falls under org administration instead)")
   .option('--org <id>', "target org (default: linked project's org)").option('--json')
   .action(guard((domain, o) => domainCmd.zoneDelegate(domain, o)))
</file context>

Comment thread src/commands/domain.ts
const z = res.body as OrgZone
for (const line of zoneLines(z, opts.org)) info(line)
info(`the zone was seeded by a provider scan — a heuristic. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, add anything missing at your CURRENT provider (re-running delegate re-imports), and only then switch the nameservers.`)
info(`the zone was seeded by a provider scan, which can miss records. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, and only then switch the nameservers.`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This removes the only recovery instruction for records the scan misses. zoneRecords only reads and prints records, so users cannot correct the managed zone there; retain the add-at-provider and re-run-delegate step before telling them to switch nameservers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/commands/domain.ts, line 402:

<comment>This removes the only recovery instruction for records the scan misses. `zoneRecords` only reads and prints records, so users cannot correct the managed zone there; retain the add-at-provider and re-run-delegate step before telling them to switch nameservers.</comment>

<file context>
@@ -402,7 +399,7 @@ export async function zoneDelegate(domainName: string, opts: RecordsOpts, deps?:
   const z = res.body as OrgZone
   for (const line of zoneLines(z, opts.org)) info(line)
-  info(`the zone was seeded by a provider scan — a heuristic. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, add anything missing at your CURRENT provider (re-running delegate re-imports), and only then switch the nameservers.`)
+  info(`the zone was seeded by a provider scan, which can miss records. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, and only then switch the nameservers.`)
 }
 
</file context>
Suggested change
info(`the zone was seeded by a provider scan, which can miss records. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, and only then switch the nameservers.`)
info(`the zone was seeded by a provider scan, which can miss records. Check \`insta domain zone records ${z.domainName}${orgArgOf(opts)}\` against your current DNS, add anything missing at your current provider and re-run \`insta domain zone delegate ${z.domainName}${orgArgOf(opts)}\` to re-import, and only then switch the nameservers.`)

Comment thread test/domain.test.ts
})
it('a canceled checkout says how to order again', async () => {
// New orders are paused (platform #689), so a canceled checkout names no way to order again.
it('a canceled checkout says why and does not send the reader to buy again', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Removing this test drops the only coverage for domainStatus with --org, even though that path is unchanged. Keep a test that checks a canceled status with --org queries the named org and prints the canceled line.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/domain.test.ts, line 233:

<comment>Removing this test drops the only coverage for `domainStatus` with `--org`, even though that path is unchanged. Keep a test that checks a canceled status with `--org` queries the named org and prints the canceled line.</comment>

<file context>
@@ -228,21 +229,20 @@ describe('domain list / status', () => {
   })
-  it('a canceled checkout says how to order again', async () => {
+  // New orders are paused (platform #689), so a canceled checkout names no way to order again.
+  it('a canceled checkout says why and does not send the reader to buy again', async () => {
     const { deps: d } = deps({ '/domains/orders': { items: [{ ...order, status: 'canceled', failedReason: 'checkout expired before payment' }] }, '/orgs/org1/domains': { items: [] } })
     await domainStatus('myapp.com', {}, d)
</file context>

Comment thread test/zone.test.ts
await zoneRecords('Byo.Example', { org: 'org9' }, d)
expect(out()).toContain('re-run `insta domain zone delegate byo.example --org org9` to re-import')
expect(out()).toContain('compare them against your current DNS before switching nameservers')
// BYO delegation is paused (platform #687): the reminder no longer sends anyone to re-run it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The removed zoneRecords('Byo.Example', { org: 'org9' }) case was the only test of --org routing and domain normalization for zone records. Keep a call-path assertion for it, such as checking the GET path is /orgs/org9/zones/byo.example/records, even though the reminder assertion is gone.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/zone.test.ts, line 112:

<comment>The removed `zoneRecords('Byo.Example', { org: 'org9' })` case was the only test of `--org` routing and domain normalization for `zone records`. Keep a call-path assertion for it, such as checking the GET path is `/orgs/org9/zones/byo.example/records`, even though the reminder assertion is gone.</comment>

<file context>
@@ -105,11 +108,9 @@ describe('domain zone records', () => {
-    await zoneRecords('Byo.Example', { org: 'org9' }, d)
-    expect(out()).toContain('re-run `insta domain zone delegate byo.example --org org9` to re-import')
+    expect(out()).toContain('compare them against your current DNS before switching nameservers')
+    // BYO delegation is paused (platform #687): the reminder no longer sends anyone to re-run it.
+    expect(out()).not.toContain('zone delegate')
   })
</file context>

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Copy-and-visibility-only change that stops the CLI pointing users at the paused buy / BYO-delegate paths; the hiding mechanism, the removed hints and the new help-surface test all hold up under mutation, and I found no blocking issue.

Requirements context

No /docs/superpowers/ and in fact no docs/ directory at all in this repo at a62874b — no matching spec/plan found, so I assessed against the PR body plus the repo's own written intent: AGENTS.md (§4 mirror command/flag changes in skills/insta/cli-reference.md; §6 MCP-tool rule) and .claude/skills/developing-insta-cli/SKILL.md. For the platform premise I read the companion PRs directly: InsForge/instacloud-platform #687 (BYO pause), #689 (purchase pause), #691 (BYO pause follow-up), instacloud-skills #180 and instacloud-mcp #100.

Verification I ran at this head (node v20.20.2, clean npm ci): tsc --noEmit exit 0; npx vitest run = 106 files, 2244 passed, 1 skipped (2245) — matches the body. CI at a62874b: test, test-windows and cubic all success.

Nine targeted negative controls, all RED (good coverage): un-hiding each of domain search / domain buy / domain zone delegate separately, and restoring each of the six removed hints (search → "buy one", canceled order → "order again", empty domain list → "(search: …)", empty zone list → "start one: …", zone records → "re-run … to re-import", zone delegate answer → "re-running delegate re-imports"). The new help-surface test really does pin all three hides, including zone being exactly list / records / release.

Findings

Critical

(none)

Suggestion

1. Functionality / release ordering — half the stated premise is not live yet (src/index.ts:288-295).
The body opens with "the platform has paused new domain orders (POST /orgs/:orgId/domains/orders answers 409 domain_purchase_paused)". The BYO half is true — platform #687 is merged, src/config.ts parses INSTA_BYO_ZONE_DELEGATION_ENABLED with an off-by-default regex and .env.example ships false, so byo_delegation_paused is real on main. The purchase half is not: domain_purchase_paused does not exist anywhere on platform main (code search: 0 hits; byo_delegation_paused: 5 hits), because #689 is still open. So if this merges and ships first, domain search / domain buy are undiscoverable while the platform still happily sells. The direction is conservative, so this is not blocking — but either land/ship this after #689, or soften the body's premise to "will answer" for the purchase half.

2. Functionality / UX — the review-then-switch contract lost its only remedy (src/commands/domain.ts:376-384, :430).
The old zone records line and the zone delegate answer told the reader what to do about a record the provider scan missed: add it at your CURRENT provider and re-run zone delegate to re-import. The new copy keeps the detection step ("compare them against your current DNS before switching nameservers") but names no remedy, and the CLI has none: the zone group is only list / records / release, nothing writes a record into a managed zone, and domain records 409s while the zone is managed (src/index.ts:314). Worth noting the dropped advice still works today — platform #691, which makes the pause cover re-runs, is open, so #687 only refuses new delegations. Suggest either keeping the re-run sentence until #691 lands, or naming the remaining escape hatch (insta domain zone release <domain> → back to the records path via domain attach) so a user who spots a gap in the review is not left at a dead end. (Same ground as cubic's P2 on src/commands/domain.ts:402; I agree with the concern, not with restoring the paused command unconditionally.)

3. Software engineering / test coverage — two deleted cases were the sole pin on behaviour this PR did not change. Both confirmed by running the same mutation at base (25a1fa3) and at head:

  • test/zone.test.ts:108-113 dropped the zoneRecords('Byo.Example', { org: 'org9' }) case. Mutating zonePath (src/commands/domain.ts:356-357) from domainName.trim().toLowerCase() to domainName.trim() is RED at base, GREEN at head (full suite 2244 pass) — the domain-name normalisation is now unpinned.
  • test/domain.test.ts:233-240 dropped the --org case. Making domainStatus ignore opts.org (orgDeps(opts, deps) → orgDeps({}, deps)) is RED at base, GREEN at head — --org routing for domain status is now unpinned.
    Cheap fix: keep both cases, asserting only the request path (/orgs/org9/zones/byo.example/records, /orgs/org9/domains) now that the hint assertions are gone. (Confirms cubic's two P3s.)

Information

  • The agent-facing surface is covered by companions the body undersells. "Not in this PR: skills/insta/cli-reference.md and the MCP insta_search_domains / insta_buy_domain tools" reads as "not done", but both exist and are open: InsForge/instacloud-skills #180 ("docs(insta): stop teaching domain buy and BYO zone delegation", 48+/66-, 4 files) and InsForge/instacloud-mcp #100 (registers both tools only behind INSTA_DOMAIN_PURCHASE_ENABLED). Linking them in the body satisfies AGENTS.md §4's "same change set" in the way this repo normally does (submodule PR) and answers cubic's P2 on src/index.ts:326 outright. AGENTS.md §6 is not owed anything — no new leaf command.
  • Hidden ≠ refused, by design. insta domain buy still runs and the user sees the platform's 409 message verbatim (src/api.ts:104 → ApiError(res.status, res.body?.error …)); nothing in the CLI special-cases domain_purchase_paused / byo_delegation_paused. That is the right call for a thin client, and it is what makes re-enabling a one-flag change — just flagging that an agent that already knows the verb gets the platform's wording, not the CLI's.
  • Mechanism check: { hidden: true } is the same mechanism env and setup already use, and the new test proves each hidden path still resolves to its own Usage: line, so this is genuinely reversible by deleting three flags.
  • Security: no security-relevant changes in this PR. No new user input, no new sink, no auth/authorization change, no dependency change (package.json / lockfile untouched); the only behavioural edits are printed strings and commander visibility flags.
  • Performance: nothing to report. No new request, loop, or allocation; orderStatusLines actually loses a parameter.

Verdict

approved — 0 Critical, 3 Suggestion, 5 Information. Informational only: the GitHub green check is a separate human action via the approve flow.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@CarmenDou
CarmenDou merged commit 52aa00c into main Oct 19, 2026
3 checks passed
@CarmenDou CarmenDou mentioned this pull request Oct 19, 2026
CarmenDou added a commit that referenced this pull request Oct 19, 2026
## What

Release 0.1.26. It ships #378: `domain search`, `domain buy` and `domain zone delegate` drop out of help (they still run), and the hints that sent users to buy, search or re-run delegate are gone, matching the platform pauses that are now live.

## How

`npm version 0.1.26 --no-git-tag-version`: `package.json` and `package-lock.json` only. After merge, tag `v0.1.26` on main to run `release.yml` (binaries, GitHub Release, npm).

## Verify

- `npm run typecheck`, `npm test` and `npm run build` on Node 20.
- After the tag: the GitHub Release has the binaries and `npm view insta version` answers 0.1.26.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants