Declared side by side, so they read as parallel; the lab runs a single act_runner with one slot, so the backend job starts at the exact second the frontend one ends. Measured across runs #86-#91: ~4 + ~6 = ~10 minutes from the push, not ~6. Written down because believing the comment cost something real — the ops release read-back budgeted the max instead of the sum and timed out on its first live run, reporting a still-running gate as a failing one.
104 lines
5.4 KiB
YAML
104 lines
5.4 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 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
|
|
#
|
|
# They are declared side by side but they DO NOT run in parallel: the lab has a single act_runner
|
|
# with one slot, so they queue. Measured across runs #86-#91 — the backend job starts at the exact
|
|
# second the frontend one ends, every time, and the pair costs ~4 + ~6 = **~10 minutes from the
|
|
# push**. Anything that waits on this gate has to budget for the sum, not the max; the ops
|
|
# `release` read-back timed out on its first real run for believing otherwise.
|
|
#
|
|
# 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
|
|
|
|
# Branch pushes only. A release pushes `main` and then the `v<ver>` tag, and a bare `on: [push]`
|
|
# treated the tag as a second push of the IDENTICAL commit — every release ran this whole gate
|
|
# twice, to the same verdict, for nothing (runs #81-#90 are five such pairs). `branches: ['**']`
|
|
# keeps every branch covered and drops tag refs.
|
|
#
|
|
# It has to be `branches`, NOT the `tags-ignore` this obviously wants to be. Gitea's matchPushEvent
|
|
# requires EVERY named filter to match, and each one bails on the wrong ref kind: `tags-ignore`
|
|
# starts with `if !refName.IsTag() { break }`, so a filter meant to skip tags rejects every BRANCH
|
|
# push and the gate stops running entirely. `branches` bails the same way on a tag, which is the
|
|
# direction we want. (Measured in go-gitea v1.27.3 modules/actions/workflows.go, not assumed — it
|
|
# differs from GitHub, where `tags-ignore` alone leaves branches unfiltered.)
|
|
#
|
|
# If that filter were ever wrong the gate would not run at all — which is why `siftlode release`
|
|
# now ends by reading the verdict back for the sha it pushed, and says so out loud when there is
|
|
# no run for it. A gate that silently stops running is the failure this repo already lived through.
|
|
on:
|
|
push:
|
|
branches: ["**"]
|
|
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
|