Files
geointel/backend/tests/test_training_release_manifest.py
Jens faeb58ef6d
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
Initial public release
2026-08-31 21:56:53 +02:00

415 lines
16 KiB
Python

from __future__ import annotations
import hashlib
import importlib.util
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
SCRIPTS = ROOT / "scripts"
if str(SCRIPTS) not in sys.path:
sys.path.insert(0, str(SCRIPTS))
SCRIPT = SCRIPTS / "training_release_manifest.py"
SPEC = importlib.util.spec_from_file_location("training_release_manifest", SCRIPT)
assert SPEC and SPEC.loader
MODULE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MODULE
SPEC.loader.exec_module(MODULE)
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
@pytest.fixture(autouse=True)
def _synthetic_release_uses_a_static_live_registry_spy(monkeypatch: pytest.MonkeyPatch) -> None:
"""Filesystem unit fixtures cannot resolve a real database, but must ask for it."""
original_assert = MODULE.assert_frozen_manifest_training_eligible
original_failures = MODULE.frozen_manifest_training_eligibility_failures
def static_assert(*args, **kwargs):
assert kwargs.get("verify_live") is True
kwargs["verify_live"] = False
return original_assert(*args, **kwargs)
def static_failures(*args, **kwargs):
assert kwargs.get("verify_live") is True
kwargs["verify_live"] = False
return original_failures(*args, **kwargs)
monkeypatch.setattr(MODULE, "assert_frozen_manifest_training_eligible", static_assert)
monkeypatch.setattr(MODULE, "frozen_manifest_training_eligibility_failures", static_failures)
def write_corpus_manifest(tmp_path: Path, *, fixture_mode: bool = False) -> Path:
policy = "geointel-training-source-eligibility/v1"
def pair(sample_slug: str) -> dict:
raster_id = f"dataset:raster:{sample_slug}"
reference_id = f"dataset:reference:{sample_slug}"
return {
"policy_version": policy,
"eligible": True,
"fixture_mode": fixture_mode,
"raster": {
"eligible": True,
"reasons": [],
"evidence": {
"dataset_id": raster_id,
"checksum_sha256": "a" * 64,
"source_registry_id": "registry:orthophoto",
"source_snapshot_id": "snapshot:orthophoto",
},
},
"reference": {
"eligible": True,
"reasons": [],
"evidence": {
"dataset_id": reference_id,
"checksum_sha256": "b" * 64,
"source_registry_id": "registry:grb",
"source_snapshot_id": "snapshot:grb",
},
},
}
samples = []
for sample_slug, split in (("fixture-train", "train"), ("fixture-val", "val")):
samples.append(
{
"sample_slug": sample_slug,
"split": split,
"raster_dataset_id": f"dataset:raster:{sample_slug}",
"reference_dataset_id": f"dataset:reference:{sample_slug}",
"training_eligibility": pair(sample_slug),
}
)
manifest = tmp_path / "operator_samples_manifest.json"
manifest.write_text(
json.dumps(
{
"immutable": True,
"training_eligibility": {
"policy_version": policy,
"status": "eligible",
"fixture_mode": fixture_mode,
},
"samples": samples,
}
),
encoding="utf-8",
)
(tmp_path / "corpus-freeze.json").write_text(
json.dumps(
{
"schema_version": 2,
"immutable": True,
"fixture_mode": fixture_mode,
"training_eligibility_policy": policy,
"manifest_sha256": sha256(manifest),
}
),
encoding="utf-8",
)
return manifest
def write_yolo_dataset(tmp_path: Path, *, empty_train_label: bool = False) -> Path:
dataset_root = tmp_path / "dataset"
for split, sample_slug in (("train", "fixture-train"), ("val", "fixture-val")):
image = dataset_root / "images" / split / f"{sample_slug}.png"
label = dataset_root / "labels" / split / f"{sample_slug}.txt"
image.parent.mkdir(parents=True, exist_ok=True)
label.parent.mkdir(parents=True, exist_ok=True)
image.write_bytes(f"{split}-image".encode("utf-8"))
label.write_text("" if split == "train" and empty_train_label else "0 0.5 0.5 0.2 0.2\n", encoding="utf-8")
yaml_path = dataset_root / "dataset.yaml"
yaml_path.write_text(
f"path: {dataset_root}\ntrain: images/train\nval: images/val\nnames:\n 0: building\n",
encoding="utf-8",
)
return yaml_path
def write_accepted_review_audit(tmp_path: Path, corpus_manifest: Path) -> Path:
artifacts = {}
for sample_slug in ("fixture-train", "fixture-val"):
artifact = tmp_path / f"{sample_slug}-contact-sheet.png"
artifact.write_bytes(f"reviewed {sample_slug}".encode("utf-8"))
artifacts[sample_slug] = artifact
decisions = tmp_path / "review-decisions.json"
decisions.write_text(
json.dumps(
{
"decisions": [
{
"sample_slug": sample_slug,
"decision": "accepted",
"reviewer": "reviewer@example.test",
"reviewed_at": "2026-08-01T12:00:00+00:00",
"reviewed_artifact_path": str(artifact.resolve()),
"reviewed_artifact_sha256": sha256(artifact),
}
for sample_slug, artifact in artifacts.items()
]
}
),
encoding="utf-8",
)
evidence = {
"review_decisions_path": str(decisions.resolve()),
"review_decisions_sha256": sha256(decisions),
"required_sample_count": 2,
"accepted_sample_count": 2,
"accepted_sample_slugs": ["fixture-train", "fixture-val"],
"reviewer_ids": ["reviewer@example.test"],
"reviewed_at_by_sample": {
"fixture-train": "2026-08-01T12:00:00+00:00",
"fixture-val": "2026-08-01T12:00:00+00:00",
},
"reviewed_artifact_path_by_sample": {
sample_slug: str(artifact.resolve()) for sample_slug, artifact in artifacts.items()
},
"reviewed_artifact_sha256_by_sample": {
sample_slug: sha256(artifact) for sample_slug, artifact in artifacts.items()
},
}
audit = tmp_path / "belgium-building-corpus-audit.json"
audit.write_text(
json.dumps(
{
"status": "ok",
"manifest_immutable": True,
"spatial_leakage_status": "ok",
"review_complete": True,
"corpus_manifest_path": str(corpus_manifest.resolve()),
"corpus_manifest_sha256": sha256(corpus_manifest),
"human_review_evidence": evidence,
}
),
encoding="utf-8",
)
return audit
def test_valid_operational_release_binds_yaml_assets_corpus_and_human_review(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
paths = MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
assert all(path.is_file() for path in paths.values())
assert MODULE.training_release_failures(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
) == []
def test_tile_summary_must_be_an_exact_view_of_the_live_verified_release(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
paths = MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
assets = json.loads(paths["asset_manifest"].read_text(encoding="utf-8"))
summary = yaml_path.parent / "yolo_tile_dataset_summary.json"
summary.write_text(
json.dumps(
{
"dataset_yaml": str(yaml_path.resolve()),
"training_release_manifest": str(paths["release_manifest"].resolve()),
"training_release_manifest_sha256": sha256(paths["release_manifest"]),
"training_asset_manifest": str(paths["asset_manifest"].resolve()),
"source_manifest_sha256": sha256(corpus_manifest),
"tiles": [
{
"split": entry["split"],
"image_path": entry["image_path"],
"label_path": entry["label_path"],
}
for entry in assets["entries"]
],
}
),
encoding="utf-8",
)
MODULE.assert_yolo_summary_bound_to_training_release(
summary_path=summary,
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
)
payload = json.loads(summary.read_text(encoding="utf-8"))
payload["tiles"] = payload["tiles"][:1]
summary.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(MODULE.TrainingReleaseError, match="complete immutable view"):
MODULE.assert_yolo_summary_bound_to_training_release(
summary_path=summary,
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
)
def test_unbound_or_changed_yaml_is_rejected_before_training(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
assert MODULE.training_release_failures(train_yaml=yaml_path) == ["training_release_manifest_missing"]
MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
yaml_path.write_text(yaml_path.read_text(encoding="utf-8") + "# tampered\n", encoding="utf-8")
assert "training_release_yaml_checksum_mismatch" in MODULE.training_release_failures(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
)
def test_changed_label_asset_is_rejected_even_when_yaml_bytes_are_unchanged(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
label = yaml_path.parent / "labels" / "train" / "fixture-train.txt"
label.write_text("0 0.4 0.4 0.2 0.2\n", encoding="utf-8")
failures = MODULE.training_release_failures(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
)
assert "training_release_asset_manifest_content_mismatch" in failures
def test_operational_release_requires_complete_accepted_human_review(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
incomplete_audit = tmp_path / "audit.json"
incomplete_audit.write_text(
json.dumps(
{
"status": "needs_human_review",
"manifest_immutable": True,
"spatial_leakage_status": "ok",
"review_complete": False,
}
),
encoding="utf-8",
)
try:
MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=incomplete_audit,
)
except MODULE.TrainingReleaseError as exc:
assert "review_complete_not_true" in str(exc)
assert "accepted_human_review_evidence_missing" in str(exc)
else:
raise AssertionError("operational release accepted an incomplete human review")
def test_operational_release_rejects_tampered_accepted_review_artifact(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
artifact = tmp_path / "fixture-train-contact-sheet.png"
artifact.write_bytes(b"changed after review")
try:
MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
except MODULE.TrainingReleaseError as exc:
assert "reviewed_artifact_checksum_mismatch" in str(exc)
else:
raise AssertionError("tampered human-review artifact was accepted")
def test_fixture_relaxation_requires_explicit_fixture_corpus_and_is_not_operational(tmp_path: Path) -> None:
fixture_manifest = write_corpus_manifest(tmp_path, fixture_mode=True)
yaml_path = write_yolo_dataset(tmp_path)
MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=fixture_manifest,
fixture_mode=True,
)
assert MODULE.training_release_failures(
train_yaml=yaml_path,
corpus_manifest=fixture_manifest,
fixture_mode=True,
) == []
assert "training_release_fixture_mode_mismatch" in MODULE.training_release_failures(
train_yaml=yaml_path,
corpus_manifest=fixture_manifest,
fixture_mode=False,
)
def test_release_contracts_a_reviewed_empty_label_as_explicit_pure_background(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path, empty_train_label=True)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
paths = MODULE.create_training_release_manifest(
train_yaml=yaml_path,
corpus_manifest=corpus_manifest,
review_audit_path=review_audit,
)
labels = json.loads(paths["label_contract_manifest"].read_text(encoding="utf-8"))
assert labels["counts"]["pure_background"] == 1
assert MODULE.training_release_failures(train_yaml=yaml_path, corpus_manifest=corpus_manifest) == []
def test_empty_label_without_accepted_sample_review_cannot_become_a_background_negative(tmp_path: Path) -> None:
corpus_manifest = write_corpus_manifest(tmp_path)
yaml_path = write_yolo_dataset(tmp_path, empty_train_label=True)
review_audit = write_accepted_review_audit(tmp_path, corpus_manifest)
audit_payload = json.loads(review_audit.read_text(encoding="utf-8"))
evidence = audit_payload["human_review_evidence"]
evidence["accepted_sample_slugs"] = ["fixture-val"]
review_audit.write_text(json.dumps(audit_payload), encoding="utf-8")
review = {
"status": "accepted",
"fixture_only": False,
"review_complete": True,
"evidence": evidence,
}
try:
MODULE.build_training_label_contract_manifest(
corpus_manifest_path=corpus_manifest,
asset_manifest=MODULE.build_yolo_asset_manifest(yaml_path),
review=review,
fixture_mode=False,
)
except MODULE.TrainingReleaseError as exc:
assert "Pure-background label sample was not accepted by review" in str(exc)
else:
raise AssertionError("unreviewed empty label was accepted as a background negative")