feat(provenance): govern source snapshots and data inputs
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -13,6 +14,94 @@ assert SPEC and SPEC.loader
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
from training_release_manifest import create_training_release_manifest # noqa: E402
|
||||
|
||||
|
||||
def write_fixture_manifest(path: Path) -> None:
|
||||
policy = "geointel-training-source-eligibility/v1"
|
||||
def eligible(sample_slug: str) -> dict[str, object]:
|
||||
return {
|
||||
"policy_version": policy,
|
||||
"eligible": True,
|
||||
"fixture_mode": True,
|
||||
"raster": {
|
||||
"eligible": True,
|
||||
"reasons": [],
|
||||
"evidence": {
|
||||
"dataset_id": f"raster:{sample_slug}",
|
||||
"checksum_sha256": "a" * 64,
|
||||
"source_registry_id": "fixture-raster",
|
||||
"source_snapshot_id": "fixture-raster-snapshot",
|
||||
},
|
||||
},
|
||||
"reference": {
|
||||
"eligible": True,
|
||||
"reasons": [],
|
||||
"evidence": {
|
||||
"dataset_id": f"reference:{sample_slug}",
|
||||
"checksum_sha256": "b" * 64,
|
||||
"source_registry_id": "fixture-reference",
|
||||
"source_snapshot_id": "fixture-reference-snapshot",
|
||||
},
|
||||
},
|
||||
}
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"training_eligibility": {
|
||||
"policy_version": policy,
|
||||
"status": "eligible",
|
||||
"fixture_mode": True,
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"sample_slug": sample_slug,
|
||||
"split": split,
|
||||
"raster_dataset_id": f"raster:{sample_slug}",
|
||||
"reference_dataset_id": f"reference:{sample_slug}",
|
||||
"training_eligibility": eligible(sample_slug),
|
||||
}
|
||||
for sample_slug, split in (("fixture-train", "train"), ("fixture-val", "val"))
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(path.parent / "corpus-freeze.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": 2,
|
||||
"manifest_sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
||||
"immutable": True,
|
||||
"training_eligibility_policy": policy,
|
||||
"fixture_mode": True,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def write_fixture_training_release(tmp_path: Path, manifest: Path) -> Path:
|
||||
dataset_dir = tmp_path / "fixture-dataset"
|
||||
for split, sample_slug in (("train", "fixture-train"), ("val", "fixture-val")):
|
||||
image = dataset_dir / "images" / split / f"{sample_slug}.png"
|
||||
label = dataset_dir / "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(split.encode("utf-8"))
|
||||
label.write_text("0 0.5 0.5 0.2 0.2\n", encoding="utf-8")
|
||||
yaml_path = dataset_dir / "dataset.yaml"
|
||||
yaml_path.write_text(
|
||||
f"path: {dataset_dir}\ntrain: images/train\nval: images/val\nnames:\n 0: building\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
create_training_release_manifest(
|
||||
train_yaml=yaml_path,
|
||||
corpus_manifest=manifest,
|
||||
fixture_mode=True,
|
||||
)
|
||||
return yaml_path
|
||||
|
||||
|
||||
def test_training_command_is_cuda_deterministic_and_bound_to_frozen_inputs(tmp_path: Path) -> None:
|
||||
command = MODULE.training_command(
|
||||
@@ -78,6 +167,7 @@ def test_failed_iteration_builds_train_only_sampling_for_next_checkpoint(tmp_pat
|
||||
corpus_manifest=tmp_path / "manifest.json",
|
||||
assessment=tmp_path / "assessment.json",
|
||||
output_dir=tmp_path / "iteration-001" / "failure-driven-training",
|
||||
review_audit=tmp_path / "review-audit.json",
|
||||
)
|
||||
assert command[1].endswith("build_failure_driven_yolo_sampling.py")
|
||||
assert command[command.index("--summary") + 1].endswith("train-summary.json")
|
||||
@@ -103,20 +193,23 @@ def test_dry_run_can_gate_existing_checkpoint_without_training(tmp_path: Path) -
|
||||
"status": "ok", "low_variance_positive_tile_count": 0,
|
||||
"label_stats": {"invalid_label_count": 0, "missing_label_file_count": 0},
|
||||
}))
|
||||
manifest = tmp_path / "manifest.json"
|
||||
write_fixture_manifest(manifest)
|
||||
train_yaml = write_fixture_training_release(tmp_path, manifest)
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable, str(SCRIPT),
|
||||
"--initial-model", str(tmp_path / "candidate.pt"),
|
||||
"--train-yaml", str(tmp_path / "dataset.yaml"),
|
||||
"--train-yaml", str(train_yaml),
|
||||
"--train-summary", str(tmp_path / "train-summary.json"),
|
||||
"--dataset-audit", str(audit),
|
||||
"--train-quality-audit", str(quality),
|
||||
"--calibration-summary", str(tmp_path / "cal.json"),
|
||||
"--test-summary", str(tmp_path / "test.json"),
|
||||
"--background-summary", str(tmp_path / "background.json"),
|
||||
"--corpus-manifest", str(tmp_path / "manifest.json"),
|
||||
"--corpus-manifest", str(manifest),
|
||||
"--output-dir", str(tmp_path / "output"),
|
||||
"--evaluate-initial-model", "--dry-run",
|
||||
"--evaluate-initial-model", "--fixture-mode", "--dry-run",
|
||||
], capture_output=True, text=True, check=False,
|
||||
)
|
||||
assert result.returncode == 0
|
||||
@@ -139,6 +232,9 @@ def test_loop_refuses_failed_dataset_audit(tmp_path: Path) -> None:
|
||||
"status": "ok", "low_variance_positive_tile_count": 0,
|
||||
"label_stats": {"invalid_label_count": 0, "missing_label_file_count": 0},
|
||||
}))
|
||||
manifest = tmp_path / "manifest.json"
|
||||
write_fixture_manifest(manifest)
|
||||
train_yaml = write_fixture_training_release(tmp_path, manifest)
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
@@ -146,7 +242,7 @@ def test_loop_refuses_failed_dataset_audit(tmp_path: Path) -> None:
|
||||
"--initial-model",
|
||||
str(tmp_path / "base.pt"),
|
||||
"--train-yaml",
|
||||
str(tmp_path / "dataset.yaml"),
|
||||
str(train_yaml),
|
||||
"--train-summary",
|
||||
str(tmp_path / "train-summary.json"),
|
||||
"--dataset-audit",
|
||||
@@ -160,9 +256,10 @@ def test_loop_refuses_failed_dataset_audit(tmp_path: Path) -> None:
|
||||
"--background-summary",
|
||||
str(tmp_path / "background.json"),
|
||||
"--corpus-manifest",
|
||||
str(tmp_path / "manifest.json"),
|
||||
str(manifest),
|
||||
"--output-dir",
|
||||
str(tmp_path / "output"),
|
||||
"--fixture-mode",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -172,7 +269,45 @@ def test_loop_refuses_failed_dataset_audit(tmp_path: Path) -> None:
|
||||
assert "Dataset audit is not eligible for training" in result.stderr
|
||||
|
||||
|
||||
def test_pending_human_review_does_not_block_objective_training() -> None:
|
||||
def test_loop_rejects_manifest_without_source_eligibility_before_cuda_training(tmp_path: Path) -> None:
|
||||
audit = tmp_path / "audit.json"
|
||||
audit.write_text(json.dumps({
|
||||
"status": "needs_human_review", "failures": [],
|
||||
"manifest_immutable": True, "spatial_leakage_status": "ok",
|
||||
}))
|
||||
quality = tmp_path / "quality.json"
|
||||
quality.write_text(json.dumps({
|
||||
"status": "ok", "low_variance_positive_tile_count": 0,
|
||||
"label_stats": {"invalid_label_count": 0, "missing_label_file_count": 0},
|
||||
}))
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text(json.dumps({"samples": [{"sample_slug": "unproven"}]}), encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable, str(SCRIPT),
|
||||
"--initial-model", str(tmp_path / "candidate.pt"),
|
||||
"--train-yaml", str(tmp_path / "dataset.yaml"),
|
||||
"--train-summary", str(tmp_path / "train-summary.json"),
|
||||
"--dataset-audit", str(audit),
|
||||
"--train-quality-audit", str(quality),
|
||||
"--calibration-summary", str(tmp_path / "cal.json"),
|
||||
"--test-summary", str(tmp_path / "test.json"),
|
||||
"--background-summary", str(tmp_path / "background.json"),
|
||||
"--corpus-manifest", str(manifest),
|
||||
"--output-dir", str(tmp_path / "output"),
|
||||
"--evaluate-initial-model", "--dry-run",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "manifest_training_eligibility_missing" in result.stderr
|
||||
|
||||
|
||||
def test_pending_human_review_blocks_operational_training() -> None:
|
||||
audit = {
|
||||
"status": "needs_human_review",
|
||||
"failures": [],
|
||||
@@ -185,7 +320,58 @@ def test_pending_human_review_does_not_block_objective_training() -> None:
|
||||
"status": "ok", "low_variance_positive_tile_count": 0,
|
||||
"label_stats": {"invalid_label_count": 0, "missing_label_file_count": 0},
|
||||
}
|
||||
assert MODULE.dataset_audit_failures(audit, quality) == []
|
||||
failures = MODULE.dataset_audit_failures(audit, quality)
|
||||
assert "unsupported audit status: needs_human_review" in failures
|
||||
assert "review_complete_not_true" in failures
|
||||
assert "accepted_human_review_evidence_missing" in failures
|
||||
|
||||
|
||||
def test_fixture_mode_can_relax_review_only_after_fixture_manifest_gate() -> None:
|
||||
audit = {
|
||||
"status": "needs_human_review",
|
||||
"failures": [],
|
||||
"manifest_immutable": True,
|
||||
"spatial_leakage_status": "ok",
|
||||
"review_complete": False,
|
||||
}
|
||||
quality = {
|
||||
"status": "ok", "low_variance_positive_tile_count": 0,
|
||||
"label_stats": {"invalid_label_count": 0, "missing_label_file_count": 0},
|
||||
}
|
||||
assert MODULE.dataset_audit_failures(audit, quality, fixture_mode=True) == []
|
||||
|
||||
|
||||
def test_operational_dataset_audit_must_be_the_one_bound_into_the_release(tmp_path: Path) -> None:
|
||||
bound = tmp_path / "bound-audit.json"
|
||||
other = tmp_path / "other-audit.json"
|
||||
bound.write_text("{}", encoding="utf-8")
|
||||
other.write_text("{}", encoding="utf-8")
|
||||
release = {"human_review": {"audit_path": str(bound.resolve())}}
|
||||
|
||||
MODULE.assert_dataset_audit_bound_to_release(
|
||||
release=release,
|
||||
dataset_audit=bound,
|
||||
fixture_mode=False,
|
||||
)
|
||||
try:
|
||||
MODULE.assert_dataset_audit_bound_to_release(
|
||||
release=release,
|
||||
dataset_audit=other,
|
||||
fixture_mode=False,
|
||||
)
|
||||
except MODULE.TrainingReleaseError as exc:
|
||||
assert "does not match" in str(exc)
|
||||
else:
|
||||
raise AssertionError("unbound dataset audit was accepted")
|
||||
|
||||
|
||||
def test_protected_assessment_feedback_is_terminal_and_cannot_seed_another_yaml() -> None:
|
||||
assert MODULE.protected_feedback_roles(
|
||||
{"status": "continue_training_loop", "test": {"aggregate": {}}, "background": None}
|
||||
) == ["test"]
|
||||
assert MODULE.protected_feedback_roles(
|
||||
{"status": "continue_training_loop", "test": None, "background": {"aggregate": {}}}
|
||||
) == ["background"]
|
||||
|
||||
|
||||
def test_training_audit_still_fails_closed_on_automated_integrity_gates() -> None:
|
||||
|
||||
Reference in New Issue
Block a user