# Spec 037 — DNS multi-conta Cloudflare + BYO + Registrador

**Criado:** 2026-06-22  
**Solicitado por:** Roger  
**Status:** 🔄 Em implementação (Fase A activa · B/C/D em construção)  
**Prioridade:** P1  
**VM:** 112 (wizard API `:8090`)  
**Relacionado:** [004 cloudflare-zone-provision](../004-onboard-funnel-events/cloudflare-zone-provision.md) · 025 · 017 · **[dns-viewer.md](./dns-viewer.md)** · [035 §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md)

---

## Resumo

O passo DNS do onboarding usa **três contas Cloudflare gerenciadas pela Ligbox** (ligit, itecnologys, ibytera). O wizard:

1. **Se a zona já existe** numa das 3 contas → aplica apontamentos com o token dessa conta.
2. **Se o cliente escolhe «Cloudflare Ligbox»** e a zona **ainda não existe** → o wizard **cria a zona do cliente** na conta Ligbox correcta (`provision-zone`), depois aplica MX/SPF/etc.
3. **Se o cliente não quer DNS na Ligbox** → BYO (token CF dele) ou registrador externo.

**Política:** não importar domínios alheios nem criar zonas fora do fluxo wizard; **sim** criar a zona **do cliente** na CF gerenciada pela Ligbox quando esse for o caminho escolhido.

**Evolução (Roger 2026-06-22):** cada cliente novo terá **conta Cloudflare dedicada** — e-mail inicial `proj_{id}@ligbox.com.br`, dados do domínio do cliente; após go-live, convidar gestor `admin@dominio`. Ver [client-cf-account-lifecycle.md](./client-cf-account-lifecycle.md).

**Identidade projeto (Roger 2026-06-22):** o agente **cria** `proj_{id}@ligbox.com.br` no Carbonio, **entrega senha** ao cliente e referencia em CF, DNS, registry e webhooks. Ver [project-email-identity.md](./project-email-identity.md).

---

## Três contas Ligbox (pré-cadastro)

| ID interno | E-mail admin (referência) | Ficheiro token (VM112) | `account_id` CF |
|------------|---------------------------|-------------------------|-----------------|
| `ligit` | admin@ligit.com.br | `secrets/cloudflare-ligit.token` | _configurar_ |
| `itecnologys` | admin@itecnologys.com | `secrets/cloudflare-itecnologys.token` | _configurar_ |
| `ibytera` | ibytera@gmail.com | `secrets/cloudflare-ibytera.token` | _configurar_ |

Catálogo: `deploy/vm112-wizard/dns-accounts.yaml` (não commitar tokens).

Conta **legacy** `cloudflare.token` + `cloudflare_account_id` em `config.py` → migrar para `ibytera` ou deprecar após cutover.

---

## Fluxo decisão (passo DNS)

```mermaid
flowchart TD
  A[POST validate-domain] --> B[GET /dns/resolve/{domain}]
  B --> C{Zona já existe em conta Ligbox?}
  C -->|sim| D[apply na conta encontrada]
  C -->|não| E{Cliente escolhe caminho}
  E -->|Cloudflare Ligbox| F[provision-zone: cria zona do cliente na conta default]
  F --> G[apply apontamentos]
  E -->|BYO| H[connect-custom + apply]
  E -->|Registrador| I[instructions + verify manual]
  D --> V[GET dns/verify]
  G --> V
  H --> V
  I --> V
  V --> J[account/create]
```

### Regra «zona do cliente na CF Ligbox»

- **Existente:** probe `ligit → itecnologys → ibytera`; usa a conta onde a zona já está.
- **Nova (wizard):** `POST provision-zone` cria `cliente.com.br` na conta `default_provision_account` (yaml, hoje `ibytera`) — **só** quando o utilizador escolhe Cloudflare Ligbox.
- **Nunca:** criar zona sem escolha explícita Ligbox; nunca BYO/registrador criar em conta Ligbox.

---

## Endpoints novos / alterados

| Método | Path | Descrição |
|--------|------|-----------|
| GET | `/api/onboarding/dns/resolve/{domain}` | Procura zona nas 3 contas; devolve `matched_account`, `zone_id`, `paths_available` |
| POST | `/api/onboarding/dns/cloudflare/connect-custom` | Valida token BYO + zona; guarda em vault sessão |
| POST | `/api/onboarding/dns/cloudflare/apply` | **Alterado:** usa conta resolvida, BYO, ou body `account_id` |
| POST | `/api/onboarding/dns/cloudflare/provision-zone` | **Cria zona do cliente** na CF Ligbox (conta default ou existente) — só caminho Ligbox |

