# F5.I4.T4 (F5.I4.b) — Lote de 20–50 imagens com orçamento e cota

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

## O que foi implementado

- `media.media_batches` (dono, `expected_files`, cap 50 arquivos / 250 MiB / 24 h) +
  `media_uploads.batch_id`/`user_id`; reserva atômica (**advisory lock** por usuário + row lock do
  lote) **antes** de publicar em `staging/`.
- Cota **2 GiB/dia** (bytes + arquivos) e rate `media-upload` 120/min; cliente com concorrência 3 e
  `alt` por imagem na grade; commit do Gallery idempotente por `path`, só `concluido`, com falhas
  parciais reportadas.
- E2E `15-cms-galeria-lote` (3 imagens, uma por requisição) + PHPUnit com **50 arquivos**.

## Arquivos para revisar

- `src/services/Media/Actions/{CreateMediaBatch,CreateMediaUpload}.php`
- `src/services/Media/Http/{MediaBatchController,MediaBatchStatusController,MediaBatchCompleteController}.php`
- `src/services/Media/Http/Requests/MediaBatchRequest.php`
- `src/services/Media/Migrations/2026_10_23_1500{00,10}_*.php`
- `src/services/Gallery/Http/Controllers/Cms/GalleryBatchCommitController.php`
- `src/services/Gallery/Assets/js/batch.js` · `View/index.blade.php`
- `src/services/Media/Test/MediaBatchTest.php` · `src/services/Gallery/Test/GalleryBatchTest.php`
- `src/tests-e2e/{tests,histories}/15-cms-galeria-lote.*`

## 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

- [ ] 20–50 imagens **nunca** num único multipart (N requisições de 1 arquivo).
- [ ] Orçamento por lote e cota diária reservados atomicamente (sem corrida entre abas).
- [ ] Dono do lote verificado em todas as rotas (`404` para terceiros).
- [ ] Commit idempotente e com falhas parciais; `alt` por imagem obrigatório.
- [ ] Borda/PHP aceitam o maior arquivo do lote (2–3 MB por imagem).

## 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** (médio, D8) mensagens de lote em pt_BR fixo nos 4 pontos (ações + controller do
commit) — ver F5.I3.T3-A (`fact_01j4nvg7`). **B** (alto, D13) o e2e **não conseguia** rodar no
caminho dourado: `make check` não aplicava migrações pendentes e `/galeria` respondia `500`
(`relation "gallery.gallery_media" does not exist`) — corrigido com passo `prep:migrate` no
`tools/check.sh` + gate (`CheckPipelineTest::the_golden_path_applies_pending_migrations_before_testing`)
— `fact_01jvjp28`. **C** (médio → `task_01v75xex`) idempotência do commit sem índice único.
**D** (médio → `task_01htnzob`) reentrega do job sobrescreve `concluido` com `falha`.
**E** (baixo, D1) `ProcessMediaUpload::failed()` usa `app(MediaStorageInterface::class)` no corpo
(o `failed()` do Laravel não injeta — dívida registrada em `task_01htnzob`). **Verificado OK**:
reserva atômica, cota com `Retry-After`, 50 arquivos em 50 requisições, dono do lote (404),
concorrência 3 no cliente, `alt` por imagem, e2e com axe na grade e no resultado. |

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