#!/usr/bin/env python3 """Structural acceptance of gate evidence. Behavioral acceptance belongs to campaign review.""" import hashlib import json from pathlib import Path import re REQUIRED_CHECKS = frozenset(( "sourceCopy", "cleanRoom", "shimConfigs", "configure", "build", "inventory", "ctest", "junitComplete", "outputComplete", "testStatuses", "skipClassification", "corpusExecution", "corpusNonzero", "sourceUnchanged", "toolSourceUnchanged", "corpusUnchanged", "inputsUnchanged", "binaryExists")) CORPUS_TESTS = {"mars_stream_save", "mars_stream_domains", "app_turn", "app_turn_record"} def sha256(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def require(value, message): if not value: raise ValueError(message) def hash_valid(value): return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{64}", value) is not None def validate_gate(manifest): require(isinstance(manifest, dict) and manifest.get("schema") == "sots-gate/1" and manifest.get("status") == "passed", "expected a passed sots-gate/1 manifest") require(manifest.get("profile") in ("host", "full"), "invalid gate profile") binary, source, tests = (manifest.get(k) for k in ("binary", "source", "tests")) require(isinstance(binary, dict) and binary.get("path") and hash_valid(binary.get("sha256")), "passed gate lacks a bound binary") require(isinstance(source, dict) and set(source) == {"engine", "re"}, "missing source identities") for name, identity in source.items(): require(isinstance(identity, dict) and identity.get("path") and identity.get("head") and isinstance(identity.get("dirty"), bool) and identity.get("files"), f"incomplete {name} identity") paths = [] for row in identity["files"]: require(isinstance(row, dict) and row.get("path") and hash_valid(row.get("sha256")), "invalid source file") relative = Path(row["path"]) require(not relative.is_absolute() and ".." not in relative.parts, "invalid source path") paths.append(row["path"]) require(len(paths) == len(set(paths)), "duplicate source paths") require(isinstance(tests, dict), "missing tests") for key in ("expected", "passed", "skipped", "failed"): values = tests.get(key) require(isinstance(values, list) and all(isinstance(v, str) for v in values) and len(values) == len(set(values)), f"invalid tests.{key}") require(tests["expected"] and tests["passed"] and not tests["failed"] and not set(tests["passed"]) & set(tests["skipped"]) and sorted(tests["passed"] + tests["skipped"]) == sorted(tests["expected"]), "incomplete test execution") require(CORPUS_TESTS <= set(tests["passed"]), "required corpus tests did not execute") required, checks = manifest.get("requiredChecks"), manifest.get("checks") require(isinstance(required, list) and all(isinstance(v, str) for v in required) and REQUIRED_CHECKS <= set(required), "required gate checks were omitted") require(isinstance(checks, dict) and all(checks.get(name) is True for name in required), "required gate checks failed") inputs = manifest.get("inputs", {}) corpus = inputs.get("SOTS_SAVES_DIR", {}) if isinstance(inputs, dict) else {} require(isinstance(corpus, dict) and corpus.get("root") and corpus.get("files"), "missing corpus manifest") for item in inputs.values(): require(isinstance(item, dict) and item.get("root") and isinstance(item.get("files"), list), "invalid input manifest") require(all(isinstance(row, dict) and row.get("path") and hash_valid(row.get("sha256")) for row in item["files"]), "invalid input hashes") counts = tests.get("corpusSummaryCounts", {}) require(all(counts.get(name) == [len(corpus["files"])] for name in CORPUS_TESTS), "missing positive corpus counts") require(isinstance(manifest.get("limitations"), list) and manifest.get("tools"), "missing tools/limitations") if manifest["profile"] == "full": require(not tests["skipped"] and "SOTS_DATA_DIR" in inputs and checks.get("shimBuild") is True and checks.get("shimBinaryExists") is True and hash_valid(manifest.get("shimBinary", {}).get("sha256")), "incomplete full gate") return manifest def load_passed_gate(path): return validate_gate(json.loads(Path(path).read_text()))