# Implementation Plan: Audit Overview Dashboard (009)

**Branch**: `009-ops-audit-overview` | **Date**: 2026-06-08 | **Spec**: [spec.md](./spec.md)

## Summary

Implementar Track A MVP: collectors read-only, persistência SQLite, API overview/scorecard, worker periódico, UI grid estilo Cloudflare. Primeiro tenant: VM112; domínios auto-descobertos via eventos onboarding.

## Technical Context

**Language/Version**: Python 3.11+ (API + worker VM122); JS vanilla (frontend)

**Primary Dependencies**: FastAPI, httpx, dnspython (ou subprocess dig), sqlite3, redis (worker queue existente)

**Storage**: SQLite novas tabelas `audit_domains`, `audit_checks`

**Testing**: `scripts/verify-audit-overview.sh`; mock tenant offline

**Target Platform**: VM122 worker → VM112 API `:8090` + DNS público + HTTPS webmail

**Performance Goals**: Ciclo completo 1 domínio < 30s; overview API < 500ms

**Constraints**: Read-only; LAN para VM112; sem novos containers (worker existente)

## Constitution Check

| Princípio | Status |
|-----------|--------|
| IV. Mail vs Ops | ✅ PASS — collectors read-only, Ops separado |
| VII. Spec-Driven | ✅ PASS |
| IX. YAGNI | ✅ PASS — SQLite, 8 checks fixos |

## Project Structure

```text
specs/009-ops-audit-overview/
├── spec.md
├── plan.md
├── research.md
├── contracts/audit-api.md
├── checklists/requirements.md
└── tasks.md

api/app/
├── main.py                    # routes /audit/*
├── collectors/
│   ├── __init__.py
│   ├── base.py
│   ├── vm112.py               # carbonio, nginx, cert via portal API
│   ├── dns.py                 # mx, spf, dkim, dmarc
│   └── webmail.py             # HTTP check
└── audit_store.py             # SQLite CRUD

worker/
└── audit_runner.py            # loop ou job redis

frontend/assets/
├── app.js                     # view overview + scorecard drill-down
└── styles.css                 # cards health grid
```

## Phase 0: Research

Ver [research.md](./research.md).

## Phase 1: Data Model

```sql
CREATE TABLE audit_domains (
  id INTEGER PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  domain TEXT NOT NULL,
  source TEXT NOT NULL DEFAULT 'onboarding',
  created_at TEXT NOT NULL,
  UNIQUE(tenant_id, domain)
);

CREATE TABLE audit_checks (
  id INTEGER PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  domain TEXT NOT NULL,
  check_id TEXT NOT NULL,
  status TEXT NOT NULL,
  message TEXT,
  evidence TEXT,
  checked_at TEXT NOT NULL,
  UNIQUE(tenant_id, domain, check_id)
);
```

## Phase 2: Collectors

| Module | Checks |
|--------|--------|
| `vm112.py` | carbonio, nginx_vhost, cert_le |
| `dns.py` | dns_mx, dns_spf, dns_dkim, dns_dmarc |
| `webmail.py` | webmail_http |

Runner: `run_audit(tenant_id, domain) -> dict[check_id, result]`

## Phase 3: API

- `GET /api/v1/audit/overview`
- `GET /api/v1/audit/tenants/{id}/scorecard?domain=`
- `POST /api/v1/audit/run/{tenant_id}?domain=` (manual trigger, ops use)

## Phase 4: Worker

- Env `AUDIT_INTERVAL_SEC=600`
- A cada ciclo: list domains → run_audit → upsert audit_checks
- Auto-register domains from `webhook_events` where event in (`account.created`, `onboarding.completed`)

## Phase 5: UI

- Nova tab **Overview** ou substituir Infra básica
- Grid cards: tenant name, score X/8, status badge, last audit
- Click → scorecard modal/panel com 8 rows

## Implementation Phases (time estimate)

| Phase | Tasks | ~Time |
|-------|-------|-------|
| A Schema + store | T001-T003 | 1h |
| B Collectors | T004-T008 | 3h |
| C API | T009-T011 | 1.5h |
| D Worker | T012-T014 | 1.5h |
| E UI | T015-T018 | 2h |
| F Test + deploy | T019-T021 | 1h |

**Total ~10h** — pode paralelizar com 004 após API base pronta.

## Risk & Mitigation

| Risco | Mitigação |
|-------|-----------|
| Portal API lenta | Timeout 10s por check; partial results |
| DNS rate limit | Cache 10 min; sequential checks |
| Falso negativo DKIM | evidence field com TXT encontrado |
| Worker sobrecarga | 1 tenant MVP; queue single-thread |

## Sequencing with 004

- **Paralelo possível**: equipas diferentes (004 portal+funil, 009 worker+UI)
- **Dependência soft**: 009 domain auto-discovery beneficia de 004 `onboarding.completed` mas funciona com `account.created` existente
- **Recomendação**: implementar 004 Phase A primeiro; 009 Phase A-B em paralelo; UI 004+009 na mesma sprint UI
