# CI Requirements — Steward > Spec: https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/process.md ## Runtime image git.fabledsword.com/bvandeusen/ci-python:3.14 ## Image deps used - python 3.14 - ruff (analyzer for `steward/`, `plugins/`, `tests/`) - docker CLI (integration lane: bridge-IP discovery of the Postgres service container) ## Per-job tool installs - `uv pip install --system -e '.[dev]'` (pip fallback) — in the `unit` job. - `uv pip install --system -e '.[dev,ansible]'` (pip fallback) — in the `integration` job, which boots the real app and exercises the Ansible runner, so it needs the `ansible` extra (`ansible>=10,<13`) at import time. Steward declares its deps in `pyproject.toml`; the `[dev]` extra adds `pytest` + `pytest-asyncio`. No `requirements.txt` is tracked. ## Lanes - **lint** — `ruff check steward/ plugins/ tests/`, no dep install. - **unit** — `pytest -m "not integration"`. The whole current suite runs here: it builds the app with `testing=True`, which mocks `db_sessionmaker`, so no DB is touched. First-party plugins are bundled in-tree under `plugins/`, so plugin unit tests import them directly — no cross-repo checkout. - **integration** — `pytest tests/integration -m integration` against a `postgres:16-alpine` service. The single test boots the real app (`create_app(testing=False)`), which runs core + every bundled plugin's migrations, then round-trips the DB. This is the guard that the folded-in plugin migration graph resolves. - **publish** — builds the runtime `Dockerfile` and pushes to the package registry. Runs in `ci-builder:latest` (docker + buildx, socket auto-mounted), gated on `needs: [lint, unit, integration]` so a red commit never ships an image. Publishes `git.fabledsword.com/bvandeusen/steward:` always, plus `:dev` on `dev` and `:latest` on `main` (FabledRulebook rule 46). Needs the `REGISTRY_TOKEN` repo Actions secret (`read:package` + `write:package`) — the injected `GITHUB_TOKEN` can't push packages. ## Notes - Steward has **no frontend and no Redis/Celery** (no external workers), so there is no frontend-build lane and the only service container is Postgres. - Integration uses Forgejo Actions `services:` + a socket-discovered bridge IP because `act_runner` (swarm-runner v0.6+) puts services on the default bridge with no embedded DNS. FabledCurator's `ci.yml` is the canonical example of the pattern; Steward's is the same shape minus Redis and minus sharding. - The job name `integration` has no underscore — `act_runner` strips underscores from job names when building service-container labels, so the `docker ps` name filter uses the bare job name. - Migrations run via the app itself (`create_app` → `run_core_migrations`, async through `asyncpg`), so the integration lane needs no separate `alembic upgrade` step and no psycopg/sync driver.