88 lines
3.8 KiB
Python
88 lines
3.8 KiB
Python
"""WP-170 release-contract regression tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from scripts.promotion_preflight import _RESET, _ComposeMergeLoader
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
COMPOSE = ROOT / "infrastructure/compose/docker-compose.yml"
|
|
PROD_COMPOSE = ROOT / "infrastructure/compose/docker-compose.prod.yml"
|
|
INVENTORY = ROOT / "evidence/WP-170/release-inventory.json"
|
|
TRACEABILITY = ROOT / "evidence/WP-170/traceability.json"
|
|
|
|
|
|
def test_release_compose_overlay_requires_immutable_images_and_never_builds() -> None:
|
|
# _ComposeMergeLoader subclasses yaml.SafeLoader and only adds the !reset tag.
|
|
document = yaml.load(PROD_COMPOSE.read_text(encoding="utf-8"), Loader=_ComposeMergeLoader) # noqa: S506
|
|
base = yaml.safe_load(COMPOSE.read_text(encoding="utf-8"))
|
|
services = document["services"]
|
|
assert services
|
|
for name, service in services.items():
|
|
if base["services"][name].get("profiles"):
|
|
# Optional services are absent from the baseline promotion. Their
|
|
# activation overlay pins the image and is tested separately.
|
|
assert service.get("build") is _RESET
|
|
continue
|
|
if "image" not in service:
|
|
# Production may override runtime configuration without repeating an
|
|
# image only when the inherited base service is third-party,
|
|
# build-free, and already immutable by digest.
|
|
base_service = base["services"][name]
|
|
assert "build" not in base_service
|
|
assert "@sha256:" in str(base_service["image"])
|
|
continue
|
|
|
|
assert service.get("build") is _RESET
|
|
image = str(service["image"])
|
|
if image.startswith("${"):
|
|
assert "immutable" in image and ":?" in image
|
|
else:
|
|
# The recovered ContextForge baseline is one deliberately bounded
|
|
# exception until this exact compatibility image is promoted to an
|
|
# immutable registry digest. It must stay on the reviewed local tag
|
|
# and must never be pulled implicitly.
|
|
if name == "contextforge":
|
|
assert image == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c"
|
|
assert service.get("pull_policy") == "never"
|
|
continue
|
|
# Other literal production images remain immutable by digest.
|
|
assert "@sha256:" in image
|
|
|
|
|
|
def test_base_compose_preserves_socket_and_edge_boundaries() -> None:
|
|
document = yaml.safe_load(COMPOSE.read_text(encoding="utf-8"))
|
|
assert any(
|
|
"/var/run/docker.sock" in str(value)
|
|
for value in document["services"]["docker-read-broker"].get("volumes", [])
|
|
)
|
|
assert not document["services"]["docker-read-broker"].get("ports")
|
|
|
|
|
|
def test_release_inventory_records_all_rc_images_and_external_boundaries() -> None:
|
|
inventory = json.loads(INVENTORY.read_text(encoding="utf-8"))
|
|
assert inventory["sourceCommit"]
|
|
assert inventory["releaseDecision"]["v1Tag"] is None
|
|
assert inventory["releaseDecision"]["reason"] == "external-input-required"
|
|
assert len(inventory["images"]) == 8
|
|
assert all(image["digest"].startswith("sha256:") for image in inventory["images"])
|
|
assert inventory["externalInputs"]
|
|
|
|
|
|
def test_traceability_covers_every_acceptance_matrix_capability() -> None:
|
|
matrix = (ROOT / "ACCEPTANCE_MATRIX.md").read_text(encoding="utf-8")
|
|
traceability = json.loads(TRACEABILITY.read_text(encoding="utf-8"))
|
|
capabilities = [
|
|
line.split("|", 2)[1].strip()
|
|
for line in matrix.splitlines()
|
|
if line.startswith("|")
|
|
and not line.startswith("| Capability")
|
|
and not line.startswith("|---")
|
|
]
|
|
assert {item["capability"] for item in traceability["requirements"]} == set(capabilities)
|
|
assert all(item["evidence"] for item in traceability["requirements"])
|