diff --git a/.gitea/workflows/release-gates.yml b/.gitea/workflows/release-gates.yml index 2177eb83..7291620f 100644 --- a/.gitea/workflows/release-gates.yml +++ b/.gitea/workflows/release-gates.yml @@ -120,6 +120,8 @@ jobs: name: GIS image, SBOM and container scan runs-on: ubuntu-latest timeout-minutes: 60 + env: + GEOINTEL_IMAGE_ARCHIVE: artifacts/geointel-image.tar steps: - uses: actions/checkout@v4 - name: Build non-AI release image @@ -136,6 +138,7 @@ jobs: -t "geointel-ci:$RELEASE_SHA-gis" \ . docker image inspect "geointel-ci:$RELEASE_SHA-gis" > artifacts/image-inspect.json + docker save "geointel-ci:$RELEASE_SHA-gis" --output "$GEOINTEL_IMAGE_ARCHIVE" - name: Generate SPDX SBOM env: RELEASE_SHA: ${{ gitea.sha }} @@ -144,6 +147,9 @@ jobs: env: RELEASE_SHA: ${{ gitea.sha }} run: bash scripts/scan_container_image.sh "geointel-ci:$RELEASE_SHA-gis" + - name: Remove transient image archive + if: always() + run: rm -f "$GEOINTEL_IMAGE_ARCHIVE" - name: Publish container evidence if: always() uses: actions/upload-artifact@v3.2.2-node20 diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md index 2a94b0a8..5d801d09 100644 --- a/docs/API_CONTRACTS.md +++ b/docs/API_CONTRACTS.md @@ -1097,7 +1097,7 @@ A regional run holds tens of thousands of detections, and these are the endpoints the results table and the map overlay call after every run; they previously returned all of them. -### GET `/api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson` +### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/evidence/geojson` Returns the reviewable geometry behind one quality check: the objects the model missed, the ones it found without a reference, and the confirmed matches. @@ -1863,7 +1863,7 @@ Each feature includes: Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint. -### GET `/api/v1/projects/{project_id}/quality-checks/{id}/reviews` +### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews` Returns the evidence queue plus a `summary`, which now carries `reviewed_metrics`: the score with the operator's verdicts applied, next to the @@ -2084,6 +2084,8 @@ Request: Fixture segmenter mode is test/demo-only. It persists only explicit `parameters_json.fixture_segmentations` entries when `parameters_json.fixture_mode=true`; it is never invoked automatically and does not represent production inference. +### POST `/api/v1/segmentation/run-async` + The production frontend uses `POST /api/v1/segmentation/run-async`, then polls `GET /api/v1/projects/{project_id}/jobs/{job_id}` and reconciles the terminal job with its persisted `AnalysisRun` and polygon records. It does not fall back diff --git a/scripts/generate_container_sbom.sh b/scripts/generate_container_sbom.sh index aef46b7d..2ce1c86b 100644 --- a/scripts/generate_container_sbom.sh +++ b/scripts/generate_container_sbom.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TARGET_IMAGE="${1:-geointel-ci:local}" OUTPUT="${2:-artifacts/geointel-sbom.spdx.json}" +IMAGE_ARCHIVE="${GEOINTEL_IMAGE_ARCHIVE:-artifacts/geointel-image.tar}" SYFT_IMAGE="anchore/syft:v1.44.0@sha256:86fde6445b483d902fe011dd9f68c4987dd94e07da1e9edc004e3c2422650de6" case "$OUTPUT" in @@ -12,9 +13,19 @@ case "$OUTPUT" in exit 1 ;; esac +case "$IMAGE_ARCHIVE" in + /*|*..*) + echo "Image archive must be a repository-relative path without '..'." >&2 + exit 1 + ;; +esac docker image inspect "$TARGET_IMAGE" >/dev/null mkdir -p "$ROOT/$(dirname "$OUTPUT")" +if [[ ! -s "$ROOT/$IMAGE_ARCHIVE" ]]; then + mkdir -p "$ROOT/$(dirname "$IMAGE_ARCHIVE")" + docker save "$TARGET_IMAGE" --output "$ROOT/$IMAGE_ARCHIVE" +fi WORKDIR="/workspace" WORKSPACE_ARGS=(-v "$ROOT:/workspace") if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then @@ -22,11 +33,10 @@ if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then WORKSPACE_ARGS=(--volumes-from "$HOSTNAME") fi docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ "${WORKSPACE_ARGS[@]}" \ -w "$WORKDIR" \ "$SYFT_IMAGE" \ - "$TARGET_IMAGE" \ + "docker-archive:$WORKDIR/$IMAGE_ARCHIVE" \ -o "spdx-json=$OUTPUT" test -s "$ROOT/$OUTPUT" diff --git a/scripts/scan_container_image.sh b/scripts/scan_container_image.sh index 4eb478c6..00cf9083 100644 --- a/scripts/scan_container_image.sh +++ b/scripts/scan_container_image.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TARGET_IMAGE="${1:-geointel-ci:local}" OUTPUT="${2:-artifacts/geointel-container-vulnerabilities.json}" +IMAGE_ARCHIVE="${GEOINTEL_IMAGE_ARCHIVE:-artifacts/geointel-image.tar}" TRIVY_IMAGE="aquasec/trivy:0.70.0@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e" CACHE_DIR="${GEOINTEL_TRIVY_CACHE:-$ROOT/.cache/trivy}" PYTHON_CMD="${PYTHON_BIN:-python3}" @@ -16,9 +17,19 @@ case "$OUTPUT" in exit 1 ;; esac +case "$IMAGE_ARCHIVE" in + /*|*..*) + echo "Image archive must be a repository-relative path without '..'." >&2 + exit 1 + ;; +esac docker image inspect "$TARGET_IMAGE" >/dev/null mkdir -p "$ROOT/$(dirname "$OUTPUT")" "$CACHE_DIR" +if [[ ! -s "$ROOT/$IMAGE_ARCHIVE" ]]; then + mkdir -p "$ROOT/$(dirname "$IMAGE_ARCHIVE")" + docker save "$TARGET_IMAGE" --output "$ROOT/$IMAGE_ARCHIVE" +fi WORKDIR="/workspace" WORKSPACE_ARGS=(-v "$ROOT:/workspace") if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then @@ -33,27 +44,26 @@ CONTAINER_IGNORE_FILE="$CONTAINER_CACHE_DIR/geointel-trivy-ignore.txt" # Keep the complete report, including vulnerabilities without an available fix. docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ "${WORKSPACE_ARGS[@]}" \ "$TRIVY_IMAGE" \ image \ + --input "$WORKDIR/$IMAGE_ARCHIVE" \ --scanners vuln \ --timeout 20m \ --skip-version-check \ --format json \ --cache-dir "$CONTAINER_CACHE_DIR" \ - --output "$WORKDIR/$OUTPUT" \ - "$TARGET_IMAGE" + --output "$WORKDIR/$OUTPUT" # Release policy: fixed HIGH/CRITICAL findings block the build. Unfixed findings # remain visible in the full report and must be reviewed before release. The # postgres base image's Go gosu binary remains discoverable in a lower layer, # but the final filesystem replaces it with the audited setpriv shell wrapper. docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ "${WORKSPACE_ARGS[@]}" \ "$TRIVY_IMAGE" \ image \ + --input "$WORKDIR/$IMAGE_ARCHIVE" \ --scanners vuln \ --timeout 20m \ --skip-version-check \ @@ -62,8 +72,7 @@ docker run --rm \ --ignorefile "$CONTAINER_IGNORE_FILE" \ --skip-files /usr/local/bin/gosu \ --severity HIGH,CRITICAL \ - --exit-code 1 \ - "$TARGET_IMAGE" + --exit-code 1 test -s "$ROOT/$OUTPUT" echo "Container vulnerability report written to $OUTPUT"