# F2.5 — Auditoria, evidências e observabilidade de domínio (pre-UI)

> **Épico kd:** `epic_019kuc07` · **Depende de:** F2 (Núcleo DDD-lite) · **Habilita:** F3 (Layout & UX)
> **Decisão:** [ADR-035](../adr/ADR-035-trilha-de-auditoria-e-evidencias.md) ·
> **Spec:** `wiki/specs/14-auditoria.md`
>
> Esta fase roda **antes** da F3 (Layout & UX), por decisão do dono: a evidência de auditoria é
> pré-requisito para o CMS e para a operação.

## 1. Objetivo

Tornar a trilha de auditoria uma **evidência forense** completa e consistente (ator, módulo, origem,
ação semântica, antes/depois, correlação) — **rigorosa no CMS**, **simples no portal** — sobre a
infraestrutura já existente (`core.core_audit_logs`: imutável + BLAKE3 + particionada).

E, na mesma fase (antes da UI), **fechar os transversais**: `log_event` de domínio realmente usado,
telemetria completa (módulos **e** services) e specs publicadas — **sem abstração nova**.

## 2. Escopo por área (política)

| Área | Nível | O que registra |
|---|---|---|
| `cms` | **full** | toda mutação (automática) + leituras sensíveis + login/logout |
| `portal` | mínimo | **nenhuma** trilha individual; só log técnico + analytics agregado |
| `api` | por chave/rota | mutações de escrita; leituras conforme sensibilidade |

Fonte de configuração: `config/audit.php` (`levels`, `sensitive`).

## 3. Arquitetura da trilha

### 3.1. Forma do registro (base sempre presente; extensões por caso)

```
core.core_audit_logs
  id             uuidv7 (PK, + created_at)
  created_at     timestamptz (µs; Clock injetado — ADR-001)
  area           text          -- cms | portal | api
  module         text          -- token canônico (ADR-004)
  origin         text  null    -- rota / data-audit-source / console / queue
  event          text          -- {module}.{action}
  action         text          -- created | updated | deleted | view (compat.)
  entity_type    text          -- target.type
  entity_id      text          -- target.id
  actor          jsonb         -- {id,name,email} snapshot; CHECK jsonb_typeof='object'
  user_id        uuid  null    -- compat./índice
  payload_before jsonb         -- estado anterior
  payload_after  jsonb         -- estado atual
  changes        jsonb  null   -- diff só dos campos alterados (atalho de consulta)
  request_id     text  null    -- correlação (ADR-024)
  trace_id       text  null    -- correlação (ADR-024)
  metadata       jsonb         -- ip, user_agent, url
  integrity_hash text          -- BLAKE3 (ADR-029)
```

### 3.2. Via única de escrita

- `Core\Contracts\AuditEntry` — DTO `readonly` que transporta o evento:
  - `AuditEntry::model(string $module, string $event, Model $model)` — mutação de modelo;
  - `AuditEntry::event(string $module, string $event, ?string $targetType = null, ?string $targetId = null, array $before = [], array $after = [])` — evento de domínio;
  - `AuditEntry::access(string $module, string $event, ?string $targetType = null, ?string $targetId = null)` — leitura.
- `Core\Contracts\AuditWriter`:
  - `record(AuditEntry $entry): void` — **novo**;
  - `log(string $action, Model $model): void` — atalho (compatibilidade; delega a `record`).
- `Core\Internal\Audit\Services\AuditService` (implementação) resolve, sozinho:
  - `actor` snapshot (`Auth::user()` → `{id,name,email}`) e `user_id` (compat.);
  - `module` (default: derivado do namespace `Modules\{Grupo}\{Modulo}\…` → token; Core → `core`);
  - `area`/`origin` (`PlatformArea`, `route()->getName()`, header `X-Audit-Origin`);
  - `request_id`/`trace_id` (`Context`, ADR-024);
  - `changes` (diff), masking de campos sensíveis (`SENSITIVE_FIELDS`) e `integrity_hash` (BLAKE3).

### 3.3. Enriquecimento automático

O `GlobalAuditSubscriber` (já existente) passa a montar um `AuditEntry::model(...)`:
- `module` = token derivado do namespace do modelo;
- `event` = `{module}.{created|updated|deleted}`;
- `origin`/`area`/`actor`/correlação = resolvidos pelo `AuditService`.

→ **Toda** mutação de modelo sai completa, em qualquer área, sem depender do autor lembrar.

### 3.4. Acesso (leitura) — só CMS

Middleware `Core\Internal\Audit\Http\Middleware\AuditAccess` (alias **`audit.access`**):
- aplicado à área **CMS** no `AreaRegistrar` (após `support.group` e `module.activation`);
- consulta `config('audit.sensitive')` (recursos/rotas considerados sensíveis);
- grava `AuditEntry::access()` para leituras de dado pessoal/exports;
- **não** atua no portal.

## 4. Issues e tasks

