Files
linuxcncWebWASM/scripts/validate_baseline.py

536 lines
31 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate the M0 baseline and fail closed on unassigned coverage entries."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
REQUIRED_MANIFEST_FIELDS = {
"id", "source", "source_sha", "domain", "kind", "dependencies", "inputs",
"outputs", "errors", "sim_status", "web_status", "test_id", "oracle", "golden",
"difference", "coverage_dimensions", "context_id", "exclusion_reason",
}
ALLOWED_STATUS = {"planned", "in_progress", "implemented", "verified", "accepted", "blocked", "REAL-EXCLUDED"}
def read_jsonl(path: Path) -> list[dict[str, object]]:
rows: list[dict[str, object]] = []
with path.open("r", encoding="utf-8") as handle:
for line_number, line in enumerate(handle, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number}: expected object")
rows.append(value)
return rows
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def validate(repo_root: Path, lock_path: Path, source_path: Path, manifest_path: Path) -> list[str]:
errors: list[str] = []
lock = json.loads(lock_path.read_text(encoding="utf-8"))
for field in ("context_id", "linuxcnc_version", "linuxcnc_source_sha", "emscripten_version", "schema_versions"):
if not lock.get(field):
errors.append(f"context lock missing {field}")
sources = read_jsonl(source_path)
manifests = read_jsonl(manifest_path)
if lock.get("source_index_sha256") != sha256(source_path):
errors.append("source index hash does not match context lock")
if lock.get("manifest_seed_sha256") != sha256(manifest_path):
errors.append("manifest seed hash does not match context lock")
document_index_path = repo_root / "coverage/document-capabilities.jsonl"
document_index_hash = lock.get("document_capabilities_sha256")
if document_index_hash:
if not document_index_path.is_file():
errors.append("document capabilities index is missing")
elif document_index_hash != sha256(document_index_path):
errors.append("document capabilities index hash does not match context lock")
version_path = repo_root / "linuxcnc/VERSION"
if lock.get("linuxcnc_version") != version_path.read_text(encoding="utf-8").strip():
errors.append("linuxcnc version does not match context lock")
try:
source_sha = subprocess.check_output(
["git", "-C", str(repo_root / "linuxcnc"), "rev-parse", "HEAD"],
text=True,
).strip()
except (OSError, subprocess.CalledProcessError):
source_sha = None
if source_sha and lock.get("linuxcnc_source_sha") != source_sha:
errors.append("linuxcnc source commit does not match context lock")
inventory_count = lock.get("inventory_counts", {})
if isinstance(inventory_count, dict):
if inventory_count.get("total") != len(sources):
errors.append("source index count does not match context lock")
if inventory_count.get("source_index") != len(sources):
errors.append("source_index count does not match context lock")
if inventory_count.get("manifest_seed") != len(manifests):
errors.append("manifest_seed count does not match context lock")
if document_index_hash and inventory_count.get("document_capabilities") is not None:
if inventory_count.get("document_capabilities") != sum(
1 for line in document_index_path.read_text(encoding="utf-8").splitlines() if line.strip()
):
errors.append("document_capabilities count does not match context lock")
symbols_path = repo_root / "coverage/source-symbols.jsonl"
symbols_hash = lock.get("source_symbols_sha256")
if symbols_hash:
if not symbols_path.is_file():
errors.append("source symbols index is missing")
elif symbols_hash != sha256(symbols_path):
errors.append("source symbols index hash does not match context lock")
if isinstance(inventory_count, dict) and symbols_hash and inventory_count.get("source_symbols") is not None:
if inventory_count.get("source_symbols") != sum(1 for line in symbols_path.read_text(encoding="utf-8").splitlines() if line.strip()):
errors.append("source_symbols count does not match context lock")
resources_path = repo_root / "coverage/config-resources.jsonl"
resources_hash = lock.get("config_resources_sha256")
if resources_hash:
if not resources_path.is_file():
errors.append("config resources index is missing")
elif resources_hash != sha256(resources_path):
errors.append("config resources index hash does not match context lock")
if isinstance(inventory_count, dict) and resources_hash and inventory_count.get("config_resources") is not None:
if inventory_count.get("config_resources") != sum(1 for line in resources_path.read_text(encoding="utf-8").splitlines() if line.strip()):
errors.append("config_resources count does not match context lock")
test_entries_path = repo_root / "coverage/test-entries.jsonl"
test_entries_hash = lock.get("test_entries_sha256")
if test_entries_hash:
if not test_entries_path.is_file():
errors.append("test entries index is missing")
elif test_entries_hash != sha256(test_entries_path):
errors.append("test entries index hash does not match context lock")
if isinstance(inventory_count, dict) and test_entries_hash and inventory_count.get("test_entries") is not None:
if inventory_count.get("test_entries") != sum(1 for line in test_entries_path.read_text(encoding="utf-8").splitlines() if line.strip()):
errors.append("test_entries count does not match context lock")
normalized_path = repo_root / "coverage/normalized-sources.jsonl"
normalized_hash = lock.get("normalized_sources_sha256")
if normalized_hash:
if not normalized_path.is_file():
errors.append("normalized sources index is missing")
elif normalized_hash != sha256(normalized_path):
errors.append("normalized sources index hash does not match context lock")
if isinstance(inventory_count, dict) and normalized_hash and inventory_count.get("normalized_sources") is not None:
if inventory_count.get("normalized_sources") != sum(1 for line in normalized_path.read_text(encoding="utf-8").splitlines() if line.strip()):
errors.append("normalized_sources count does not match context lock")
closure_path = repo_root / "coverage/dependency-closure.jsonl"
closure_hash = lock.get("dependency_closure_sha256")
if closure_hash:
if not closure_path.is_file():
errors.append("dependency closure index is missing")
elif closure_hash != sha256(closure_path):
errors.append("dependency closure index hash does not match context lock")
if isinstance(inventory_count, dict) and closure_hash and inventory_count.get("dependency_edges") is not None:
if inventory_count.get("dependency_edges") != sum(1 for line in closure_path.read_text(encoding="utf-8").splitlines() if line.strip()):
errors.append("dependency_edges count does not match context lock")
golden_schema_path = repo_root / "coverage/golden-schema.json"
golden_schema_hash = lock.get("golden_schema_sha256")
if golden_schema_hash:
if not golden_schema_path.is_file():
errors.append("golden schema is missing")
elif golden_schema_hash != sha256(golden_schema_path):
errors.append("golden schema hash does not match context lock")
runner_report_path = repo_root / "coverage/native-runner.report.json"
runner_report_hash = lock.get("native_runner_report_sha256")
if runner_report_hash:
if not runner_report_path.is_file():
errors.append("native runner report is missing")
elif runner_report_hash != sha256(runner_report_path):
errors.append("native runner report hash does not match context lock")
recovery_report_path = repo_root / "coverage/workspace-recovery.report.json"
recovery_report_hash = lock.get("workspace_recovery_report_sha256")
if recovery_report_hash:
if not recovery_report_path.is_file():
errors.append("workspace recovery report is missing")
elif recovery_report_hash != sha256(recovery_report_path):
errors.append("workspace recovery report hash does not match context lock")
runtime_report_path = repo_root / "coverage/runtime-mapping.report.json"
runtime_report_hash = lock.get("runtime_mapping_report_sha256")
if runtime_report_hash:
if not runtime_report_path.is_file():
errors.append("runtime mapping report is missing")
elif runtime_report_hash != sha256(runtime_report_path):
errors.append("runtime mapping report hash does not match context lock")
python_manifest_path = repo_root / "python-runtime/manifest.json"
python_manifest_hash = lock.get("python_runtime_manifest_sha256")
if python_manifest_hash:
if not python_manifest_path.is_file():
errors.append("python runtime manifest is missing")
elif python_manifest_hash != sha256(python_manifest_path):
errors.append("python runtime manifest hash does not match context lock")
python_report_path = repo_root / "coverage/python-runtime.report.json"
python_report_hash = lock.get("python_runtime_report_sha256")
if python_report_hash:
if not python_report_path.is_file():
errors.append("python runtime report is missing")
elif python_report_hash != sha256(python_report_path):
errors.append("python runtime report hash does not match context lock")
hal_report_path = repo_root / "coverage/hal-runtime.report.json"
hal_report_hash = lock.get("hal_runtime_report_sha256")
if hal_report_hash:
if not hal_report_path.is_file():
errors.append("hal runtime report is missing")
elif hal_report_hash != sha256(hal_report_path):
errors.append("hal runtime report hash does not match context lock")
posemath_report_path = repo_root / "coverage/posemath-runtime.report.json"
posemath_report_hash = lock.get("posemath_runtime_report_sha256")
if posemath_report_hash:
if not posemath_report_path.is_file():
errors.append("posemath runtime report is missing")
elif posemath_report_hash != sha256(posemath_report_path):
errors.append("posemath runtime report hash does not match context lock")
interpreter_report_path = repo_root / "coverage/interpreter-runtime.report.json"
interpreter_report_hash = lock.get("interpreter_runtime_report_sha256")
if interpreter_report_hash:
if not interpreter_report_path.is_file():
errors.append("interpreter runtime report is missing")
elif interpreter_report_hash != sha256(interpreter_report_path):
errors.append("interpreter runtime report hash does not match context lock")
motion_report_path = repo_root / "coverage/motion-runtime.report.json"
motion_report_hash = lock.get("motion_runtime_report_sha256")
if motion_report_hash:
if not motion_report_path.is_file():
errors.append("motion runtime report is missing")
elif motion_report_hash != sha256(motion_report_path):
errors.append("motion runtime report hash does not match context lock")
kinematics_runtime_report_path = repo_root / "coverage/kinematics-runtime.report.json"
kinematics_runtime_report_hash = lock.get("kinematics_runtime_report_sha256")
if kinematics_runtime_report_hash:
if not kinematics_runtime_report_path.is_file():
errors.append("kinematics runtime report is missing")
elif kinematics_runtime_report_hash != sha256(kinematics_runtime_report_path):
errors.append("kinematics runtime report hash does not match context lock")
kinematics_closure_path = repo_root / "coverage/kinematics-source-closure.report.json"
kinematics_closure_hash = lock.get("kinematics_source_closure_report_sha256")
if kinematics_closure_hash:
if not kinematics_closure_path.is_file():
errors.append("kinematics source closure report is missing")
elif kinematics_closure_hash != sha256(kinematics_closure_path):
errors.append("kinematics source closure report hash does not match context lock")
if kinematics_closure_path.is_file() and isinstance(inventory_count, dict):
try:
kinematics_report = json.loads(kinematics_closure_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics source closure report is not valid JSON")
else:
expected_counts = {
"kinematics_source_files": kinematics_report.get("source", {}).get("implementation_file_count"),
"kinematics_config_entries": kinematics_report.get("configuration", {}).get("entry_count"),
"kinematics_hal_entries": kinematics_report.get("hal", {}).get("entry_count"),
"kinematics_test_files": kinematics_report.get("tests", {}).get("file_count"),
}
for field, value in expected_counts.items():
if value is not None and inventory_count.get(field) != value:
errors.append(f"{field} count does not match kinematics closure report")
kinematics_graph_path = repo_root / "coverage/kinematics-config-graph.report.json"
kinematics_graph_hash = lock.get("kinematics_config_graph_report_sha256")
if kinematics_graph_hash:
if not kinematics_graph_path.is_file():
errors.append("kinematics config graph report is missing")
elif kinematics_graph_hash != sha256(kinematics_graph_path):
errors.append("kinematics config graph report hash does not match context lock")
samples_path = repo_root / "coverage/kinematics-oracle-samples.jsonl"
samples_hash = lock.get("kinematics_oracle_samples_sha256")
if samples_hash:
if not samples_path.is_file():
errors.append("kinematics oracle samples are missing")
elif samples_hash != sha256(samples_path):
errors.append("kinematics oracle samples hash does not match context lock")
oracle_report_path = repo_root / "coverage/kinematics-oracle.report.json"
oracle_report_hash = lock.get("kinematics_oracle_report_sha256")
if oracle_report_hash:
if not oracle_report_path.is_file():
errors.append("kinematics oracle report is missing")
elif oracle_report_hash != sha256(oracle_report_path):
errors.append("kinematics oracle report hash does not match context lock")
oracle_schema_path = repo_root / "coverage/kinematics-golden-schema.json"
oracle_schema_hash = lock.get("kinematics_golden_schema_sha256")
if oracle_schema_hash:
if not oracle_schema_path.is_file():
errors.append("kinematics golden schema is missing")
elif oracle_schema_hash != sha256(oracle_schema_path):
errors.append("kinematics golden schema hash does not match context lock")
if kinematics_graph_path.is_file() and isinstance(inventory_count, dict):
try:
graph = json.loads(kinematics_graph_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics config graph report is not valid JSON")
else:
graph_counts = graph.get("counts", {})
for field, report_field in (("kinematics_graph_entries", "entry_nodes"), ("kinematics_graph_edges", "edges"), ("kinematics_oracle_samples", "samples")):
value = graph_counts.get(report_field)
if value is not None and inventory_count.get(field) != value:
errors.append(f"{field} count does not match kinematics config graph report")
if oracle_report_path.is_file() and isinstance(inventory_count, dict):
try:
oracle_report = json.loads(oracle_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics oracle report is not valid JSON")
else:
if oracle_report.get("sample_count") is not None and inventory_count.get("kinematics_oracle_samples") != oracle_report.get("sample_count"):
errors.append("kinematics oracle sample count does not match oracle report")
abi_contract_path = repo_root / "coverage/kinematics-abi-contract.json"
abi_contract_hash = lock.get("kinematics_abi_contract_sha256")
if abi_contract_hash:
if not abi_contract_path.is_file():
errors.append("kinematics ABI contract is missing")
elif abi_contract_hash != sha256(abi_contract_path):
errors.append("kinematics ABI contract hash does not match context lock")
if abi_contract_path.is_file():
try:
abi_contract = json.loads(abi_contract_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics ABI contract is not valid JSON")
else:
if abi_contract.get("status") != "verified":
errors.append("kinematics ABI contract is not verified")
mapping_contract_path = repo_root / "coverage/kinematics-mapping-contract.json"
mapping_contract_hash = lock.get("kinematics_mapping_contract_sha256")
if mapping_contract_hash:
if not mapping_contract_path.is_file():
errors.append("kinematics mapping contract is missing")
elif mapping_contract_hash != sha256(mapping_contract_path):
errors.append("kinematics mapping contract hash does not match context lock")
if mapping_contract_path.is_file():
try:
mapping_contract = json.loads(mapping_contract_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics mapping contract is not valid JSON")
else:
if mapping_contract.get("status") != "verified":
errors.append("kinematics mapping contract is not verified")
numeric_policy_path = repo_root / "coverage/kinematics-numeric-policy.json"
numeric_policy_hash = lock.get("kinematics_numeric_policy_sha256")
if numeric_policy_hash:
if not numeric_policy_path.is_file():
errors.append("kinematics numeric policy is missing")
elif numeric_policy_hash != sha256(numeric_policy_path):
errors.append("kinematics numeric policy hash does not match context lock")
if numeric_policy_path.is_file():
try:
numeric_policy = json.loads(numeric_policy_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("kinematics numeric policy is not valid JSON")
else:
if numeric_policy.get("status") != "verified":
errors.append("kinematics numeric policy is not verified")
trivkins_report_path = repo_root / "coverage/trivkins-integration.report.json"
trivkins_report_hash = lock.get("trivkins_integration_report_sha256")
if trivkins_report_hash:
if not trivkins_report_path.is_file():
errors.append("trivkins integration report is missing")
elif trivkins_report_hash != sha256(trivkins_report_path):
errors.append("trivkins integration report hash does not match context lock")
coupled_report_path = repo_root / "coverage/coupled-kinematics.report.json"
coupled_report_hash = lock.get("coupled_kinematics_report_sha256")
if coupled_report_hash:
if not coupled_report_path.is_file():
errors.append("coupled kinematics report is missing")
elif coupled_report_hash != sha256(coupled_report_path):
errors.append("coupled kinematics report hash does not match context lock")
if coupled_report_path.is_file():
try:
coupled_report = json.loads(coupled_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("coupled kinematics report is not valid JSON")
else:
if coupled_report.get("implementation_status") != "verified":
errors.append("coupled kinematics implementation is not verified")
if coupled_report.get("closure", {}).get("official_oracle") != "blocked":
errors.append("coupled kinematics oracle status must remain explicitly blocked")
corexy_hal_report_path = repo_root / "coverage/corexy-hal.report.json"
corexy_hal_report_hash = lock.get("corexy_hal_report_sha256")
if corexy_hal_report_hash:
if not corexy_hal_report_path.is_file():
errors.append("corexy HAL report is missing")
elif corexy_hal_report_hash != sha256(corexy_hal_report_path):
errors.append("corexy HAL report hash does not match context lock")
if corexy_hal_report_path.is_file():
try:
corexy_hal_report = json.loads(corexy_hal_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("corexy HAL report is not valid JSON")
else:
if corexy_hal_report.get("implementation_status") != "verified":
errors.append("corexy HAL implementation is not verified")
if corexy_hal_report.get("oracle", {}).get("status") != "blocked":
errors.append("corexy HAL oracle status must remain explicitly blocked")
fiveaxis_report_path = repo_root / "coverage/fiveaxis-kinematics.report.json"
fiveaxis_report_hash = lock.get("fiveaxis_kinematics_report_sha256")
if fiveaxis_report_hash:
if not fiveaxis_report_path.is_file():
errors.append("fiveaxis kinematics report is missing")
elif fiveaxis_report_hash != sha256(fiveaxis_report_path):
errors.append("fiveaxis kinematics report hash does not match context lock")
if fiveaxis_report_path.is_file():
try:
fiveaxis_report = json.loads(fiveaxis_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("fiveaxis kinematics report is not valid JSON")
else:
if fiveaxis_report.get("implementation_status") != "verified":
errors.append("fiveaxis kinematics implementation is not verified")
if fiveaxis_report.get("oracle", {}).get("status") != "blocked":
errors.append("fiveaxis kinematics oracle status must remain explicitly blocked")
trt_report_path = repo_root / "coverage/trt-kinematics.report.json"
trt_report_hash = lock.get("trt_kinematics_report_sha256")
if trt_report_hash:
if not trt_report_path.is_file():
errors.append("TRT kinematics report is missing")
elif trt_report_hash != sha256(trt_report_path):
errors.append("TRT kinematics report hash does not match context lock")
if trt_report_path.is_file():
try:
trt_report = json.loads(trt_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("TRT kinematics report is not valid JSON")
else:
if trt_report.get("implementation_status") != "verified":
errors.append("TRT kinematics implementation is not verified")
if trt_report.get("oracle", {}).get("status") != "blocked":
errors.append("TRT kinematics oracle status must remain explicitly blocked")
robot_report_path = repo_root / "coverage/robot-kinematics.report.json"
robot_report_hash = lock.get("robot_kinematics_report_sha256")
if robot_report_hash:
if not robot_report_path.is_file():
errors.append("robot kinematics report is missing")
elif robot_report_hash != sha256(robot_report_path):
errors.append("robot kinematics report hash does not match context lock")
if robot_report_path.is_file():
try:
robot_report = json.loads(robot_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("robot kinematics report is not valid JSON")
else:
if robot_report.get("implementation_status") != "verified":
errors.append("robot kinematics implementation is not verified")
if robot_report.get("oracle", {}).get("status") != "blocked":
errors.append("robot kinematics oracle status must remain explicitly blocked")
genser_report_path = repo_root / "coverage/genser-kinematics.report.json"
genser_report_hash = lock.get("genser_kinematics_report_sha256")
if genser_report_hash:
if not genser_report_path.is_file():
errors.append("genser kinematics report is missing")
elif genser_report_hash != sha256(genser_report_path):
errors.append("genser kinematics report hash does not match context lock")
if genser_report_path.is_file():
try:
genser_report = json.loads(genser_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("genser kinematics report is not valid JSON")
else:
if genser_report.get("implementation_status") != "verified":
errors.append("genser kinematics implementation is not verified")
if genser_report.get("oracle", {}).get("status") != "blocked":
errors.append("genser kinematics oracle status must remain explicitly blocked")
delta_report_path = repo_root / "coverage/delta-kinematics.report.json"
delta_report_hash = lock.get("delta_kinematics_report_sha256")
if delta_report_hash:
if not delta_report_path.is_file():
errors.append("delta kinematics report is missing")
elif delta_report_hash != sha256(delta_report_path):
errors.append("delta kinematics report hash does not match context lock")
if delta_report_path.is_file():
try:
delta_report = json.loads(delta_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("delta kinematics report is not valid JSON")
else:
if delta_report.get("implementation_status") != "verified":
errors.append("delta kinematics implementation is not verified")
if delta_report.get("oracle", {}).get("status") != "blocked":
errors.append("delta kinematics oracle status must remain explicitly blocked")
tripod_report_path = repo_root / "coverage/tripod-kinematics.report.json"
tripod_report_hash = lock.get("tripod_kinematics_report_sha256")
if tripod_report_hash:
if not tripod_report_path.is_file():
errors.append("tripod kinematics report is missing")
elif tripod_report_hash != sha256(tripod_report_path):
errors.append("tripod kinematics report hash does not match context lock")
if tripod_report_path.is_file():
try:
tripod_report = json.loads(tripod_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("tripod kinematics report is not valid JSON")
else:
if tripod_report.get("implementation_status") != "verified":
errors.append("tripod kinematics implementation is not verified")
if tripod_report.get("oracle", {}).get("status") != "blocked":
errors.append("tripod kinematics oracle status must remain explicitly blocked")
genhex_report_path = repo_root / "coverage/genhex-kinematics.report.json"
genhex_report_hash = lock.get("genhex_kinematics_report_sha256")
if genhex_report_hash:
if not genhex_report_path.is_file():
errors.append("genhex kinematics report is missing")
elif genhex_report_hash != sha256(genhex_report_path):
errors.append("genhex kinematics report hash does not match context lock")
if genhex_report_path.is_file():
try:
genhex_report = json.loads(genhex_report_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append("genhex kinematics report is not valid JSON")
else:
if genhex_report.get("implementation_status") != "verified":
errors.append("genhex kinematics implementation is not verified")
if genhex_report.get("oracle", {}).get("status") != "blocked":
errors.append("genhex kinematics oracle status must remain explicitly blocked")
source_ids = [str(row.get("source_id", "")) for row in sources]
if len(source_ids) != len(set(source_ids)):
errors.append("source index contains duplicate source_id")
manifest_ids = [str(row.get("id", "")) for row in manifests]
if len(manifest_ids) != len(set(manifest_ids)):
errors.append("manifest contains duplicate id")
source_sha_by_id = {str(row["source_id"]): str(row["sha256"]) for row in sources}
manifest_sources = {str(row.get("source", "")) for row in manifests}
missing = sorted(set(source_ids) - manifest_sources)
if missing:
errors.append(f"manifest has {len(missing)} orphan sources; first={missing[0]}")
extra = sorted(manifest_sources - set(source_ids))
if extra:
errors.append(f"manifest has {len(extra)} unknown sources; first={extra[0]}")
for row in manifests:
missing_fields = REQUIRED_MANIFEST_FIELDS - row.keys()
if missing_fields:
errors.append(f"{row.get('id')}: missing fields {sorted(missing_fields)}")
source_id = str(row.get("source", ""))
if source_id in source_sha_by_id and row.get("source_sha") != source_sha_by_id[source_id]:
errors.append(f"{row.get('id')}: source_sha does not match source index")
for field in ("sim_status", "web_status"):
if row.get(field) not in ALLOWED_STATUS:
errors.append(f"{row.get('id')}: invalid {field}={row.get(field)!r}")
if row.get("sim_status") == "accepted" or row.get("web_status") == "accepted":
if not row.get("feature_id") or not row.get("test_id") or not row.get("oracle") or not row.get("context_id"):
errors.append(f"{row.get('id')}: accepted entry lacks feature/test/oracle/context evidence")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--lock", type=Path, default=Path("context-lock.json"))
parser.add_argument("--source-index", type=Path, default=Path("coverage/source-index.jsonl"))
parser.add_argument("--manifest", type=Path, default=Path("coverage/manifest.jsonl"))
args = parser.parse_args()
root = args.repo_root.resolve()
errors = validate(root, root / args.lock, root / args.source_index, root / args.manifest)
if errors:
for error in errors:
print(f"ERROR: {error}")
return 1
print("baseline=valid")
return 0
if __name__ == "__main__":
raise SystemExit(main())