Files
ModelForge/backend/tests/test_release_provenance_v121.py

328 lines
13 KiB
Python

"""Release packaging and provenance guarantees added in v1.2.1.
Two defects reached production before these existed, and both were invisible to every gate the
project had. Neither was a code bug: the source was correct in both cases and the *artifact* was
wrong, which is precisely the class of failure a unit test looking at source cannot see.
**The console could not reach its own API.** Vite inlines ``VITE_*`` at build time, but
``release_build.py`` never passed ``VITE_API_BASE_URL``, so every release image compiled the
Dockerfile's development default — ``http://localhost:8000`` — into an immutable bundle, and the
nginx CSP, derived from the same argument, hardcoded the same wrong origin. The frontend test suite
passed because jsdom never performs a cross-origin fetch; the release gate passed because it
inspects labels and checksums, not bundle contents. It surfaced only when a human opened the
production console.
**The Node Agent's identity drifted from its tag.** ``docker-compose.node-agent.yml`` declared a
``build:`` block with no ``args:``, so a Compose-built agent got version ``0.0.0`` and empty
revision/created labels while still being tagged from ``MODELFORGE_VERSION``. Production ran an
image tagged ``1.1.1`` whose contents were ``1.2.0`` and whose OCI labels were blank.
These tests read the actual release inputs — the Dockerfiles, the Compose projections and the
release builder — so the same two failures cannot ship again without failing here first.
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
import yaml
from modelforge_api.domain.release import PRODUCT_VERSION
ROOT = Path(__file__).resolve().parents[2]
RELEASE_BUILD = ROOT / "scripts" / "release_build.py"
FRONTEND_DOCKERFILE = ROOT / "frontend" / "Dockerfile"
NODE_AGENT_DOCKERFILE = ROOT / "node-agent" / "Dockerfile"
#: Every image the release builder publishes. The Runtime Worker is a first-class private runtime
#: artifact even though it exposes no public listener.
RELEASE_IMAGES = (
"modelforge-api",
"modelforge-web",
"modelforge-node-agent",
"modelforge-runtime-worker",
)
#: The OCI fields a release image must carry for an operator to trace it back to a commit.
REQUIRED_OCI_LABELS = (
"org.opencontainers.image.title",
"org.opencontainers.image.version",
"org.opencontainers.image.revision",
"org.opencontainers.image.created",
"org.opencontainers.image.source",
)
def _release_build_source() -> str:
return RELEASE_BUILD.read_text(encoding="utf-8")
def _compose(name: str) -> dict:
return yaml.safe_load((ROOT / name).read_text(encoding="utf-8"))
# --------------------------------------------------------------- console API origin (defect A)
def test_the_release_builder_requires_an_explicit_public_api_origin() -> None:
"""No origin, no release. The default that shipped v1.2.0 was a development convenience."""
source = _release_build_source()
assert "--public-api-origin" in source
assert "MODELFORGE_PUBLIC_API_ORIGIN" in source
assert "def normalise_public_api_origin" in source
def test_a_release_build_without_an_api_origin_fails_closed() -> None:
import importlib.util
spec = importlib.util.spec_from_file_location("modelforge_release_build", RELEASE_BUILD)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for empty in ("", " "):
with pytest.raises(SystemExit) as raised:
module.normalise_public_api_origin(empty)
assert "public API origin" in str(raised.value)
@pytest.mark.parametrize(
"value",
[
"localhost:8000", # no scheme: ambiguous, and how the original default looked
"ftp://example.com",
"https://",
"https://example.com/api", # a path would be appended twice
"https://example.com?x=1",
],
)
def test_a_malformed_api_origin_is_refused(value: str) -> None:
import importlib.util
spec = importlib.util.spec_from_file_location("modelforge_release_build", RELEASE_BUILD)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
with pytest.raises(SystemExit):
module.normalise_public_api_origin(value)
@pytest.mark.parametrize(
("given", "expected"),
[
("https://modelforge.example.com", "https://modelforge.example.com"),
("http://192.0.2.10:18000/", "http://192.0.2.10:18000"),
(" https://example.com ", "https://example.com"),
],
)
def test_a_valid_api_origin_is_normalised(given: str, expected: str) -> None:
import importlib.util
spec = importlib.util.spec_from_file_location("modelforge_release_build", RELEASE_BUILD)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert module.normalise_public_api_origin(given) == expected
def test_the_api_origin_reaches_the_console_image_build() -> None:
"""The validated origin must actually be passed to the build that inlines it."""
source = _release_build_source()
assert "VITE_API_BASE_URL={public_api_origin}" in source
assert "ORIGIN_DEPENDENT_IMAGES" in source
assert "modelforge-web" in source
def test_the_release_manifest_records_the_console_api_origin() -> None:
"""An operator must be able to see the compiled-in origin without unpacking the image."""
assert '"public_api_origin"' in _release_build_source()
def test_the_console_csp_is_derived_from_the_same_origin_argument() -> None:
"""The bundle and the policy that protects it cannot be allowed to disagree."""
dockerfile = FRONTEND_DOCKERFILE.read_text(encoding="utf-8")
assert 'API_ORIGIN=$(printf \'%s\' "${VITE_API_BASE_URL}" | cut -d/ -f1-3)' in dockerfile
# The build fails rather than shipping a policy with an unsubstituted placeholder.
assert 'grep -q "connect-src \'self\' ${API_ORIGIN};"' in dockerfile
def test_the_console_csp_is_never_widened_to_a_wildcard() -> None:
template = (ROOT / "frontend" / "security-headers.inc.template").read_text(encoding="utf-8")
connect = [line for line in template.splitlines() if "connect-src" in line]
assert connect, "the template must define connect-src"
for line in connect:
assert "connect-src 'self' __API_ORIGIN__" in line
assert "*" not in line.split("connect-src", 1)[1].split(";")[0]
def test_local_development_keeps_its_convenient_default() -> None:
"""Fail-closed is a property of the release path, not of `docker compose up` on a laptop."""
dockerfile = FRONTEND_DOCKERFILE.read_text(encoding="utf-8")
assert "ARG VITE_API_BASE_URL=http://localhost:8000" in dockerfile
development = _compose("docker-compose.yml")["services"]["web"]["build"]["args"]
assert development["VITE_API_BASE_URL"] == "${VITE_API_BASE_URL:-http://localhost:8000}"
def test_the_production_overlay_still_demands_an_api_base_url() -> None:
production = _compose("docker-compose.production.yml")["services"]["web"]["build"]["args"]
assert production["VITE_API_BASE_URL"].startswith("${VITE_API_BASE_URL:?")
# ------------------------------------------------------------- image provenance (defect B)
def test_every_release_dockerfile_declares_the_release_identity_arguments() -> None:
for relative in (
"backend/Dockerfile",
"frontend/Dockerfile",
"node-agent/Dockerfile",
"runtime-worker/Dockerfile",
):
text = (ROOT / relative).read_text(encoding="utf-8")
for argument in ("MODELFORGE_VERSION", "MODELFORGE_COMMIT", "MODELFORGE_BUILT_AT"):
assert f"ARG {argument}" in text, f"{relative} does not accept {argument}"
def test_every_release_dockerfile_emits_the_required_oci_labels() -> None:
for relative in (
"backend/Dockerfile",
"frontend/Dockerfile",
"node-agent/Dockerfile",
"runtime-worker/Dockerfile",
):
text = (ROOT / relative).read_text(encoding="utf-8")
for label in REQUIRED_OCI_LABELS:
assert f'LABEL {label}=' in text, f"{relative} does not set {label}"
def test_the_oci_version_and_revision_come_from_the_release_arguments() -> None:
"""A label typed in by hand is a label that drifts."""
for relative in (
"backend/Dockerfile",
"frontend/Dockerfile",
"node-agent/Dockerfile",
"runtime-worker/Dockerfile",
):
text = (ROOT / relative).read_text(encoding="utf-8")
assert 'org.opencontainers.image.version="${MODELFORGE_VERSION}"' in text
assert 'org.opencontainers.image.revision="${MODELFORGE_COMMIT}"' in text
assert 'org.opencontainers.image.created="${MODELFORGE_BUILT_AT}"' in text
def test_the_node_agent_compose_build_passes_the_release_identity() -> None:
"""The exact gap that let production run a 1.1.1 tag containing 1.2.0."""
build = _compose("docker-compose.node-agent.yml")["services"]["node-agent"]["build"]
args = build.get("args")
assert args, "the node-agent build must pass release identity arguments"
assert args["MODELFORGE_VERSION"] == "${MODELFORGE_VERSION:-0.0.0}"
assert args["MODELFORGE_COMMIT"] == "${MODELFORGE_COMMIT:-}"
assert args["MODELFORGE_BUILT_AT"] == "${MODELFORGE_BUILT_AT:-}"
def test_every_composed_release_image_passes_the_release_identity() -> None:
"""Whatever builds a published image must stamp it; otherwise the tag is the only identity."""
for compose_file, service in (
("docker-compose.yml", "api"),
("docker-compose.yml", "web"),
("docker-compose.node-agent.yml", "node-agent"),
("docker-compose.runtime-worker.yml", "runtime-worker"),
):
build = _compose(compose_file)["services"][service].get("build")
assert build, f"{compose_file}:{service} has no build section"
args = build.get("args") or {}
assert "MODELFORGE_VERSION" in args, f"{compose_file}:{service} omits MODELFORGE_VERSION"
assert "MODELFORGE_COMMIT" in args, f"{compose_file}:{service} omits MODELFORGE_COMMIT"
def test_the_node_agent_image_reference_is_versioned_and_never_floating() -> None:
service = _compose("docker-compose.node-agent.yml")["services"]["node-agent"]
reference = service["image"]
assert "${MODELFORGE_NODE_AGENT_IMAGE:-" in reference
assert "${MODELFORGE_VERSION:-local}" in reference
assert ":latest" not in reference
def test_the_runtime_worker_image_reference_is_versioned_and_never_floating() -> None:
service = _compose("docker-compose.runtime-worker.yml")["services"]["runtime-worker"]
reference = service["image"]
assert "${MODELFORGE_RUNTIME_WORKER_IMAGE:-" in reference
assert "${MODELFORGE_VERSION:-local}" in reference
assert ":latest" not in reference
def test_the_agent_version_derives_from_the_repository_version_file() -> None:
"""Single-source versioning: VERSION drives every packaged manifest.
Read from the file rather than imported: the Node Agent is a separate distribution and is not
installed into the backend's environment, and a version test that silently skips when the
import fails would be worthless.
"""
text = (ROOT / "node-agent" / "src" / "modelforge_node_agent" / "__init__.py").read_text(
encoding="utf-8"
)
match = re.search(r'__version__\s*=\s*"([^"]+)"', text)
assert match, "the Node Agent must declare __version__"
assert match.group(1) == PRODUCT_VERSION
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == PRODUCT_VERSION
def test_the_runtime_worker_version_matches_the_release() -> None:
text = (ROOT / "runtime-worker" / "src" / "modelforge_runtime_worker" / "__init__.py").read_text(
encoding="utf-8"
)
match = re.search(r'__version__\s*=\s*"([^"]+)"', text)
assert match and match.group(1) == PRODUCT_VERSION
def test_the_release_builder_publishes_exactly_the_expected_images() -> None:
source = _release_build_source()
for image in RELEASE_IMAGES:
assert f'"{image}"' in source
# ---------------------------------------------------------------- artifact reproducibility
def test_release_artifacts_are_written_with_unix_line_endings() -> None:
"""`sha256sum -c` treats a trailing CR as part of the filename and fails on every entry.
Found while verifying v1.2.0 on Windows: all four digests were correct and not one filename
could be read.
"""
source = _release_build_source()
assert source.count('newline="\\n"') >= 2
assert 'checksums.write_text("\\n".join(lines) + "\\n", encoding="utf-8", newline="\\n")' in (
source
)
def test_the_build_timestamp_feeds_the_reproducible_build_contract() -> None:
source = _release_build_source()
assert "SOURCE_DATE_EPOCH" in source
assert "def source_date_epoch" in source
def test_the_release_build_refuses_a_dirty_tree_by_default() -> None:
source = _release_build_source()
assert "refusing to build a release from a dirty working tree" in source
def test_the_release_build_records_no_host_specific_paths() -> None:
"""A manifest that names the machine that built it is not a portable release record."""
source = _release_build_source()
for accidental in ("C:\\\\", "/home/", "/Users/", "os.getcwd()"):
assert accidental not in source, f"release build references {accidental!r}"