# 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 _prior_recipe_receipt_hash(data: bytes) -> str: """Only this Git LF-normalized historical JSON receipt permits CRLF checkout bytes.""" normalized = data.replace(b"\r\n", b"\n") if b"\r" in normalized: raise ValueError("unsupported historical receipt newline") return _hash(normalized) def test_prior_recipe_receipt_identity_accepts_only_git_lf_or_crlf_checkout() -> None: import subprocess path = "evidence/WP-400/CF_POSTGRES_BINARY_RECIPE.json" blob = subprocess.check_output(["git", "show", "HEAD:" + path], cwd=ROOT) # noqa: S603,S607 - fixed local Git object assert b"\r" not in blob assert ( _prior_recipe_receipt_hash(blob) == _prior_recipe_receipt_hash(blob.replace(b"\n", b"\r\n")) == _hash(blob) ) assert _prior_recipe_receipt_hash(blob + b" ") != _hash(blob) with pytest.raises(ValueError, match="unsupported historical receipt newline"): _prior_recipe_receipt_hash(blob.replace(b"\n", b"\r", 1)) 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[project.optional-dependencies]\npostgres=["psycopg[c,binary]>=3.3.4"]\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"] [project.optional-dependencies] postgres = ["psycopg[c,binary]>=3.3.4"] [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"], "optional-dependencies": {"postgres": ["psycopg[binary]>=3.3.4"]}, } 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" for package, version in ( ("anyio", "4.14.2"), ("cryptography", "50.0.0"), ("langgraph-sdk", "0.4.4"), ("langgraph", "1.2.3"), ): assert f"{package}=={version}" in parsed["tool"]["uv"]["constraint-dependencies"] assert parsed["tool"]["uv"]["exclude-newer-package"][package] == "2026-10-14T00:00:00Z" assert parsed["dependency-groups"]["build-tools"] == list(recipe.BUILD_TOOLS) assert "dev" not in parsed["dependency-groups"] @pytest.mark.parametrize( "requirement", ["langgraph<=1.2.0", "langgraph==1.2.0", 'langgraph<=1.2.0","langgraph<=1.2.0'] ) def test_runtime_langgraph_compatibility_patch_has_one_exact_source_anchor( requirement: str, ) -> None: source = ( '[project.optional-dependencies]\npostgres=["psycopg[c,binary]>=3.3.4"]\n' f'llmchat=["{requirement}"]\n' ) if requirement == "langgraph<=1.2.0": result = recipe.runtime_pyproject(source) assert 'llmchat=["langgraph==1.2.3"]' in result assert 'postgres=["psycopg[binary]>=3.3.4"]' in result assert "langgraph<=1.2.0" in source else: with pytest.raises(recipe.CandidateRecipeError, match="LangGraph compatibility"): recipe.runtime_pyproject(source) def test_runtime_lock_rejects_source_only_postgres_adapter(monkeypatch) -> None: manifest = recipe._read_lock_manifest() original = recipe._require_file def read(path): data = original(path) return data + b"\npsycopg-c==3.3.4\n" if path == recipe.PRODUCTION_LOCK else data monkeypatch.setattr(recipe, "_require_file", read) with pytest.raises(recipe.CandidateRecipeError, match="source-only PostgreSQL adapter"): recipe.verify_lock_contents(manifest) @pytest.mark.parametrize("value", ["psycopg[c]>=3.3.4", "psycopg[c,binary]>=3.3.5"]) def test_binary_adapter_policy_denies_unreviewed_upstream_drift(value) -> None: source = f'[project.optional-dependencies]\npostgres=["{value}"]\n' with pytest.raises( recipe.CandidateRecipeError, match="upstream PostgreSQL adapter contract changed" ): recipe.binary_postgres_pyproject(source) 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 "COPY pyproject.itworx-candidate.toml /app/pyproject.toml" in transformed assert "assert psycopg.pq.__impl__ == 'binary'" in transformed 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() assert _hash((source / "pyproject.toml").read_bytes()) == source_hashes["pyproject.toml"] derived = (source / "pyproject.itworx-candidate.toml").read_text() assert '"psycopg[binary]>=3.3.4"' in derived assert "psycopg[c,binary]" not in derived 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", } # The published OS receipt is immutable historical evidence. A later, # explicit Python-only recipe delta binds its changed inputs separately. delta = json.loads((ROOT / "evidence/WP-400/CF_POSTGRES_BINARY_RECIPE.json").read_text()) assert delta["schema"] == "itworx.contextforge-postgres-binary-recipe/v1" assert delta["frozenBaseEvidenceSha256"] == _hash(evidence_path.read_bytes()) changed = { "scripts/contextforge_candidate_recipe.py", "infrastructure/images/contextforge/candidate/locks.json", "infrastructure/images/contextforge/candidate/production-requirements.lock", } assert set(delta["historicalInputsSha256"]) == changed assert set(delta["candidateInputsSha256"]) == changed assert delta["historicalInputsSha256"] == { name: evidence["hubInputsSha256"][name] for name in changed } security = json.loads((ROOT / "evidence/WP-400/CF_SECURITY_RECIPE.json").read_text()) assert security["schema"] == "itworx.contextforge-security-recipe/v1" assert security["priorRecipeEvidenceHashMode"] == "git-blob-lf" assert security["priorRecipeEvidenceSha256"] == _prior_recipe_receipt_hash( (ROOT / "evidence/WP-400/CF_POSTGRES_BINARY_RECIPE.json").read_bytes() ) assert security["historicalInputsSha256"] == delta["candidateInputsSha256"] assert set(security["candidateInputsSha256"]) == changed for name, historical_hash in evidence["hubInputsSha256"].items(): expected = security["candidateInputsSha256"].get(name, historical_hash) assert _hash((ROOT / name).read_bytes()) == expected assert delta["finalCandidateBuilt"] is False assert delta["policy"] == "psycopg[binary]==3.3.4; hash-locked wheels only" 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()