# F5.I2.T2 — LGPD: DSAR, retenção, legal hold, auditoria, runbook ANPD, DPO

| Campo | Valor |
|---|---|
| **Fase** | F5 — Borda |
| **Issue** | F5.I2 — Dados abertos e LGPD (plano 17) |
| **kd task** | `task_01hqfr8y` (`closed`) |
| **Âncora** | `wiki/implementation/05-borda.md` |
| **Status da revisão** | 🔧 com correções |

## O que foi implementado

- Módulo `Portal/Lgpd`: DSAR (`lgpd.dsar_requests`, dado pessoal **cifrado** com cast `encrypted`, protocolo, prazo, auditoria explícita); política `/politica-de-privacidade` + DPO; atendimento no CMS.
- Retenção: `Core\Contracts\RetentionRegistry` + `Ports\RetentionTarget` + `retention:expunge` (alvo `lgpd-dsar` anonimiza além do prazo, preserva histórico, respeita `legal_hold`).
- `AuditEntry::event/access` agora **explícitos** (a política por área barra só a trilha ambiente).

## Arquivos para revisar

- `src/modules/Portal/Lgpd/**`
- `src/modules/Portal/Lgpd/Database/Migrations/2026_10_22_000000_create_lgpd_dsar_requests_table.php`
- `src/modules/Portal/Lgpd/Database/Models/LgpdDsarRequest.php`
- `src/modules/Portal/Lgpd/Logic/Actions/{LgpdSubmitDsar,LgpdUpdateDsar}.php`
- `src/modules/Portal/Lgpd/Retention/LgpdDsarRetention.php`
- `src/modules/Portal/Lgpd/Layout/Views/**`
- `src/modules/Portal/Lgpd/ModuleProvider.php`
- `src/modules/Portal/Lgpd/MODULE.md`
- `src/modules/Portal/Lgpd/Test/LgpdTest.php`
- `config/lgpd.php`
- `src/core/Contracts/{RetentionRegistry,AuditEntry}.php`
- `src/core/Contracts/Ports/RetentionTarget.php`
- `src/core/Internal/Retention/**`
- `src/core/Internal/Audit/Services/AuditService.php`
- `src/core/CoreServiceProvider.php`
- `routes/console.php`
- `wiki/runbooks/anpd-incidentes.md`
- `config/layout.php`, `src/layout/Lang/*/shell.php`
- `src/tests-e2e/tests/13-lgpd.spec.ts`

## Pontos de revisão

### PHP 8.4
- [ ] `declare(strict_types=1)`; tipos nativos (union/enum/`readonly`); sem `mixed` desnecessário.
- [ ] Dependências **injetadas** (nenhum `new`/`app()`/`resolve()` no corpo); sem traits/abstract/generics.
- [ ] `match` em vez de cadeias de `if`; `?->`/`??`; sem código morto; PHPStan `max` limpo.

### Laravel 12
- [ ] Nativo primeiro (Eloquent casts/scopes, `FormRequest`, `Gate`, scheduler, events) — sem reimplementar.
- [ ] Sem closure em rota; resources/rotas nomeadas; `config()` só lê, `env()` só em `config/`.
- [ ] Migração **idempotente**; `down()` → `backup_*` (nunca `DROP`); sem `orderBy` cru quando `latest/oldest` serve.

### HTML semântico + a11y (eMAG/WCAG 2.2 AA)
- [ ] Um `<main>`, headings em nível correto, landmarks; só ARIA quando necessário.
- [ ] Alvos ≥ 24 px (≥ 44 px em ponteiro grosso); foco visível; rótulos em todos os campos; contraste (axe).
- [ ] Funciona **sem JS**; `sniffCSS` sem `fail`/`warn` fora do ledger.

### CSS moderno
- [ ] `oklch`, `clamp`, `rem`/`fr`/`dvh`/`%`; `@layer` (a11y por último); `@property`; subgrid com fallback.
- [ ] Sem `!important`, sem `style="…"` inline, sem hex/rgb (exceto justificado); sem CDN.

### Decisões do projeto (ADRs)
- [ ] Respeita a ADR do escopo (ver cabeçalho) e não escapa das demais (001, 002, 004, 013, 015…).
- [ ] Determinismo: relógio/id injetados, ordem explícita, dados idempotentes (ADR-001).

### i18n (ADR-012)
- [ ] Nenhuma string visível fixa; 4 locales com paridade de chaves; `route()`/`localized_url()` (nunca path literal).

### Observabilidade/segurança/testes
- [ ] Span + `log_event('{module}.{action}')` com atributos `scalar|null`, **sem PII** (ADR-024).
- [ ] Teste que falha sem a mudança (vermelho→verde) + evidência no `kd`; `make quality` 2×.

### Específicos desta task

- [ ] Cifra em repouso comprovada (coluna crua ≠ claro); log sem PII.
- [ ] Retenção nunca `DROP`; respeita `legal_hold`; determinística (Clock).
- [ ] Exceção de nome em view (`privacy`/`received`) documentada; freio isola por IP nos testes.

## Comandos úteis

```bash
make quality            # lint + PHPStan max + gates + testes
make e2e                # Playwright + axe (se a task toca UI)
make check-boundaries   # fronteiras core/services/layout/modules
```

---

> Revisar contra `AGENTS.md`, `wiki/adr/` e o plano citado. Registrar achados no `kd` (`kd write --anchor`) e, se houver correção, teste de regressão.

## Notas da revisão

| Data | Revisor | Resultado | Observações |
|---|---|---|---|
| 2026-10-19 | agente | 🔧 | **A** (alto, i18n/D8) rótulos de classificação saíam em pt_BR fixo em todos os locales (config como texto visível) → `Lang/{locale}/privacy.php` + teste vermelho→verde (`fact_00m2spdk`). **B** (alto, D13/D14) `tools/check.sh` usava `BASE_URL :8090` e pulava e2e/a11y/perf em silêncio — o DoD `make check E2E=1` podia ser verde sem e2e → default `:8080` + falha explícita com `E2E=1` offline + gate `CheckPipelineTest` (`error_01oxuv5y`). **C** (alto, D7) com o gate consertado, o e2e expôs contraste AA 1,07:1 no badge `saude` de Notícias (alto contraste) → `text-civic-on-yellow` (`error_01psxo7h`). **D** (médio, D13) testes que só afirmavam sucesso → agora provam auditoria da leitura no CMS (`lgpd.view`), protocolo na página de recebido e o alvo `lgpd-dsar` no comando. **E** (médio, D14) `MODULE.md` dizia que o runbook ANPD estava fora do escopo (T2 o entrega) → corrigido. **F/G/H** (baixo/obs) `isOverdue()` com `Carbon::now()` (`risk_00jg0337`), `legal_hold` sem superfície de gestão + `type`/`status` sem CHECK + retenção sem chunking (`risk_000ctztx`), vocabulário da auditoria ambiente ≠ span/log (`risk_01vx21pf`). Env drift tratado (`fact_01bt2bd1`). Evidência: `make check E2E=1` verde 2× seguidas (20 OK · 0 FAIL · 1 SKIP), e2e 87/87 + axe no alto contraste, a11y OK. |

> Resultado: `✅ revisado` · `🔧 com correções` · `⏭️ delegado` (com justificativa).