### Response `dns/resolve`

```json
{
  "domain": "cliente.com.br",
  "matched": true,
  "account_id": "ligit",
  "account_label": "Ligbox Ligit (admin@ligit.com.br)",
  "zone_id": "…",
  "zone_status": "active",
  "dns_mode": "ligbox_cf_ligit",
  "paths_available": ["apply_ligbox", "byo", "external"]
}
```

Se `matched: false` (zona nova):

```json
{
  "matched": false,
  "can_provision_ligbox": true,
  "provision_account_id": "ibytera",
  "paths_available": ["provision_ligbox", "byo", "external"],
  "message": "Domínio novo — pode criar zona na Cloudflare gerenciada pela Ligbox ou usar BYO/registrador."
}
```

---

## BYO Cloudflare (cliente)

1. Cliente cola **API Token** (scope: DNS Edit + Zone Read **só na zona**).
2. `verify_token()` + `get_zone_by_name(domain)`.
3. Token em vault (`/var/lib/.../dns_tokens/{session_hash}`), TTL 7 dias pós-onboard.
4. `apply` com `CloudflareDNS(token=customer_token)`.
5. Purge Spec 017 **não** apaga zona BYO — só registos criados por nós (futuro: tracking).

---

## Registrador / DNS externo

Sem mudança funcional core: `GET /dns/instructions/{domain}` + verificação `dns_verify`.

Fase 2: adapters EPP/API (Gandi, GoDaddy, Registro.br homologado).

---

## DNS Viewer (read-only) — Spec 037-DNS-VIEWER

Painel unificado para **exibir** apontamentos (staff Desk + gerente Console) **sem editar** na UI.

| Caminho wizard | O viewer mostra |
|----------------|-----------------|
| **Cloudflare Ligbox** | Registos **planeados** (pré-NS) ou **aplicados** (pós-apply) + NS CF |
| **BYO / Registrador / externo** | DNS **actual** (público + instructions) — **não** preview Ligbox |

Documento completo: **[dns-viewer.md](./dns-viewer.md)** — endpoints, links editar, fases V0–V4.

**Estado deploy (2026-06-25):** V1–V3 produção (VM122/123) · commit `038fb8f` · V4 wizard patches prontos · deploy VM112 pendente.

---

## Código

| Caminho monorepo | Deploy VM112 |
|------------------|--------------|
| `projects/wizard/backend/app/services/dns_account_registry.py` | `/opt/ligbox-wizard/backend/app/services/` |
| `deploy/vm112-wizard/dns-accounts.yaml` | `/opt/ligbox-wizard/dns-accounts.yaml` |
| `deploy/vm112-wizard/dns-accounts.yaml.example` | exemplo versionado |

---

## Critérios de aceitação (Fase 1)

1. Domínio existente na conta `ibytera` → `resolve` retorna `matched` + `apply` OK.
2. Domínio **novo** + caminho Ligbox → `provision-zone` cria zona na conta default + `apply` OK.
3. BYO com token válido → `apply` na conta do cliente.
4. Caminho registrador → sem `provision-zone` Ligbox.
5. Tokens em `secrets/` — nunca no Git.

---

## Decisões Roger (2026-06-22, corrigido)

| # | Regra |
|---|--------|
| 1 | Não importar domínios alheios nas contas Ligbox |
| 2 | **Sim** criar zona **do cliente** na CF gerenciada Ligbox via wizard (escolha explícita) |
| 3 | Conta para zona nova: `default_provision_account` no yaml (hoje `ibytera`) |
| 4 | BYO / registrador = DNS fora das contas Ligbox |

---

## Fase 2 — Conta CF dedicada por cliente

Ver [client-cf-account-lifecycle.md](./client-cf-account-lifecycle.md):

| Fase | E-mail Cloudflare | Quando |
|------|-------------------|--------|
| A — Onboarding | `proj_{id}@ligbox.com.br` | Criação conta + zona |
| B — Handoff | `admin@{dominio_cliente}` convidado como gestor | DNS + email + infra OK |

**Orquestração agentica (edge):** [cf-agents-sdk-architecture.md](./cf-agents-sdk-architecture.md) — Cloudflare Agents SDK (Durable Objects, multi-agent, human-in-the-loop) + roster Spec 029 A0–A7.
