diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b6ad8f81..3045d8939 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -247,12 +247,56 @@ jobs: if-no-files-found: ignore ui-regression: - name: Advisory UI regression + name: UI regression + needs: changes + if: needs.changes.outputs.ui_changed == 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Restore Chromium browser cache + uses: actions/cache@v6 + with: + path: ~/.cache/ms-playwright + key: playwright-chromium-${{ runner.os }}-${{ hashFiles('package-lock.json') }} + + - name: Install Chromium browser + run: npx playwright install --with-deps chromium + + - name: Chromium stable regression suite + run: npm run test:e2e:regression + + - name: Upload UI regression diagnostics + if: failure() + uses: actions/upload-artifact@v7 + with: + name: ui-regression-diagnostics-${{ github.run_id }} + path: | + test-results/ + playwright-report/ + if-no-files-found: ignore + + ui-quarantine: + name: Advisory quarantined UI tests needs: changes if: github.event_name == 'pull_request' && needs.changes.outputs.ui_changed == 'true' continue-on-error: true runs-on: ubuntu-24.04 - timeout-minutes: 45 + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v6 @@ -278,14 +322,14 @@ jobs: - name: Install Chromium browser run: npx playwright install --with-deps chromium - - name: Chromium advisory regression - run: npm run test:e2e:advisory + - name: Chromium quarantined tests (advisory) + run: npm run test:e2e:quarantine - - name: Upload advisory UI diagnostics + - name: Upload quarantine diagnostics if: failure() uses: actions/upload-artifact@v7 with: - name: advisory-ui-diagnostics-${{ github.run_id }} + name: quarantine-ui-diagnostics-${{ github.run_id }} path: | test-results/ playwright-report/ @@ -341,7 +385,7 @@ jobs: pr-required: name: PR required - needs: [changes, static-pr, safety, coverage, build, ui-critical, db-reset-verify] + needs: [changes, static-pr, safety, coverage, build, ui-critical, ui-regression, db-reset-verify] if: always() runs-on: ubuntu-24.04 timeout-minutes: 5 @@ -359,6 +403,7 @@ jobs: COVERAGE_RESULT: ${{ needs.coverage.result }} BUILD_RESULT: ${{ needs.build.result }} UI_RESULT: ${{ needs.ui-critical.result }} + UI_REGRESSION_RESULT: ${{ needs.ui-regression.result }} DB_RESULT: ${{ needs.db-reset-verify.result }} run: | set -euo pipefail @@ -404,8 +449,10 @@ jobs: if [ "$UI_CHANGED" = "true" ]; then require_success "ui-critical" "$UI_RESULT" + require_success "ui-regression" "$UI_REGRESSION_RESULT" else require_skipped_or_success "ui-critical" "$UI_RESULT" + require_skipped_or_success "ui-regression" "$UI_REGRESSION_RESULT" fi if [ "$DB_CHANGED" = "true" ]; then diff --git a/docs/process-hardening.md b/docs/process-hardening.md index 7091dae4e..3992c67a8 100644 --- a/docs/process-hardening.md +++ b/docs/process-hardening.md @@ -131,10 +131,11 @@ passes `p_worker_id`. Ordered apply steps, R17 manual `CONCURRENTLY` index, and ## PR merge gate: risk-scoped CI + required aggregate (2026-07-10) -- CI now has one always-reporting required aggregate: `CI / PR required`. The aggregate depends on `changes`, `static-pr`, `safety`, `coverage`, `build`, `ui-critical`, and `db-reset-verify`, then enforces only the jobs whose scopes apply. +- CI now has one always-reporting required aggregate: `CI / PR required`. The aggregate depends on `changes`, `static-pr`, `safety`, `coverage`, `build`, `ui-critical`, `ui-regression`, and `db-reset-verify`, then enforces only the jobs whose scopes apply. - `static-pr` is the deterministic baseline for every PR: runtime, action pin check, CI scope self-test, format, lint, typecheck, and unit tests. Coverage, build, safety/config, critical UI, production-backed offline RAG preflight, and migration replay are independent jobs so reruns stay focused. Coverage is limited to `src`/test changes; process-only changes do not trigger builds. - `db-reset-verify` is path-scoped to Supabase migrations/schema/config and database-access code. Do not also require an external Supabase Preview replay unless the repo owner intentionally wants duplicate migration replay. -- `ui-critical` is path-scoped to UI/routing/styling/browser-facing changes and runs only the `@critical` Chromium smoke subset. `ui-regression` runs the remaining Chromium cases on UI pull requests as advisory feedback and is deliberately excluded from `pr-required`. The full browser matrix remains main/release/manual/scheduled. +- `ui-critical` is path-scoped to UI/routing/styling/browser-facing changes and runs only the `@critical` Chromium smoke subset. `ui-regression` (2026-07-13 audit, finding 8) runs the remaining stable Chromium cases via `test:e2e:regression` (`--grep-invert "@critical|@quarantine"`) and is **merge-blocking** through `pr-required` on UI changes. Genuinely flaky specs are tagged `@quarantine` and run in the advisory `ui-quarantine` job (`test:e2e:quarantine`, `--pass-with-no-tests`); fix and untag them rather than letting the quarantine grow. The full browser matrix remains main/release/manual/scheduled. +- 2026-07-13 flip-day triage: the whole 142-test regression set was green locally except one stale `/privacy` heading assertion (fixed — the page title is "Privacy & data handling" since the footer simplification) and three cold-dev-server timing timeouts that pass on a warmed server and have no CI failure history (`ui-tools` mode-home centering `:462`, differentials comparison wiring `:1367`, `ui-universal-search` grouped-result navigation `:102`). Watch those three; if one flakes in CI, tag it `@quarantine` rather than reverting the gate. - Branch protection for `main` should require `CI / PR required` and `Secret Scan / Gitleaks`. Keep `SAST / Semgrep` required only if the repository owner accepts its external-rule/network dependency as part of the normal merge gate. Do not require path-filtered or scheduled/manual contexts such as `CI / Unit coverage`, `CI / Critical UI smoke`, `CI / Migration replay`, `Docker image build / app-image`, `Docker image build / worker-image`, `CI / release-browser-matrix`, `Eval Canary`, or `Live drift check`; they can be skipped on ordinary PRs and would leave branches stuck at "Expected - Waiting for status to be reported." ## CSS cascade layering (2026-07-02) diff --git a/package.json b/package.json index a215e063c..79758e855 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "test:e2e:all": "node scripts/run-playwright.mjs", "test:e2e:accessibility": "node scripts/run-playwright.mjs tests/ui-accessibility.spec.ts --project=chromium", "test:e2e:critical": "node scripts/run-playwright.mjs --project=chromium --grep @critical", - "test:e2e:advisory": "node scripts/run-playwright.mjs --project=chromium --grep-invert @critical", + "test:e2e:regression": "node scripts/run-playwright.mjs --project=chromium --grep-invert \"@critical|@quarantine\"", + "test:e2e:quarantine": "node scripts/run-playwright.mjs --project=chromium --grep @quarantine --pass-with-no-tests", "test:e2e:chromium": "node scripts/run-playwright.mjs --project=chromium", "test:e2e:visual": "node scripts/run-playwright.mjs --config=playwright.visual.config.ts", "verify:cheap": "npm run check:runtime && npm run check:github-actions && npm run sitemap:check && npm run brand:check && npm run check:type-scale && npm run check:icon-scale && npm run lint && npm run typecheck && npm run test",