Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# 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. Its release workflow builds
|
||||
the production AI variant and is the only workflow that can automatically
|
||||
deploy. The GitHub mirror builds and scans the same AI variant for external
|
||||
review but is not a production deployment authority.
|
||||
|
||||
## 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 AI image and scanner databases.
|
||||
|
||||
Both container jobs build the explicit AI image, including the
|
||||
PyTorch/Ultralytics layers used on Tower. AI packages remain outside the
|
||||
standard backend lock and are pinned by Docker build arguments.
|
||||
|
||||
## 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 Ruff, repository-layout validation, 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.
|
||||
|
||||
Pull requests run managed validation against the real `backend/` and
|
||||
`frontend/` projects plus the complete release gates. On a `main` push, the
|
||||
Unraid deploy job has explicit `needs` dependencies on quality, dependency and
|
||||
AI-container jobs. There is no separate deployment workflow: manual validation
|
||||
uses `workflow_dispatch` on this same release-gates workflow and cannot skip
|
||||
quality, dependency or AI-container jobs.
|
||||
|
||||
The deploy host requires a full controller commit SHA, builds the AI variant,
|
||||
records its Docker image/config digest, generates SBOM and Trivy evidence for
|
||||
that exact local image, and starts the immutable image ID. Deployment fails if
|
||||
the running container ID, revision label or AI label differs from the retained
|
||||
attestation.
|
||||
|
||||
## 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.
|
||||
|
||||
There are currently no Python or container vulnerability exceptions.
|
||||
`security/pip-audit-exceptions.json` remains as a strict, machine-readable
|
||||
registry: every future exception must identify one advisory, package, specific
|
||||
reason and expiry date. Readiness and CI fail on malformed or expired entries;
|
||||
new advisories are never auto-ignored. GeoIntel requires Starlette 1.3.1 or
|
||||
newer and therefore no longer suppresses the five 2026 Starlette advisories.
|
||||
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;
|
||||
the final runtime filesystem no longer exposes the vulnerable Go executable.
|
||||
Because OCI layers are immutable, Trivy still records the shadowed Go binary
|
||||
from the official Postgres base layer in the complete JSON report. The
|
||||
release-policy pass skips only `/usr/local/bin/gosu` after the final image is
|
||||
verified to contain the audited shell wrapper. This is not a vulnerability
|
||||
exception: the raw evidence remains published and the runtime wrapper is
|
||||
exercised during live release validation.
|
||||
|
||||
The operational container job builds the production 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=true \
|
||||
--build-arg GEOINTEL_BUILD_SHA=local \
|
||||
--build-arg GEOINTEL_BUILD_TIME=local \
|
||||
-t geointel-ci:local-ai .
|
||||
bash scripts/generate_container_sbom.sh geointel-ci:local-ai
|
||||
bash scripts/scan_container_image.sh geointel-ci:local-ai
|
||||
```
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user