78 lines
4.4 KiB
Python
78 lines
4.4 KiB
Python
#!/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()))
|