name: Quality Gates on: pull_request: branches: ["release/**"] types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read env: # CI must never mutate the runner's OS trust store (2026-07-05: a cert-flow # test installed a fake PEM on a persistent self-hosted runner and broke all # system TLS). Belt-and-suspenders with tests/_setup/isolateDataDir.ts. OMNIROUTE_SKIP_SYSTEM_TRUST: "1" CI_NODE_VERSION: "24" jobs: fast-gates: name: Fast Quality Gates runs-on: ubuntu-latest # tsx gates (known-symbols, route-guard-membership) import modules that open # SQLite on load; provide DB env so a fresh CI DB initializes cleanly. env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci - run: npm run check:provider-consistency - run: npm run check:fetch-targets - run: npm run check:openapi-routes - run: npm run check:docs-symbols - name: Docs accuracy (fabricated-docs + i18n mirrors, strict) run: npm run check:docs-all - run: npm run check:deps - run: npm run check:file-size - run: npm run check:error-helper - run: npm run check:migration-numbering - run: npm run check:public-creds - run: npm run check:db-rules - run: npm run check:known-symbols - run: npm run check:route-guard-membership - run: npm run check:test-discovery - run: npm run check:test-runner-api # Guards tap.testFiles drift: a covering unit test absent from stryker.conf.json # tap.testFiles makes its module's mutants survive on a cold nightly-mutation run, # false-failing the blocking mutationScore ratchet. See check-mutation-test-coverage.mjs. - run: npm run check:mutation-test-coverage - run: npm run check:any-budget:t11 # Build-scope guard: fails if worktrees/cruft leak into the tsconfig include # scope (would OOM `next build`). Instant. See incident 2026-06-25 / #5031. - run: npm run check:build-scope # Pack-policy (unexpected-files allowlist) WITHOUT a build — catches a stray file # leaking into the npm tarball (v3.8.36: 6 ops bin/*.sh) per-PR instead of only on # the release PR's heavy Package Artifact job. - run: npm run check:pack-policy # Complexity + cognitive-complexity ratchets on the fast-path (PR→release) so # cycle drift is rebaselined PER-PR instead of cascading onto the release PR's # Quality Ratchet (v3.8.36: +30 complexity / +15 cognitive surfaced only post-merge). - run: npm run check:complexity - run: npm run check:cognitive-complexity - name: Typecheck (core) run: npm run typecheck:core # TIA: build the impact map at runtime (gitignored, ~21MB) and run only the # unit tests impacted by this PR's changed files. Fail-safe runs the FULL # unit suite on hub/unmapped changes — TIA accelerates, never replaces, the net. # # BLOCKING (flipped 2026-06-17). The pre-existing release unit test-debt that kept # this advisory was cleared: #4030 (16 Zod/registry reds, lossless restore) and # #4063 (the last red — the LiveWS boot test — root-caused as a real event-loop # stall in the WS sidecar, fixed + relocated to the integration suite). A full # ci.yml run on release/v3.8.28 then showed all 8 unit shards green, so PR->release # now blocks on unit-test regressions in the impacted set (typecheck:core already # blocked above). Fail-safe still runs the FULL unit suite on hub/unmapped changes. - name: Impacted unit tests (TIA, fail-safe full; blocking) env: GITHUB_BASE_REF: ${{ github.base_ref }} run: | git fetch --no-tags origin "$GITHUB_BASE_REF" || true node scripts/quality/build-test-impact-map.mjs SEL="$(node scripts/quality/select-impacted-tests.mjs)" if [ -z "$SEL" ]; then echo "No source/test changes — skipping unit tests"; exit 0; fi # CI runners are 4-vCPU; run at --test-concurrency=4 (matching the ci.yml unit # job) rather than test:unit's local-tuned concurrency=20. Oversubscribing the # runner makes timing-sensitive tests (db-backup, upstream-timeout, ...) flake, # which must not happen on a blocking gate. DATA_DIR isolation keeps the parallel # run race-free regardless of concurrency. if echo "$SEL" | grep -q "__RUN_ALL__"; then echo "Fail-safe: running FULL unit suite (CI concurrency)"; npm run test:unit:ci; exit $? fi echo "Running impacted tests:"; echo "$SEL" mapfile -t FILES <<< "$SEL" node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 "${FILES[@]}" fast-vitest: name: Vitest (fast-path) runs-on: ubuntu-latest env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci - run: npm run test:vitest fast-unit: name: Unit Tests fast-path (${{ matrix.shard }}/2) runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1, 2] env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci # QW-d: fonte única — o mesmo npm script do CI pesado/local. Fecha dois drifts do # comando inline antigo: os dirs `memory` e `usage` estavam FORA do glob (testes # silenciosamente não rodavam no fast path) e o setupPolyfill não era importado. - run: npm run test:unit:ci:shard env: TEST_SHARD: ${{ matrix.shard }}/2 # ── Pacote 4 (plano mestre testes+CI, aprovado 2026-07-04) ───────────────────────── # No-new-warnings por PR via ESLint bulk suppressions nativo (>=9.24). O baseline # config/quality/eslint-suppressions.json congela as violações EXISTENTES por # arquivo+regra; qualquer warning NOVO aparece e o --max-warnings 0 falha o job — o # drift de +41/+88 warnings por ciclo passa a morrer no PR que o introduz, em vez de # ser rebaselinado às cegas na release. Aperto do baseline (na reconciliação da # release): npx eslint . --prune-suppressions --suppressions-location config/quality/eslint-suppressions.json # # Princípio Zero: bloqueante SÓ para branches internas (as campanhas/sessões são a # origem do drift). PR de FORK roda em modo report (continue-on-error → o job fica # verde com anotação; a campanha /green-prs aplica o fix via co-autoria — o # contribuidor NUNCA é bloqueado nem cobrado). lint-guard: name: No new ESLint warnings runs-on: ubuntu-latest continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci - name: ESLint (baseline congelado — warning novo = vermelho) run: npx eslint . --suppressions-location config/quality/eslint-suppressions.json --max-warnings 0 # Merge-integrity: pega no PR os dois vazamentos crônicos de merge que hoje só # explodem na release-PR. (1) CHANGELOG-eat — o auto-resolve do merge come # bullets vizinhos/seções inteiras (incidente #6193, 2026-07-05: 212 linhas / # 130 bullets); o checkout de PR é refs/pull/N/merge, então comparar contra a # base detecta o eat ANTES do merge. (2) SKILL.md gerado stale vs o catálogo de # agent-skills (#6186 mergeou um id de catálogo sem rodar o gerador → 8 reds de # integration invisíveis até a release). # # Princípio Zero: bloqueante SÓ para branches internas; PR de FORK roda em modo # report (continue-on-error) — a campanha corrige via co-autoria, o contribuidor # nunca é bloqueado. merge-integrity: name: Merge integrity (changelog + generated skills) runs-on: ubuntu-latest continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci - name: CHANGELOG integrity (nenhum bullet da base pode sumir no merge-result) run: npm run check:changelog-integrity - name: Agent-skills generator sync (SKILL.md gerado ≡ catálogo) run: npm run check:agent-skills-sync