Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions docs/process-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down