# Spec 037-DNS-VIEWER — Painel DNS read-only unificado

**Criado:** 2026-06-25  
**Solicitado por:** Roger  
**Status:** 🏗️ V1–V3 em produção (VM122/VM123) · V4 patches prontos · deploy VM112 pendente  
**Prioridade:** P1  
**Depende de:** [spec.md](./spec.md) · [004 cloudflare-zone-provision](../004-onboard-funnel-events/cloudflare-zone-provision.md) · [009](../009-ops-audit-overview/spec.md) · [028](../028-openpanel-ce-ligbox-reengineering/DNS53_OPENPANEL_PORTA53.md) · [035 §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md) · [027 RBAC](../027-desk-rbac-function-matrix/spec.md)

---

## 1. Objectivo

Permitir que **staff Ligbox** e **gerente de domínio** vejam **todos os apontamentos DNS** relevantes para um domínio — **sem alterar** registos na UI — com:

1. **Lista completa read-only** (tipo, nome, conteúdo, TTL, função)
2. **Origem do DNS** (Cloudflare Ligbox · BYO · OpenPanel BIND · registrador externo · resolução pública)
3. **Link «Editar aqui»** para a consola correcta (Cloudflare, OpenPanel, registrador)
4. **Regra wizard:** se o cliente escolhe **trazer DNS para Ligbox** → mostrar o que **será / foi aplicado**; caso contrário → mostrar **onde o DNS está no momento** da operação

**Fora de scope:** PATCH/POST/DELETE de registos no Desk ou Console — edição só via deep-link externo.

---

## 2. Problema actual (Roger 2026-06-25)

| Superfície | O que existe hoje | Lacuna |
|------------|-------------------|--------|
| Desk Overview → modal domínio | Tabela CF via `GET /api/v1/dns/cloudflare/records` | Só Cloudflare; sem OpenPanel; sem link editar |
| Desk Serviços IaaS | Resumo zona CF | Sem tabela de registos |
| Wizard passo DNS | Modos Ligbox/BYO/Registrador (037) | Cliente vê passos; staff não tem painel unificado |
| OpenPanel BIND | Zonas em `openpanel_dns` VM123 | **Zero UI Desk/Console** — operador não sabe apontamentos |
| Console `/admin` §5.5 | MX/NS status (mock) | Sem lista completa |

---

## 3. Regra de produto — caminho Ligbox vs externo

Esta regra **obrigatória** aplica-se em **Wizard**, **Desk** e **Console `/admin`**.

```mermaid
flowchart TD
  A[Domínio + sessão onboarding] --> B{dns_mode escolhido?}
  B -->|ligbox_cf_*| C[Modo PLANEADO / APLICADO]
  B -->|byo_cf| D[Modo BYO — zona cliente CF]
  B -->|external / registrador| E[Modo ACTUAL — DNS público]
  B -->|openpanel_bind| F[Modo OPENPANEL — zona BIND VM123]

  C --> C1[Mostrar NS Cloudflare Ligbox]
  C --> C2[Mostrar mail_dns_records preview]
  C --> C3[Após apply: registos CF reais]
  C --> C4[Link: Cloudflare conta Ligbox]

  D --> D1[Mostrar registos zona BYO via token vault]
  D --> D2[Link: dash.cloudflare.com zona cliente]

  E --> E1[GET dns/instructions — o que falta colar]
  E --> E2[GET dns/verify + dig público 009]
  E --> E3[NS actuais no registrador]
  E --> E4[Link: registrador ou painel CF cliente]

  F --> F1[Listar zona BIND OpenPanel]
  F --> F2[Link: OpenPanel DNS UI]
```

### 3.1 Tabela de decisão UI

| `dns_mode` (persistido) | Badge UI | Fonte de dados | O que mostrar |
|-------------------------|----------|----------------|---------------|
| `ligbox_cf_ligit` / `itecnologys` / `ibytera` | **DNS Ligbox** | CF API conta Ligbox + `mail_dns_records()` | Registos **aplicados**; se pré-NS → **preview** + NS a configurar |
| `ligbox_cf_provision_pending` | **DNS Ligbox (aguarda NS)** | `provision-zone` + preview | NS Cloudflare + tabela «será aplicado após NS» |
| `byo_cf` | **Cloudflare cliente** | Token BYO vault + CF API | Registos actuais na zona BYO |
| `external` / `registrar` | **DNS externo** | `dns/instructions` + `dig` público | Estado **actual** + instruções manuais |
| `openpanel_bind` | **OpenPanel BIND** | OpenAdmin API / zone file VM123 | Registos autoritativos `:53` Ligbox |
| `unknown` | **A determinar** | `dns/resolve` + NS lookup | NS actuais + sugestão de caminho |

