# F4.I1.T4 — Acesso ao painel: login nativo + freio por e-mail/IP + auditoria (ADR-037)

| Campo | Valor |
|---|---|
| **Fase** | F4 — Produto & CMS |
| **Issue** | F4.I1 — CMS próprio e editor (plano 12) |
| **kd task** | `task_00fivlye` (`closed`) |
| **Âncora** | `wiki/implementation/04-produto-cms.md` |
| **Status da revisão** | 🔧 com correções |

## O que foi implementado

- Sessão nativa sobre `cms_users` (guard `web`, eloquent), login/logout no domínio do CMS, Argon2id, `session()->regenerate()`, falha genérica, `POST`+CSRF logout.
- Freio por e-mail+IP (`ThrottlePanelLogin`, 429 + `Retry-After`); auditoria herdada (`core.login`/`core.logout`/`core.login_failed`).
- Rota estável `cms.login` (ADR-004) e `redirectGuestsTo`.

## Arquivos para revisar

- `src/core/Area/Cms/**` (Models/User, Routes/web.php, Controllers)
- `src/core/Area/Cms/Logic/Controllers/{AuthenticateController,LogoutController}.php`
- `src/core/Internal/Http/Middleware/ThrottlePanelLogin.php`
- `src/layout/Cms/auth/login.blade.php`
- `config/auth.php`
- `src/core/Area/Cms/Database/Seeders/CmsUserSeeder.php`
- `tests/Feature/CmsAuthTest.php`
- `src/tests-e2e/tests/07-cms-shell.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

- [ ] Guard `web` isolado (cookie/sessão por domínio); `noindex` no login.
- [ ] Auditoria herda eventos nativos; sem revelar existência do e-mail.

## 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-18 | agente | 🔧 | **A** (**bloqueador**) **4 migrations** declaravam `varchar(n)` (`->string()`/`->rememberToken()`) contra a **ADR-007** (“`text` com `CHECK` (não `varchar(n)`)”, reforçada pela spec 10 §21) e o `tools/migration-check` **não tinha regra** para isso — o desvio atravessou F1→F4 sem gate → **regra nova `no-varchar`** (com fixture provando que morde) + `create_*` em `text` + **4 migrations de convergência** (por dono, no-op quando o tipo já é `text`) + teste de esquema (e do **caminho de ação**: `varchar` → `text` provado de verdade); verificado: **zero** coluna `varchar` nas 4 tabelas (`fact_018x2miq`); **B** (médio) a suíte de auth não cobria o **balde zerando no sucesso** (comportamento documentado) nem o **logout só-`POST`** → 2 testes; o de `405` foi **provado vermelho** trocando a rota para `GET` (`Expected 405, received 302`) — `fact_00fkwuys`; **C** (baixo) `config/auth.php` mantém o broker `passwords` apontando para `password_reset_tokens`, tabela que não existe e que nada usa (reset fora de escopo — ADR-037) — `risk_00ap1w5z`; **D** (baixo) o controller lê a credencial por `$request->string()` em vez de `validated()`, apesar do `LoginRequest` existir — `risk_00aakt4d`; **E–I** (obs) `Notifiable` sem uso no `User`; `lock_version` existe em 5 tabelas e nada lê (padrão do projeto); o freio não emite `X-RateLimit-*`; freio por **e-mail+IP** não freia stuffing com e-mails variados do mesmo IP (decisão da ADR-037) — `risk_01i3xldb`; falha de login não guarda identificador na trilha (deliberado, sem PII). Verificados e OK: sessão nativa (`web`+eloquent) com `SESSION_DOMAIN` nulo (cookie host-only = isolamento real entre portal e painel), Argon2id pelo cast `hashed`, `session()->regenerate()`, falha **genérica** (senha errada e e-mail inexistente com a mesma mensagem), `POST`+CSRF no logout, `429` **RFC 7807** (`application/problem+json` + `trace_id` + `Retry-After`), freio **antes** do banco e contando só falhas, `noindex` no login, um único `<h1>`, rótulos implícitos com `aria-invalid`/`aria-describedby`, auditoria pelos eventos nativos (`core.login`/`core.logout`/`core.login_failed`) sem credenciais, migração idempotente com `down()` não-destrutivo. **Nota:** CSRF **não** é testável em PHPUnit (`ValidateCsrfToken` se dispensa em `runningUnitTests()`); a prova fica com o e2e — `fact_001geqqs`. |

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