### `F2.5.I1` — Núcleo da trilha (schema + DTO + enriquecimento)
- **T1** **Migration aditiva** (`Database/Migrations/…_add_evidence_columns_to_core_audit_logs.php`,
  ADR-017): colunas `area/module/origin/event/actor/changes/request_id/trace_id`; índices
  `CREATE INDEX IF NOT EXISTS` (`(module,created_at)`, `(event,created_at)`,
  `(entity_type,entity_id,created_at)`); `CHECK jsonb_typeof(actor)='object'`; `down()` = **rename
  para `backup_*`** (nunca drop).
- **T2** `Core\Contracts\AuditEntry` (DTO `readonly`) + `AuditWriter::record()`; `log()` vira atalho.
- **T3** `AuditService`: preenche ator/módulo/origem/área/correlação/diff; mapeia `{module}.{action}`.
- **T4** `GlobalAuditSubscriber`: monta `AuditEntry::model(...)` (módulo do namespace) + teste de enriquecimento.

### `F2.5.I2` — Acesso no CMS e eventos explícitos
- **T1** Middleware `audit.access` + `config/audit.php` (`levels`, `sensitive`); registrar no `AreaRegistrar`.
- **T2** Eventos explícitos sem modelo: **login/logout**, **export/download**, **DSAR**, **perfil/permissão**.
- **T3** Política por área (`PlatformArea` → nível) + teste de que o **portal não** gera trilha.

### `F2.5.I3` — Retenção/LGPD, verificação e spec
- **T1** Retenção explícita + `legal hold` + expurgo agendado (Scheduler nativo — ADR-019).
- **T2** Testes: *shape* completo, ator/módulo/origem/área, acesso, imutabilidade e hash
  (ampliar `AuditTrailTest`; novos casos de acesso/enriquecimento).
- **T3** Spec `wiki/specs/14-auditoria.md` (Vigente) + entrada no índice (`docs-check`).

### `F2.5.I4` — Observabilidade de domínio (log_event + telemetria) e specs
> Fecha as lacunas dos transversais **antes da UI**, sem abstração nova: reusa o `log_event()` e o
> `Core\Contracts\Telemetry` que já existem. Simples, determinístico e testável.
- **T1** **`log_event` de domínio:** em cada Action de módulo e operação de service, **uma linha**
  `log_event('{module}.{action}', ['module' => …, 'action' => …, 'id' => …])` — mesmo vocabulário do
  span; atributos `scalar|null`, **sem PII**. Nada de wrapper/fachada.
- **T2** **Teste estrutural (determinístico):** espelha `ModulesTelemetryTest` — toda
  `Logic/Actions/*` de módulo **injetá** `Telemetry` e chama `log_event('{token}.…')`; idem
  `src/services/*`. Usa `RecordingTelemetry` + `Log` fake (sem I/O).
- **T3** **Specs dos transversais:** atualizar `wiki/specs/06-observabilidade.md` (convenção de log
  de domínio + evidências) e criar `wiki/specs/13-i18n.md` (índice + `docs-check`).

## 5. DoD (binário)

- [x] Toda mutação de modelo na área **CMS** gera registro com
  `module/event/actor/origin/area/before/after`.
- [x] Toda **leitura sensível** no CMS gera registro `event='{module}.view'`.
- [x] **Portal** não gera trilha individual (teste).
- [x] `UPDATE`/`DELETE` na trilha → exceção (trigger `trg_immutable_audit`) e `integrity_hash` BLAKE3 válido.
- [x] Retenção e `legal hold` **implementados** (`config/audit.retention` + `audit:expunge` + `core_audit_legal_holds`); **DSAR** (ADR-019) documentado (spec 14) e registrável via `AuditEntry::event` — fluxo completo em fase própria.
- [x] **Toda** Action de módulo e operação de service emite `log_event('{module}.{action}', …)` **e**
  o span homônimo — garantido por teste estrutural (sem abstração nova).
- [x] Specs `06` (log de domínio), `13-i18n` e `14-auditoria` no `docs-check`.
- [x] `make check` verde **2×**.

## 6. Evidência

- **F2.5.I1** — migration `2026_10_17_000000` (8 colunas + 3 índices + `CHECK` actor) ·
  `AuditEntry`/`AuditWriter::record` · `AuditService` (ator, módulo, área, origem, correlação, diff, hash) ·
  `GlobalAuditSubscriber`.
- **F2.5.I2** — `audit.access` + `config/audit.php` · `AuditAuthSubscriber` · `AuditPolicy` (portal sem trilha).
- **F2.5.I3** — `ExpungeAuditLogs` + `audit:expunge` + `core_audit_legal_holds` · testes de
  shape/hash/acesso/imutabilidade · [spec 14](../specs/14-auditoria.md).
- **F2.5.I4** — `log_event('{module}.{action}')` em Actions/services · teste estrutural ·
  specs [06](../specs/06-observabilidade.md)/[13](../specs/13-i18n.md).
- **Gate:** `make check` **EXIT 0** (148 testes / 409 asserções · PHPStan max **0** · baseline **144**);
  `bash tools/docs-check.sh` **11 specs**; `make check` OK.