### 3.2 Texto UX (gerente / staff)

| Modo | Mensagem principal |
|------|-------------------|
| Ligbox (pré-apply) | «Estes apontamentos **serão configurados** na Cloudflare Ligbox quando confirmar o passo DNS.» |
| Ligbox (pós-apply) | «Apontamentos **activos** na Cloudflare Ligbox.» |
| Externo | «O domínio usa DNS **fora da Ligbox**. Abaixo: o que está **publicamente** resolvido agora.» |
| OpenPanel | «Zona servida pelo **DNS Ligbox (OpenPanel)** em `95.216.14.162`.» |

---

## 4. Fontes de dados (backend)

### 4.1 Matriz de providers

| Provider ID | Quando usar | API / método | Edit link template |
|-------------|-------------|--------------|-------------------|
| `cf_ligbox` | Zona numa conta Ligbox (037) | CF API token conta + `zone_id` | `https://dash.cloudflare.com/{account_id}/{zone_name}/dns` |
| `cf_byo` | BYO wizard | Token vault sessão / entitlements | `https://dash.cloudflare.com/` (zona cliente) |
| `openpanel_bind` | Subdomínio/site bundle OP | OpenAdmin `GET /api/domains/{domain}/dns` ou `rndc`/zone export | Staff: `https://admin.openpanel.ligbox.com.br/domains/dns?domain={domain}` · Cliente: `openpanel.ligbox.com.br/domains/{domain}/dns` |
| `public_resolver` | Sempre (fallback / externo) | `dig` + `dns/verify` VM112 | — |
| `planned_ligbox` | Ligbox antes de apply | `mail_dns_records(domain)` wizard | — (preview only) |

### 4.2 Endpoint unificado (novo — Desk + Console)

```http
GET /api/v1/dns/viewer/{domain}
Authorization: Bearer <JWT staff ou domain-admin>
# OU (wizard / workers internos):
X-Ops-Internal-Token: <OPS_INTERNAL_TOKEN>
Query: ?include_public=true&email_service=true
```

**Variante Console gerente (Spec 035 V3):**

```http
GET /api/v1/domain-console/dns/viewer/{domain}
Authorization: Bearer <JWT domain-admin ou staff>
Query: ?email_service=true&include_public=true
```

Implementação: `domain_console_routes.py` — proxy para `fetch_dns_viewer()`; filtra `edit_links` CF Ligbox para roles não-staff.

**Autenticação interna (V4 wizard → Desk):**

| Header | Quem usa | Função |
|--------|----------|--------|
| `Authorization: Bearer …` | Desk UI, Console `/admin` | JWT staff ou domain-admin |
| `X-Ops-Internal-Token` | VM112 wizard backend | `require_internal_or_user` em `main.py` — evita duplicar lógica DNS no wizard |

Variáveis VM112: `DESK_API_URL=http://10.10.10.122:8080`, `OPS_INTERNAL_TOKEN` (mesmo valor Desk).

**Response:**

```json
{
  "domain": "empresa.com.br",
  "dns_mode": "ligbox_cf_ibytera",
  "mode_label": "Cloudflare Ligbox (ibytera)",
  "display_mode": "applied",
  "authoritative_source": "cf_ligbox",
  "nameservers": {
    "current_public": ["ns1.registro.br", "ns2.registro.br"],
    "ligbox_cloudflare": ["ada.ns.cloudflare.com", "bob.ns.cloudflare.com"],
    "match_ligbox": false
  },
  "records": [
    {
      "source": "cf_ligbox",
      "status": "applied",
      "type": "MX",
      "name": "empresa.com.br",
      "content": "mail.empresa.com.br",
      "priority": 10,
      "ttl": 3600,
      "purpose": "mx",
      "email_related": true
    }
  ],
  "planned_records": [],
  "public_checks": {
    "mx": { "ok": true, "values": ["10 mail.empresa.com.br"] },
    "spf": { "ok": true },
    "dkim": { "ok": false, "hint": "TXT _domainkey ausente" },
    "dmarc": { "ok": true }
  },
  "edit_links": [
    {
      "label": "Editar na Cloudflare (Ligbox)",
      "provider": "cf_ligbox",
      "url": "https://dash.cloudflare.com/…/empresa.com.br/dns",
      "roles": ["super_admin", "ops_lead", "devops", "seo"]
    }
  ],
  "instructions": null,
  "errors": []
}
```

