The server gate ran node:22 while the dev gate ran node:24, and the two disagree about Intl.NumberFormat's compact currency padding and about which realm's Blob a Response hands back — so the frontend job had been red for 73 of its last 90 runs while the local gate was green, and nothing said so. Align everything on 24, with `.nvmrc` as the single source of truth: the CI container, the prod build stage, `engines`, and `@types/node` (which was two majors ahead of the runtime, so tsc was green over APIs the shipped engine does not have). The backend job's node only runs the checkout action, so it tracks the pin as hygiene — node 20 is EOL — but deliberately stays outside the contract. The pin is held by a contract test that reads every one of those places AND the engine running it, so the next drift is a red lane on its first run instead of a CI log nobody reads. Verified on node:24-bookworm (the CI lane: 228 files / 1552 tests, typecheck, lint, format:check, knip all exit 0) and on node:24-alpine (the prod build stage, v24.21.0, SPA built). Each assertion of the new test was mutation-checked red.
80 lines
3.8 KiB
YAML
80 lines
3.8 KiB
YAML
# Server-side mirror of the local `siftlode check` gate (E1 S3 / DX-1), so `main` is always green
|
|
# independent of any one machine. Two parallel jobs match the two local lanes:
|
|
# frontend — tsc (3 projects) + eslint + prettier --check + knip + vitest
|
|
# backend — ruff + the full pytest suite (pure-logic + the DB-backed lane) against a real Postgres
|
|
#
|
|
# Runner: the lab act_runner (LXC 115) mounts the Docker socket, so service containers work. The backend
|
|
# runs pytest NATIVELY against a `postgres` service (not the local docker-compose.test.yml, whose
|
|
# bind-mount is unreliable under a mounted-socket runner); fidelity is kept by installing the SAME pinned
|
|
# requirements, Postgres 16, and ffmpeg, and by setting SIFTLODE_TEST_REQUIRE_DB=1 so a DB/ffmpeg that
|
|
# failed to come up is a hard error, never a silent skip.
|
|
#
|
|
# The frontend container's node major is NOT free: it must equal the prod build image's and the dev
|
|
# machine's, because vitest/tsc read the engine's own Intl and web-API implementations and those split
|
|
# between majors. `.nvmrc` is the single source of truth and
|
|
# `frontend/src/lib/nodeEngine.contract.test.ts` fails this gate if any of them drift apart.
|
|
name: gate
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
frontend:
|
|
runs-on: ubuntu-latest
|
|
container: node:24-bookworm
|
|
defaults:
|
|
run:
|
|
working-directory: frontend
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- run: npm ci
|
|
- run: npm run typecheck
|
|
- run: npm run lint
|
|
- run: npm run format:check
|
|
- run: npm run knip
|
|
- run: npm run test
|
|
|
|
backend:
|
|
runs-on: ubuntu-latest
|
|
# A combined python+node image: python 3.12 (matches the prod Dockerfile) for the tests, and node
|
|
# so the JS actions (checkout) run. actions/setup-python is NOT usable here — the runner can't reach
|
|
# GitHub's python-versions download, so it must be baked into the image.
|
|
#
|
|
# This node runs the checkout action, never our code, so it is deliberately OUTSIDE the engine
|
|
# contract the frontend job is held to — it tracks `.nvmrc` as hygiene (node 20 went EOL in April
|
|
# 2026), but a missing upstream tag here must never be able to block the gate.
|
|
container: nikolaik/python-nodejs:python3.12-nodejs24
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: siftlode
|
|
POSTGRES_PASSWORD: siftlode
|
|
POSTGRES_DB: siftlode_test
|
|
options: >-
|
|
--health-cmd "pg_isready -U siftlode -d siftlode_test"
|
|
--health-interval 2s
|
|
--health-timeout 3s
|
|
--health-retries 30
|
|
env:
|
|
DATABASE_URL: postgresql+psycopg://siftlode:siftlode@postgres:5432/siftlode_test
|
|
SIFTLODE_TEST_REQUIRE_DB: "1"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
# ffmpeg for the poster/thumbnail tests; postgresql-client for the readiness wait below.
|
|
- run: apt-get update && apt-get install -y --no-install-recommends ffmpeg postgresql-client
|
|
- name: Wait for Postgres
|
|
run: until pg_isready -h postgres -U siftlode -d siftlode_test; do sleep 1; done
|
|
- run: pip install --no-cache-dir -r backend/requirements.lock -r backend/requirements-dev.txt
|
|
- name: ruff
|
|
run: python -m ruff check --config backend/ruff.toml backend scripts
|
|
- name: ruff format --check
|
|
run: python -m ruff format --check --config backend/ruff.toml backend scripts
|
|
- name: pytest
|
|
run: python -m pytest
|
|
working-directory: backend
|
|
# Dependency CVE audit over the pinned lock. Non-blocking for now (informational) — a real CVE
|
|
# shows up in the run without failing the gate; tighten to a hard fail once the noise is known.
|
|
- name: pip-audit (advisory)
|
|
run: pip install --no-cache-dir pip-audit && pip-audit -r backend/requirements.lock
|
|
continue-on-error: true
|