ai_api / docs /ops /QUALITY_GATE_PLAYBOOK.md
Yogesh
initial deploy
cd8bd0a
|
Raw
History Blame Contribute Delete
24.3 kB
---
title: "Quality Gate Playbook"
---
# Quality-Gate System — Avaliação Crítica, Catálogo e Playbook de Replicação
> **O que é este documento.** Uma avaliação crítica do sistema de quality-gates do OmniRoute,
> comparado às melhores práticas da indústria, **mais** um catálogo completo de todos os pontos
> de qualidade e um **plano de replicação tool-agnóstico** para aplicar o mesmo sistema em
> qualquer projeto. Gerado em 2026-06-16 a partir do estado real do repositório (não da memória).
>
> Régua de comparação: OWASP DSOMM · OpenSSF Scorecard · SLSA · SonarQube "Clean as You Code" ·
> Quality-Ratchet pattern · DORA 2024 · OWASP LLM Top 10 (2025) · mutation-testing best practices.
---
## Parte 1 — Veredito e Classificação de Maturidade
**Nota geral: A− / "Avançado". Top ~5–10% de projetos.** O sistema implementa, de forma
independente, vários padrões que a indústria nomeia explicitamente — o que é o melhor sinal de
alinhamento (não copiamos uma checklist; convergimos para as práticas certas).
| Framework de referência | Onde estamos | Nota |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| **OWASP DSOMM** (5 níveis, 5 dimensões) | Nível 3 sólido, alcançando 4 em _Test Intensity_ e _Static Depth_. A maioria das orgs fica em 1–2. | **L3→L4** |
| **OpenSSF Scorecard** (18 checks) | Atendemos CI-Tests, Code-Review, Dependency-Update-Tool, Fuzzing, SAST, Signed-Releases (provenance), Token-Permissions, Vulnerabilities, Dangerous-Workflow. **Gaps:** Branch-Protection na `main` OFF; algumas actions não-pinadas. | **~7–8/10** |
| **SLSA** (4 níveis) | `npm publish --provenance` + `id-token: write` + build GitHub-hosted = **L2**, encostando em L3. Falta builder endurecido/hermético p/ L3+. | **L2→L3** |
| **SonarQube "Clean as You Code"** | Filosofia idêntica: o ratchet gateia _não-regressão_ (código novo não piora a métrica). **Divergência:** Sonar recomenda **poucas** condições; temos ~46 gates (risco de fadiga). | **Alinhado, com ressalva** |
| **Quality-Ratchet pattern** | Implementação de referência: ratchet + `dedicatedGate` + `tightenSlack` + `--require-tighten` + skip-gracioso. Mais sofisticado que a maioria dos exemplos públicos. | **Exemplar** |
| **DORA 2024** | Fortíssimos no eixo _estabilidade_. Risco: gates pesados podem custar _lead time_ — mitigado pelo split fast-gates, mas com buraco de cobertura (ver Parte 2). | **Forte (estabilidade)** |
| **OWASP LLM Top 10 (2025)** | Cobrimos o risco #1 (prompt-injection) com guard em runtime + promptfoo (eval) + garak (red-team). Ferramentas-padrão da indústria. | **Coberto** |
| **Mutation testing** | Stryker nightly, thresholds 70/50, 8 módulos críticos. Consenso da indústria (60% existente / 80% novo, nightly) — **batemos**. **Gap:** score ainda não é catraca. | **Quase lá** |
---
## Parte 2 — Avaliação Crítica (forças + fraquezas honestas)
### Forças (o que está acima da média)
1. **Motor de ratchet multi-métrica.** O coração do sistema. 24 métricas em `quality-baseline.json`
- 4 baselines dedicados, cada uma com direção (`up`/`down`), tolerância (`eps`), folga
(`tightenSlack`) e flag `dedicatedGate`. Coisas consertadas **ficam** consertadas — é o
antídoto da entropia de codebase.
2. **Defesa-em-profundidade de supply-chain.** SAST (CodeQL/Sonar) + segredos (gitleaks com
`useDefault`) + SCA (osv/npm-audit/Trivy/Dependabot) + licenças + lockfile + SBOM + proveniência
SLSA + Scorecard + hardening de workflow (zizmor). Poucas codebases têm essa pilha completa.
3. **Antídotos contra a Lei de Goodhart.** Cobertura como alvo é um anti-padrão clássico
("quando a medida vira alvo, deixa de ser boa medida"). Temos os contra-pesos: **mutation
testing** (mede se o teste pega o bug, não só se executa a linha), **`check-test-masking`**
(bloqueia enfraquecer asserts pra passar), **pisos de cobertura por-módulo** (força testar o
código de ALTO risco, não só o fácil) e **`check-pr-evidence`** (Hard Rule #18).
4. **Gates anti-alucinação / consistência.** Categoria rara e valiosa: `check-known-symbols`,
`check-fetch-targets`, `check-openapi-routes`, `check-docs-symbols` garantem que docs, specs e
dispatch por-string apontam para símbolos vivos. Pega "rot" que lint/test não pegam.
5. **Ciclo de vida advisory→bloqueante.** Gate novo entra advisory (não trava merges enquanto
amadurece), depois vira bloqueante no fim do ciclo. Reduz fricção sem perder o teto.
6. **Skip-gracioso quando a infra falta.** Scanners (`--ratchet`) saem `exit 0` se o binário/rede
falha — infra ausente nunca trava um PR legítimo. Engenharia madura.
7. **Cultura codificada.** Hard Rules + `trust-but-verify` + stale-allowlist + evidence-gate
transformam disciplina em verificação automática.
### Fraquezas honestas (gaps reais)
1. **🔴 O split fast-gates é um buraco estrutural.** `quality.yml` (PR→`release/**`) roda **só os
gates de filesystem** — sem typecheck, sem testes, sem build, sem cobertura. Uma regressão de
typecheck/teste passa num PR de release e só explode no forward-merge pra `main`. A motivação
(velocidade) é válida, mas o gate deveria estar onde o merge acontece (shift-left). **Maior
correção estrutural pendente.**
2. **🟠 Risco de sprawl/fadiga de gates.** ~46 gates + 25 jobs é MUITO. O próprio Sonar alerta:
muitas condições causam "fadiga de gate" e debate sobre prioridade, com risco de um gate
ignorado. DORA alerta que gates pesados custam lead-time. Mitigamos com tiers advisory e
ratchet-não-absoluto, mas falta um **review periódico de ROI por gate** (alguns micro-gates de
doc-sync são consolidáveis).
3. **🟠 Mutation score ainda não é catraca.** O antídoto mais forte contra coverage-gaming está
**advisory**. É o item de maior valor pendente (e já 90% construído).
4. **🟡 Advisory que deveriam bloquear (com escopo certo).** `osv` (vulnCount) e `oasdiff` são
advisory apesar de baseline congelado. osv-advisory tem razão (CVE nova em dep velha bloquearia
PR não-relacionado) — mas há meio-termo (bloquear só CRITICAL+fixable, como fizemos no Trivy).
oasdiff advisory significa que uma mudança quebra-contrato pode passar.
5. **🟡 Segurança runtime é nightly-only.** schemathesis/garak/promptfoo/chaos/k6 rodam à noite.
Decisão correta (lentos, precisam de servidor vivo), mas um PR pode introduzir regressão de
injection-guard só pega na noite seguinte.
6. **🟡 Branch-protection na `main` OFF.** O `BRANCH_LOCK_TOKEN` trava branches de _release_, mas a
`main` em si não é protegida. Ding no Scorecard/DSOMM. Ação do owner.
7. **🟡 CodeQL default-setup; semgrep não codificado.** default-setup funciona (0 alertas), mas um
`codeql.yml` commitado dá mais controle; o semgrep roda via plataforma cloud externa, não está
versionado no repo.
---
## Parte 3 — Catálogo Completo dos Pontos de Qualidade (portável)
As 12 categorias abaixo são o "sistema de qualidade" em forma reutilizável. Cada uma lista o
**objetivo** (o que proteger), as **ferramentas que usamos** e o **equivalente tool-agnóstico**
para replicar em qualquer stack.
### 1. Estilo & formatação (determinístico, rápido)
- **OmniRoute:** Prettier + ESLint via lint-staged (pre-commit), 2-espaços/aspas-duplas/100col.
- **Genérico:** um formatter auto-fixável + um linter, rodando em pre-commit nos arquivos staged.
### 2. Tipos
- **OmniRoute:** `typecheck:core` (bloqueante) + `typecheck:noimplicit:core` (advisory) + `type-coverage` ratchet 92.17% + any-budget por-arquivo.
- **Genérico:** typecheck estrito no CI + métrica de cobertura-de-tipo ratcheteada + orçamento de `any`/escape-hatches por-arquivo.
### 3. Testes (intensidade)
- **OmniRoute:** 2 runners não-sobrepostos (Node native + vitest), 8 shards, cobertura global 60/60/60/60 + ratchet ~76% + **8 pisos por-módulo crítico** + testes de propriedade nightly + **mutation testing** nightly.
- **Genérico:** runner(s) de teste + piso de cobertura **absoluto** (anti-zero) + **ratchet** de cobertura (anti-regressão) + **pisos por-módulo de alto risco** (anti-Goodhart) + property-based para lógica pura + **mutation testing** nightly como medida real de qualidade-de-teste.
### 4. Política de testes (anti-gaming)
- **OmniRoute:** `pr-test-policy` (código de prod exige teste), `check-test-masking` (bloqueia enfraquecer asserts), `pr-evidence` (claim de sucesso exige bloco de evidência), `test-discovery` (todo teste coletado por um runner).
- **Genérico:** gate "código novo ⇒ teste novo" + detector de assert-removido/tautologia + exigência de evidência (TDD ou teste-vivo) + garantia de que nenhum teste fica órfão fora dos globs.
### 5. Complexidade & saúde de código (ratchets)
- **OmniRoute:** ESLint-warnings (3769↓), duplicação jscpd (5.72%↓), complexidade ciclomática+max-lines (1800↓), complexidade cognitiva sonarjs (753↓), dead-code/unused-exports knip (339↓), file-size por-arquivo (frozen, só-encolhe), circular-deps (Tarjan próprio, bloqueante).
- **Genérico:** ratchetear toda métrica de saúde (warnings, duplicação, complexidade ciclomática **e** cognitiva, código-morto, tamanho-de-arquivo, ciclos de import). Direção sempre "não-piorar".
### 6. Segurança estática (SAST + segredos)
- **OmniRoute:** CodeQL (ratchet de alertas = 0), gitleaks (`[extend] useDefault=true` — crítico!), SonarQube, regras de segurança próprias (public-creds, error-helper, route-guard-membership, route-validation).
- **Genérico:** SAST (CodeQL/Sonar/semgrep) com ratchet-de-alertas + scanner de segredos com **ruleset default herdado** (config custom que substitui o default = cego) + gates próprios para as Hard Rules de segurança do projeto.
### 7. Supply-chain (dependências)
- **OmniRoute:** osv-scanner + npm-audit + Trivy + Dependabot (SCA), license-checker (SPDX allowlist), lockfile-lint (HTTPS+sha512+registry), `check-deps` anti-slopsquatting (allowlist + idade ≥72h).
- **Genérico:** SCA multi-fonte + allowlist de licenças + verificação de integridade de lockfile + allowlist de dependências com checagem de idade/typosquatting + bot de atualização agrupado.
### 8. Supply-chain (build & release)
- **OmniRoute:** SBOM (CycloneDX + syft), proveniência SLSA (`--provenance`), OpenSSF Scorecard (weekly), hardening de workflow (zizmor: artipacked→`persist-credentials:false`, cache-poisoning, token-permissions).
- **Genérico:** gerar SBOM no publish + proveniência assinada (SLSA L2+) + Scorecard agendado + endurecer todos os workflows (mínimo-privilégio de token, sem credencial persistida em checkout não-pusher, actions pinadas por SHA).
### 9. Contratos & API
- **OmniRoute:** oasdiff (breaking-change OpenAPI), schemathesis (fuzz de contrato nightly), openapi-coverage (% rotas documentadas, ratchet 38.3%), openapi-security-tiers (spec vs route-guard).
- **Genérico:** diff de breaking-change do contrato (oasdiff/buf) + fuzz property-based contra o spec (schemathesis) + cobertura-de-documentação ratcheteada + consistência spec↔código.
### 10. Docs & i18n (anti-rot)
- **OmniRoute:** docs-sync (versões espelhadas), docs-counts-sync (números nos docs vs código), env-doc-sync, doc-links, fabricated-docs, cli-i18n, i18n-ui-coverage (`--threshold=65` + ratchet 80.1%).
- **Genérico:** sincronizar versões/contagens/env-vars entre docs e código (gate, não confiança) + validar links internos + cobertura de i18n ratcheteada.
### 11. Anti-alucinação / consistência (a categoria rara)
- **OmniRoute:** known-symbols (dispatch por-string ⇒ símbolo vivo), provider-consistency, fetch-targets (fetch cliente ⇒ rota real), docs-symbols, db-rules (Hard Rules #2/#5), migration-numbering.
- **Genérico:** para toda "fonte de verdade duplicada" (registry, dispatch por-string, referências cross-camada), um gate que prova que os dois lados batem. Pega o rot que typecheck/test não pegam.
### 12. Resiliência & domínio (específico do produto)
- **OmniRoute:** chaos (fault-injection), heap-growth (leak), k6 (soak), promptfoo+garak (LLM red-team OWASP LLM Top 10), as 3 leis de resiliência (circuit-breaker/cooldown/lockout).
- **Genérico:** identificar os modos-de-falha do **seu** domínio e ter um gate (ainda que nightly) para cada um. Para apps de IA: red-team de injeção. Para sistemas distribuídos: chaos + leak + soak.
---
## Parte 4 — Plano de Replicação em Qualquer Projeto
Construa em **fases**, cada uma entregando valor sozinha. Não tente as 12 categorias de uma vez —
isso causa exatamente a fadiga de gate que a Parte 2 alerta. Cada gate novo entra **advisory** e
vira **bloqueante** quando estável.
### A peça central reutilizável: a "anatomia de um gate de ratchet"
Todo o sistema gira em torno deste padrão de 3 arquivos. Copie-o primeiro:
1. **`baseline.json`** — o valor congelado da métrica + `direction` (`up`/`down`) + `eps` (anti-flake) + `tightenSlack` + `dedicatedGate`.
2. **`collect-metrics.<ext>`** — roda a ferramenta, extrai o número, escreve `metrics.json`.
3. **`check-ratchet.<ext>`** — compara `metrics.json` vs `baseline.json`; `exit 1` **só** se regrediu além de `eps`; `exit 0` (skip-gracioso) se a ferramenta/infra faltou; com `--require-tighten`, `exit 1` se **melhorou** sem atualizar o baseline (trava o ganho).
Com isso pronto, **toda** métrica nova (cobertura, complexidade, warnings, alertas SAST, tamanho de bundle, mutation score…) é só uma linha no baseline.
### Fase 0 — Fundação (semana 1)
CI existe; formatter + linter + typecheck + 1 runner de teste + piso de cobertura **absoluto**
(ex.: 60%). Pre-commit roda os checks rápidos auto-fixáveis. _Saída: nenhum PR entra quebrando o básico._
### Fase 1 — O motor de ratchet (semana 2) — **a fundação de tudo**
Implemente os 3 arquivos acima. Congele baselines de: warnings, cobertura, complexidade, duplicação,
código-morto, tamanho-de-arquivo. _Saída: a codebase só pode melhorar dali pra frente._
### Fase 2 — Profundidade estática (semana 3)
SAST (CodeQL/Sonar/semgrep) com ratchet-de-alertas; scanner de segredos (**herde o ruleset default**);
SCA (osv/Dependabot) + allowlist de licenças + lockfile-lint. _Saída: vulnerabilidade conhecida e
segredo vazado não passam._
### Fase 3 — Supply-chain de build (semana 4)
SBOM no publish + proveniência assinada (SLSA L2) + Scorecard agendado + hardening de workflow
(zizmor: token mínimo, sem credencial persistida, actions pinadas). _Saída: release rastreável e
à prova de adulteração._
### Fase 4 — Intensidade de teste (semana 5–6)
2º runner se útil; **pisos de cobertura por-módulo crítico** (anti-Goodhart); property-based para
lógica pura; **mutation testing nightly** → quando der o 1º score, vire catraca `mutationScore`.
_Saída: cobertura deixa de ser vanity-metric; testes provadamente pegam bugs._
### Fase 5 — Contrato & dinâmico (semana 7)
Se há API pública: oasdiff (breaking-change, **bloqueante**) + schemathesis (fuzz nightly). DAST/
red-team nightly conforme o domínio. _Saída: contrato não quebra em silêncio._
### Fase 6 — Anti-alucinação & domínio (semana 8)
Um gate de consistência para cada "verdade duplicada" do projeto. Gates de modo-de-falha do seu
domínio (para IA: red-team de injeção). _Saída: rot estrutural e falhas de domínio têm rede._
### Fase 7 — Governança (contínuo)
- Ciclo advisory→bloqueante para cada gate novo.
- `stale-allowlist`: toda supressão tem justificativa + issue; supressão obsoleta é pega.
- `evidence-gate`: claim de sucesso em PR exige prova (teste ou teste-vivo).
- **Review trimestral de ROI por gate** (mate/funda os que não pagam o custo — combate a fadiga).
- Mature os Hard Rules do projeto em gates executáveis.
### Princípios transversais (não-negociáveis)
- **Ratchet, não absoluto.** Gateie _não-regressão_, não um número fixo (exceto pisos anti-zero).
- **Piso absoluto + ratchet juntos.** O piso impede o colapso; o ratchet impede a erosão lenta.
- **Anti-Goodhart por design.** Toda métrica-alvo precisa de um contra-peso (cobertura ⇒ mutation + anti-masking; pisos por-módulo p/ forçar o código difícil).
- **Skip-gracioso.** Infra ausente nunca bloqueia; só regressão real bloqueia.
- **`dedicatedGate` para métricas caras.** Métrica que precisa de binário externo tem seu próprio script (com skip), fora do ratchet central síncrono.
- **Gate onde o merge acontece.** Não deixe buraco entre o gate-rápido e o merge real (a lição do split fast-gates).
- **Poucos gates bloqueantes, bem-escolhidos.** Sonar/DORA: muitas condições = fadiga. Prefira advisory + ratchet a um muro de gates bloqueantes.
---
## Parte 5 — Melhorias recomendadas (priorizadas, compatíveis)
**P0 — maior ROI, já quase prontas**
1. **Catraca de mutation score** (após 1º nightly Stryker dar valores). Antídoto-chave contra coverage-Goodhart; ~90% pronto.
2. **Fechar o buraco fast-gates** — adicionar typecheck + testes-impactados ao `quality.yml` (PR→release).
3. **Branch-protection na `main`** (setting do owner) — sobe Scorecard, fecha o gap DSOMM.
**P1 — valiosas** 4. **osv/oasdiff → bloqueante com escopo certo** — osv só CRITICAL+fixable (two-step como o Trivy); oasdiff bloqueia breaking-change. 5. **`require-tighten` → bloqueante** (fim de ciclo) — trava ganhos de métrica. 6. **Review de ROI / timing por-gate** no `ci-summary` — achar e podar gates lentos/de-baixo-valor.
**P2 — diminishing returns** 7. **SLSA L3** — builder hermético/reprodutível (gerador SLSA do GitHub) se quiser subir de L2. 8. **CodeQL config commitado + semgrep versionado** — mais controle/reprodutibilidade. 9. **DAST smoke por-PR** — subconjunto rápido de schemathesis/promptfoo nos endpoints de maior risco (não só nightly). 10. **Dashboard de flakiness + métricas DORA** — garantir que os gates não erodem a velocidade.
---
## Parte 6 — Lições concretas de release (gates a adicionar na Fase 9)
> Esta parte registra incidentes reais de fechamento de release onde um gate **faltou**,
> com a evidência concreta e o gate proposto. Cada item é candidato a entrar na Parte 5.
### Lição v3.8.27 (2026-06-17) — o "buraco fast-gates" deixa regressões determinísticas chegarem ao release-day
**O que aconteceu.** No `/generate-release` da v3.8.27, o PR de release (`release/v3.8.27``main`)
foi a **primeira** execução da matriz completa do `ci.yml` no ciclo integrado. Resultado: 12 falhas
de uma vez — **3 testes determinísticos** + ~9 flakes/env. Nenhuma era regressão de produto viva, mas
todas tinham passado despercebidas porque os PRs do ciclo entram em `release/**` pelo **Fast QG
(`quality.yml`)**, que NÃO roda a suíte unitária completa, nem `pr-test-policy` (test-masking), nem a
integração completa, nem checagem de paridade de schema. As 3 determinísticas:
1. **Teste defasado por mudança de UI**`permissions modal switch buttons declare button type`:
#4034 adicionou um 4º switch (a11y `type="button"` mantida); a contagem `=== 3` do teste ficou
defasada. Estático, deveria ter sido pego no PR do #4034.
2. **Teste defasado por mudança de packaging**`findMissingArtifactPaths ... root runtime files`:
`dist/http-method-guard.cjs` virou required-path legítimo; a lista esperada do teste ficou defasada.
3. **Divergência de modularização lossy (a mais séria)** — `settings schemas accept ... unprefixed
toggle`: o `updateSettingsSchema` **modularizado** (`schemas/settings.ts`, criado por #3988) divergiu
do canônico (`settingsSchemas.ts`): **45 campos vs 85 — 40 dropados + 6 divergentes (qdrant\*)**. Era
**dead-code** (runtime usa o canônico), então sem impacto vivo, mas só um teste de paridade
hand-written pegou. O #4030 restaurou 16 drops análogos do #3988/#3993, mas este passou.
**Gates propostos (Fase 9):**
- **G1 — Fechar o buraco fast-gates de verdade (estende P0 #2).** No `quality.yml` (PR→`release/**`),
além de typecheck + testes-impactados, rodar **`pr-test-policy` (test-masking) + a suíte unitária
determinística completa** (ou ao menos os arquivos estáticos/parity, que são rápidos e não-flaky).
Assim, teste-defasado e remoção-de-assert são pegos no PR que os introduz — não no release-day.
Manter integração/e2e fora (lentos/flaky), mas a camada determinística NÃO pode ficar só no PR→main.
- **G2 — Gate de paridade de modularização (NOVO, não coberto hoje).** Um check que, para cada símbolo
re-exportado por um barrel modularizado (`src/shared/validation/schemas/*`, `providerRegistry`
módulos, etc.), compara o **shape** (chaves do `z.object`, entries do registry) contra a fonte
canônica e **falha em divergência** (campo dropado/extra). Teria pego o drop de 40 campos do #3988 no
próprio PR. Generaliza os testes de paridade hand-written (que só existem onde alguém lembrou de
escrever). Barato: importa os dois e diffa `Object.keys(shape)`.
- **G3 — Triagem de flakes determinística (suporte).** LiveWS-startup e os integration-combo/breaker
falham por timeout/cascade de servidor em CI (env), não por lógica. Marcar esses como
`known-flaky` (quarentena com issue) para o vermelho do release-PR ser **só sinal real**, não ruído
que mascara regressões determinísticas no meio.
**Princípio:** _o gate tem que rodar onde o merge acontece_ (já está em "Princípios transversais"). A
v3.8.27 mostra que isso vale também para a **camada determinística de testes**, não só lint/typecheck —
senão o débito de teste-defasado + modularização-lossy só aparece no PR→main, em lote, no pior momento.
---
## Fontes (boas práticas da indústria)
- OWASP DevSecOps Maturity Model (DSOMM) — https://dsomm.owasp.org/about
- OpenSSF Scorecard / SLSA — https://openssf.org · https://slsa.dev
- SonarQube "Clean as You Code" — https://docs.sonarsource.com/sonarqube-server/latest/user-guide/clean-as-you-code
- Quality Ratchets (LeadDev) — https://leaddev.com/software-quality/introducing-quality-ratchets-tool-managing-complex-systems
- Continuous Code Improvement Using Ratcheting (Greiner) — https://robertgreiner.com/continuous-code-improvement-using-ratcheting/
- DORA 2024 State of DevOps — https://cloud.google.com/blog/products/devops-sre/announcing-the-2024-dora-report
- Mutation testing best practices (Stryker) — https://stryker-mutator.io
- Coverage como anti-padrão (Goodhart) — https://www.industriallogic.com/blog/code-coverage-complications/
- OWASP Top 10 for LLM Applications (2025) — https://owasp.org/www-project-top-10-for-large-language-model-applications/
- Contract testing (oasdiff/schemathesis) — https://www.oasdiff.com · https://schemathesis.readthedocs.io