**Variante wizard (sessão cliente — sem staff JWT):**

```http
GET /api/onboarding/dns/viewer/{domain}
Header: X-Onboarding-Session: {session}
```

Reutiliza mesma shape; filtra `edit_links` vazios para cliente; mostra `planned_records` quando `display_mode=planned`.

### 4.3 Endpoints existentes reutilizados

| Endpoint | Papel no viewer |
|----------|-----------------|
| `GET /api/onboarding/dns/resolve/{domain}` | Detectar `dns_mode` + conta CF |
| `GET /api/onboarding/dns/instructions/{domain}` | Modo externo — o que colar |
| `GET /api/onboarding/dns/verify/{domain}` | Checks públicos |
| `GET /api/onboarding/dns/portal-onboarding/{domain}` | NS + passos registrador |
| `GET /api/v1/dns/cloudflare/records` | **Legado Desk** — migrar para viewer |
| Spec 009 audit | `public_checks` MX/SPF/DKIM/DMARC |

### 4.4 OpenPanel BIND (Fase 2 viewer)

| Método | Descrição |
|--------|-----------|
| Desk proxy | `GET /api/v1/dns/openpanel/records?domain=` |
| Backend | Bridge VM123 → OpenAdmin list records |
| Fallback | SSH `docker exec openpanel_dns` zone dump (read-only) |

Ver [028 DNS53](../028-openpanel-ce-ligbox-reengineering/DNS53_OPENPANEL_PORTA53.md).

---

## 5. Superfícies UI

### 5.1 Desk VM122 (staff)

| Local | Componente | RBAC |
|-------|------------|------|
| Overview → modal tenant → clicar domínio | Secção **«DNS do domínio»** (substitui só-CF) | `cloudflare_dns.read` + roles 027 |
| Serviços IaaS → modal domínio | Mesma secção embed | idem |
| Chamado / ticket domínio | Tab DNS read-only | technician+ |

**Wireframe Desk:**

```
┌─ DNS — empresa.com.br ────────────────────────────────────────┐
│ [DNS Ligbox ▼]  Zona activa · 14 registos · 6 e-mail          │
│ NS públicos: ns1.registro.br …  ⚠ ainda não apontam CF Ligbox   │
├───────────────────────────────────────────────────────────────┤
│ Função │ Nome │ Tipo │ Conteúdo │ Origem │ Estado              │
│ MX     │ @    │ MX   │ mail…    │ CF Lig │ ✅ aplicado         │
│ SPF    │ @    │ TXT  │ v=spf1…  │ CF Lig │ ✅ aplicado         │
├───────────────────────────────────────────────────────────────┤
│ Checks públicos (dig): MX ✅ SPF ✅ DKIM ⚠ DMARC ✅             │
├───────────────────────────────────────────────────────────────┤
│ [Editar na Cloudflare ↗]  [Verificação avançada]  [Actualizar]│
└───────────────────────────────────────────────────────────────┘
```

### 5.2 Console gerente `/admin` (Spec 035)

