`frontend/tsconfig.tsbuildinfo` was swept into the previous commit by a `git add -A`. It is a machine-local incremental artifact carrying absolute Windows paths, no lane reads or regenerates it, and it was already stale in the commit that added it — it does not list the contract test that same commit created. Untracked, and `*.tsbuildinfo` ignored, because the day anything does start rewriting it a green gate would leave a tracked file dirty: the exact hazard the basedpyright baseline just got a guard for. And CI ran bare basedpyright while the local lane gained that guard, so a burn-down would have been green on the server and red on the developer's machine over the same tree. Two gates, one tree, one answer.
120 lines
6.6 KiB
YAML
120 lines
6.6 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
|
|
# The ninth lane (E27 S3). Run from backend/ so it resolves exactly as the local lane does —
|
|
# pyrightconfig.json's include/extraPaths are relative to the config, but the baseline is
|
|
# found relative to the working directory. New errors fail; the ~200 pre-existing ones are in
|
|
# .basedpyright/baseline.json and burn down epic by epic. Matching is per (file, rule, column
|
|
# span) plus a count, so a new error of the SAME shape in a file where a sibling was just
|
|
# fixed is absorbed — burn the hot files down first.
|
|
#
|
|
# The second step is not redundant. basedpyright TIGHTENS the baseline in place when the count
|
|
# drops, with no --writebaseline, and CI throws its workspace away — so without this a
|
|
# burn-down is green here and red on the developer's `siftlode check`, which has the same
|
|
# guard. Two gates over one tree must not answer differently.
|
|
- name: basedpyright
|
|
run: python -m basedpyright --project pyrightconfig.json
|
|
working-directory: backend
|
|
- name: basedpyright baseline is committed
|
|
run: git diff --exit-code -- backend/.basedpyright/baseline.json
|
|
- 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
|