# P0.S2 — settings: cache, auditoria, segredos e `core:settings:show`

| Campo | Valor |
|---|---|
| **Fase** | P0 — Fundação do painel: configurações tipadas e primitivas |
| **Issue** | P0.S2 — cache + auditoria + segredos + comando (ADR-046) |
| **`kd` task** | `task_01msi6ys` (épico `epic_00j5mhqf`) |
| **Âncora** | `wiki/implementation-phases/P0-fundacao-painel.md` · `wiki/adr/ADR-046-configuracoes-tipadas-por-escopo.md` |
| **Status da revisão** | ✅ revisado |

## O que foi implementado

- `Core\Internal\Settings\DatabaseSettings` implementa `Settings`: leitura de uma seção em uma
  query, cache da linha crua (`payload` + ciphertext), resolução banco → fallback `config/` →
  default do schema, valor inválido do banco ignorado + `core.settings.invalid`, escrita transacional
  com validação do schema, invalidação síncrona do cache, `updated_by`, span `core.settings.write`,
  `log_event('core.settings.updated')` e auditoria `core.settings.updated` com antes/depois
  redigidos.
- `Core\Internal\Settings\SecretPayload`: cifra os campos `secret` em `encrypted_payload`
  (`Encrypter`, sem plaintext no payload público) e mascara a leitura para tela/comando.
- `core:settings:show {section?}`: diagnóstico JSON por seção, máscara de segredo, 404/erro para
  seção desconhecida.
- `Core\Contracts\Settings::section` ganhou `bool $maskSecrets = false`; o binding é singleton no
  `CoreServiceProvider`.
- Sem cache negativo: seção inexistente é relida do banco até ser criada.

## Arquivos para revisar

- `src/core/Internal/Settings/DatabaseSettings.php`
- `src/core/Internal/Settings/SecretPayload.php`
- `src/core/Internal/Settings/Console/ShowSettingsCommand.php`
- `src/core/Contracts/Settings.php`
- `src/core/Settings/UnknownSettingsSection.php`
- `src/core/Settings/SettingsValueInvalid.php`
- `src/core/CoreServiceProvider.php`
- `src/core/Internal/Settings/Test/DatabaseSettingsTest.php`
- `src/core/Internal/Settings/Test/SecretPayloadTest.php`
- `src/core/Internal/Settings/Test/ShowSettingsCommandTest.php`

## Pontos de revisão

### PHP 8.4 / D1
- [ ] `declare(strict_types=1)`; DI no construtor; sem `new`/`app()`/`resolve()` no corpo.
- [ ] `final readonly`; exceções concretas; PHPStan `max` 0.

### Laravel 12 / D2
- [ ] Query Builder + `DB::transaction`; sem model para evitar auditoria ambiente duplicada.
- [ ] Cache via contrato `Repository`; `forget` síncrono; sem `Cache::flush()` global.
- [ ] Comando sem regra de negócio; binding singleton; sem `env()`.

### Dados / D10
- [ ] Payload público só com campos não secretos; `encrypted_payload` quando há `secret`.
- [ ] `updated_by` do ator; `schema_version` preservado; timestamps com `Clock`.
- [ ] Escrita inválida lança `SettingsValueInvalid` (422 na UI); leitura inválida ignora + loga.

### Determinismo / D3
- [ ] Nenhum `now()` no domínio (Clock injetado); ordem de campos do schema.
- [ ] Cache invalidado no mesmo request; dois requests provam.

### i18n / D8
- [ ] Nenhuma string visível de UI nesta fatia; command em pt-BR operacional.

### Segurança / D11
- [ ] Segredo não aparece em payload, cache (ciphertext), auditoria, log ou comando.
- [ ] `SecretPayload::decrypt` falha fechado (retorna `[]`) e o `resolve` loga sem valor.
- [ ] Auditoria `core.settings.updated` é explícita (persistida) e sem valores secretos.

### Observabilidade / D12
- [ ] Span `core.settings.write` com `settings.section`/`settings.keys` (sem PII).
- [ ] `log_event('core.settings.updated')` com módulo/ação/seção/contagem.

### Testes / D13
- [ ] Cache hit + invalidação na escrita; ausência de cache negativo; flush global.
- [ ] Fallback de `config/`; default; leitura inválida; escrita inválida/obrigatória.
- [ ] Auditoria + span; segredo cifrado/mascarado; comando (seção, todas, desconhecida).
- [ ] `make check` 2×; `E2E=1` fica para a UI (S3/S4).

## Comandos úteis

```bash
make check
make exec cmd='vendor/bin/phpunit src/core/Internal/Settings/Test src/core/Settings/Test'
make exec cmd='vendor/bin/phpstan analyse --no-progress --memory-limit=1G'
make exec cmd='php artisan core:settings:show institution'
make exec cmd='php artisan core:settings:show'
```

## Notas da revisão

| Data | Revisor | Resultado | Observações |
|---|---|---|---|
| 2026-10-24 | agente revisor P0-S2 | ✅ | Revisão adequada, porém **A** é dormente (nenhuma seção declara `secret`): teste renomeado; integração fica para a primeira seção secreta (P1/P13, `task_01uq1k4y`). **B corrigido:** cache aquecido antes da escrita; `forget` provado. **C:** `upsert` elimina a corrida `exists→insert`; lost update de documento inteiro fica como risco aceito (`task_01uq1k4y`). **D corrigido:** TTL de 5 min. **E corrigido:** auditoria/log dentro da transação. **F/H** latentes/baixos; **G** docs corrigidos; **I** ADR-024 confirmado. |