Secção **Domínio & DNS** — ver [domain-manager-console-ui.md §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md#55-domínio--dns-dns-viewer).

Gerente vê read-only + link externo **se** tiver permissão (BYO: link CF cliente; Ligbox: mensagem «contacte suporte» ou link help).

### 5.3 Wizard passo DNS (cliente)

| Estado wizard | Painel lateral viewer |
|---------------|----------------------|
| Escolheu Ligbox | Preview `planned_records` + NS |
| Escolheu BYO | Registos actuais BYO + diff vs mail |
| Escolheu Registrador | `instructions` + verify |

---

## 6. Links «Editar aqui» (deep-link)

### Regra Roger (2026-06-25) — staff vs cliente

| Princípio | Detalhe |
|-----------|---------|
| **Visualização DNS** | Read-only na Console/Desk — **perfeita para suporte** (apontamentos, faltas, erros, origem) |
| **Login Console `/admin/*`** | **Uma vez** no shell Admin (`AdminShell`) — **não** por página. Handoff Desk→Console (`POST /auth/console-handoff` + `?desk_handoff=`) evita re-login ao navegar setups do Wizard |
| **Desk → Console** | Botão **«Abrir no Console ↗»** no painel DNS (staff com `can_read_dns_viewer`) |
| **Suporte NUNCA** | Painel **cliente** OpenPanel (`openpanel.ligbox.com.br/domains/…/dns`) — dá 500 e é vista errada |
| **Suporte SEMPRE** | **OpenAdmin** (`admin.openpanel.ligbox.com.br/domains/dns?domain=…`) ou Cloudflare **conta partner Ligbox** |
| **Gerente domínio** (futuro) | Links `audience: client` — painel cliente / CF BYO própria; **sem** OpenAdmin |

Campo `audience` em cada link: `staff` | `client` | `all`.

| Provider | Quem vê | URL staff | URL cliente |
|----------|---------|-----------|-------------|
| Cloudflare Ligbox | staff | `dash.cloudflare.com/.../zones/{zone_id}/dns` | — (gerente: «contacte suporte») |
| Cloudflare BYO | gerente + staff | dash CF zona BYO | idem |
| OpenPanel BIND | staff | `https://admin.openpanel.ligbox.com.br/domains/dns?domain={domain}` | `https://openpanel.ligbox.com.br/domains/{domain}/dns` |
| Registrador | todos | registro.br / detectado | idem |

**Desk / Console:** botão abre **nova tab** — nunca iframe.

**Implementação:** `dns_viewer._edit_links()` + `permissions.can_open_openpanel_admin_link()` vs `can_open_openpanel_client_link()`.

---

## 7. RBAC (Spec 027)

| Acção | Roles |
|-------|-------|
| `dns.viewer.read` — ver painel | super_admin, ops_lead, technician, noc, seo, devops, developer |
| Ver link Cloudflare Ligbox | super_admin, ops_lead, devops, seo |
| Ver link OpenAdmin (staff) | super_admin, ops_lead, devops, sales_admin, sales_support, seo, technician, noc |
| Ver link OpenPanel cliente | domain_manager (futuro) — **proibido** para staff suporte |
| Gerente domínio `/admin` | Só domínio próprio; sem CF Ligbox interna |
| Agente A2/A3 (Desk) | Lê viewer API — sugere fixes, não edita |

Formalizar permissão `dns.viewer.read` em `data-model.md` 027 (backlog).

---

## 8. Persistência `dns_mode`

Gravar em:

| Store | Campo |
|-------|-------|
| Wizard sessão onboarding | `session.dns_mode`, `session.cf_account_id` |
| `bundle_entitlements` (035) | `dns_mode`, `dns_provider`, `cf_zone_id` |
| Desk `billing_accounts` / tenant meta | espelho read-only |

Resolver ordem:

1. Entitlements domínio activo  
2. Sessão onboarding em curso  
3. `GET dns/resolve/{domain}`  
4. NS lookup público → `unknown`

---

## 9. Fases de implementação

| Fase | Entregável | Estado |
|------|------------|--------|
| **V0** | Desk CF only (`cloudflare_dns.py`) | ✅ Parcial |
| **V1** | `GET /api/v1/dns/viewer/{domain}` — CF + public + planned | ✅ VM122 2026-06-25 |
| **V1b** | Desk UI unificada (`dns-viewer.js`) | ✅ VM122 2026-06-25 |
| **V2** | OpenPanel BIND records no viewer | ✅ VM122 2026-06-25 |
| **V3** | Console `/admin/dominio` | ✅ VM123 2026-06-25 |
| **V4** | Wizard painel lateral + diff planned vs actual | 📋 patches no repo · deploy VM112 pendente (SSH) |

---

## 10. Critérios de aceitação

1. Staff abre domínio email Ligbox no Desk → vê **≥ MX, SPF, DKIM, DMARC, A mail** com origem «CF Ligbox».
2. Domínio **externo** (registrador) → viewer mostra **NS actuais** + registos **públicos** (`dig`), **não** preview Ligbox.
3. Domínio **Ligbox pré-NS** → viewer mostra **planned_records** + NS Cloudflare a configurar.
4. Botão «Editar na Cloudflare» visible para `ops_lead` → abre zona correcta (conta ibytera/ligit/itecnologys).
5. **Nenhum** botão «Guardar» / «Apagar registo» no viewer.
6. Gerente em `/admin` vê mesma tabela (domínio próprio) — Spec 035.
7. OpenPanel zone (Fase V2): registos BIND listados + link OpenPanel.
8. API responde ≤3s (cache 60s por domínio OK).

---

## 11. Código (monorepo + deploy)

| Caminho | Função | Deploy |
|---------|--------|--------|
| `projects/ops-desk/api/app/dns_viewer.py` | Orquestrador providers | VM122 API |
| `projects/ops-desk/api/app/cloudflare_dns.py` | Provider CF | VM122 API |
| `projects/ops-desk/api/app/openpanel_dns.py` | Provider BIND (`dig @10.10.10.123`) | VM122 API |
| `projects/ops-desk/api/app/domain_console_routes.py` | Proxy Console `/admin/dominio` | VM122 API |
| `projects/ops-desk/api/app/main.py` | Rotas viewer + `require_internal_or_user` | VM122 API |
| `projects/ops-desk/frontend/assets/dns-viewer.js` | Componente Desk | VM122 frontend |
| `specs/019-.../deploy/frontend/src/components/DnsViewerPanel.jsx` | Componente Console | VM123 |
| `specs/019-.../deploy/frontend/src/views/admin/AdminDominio.jsx` | Página `/admin/dominio` | VM123 |
| `deploy/vm112-wizard/onboarding-dns-viewer-v4.patch.py` | Endpoint wizard proxy Desk | VM112 backend |
| `deploy/vm112-wizard/frontend-dns-viewer-v4.patch.py` | Painel lateral step DNS | VM112 frontend |
| `deploy/vm112-wizard/DNS-VIEWER-V4.md` | Runbook deploy V4 | — |

**Nota deploy VM122:** código API corre em container Docker — após `rsync` ao host, copiar para o container (`docker cp …/main.py …_api_1:/app/app/`) ou rebuild imagem.

---

## 12. Documentos relacionados

| Doc | Relação |
|-----|---------|
| [spec.md](./spec.md) | Fluxo decisão DNS wizard |
| [cloudflare-zone-provision.md](../004-onboard-funnel-events/cloudflare-zone-provision.md) | provision-zone / apply |
| [domain-manager-console-ui.md §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md) | UI gerente |
| [009 spec](../009-ops-audit-overview/spec.md) | Checks públicos |
| [027 spec](../027-desk-rbac-function-matrix/spec.md) | Deep-links CF / OP |
| [DNS-VIEWER-ROLLBACK](./deploy/DNS-VIEWER-ROLLBACK.md) | Rollback e feature flag |
| [DNS-VIEWER-EXEC-20260625](./deploy/DNS-VIEWER-EXEC-20260625.md) | Registo deploys e commits |
| [deploy/vm112-wizard/DNS-VIEWER-V4.md](../../../deploy/vm112-wizard/DNS-VIEWER-V4.md) | Runbook V4 wizard |

---

## 13. Decisões Roger (2026-06-25)

| # | Decisão |
|---|---------|
| D1 | Viewer é **read-only** — edição só via link externo |
| D2 | Caminho **Ligbox** → mostrar **planned/applied** Ligbox; **externo** → mostrar **actual** público |
| D3 | OpenPanel BIND **deve** aparecer no viewer (Fase V2) — gap operacional actual |
| D4 | Um endpoint unificado `dns/viewer` — não proliferar modais CF-only |
| D5 | Mesmo componente visual Desk + Console `/admin` (design system 035) |

---

## 14. Versionamento e rollback

**Obrigatório antes de cada deploy V1+:** backup + tag git + feature flag.

| Documento | Função |
|-----------|--------|
| [deploy/DNS-VIEWER-EXEC-20260625.md](./deploy/DNS-VIEWER-EXEC-20260625.md) | Checklist deploy, ficheiros, validação |
| [deploy/DNS-VIEWER-ROLLBACK.md](./deploy/DNS-VIEWER-ROLLBACK.md) | Rollback por fase (Desk / Wizard / Console) |
| [deploy/scripts/preflight-dns-viewer.sh](./deploy/scripts/preflight-dns-viewer.sh) | Backup VM122 (+ `--wizard` / `--console`) |
| [deploy/scripts/verify-dns-viewer.sh](./deploy/scripts/verify-dns-viewer.sh) | Smoke test pós-deploy ou pós-rollback |
| [deploy/scripts/rollback-dns-viewer.sh](./deploy/scripts/rollback-dns-viewer.sh) | Rollback automatizado VM122 |

### Feature flag VM122

```bash
DNS_VIEWER_ENABLED=1   # viewer activo
DNS_VIEWER_ENABLED=0   # fallback legado /dns/cloudflare/records + UI V0
```

### Tag git recomendada

```bash
git tag -a dns-viewer-pre-v1-YYYYMMDD -m "Antes DNS Viewer V1"
```

Entrega monorepo V1–V3 + patches V4: commit **`038fb8f`** (2026-06-25).

---

## Incidente 2026-06-25 — Console «Failed to fetch» no login DNS

### Sintoma

`console.ligbox.com.br/admin/dominio` → Login Desk → **Failed to fetch** (abaixo do botão Entrar).  
«Sem dados DNS.» aparece em baixo — **não é erro**, é estado vazio antes de login + consulta.

### Causa

O frontend Console (`api.js`) enviava `credentials: 'include'` nas chamadas cross-origin para `https://api.ops.ligbox.com.br`.  
A API Desk responde `Access-Control-Allow-Origin: *`. Browsers **bloqueiam** pedidos credenciados com wildcard → `TypeError: Failed to fetch`.

### Correção

1. Remover `credentials: 'include'` do `apiGet` / `apiPost` — auth usa **Bearer JWT** em `localStorage`, não cookies.
2. API Desk: `CORS_ORIGINS` explícito inclui `https://console.ligbox.com.br` (deixa de depender só de `*`).
3. Console nginx: `index.html` com `Cache-Control: no-cache` para forçar bundle novo após deploy.
4. Mensagens de erro amigáveis quando a rede/CORS falha; sessão expirada (401) pede re-login.

| Ficheiro | Alteração |
|----------|-----------|
| `specs/019-.../deploy/frontend/src/lib/api.js` | Sem `credentials`; `wrapNetworkError` |
| `specs/019-.../deploy/frontend/src/views/admin/AdminDominio.jsx` | 401 → clear session |
| `projects/ops-desk/api/app/main.py` | `CORS_ORIGINS` |
| `specs/019-.../deploy/nginx/default.conf` | no-cache em `index.html` |

### Consulta DNS (ex.: diarissima.com)

Após fix, `GET /api/v1/domain-console/dns/viewer/diarissima.com` retorna **200** (~3s) com registos OpenPanel BIND — validado 2026-06-25.

---

## Incidente 2026-06-25 — Link «Editar no OpenPanel» abria painel cliente (500)

### Sintoma

Staff em Console → `diarissima.com` → **Editar no OpenPanel** → `openpanel.ligbox.com.br/domains/diarissima.com/dns` → **ERROR 500** (vista **cliente**, não OpenAdmin).

### Causa

`edit_links` apontava `OPENPANEL_URL` (painel utilizador) em vez de `OPENADMIN_URL` (DNS Zone Editor staff).

### Correção

| Antes | Depois (staff) |
|-------|----------------|
| `openpanel.ligbox.com.br/domains/{d}/dns` | `admin.openpanel.ligbox.com.br/domains/dns?domain={d}` |
| Label «Editar no OpenPanel» | «Editar DNS (OpenAdmin)» |
| `provider: openpanel_bind` | `provider: openpanel_admin`, `audience: staff` |

Gerente futuro (`domain_manager`) receberá link cliente separado — staff **nunca**.

### Ficheiros

`openpanel_dns.py`, `dns_viewer.py`, `permissions.py`, `domain_console_routes.py`, `dns-viewer.md` §6.

### Credenciais

- **Login Desk** exige utilizador **Ops Desk** (ex.: `Roger`, `root`) — **não** `ligboxadmin` (conta OpenAdmin/hosting).
- Após login: introduzir domínio e **Consultar DNS**.

### Endpoint legado (mantido até cutover)

`GET /api/v1/dns/cloudflare/records` — **não remover** enquanto rollback não estiver validado em produção ≥7 dias.
