# F5.I4.T2 — Catálogo `gallery_media` + `<x-media::picture>` + `alt_text` obrigatório

| Campo | Valor |
|---|---|
| **Fase** | F5 — Borda |
| **Issue** | F5.I4 — Media e Gallery (plano 19) |
| **kd task** | `task_00msrvka` |
| **Âncora** | `wiki/implementation/05-borda.md` |
| **Status da revisão** | 🔧 com correções |

## O que foi implementado

- Upload do editor **assíncrono**: `media.media_uploads` (`pendente → processando → concluido |
  falha`), `ProcessMediaUpload` na fila e `POST 202` + polling (`media.upload.status`).
- `SecureMediaStorage` grava por **stream** em quarentena (`staging/`, nunca servida) e materializa
  para a fila.
- Service **`Gallery`** (`gallery.gallery_media`, `GalleryStore`, `GalleryConfig`, `alt_text`
  obrigatório no request **e** `CHECK` no banco, `checksum`, `archive/restore` sem apagar o físico)
  + superfície `/cms/galeria` no shell CMS.
- `<x-media::picture>`: AVIF/WebP + `width`/`height`, ordem determinística, `alt` e `figcaption`.

## Arquivos para revisar

- `src/services/Gallery/{GalleryService.php,Contracts/GalleryStore.php,DTOs/GalleryConfig.php,Models/GalleryMedia.php}`
- `src/services/Gallery/{Routes/cms.php,Http/Controllers/Cms/*,Http/Requests/GalleryUploadRequest.php,View/index.blade.php,Lang/*}`
- `src/services/Gallery/Migrations/2026_10_23_130000_create_gallery_media_table.php`
- `src/services/Media/{Models/MediaUpload.php,Models/MediaBatch.php,Jobs/ProcessMediaUpload.php,Http/*}`
- `src/services/Media/View/picture.blade.php`
- `src/services/Gallery/Test/{GalleryTest,GalleryBatchTest}.php` · `src/services/Media/Test/MediaUploadTest.php`

## Pontos de revisão

### PHP 8.4 / Laravel 12
- [ ] `declare(strict_types=1)`; DI por construtor (sem `new`/`app()`/`resolve()` no corpo); PHPStan `max`.
- [ ] Nativo primeiro; sem closure em rota; `env()` só em `config/`; validação em `FormRequest`.

### Determinismo (ADR-001)
- [ ] `Clock`/`IdGen` injetados; ordem explícita; idempotência provada por teste.

### Fronteiras (ADR-013/014/015)
- [ ] Camadas respeitadas; módulo consome `Services\{Name}\{Contracts,DTOs,Enums}\*`; nada de storage fora do Media.

### HTML/a11y (ADR-025) e CSS (ADR-010)
- [ ] Semântica, rótulos, alvos; sem `!important`/inline/CDN; `oklch`/`rem`.

### i18n (ADR-012)
- [ ] Nenhuma string visível fixa — inclusive **títulos de erro lançados em PHP** (o `ModuleI18nTest` só varre views).

### Dados (ADR-007/017) e segurança (ADR-020/021/026/030/035)
- [ ] `text`/`jsonb`/`uuidv7()`; `down()` → `backup_*`; CHECKs; PII cifrada; `throttle`; MIME real; `alt` obrigatório.

### Observabilidade/auditoria (ADR-024/035)
- [ ] Span + `log_event` com o **mesmo** `{service}.{action}`; auditoria via `AuditWriter`; sem PII.

### Específicos desta task

- [ ] `alt_text` obrigatório nas 3 camadas (request, serviço, `CHECK`).
- [ ] Arquivar não apaga o físico; restaurar não redigere.
- [ ] Idempotência do catálogo (`path`+`module`) **provada por constraint**, não por leitura.
- [ ] Toda escrita no catálogo entra na trilha (`AuditWriter`) e emite span+log com o mesmo nome.
- [ ] Superfície só em `/cms` e autenticada; upload/status por dono.

## Comandos úteis

```bash
make check              # caminho único (rápido)
make check E2E=1        # + Playwright/axe/perf (2× ao fechar o escopo)
```

---

> 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 (PROTOCOLO v2.0) | 🔧 | **A** (**alto**, D11) `GET /cms/media/upload/{upload}` lia o upload de **qualquer** pessoa
(nome original, `payload`) — as rotas de lote checavam dono, esta não; corrigido com 404 (como as
demais) + teste vermelho→verde (`MediaUploadTest::another_user_cannot_read_the_upload_status`) —
`error_01t2yiow`. **B** (médio, D8) títulos de erro em pt_BR fixo (ver F5.I3.T3-A) —
`fact_01j4nvg7`. **C** (médio → `task_01v75xex`, D10) “commit idempotente” é garantido por
`SELECT` antes do `INSERT`: duas requisições concorrentes criam duas linhas — falta índice único
`(module, path)`. **D** (médio → `task_01htnzob`, D3) reentrega da fila (at-least-once) marca
`falha` um upload já `concluido` (a quarentena já foi apagada) — falta curto-circuito por status.
**E** (baixo, D10) `gallery_media.deleted_at` (SoftDeletes) nunca é usado (arquivar usa
`archived_at`) — coluna morta. **F** (baixo, D7) `figcaption` `sr-only` repete o `alt` quando não há
legenda (leitor de tela lê duas vezes). **G** (obs) `paginate()` lista arquivadas junto (sem filtro
na UI). **Verificado OK**: `alt` nas 3 camadas, arquivar preserva o físico, `AuditWriter` em todas as
escritas, superfície só em `/cms`, `<picture>` determinístico e sem `<img>` cru. |

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