feat(provenance): govern source snapshots and data inputs

This commit is contained in:
Jens
2026-08-01 23:46:17 +02:00
parent cebeb5f3b4
commit 5b3c17b494
96 changed files with 20156 additions and 351 deletions
@@ -7,6 +7,7 @@ import argparse
import hashlib
import json
import shutil
import sys
from collections import Counter
from pathlib import Path
from typing import Any
@@ -15,6 +16,19 @@ from pyproj import Transformer
from shapely.geometry import box
from shapely.ops import transform as shapely_transform
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from training_dataset_eligibility import ( # noqa: E402
TrainingEligibilityError,
assert_frozen_manifest_training_eligible,
)
from training_release_manifest import ( # noqa: E402
TrainingReleaseError,
assert_yolo_summary_bound_to_embedded_training_release,
)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
@@ -73,6 +87,11 @@ def main() -> int:
parser.add_argument("--internal-val-samples", required=True)
parser.add_argument("--version", required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument(
"--fixture-mode",
action="store_true",
help="Accept only an explicitly fixture-only source corpus; never use for operational holdout rotation.",
)
args = parser.parse_args()
role_slugs = {
@@ -86,6 +105,20 @@ def main() -> int:
raise SystemExit("rotated holdout lists overlap")
manifest = json.loads(args.corpus_manifest.read_text(encoding="utf-8"))
try:
assert_frozen_manifest_training_eligible(
args.corpus_manifest,
fixture_mode=args.fixture_mode,
verify_live=True,
)
for path in args.source_summary:
assert_yolo_summary_bound_to_embedded_training_release(
summary_path=path,
corpus_manifest=args.corpus_manifest,
fixture_mode=args.fixture_mode,
)
except (TrainingEligibilityError, TrainingReleaseError) as exc:
raise SystemExit(str(exc)) from exc
source_samples = {item["sample_slug"]: item for item in manifest["samples"]}
missing = sorted(all_holdouts - source_samples.keys())
if missing:
@@ -126,6 +159,18 @@ def main() -> int:
args.output_dir.mkdir(parents=True, exist_ok=True)
manifest_path = args.output_dir / "operator_samples_manifest.json"
write_json(manifest_path, rotated_manifest)
write_json(
args.output_dir / "corpus-freeze.json",
{
"schema_version": 2,
"dataset_version": rotated_manifest["dataset_version"],
"manifest_sha256": sha256(manifest_path),
"sample_count": len(rotated_manifest["samples"]),
"immutable": True,
"training_eligibility_policy": rotated_manifest["training_eligibility"]["policy_version"],
"fixture_mode": bool(args.fixture_mode),
},
)
leakage = audit_spatial_leakage(rotated_manifest["samples"])
write_json(args.output_dir / "spatial-leakage-audit.json", leakage)
if leakage["status"] != "ok":
@@ -193,6 +238,11 @@ def main() -> int:
"fit_samples_in_internal_validation": sorted(
{tile["sample_slug"] for tile in train_tiles} & {tile["sample_slug"] for tile in internal_val_tiles}
),
"fixture_mode": bool(args.fixture_mode),
"training_eligible": False,
"training_eligibility_reason": (
"A rotated split needs a new human review, label contract validation and immutable training release."
),
}
if evidence["protected_samples_in_training"]:
raise SystemExit("protected samples leaked into rotated training lists")