Files
OmniRoute/docs/ops/QUALITY_GATE_PLAYBOOK.md
Diego Rodrigues de Sa e Souza fa367dd99e Release v3.8.27 (#3968)
* chore(release): open v3.8.27 development cycle

* fix(security): polynomial ReDoS in comboAgentMiddleware regex (#3982)

* fix(security): eliminate polynomial ReDoS in comboAgentMiddleware <omniModel> regex (CodeQL js/polynomial-redos)

CACHE_TAG_PATTERN wrapped the tag in an unbounded `(?:\\n|\n|\r)*` prefix/suffix.
On an unanchored `.test()`/`.exec()` that is O(n²) on inputs with many newlines
(CodeQL js/polynomial-redos, alerts #612/#613). The surrounding runs are irrelevant
to detecting/capturing the tag, so the detection pattern now matches only the core
`<omniModel>([^<]+)</omniModel>`; the global strip pattern still consumes the
wrapping newlines (combo.ts streaming, #531) but BOUNDED ({0,16}) so it stays linear.

Behavior preserved: detection, model extraction, multi-tag stripping (#454) and
blank-line cleanup all unchanged (107 related tests green). Adds ReDoS-safety
regression tests (50k-newline inputs complete in <1ms).

* docs(changelog): add #3982 ReDoS fix to [3.8.27]

* ci(security): harden workflows — artipacked persist-credentials + cache-poisoning + SC2086 (#3965)

* Refine provider quota card display (#3969)

Integrated into release/v3.8.27

* feat: add sidebar group separator toggles (#3971)

Integrated into release/v3.8.27

* Gate control-plane proxy direct fallback (#3963)

Integrated into release/v3.8.27

* Capture actual upstream provider requests (#3941)

Integrated into release/v3.8.27

* ci(quality): flip require-tighten + osv + Trivy to blocking (v3.8.27 cycle-end) (#3984)

* fix(resilience): respect connection cooldown stored as numeric epoch (#3954) (#3995)

rate_limited_until is a TEXT column, but setConnectionRateLimitUntil (Antigravity full-quota path) persists a raw epoch number that SQLite coerces to a numeric string ("1781696905131.0"). The selection predicate isAccountUnavailable then did new Date("1781696905131.0") -> NaN, so the cooling connection was never skipped and the router kept dispatching to rate-limited accounts. Normalize numeric-epoch strings (and number/Date/ISO) via a shared cooldownUntilMs() helper in isAccountUnavailable / getEarliestRateLimitedUntil / filterAvailableAccounts / parseFutureDateMs. ISO behavior preserved.

* fix(providers): fetch live /models for LLM7 and BytePlus (#3976) (#3996)

llm7 and byteplus carry a real modelsUrl but were not classified by any live-fetch branch of the model-import route, so their hardcoded 4-entry registry catalog was served (source local_catalog) instead of the upstream catalog. Add both to NAMED_OPENAI_STYLE_PROVIDERS so the route probes <baseUrl>/models and serves the live list, falling back to the local catalog only on fetch failure.

* fix(dashboard): logs auto-refresh reads live visibility, not a stale mount ref (#3972) (#3997)

The auto-refresh interval gated each tick on visibleRef, seeded once at mount and updated only by a visibilitychange event. A tab mounted while document.visibilityState is 'hidden' (background load, bfcache, embedded/proxied webviews) with no later visibilitychange left the ref false forever, so the interval ticked but never fetched — only the manual button worked. Read the live document.visibilityState in the tick instead.

* feat(compression): add Indonesian caveman rules and language pack (#3975)

Integrated into release/v3.8.27

(cherry picked from commit c9b5b1a892)

* fix(combo): shuffle strict-random fallback remainder to spread load (#3959) (#3998)

strict-random shuffled only the deck-selected slot 0 and left the fallback remainder in fixed priority order, so after a failing deck pick the chain always fell through to the same top-priority model — a persistently-failing model was retried on essentially every request and fallback load never spread across peers. Shuffle the remainder too (like the random strategy).

* Add provider auth visibility controls (#3953)

Integrated into release/v3.8.27

* fix(claude): forward client tool-search-tool anthropic-beta on the Claude OAuth path (#3974) (#3999)

The client-negotiated anthropic-beta: tool-search-tool-2025-10-19 was dropped on both Claude code paths (default executor rebuilt from static ANTHROPIC_BETA_CLAUDE_OAUTH; selectBetaFlags only read the client beta to gate thinking/effort), so claude.ai rejected deferred-tool requests with 400 'Tool reference not found'. Add an allowlist-merge (mergeClientAnthropicBeta) that unions the client's allowlisted betas into the outbound set on both paths, preserving #3415 (no forced thinking/effort).

* feat(providers): add model search filter to provider dashboard (#3950)

Integrated into release/v3.8.27

* fix(vision-bridge): force bridge for tokenrouter deepseek models (#3946)

Integrated into release/v3.8.27

* fix(executor): strip stream_options on non-streaming requests (#3884) (#4000)

Clients that send stream_options:{include_usage:true} regardless of stream (e.g. the OpenAI Python SDK) had it passed through on non-streaming calls; NVIDIA NIM rejected it with 400 'Stream options can only be defined when stream=True'. DefaultExecutor.transformRequest only injected/cleared stream_options on the streaming branch and never stripped a client-sent value when stream=false. Add a !stream strip branch; the streaming injection path is unchanged. Global to openai-compat providers.

* fix(qwen-web): cookie validation false-positive - check response body for user object (#3958)

Integrated into release/v3.8.27

* fix(db): persist backup retention days (#3970)

Integrated into release/v3.8.27

* 大量UI显示和i18n优化 (#3973)

Integrated into release/v3.8.27

* deps: bump the npm_and_yarn group across 1 directory with 2 updates (#3943)

Integrated into release/v3.8.27

* deps: bump form-data from 4.0.5 to 4.0.6 (#3944)

Integrated into release/v3.8.27

* deps: bump vite from 8.0.5 to 8.0.16 (#3942)

Integrated into release/v3.8.27

* chore(quality): re-baseline validation.ts 4407->4428 (#3958 qwen body-check)

The qwen-web validation body-check merged in #3958 pushed validation.ts past its
frozen size on the integrated release tip. Bump the baseline with justification;
no logic is separately extractable from the existing qwen-web validation branch.

* deps: bump the production group with 13 updates (#3915)

Integrated into release/v3.8.27 — low-risk group (playwright 1.60→1.61 minor + transitive patches; fumadocs-core 16.9→16.10 minor).

* chore(deps): ignore jscpd major bumps (v5 Rust rewrite breaks the duplication gate)

Our duplication ratchet (scripts/check/check-duplication.mjs) is pinned to jscpd@4
and parses jscpd-report.json against a frozen baseline. jscpd v5 is a native Rust
binary with no Node.js API and a different report/bin, so a major bump would break
the gate. Migrate deliberately, not via dependabot. Closes the noise from #3916.

* fix(perplexity-web): parse schematized diff_block stream so answers aren't empty (#4001)

Integrated into release/v3.8.27 — schematized diff_block parsing follow-up to #3938.

* refactor: modularize providerRegistry.ts into 159 individual provider plugins (#3993)

Modularize provider registry (#3594). Integrated into release/v3.8.27 after rebase + behavior-preservation verification (provider-consistency gate 159/232/0, typecheck, registry tests, build 556/556).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(registry): restore byteplus + mimocode dropped by #3993 modularization

The provider-registry modularization (#3993) was cut from a base predating the
byteplus (#3877) and mimocode (#3837) registry entries, so merging it silently
dropped both providers (getRegistryEntry returned undefined → validation reported
'not supported'). Re-add them as registry modules in the new structure; registered
count 159→161, provider-consistency 161/232/0.

Also align the pre-existing qwen-web validator test to #3958: since the validator
now requires a real `user` object in the 200 body, the mock must carry one.

* refactor: modularize schemas (non-stacked) (#3988)

Modularize validation schemas (#3594). Integrated into release/v3.8.27 after rebase (reconciled the merged hiddenSidebarGroupLabels #3971 + intelligenceSyncRequestSchema into the new modules) + behavior verification (typecheck, 195 schema/settings/validation tests, build 556/556).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(default-executor): honor custom providerSpecificData.baseUrl for OpenAI-format providers (#4002)

Integrated into release/v3.8.27 — honor custom providerSpecificData.baseUrl in DefaultExecutor (openai-format), tested.

* feat(openai): honor custom base URL in model discovery + complete openai/codex pricing (#4005)

Integrated into release/v3.8.27 — openai model-discovery honors custom base URL (SSRF-guarded) + pricing rows for new openai/codex models. Tested + baselines bumped.

* fix(live-ws): bridge sidecar events to dashboard (#4004)

Integrated into release/v3.8.27 — repair LiveWS sidecar (startup, same-origin /live-ws, main→sidecar compression.completed bridge, early-msg queue). Fixed the cookie-parse regex (\s) + added a focused unit test; baseline bumped for the non-blocking chatCore bridge.

* docs(troubleshooting): note MITM proxy cannot intercept Windows-host apps under WSL (#4003)

Integrated into release/v3.8.27 — MITM/WSL troubleshooting note.

* fix(repo): untrack accidentally-committed root node_modules symlink + gitignore it

A worktree node_modules symlink (-> the main checkout's node_modules) was staged by a
`git add -A` during the #3988 merge and committed into 05213ac6a. The symlink points
at the repo's own node_modules path, so checking it out turns the main checkout's
node_modules into a self-referential symlink (breaking tsx/all node ops). Untrack it and
add a root-anchored /node_modules ignore so the symlink form can't be re-committed (the
existing 'node_modules/' only matches directories).

* fix(quality): allowlist socks dep (declared by #4004, never allowlisted)

socks@^2.8.7 was added to package.json in #4004 (LiveWS sidecar, 02302131f)
as a phantom-dep cleanup but never added to dependency-allowlist.json, so
check:deps has been red on the release tip ever since. socks is the standard
SOCKS proxy client (dep of fetch-socks), legitimate and years old.

* feat(sse): real LLMLingua-2 ONNX compression engine (stable) (#4014)

Integrated into release/v3.8.27.

Adjustments before merge:
- Synced with the current release tip (was 11 commits behind).
- Added the 3 LLMLingua-2 ONNX optional-runtime deps to dependency-allowlist.json
  (@atjsh/llmlingua-2, @tensorflow/tfjs, js-tiktoken) — the only gate that was red.
- socks was allowlisted directly on release (separate fix d7db5c73d; it was declared
  by #4004 but never allowlisted, leaving check:deps red release-wide).

Verified locally: check:deps OK, file-size OK, public-creds OK, provider-consistency
161/232/0, typecheck:core clean, 24/24 LLMLingua tests pass. The only remaining Fast-QG
red is the pre-existing #3972 orphan test (request-logger-autorefresh-visibility-3972.test.tsx),
which is release-wide and unrelated to this PR.

* test(dashboard): rehome #3972 logs auto-refresh test so a runner collects it

tests/unit/request-logger-autorefresh-visibility-3972.test.tsx (added by #3972
via #3997) sat at the top level of tests/unit/ as a .tsx vitest test, which NO
runner collects: the node runner only globs *.test.ts, and test:vitest:ui only
runs tests/unit/ui. So the #3972 regression guard never executed in CI and
check:test-discovery was red release-wide. Move it under tests/unit/ui/ (the
collected vitest:ui path) and fix the relative import depth. Verified: the test
now runs and passes (2/2), and check:test-discovery is green.

* feat(compression): capture per-engine analytics (#3960) + Lite schema fix (#3952) (#4018)

Captures the net-new value from #3960 (per-engine breakdown analytics) and #3952 (Lite engine schema fix) onto release/v3.8.27. Fast QG green; 622/622 compression+analytics tests pass.

* fix(sse): guard model-less registry entries in getUnsupportedParams (mimocode) (#4015)

Real bugfix: guard model-less registry entries (mimocode) in getUnsupportedParams so handleChatCore no longer throws 'entry.models is not iterable' / reports 'All models failed' for unrelated requests. Includes a regression test. Fast QG green.

* feat(ci): Quality Gate v2 — Onda 0 + Onda 1 (gate flips, TIA, SAST, DAST-smoke, mutation infra) (#4016)

* docs(ops): add quality-gate assessment + replication playbook (Fase 9 foundation)

* feat(ci): flip oasdiff breaking-change gate to blocking (ratchet)

* docs(ops): deliver main branch-protection ruleset for owner to apply

* fix(ci): run typecheck:core in PR->release fast-gates (close fast-gates hole, part 1)

* perf(mutation): enable Stryker incremental mode + cache (scales the 60/80 rollout)

* feat(ci): commit CodeQL advanced config (security-extended), replacing default-setup

* feat(ci): version semgrep SAST workflow (owasp/secrets), advisory

* feat(quality): TIA test-impact map builder (import-graph; map built at runtime, gitignored)

* feat(quality): TIA impacted-test selector with run-all fail-safe

* fix(ci): run TIA-impacted unit tests in PR->release fast-gates (build map at runtime, fail-safe full)

* feat(ci): DAST-smoke per-PR (schemathesis subset + promptfoo injection-guard, blocking)

* fix(ci): unbreak Fase 9 PR CI (MDX frontmatter, CodeQL conflict, dast-smoke advisory)

- Add MDX frontmatter to docs/ops/{BRANCH_PROTECTION_MAIN,QUALITY_GATE_PLAYBOOK}.md.
  fumadocs rejects frontmatter-less docs -> 'npm run build' failed -> broke dast-smoke's
  build step (the release fast-gates never runs build, so this only surfaced on the PR).
- codeql.yml: workflow_dispatch-only until the owner switches repo CodeQL Default->Advanced
  (advanced configs cannot be processed while default setup is enabled; documented inline).
- dast-smoke.yml: job-level continue-on-error (advisory) so this brand-new gate matures
  before it blocks (repo convention: advisory -> blocking).

* ci(quality): make TIA unit-test step advisory until release test-debt is cleared

release/v3.8.27 carries ~17 pre-existing failing unit tests (budget #3537, apiKey
#3552, several Zod schemas, Puter/Qwen executors, mimocode entry, etc.) unrelated to
this PR — the new 'run tests on PR->release' gate surfaced them. Per the repo's
advisory->blocking convention, this step enters advisory (it still runs + reports)
so pre-existing debt doesn't block the gate program. typecheck:core stays blocking.
Flip to blocking (remove continue-on-error) once the release suite is green.

* fix(sse): preserve Kiro streaming finish_reason tool_calls (#3980) (#4025)

* fix(guardrails): preserve original image when vision-bridge describe fails (#4012) (#4026)

* feat(api): advertise combo capabilities on import surfaces (#3979) (#4027)

* feat(sse): delegated Anthropic Context Editing for Claude (clear_tool_uses) (#4021)

Opt-in Claude-only delegated compression: injects context_management.clear_tool_uses_20250919 at the Claude pre-serialization chokepoint (composes with clear_thinking, thinking first), threaded via ExecuteInput from handleChatCore. Pure edit-builder + 11 tests (7 unit + 4 e2e fetch-capture). Beta context-management-2025-06-27 already advertised; allowlist done. Telemetry/400-fallback/claude-web coverage deferred.

* fix(opencode): map x-session-affinity to x-opencode-session for custom providers (#4022) (#4028)

* fix(dashboard): Playground Compare tab loading + HTTP method guard (#4024)

randomUUID non-HTTPS fallback + static CompareTab import; raw HTTP TRACE->405 method guard wired into dev + standalone servers. Integrated into release/v3.8.27.

* refactor(dashboard): settings UI layout + API Keys naming (#4020)

Presentation/relabel refactor of the Settings dashboard (API Manager -> API Keys), card relocations, Toggle adoption, present-but-disabled engine steps. Auth-file changes are string/comment-only (no behavior change). Integrated into release/v3.8.27.

* fix: restore unit regressions dropped by lossy schema/registry modularizations (#4030)

Restores schema fields (combo reasoningTokenBuffer, budget-0 #3537, openrouter preset, proxy family #3777, resilience degradation/providerCooldown), qwen-web v2 endpoint+catalog, mimocode models key — all dropped by #3988/#3993 — and aligns 3 tests to #3941/#3993. Verified: 8 failing regression tests on release tip -> 131/131 green on this branch. Integrated into release/v3.8.27.

* fix(api): return 400 (not 500) for malformed JSON on /api/auth/login (#4031)

Wrap request.json() so a malformed/non-JSON login body returns a structured 400 instead of falling through to the 500 catch. Fixes the schemathesis high-risk-endpoint DAST finding (verified: schemathesis step now passes). +TDD test. Integrated into release/v3.8.27.

* feat(dashboard): real circuit-breaker state in the Combo Live cascade (U1b) (#4029)

Overlays real provider circuit-breaker state (GET /api/monitoring/health) onto the Combo Live cascade as a 'CB: OPEN · 41s' badge. Pure enrichRunWithBreakers + fail-soft useProviderBreakerHealth poll; graceful when health is absent. +13 tests. Integrated into release/v3.8.27.

* Fix promptfoo security assertion parsing (#4032)

* chore(deps): dependabot security bumps + drop unused gray-matter (#4036)

Integrated into release/v3.8.27 — dependabot security bumps (form-data/js-yaml/protobufjs/dompurify/hono) + drop unused gray-matter. Unblocks the npm audit:deps gate (Lint) branch-wide.

* fix(ci): scope TIA to node:test unit files only (mirror test:unit glob) (#4035)

Integrated into release/v3.8.27 — scopes the advisory TIA step to the test:unit node:test glob, fixing the 99 false failures. +4 TDD.

* Refine compression settings, storage labels, and sidebar grouping (#4033)

Integrated into release/v3.8.27 — relocate Token Saver into Compression Settings (controlled component), reorder Security/Authz tabs, storage labels + i18n relabel. Thanks @rdself!

* [codex] add per-key local usage command (#4034)

Integrated into release/v3.8.27 — per-key local @@om-usage command (cached quota, no upstream routing). Rebased onto modularized schemas/keys.ts + file-size rebaseline. Thanks @Witroch4!

* chore(release): reconcile v3.8.27 CHANGELOG + i18n mirrors

* ci(quality): unblock v3.8.27 release gates (zizmor pin + test-masking allowlist)

- zizmor ratchet (151→139, no regression): SHA-pin every action ref ADDED this
  cycle — codeql/dast-smoke/semgrep (3 new workflows) + trivy-action (docker-publish)
  + actions/cache (nightly-mutation). Pre-existing tag refs keep the repo convention.
- test-masking: add config/quality/test-masking-allowlist.json + allowlist support in
  check-test-masking.mjs (exempts ONLY the net-assert-reduction signal; tautology/skip/
  deletion still fire). Allowlists 2 verified-legitimate reductions:
  appearance-widget-settings-schema (#4033 removed showTokenSaverOnEndpoint field) and
  dashboard-shell-tabs (#3973 tabs→redirect refactor, asserts replaced). +4 gate tests.

* test(quality): reword test-masking self-test comments to avoid literal masking patterns

The added allowlist-test comments contained the literal strings 'assert.ok(true)' and
'.skip' which the masking detector's own regexes match as text — making the gate flag
its own test file (net +1 tautology/skip/extended-tautology vs main). Reworded to plain
prose ('a new tautology', 'a new skip marker'); test logic unchanged (24/24 pass).

* fix(quality): unblock v3.8.27 release — align 3 stale tests + restore modularized settings-schema parity

Release-PR full CI surfaced 3 deterministic test failures (no live product regression),
all stale vs legitimate cycle changes:

- settings-schema parity (#3988): the modularized updateSettingsSchema barrel
  (schemas/settings.ts) had diverged from the canonical settingsSchemas.ts (45 vs 85
  fields — 40 dropped + 6 extra), a lossy-modularization dead-code copy. Re-export from
  the canonical source so the barrel can never diverge again (runtime already uses
  canonical). Parity test now passes.
- api-manager permissions modal: #4034 added a 4th self-service switch (per-key usage
  allowance); a11y invariant (every switch type="button") still holds. Updated the
  static count 3 -> 4.
- pack-artifact policy: dist/http-method-guard.cjs became a required runtime path;
  added it to the test's expected missing-paths list.

Also documents the gate gap for Fase 9 (QUALITY_GATE_PLAYBOOK Parte 6): G1 run the
deterministic unit layer + test-masking on PR->release (not just PR->main), G2 a
modularization-parity gate (would have caught the #3988 drop at its PR), G3 flake
quarantine. Env flakes (LiveWS startup timeout, integration server-startup cascade)
are pre-existing/CI-env, triaged separately.

---------

Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Veier04 <118300867+Veier04@users.noreply.github.com>
Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com>
Co-authored-by: WormAlien <164898390+WormAlien@users.noreply.github.com>
Co-authored-by: thezukiru <121331256+thezukiru@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
2026-06-17 02:43:21 -03:00

308 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 ~510% 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 12. | **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. | **~78/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 56)
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