# CI and supply-chain release gates GeoIntel uses the same release gates in Gitea Actions and GitHub Actions: - `.gitea/workflows/release-gates.yml` - `.github/workflows/release-gates.yml` Gitea is the operational source-control platform. The GitHub workflow is kept equivalent so a mirror or external review does not receive a weaker gate. ## Runner requirements The `ubuntu-latest` runner must provide: - outbound HTTPS access to PyPI, npm, Docker Hub and vulnerability databases; - Python 3.11 and Node 20 through the official setup actions; - Bash and Docker with Compose v2; - permission to build images and mount `/var/run/docker.sock`; - sufficient disk for the all-in-one GIS image and scanner databases. The container job builds the GIS release variant only. PyTorch and Ultralytics remain in the optional `ai` extra and in the explicit AI image variant; CI does not silently make them base dependencies. ## Quality gate The quality job installs: ```bash python -m pip install --require-hashes -r backend/requirements-ci.lock python -m pip install --no-deps -e backend cd frontend && npm ci ``` It then validates the lock policy and runs the complete readiness script. The readiness script covers backend compile/tests, contract audits, Alembic single-head, frontend typecheck/build and release-script syntax. CI also renders offline migration SQL and resolved Compose configuration as retained evidence. ## Reproducible Python lock `backend/requirements-runtime.lock` and `backend/requirements-ci.lock` are generated in a digest-pinned Linux Python 3.11 container. The runtime lock contains base and GIS packages used by the release image. The CI lock adds developer/test dependencies. Both use package hashes and deliberately exclude the optional AI dependency group. Regenerate after changing relevant `pyproject.toml` dependencies: ```bash bash scripts/generate_python_lock.sh python scripts/verify_python_lock.py ``` The all-in-one frontend build uses `npm ci`; the non-AI runtime installs the hashed runtime lock. The optional AI image additionally uses explicit PyTorch, torchvision and Ultralytics build-argument versions. Do not hand-edit dependency versions or hashes in either generated lock. `verify_python_lock.py` rejects a stale input fingerprint, another Python generation version, unhashed packages, missing direct dependencies and AI packages leaking into the standard CI environment. ## Vulnerability policy The dependency job: - fails on any non-excepted vulnerability reported by `pip-audit` for the complete exact lock without platform-specific re-resolution; - fails when `npm audit --audit-level=high` finds a high or critical frontend dependency vulnerability; - publishes both unfiltered and policy-filtered Python JSON reports plus the npm JSON report, including on failure. The only current Python/container exceptions are the Starlette 2026 advisories recorded in `security/pip-audit-exceptions.json`. FastAPI 0.139.2 still constrains Starlette below 0.53 while patched releases begin at 1.x. GeoIntel applies request-target, form-content, route-class and Linux-runtime compensating controls. The exception file has a mandatory review date; readiness and CI fail automatically after it expires. New advisories are never auto-ignored. The all-in-one image replaces the Go-based base-image `gosu` helper with a small `setpriv` exec wrapper and upgrades packaged setuptools/wheel metadata; these scanner findings are fixed rather than excepted. The container job builds a non-AI all-in-one image and uses digest-pinned scanner images: - Syft 1.44.0 generates an SPDX JSON SBOM; - Trivy 0.70.0 generates a complete JSON vulnerability report; - fixed high or critical image vulnerabilities fail the gate; - unfixed findings remain in the full report and require explicit release review, but do not make a rebuild impossible when no patched package exists. Run these controls on a Docker-enabled workstation: ```bash docker build \ -f deploy/unraid/Dockerfile.all-in-one \ --build-arg GEOINTEL_INSTALL_AI=false \ --build-arg GEOINTEL_BUILD_SHA=local \ --build-arg GEOINTEL_BUILD_TIME=local \ -t geointel-ci:local . bash scripts/generate_container_sbom.sh geointel-ci:local bash scripts/scan_container_image.sh geointel-ci:local ``` Outputs are written below ignored `artifacts/`; scanner cache is written below ignored `.cache/trivy/`. ## Published evidence Every workflow run retains: - offline Alembic upgrade SQL; - resolved Docker Compose configuration; - pip-audit and npm-audit JSON; - image inspection metadata; - SPDX JSON SBOM; - complete Trivy JSON report. No secret or plaintext database credential belongs in these artefacts.