# CI Requirements — FabledCurator > 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 `backend/`, `tests/`, `alembic/`) - node (frontend job: `npm install` + vitest + vite build) - docker CLI + buildx (`.forgejo/workflows/build.yml`: build-web, build-ml — Fabled-Git registry push) ## Secondary runtime image node:24-bookworm-slim — `.forgejo/workflows/extension.yml` only. The extension lane is the one job that does NOT run on `ci-python:3.14`: it needs a current Node for `web-ext` and vitest and nothing Python at all. Kept on the upstream slim image rather than adding a Node toolchain to `ci-python`, per `docs/process.md`'s "add deps to the image when used by >1 project". ## Per-job tool installs - `pip install -r requirements.txt pytest pytest-asyncio` — in `backend-lint-and-test` and `integration` jobs - `npm install --no-audit --no-fund` — in `frontend-build` job - `npm install --no-audit --no-fund` — in `extension.yml`'s `lint` job (web-ext + vitest) - `unzip` — in `extension.yml`'s "Verify XPI contents" step, installed via apt only when absent (`node:24-bookworm-slim` may or may not carry it). Debian package, ~2s. Not worth baking into a shared image for a single consumer, per `docs/process.md`'s ">1 project" rule. ## Notes - Integration wall time ~3 min, dominated by pgvector container start + the `pip install` step (~30-45s on cold cache) + alembic + 300+ integration tests. - The `pip install` in two jobs is intentional and per `docs/process.md`'s "add deps to image when used by >1 project" rule: FC alone is one Python project, so the deps live in `requirements.txt` and install per-job. Reconsider when a second Fabled-family Python backend lands. - Integration uses Fabled-Git Actions `services:` + socket-discovered bridge IPs because `act_runner` (swarm-runner v0.6+) puts services on the default bridge with no embedded DNS. The pattern is documented in the rulebook's `fabled-git.md` "CI philosophy" section and FC's `ci.yml` is the canonical example. - No `package-lock.json` is tracked yet (FC's `feedback_no_local_runs` memory bans `npm install` locally). Using `npm install` rather than `npm ci` until a lockfile lands. - No `imagemagick` / `pandoc` per-job installs needed. - `extension/`'s vitest specs load `lib/*.js` by evaluating the real file as a classic script (`test/helpers/loadLib.js`) rather than adding `module.exports` shims to production code — the libs ship as `background.scripts`, not ES modules, so the specs exercise exactly the bytes packaged into the XPI. - **`extension/scripts/packaging.sh` is the single definition of what ships inside the XPI.** Three consumers read from it rather than keeping their own copy: web-ext's `--ignore-files` (`extension/package.json`), the `:(exclude)` pathspec in `ci.yml`'s `extension-version` guard, and the commit count that derives the extension version. Three hand-kept copies of that one fact is what allowed issue #2397. - `build.yml`'s `sign-extension` checks out with `fetch-depth: 0` — the derived extension version is a commit count, which a shallow clone cannot produce. - Callers MUST `set -f` before substituting the script's output. Without it the shell expands `test/**` against the working tree and silently narrows the pattern to whatever files exist at that moment — a failure that looks like nothing until dev files start appearing in the XPI. `test/version.spec.js` asserts every `--ignore-files` consumer sets it, and that no consumer has quietly reinstated a hardcoded list.