# Fluxo PDV — SLO e baseline de carga

## 1. Escopo

Este documento define o contrato de performance da API do PDV e o protocolo
reproduzível de carga da **API-11.2**.

Os SLOs abaixo medem latência do backend no ambiente de referência. Eles não
incluem latência de internet do dispositivo do cliente.

O transporte WebSocket até o navegador também não está incluído no SLO de
`realtime_mutation`: este mede a mutação HTTP da comanda até o enqueue do
evento pós-commit no backend.

## 2. Orçamentos de latência

A fonte executável dos budgets é `config/pos.php`, em
`pos.performance.slo_ms`.

| Operação | P50 | P95 | P99 |
| --- | ---: | ---: | ---: |
| Finalização de venda | < 500 ms | < 1.200 ms | < 2.000 ms |
| Busca operacional | < 250 ms | < 600 ms | < 1.000 ms |
| Mutação de comanda + enqueue realtime | < 400 ms | < 900 ms | < 1.500 ms |
| Ingestão offline em lote | < 1.500 ms | < 4.000 ms | < 7.000 ms |

### Finalização de venda

Enquanto não existe endpoint público dedicado de checkout online, o cenário
público usado como proxy da finalização interna é uma operação
`sale.completed` unitária enviada a:

`POST /api/v1/pos/offline-sync`

Esse caminho atravessa validação, idempotência, estoque, pagamento, fechamento
da venda, outbox fiscal e projeção diária. Portanto o valor observado é um
limite end-to-end mais conservador que o custo isolado do service de
finalização.

### Busca operacional

O cenário de busca usa:

`GET /api/v1/wms/stock-map?search=<sku>`

A massa contém o SKU de referência e mantém o filtro dentro do read path
tenant-scoped.

### Realtime

O cenário abre uma comanda e adiciona um item. A métrica é registrada na
segunda mutação:

`POST /api/v1/pos/tabs/{tab}/items`

O request inclui optimistic locking e dispara o evento realtime pós-commit.

### Offline batch

O cenário envia múltiplas vendas offline completas em um único request para
`POST /api/v1/pos/offline-sync`.

O tamanho padrão do lote é:

- CI: 5 operações;
- smoke: 5 operações;
- baseline: 10 operações;
- stress: 20 operações.

## 3. Perfis de carga

O script canônico é:

`tests/Load/pdv-operational-paths.js`

| Perfil | Duração | Venda concorrente | Busca | Comanda/realtime | Lote offline |
| --- | ---: | ---: | ---: | ---: | ---: |
| `ci` | 20 s | 2 req/s | 3 req/s | 2 req/s | 1 req/s |
| `smoke` | 30 s | 1 req/s | 2 req/s | 1 req/s | 1 req/s |
| `baseline` | 2 min | 5 req/s | 10 req/s | 5 req/s | 2 req/s |
| `stress` | 5 min | 20 req/s | 40 req/s | 20 req/s | 5 req/s |

Os cenários usam `constant-arrival-rate`. Além dos percentis, todos exigem:

- taxa HTTP de erro inferior a 1%;
- checks funcionais acima de 99%;
- zero `dropped_iterations`.

## 4. Fixture isolada

O comando:

`php artisan pdv:load-fixture --json`

cria/reutiliza um tenant dedicado `pdv-load-lab`, operador, membership,
terminal, produto e saldo amplo para carga.

Por segurança, o comando é bloqueado fora de:

- `local`;
- `testing`;
- `staging`;
- `qa`.

O token retornado é efêmero e deve ser tratado como segredo. O workflow mascara
o JWT antes de qualquer execução do k6.

## 5. Execução manual

Pré-requisitos:

- k6 0.50+;
- banco não produtivo;
- `RoleAndPermissionSeeder` executado.

Exemplo:

```bash
php artisan pdv:load-fixture --json > /tmp/pdv-load-fixture.json

BASE_URL="https://api-staging.exemplo.com" \
API_TOKEN="<jwt>" \
TENANT_ID="1" \
OPERATOR_ID="1" \
TERMINAL_ID="1" \
PRODUCT_ID="1" \
PRODUCT_SKU="PDV-LOAD-001" \
TARGET_ENV="staging" \
LOAD_PROFILE="baseline" \
k6 run tests/Load/pdv-operational-paths.js
```

Produção permanece bloqueada. Uma execução deliberada exige
`ALLOW_PRODUCTION_LOAD_TESTS=true` e somente deve ocorrer em janela aprovada.

## 6. Baseline CI de referência

O workflow `.github/workflows/pdv-performance.yml` sobe MySQL 8, prepara uma
base limpa, cria a fixture, inicia a API com múltiplos workers PHP e executa o
perfil `ci`.

O resultado machine-readable é gravado em:

`storage/logs/pdv-k6-baseline.json`

e publicado como artifact `pdv-performance-baseline` por 14 dias.

### Baseline registrado em 2026-10-02

Execução de referência:

- workflow: `PDV Performance`;
- perfil: `ci`;
- duração: 20 segundos;
- MySQL: 8.0;
- PHP: 8.3;
- servidor de teste: `PHP_CLI_SERVER_WORKERS=8` com `--no-reload`;
- artifact: `pdv-performance-baseline`;
- ambiente: CI não produtivo.

A execução anterior do PR #34 não é considerada baseline válido porque o
`artisan serve` ignorou `PHP_CLI_SERVER_WORKERS` sem `--no-reload` e
serializou a carga em um único worker.

Resultado da execução multi-worker:

| Cenário | P50 medido | P95 medido | P99 medido | SLO P50/P95/P99 | Estado |
| --- | ---: | ---: | ---: | --- | --- |
| Finalização de venda | 1.209,19 ms | 1.874,36 ms | 1.997,20 ms | 500 / 1.200 / 2.000 ms | vermelho em P50/P95; P99 dentro |
| Busca operacional | 312,08 ms | 500,73 ms | 560,86 ms | 250 / 600 / 1.000 ms | vermelho em P50; P95/P99 dentro |
| Mutação de comanda + realtime | 259,93 ms | 1.272,55 ms | 1.658,24 ms | 400 / 900 / 1.500 ms | vermelho em P95/P99 |
| Ingestão offline em lote | 4.609,60 ms | 5.700,16 ms | 6.579,14 ms | 1.500 / 4.000 / 7.000 ms | vermelho em P50/P95; P99 dentro |

Além dos percentis, o k6 reportou threshold vermelho para checks funcionais em
finalização concorrente, mutação realtime e ingestão offline, e
`dropped_iterations` no lote offline. Isso significa que o baseline está
**medido e documentado, porém não aprovado como SLO de release**.

Os budgets não foram relaxados para tornar o job verde. Esses desvios passam a
ser evidência objetiva para otimização posterior e para os gates da
release corporativa.

## 7. Interpretação

O baseline de CI é uma referência de regressão, não uma previsão de capacidade
de produção. A aprovação de produção exige repetição do perfil `baseline` ou
`stress` em staging com cardinalidade e infraestrutura representativas.

Não aumente os SLOs apenas para tornar uma execução verde. Uma violação deve
gerar investigação sobre:

- locks e deadlocks;
- slow queries;
- saturação de workers;
- pool/conexões MySQL;
- filas realtime/fiscais;
- CPU e memória;
- cardinalidade da massa;
- `dropped_iterations`.
