# 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.** Two consumers read from it rather than keeping their own copy: web-ext's `--ignore-files` (`extension/package.json`), and the `git log` pathspec inside the script's own version derivation. It was three until 2026-08-27 — `ci.yml`'s `extension-version` guard held the third and went when the manual bump it guarded did (milestone 271 step 5). Hand-kept copies of that one fact is what allowed issue #2397, so `extension/test/version.spec.js` asserts no workflow has reintroduced a literal `:(exclude)extension/…`. - **The shipped extension version is derived, not committed.** It is the commit TIME of the newest packaged-extension change (minutes since 2020-01-01, per family rule 149 — never a commit count, which orders by branch rather than by recency). `build.yml`'s `sign-extension` computes it and stamps it into `extension/manifest.json` + `package.json` in the working tree before signing; the stamp is never committed. Treat the version in the repo as a base: only its MAJOR.MINOR is read, and its patch component is inert. - Every job that calls `packaging.sh version` checks out with `fetch-depth: 0` — `build.yml`'s `sign-extension` and `build-web`, and `ci.yml`'s `extension-version`. A depth-1 clone sees one commit and derives a wrong, too-low value **rather than failing**, so the full-history checkout is load-bearing rather than incidental. - 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.