#!/usr/bin/env python3 """Run the native kinematics smoke and emit strict FK/IK oracle readiness evidence.""" from __future__ import annotations import argparse import hashlib import json import os import subprocess from pathlib import Path SCHEMA = "kinematics-oracle-report.v1" GOLDEN_SCHEMA = "kinematics-golden.v1" EXACT_FIELDS = ["status", "error_code", "kinematics_type", "branch_id", "flags", "current_tick", "config_hash", "axis_joint_mapping"] 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 sha256_bytes(value: bytes) -> str: return hashlib.sha256(value).hexdigest() def fixed_env(root: Path) -> dict[str, str]: return { "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME": ".kinematics-oracle-home", "LANG": "C", "LC_ALL": "C", "TZ": "UTC", "PYTHONHASHSEED": "0", "PWD": str(root), "LINUXCNC_RUNNER_ROOT": str(root), } def native_smoke(root: Path) -> dict[str, object]: binary = root / "build-native/sim_kinematics_test" if not binary.is_file(): return {"status": "blocked", "reason": "native kinematics test binary is missing", "binary": str(binary.relative_to(root))} try: completed = subprocess.run([str(binary)], cwd=root, env=fixed_env(root), stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False) except subprocess.TimeoutExpired: return {"status": "timeout", "binary": str(binary.relative_to(root))} return { "status": "passed" if completed.returncode == 0 else "failed", "binary": str(binary.relative_to(root)), "exit_code": completed.returncode, "stdout_sha256": sha256_bytes(completed.stdout), "stderr_sha256": sha256_bytes(completed.stderr), "result_sha256": sha256_bytes(completed.stdout + b"\0" + completed.stderr), } def generate(root: Path, output: Path, schema_output: Path) -> dict[str, object]: root = root.resolve() samples_path = root / "coverage/kinematics-oracle-samples.jsonl" closure_path = root / "coverage/kinematics-source-closure.report.json" samples = [json.loads(line) for line in samples_path.read_text(encoding="utf-8").splitlines() if line.strip()] closure = json.loads(closure_path.read_text(encoding="utf-8")) missing_roots = list(closure.get("missing_required_test_roots", [])) smoke = native_smoke(root) schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "linuxcnc-web-wasm/kinematics-golden.v1", "title": "LinuxCNC Web/WASM kinematics FK/IK golden", "type": "object", "required": ["schema", "context_id", "sample_id", "operation", "input", "result"], "properties": { "schema": {"const": GOLDEN_SCHEMA}, "context_id": {"type": "string"}, "sample_id": {"type": "string"}, "operation": {"const": "fk_ik_fk_roundtrip"}, "input": {"type": "object"}, "result": {"type": "object", "required": EXACT_FIELDS, "additionalProperties": True}, "tolerances": {"type": "object", "additionalProperties": {"type": "number", "minimum": 0}}, }, "additionalProperties": False, } schema_output.parent.mkdir(parents=True, exist_ok=True) schema_output.write_text(json.dumps(schema, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") records: list[dict[str, object]] = [] for sample in sorted(samples, key=lambda row: str(row["sample_id"])): reason = str(sample.get("execution", {}).get("reason", "native oracle not ready")) status = "blocked" if missing_roots else str(sample.get("execution", {}).get("status", "blocked")) records.append({ "schema": GOLDEN_SCHEMA, "context_id": closure.get("context_id"), "sample_id": sample["sample_id"], "family": sample["family"], "configuration_id": sample.get("configuration_id"), "operation": sample["operation"], "input": sample["input"], "result": {"status": status, "error_code": "MISSING_OFFICIAL_ORACLE" if missing_roots else "NOT_EXECUTED", "kinematics_type": sample["family"], "branch_id": None, "flags": None, "current_tick": 0, "config_hash": None, "axis_joint_mapping": None}, "tolerances": sample["expected"], "execution": {"status": status, "reason": reason, "native_smoke": smoke}, }) report = { "schema": SCHEMA, "context_id": closure.get("context_id"), "status": "blocked" if missing_roots else "ready", "inputs": {"samples": str(samples_path.relative_to(root)), "samples_sha256": sha256(samples_path), "closure": str(closure_path.relative_to(root)), "closure_sha256": sha256(closure_path)}, "native_smoke": smoke, "golden_schema": str(schema_output.relative_to(root)), "golden_schema_sha256": sha256(schema_output), "sample_count": len(records), "ready_count": sum(1 for row in records if row["execution"]["status"] == "ready"), "blocked_count": sum(1 for row in records if row["execution"]["status"] == "blocked"), "records": records, "blocking_reasons": sorted(set(missing_roots)) if missing_roots else [], } report["report_fingerprint_sha256"] = hashlib.sha256(json.dumps(report, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest() output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--output", type=Path, default=Path("coverage/kinematics-oracle.report.json")) parser.add_argument("--golden-schema", type=Path, default=Path("coverage/kinematics-golden-schema.json")) args = parser.parse_args() report = generate(args.repo_root, args.repo_root / args.output, args.repo_root / args.golden_schema) print(f"kinematics_oracle={report['status']} samples={report['sample_count']} ready={report['ready_count']} blocked={report['blocked_count']} native_smoke={report['native_smoke']['status']}") return 0 if __name__ == "__main__": raise SystemExit(main())