Publish curated ITWorx_MCP_Hub source from 2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d

This commit is contained in:
Public Source Publisher committed 2026-10-02 22:43:53 +00:00
1 parent 49627c7a7c
commit 378ae83d30
22 files changed
+4055 -81

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export
This source snapshot was generated from the private canonical repository at revision `8aef76ab42f1ff10ee5a1b3ad94a184120687e47`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
This source snapshot was generated from the private canonical repository at revision `2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+65 -25
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceRevision": "8aef76ab42f1ff10ee5a1b3ad94a184120687e47",
"sourceRevision": "2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d",
"files": [
{
"path": ".dockerignore",
@@ -2037,6 +2037,21 @@
"sha256": "90d0a0017a33ac6e25b74b42f7209d54fcb1424854a56a4d22332fc8af65044c",
"bytes": 702
},
{
"path": "infrastructure/images/contextforge/candidate/build-tools.lock",
"sha256": "4ba5f91f6473fc4fc9971b2353629203099d9409f537895972e60a65890af02f",
"bytes": 741
},
{
"path": "infrastructure/images/contextforge/candidate/locks.json",
"sha256": "28029214650a4d864ff1f6cfdecf4f2c2e1f8438e3f792b35ce52fda90d2d896",
"bytes": 3132
},
{
"path": "infrastructure/images/contextforge/candidate/production-requirements.lock",
"sha256": "daaa815ef1ce2e58c60d9bac840f5754a12eab4b18642713e197d3f5f9467c30",
"bytes": 131433
},
{
"path": "infrastructure/images/contextforge/compat-image.env",
"sha256": "ff35e0be3f6abfe440fb901fec92219410d3d0aef78bc75b7cb0fb679219a81f",
@@ -2047,15 +2062,30 @@
"sha256": "d6f502ecb2f98d929c7c050a67313e3802271ab123cfedd6b3ea7c886a558560",
"bytes": 3095
},
{
"path": "infrastructure/images/contextforge/os-bases/Dockerfile.builder",
"sha256": "0c7962ca473f7b61073e4e57b0211fc84cdedc1a89effcda8d9e4ee796a0a0e0",
"bytes": 1466
},
{
"path": "infrastructure/images/contextforge/os-bases/Dockerfile.runtime",
"sha256": "5ff86c30ed089fc0d74f1b05190059990bf1b1870b66fa33f3f6d5a3b2414848",
"bytes": 1618
},
{
"path": "infrastructure/images/contextforge/runtime-contract.json",
"sha256": "11345ea89e229e7bf164eb037b525b685a5dfb143c08a220544c0b6cdde411d9",
"bytes": 700
},
{
"path": "infrastructure/images/contextforge/security-requirements.txt",
"sha256": "1fdf9cd965f70ce32364bfdb538a5d93cc24559b4a4b69fcd35fb1190d994ecb",
"bytes": 187
},
{
"path": "infrastructure/images/docker-read-broker/Dockerfile",
"sha256": "453d0024e175c77105544756c98f890a7839be2c8475ecbadbff5dddd0f91300",
"bytes": 1153
"sha256": "17d49ab4af92d0c26a0a62491cc5c296ad57d9f758f031d0d57e343361a079cd",
"bytes": 1391
},
{
"path": "infrastructure/images/docker-read-broker/broker_health.py",
@@ -2069,8 +2099,8 @@
},
{
"path": "infrastructure/images/observability/Dockerfile",
"sha256": "27082e021d0f808cb83c8a9f8033b1bebedbbf8ceec9d2407e57b079d0b98088",
"bytes": 1012
"sha256": "c9804d64b0e137721ec5c57e1a7956483a38109dc3063262b19d94a45991e6f9",
"bytes": 1250
},
{
"path": "infrastructure/images/observability/collector.py",
@@ -2084,8 +2114,8 @@
},
{
"path": "infrastructure/images/python-service/Dockerfile",
"sha256": "f00cf3e66c3918a362b02a1dce1efc4ebe383db27fc06474b697e1b220098f72",
"bytes": 2219
"sha256": "011a5b3d2900b996ab3f522d9de09e79cfc1cae76c91aa6c03994c660f0965e3",
"bytes": 2457
},
{
"path": "infrastructure/images/python-service/health_server.py",
@@ -2389,8 +2419,8 @@
},
{
"path": "scripts/build_contextforge_compat_image.sh",
"sha256": "df0fa058b3837add913de32da29bea7a464bcf7cfadff730b3b61edfb431f4da",
"bytes": 8234
"sha256": "b77dee65d61ff2425a1f4de75e38d854c738ae21713ecd992c5609dde866618e",
"bytes": 17296
},
{
"path": "scripts/check_boundaries.py",
@@ -2404,14 +2434,19 @@
},
{
"path": "scripts/check_cve_exceptions.py",
"sha256": "3a140095f2cb6ec7c23760503410236dd644d780c7456d3248276e1ce9bf507d",
"bytes": 6761
"sha256": "6e72a325f0c4733db0be84c2afb90e57dd15060101f2b0d6d3509d6a5031e936",
"bytes": 14228
},
{
"path": "scripts/contextforge_bootstrap.py",
"sha256": "e7ae621d1803f98ce7022c673435088a24863761448b162f8479d403eb63b225",
"bytes": 12239
},
{
"path": "scripts/contextforge_candidate_recipe.py",
"sha256": "d33f5c11d10ab2987128adc264a20ef4dc9913b80c9e55563a1908b2c7154962",
"bytes": 21090
},
{
"path": "scripts/contextforge_runtime_contract.py",
"sha256": "3a93aa6b278a974e7b09688f34ecb9016f298cc859c724e208ff418e5336d6a3",
@@ -2424,8 +2459,8 @@
},
{
"path": "scripts/draft_cve_exceptions.py",
"sha256": "aaebac11f274002eb86562f3ca62a4601bd6eee78b8cb987934db3c407cba000",
"bytes": 4176
"sha256": "15bc47c90ed7f83d790219555d31939e88e92a0cffe02eb0b2da04035eff08b3",
"bytes": 6798
},
{
"path": "scripts/export-public-mirror.py",
@@ -3029,8 +3064,8 @@
},
{
"path": "tests/fixtures/wp200-compose-config.env",
"sha256": "beb0865fd116bdf1fae350803735ec15f273f326b0806b5c9bbf330807673aa6",
"bytes": 2208
"sha256": "4c341dd335e4e32d9f25ca5cc142ad4013d6c1e7c4012dd755840b1e5458441c",
"bytes": 2326
},
{
"path": "tests/test_appops_connector.py",
@@ -3074,8 +3109,8 @@
},
{
"path": "tests/test_compose.py",
"sha256": "2d02f6106ae5bf7cd28e7e5202be7ed721dd9f527f2228ba7df5dcb4a7d9be87",
"bytes": 16814
"sha256": "b0cf2be5b0b53aa6698a30568ad7999e113daa37bd831aa535d1866a0d373fc6",
"bytes": 18737
},
{
"path": "tests/test_contextforge.py",
@@ -3087,10 +3122,15 @@
"sha256": "c2aba6c99e221b31ea0f562b150e60894051d5aa98ae8d51d8ac76ad7147a533",
"bytes": 15850
},
{
"path": "tests/test_contextforge_candidate_recipe.py",
"sha256": "379a240d4ca41dcd04e514fa721c692cd5b6cf23d0d5945f99039b24e2ef3474",
"bytes": 17265
},
{
"path": "tests/test_contextforge_compat_image_contract.py",
"sha256": "430332785d64ddab20e42727c00aeae3a54ffcdb9f80a56dfe5441e09791844a",
"bytes": 4480
"sha256": "d415e2bc7b64144fb9c3f5b0115ab2e8b828516053bc0bbdce3b46e710f87b56",
"bytes": 24559
},
{
"path": "tests/test_contextforge_integration.py",
@@ -3099,8 +3139,8 @@
},
{
"path": "tests/test_contextforge_production_image_contract.py",
"sha256": "fc6fe95a81e2e2265c82f2b3ed679e9d7791735070c9bfcf0926005215307462",
"bytes": 1787
"sha256": "45a656050f73b6e64a626c2407f5ba6fffcfe25886bb42cd408c57a8d64e2633",
"bytes": 2257
},
{
"path": "tests/test_contextforge_promotion_preflight_contract.py",
@@ -3244,8 +3284,8 @@
},
{
"path": "tests/test_ingestion_catalog_compose.py",
"sha256": "835bc9218a27aa26ace1882daf0f5142a54e135ead7575c0f88d89d0272dde24",
"bytes": 10746
"sha256": "60aa6957947ba96974ed0a6941c5e40b8871ec612919f0812e1f4bc1b427a0da",
"bytes": 10888
},
{
"path": "tests/test_ingestion_connector.py",
@@ -3409,8 +3449,8 @@
},
{
"path": "tests/test_wp220_cve_exceptions.py",
"sha256": "afdd0aebfca0c2bb379edbd91d700f27fb6684f85a3b77ed339e7f7f901b03b4",
"bytes": 6217
"sha256": "ceae7f0a167ae8083be4daee0e291b2cb669c90877e2a88113861e85d038b07b",
"bytes": 18080
},
{
"path": "tests/test_wp220_export.py",
@@ -0,0 +1,12 @@
packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
pip==26.2.1 \
--hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \
--hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f
setuptools==82.0.1 \
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
wheel==0.46.2 \
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
--hash=sha256:3d79e48fde9847618a5a181f3cc35764c349c752e2fe911e65fa17faab9809b0
@@ -0,0 +1,65 @@
{
"baseImages": [
{
"arg": "UBI_BASE",
"childManifestDigest": "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483",
"imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8",
"parentReference": "registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8",
"platform": "linux/amd64",
"reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483",
"rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0",
"sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8"
},
{
"arg": "NODEJS_IMAGE",
"childManifestDigest": "sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06",
"platform": "linux/amd64",
"reference": "registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06",
"sourceTag": "registry.access.redhat.com/ubi10/nodejs-24:1784092609"
},
{
"arg": "UBI_MINIMAL",
"childManifestDigest": "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236",
"imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b",
"parentReference": "registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078",
"platform": "linux/amd64",
"reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236",
"rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034",
"sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b"
}
],
"buildTools": [
"pip==26.2.1",
"setuptools==82.0.1",
"wheel==0.46.2"
],
"constraints": [
"mcp==1.30.0",
"PyJWT==2.15.0",
"urllib3==2.8.0"
],
"extras": [
"redis",
"postgres",
"observability",
"granian",
"plugins",
"llmchat"
],
"freshnessOverrideUtc": "2026-10-02T00:00:00Z",
"globalExcludeNewerUtc": "2026-09-22T00:00:00Z",
"platform": "linux/amd64",
"python": "3.12",
"requirements": {
"build-tools.lock": "4ba5f91f6473fc4fc9971b2353629203099d9409f537895972e60a65890af02f",
"production-requirements.lock": "daaa815ef1ce2e58c60d9bac840f5754a12eab4b18642713e197d3f5f9467c30"
},
"schema": "itworx.contextforge-candidate-locks/v1",
"upstreamCommit": "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1",
"upstreamFiles": {
"Containerfile": "2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2",
"pyproject.toml": "cd365566f922546e569c65475d78cd53e5d49f10df2c82534efa607ffcf55e4b",
"uv.lock": "c5297a4701e4a0ecb9903bd3f4aa1c8cf40cf4e5568880fcb8f4078703589df1"
},
"uvVersion": "0.12.10"
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1.12
# Scratch-derived, linux/amd64 only. Freeze by registry manifest digest after validation.
FROM registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8
LABEL io.itworx.contextforge.os-base.role="builder" \
io.itworx.contextforge.os-base.upstream-commit="1e58a0434a78bbdbe1e975abeb0bed1043ff14c1" \
io.itworx.contextforge.os-base.upstream-containerfile-sha256="2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2" \
io.itworx.contextforge.os-base.parent-ref="registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8" \
io.itworx.contextforge.os-base.platform="linux/amd64"
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
RUN set -euo pipefail \
&& dnf upgrade -y \
&& dnf install -y --allowerasing \
python3.12 \
python3.12-devel \
binutils openssl-devel gcc postgresql-devel gcc-c++ curl libpq-devel \
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 \
&& dnf clean all
RUN set -eu \
&& mkdir -p /usr/share/itworx \
&& rpm -qa --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{ARCH}\n' > /tmp/itworx-rpm-inventory.unsorted \
&& LC_ALL=C sort /tmp/itworx-rpm-inventory.unsorted > /usr/share/itworx/rpm-inventory.txt \
&& rm /tmp/itworx-rpm-inventory.unsorted \
&& chmod 0444 /usr/share/itworx/rpm-inventory.txt
@@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1.12
# Scratch-derived, linux/amd64 only. Freeze by registry manifest digest after validation.
FROM registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078
LABEL io.itworx.contextforge.os-base.role="runtime" \
io.itworx.contextforge.os-base.upstream-commit="1e58a0434a78bbdbe1e975abeb0bed1043ff14c1" \
io.itworx.contextforge.os-base.upstream-containerfile-sha256="2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2" \
io.itworx.contextforge.os-base.parent-ref="registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078" \
io.itworx.contextforge.os-base.platform="linux/amd64"
RUN microdnf upgrade -y --nodocs --setopt=install_weak_deps=0 \
&& microdnf install -y --nodocs --setopt=install_weak_deps=0 \
python3.12 \
ca-certificates \
procps-ng \
shadow-utils \
libpq \
&& microdnf clean all \
&& rm -rf /var/cache/yum \
&& ln -sf /usr/bin/python3.12 /usr/bin/python3 \
&& groupadd --gid 10001 app \
&& useradd --uid 10001 --gid 10001 --home-dir /app --shell /sbin/nologin --no-create-home --comment app app
RUN set -eu \
&& mkdir -p /usr/share/itworx \
&& rpm -qa --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{ARCH}\n' > /tmp/itworx-rpm-inventory.unsorted \
&& LC_ALL=C sort /tmp/itworx-rpm-inventory.unsorted > /usr/share/itworx/rpm-inventory.txt \
&& rm /tmp/itworx-rpm-inventory.unsorted \
&& chmod 0444 /usr/share/itworx/rpm-inventory.txt
@@ -0,0 +1,2 @@
PyJWT==2.15.0 --hash=sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8
urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3
@@ -6,6 +6,8 @@ RUN sh /tmp/install-base-security-updates.sh \
&& rm /tmp/install-base-security-updates.sh
ARG GIT_REVISION
ARG GIT_SOURCE_TREE
ARG IMAGE_VERSION
ARG SOURCE_DATE_EPOCH
ARG BUILD_DATE
@@ -20,6 +22,9 @@ RUN chown -R 10002:10002 /opt/broker
LABEL org.opencontainers.image.title="itworx-mcp-hub-docker-read-broker" \
org.opencontainers.image.revision="${GIT_REVISION}" \
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \
org.opencontainers.image.version="${IMAGE_VERSION}" \
org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.vendor="ITWorx"
@@ -6,6 +6,8 @@ RUN sh /tmp/install-base-security-updates.sh \
&& rm /tmp/install-base-security-updates.sh
ARG GIT_REVISION
ARG GIT_SOURCE_TREE
ARG IMAGE_VERSION
ARG SOURCE_DATE_EPOCH
ARG BUILD_DATE
@@ -17,6 +19,9 @@ RUN chown -R 10001:10001 /opt/hub
LABEL org.opencontainers.image.title="itworx-mcp-hub-observability-collector" \
org.opencontainers.image.revision="${GIT_REVISION}" \
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \
org.opencontainers.image.version="${IMAGE_VERSION}" \
org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.vendor="ITWorx"
@@ -18,6 +18,8 @@ RUN sh /tmp/install-base-security-updates.sh \
ARG SERVICE_NAME=itworx-mcp-hub-service
ARG GIT_REVISION
ARG GIT_SOURCE_TREE
ARG IMAGE_VERSION
ARG SOURCE_DATE_EPOCH
ARG BUILD_DATE
@@ -49,6 +51,9 @@ RUN chown -R 10001:10001 /opt/hub
LABEL org.opencontainers.image.title="itworx-mcp-hub-${SERVICE_NAME}" \
org.opencontainers.image.revision="${GIT_REVISION}" \
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \
org.opencontainers.image.version="${IMAGE_VERSION}" \
org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.vendor="ITWorx"
+179 -9
View File
@@ -1,13 +1,25 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Candidate-only mode emits a local SHA-scoped image and does not publish or deploy it.
MODE="legacy"
if [[ "$#" -gt 0 ]]; then
[[ "$#" -eq 1 && "$1" == "--release-candidate" ]] || {
echo "FATAL: supported mode is --release-candidate (or no arguments for legacy mode)" >&2
exit 64
}
MODE="release-candidate"
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MANIFEST="$ROOT/infrastructure/images/contextforge/compat-image.env"
CONTRACT="$ROOT/infrastructure/images/contextforge/runtime-contract.json"
ENTRYPOINT="$ROOT/infrastructure/images/contextforge/entrypoint.sh"
RUNTIME_VERIFIER="$ROOT/scripts/contextforge_runtime_contract.py"
SECURITY_REQUIREMENTS="$ROOT/infrastructure/images/contextforge/security-requirements.txt"
CANDIDATE_RECIPE="$ROOT/scripts/contextforge_candidate_recipe.py"
for required in "$MANIFEST" "$CONTRACT" "$ENTRYPOINT" "$RUNTIME_VERIFIER"; do
for required in "$MANIFEST" "$CONTRACT" "$ENTRYPOINT" "$RUNTIME_VERIFIER" "$SECURITY_REQUIREMENTS"; do
[[ -f "$required" ]] || {
echo "FATAL: required ContextForge compatibility input is missing: $required" >&2
exit 66
@@ -22,15 +34,87 @@ source "$MANIFEST"
: "${CONTEXTFORGE_MCP_SDK:?missing MCP SDK version}"
: "${CONTEXTFORGE_LOCAL_IMAGE:?missing local output image}"
if [[ "$MODE" == "release-candidate" ]]; then
[[ -f "$CANDIDATE_RECIPE" ]] || {
echo "FATAL: the source-locked candidate recipe helper is missing" >&2
exit 66
}
# Validate every interpolated value before invoking Git, Docker, or Python.
# These fixed-format fields become Docker tags, Git comparisons, and labels.
: "${APPOPS_SOURCE_REPOSITORY:?missing APPOPS_SOURCE_REPOSITORY}"
: "${APPOPS_SOURCE_REVISION:?missing APPOPS_SOURCE_REVISION}"
: "${APPOPS_SOURCE_TREE:?missing APPOPS_SOURCE_TREE}"
: "${APPOPS_BUILD_ID:?missing APPOPS_BUILD_ID}"
: "${APPOPS_BUILD_DATE:?missing APPOPS_BUILD_DATE}"
: "${SOURCE_DATE_EPOCH:?missing SOURCE_DATE_EPOCH}"
: "${GITEA_REPOSITORY:?missing GITEA_REPOSITORY}"
: "${GITEA_COMMIT_SHA:?missing GITEA_COMMIT_SHA}"
[[ "$APPOPS_SOURCE_REPOSITORY" == "jens/itworx_mcp_hub" ]] || {
echo "FATAL: release-candidate source repository is not the protected Hub repository" >&2
exit 64
}
[[ "$GITEA_REPOSITORY" == "$APPOPS_SOURCE_REPOSITORY" ]] || {
echo "FATAL: Gitea and AppOps repositories disagree" >&2
exit 64
}
[[ "$APPOPS_SOURCE_REVISION" =~ ^[0-9a-f]{40}$ ]] || {
echo "FATAL: APPOPS_SOURCE_REVISION must be a full lowercase Git SHA" >&2
exit 64
}
[[ "$GITEA_COMMIT_SHA" == "$APPOPS_SOURCE_REVISION" ]] || {
echo "FATAL: Gitea and AppOps revisions disagree" >&2
exit 64
}
[[ "$APPOPS_SOURCE_TREE" =~ ^[0-9a-f]{40}$ ]] || {
echo "FATAL: APPOPS_SOURCE_TREE must be a full lowercase Git tree SHA" >&2
exit 64
}
[[ "$APPOPS_BUILD_ID" == "${APPOPS_SOURCE_REVISION:0:12}" ]] || {
echo "FATAL: APPOPS_BUILD_ID must be the first 12 revision characters" >&2
exit 64
}
[[ "$APPOPS_BUILD_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$ ]] || {
echo "FATAL: APPOPS_BUILD_DATE must be canonical UTC with millisecond precision" >&2
exit 64
}
[[ "$SOURCE_DATE_EPOCH" =~ ^[1-9][0-9]{0,11}$ ]] || {
echo "FATAL: SOURCE_DATE_EPOCH must be a canonical positive integer of at most 12 digits" >&2
exit 64
}
[[ "$CONTEXTFORGE_LOCAL_IMAGE" == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c" ]] || {
echo "FATAL: compatibility manifest image name is not the reviewed Hub image" >&2
exit 64
}
fi
command -v git >/dev/null 2>&1 || { echo "FATAL: git is required" >&2; exit 69; }
command -v docker >/dev/null 2>&1 || { echo "FATAL: docker is required" >&2; exit 69; }
command -v python3 >/dev/null 2>&1 || { echo "FATAL: python3 is required" >&2; exit 69; }
if [[ "$MODE" == "release-candidate" ]]; then
python3 - "$APPOPS_BUILD_DATE" <<'PY'
from datetime import datetime
import sys
try:
datetime.strptime(sys.argv[1], "%Y-%m-%dT%H:%M:%S.%fZ")
except ValueError as exc:
raise SystemExit("FATAL: APPOPS_BUILD_DATE is not a valid UTC calendar timestamp") from exc
PY
fi
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
SRC="$TMP/contextforge"
BASE_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-upstream"
STAGED_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-staged"
if [[ "$MODE" == "release-candidate" ]]; then
CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}"
BASE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}-upstream"
STAGED_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}-staged"
else
BASE_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-upstream"
STAGED_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-staged"
fi
REVISION_PREFIX="${CONTEXTFORGE_ALEMBIC_HEAD}_"
MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"
@@ -70,12 +154,41 @@ CONTRACT_REQUIRED_SYMBOL="$(read_contract requiredSymbol)"
CONTRACT_SHA256="$(sha256_file "$CONTRACT")"
ENTRYPOINT_SHA256="$(sha256_file "$ENTRYPOINT")"
VERIFIER_SHA256="$(sha256_file "$RUNTIME_VERIFIER")"
SECURITY_REQUIREMENTS_SHA256="$(sha256_file "$SECURITY_REQUIREMENTS")"
[[ "$CONTRACT_SOURCE_COMMIT" == "$CONTEXTFORGE_UPSTREAM_COMMIT" ]] || fail "contract source commit disagrees with compatibility manifest"
[[ "$CONTRACT_ALEMBIC_HEAD" == "$CONTEXTFORGE_ALEMBIC_HEAD" ]] || fail "contract Alembic head disagrees with compatibility manifest"
[[ "$CONTRACT_MCP_SDK" == "$CONTEXTFORGE_MCP_SDK" ]] || fail "contract MCP SDK disagrees with compatibility manifest"
[[ "$CONTRACT_REQUIRED_SYMBOL" == "McpError" ]] || fail "unsupported required MCP symbol: $CONTRACT_REQUIRED_SYMBOL"
if [[ "$MODE" == "release-candidate" ]]; then
[[ -z "$(git -C "$ROOT" status --porcelain --untracked-files=all)" ]] || fail "Hub source checkout has changes"
[[ "$(git -C "$ROOT" rev-parse HEAD)" == "$APPOPS_SOURCE_REVISION" ]] || fail "checked-out Hub revision differs from frozen release metadata"
[[ "$(git -C "$ROOT" rev-parse 'HEAD^{tree}')" == "$APPOPS_SOURCE_TREE" ]] || fail "checked-out Hub source tree differs from frozen release metadata"
[[ "$(git -C "$ROOT" show -s --format=%ct HEAD)" == "$SOURCE_DATE_EPOCH" ]] || fail "source date epoch differs from frozen release metadata"
ensure_candidate_tag_absent() {
local image="$1"
local diagnostic="$TMP/docker-image-inspect.err"
if docker image inspect "$image" >/dev/null 2>"$diagnostic"; then
fail "release-candidate image tag already exists: $image"
fi
local error_line
IFS= read -r error_line < "$diagnostic" || true
case "$error_line" in
*"No such image"*|*"no such image"*|*"No such object"*|*"no such object"*|*"reference does not exist"*) ;;
*) fail "could not prove release-candidate image tag is absent: $image" ;;
esac
}
# Never build over or retag an existing revision-scoped image. Docker errors
# other than its explicit not-found response fail closed.
ensure_candidate_tag_absent "$BASE_IMAGE"
ensure_candidate_tag_absent "$STAGED_IMAGE"
ensure_candidate_tag_absent "$CANDIDATE_IMAGE"
fi
echo "=== FETCH EXACT CONTEXTFORGE SOURCE ==="
git init -q "$SRC"
git -C "$SRC" remote add origin https://github.com/IBM/mcp-context-forge.git
@@ -97,13 +210,28 @@ echo "contract sha : $CONTRACT_SHA256"
echo
echo "=== BUILD UPSTREAM SOURCE IMAGE ==="
docker build --pull=false -f "$SRC/Containerfile" -t "$BASE_IMAGE" "$SRC"
if [[ "$MODE" == "release-candidate" ]]; then
python3 "$CANDIDATE_RECIPE" prepare --source-dir "$SRC"
BUILD_ARG_FILE="$TMP/contextforge-build-args.txt"
python3 "$CANDIDATE_RECIPE" build-args > "$BUILD_ARG_FILE"
mapfile -t CANDIDATE_BUILD_ARGS < "$BUILD_ARG_FILE"
[[ "${#CANDIDATE_BUILD_ARGS[@]}" -eq 9 ]] || fail "candidate base/build argument contract is incomplete"
CANDIDATE_DOCKER_ARGS=()
for build_arg in "${CANDIDATE_BUILD_ARGS[@]}"; do
[[ "$build_arg" =~ ^[A-Z_]+=([A-Za-z0-9./:@_-]+)$ ]] || fail "candidate build argument is malformed"
CANDIDATE_DOCKER_ARGS+=(--build-arg "$build_arg")
done
docker build --pull=false --platform linux/amd64 "${CANDIDATE_DOCKER_ARGS[@]}" -f "$SRC/Containerfile.itworx-candidate" -t "$BASE_IMAGE" "$SRC"
else
docker build --pull=false -f "$SRC/Containerfile" -t "$BASE_IMAGE" "$SRC"
fi
BASE_USER="$(docker image inspect -f '{{.Config.User}}' "$BASE_IMAGE")"
LAYER="$TMP/Containerfile.compat"
cp "$CONTRACT" "$TMP/contextforge-runtime-contract.json"
cp "$ENTRYPOINT" "$TMP/contextforge-entrypoint.sh"
cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py"
cp "$SECURITY_REQUIREMENTS" "$TMP/contextforge-security-requirements.txt"
{
echo "FROM $BASE_IMAGE"
@@ -111,6 +239,7 @@ cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py"
echo 'COPY contextforge-runtime-contract.json /opt/itworx/contextforge-runtime-contract.json'
echo 'COPY contextforge-entrypoint.sh /opt/itworx/contextforge-entrypoint.sh'
echo 'COPY contextforge-runtime-contract.py /opt/itworx/contextforge-runtime-contract.py'
echo 'COPY contextforge-security-requirements.txt /opt/itworx/contextforge-security-requirements.txt'
echo 'RUN chmod 0555 /opt/itworx/contextforge-entrypoint.sh /opt/itworx/contextforge-runtime-contract.py'
printf 'LABEL io.itworx.contextforge.contract-sha256="%s" \\\n' "$CONTRACT_SHA256"
printf ' io.itworx.contextforge.entrypoint-sha256="%s" \\\n' "$ENTRYPOINT_SHA256"
@@ -118,8 +247,24 @@ cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py"
printf ' io.itworx.contextforge.source-commit="%s" \\\n' "$CONTEXTFORGE_UPSTREAM_COMMIT"
printf ' io.itworx.contextforge.alembic-head="%s" \\\n' "$CONTEXTFORGE_ALEMBIC_HEAD"
printf ' io.itworx.contextforge.mcp-sdk="%s"\n' "$CONTEXTFORGE_MCP_SDK"
printf 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --upgrade %q \\\n' "$MCP_REQUIREMENT"
printf '%s\n' ' && /app/.venv/bin/python -c "import importlib.metadata as md; from mcp import McpError; assert md.version(\"mcp\") == \"'"${CONTEXTFORGE_MCP_SDK}"'\"; print(\"MCP SDK\", md.version(\"mcp\"), \"McpError OK\")"'
printf 'LABEL io.itworx.contextforge.security-requirements-sha256="%s"\n' "$SECURITY_REQUIREMENTS_SHA256"
if [[ "$MODE" == "release-candidate" ]]; then
printf 'LABEL org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \\\n'
printf ' org.opencontainers.image.revision="%s" \\\n' "$APPOPS_SOURCE_REVISION"
printf ' org.opencontainers.image.source-tree="%s" \\\n' "$APPOPS_SOURCE_TREE"
printf ' org.opencontainers.image.version="%s" \\\n' "$APPOPS_BUILD_ID"
printf ' org.opencontainers.image.source-date-epoch="%s" \\\n' "$SOURCE_DATE_EPOCH"
printf ' org.opencontainers.image.created="%s"\n' "$APPOPS_BUILD_DATE"
fi
if [[ "$MODE" == "release-candidate" ]]; then
echo 'RUN /app/.venv/bin/python -c "import importlib.metadata as md; import importlib.util; from mcp import McpError; assert md.version(\"mcp\") == \"1.30.0\"; assert md.version(\"PyJWT\") == \"2.15.0\"; assert md.version(\"urllib3\") == \"2.8.0\"; assert all(importlib.util.find_spec(name) is None for name in (\"pip\", \"setuptools\", \"wheel\", \"uv\"))"'
else
printf 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --upgrade %q \\\n' "$MCP_REQUIREMENT"
printf '%s\n' ' && /app/.venv/bin/python -c "import importlib.metadata as md; from mcp import McpError; assert md.version(\"mcp\") == \"'"${CONTEXTFORGE_MCP_SDK}"'\"; print(\"MCP SDK\", md.version(\"mcp\"), \"McpError OK\")"'
echo 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --no-deps --require-hashes --only-binary=:all: -r /opt/itworx/contextforge-security-requirements.txt \'
echo ' && /app/.venv/bin/python -m pip check \'
echo ' && /app/.venv/bin/python -c "import importlib.metadata as md; assert md.version(\"PyJWT\") == \"2.15.0\"; assert md.version(\"urllib3\") == \"2.8.0\""'
fi
if [[ -n "$BASE_USER" ]]; then
printf 'USER %s\n' "$BASE_USER"
fi
@@ -142,6 +287,7 @@ embedded_sha() {
[[ "$(embedded_sha /opt/itworx/contextforge-runtime-contract.json)" == "$CONTRACT_SHA256" ]] || fail "embedded runtime contract digest mismatch"
[[ "$(embedded_sha /opt/itworx/contextforge-entrypoint.sh)" == "$ENTRYPOINT_SHA256" ]] || fail "embedded entrypoint digest mismatch"
[[ "$(embedded_sha /opt/itworx/contextforge-runtime-contract.py)" == "$VERIFIER_SHA256" ]] || fail "embedded verifier digest mismatch"
[[ "$(embedded_sha /opt/itworx/contextforge-security-requirements.txt)" == "$SECURITY_REQUIREMENTS_SHA256" ]] || fail "embedded security requirements digest mismatch"
label_value() {
docker image inspect -f "{{ index .Config.Labels \"$1\" }}" "$STAGED_IMAGE"
@@ -152,19 +298,43 @@ label_value() {
[[ "$(label_value io.itworx.contextforge.source-commit)" == "$CONTEXTFORGE_UPSTREAM_COMMIT" ]] || fail "source label mismatch"
[[ "$(label_value io.itworx.contextforge.alembic-head)" == "$CONTEXTFORGE_ALEMBIC_HEAD" ]] || fail "Alembic label mismatch"
[[ "$(label_value io.itworx.contextforge.mcp-sdk)" == "$CONTEXTFORGE_MCP_SDK" ]] || fail "MCP SDK label mismatch"
[[ "$(label_value io.itworx.contextforge.security-requirements-sha256)" == "$SECURITY_REQUIREMENTS_SHA256" ]] || fail "security requirements label mismatch"
if [[ "$MODE" == "release-candidate" ]]; then
[[ "$(label_value org.opencontainers.image.source)" == "https://gitea.example.com/jens/itworx-mcp-hub" ]] || fail "OCI source label mismatch"
[[ "$(label_value org.opencontainers.image.revision)" == "$APPOPS_SOURCE_REVISION" ]] || fail "OCI revision label mismatch"
[[ "$(label_value org.opencontainers.image.source-tree)" == "$APPOPS_SOURCE_TREE" ]] || fail "OCI source tree label mismatch"
[[ "$(label_value org.opencontainers.image.version)" == "$APPOPS_BUILD_ID" ]] || fail "OCI version label mismatch"
[[ "$(label_value org.opencontainers.image.source-date-epoch)" == "$SOURCE_DATE_EPOCH" ]] || fail "OCI source date epoch label mismatch"
[[ "$(label_value org.opencontainers.image.created)" == "$APPOPS_BUILD_DATE" ]] || fail "OCI created label mismatch"
fi
echo "MCP SDK : $CONTEXTFORGE_MCP_SDK"
echo "Alembic head : $IMAGE_HEAD"
echo "Contract SHA : $CONTRACT_SHA256"
docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"
if [[ "$MODE" == "release-candidate" ]]; then
# Recheck immediately before publishing. There is intentionally no cleanup or
# retagging of any static compatibility image in candidate mode.
ensure_candidate_tag_absent "$CANDIDATE_IMAGE"
docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE"
else
docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"
fi
echo
echo "======================================================"
echo " SUCCESS: CONTEXTFORGE COMPATIBILITY IMAGE VERIFIED"
if [[ "$MODE" == "release-candidate" ]]; then
echo " CANDIDATE IMAGE BUILT AND VERIFIED (NOT APPROVED OR DEPLOYED)"
else
echo " SUCCESS: CONTEXTFORGE COMPATIBILITY IMAGE VERIFIED"
fi
echo " source commit : $CONTEXTFORGE_UPSTREAM_COMMIT"
echo " Alembic head : $CONTEXTFORGE_ALEMBIC_HEAD"
echo " MCP SDK : $CONTEXTFORGE_MCP_SDK"
echo " contract SHA : $CONTRACT_SHA256"
echo " local image : $CONTEXTFORGE_LOCAL_IMAGE"
if [[ "$MODE" == "release-candidate" ]]; then
echo " candidate tag : $CANDIDATE_IMAGE"
else
echo " local image : $CONTEXTFORGE_LOCAL_IMAGE"
fi
echo "======================================================"
+190 -22
View File
@@ -12,9 +12,8 @@ Specifically fails on:
grant);
- a matching exception that has expired (expiryDate in the past) or is past
its mandatory rescan date;
- a matching exception whose recorded affectedImageDigests disagree with
--image-digests for any service both documents name (the digest changed --
the exception was reviewed against a build that no longer exists);
- a matching exception without a valid image binding for each affected
service, or whose binding differs from the current image inventory;
- an exception missing any required field.
See docs/09-release/CVE_EXCEPTIONS.md for the full policy and
@@ -25,6 +24,7 @@ context. This script has no "--force"/override flag by design.
from __future__ import annotations
import argparse
import re
import sys
from datetime import date
from pathlib import Path
@@ -36,6 +36,8 @@ if str(_ROOT_DIR) not in sys.path:
from scripts.packlib import load_json # noqa: E402
_SHA256_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$")
REQUIRED_FIELDS = (
"id",
"cve",
@@ -57,24 +59,134 @@ REQUIRED_FIELDS = (
)
def _findings(scan: dict[str, Any]) -> list[tuple[str, str, str]]:
"""Return (package, cve, severity) for every CRITICAL/HIGH finding."""
def _findings(scan: dict[str, Any]) -> list[tuple[dict[str, Any], str]]:
"""Return (scan row, CVE) pairs for every CRITICAL/HIGH finding."""
findings = []
for row in scan.get("unresolvedCriticalOrHigh", []):
for cve in row["cves"]:
findings.append((row["package"], cve, row.get("severity", "UNKNOWN")))
findings.append((row, cve))
return findings
def _current_image_inventory(
image_digests: Any,
) -> tuple[dict[str, dict[str, str]], list[str]]:
"""Validate immutable current image bindings while retaining optional tags."""
errors: list[str] = []
if not isinstance(image_digests, dict) or not image_digests:
return {}, ["IMAGE INVENTORY INVALID: --image-digests must contain a non-empty images map"]
inventory: dict[str, dict[str, str]] = {}
for service, entry in image_digests.items():
if not isinstance(service, str) or not service.strip():
errors.append("IMAGE INVENTORY INVALID: every image service needs a non-empty name")
continue
if not isinstance(entry, dict):
errors.append(f"IMAGE INVENTORY INVALID: {service} must be an image evidence object")
continue
image_id = entry.get("imageId")
if not isinstance(image_id, str) or not _SHA256_IMAGE_ID.fullmatch(image_id):
errors.append(f"CURRENT IMAGE ID INVALID: {service} needs a sha256 imageId")
continue
if "digest" in entry:
digest = entry.get("digest")
if not isinstance(digest, str) or not _SHA256_IMAGE_ID.fullmatch(digest):
errors.append(f"CURRENT IMAGE DIGEST INVALID: {service} has a malformed digest")
continue
binding = digest
else:
# Older v1 inventory evidence records the immutable local image ID
# instead of a registry manifest digest.
binding = image_id
tag = entry.get("tag")
if tag is not None and (not isinstance(tag, str) or not tag.strip()):
errors.append(f"IMAGE INVENTORY INVALID: {service} has a malformed tag")
continue
inventory[service] = {"binding": binding, "imageId": image_id}
if isinstance(tag, str):
inventory[service]["tag"] = tag
if not inventory:
errors.append("IMAGE INVENTORY INVALID: no valid current image bindings were supplied")
return inventory, errors
def _affected_services(
row: dict[str, Any], inventory: dict[str, dict[str, str]]
) -> tuple[list[str], list[str]]:
"""Resolve scan image tags to services, or use the legacy full inventory."""
if "affectedImages" not in row:
# Legacy v1 scan rows had no affectedImages field. Requiring the
# exception to cover the whole supplied inventory is the safe fallback.
return list(inventory), []
affected_images = row.get("affectedImages")
if (
not isinstance(affected_images, list)
or not affected_images
or any(not isinstance(image, str) or not image.strip() for image in affected_images)
or len(set(affected_images)) != len(affected_images)
):
return [], ["SCAN AFFECTED IMAGES INVALID: expected a non-empty list of unique image tags"]
services_by_tag: dict[str, list[str]] = {}
for service, evidence in inventory.items():
tag = evidence.get("tag")
if tag:
services_by_tag.setdefault(tag, []).append(service)
services: list[str] = []
errors: list[str] = []
for image in affected_images:
matches = services_by_tag.get(image, [])
if not matches:
errors.append(
f"AFFECTED IMAGE UNBOUND: scan image {image} has no current service binding"
)
elif len(matches) > 1:
errors.append(f"AFFECTED IMAGE AMBIGUOUS: scan image {image} maps to multiple services")
else:
services.append(matches[0])
return services, errors
def check(
scan: dict[str, Any],
exceptions_doc: dict[str, Any],
image_digests: dict[str, str] | None,
image_digests: dict[str, Any] | None,
today: date,
) -> list[str]:
errors: list[str] = []
by_key: dict[tuple[str, str], dict[str, Any]] = {}
image_inventory: dict[str, dict[str, str]] = {}
if image_digests is not None:
image_inventory, inventory_errors = _current_image_inventory(image_digests)
errors.extend(inventory_errors)
by_key: dict[tuple[str, str, str], dict[str, Any]] = {}
raw_by_key: dict[tuple[str, str, str], dict[str, Any]] = {}
conflicting_keys: set[tuple[str, str, str]] = set()
for exception in exceptions_doc.get("exceptions", []):
if not isinstance(exception, dict):
errors.append("exception entry must be an object")
continue
package = exception.get("package")
cve = exception.get("cve")
version = exception.get("installedVersion")
key: tuple[str, str, str] | None = None
if isinstance(package, str) and isinstance(cve, str) and isinstance(version, str):
if package and cve and version:
key = (package, cve, version)
if key is not None and key in raw_by_key and raw_by_key[key] != exception:
conflicting_keys.add(key)
errors.append(
f"CONFLICTING DUPLICATE: multiple exception entries disagree for {package}/{cve}"
)
elif key is not None:
raw_by_key.setdefault(key, exception)
missing = [field for field in REQUIRED_FIELDS if not exception.get(field)]
# riskOwner/approver/compensatingControls may be legitimately empty
# only while approvalStatus is "pending" -- once "approved" they are
@@ -87,12 +199,44 @@ def check(
f"exception {exception.get('id', '<no id>')} is missing required fields: {missing}"
)
continue
key = (exception["package"], exception["cve"])
by_key[key] = exception
if key is not None:
by_key.setdefault(key, exception)
for package, cve, severity in _findings(scan):
key = (package, cve)
exception = by_key.get(key)
for key in conflicting_keys:
by_key.pop(key, None)
by_pair: dict[tuple[str, str], list[dict[str, Any]]] = {}
for (package, cve, _version), exception in by_key.items():
by_pair.setdefault((package, cve), []).append(exception)
for row, cve in _findings(scan):
package = row["package"]
severity = row.get("severity", "UNKNOWN")
reviewed = by_pair.get((package, cve), [])
if "installedVersion" in row:
scanned_version = row.get("installedVersion")
if not isinstance(scanned_version, str) or not scanned_version:
errors.append(
f"SCAN VERSION MISSING: {package}/{cve} has no valid installedVersion"
)
continue
exception = by_key.get((package, cve, scanned_version))
if exception is None and reviewed:
errors.append(
f"VERSION MISMATCH: {package}/{cve} scan found {scanned_version}, "
"but no exception reviewed that exact version"
)
continue
else:
# Existing v1 evidence may omit the installed version. It remains
# usable only when one unambiguous reviewed version exists.
if len(reviewed) > 1:
errors.append(
f"AMBIGUOUS VERSION: {package}/{cve} legacy scan omits installedVersion "
"and multiple versions were reviewed"
)
continue
exception = reviewed[0] if reviewed else None
if exception is None:
errors.append(
f"UNAPPROVED: {severity} finding {package}/{cve} has no exception recorded at all"
@@ -128,11 +272,38 @@ def check(
f"{rescan_due.isoformat()} has passed"
)
if image_digests:
if image_digests is not None:
legacy_full_inventory = "affectedImages" not in row
services, scope_errors = _affected_services(row, image_inventory)
errors.extend(scope_errors)
recorded = exception.get("affectedImageDigests", {})
for service, current_digest in image_digests.items():
if not isinstance(recorded, dict):
errors.append(
f"IMAGE BINDING INVALID: {package}/{cve} affectedImageDigests must be an object"
)
continue
if legacy_full_inventory and set(recorded) != set(image_inventory):
errors.append(
f"IMAGE INVENTORY SCOPE CHANGED: {package}/{cve} uses a legacy scan "
"without affectedImages and its reviewed service set does not exactly "
"match the current full inventory"
)
for service in services:
current_digest = image_inventory[service]["binding"]
recorded_digest = recorded.get(service)
if recorded_digest and recorded_digest != current_digest:
if recorded_digest is None or recorded_digest == "":
errors.append(
f"IMAGE BINDING MISSING: {package}/{cve} has no valid reviewed digest "
f"for {service}"
)
elif not isinstance(recorded_digest, str) or not _SHA256_IMAGE_ID.fullmatch(
recorded_digest
):
errors.append(
f"IMAGE BINDING INVALID: {package}/{cve} has a malformed reviewed digest "
f"for {service}"
)
elif recorded_digest != current_digest:
errors.append(
f"DIGEST CHANGED: {package}/{cve} exception recorded "
f"{service}={recorded_digest}, but the current build is "
@@ -151,19 +322,16 @@ def main() -> int:
"--image-digests",
type=Path,
help="evidence/<packet>/image-digests.json; when given, exceptions whose "
"recorded digest for a named service disagrees with the current build fail.",
"current image IDs or reviewed bindings are missing, malformed, or changed fail.",
)
args = parser.parse_args()
scan = load_json(args.scan_summary)
exceptions_doc = load_json(args.exceptions)
image_digests: dict[str, str] | None = None
image_digests: dict[str, Any] | None = None
if args.image_digests:
digest_doc = load_json(args.image_digests)
image_digests = {
service: (entry.get("digest") or entry.get("imageId"))
for service, entry in digest_doc.get("images", {}).items()
}
image_digests = digest_doc.get("images", {})
errors = check(scan, exceptions_doc, image_digests, date.today())
if errors:
+497
View File
@@ -0,0 +1,497 @@
#!/usr/bin/env python3
"""Prepare a hash-locked, source-checked ContextForge candidate build recipe."""
# Exact image references, source anchors, and Dockerfile instructions exceed 100 columns.
# ruff: noqa: E501
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
LOCK_DIR = ROOT / "infrastructure" / "images" / "contextforge" / "candidate"
LOCK_MANIFEST = LOCK_DIR / "locks.json"
PRODUCTION_LOCK = LOCK_DIR / "production-requirements.lock"
BUILD_TOOLS_LOCK = LOCK_DIR / "build-tools.lock"
UPSTREAM_COMMIT = "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1"
UV_VERSION = "0.12.10"
LOCK_TIMESTAMP = "2026-10-02T00:00:00Z"
GLOBAL_LOCK_CUTOFF = "2026-09-22T00:00:00Z"
EXTRAS = ("redis", "postgres", "observability", "granian", "plugins", "llmchat")
CONSTRAINTS = ("mcp==1.30.0", "PyJWT==2.15.0", "urllib3==2.8.0")
BUILD_TOOLS = ("pip==26.2.1", "setuptools==82.0.1", "wheel==0.46.2")
SOURCE_HASHES = {
"Containerfile": "2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2",
"pyproject.toml": "cd365566f922546e569c65475d78cd53e5d49f10df2c82534efa607ffcf55e4b",
"uv.lock": "c5297a4701e4a0ecb9903bd3f4aa1c8cf40cf4e5568880fcb8f4078703589df1",
}
BASE_IMAGES = (
{
"arg": "UBI_BASE",
"sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8",
"reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483",
"parentReference": "registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8",
"imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8",
"rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0",
},
{
"arg": "NODEJS_IMAGE",
"sourceTag": "registry.access.redhat.com/ubi10/nodejs-24:1784092609",
"reference": "registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06",
},
{
"arg": "UBI_MINIMAL",
"sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b",
"reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236",
"parentReference": "registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078",
"imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b",
"rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034",
},
)
LOCK_OUTPUT_NAMES = {
"production-requirements.lock": "contextforge-candidate-production-requirements.txt",
"build-tools.lock": "contextforge-candidate-build-tools.txt",
}
INSTALL_START = (
"RUN set -euo pipefail \\\n"
" && . /etc/profile.d/use-openssl.sh \\\n"
" && python3 -m venv /app/.venv"
)
INSTALL_END = "\n# ----------------------------------------------------------------------------\n# Copy Rust runtime binaries and frontend static output from their builder\n"
LOCK_COPY_ANCHOR = "COPY pyproject.toml /app/\n"
class CandidateRecipeError(ValueError):
"""Raised when a candidate recipe input is absent or differs from contract."""
def sha256_hex(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _require_file(path: Path) -> bytes:
if not path.is_file() or path.is_symlink():
raise CandidateRecipeError(f"required regular file is missing: {path.name}")
return path.read_bytes()
def verify_upstream_source(
source_dir: Path, expected_hashes: dict[str, str] | None = None
) -> dict[str, bytes]:
files: dict[str, bytes] = {}
for name, expected in (expected_hashes or SOURCE_HASHES).items():
content = _require_file(source_dir / name)
actual = sha256_hex(content)
if actual != expected:
raise CandidateRecipeError(f"pinned upstream {name} hash mismatch")
files[name] = content
return files
def _table_range(text: str, table: str) -> tuple[int, int, str]:
header = f"[{table}]"
matches = list(re.finditer(rf"(?m)^{re.escape(header)}\s*$", text))
if len(matches) != 1:
raise CandidateRecipeError(f"expected exactly one TOML table {header}")
start = matches[0].start()
next_header = re.search(r"(?m)^\[[^\r\n]+\]\s*$", text[matches[0].end() :])
end = matches[0].end() + next_header.start() if next_header else len(text)
return start, end, text[start:end]
def candidate_pyproject(source_text: str) -> str:
"""Apply the narrowly scoped candidate-only dependency resolution policy."""
try:
import tomllib
source = tomllib.loads(source_text)
except (ImportError, ValueError) as exc:
raise CandidateRecipeError("upstream pyproject is not valid TOML") from exc
uv = source.get("tool", {}).get("uv", {})
if uv.get("exclude-newer") != "10 days":
raise CandidateRecipeError("upstream uv freshness policy changed")
groups = source.get("dependency-groups", {})
if set(groups) != {"dev"}:
raise CandidateRecipeError("upstream dependency-group shape changed")
sources = uv.get("sources", {})
if sources != {
"compliance-reference-server": {
"path": "mcp-servers/python/compliance_reference_server",
"editable": True,
}
}:
raise CandidateRecipeError("upstream uv source override shape changed")
text = source_text.replace("\r\n", "\n")
source_start, source_end, source_table = _table_range(text, "tool.uv.sources")
source_lines = source_table.strip().splitlines()
expected_source_lines = [
"[tool.uv.sources]",
'compliance-reference-server = { path = "mcp-servers/python/compliance_reference_server", editable = true }',
]
if source_lines[:2] != expected_source_lines or any(
line.strip() and not line.lstrip().startswith("#") for line in source_lines[2:]
):
raise CandidateRecipeError("upstream local development source table changed")
text = text[:source_start] + text[source_end:]
group_start, group_end, _group_table = _table_range(text, "dependency-groups")
text = text[:group_start] + text[group_end:]
uv_anchor = 'exclude-newer = "10 days"\n'
if text.count(uv_anchor) != 1:
raise CandidateRecipeError("upstream uv table anchor changed")
text = text.replace(
uv_anchor,
f'exclude-newer = "{GLOBAL_LOCK_CUTOFF}"\n'
+ 'constraint-dependencies = ["mcp==1.30.0", "PyJWT==2.15.0", "urllib3==2.8.0"]\n',
1,
)
package_table = "[tool.uv.exclude-newer-package]\n"
if text.count(package_table) != 1:
raise CandidateRecipeError("upstream package freshness table is missing")
package_overrides = "".join(
f'{name.lower()} = "{LOCK_TIMESTAMP}"\n'
for name in ("mcp", "pyjwt", "urllib3", "pip", "setuptools", "wheel")
)
text = text.replace(package_table, package_table + package_overrides, 1)
text = text.rstrip() + "\n\n[dependency-groups]\nbuild-tools = [\n"
text += "".join(f' "{requirement}",\n' for requirement in BUILD_TOOLS)
text += "]\n"
try:
tomllib.loads(text)
except ValueError as exc:
raise CandidateRecipeError("generated candidate pyproject is invalid TOML") from exc
return text
def _run_uv(args: list[str], cwd: Path) -> None:
completed = subprocess.run( # noqa: S603 - fixed executable and structured arguments
[_uv_binary(), *args],
cwd=cwd,
check=False,
capture_output=True,
text=True,
)
if completed.returncode:
diagnostic = completed.stderr.strip().splitlines()[-1:] or ["uv command failed"]
raise CandidateRecipeError(diagnostic[0])
def _uv_binary() -> str:
binary = shutil.which("uv")
if binary is None or not Path(binary).is_absolute():
raise CandidateRecipeError("uv executable is missing or non-absolute")
return binary
def generate_locks(source_dir: Path, output_dir: Path) -> None:
"""Regenerate locks from the exact upstream project using the pinned uv tool."""
source = verify_upstream_source(source_dir)
version = subprocess.run( # noqa: S603 - fixed version check before lock generation
[_uv_binary(), "--version"], check=False, capture_output=True, text=True
)
version_parts = version.stdout.strip().split()
if version.returncode or version_parts[:2] != ["uv", UV_VERSION]:
raise CandidateRecipeError(f"lock generation requires uv {UV_VERSION}")
output_dir = output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="contextforge-lock-") as temp_name:
temp = Path(temp_name)
(temp / "pyproject.toml").write_text(
candidate_pyproject(source["pyproject.toml"].decode("utf-8")), encoding="utf-8"
)
(temp / "uv.lock").write_bytes(source["uv.lock"])
_run_uv(["lock", "--project", str(temp)], temp)
production_path = output_dir / "production-requirements.lock"
build_path = output_dir / "build-tools.lock"
_run_uv(
[
"export",
"--project",
str(temp),
"--format",
"requirements-txt",
"--locked",
"--no-dev",
"--no-default-groups",
"--no-emit-project",
"--no-header",
"--no-annotate",
*[value for extra in EXTRAS for value in ("--extra", extra)],
"--output-file",
str(production_path),
],
temp,
)
_run_uv(
[
"export",
"--project",
str(temp),
"--format",
"requirements-txt",
"--locked",
"--only-group",
"build-tools",
"--no-default-groups",
"--no-emit-project",
"--no-header",
"--no-annotate",
"--output-file",
str(build_path),
],
temp,
)
manifest = _make_manifest(
source_hashes=SOURCE_HASHES,
production_hash=sha256_hex(_require_file(output_dir / "production-requirements.lock")),
build_tools_hash=sha256_hex(_require_file(output_dir / "build-tools.lock")),
)
_write_atomic(output_dir / "locks.json", _canonical_json(manifest))
def _make_manifest(
*, source_hashes: dict[str, str], production_hash: str, build_tools_hash: str
) -> dict[str, Any]:
return {
"schema": "itworx.contextforge-candidate-locks/v1",
"upstreamCommit": UPSTREAM_COMMIT,
"upstreamFiles": source_hashes,
"uvVersion": UV_VERSION,
"python": "3.12",
"platform": "linux/amd64",
"extras": list(EXTRAS),
"constraints": list(CONSTRAINTS),
"buildTools": list(BUILD_TOOLS),
"globalExcludeNewerUtc": GLOBAL_LOCK_CUTOFF,
"freshnessOverrideUtc": LOCK_TIMESTAMP,
"requirements": {
"production-requirements.lock": production_hash,
"build-tools.lock": build_tools_hash,
},
"baseImages": [
{
**base,
"platform": "linux/amd64",
"childManifestDigest": base["reference"].split("@", 1)[1],
}
for base in BASE_IMAGES
],
}
def _canonical_json(value: Any) -> bytes:
return (json.dumps(value, sort_keys=True, indent=2) + "\n").encode("utf-8")
def _write_atomic(path: Path, content: bytes) -> None:
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
try:
temporary.write_bytes(content)
os.replace(temporary, path)
finally:
temporary.unlink(missing_ok=True)
def _read_lock_manifest() -> dict[str, Any]:
try:
payload = json.loads(_require_file(LOCK_MANIFEST).decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
raise CandidateRecipeError("candidate lock manifest is invalid JSON") from exc
expected = _make_manifest(
source_hashes=SOURCE_HASHES,
production_hash=str(
payload.get("requirements", {}).get("production-requirements.lock", "")
),
build_tools_hash=str(payload.get("requirements", {}).get("build-tools.lock", "")),
)
if payload != expected:
raise CandidateRecipeError("candidate lock manifest differs from the reviewed contract")
for name, expected_hash in payload["requirements"].items():
if sha256_hex(_require_file(LOCK_DIR / name)) != expected_hash:
raise CandidateRecipeError(f"candidate lock hash mismatch: {name}")
return payload
def verify_lock_contents(manifest: dict[str, Any]) -> None:
production = _require_file(LOCK_DIR / "production-requirements.lock").decode("utf-8")
build_tools = _require_file(LOCK_DIR / "build-tools.lock").decode("utf-8")
for requirement in CONSTRAINTS:
name, version = requirement.split("==", 1)
match = re.search(rf"(?im)^{re.escape(name)}==([^ ;\\]+)", production)
if match is None or match.group(1) != version:
raise CandidateRecipeError(f"production lock does not pin {name}=={version}")
for requirement in BUILD_TOOLS:
name, version = requirement.split("==", 1)
match = re.search(rf"(?im)^{re.escape(name)}==([^ ;\\]+)", build_tools)
if match is None or match.group(1) != version:
raise CandidateRecipeError(f"build-tool lock does not pin {name}=={version}")
for name, requirements in (
("production", production),
("build-tool", build_tools),
):
blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", requirements)
blocks = [block for block in blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block)]
if not blocks or any(
not re.search(r"(?m)^\s*--hash=sha256:[0-9a-f]{64}\s*$", block) for block in blocks
):
raise CandidateRecipeError(f"candidate {name} requirements must pin every wheel hash")
if "pip==26.2.1" not in build_tools:
raise CandidateRecipeError("candidate build tools must include the removable pip bootstrap")
if manifest["platform"] != "linux/amd64":
raise CandidateRecipeError("candidate lock platform differs from the fixed build platform")
def freeze_os_layers(text: str) -> str:
"""Replace the four upstream RPM transactions with frozen-base assertions."""
guards = (
(
'RUN if [ "$ENABLE_RUST" = "true" ]; then',
'RUN test "$ENABLE_RUST" = "false" && test "$ENABLE_RUST_MCP_RMCP" = "false"',
),
(
"RUN set -euo pipefail",
'RUN test "$PYTHON_VERSION" = "3.12" && test -s /usr/share/itworx/rpm-inventory.txt',
),
(
"RUN microdnf upgrade",
'RUN test "$PYTHON_VERSION" = "3.12" && test "$ENABLE_PROFILING" = "false" '
'&& test "$(id -u app):$(id -g app)" = "10001:10001" '
"&& test -s /usr/share/itworx/rpm-inventory.txt",
),
('RUN if [ "$ENABLE_FIPS" = "true" ]; then', 'RUN test "$ENABLE_FIPS" = "false"'),
)
transactions = [
match
for match in re.finditer(r"(?m)^RUN [^\n]*(?:\n(?<=\\\n)[^\n]*)*", text)
if re.search(r"\b(?:microdnf|dnf) (?:upgrade|install)\b", match.group())
]
if len(transactions) != len(guards):
raise CandidateRecipeError("upstream RPM transaction count changed")
for match, (prefix, guard) in reversed(list(zip(transactions, guards, strict=True))):
if not match.group().startswith(prefix):
raise CandidateRecipeError("upstream RPM transaction order changed")
text = text[: match.start()] + guard + text[match.end() :]
return text
def transform_containerfile(containerfile: bytes, expected_hash: str | None = None) -> bytes:
if sha256_hex(containerfile) != (expected_hash or SOURCE_HASHES["Containerfile"]):
raise CandidateRecipeError("pinned upstream Containerfile hash mismatch")
text = freeze_os_layers(containerfile.decode("utf-8"))
if text.count(LOCK_COPY_ANCHOR) != 1:
raise CandidateRecipeError("upstream dependency-copy anchor count changed")
text = text.replace(
LOCK_COPY_ANCHOR,
LOCK_COPY_ANCHOR
+ "COPY contextforge-candidate-production-requirements.txt /tmp/contextforge-production-requirements.txt\n"
+ "COPY contextforge-candidate-build-tools.txt /tmp/contextforge-build-tools.txt\n",
1,
)
start = text.find(INSTALL_START)
end = text.find(INSTALL_END, start)
if start < 0 or end < 0 or text.find(INSTALL_START, start + 1) >= 0:
raise CandidateRecipeError("upstream dependency-install block anchors changed")
locked_install = """RUN set -euo pipefail \\
&& . /etc/profile.d/use-openssl.sh \\
&& python3 -m venv /app/.venv \\
&& /app/.venv/bin/python -m ensurepip --upgrade \\
&& /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --require-hashes --only-binary=:all: -r /tmp/contextforge-build-tools.txt \\
&& /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --require-hashes --only-binary=:all: -r /tmp/contextforge-production-requirements.txt \\
&& /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --no-deps --no-build-isolation ".[redis,postgres,observability,granian,plugins,llmchat]" \\
&& /app/.venv/bin/python -m pip check \\
&& /app/.venv/bin/python -c "import importlib.metadata as md; assert md.version('mcp') == '1.30.0'; assert md.version('PyJWT') == '2.15.0'; assert md.version('urllib3') == '2.8.0'" \\
&& /app/.venv/bin/python -m pip uninstall --yes pip setuptools wheel \\
&& (find /app/.venv -name "*.dist-info" -type d \\
\\( -name "pip-*" -o -name "setuptools-*" -o -name "wheel-*" \\) \\
-exec rm -rf {} + 2>/dev/null || true) \\
&& rm -rf /app/.venv/share/python-wheels \\
&& rm -rf "/app/"*.egg-info /app/build /app/dist /app/.eggs \\
&& rm -rf /root/.cache /var/cache/dnf
"""
text = text[:start] + locked_install + text[end:]
return text.encode("utf-8")
def prepare_source(source_dir: Path) -> None:
source = verify_upstream_source(source_dir)
manifest = _read_lock_manifest()
verify_lock_contents(manifest)
transformed = transform_containerfile(source["Containerfile"])
for source_name, target_name in LOCK_OUTPUT_NAMES.items():
_write_atomic(source_dir / target_name, _require_file(LOCK_DIR / source_name))
_write_atomic(source_dir / "Containerfile.itworx-candidate", transformed)
def print_build_args() -> None:
for key, value in candidate_build_args().items():
print(f"{key}={value}")
def candidate_build_args() -> dict[str, str]:
manifest = _read_lock_manifest()
verify_lock_contents(manifest)
overrides = {item["arg"]: item["reference"] for item in BASE_IMAGES}
overrides.update(
{
"WHEELS_REF": overrides["UBI_MINIMAL"],
"PYTHON_VERSION": "3.12",
"ENABLE_RUST": "false",
"ENABLE_RUST_MCP_RMCP": "false",
"ENABLE_PROFILING": "false",
"ENABLE_FIPS": "false",
}
)
return overrides
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest="command", required=True)
prepare = commands.add_parser(
"prepare", help="verify and transform an exact fetched source tree"
)
prepare.add_argument("--source-dir", type=Path, required=True)
generate = commands.add_parser("generate-locks", help="regenerate lock files with pinned uv")
generate.add_argument("--source-dir", type=Path, required=True)
generate.add_argument("--output-dir", type=Path, default=LOCK_DIR)
commands.add_parser("build-args", help="print fixed platform/base image build arguments")
args = parser.parse_args()
try:
if args.command == "prepare":
prepare_source(args.source_dir)
elif args.command == "generate-locks":
generate_locks(args.source_dir, args.output_dir)
else:
print_build_args()
except (CandidateRecipeError, OSError, subprocess.SubprocessError) as exc:
parser.exit(1, f"FATAL: {exc}\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+69 -6
View File
@@ -16,6 +16,7 @@ satisfying the release gate for that finding.
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from datetime import date, timedelta
@@ -28,6 +29,52 @@ if str(_ROOT_DIR) not in sys.path:
from scripts.packlib import brussels_now, load_json # noqa: E402
def _affected_image_digests(row: dict[str, object], images: dict[str, object]) -> dict[str, str]:
"""Select current image bindings by scan tag, with a legacy full-inventory fallback."""
if not images:
raise ValueError("--image-digests must contain a non-empty images map")
if "affectedImages" not in row:
services = list(images)
else:
affected_images = row.get("affectedImages")
if (
not isinstance(affected_images, list)
or not affected_images
or any(not isinstance(tag, str) or not tag.strip() for tag in affected_images)
or len(set(affected_images)) != len(affected_images)
):
raise ValueError("scan affectedImages must be a non-empty list of unique image tags")
services_by_tag: dict[str, list[str]] = {}
for service, entry in images.items():
if not isinstance(entry, dict):
continue
tag = entry.get("tag")
if isinstance(tag, str) and tag:
services_by_tag.setdefault(tag, []).append(service)
services = []
for tag in affected_images:
matches = services_by_tag.get(tag, [])
if len(matches) != 1:
raise ValueError(
f"scan image {tag} must resolve to exactly one current image service"
)
services.append(matches[0])
bindings: dict[str, str] = {}
for service in services:
entry = images.get(service)
if not isinstance(entry, dict):
raise ValueError(f"image evidence for {service} must be an object")
digest = entry.get("digest") or entry.get("imageId")
if not isinstance(digest, str) or not digest:
raise ValueError(f"image evidence for {service} has no immutable digest or imageId")
bindings[service] = digest
return bindings
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--scan-summary", required=True, type=Path)
@@ -46,25 +93,41 @@ def main() -> int:
scan_date = scan.get("scannedAt", brussels_now())[:10]
scanner = f"trivy {scan.get('trivyVersion', 'unknown')}"
affected_digests: dict[str, str] = {}
image_inventory: dict[str, object] = {}
if args.image_digests:
digest_doc = load_json(args.image_digests)
for service, entry in digest_doc.get("images", {}).items():
digest = entry.get("digest") or entry.get("imageId")
if digest:
affected_digests[service] = digest
raw_images = digest_doc.get("images", {})
if isinstance(raw_images, dict):
image_inventory = raw_images
today = date.fromisoformat(scan_date) if len(scan_date) == 10 else date.today()
expiry = (today + timedelta(days=args.expiry_days)).isoformat()
rescan = (today + timedelta(days=args.rescan_days)).isoformat()
versions_by_pair: dict[tuple[str, str], set[str]] = {}
for row in scan.get("unresolvedCriticalOrHigh", []):
for cve in row["cves"]:
versions_by_pair.setdefault((row["package"], cve), set()).add(
str(row.get("installedVersion"))
)
exceptions = []
for row in scan.get("unresolvedCriticalOrHigh", []):
package = row["package"]
try:
affected_digests = (
_affected_image_digests(row, image_inventory) if args.image_digests else {}
)
except ValueError as exc:
parser.error(str(exc))
for cve in row["cves"]:
version_suffix = ""
if len(versions_by_pair[(package, cve)]) > 1:
version_suffix = (
"-" + hashlib.sha256(str(row.get("installedVersion")).encode()).hexdigest()[:12]
)
exceptions.append(
{
"id": f"{package}-{cve}",
"id": f"{package}-{cve}{version_suffix}",
"cve": cve,
"package": package,
"installedVersion": row.get("installedVersion"),
+3
View File
@@ -22,3 +22,6 @@ INSPECTOR_BIND_IP=127.0.0.1
COMPOSE_PROFILES=observability
DEMO_TOKEN_REVOCATIONS_DIR_HOST=/tmp
APPOPS_BUILD_DATE=2026-10-01T18:00:00Z
APPOPS_SOURCE_TREE=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
APPOPS_BUILD_ID=aaaaaaaaaaaa
SOURCE_DATE_EPOCH=1790888863
+33
View File
@@ -259,16 +259,43 @@ class ComposeContractTests(unittest.TestCase):
arguments.get("BUILD_DATE"),
"${APPOPS_BUILD_DATE:?APPOPS_BUILD_DATE is required}",
)
for argument, value in {
"GIT_SOURCE_TREE": "${APPOPS_SOURCE_TREE:?APPOPS_SOURCE_TREE is required}",
"IMAGE_VERSION": "${APPOPS_BUILD_ID:?APPOPS_BUILD_ID is required}",
"SOURCE_DATE_EPOCH": "${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH is required}",
}.items():
self.assertEqual(arguments.get(argument), value, name)
original_arguments = self.document["services"][name]["build"].get("args", {})
for key, value in original_arguments.items():
self.assertEqual(arguments.get(key), value)
def test_build_dockerfiles_emit_controller_tree_version_and_epoch_labels(self) -> None:
dockerfiles = (
"infrastructure/images/python-service/Dockerfile",
"infrastructure/images/docker-read-broker/Dockerfile",
"infrastructure/images/observability/Dockerfile",
)
expected_labels = {
"org.opencontainers.image.source-tree": "GIT_SOURCE_TREE",
"org.opencontainers.image.version": "IMAGE_VERSION",
"org.opencontainers.image.source-date-epoch": "SOURCE_DATE_EPOCH",
}
for relative_path in dockerfiles:
text = (ROOT / relative_path).read_text(encoding="utf-8")
with self.subTest(dockerfile=relative_path):
for label, argument in expected_labels.items():
self.assertIn(f"ARG {argument}", text)
self.assertIn(f'{label}="${{{argument}}}"', text)
def test_real_autodeploy_preserves_intake_digest_and_owned_build_revision(self) -> None:
docker = shutil.which("docker")
if docker is None:
self.skipTest("docker CLI is not available in this environment")
revision = "a" * 40
built_at = "2026-10-01T18:00:00Z"
source_tree = "b" * 40
image_version = revision[:12]
source_date_epoch = "1790888863"
with tempfile.TemporaryDirectory() as secrets_dir:
for name in self.document["secrets"]:
(Path(secrets_dir) / name).write_text("test-fixture-secret\n", encoding="utf-8")
@@ -301,6 +328,9 @@ class ComposeContractTests(unittest.TestCase):
"CONNECTOR_INGESTION_IMAGE": f"registry.example.test/intake@sha256:{'b' * 64}",
"GITEA_COMMIT_SHA": revision,
"APPOPS_BUILD_DATE": built_at,
"APPOPS_SOURCE_TREE": source_tree,
"APPOPS_BUILD_ID": image_version,
"SOURCE_DATE_EPOCH": source_date_epoch,
},
)
self.assertEqual(completed.returncode, 0, completed.stderr)
@@ -316,6 +346,9 @@ class ComposeContractTests(unittest.TestCase):
continue
self.assertEqual(service["build"]["args"]["GIT_REVISION"], revision, name)
self.assertEqual(service["build"]["args"]["BUILD_DATE"], built_at, name)
self.assertEqual(service["build"]["args"]["GIT_SOURCE_TREE"], source_tree, name)
self.assertEqual(service["build"]["args"]["IMAGE_VERSION"], image_version, name)
self.assertEqual(service["build"]["args"]["SOURCE_DATE_EPOCH"], source_date_epoch, name)
self.assertTrue(service["image"].endswith(f":{revision}"), name)
def test_unraid_folder_view_groups_the_complete_production_stack(self) -> None:
+386
View File
@@ -0,0 +1,386 @@
# The upstream TOML fixture retains exact source formatting.
# ruff: noqa: E501
from __future__ import annotations
import hashlib
import json
import re
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import contextforge_candidate_recipe as recipe # noqa: E402 - script path added above
LOCK_DIR = ROOT / "infrastructure" / "images" / "contextforge" / "candidate"
def _hash(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _containerfile_fixture() -> bytes:
return (
b"FROM base\n"
b'RUN if [ "$ENABLE_RUST" = "true" ]; then \\\n dnf upgrade -y; fi\n'
b"RUN set -euo pipefail \\\n && dnf install -y python3.12\n"
b"RUN microdnf upgrade -y \\\n && microdnf install -y python3.12\n"
b'RUN if [ "$ENABLE_FIPS" = "true" ]; then \\\n microdnf install -y crypto-policies; fi\n'
b"COPY pyproject.toml /app/\n"
+ recipe.INSTALL_START.encode("utf-8")
+ b"\n && /app/.venv/bin/uv pip install unpinned\n"
+ recipe.INSTALL_END.encode("utf-8")
+ b"COPY source /app\n"
)
def _source_fixture(tmp_path: Path, containerfile: bytes) -> tuple[Path, dict[str, str]]:
source = tmp_path / "upstream"
source.mkdir()
contents = {
"Containerfile": containerfile,
"pyproject.toml": b"[project]\nname='fixture'\n",
"uv.lock": b"version = 1\n",
}
expected = {name: _hash(value) for name, value in contents.items()}
for name, value in contents.items():
(source / name).write_bytes(value)
return source, expected
def _copy_candidate_locks(destination: Path) -> dict[str, str]:
destination.mkdir(parents=True)
requirements: dict[str, str] = {}
for name in ("production-requirements.lock", "build-tools.lock"):
data = (LOCK_DIR / name).read_bytes()
(destination / name).write_bytes(data)
requirements[name] = _hash(data)
return requirements
def test_candidate_lock_exports_are_hash_complete_and_exclude_build_tools() -> None:
manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8"))
production = (LOCK_DIR / "production-requirements.lock").read_text(encoding="utf-8")
build_tools = (LOCK_DIR / "build-tools.lock").read_text(encoding="utf-8")
assert manifest["upstreamCommit"] == recipe.UPSTREAM_COMMIT
assert manifest["uvVersion"] == recipe.UV_VERSION == "0.12.10"
assert manifest["globalExcludeNewerUtc"] == "2026-09-22T00:00:00Z"
assert manifest["freshnessOverrideUtc"] == "2026-10-02T00:00:00Z"
assert manifest["extras"] == list(recipe.EXTRAS)
assert manifest["constraints"] == list(recipe.CONSTRAINTS)
for name in ("production-requirements.lock", "build-tools.lock"):
assert _hash((LOCK_DIR / name).read_bytes()) == manifest["requirements"][name]
for requirement in recipe.CONSTRAINTS:
package, version = requirement.split("==", 1)
assert re.search(rf"(?im)^{re.escape(package)}=={re.escape(version)}\s*\\", production)
for requirement in recipe.BUILD_TOOLS:
package, version = requirement.split("==", 1)
assert re.search(rf"(?im)^{re.escape(package)}=={re.escape(version)}\s*\\", build_tools)
package_blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", production)
package_blocks = [
block for block in package_blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block)
]
assert len(package_blocks) > 100
assert all(re.search(r"--hash=sha256:[0-9a-f]{64}", block) for block in package_blocks)
assert not re.search(r"(?im)^(pip|setuptools|wheel|uv)==", production)
build_blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", build_tools)
build_blocks = [block for block in build_blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block)]
assert all(re.search(r"--hash=sha256:[0-9a-f]{64}", block) for block in build_blocks)
assert "pip==26.2.1" in build_tools
def test_candidate_pyproject_freezes_relative_freshness_and_preserves_project_metadata() -> None:
source = """[tool.uv]
exclude-newer = "10 days"
[tool.uv.exclude-newer-package]
known-safe-package = "2026-07-01T00:00:00Z"
[tool.uv.sources]
compliance-reference-server = { path = "mcp-servers/python/compliance_reference_server", editable = true }
# Project table follows the local source table.
[project]
name = "contextforge"
dependencies = ["httpx>=0.27"]
[dependency-groups]
dev = ["pytest>=8"]
"""
generated = recipe.candidate_pyproject(source)
import tomllib
parsed = tomllib.loads(generated)
assert parsed["project"] == {
"name": "contextforge",
"dependencies": ["httpx>=0.27"],
}
assert "sources" not in parsed["tool"]["uv"]
assert parsed["tool"]["uv"]["exclude-newer"] == "2026-09-22T00:00:00Z"
assert parsed["tool"]["uv"]["constraint-dependencies"] == list(recipe.CONSTRAINTS)
assert parsed["tool"]["uv"]["exclude-newer-package"]["known-safe-package"] == (
"2026-07-01T00:00:00Z"
)
assert parsed["tool"]["uv"]["exclude-newer-package"]["pyjwt"] == "2026-10-02T00:00:00Z"
assert parsed["dependency-groups"]["build-tools"] == list(recipe.BUILD_TOOLS)
assert "dev" not in parsed["dependency-groups"]
def test_candidate_transform_is_exact_and_uses_hash_locked_runtime_install() -> None:
original = _containerfile_fixture()
transformed = recipe.transform_containerfile(original, expected_hash=_hash(original)).decode()
assert transformed.count("COPY contextforge-candidate-production-requirements.txt") == 1
assert transformed.count("COPY contextforge-candidate-build-tools.txt") == 1
assert (
"--require-hashes --only-binary=:all: -r /tmp/contextforge-production-requirements.txt"
in transformed
)
assert (
"--require-hashes --only-binary=:all: -r /tmp/contextforge-build-tools.txt" in transformed
)
assert (
'--no-deps --no-build-isolation ".[redis,postgres,observability,granian,plugins,llmchat]"'
in transformed
)
assert "uv pip install unpinned" not in transformed
assert "COPY source /app" in transformed
assert not re.search(r"\b(?:dnf|microdnf) (?:upgrade|install)\b", transformed)
assert 'test "$ENABLE_RUST" = "false"' in transformed
assert 'test "$ENABLE_FIPS" = "false"' in transformed
assert 'test "$ENABLE_PROFILING" = "false"' in transformed
assert 'test "$(id -u app):$(id -g app)" = "10001:10001"' in transformed
def test_candidate_os_freeze_rejects_missing_or_additional_transactions() -> None:
original = _containerfile_fixture().decode()
for changed in (
original.replace("dnf upgrade -y", "echo changed", 1),
original + "\nRUN dnf install -y unexpected\n",
):
with pytest.raises(recipe.CandidateRecipeError, match="RPM transaction count"):
recipe.freeze_os_layers(changed)
def test_candidate_transform_rejects_source_drift_and_unexpected_anchor_counts() -> None:
original = _containerfile_fixture()
digest = _hash(original)
with pytest.raises(recipe.CandidateRecipeError, match="Containerfile hash mismatch"):
recipe.transform_containerfile(original + b"# drift\n", expected_hash=digest)
with pytest.raises(recipe.CandidateRecipeError, match="anchor count changed"):
recipe.transform_containerfile(
original.replace(b"COPY pyproject.toml /app/\n", b""),
expected_hash=_hash(original.replace(b"COPY pyproject.toml /app/\n", b"")),
)
def test_source_verification_rejects_missing_and_tampered_pinned_inputs(tmp_path: Path) -> None:
source, expected = _source_fixture(tmp_path, _containerfile_fixture())
assert recipe.verify_upstream_source(source, expected) == {
name: (source / name).read_bytes() for name in expected
}
(source / "uv.lock").write_bytes(b"changed lock")
with pytest.raises(recipe.CandidateRecipeError, match="uv.lock hash mismatch"):
recipe.verify_upstream_source(source, expected)
(source / "uv.lock").unlink()
with pytest.raises(recipe.CandidateRecipeError, match="missing: uv.lock"):
recipe.verify_upstream_source(source, expected)
def test_prepare_copies_verified_locks_and_fails_before_writing_on_missing_lock(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
source, source_hashes = _source_fixture(tmp_path, _containerfile_fixture())
lock_dir = tmp_path / "candidate-locks"
lock_hashes = _copy_candidate_locks(lock_dir)
manifest = recipe._make_manifest(
source_hashes=source_hashes,
production_hash=lock_hashes["production-requirements.lock"],
build_tools_hash=lock_hashes["build-tools.lock"],
)
(lock_dir / "locks.json").write_bytes(recipe._canonical_json(manifest))
monkeypatch.setattr(recipe, "SOURCE_HASHES", source_hashes)
monkeypatch.setattr(recipe, "LOCK_DIR", lock_dir)
monkeypatch.setattr(recipe, "LOCK_MANIFEST", lock_dir / "locks.json")
recipe.prepare_source(source)
assert (source / "Containerfile.itworx-candidate").exists()
for name, target in recipe.LOCK_OUTPUT_NAMES.items():
assert (source / target).read_bytes() == (lock_dir / name).read_bytes()
(source / "Containerfile.itworx-candidate").unlink()
(lock_dir / "build-tools.lock").unlink()
with pytest.raises(recipe.CandidateRecipeError, match="missing: build-tools.lock"):
recipe.prepare_source(source)
assert not (source / "Containerfile.itworx-candidate").exists()
def test_candidate_build_args_pin_every_base_and_disable_optional_builds() -> None:
args = recipe.candidate_build_args()
assert set(args) == {
"UBI_BASE",
"NODEJS_IMAGE",
"UBI_MINIMAL",
"WHEELS_REF",
"PYTHON_VERSION",
"ENABLE_RUST",
"ENABLE_RUST_MCP_RMCP",
"ENABLE_PROFILING",
"ENABLE_FIPS",
}
assert all("@sha256:" in args[key] for key in ("UBI_BASE", "NODEJS_IMAGE", "UBI_MINIMAL"))
assert args["UBI_BASE"] == (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@"
"sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483"
)
assert args["UBI_MINIMAL"] == (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@"
"sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236"
)
assert args["NODEJS_IMAGE"] == (
"registry.access.redhat.com/ubi10/nodejs-24@"
"sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06"
)
assert args["WHEELS_REF"] == args["UBI_MINIMAL"]
assert args["PYTHON_VERSION"] == "3.12"
assert (
args["ENABLE_RUST"] == args["ENABLE_RUST_MCP_RMCP"] == args["ENABLE_PROFILING"] == "false"
)
assert args["ENABLE_FIPS"] == "false"
def test_frozen_base_manifest_binds_published_digests_and_rpm_inventories() -> None:
manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8"))
bases = {item["arg"]: item for item in manifest["baseImages"]}
assert set(bases) == {"UBI_BASE", "NODEJS_IMAGE", "UBI_MINIMAL"}
assert bases["UBI_BASE"] == {
"arg": "UBI_BASE",
"sourceTag": (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:"
"sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8"
),
"reference": (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@"
"sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483"
),
"parentReference": (
"registry.access.redhat.com/ubi10@"
"sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8"
),
"imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8",
"rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0",
"platform": "linux/amd64",
"childManifestDigest": (
"sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483"
),
}
assert bases["UBI_MINIMAL"] == {
"arg": "UBI_MINIMAL",
"sourceTag": (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:"
"sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b"
),
"reference": (
"gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@"
"sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236"
),
"parentReference": (
"registry.access.redhat.com/ubi10/ubi-minimal@"
"sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078"
),
"imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b",
"rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034",
"platform": "linux/amd64",
"childManifestDigest": (
"sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236"
),
}
assert bases["NODEJS_IMAGE"]["reference"] == (
"registry.access.redhat.com/ubi10/nodejs-24@"
"sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06"
)
assert "imageId" not in bases["NODEJS_IMAGE"]
def test_frozen_base_reference_drift_fails_before_candidate_build(
monkeypatch: pytest.MonkeyPatch,
) -> None:
original = recipe.BASE_IMAGES[0]
changed = dict(original)
changed["reference"] = changed["reference"].replace("e23a733", "f23a733", 1)
monkeypatch.setattr(recipe, "BASE_IMAGES", (changed, *recipe.BASE_IMAGES[1:]))
with pytest.raises(recipe.CandidateRecipeError, match="reviewed contract"):
recipe._read_lock_manifest()
def test_frozen_base_evidence_matches_candidate_pins_and_repository_inputs() -> None:
evidence_path = ROOT / "evidence" / "WP-400" / "CF_FROZEN_BASES.json"
evidence = json.loads(evidence_path.read_text(encoding="utf-8"))
manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8"))
bases = {item["arg"]: item for item in manifest["baseImages"]}
assert evidence["schema"] == "itworx.contextforge-frozen-os-bases-evidence/v1"
assert evidence["status"] == "passed"
assert evidence["finalContextForgeCandidateBuilt"] is False
assert evidence["sourceInputs"]["upstreamCommit"] == manifest["upstreamCommit"]
assert evidence["sourceInputs"]["ContainerfileSha256"] == manifest["upstreamFiles"][
"Containerfile"
]
assert evidence["sourceInputs"]["pyprojectTomlSha256"] == manifest["upstreamFiles"][
"pyproject.toml"
]
assert evidence["sourceInputs"]["uvLockSha256"] == manifest["upstreamFiles"]["uv.lock"]
assert set(evidence["hubInputsSha256"]) == {
"scripts/contextforge_candidate_recipe.py",
"scripts/build_contextforge_compat_image.sh",
"infrastructure/images/contextforge/candidate/locks.json",
"infrastructure/images/contextforge/candidate/production-requirements.lock",
"infrastructure/images/contextforge/candidate/build-tools.lock",
"infrastructure/images/contextforge/os-bases/Dockerfile.builder",
"infrastructure/images/contextforge/os-bases/Dockerfile.runtime",
"infrastructure/compose/docker-compose.autodeploy.yml",
}
assert evidence["hubInputsSha256"] == {
name: _hash((ROOT / name).read_bytes()) for name in evidence["hubInputsSha256"]
}
for role, arg in (("builder", "UBI_BASE"), ("runtime", "UBI_MINIMAL")):
published = evidence["publishedBases"][role]
locked = bases[arg]
assert published["reference"] == locked["reference"]
assert published["imageId"] == locked["imageId"]
assert published["rpmInventorySha256"] == locked["rpmInventorySha256"]
assert published["platform"] == locked["platform"] == "linux/amd64"
assert published["publication"]["pulledByDigest"] is True
assert published["publication"]["manifestConfigImageIdMatches"] is True
assert published["publication"]["rpmInventoryMatchesAfterPull"] is True
assert published["rpmInventory"]["exactMatch"] is True
assert published["scan"]["secretFindings"] == 0
assert published["scan"]["vulnerabilityFindingRecords"] == 0
def test_candidate_manifest_rejects_changed_lock_bytes(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
lock_dir = tmp_path / "locks"
lock_hashes = _copy_candidate_locks(lock_dir)
manifest = recipe._make_manifest(
source_hashes=recipe.SOURCE_HASHES,
production_hash=lock_hashes["production-requirements.lock"],
build_tools_hash=lock_hashes["build-tools.lock"],
)
(lock_dir / "locks.json").write_bytes(recipe._canonical_json(manifest))
(lock_dir / "production-requirements.lock").write_bytes(b"changed")
monkeypatch.setattr(recipe, "LOCK_DIR", lock_dir)
monkeypatch.setattr(recipe, "LOCK_MANIFEST", lock_dir / "locks.json")
with pytest.raises(recipe.CandidateRecipeError, match="lock hash mismatch"):
recipe._read_lock_manifest()
@@ -1,9 +1,15 @@
# Shell fixture lines preserve exact mocked build arguments and OCI values.
# ruff: noqa: E501
from __future__ import annotations
import hashlib
import json
import os
import re
import shutil
import subprocess
import tomllib
from pathlib import Path
import pytest
@@ -12,6 +18,9 @@ ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / "infrastructure" / "images" / "contextforge" / "compat-image.env"
CONTRACT = ROOT / "infrastructure" / "images" / "contextforge" / "runtime-contract.json"
BUILDER = ROOT / "scripts" / "build_contextforge_compat_image.sh"
SECURITY_REQUIREMENTS = (
ROOT / "infrastructure" / "images" / "contextforge" / "security-requirements.txt"
)
EXPECTED = {
"CONTEXTFORGE_UPSTREAM_COMMIT": "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1",
@@ -73,18 +82,18 @@ def test_contextforge_compat_builder_guards_source_and_runtime() -> None:
assert 'CONTRACT="$ROOT/infrastructure/images/contextforge/runtime-contract.json"' in script
assert 'ENTRYPOINT="$ROOT/infrastructure/images/contextforge/entrypoint.sh"' in script
assert 'RUNTIME_VERIFIER="$ROOT/scripts/contextforge_runtime_contract.py"' in script
assert 'https://github.com/IBM/mcp-context-forge.git' in script
assert "https://github.com/IBM/mcp-context-forge.git" in script
assert 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"' in script
assert 'git -C "$SRC" checkout --detach FETCH_HEAD' in script
assert 'git -C "$SRC" rev-parse HEAD' in script
assert 'REVISION_PREFIX="${CONTEXTFORGE_ALEMBIC_HEAD}_"' in script
assert 'MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"' in script
assert "from mcp import McpError" in script
assert 'alembic -c mcpgateway/alembic.ini heads' in script
assert 'CONTEXTFORGE_ALEMBIC_HEAD' in script
assert 'contextforge-runtime-contract.json' in script
assert 'contextforge-entrypoint.sh' in script
assert 'contextforge-runtime-contract.py' in script
assert "alembic -c mcpgateway/alembic.ini heads" in script
assert "CONTEXTFORGE_ALEMBIC_HEAD" in script
assert "contextforge-runtime-contract.json" in script
assert "contextforge-entrypoint.sh" in script
assert "contextforge-runtime-contract.py" in script
for label in (
"io.itworx.contextforge.contract-sha256",
"io.itworx.contextforge.entrypoint-sha256",
@@ -92,12 +101,68 @@ def test_contextforge_compat_builder_guards_source_and_runtime() -> None:
"io.itworx.contextforge.source-commit",
"io.itworx.contextforge.alembic-head",
"io.itworx.contextforge.mcp-sdk",
"io.itworx.contextforge.security-requirements-sha256",
):
assert label in script
assert 'docker image inspect -f' in script
assert "docker image inspect -f" in script
assert 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"' in script
def test_contextforge_security_wheels_match_the_reviewed_hub_lock() -> None:
lock = tomllib.loads((ROOT / "uv.lock").read_text(encoding="utf-8"))
packages = {package["name"]: package for package in lock["package"]}
requirements = SECURITY_REQUIREMENTS.read_text(encoding="utf-8").splitlines()
assert len(requirements) == 2
names = set()
for requirement in requirements:
match = re.fullmatch(
r"([A-Za-z0-9]+)==([0-9]+\.[0-9]+\.[0-9]+) --hash=sha256:([0-9a-f]{64})", requirement
)
assert match is not None, (
"Security inputs must pin a wheel version and SHA-256 without extra flags."
)
name, version, digest = match.groups()
normalized = name.lower()
assert normalized not in names
names.add(normalized)
package = packages[normalized]
assert version == package["version"]
assert f"sha256:{digest}" in {wheel["hash"] for wheel in package["wheels"]}
assert names == {"pyjwt", "urllib3"}
def test_missing_security_inputs_stop_builder_before_any_external_command(tmp_path: Path) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
scripts = tmp_path / "scripts"
scripts.mkdir()
builder = scripts / BUILDER.name
builder.write_text(BUILDER.read_text(encoding="utf-8"), encoding="utf-8")
inputs = tmp_path / "infrastructure" / "images" / "contextforge"
inputs.mkdir(parents=True)
for name in ("compat-image.env", "runtime-contract.json", "entrypoint.sh"):
(inputs / name).write_text("fixture", encoding="utf-8")
(scripts / "contextforge_runtime_contract.py").write_text("fixture", encoding="utf-8")
bin_directory = tmp_path / "bin"
bin_directory.mkdir()
marker = tmp_path / "external-command-ran"
for name in ("git", "docker"):
executable = bin_directory / name
executable.write_text(f"#!/bin/sh\ntouch '{marker}'\nexit 99\n", encoding="utf-8")
executable.chmod(0o755)
env = {**os.environ, "PATH": f"{bin_directory}:{os.environ['PATH']}"}
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder)],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode == 66
assert "security-requirements.txt" in completed.stderr
assert not marker.exists()
def test_contextforge_compat_builder_has_valid_shell_syntax() -> None:
if os.name == "nt":
pytest.skip("WSL bash cannot read a Windows path for syntax validation")
@@ -110,3 +175,392 @@ def test_contextforge_compat_builder_has_valid_shell_syntax() -> None:
check=False,
)
assert completed.returncode == 0, completed.stderr
def _candidate_builder_fixture(tmp_path: Path) -> tuple[Path, dict[str, str], Path, Path]:
root = tmp_path / "hub"
for relative in (
"scripts/build_contextforge_compat_image.sh",
"scripts/contextforge_candidate_recipe.py",
"scripts/contextforge_runtime_contract.py",
"infrastructure/images/contextforge/compat-image.env",
"infrastructure/images/contextforge/runtime-contract.json",
"infrastructure/images/contextforge/entrypoint.sh",
"infrastructure/images/contextforge/security-requirements.txt",
):
target = root / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / relative, target)
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
log = tmp_path / "external-calls.log"
layer_capture = tmp_path / "candidate-layer.Containerfile"
git = bin_dir / "git"
git.write_text(
"#!/bin/sh\n"
'echo "git $*" >> "$CALL_LOG"\n'
'if [ "$1" = "-C" ]; then\n'
' src="$2"; action="$3"\n'
' if [ "$src" = "$SOURCE_ROOT" ]; then\n'
' case "$action:$4:$5" in\n'
' rev-parse:HEAD:) printf "%s\\n" "$GIT_HEAD" ;;\n'
' rev-parse:HEAD*:) printf "%s\\n" "$GIT_TREE" ;;\n'
' show:-s:--format=%ct) printf "%s\\n" "$GIT_EPOCH" ;;\n'
' show:-s:--format=%cI) printf "%s\\n" "$GIT_COMMIT_DATE" ;;\n'
' status:*:*) printf "%s" "${GIT_DIRTY:-}" ;;\n'
" esac\n"
" exit 0\n"
" fi\n"
' case "$action" in\n'
' fetch|checkout) mkdir -p "$src/mcpgateway/alembic/versions"; '
' printf "revision = \\"12d4a0c7789c\\"\\n" > '
'"$src/mcpgateway/alembic/versions/12d4a0c7789c_fixture.py"; '
' printf "FROM python:3.12\\n" > "$src/Containerfile" ;;\n'
' rev-parse) printf "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1\\n" ;;\n'
" esac\n"
"fi\n"
"exit 0\n",
encoding="utf-8",
)
git.chmod(0o755)
real_python = shutil.which("python3")
assert real_python is not None
python3 = bin_dir / "python3"
python3.write_text(
"#!/bin/sh\n"
'case "$1" in\n'
" */contextforge_candidate_recipe.py)\n"
' case "$2" in\n'
' prepare) printf "candidate recipe\\n" > "$4/Containerfile.itworx-candidate" ;;\n'
" build-args)\n"
' printf "%s\\n" \\\n'
' "UBI_BASE=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" \\\n'
' "NODEJS_IMAGE=registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06" \\\n'
' "UBI_MINIMAL=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" \\\n'
' "WHEELS_REF=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" \\\n'
' "PYTHON_VERSION=3.12" "ENABLE_RUST=false" "ENABLE_RUST_MCP_RMCP=false" "ENABLE_PROFILING=false" "ENABLE_FIPS=false" ;;\n'
" esac\n"
" exit 0 ;;\n"
"esac\n"
f'exec "{real_python}" "$@"\n',
encoding="utf-8",
)
python3.chmod(0o755)
docker = bin_dir / "docker"
docker.write_text(
"#!/bin/sh\n"
'echo "docker $*" >> "$CALL_LOG"\n'
'if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then\n'
' if [ "$3" != "-f" ]; then\n'
' [ "${COLLISION_TAG:-}" = "$3" ] && exit 0\n'
' printf "Error response from daemon: No such image: %s\\n" "$3" >&2\n'
" exit 1\n"
" fi\n"
' case "$4" in\n'
' *Config.User*) printf "10001:10001\\n" ;;\n'
' *contract-sha256*) printf "%s\\n" "$CONTRACT_SHA" ;;\n'
' *entrypoint-sha256*) printf "%s\\n" "$ENTRYPOINT_SHA" ;;\n'
' *verifier-sha256*) printf "%s\\n" "$VERIFIER_SHA" ;;\n'
' *source-commit*) printf "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1\\n" ;;\n'
' *alembic-head*) printf "12d4a0c7789c\\n" ;;\n'
' *mcp-sdk*) printf "1.30.0\\n" ;;\n'
' *security-requirements-sha256*) printf "%s\\n" "$SECURITY_SHA" ;;\n'
' *org.opencontainers.image.source-tree*) printf "%s\\n" "$APPOPS_SOURCE_TREE" ;;\n'
' *org.opencontainers.image.revision*) printf "%s\\n" "$APPOPS_SOURCE_REVISION" ;;\n'
' *org.opencontainers.image.version*) printf "%s\\n" "$APPOPS_BUILD_ID" ;;\n'
' *org.opencontainers.image.source-date-epoch*) printf "%s\\n" "$SOURCE_DATE_EPOCH" ;;\n'
' *org.opencontainers.image.created*) printf "%s\\n" "$APPOPS_BUILD_DATE" ;;\n'
' *org.opencontainers.image.source*) printf "%s\\n" "$OCI_SOURCE" ;;\n'
" esac\n"
" exit 0\n"
"fi\n"
'if [ "$1" = "build" ]; then\n'
' while [ "$#" -gt 0 ]; do\n'
' if [ "$1" = "-f" ]; then layer="$2"; shift 2; else shift; fi\n'
" done\n"
' case "$layer" in *Containerfile.compat) cp "$layer" "$LAYER_CAPTURE" ;; esac\n'
" exit 0\n"
"fi\n"
'if [ "$1" = "run" ]; then\n'
' case "$*" in\n'
' *"alembic -c mcpgateway/alembic.ini heads"*) printf "12d4a0c7789c (head)\\n" ;;\n'
' *"contextforge-runtime-contract.json"*) printf "%s\\n" "$CONTRACT_SHA" ;;\n'
' *"contextforge-entrypoint.sh"*) printf "%s\\n" "$ENTRYPOINT_SHA" ;;\n'
' *"contextforge-runtime-contract.py"*) printf "%s\\n" "$VERIFIER_SHA" ;;\n'
' *"contextforge-security-requirements.txt"*) printf "%s\\n" "$SECURITY_SHA" ;;\n'
" esac\n"
" exit 0\n"
"fi\n"
"exit 0\n",
encoding="utf-8",
)
docker.chmod(0o755)
contract_sha = hashlib.sha256(
(root / "infrastructure/images/contextforge/runtime-contract.json").read_bytes()
).hexdigest()
entrypoint_sha = hashlib.sha256(
(root / "infrastructure/images/contextforge/entrypoint.sh").read_bytes()
).hexdigest()
verifier_sha = hashlib.sha256(
(root / "scripts/contextforge_runtime_contract.py").read_bytes()
).hexdigest()
security_sha = hashlib.sha256(
(root / "infrastructure/images/contextforge/security-requirements.txt").read_bytes()
).hexdigest()
environment = {
**os.environ,
"PATH": f"{bin_dir}:{os.environ['PATH']}",
"CALL_LOG": str(log),
"LAYER_CAPTURE": str(layer_capture),
"SOURCE_ROOT": str(root),
"CONTRACT_SHA": contract_sha,
"ENTRYPOINT_SHA": entrypoint_sha,
"VERIFIER_SHA": verifier_sha,
"SECURITY_SHA": security_sha,
"OCI_SOURCE": "https://gitea.example.com/jens/itworx-mcp-hub",
"GIT_HEAD": "a" * 40,
"GIT_TREE": "b" * 40,
"GIT_EPOCH": "1790964000",
"GIT_COMMIT_DATE": "2026-09-30T00:00:00+00:00",
"APPOPS_SOURCE_REPOSITORY": "jens/itworx_mcp_hub",
"APPOPS_SOURCE_REVISION": "a" * 40,
"APPOPS_SOURCE_TREE": "b" * 40,
"APPOPS_BUILD_ID": "a" * 12,
"APPOPS_BUILD_DATE": "2026-10-02T18:00:00.123Z",
"SOURCE_DATE_EPOCH": "1790964000",
"GITEA_REPOSITORY": "jens/itworx_mcp_hub",
"GITEA_COMMIT_SHA": "a" * 40,
}
return root / "scripts/build_contextforge_compat_image.sh", environment, log, layer_capture
def test_candidate_mode_requires_frozen_metadata_before_git_or_docker(tmp_path: Path) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
env.pop("APPOPS_SOURCE_TREE")
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "APPOPS_SOURCE_TREE" in completed.stderr
assert not log.exists() or log.read_text(encoding="utf-8") == ""
def test_candidate_mode_rejects_unreviewed_build_variants_before_git_or_docker(
tmp_path: Path,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
completed = subprocess.run( # noqa: S603
[
shutil.which("bash") or "/bin/bash",
str(builder),
"--release-candidate",
"--enable-fips",
],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode == 64
assert "supported mode" in completed.stderr
assert not log.exists() or log.read_text(encoding="utf-8") == ""
def test_candidate_mode_rejects_invalid_calendar_date_before_git_or_docker(
tmp_path: Path,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
env["APPOPS_BUILD_DATE"] = "2026-02-30T18:00:00.123Z"
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "valid UTC calendar timestamp" in completed.stderr
assert not log.exists() or log.read_text(encoding="utf-8") == ""
@pytest.mark.parametrize("collision", ["final", "upstream", "staged"])
def test_candidate_mode_rejects_existing_revision_tags_after_source_validation_before_build(
tmp_path: Path, collision: str
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
revision = env["APPOPS_SOURCE_REVISION"]
suffix = {"final": "", "upstream": "-upstream", "staged": "-staged"}[collision]
env["COLLISION_TAG"] = f"itworx-mcp-hub/contextforge-schema:{revision}{suffix}"
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "already exists" in completed.stderr
calls = log.read_text(encoding="utf-8").splitlines()
assert any("docker image inspect" in call for call in calls)
assert any(call.startswith("git ") for call in calls)
assert not any("docker build" in call or "docker tag" in call for call in calls)
def test_candidate_mode_uses_only_revision_scoped_tags_and_frozen_oci_metadata(
tmp_path: Path,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, layer_capture = _candidate_builder_fixture(tmp_path)
static = EXPECTED["CONTEXTFORGE_LOCAL_IMAGE"]
revision = env["APPOPS_SOURCE_REVISION"]
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode == 0, completed.stderr
calls = log.read_text(encoding="utf-8").splitlines()
tags = [call for call in calls if call.startswith("docker tag ")]
assert tags == [
f"docker tag itworx-mcp-hub/contextforge-schema:{revision}-staged "
f"itworx-mcp-hub/contextforge-schema:{revision}"
]
assert any(
f"-t itworx-mcp-hub/contextforge-schema:{revision}-upstream " in call for call in calls
)
assert any(
f"-t itworx-mcp-hub/contextforge-schema:{revision}-staged " in call for call in calls
)
candidate_build = next(
call
for call in calls
if "docker build" in call and "Containerfile.itworx-candidate" in call
)
assert "--platform linux/amd64" in candidate_build
assert (
"UBI_BASE=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@"
"sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483"
in candidate_build
)
assert (
"NODEJS_IMAGE=registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e686"
in candidate_build
)
assert (
"UBI_MINIMAL=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@"
"sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236"
in candidate_build
)
assert (
"WHEELS_REF=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@"
"sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236"
in candidate_build
)
assert "ENABLE_RUST=false" in candidate_build
assert "ENABLE_RUST_MCP_RMCP=false" in candidate_build
assert "ENABLE_PROFILING=false" in candidate_build
assert "ENABLE_FIPS=false" in candidate_build
assert not any(static in call for call in calls)
assert not any(call.startswith("docker rmi ") for call in calls)
assert "NOT APPROVED OR DEPLOYED" in completed.stdout
layer = layer_capture.read_text(encoding="utf-8")
assert "-m pip" not in layer
assert "import importlib.util" in layer
assert "find_spec" in layer
assert (
'org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub"' in layer
)
assert f'org.opencontainers.image.revision="{revision}"' in layer
assert f'org.opencontainers.image.source-tree="{env["APPOPS_SOURCE_TREE"]}"' in layer
assert f'org.opencontainers.image.version="{env["APPOPS_BUILD_ID"]}"' in layer
assert f'org.opencontainers.image.created="{env["APPOPS_BUILD_DATE"]}"' in layer
assert f'org.opencontainers.image.source-date-epoch="{env["SOURCE_DATE_EPOCH"]}"' in layer
@pytest.mark.parametrize(
"dirty", [" M scripts/contextforge_runtime_contract.py\n", "?? unexpected.py\n"]
)
def test_candidate_mode_rejects_a_dirty_source_checkout_before_fetch_or_build(
tmp_path: Path,
dirty: str,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
env["GIT_DIRTY"] = dirty
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "source checkout has changes" in completed.stderr
calls = log.read_text(encoding="utf-8").splitlines()
assert not any("git init" in call or "git fetch" in call for call in calls)
assert not any(call.startswith("docker ") for call in calls)
assert not any("docker build" in call or "docker tag" in call for call in calls)
def test_candidate_mode_rejects_source_tree_mismatch_before_fetch_or_build(
tmp_path: Path,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
env["APPOPS_SOURCE_TREE"] = "c" * 40
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "source tree differs" in completed.stderr
calls = log.read_text(encoding="utf-8").splitlines()
assert any("rev-parse" in call for call in calls)
assert not any("git init" in call or "git fetch" in call for call in calls)
assert not any(call.startswith("docker ") for call in calls)
assert not any("docker build" in call or "docker tag" in call for call in calls)
def test_candidate_mode_rejects_revision_mismatch_with_checked_out_head(
tmp_path: Path,
) -> None:
if os.name == "nt":
pytest.skip("The Linux builder executes in the required Linux validation job")
builder, env, log, _layer = _candidate_builder_fixture(tmp_path)
env["APPOPS_SOURCE_REVISION"] = "c" * 40
env["GITEA_COMMIT_SHA"] = "c" * 40
env["APPOPS_BUILD_ID"] = "c" * 12
completed = subprocess.run( # noqa: S603
[shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"],
env=env,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode != 0
assert "checked-out Hub revision differs" in completed.stderr
calls = log.read_text(encoding="utf-8").splitlines()
assert any("rev-parse" in call for call in calls)
assert not any("git init" in call or "git fetch" in call for call in calls)
assert not any(call.startswith("docker ") for call in calls)
assert not any("docker build" in call or "docker tag" in call for call in calls)
@@ -3,6 +3,8 @@ from __future__ import annotations
import hashlib
from pathlib import Path
import yaml
from scripts.promotion_preflight import (
CONTEXTFORGE_PRODUCTION_IMAGE,
IMAGE_ENVIRONMENT,
@@ -14,15 +16,23 @@ ENV_EXAMPLE = ROOT / "infrastructure" / "compose" / ".env.example"
INPUT_MAP = ROOT / "infrastructure" / "compose" / "production-input-map.json"
PROD_COMPOSE = ROOT / "infrastructure" / "compose" / "docker-compose.prod.yml"
CONTRACT = ROOT / "infrastructure" / "images" / "contextforge" / "runtime-contract.json"
AUTODEPLOY = ROOT / "infrastructure" / "compose" / "docker-compose.autodeploy.yml"
def test_governed_contextforge_candidate_uses_the_exact_release_revision() -> None:
document = yaml.safe_load(AUTODEPLOY.read_text(encoding="utf-8"))
service = document["services"]["contextforge"]
assert service["image"] == (
"itworx-mcp-hub/contextforge-schema:${GITEA_COMMIT_SHA:?GITEA_COMMIT_SHA is required}"
)
assert service["pull_policy"] == "never"
assert not service.get("build")
def test_contextforge_uses_the_verified_local_compatibility_image() -> None:
assert "contextforge" not in IMAGE_ENVIRONMENT
assert "CONTEXTFORGE_IMAGE" not in REQUIRED_ENV
assert (
CONTEXTFORGE_PRODUCTION_IMAGE
== "itworx-mcp-hub/contextforge-schema:12d4a0c7789c"
)
assert CONTEXTFORGE_PRODUCTION_IMAGE == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c"
env_example = ENV_EXAMPLE.read_text(encoding="utf-8")
assert "CONTEXTFORGE_IMAGE=" not in env_example
@@ -42,8 +52,5 @@ def test_contextforge_contract_digest_is_repository_owned_and_exact() -> None:
expected = hashlib.sha256(CONTRACT.read_bytes()).hexdigest()
production = PROD_COMPOSE.read_text(encoding="utf-8")
assert (
f'ITWORX_CONTEXTFORGE_EXPECTED_CONTRACT_SHA256: "{expected}"'
in production
)
assert f'ITWORX_CONTEXTFORGE_EXPECTED_CONTRACT_SHA256: "{expected}"' in production
assert "${CONTEXTFORGE_CONTRACT_SHA256" not in production
+4 -1
View File
@@ -255,7 +255,10 @@ class IngestionCatalogComposeTests(unittest.TestCase):
**os.environ,
"SECRETS_DIR": str(ROOT / "tests/fixtures"),
"CONNECTOR_INGESTION_IMAGE": digest,
"GITEA_COMMIT_SHA": "test-sha",
"GITEA_COMMIT_SHA": "a" * 40,
"APPOPS_SOURCE_TREE": "b" * 40,
"APPOPS_BUILD_ID": "a" * 12,
"SOURCE_DATE_EPOCH": "1790888863",
},
capture_output=True,
text=True,
+358 -2
View File
@@ -3,10 +3,12 @@
from __future__ import annotations
import json
import sys
from datetime import date, timedelta
from pathlib import Path
from scripts.check_cve_exceptions import REQUIRED_FIELDS, check
from scripts.draft_cve_exceptions import main as draft_exceptions_main
ROOT = Path(__file__).resolve().parents[1]
TODAY = date(2026, 8, 15)
@@ -45,6 +47,13 @@ def _exception(**overrides) -> dict:
return base
def _image_entry(image_id: str, tag: str | None = None) -> dict[str, str]:
entry = {"imageId": image_id}
if tag is not None:
entry["tag"] = tag
return entry
def test_real_wp220_registry_has_every_required_field() -> None:
"""The actual drafted registry this packet ships must be schema-complete."""
doc = json.loads(
@@ -122,7 +131,7 @@ def test_changed_digest_fails() -> None:
exceptions = {
"exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})]
}
current_digests = {"control-api": "sha256:" + "b" * 64}
current_digests = {"control-api": _image_entry("sha256:" + "b" * 64)}
errors = check(scan, exceptions, current_digests, TODAY)
assert any("DIGEST CHANGED" in e for e in errors)
@@ -132,11 +141,258 @@ def test_matching_digest_passes() -> None:
exceptions = {
"exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})]
}
current_digests = {"control-api": "sha256:" + "a" * 64}
current_digests = {"control-api": _image_entry("sha256:" + "a" * 64)}
errors = check(scan, exceptions, current_digests, TODAY)
assert errors == []
def test_missing_current_service_for_affected_image_fails() -> None:
scan = _scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"affectedImages": ["local/control-api:dev", "local/worker:dev"],
}
)
exceptions = {
"exceptions": [
_exception(
affectedImageDigests={
"control-api": "sha256:" + "a" * 64,
"worker": "sha256:" + "b" * 64,
}
)
]
}
current = {"control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev")}
errors = check(scan, exceptions, current, TODAY)
assert any(
"AFFECTED IMAGE UNBOUND" in error and "local/worker:dev" in error for error in errors
)
def test_missing_exception_digest_binding_fails_closed() -> None:
scan = _scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"affectedImages": ["local/control-api:dev"],
}
)
exceptions = {
"exceptions": [
_exception(
affectedImageDigests={
"unrelated": "sha256:" + "c" * 64,
}
)
]
}
current = {"control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev")}
errors = check(scan, exceptions, current, TODAY)
assert any("IMAGE BINDING MISSING" in error and "control-api" in error for error in errors)
def test_empty_exception_digest_binding_fails_closed() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {"exceptions": [_exception(affectedImageDigests={"control-api": ""})]}
current = {"control-api": _image_entry("sha256:" + "a" * 64)}
errors = check(scan, exceptions, current, TODAY)
assert any("IMAGE BINDING MISSING" in error and "control-api" in error for error in errors)
def test_malformed_recorded_digest_fails_closed() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {"exceptions": [_exception(affectedImageDigests={"control-api": "sha256:abc"})]}
current = {"control-api": _image_entry("sha256:" + "a" * 64)}
errors = check(scan, exceptions, current, TODAY)
assert any("IMAGE BINDING INVALID" in error and "control-api" in error for error in errors)
def test_missing_or_malformed_current_image_id_fails_even_with_digest() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {"exceptions": [_exception()]}
current = {"control-api": {"digest": "sha256:" + "a" * 64}}
errors = check(scan, exceptions, current, TODAY)
assert any("CURRENT IMAGE ID INVALID" in error and "control-api" in error for error in errors)
def test_malformed_current_image_id_fails_even_with_digest() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {"exceptions": [_exception()]}
current = {
"control-api": {
"imageId": "sha256:not-a-full-image-id",
"digest": "sha256:" + "a" * 64,
}
}
errors = check(scan, exceptions, current, TODAY)
assert any("CURRENT IMAGE ID INVALID" in error for error in errors)
def test_empty_current_image_inventory_fails_when_supplied() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
errors = check(scan, {"exceptions": [_exception()]}, {}, TODAY)
assert any("IMAGE INVENTORY INVALID" in error for error in errors)
def test_scan_installed_version_must_match_reviewed_version() -> None:
scan = _scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": "2.0",
}
)
errors = check(scan, {"exceptions": [_exception(installedVersion="1.0")]}, None, TODAY)
assert any("VERSION MISMATCH" in error and "2.0" in error for error in errors)
def test_conflicting_duplicate_exceptions_fail_instead_of_overwriting() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
approved = _exception()
conflicting = _exception(
approvalStatus="pending", riskOwner="", approver="", compensatingControls=""
)
errors = check(scan, {"exceptions": [approved, conflicting]}, None, TODAY)
assert any("CONFLICTING DUPLICATE" in error for error in errors)
assert any("UNAPPROVED" in error for error in errors)
def test_scoped_approval_binds_only_affected_services() -> None:
scan = _scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": "1.0",
"affectedImages": ["local/worker:dev"],
}
)
exceptions = {"exceptions": [_exception(affectedImageDigests={"worker": "sha256:" + "b" * 64})]}
current = {
"control-api": _image_entry("sha256:" + "c" * 64, "local/control-api:dev"),
"worker": _image_entry("sha256:" + "b" * 64, "local/worker:dev"),
}
errors = check(scan, exceptions, current, TODAY)
assert errors == []
def test_legacy_scan_without_affected_images_uses_full_inventory() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {
"exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})]
}
current = {
"control-api": _image_entry("sha256:" + "a" * 64),
"worker": _image_entry("sha256:" + "b" * 64),
}
errors = check(scan, exceptions, current, TODAY)
assert any("IMAGE BINDING MISSING" in error and "worker" in error for error in errors)
def test_legacy_scan_rejects_inventory_missing_a_reviewed_service() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {
"exceptions": [
_exception(
affectedImageDigests={
"control-api": "sha256:" + "a" * 64,
"worker": "sha256:" + "b" * 64,
}
)
]
}
current = {"control-api": _image_entry("sha256:" + "a" * 64)}
errors = check(scan, exceptions, current, TODAY)
assert any("IMAGE INVENTORY SCOPE CHANGED" in error for error in errors)
def test_draft_is_pending_and_scopes_digests_to_affected_images(
tmp_path: Path, monkeypatch
) -> None:
scan_path = tmp_path / "scan.json"
image_path = tmp_path / "images.json"
output_path = tmp_path / "draft.json"
scan_path.write_text(
json.dumps(
{
**_scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": "1.0",
"affectedImages": ["local/worker:dev"],
}
),
"scannedAt": "2026-08-15T00:00:00Z",
}
),
encoding="utf-8",
)
image_path.write_text(
json.dumps(
{
"images": {
"control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev"),
"worker": _image_entry("sha256:" + "b" * 64, "local/worker:dev"),
}
}
),
encoding="utf-8",
)
monkeypatch.setattr(
sys,
"argv",
[
"draft_cve_exceptions.py",
"--scan-summary",
str(scan_path),
"--image-digests",
str(image_path),
"--evidence-reference",
"evidence/WP-200/test.md",
"--output",
str(output_path),
],
)
assert draft_exceptions_main() == 0
drafted = json.loads(output_path.read_text(encoding="utf-8"))["exceptions"]
assert len(drafted) == 1
assert drafted[0]["approvalStatus"] == "pending"
assert drafted[0]["affectedImageDigests"] == {"worker": "sha256:" + "b" * 64}
def test_malformed_exception_missing_required_field_fails() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
broken = _exception()
@@ -158,3 +414,103 @@ def test_multiple_cves_in_one_package_row_each_need_their_own_exception() -> Non
errors = check(scan, exceptions, None, TODAY)
assert not any("CVE-2026-1" in e for e in errors)
assert any("CVE-2026-2" in e and "UNAPPROVED" in e for e in errors)
def test_distinct_installed_versions_are_reviewed_separately_for_scoped_images() -> None:
scan = _scan(
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": "1.0",
"affectedImages": ["local/web:dev"],
},
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": "2.0",
"affectedImages": ["local/postgres:dev"],
},
)
exceptions = {
"exceptions": [
_exception(affectedImageDigests={"web": "sha256:" + "a" * 64}),
_exception(
id="pkg-CVE-2026-1-v2",
installedVersion="2.0",
affectedImageDigests={"postgres": "sha256:" + "b" * 64},
),
]
}
images = {
"web": _image_entry("sha256:" + "a" * 64, "local/web:dev"),
"postgres": _image_entry("sha256:" + "b" * 64, "local/postgres:dev"),
}
assert check(scan, exceptions, images, TODAY) == []
exceptions["exceptions"].pop()
assert any("VERSION MISMATCH" in error for error in check(scan, exceptions, images, TODAY))
def test_versionless_legacy_scan_rejects_ambiguous_version_reviews() -> None:
scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"})
exceptions = {"exceptions": [_exception(), _exception(id="v2", installedVersion="2.0")]}
assert any("AMBIGUOUS VERSION" in error for error in check(scan, exceptions, None, TODAY))
def test_draft_distinct_versions_keeps_unique_pending_image_bound_reviews(
tmp_path: Path, monkeypatch
) -> None:
scan = _scan(
*[
{
"package": "pkg",
"cves": ["CVE-2026-1"],
"severity": "HIGH",
"installedVersion": version,
"affectedImages": [f"local/{service}:dev"],
}
for version, service in [("1.0", "web"), ("2.0", "postgres")]
]
)
scans, images, output = (
tmp_path / "scan.json",
tmp_path / "images.json",
tmp_path / "draft.json",
)
scans.write_text(json.dumps(scan), encoding="utf-8")
images.write_text(
json.dumps(
{
"images": {
"web": _image_entry("sha256:" + "a" * 64, "local/web:dev"),
"postgres": _image_entry("sha256:" + "b" * 64, "local/postgres:dev"),
}
}
),
encoding="utf-8",
)
monkeypatch.setattr(
sys,
"argv",
[
"draft",
"--scan-summary",
str(scans),
"--image-digests",
str(images),
"--output",
str(output),
"--evidence-reference",
"fixture",
],
)
assert draft_exceptions_main() == 0
drafted = json.loads(output.read_text(encoding="utf-8"))["exceptions"]
assert len(drafted) == 2 and len({entry["id"] for entry in drafted}) == 2
assert all(
entry["approvalStatus"] == "pending" and not entry["riskOwner"] and not entry["approver"]
for entry in drafted
)
assert drafted[0]["affectedImageDigests"] == {"web": "sha256:" + "a" * 64}
assert drafted[1]["affectedImageDigests"] == {"postgres": "sha256:" + "b" * 64}