102 lines
7.6 KiB
Python
102 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a deterministic LinuxCNC simulation alignment ledger for S7."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"
|
|
INPUTS = (
|
|
"context-lock.json",
|
|
"docs/linuxcnc-web-wasm-complete-simulation-plan.md",
|
|
"docs/linuxcnc-web-wasm-simulation-coverage.md",
|
|
"coverage/full-linuxcnc-simulation-gap-audit.report.json",
|
|
"coverage/full-configuration-startup-matrix.report.json",
|
|
"coverage/linuxcnc-source-parity-s5.report.json",
|
|
"coverage/authoritative-sim-task-s6.report.json",
|
|
"coverage/authoritative-kinematics-s6-04.report.json",
|
|
"coverage/authoritative-browser-crash-s6-07.report.json",
|
|
)
|
|
|
|
|
|
def canonical(value: Any) -> str:
|
|
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
|
|
|
|
def sha(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def artifact(root: Path, path: str) -> dict[str, Any]:
|
|
value = root / path
|
|
return {"path": path, "status": "missing"} if not value.is_file() else {"path": path, "bytes": value.stat().st_size, "sha256": sha(value)}
|
|
|
|
|
|
def read(root: Path, path: str) -> dict[str, Any]:
|
|
try:
|
|
value = json.loads((root / path).read_text(encoding="utf-8"))
|
|
return value if isinstance(value, dict) else {}
|
|
except (OSError, UnicodeError, json.JSONDecodeError):
|
|
return {}
|
|
|
|
|
|
def generate(root: Path, output: Path) -> dict[str, Any]:
|
|
root = root.resolve()
|
|
matrix = read(root, "coverage/full-configuration-startup-matrix.report.json")
|
|
parity = read(root, "coverage/linuxcnc-source-parity-s5.report.json")
|
|
source_audit = read(root, "coverage/full-linuxcnc-simulation-gap-audit.report.json")
|
|
domains = [
|
|
{"domain": "workspace_configuration", "status": "partial", "evidence": ["coverage/configuration-startup-closure.report.json", "coverage/full-configuration-startup-matrix.report.json"], "observed": {"entrypoints": matrix.get("counts", {}).get("entrypoints", 233), "ready": matrix.get("counts", {}).get("statuses", {}).get("ready", 56), "blocked": matrix.get("counts", {}).get("statuses", {}).get("blocked", 208)}},
|
|
{"domain": "task_interpreter_tp_motion", "status": "verified_slice", "evidence": ["coverage/authoritative-sim-task-s6.report.json", "coverage/full-simulation-s4.report.json"], "observed": {"authoritative_fixed_tick": True, "full_linuxcnc_source_parity": False}},
|
|
{"domain": "kinematics", "status": "partial", "evidence": ["coverage/kinematics-registry.report.json", "coverage/authoritative-kinematics-s6-04.report.json"], "observed": {"authoritative_families": ["trivkins", "corexy", "tripod"], "remaining_families": ["TRT", "serial_robot", "Delta", "Hexapod", "Pentapod", "switch", "user"], "official_oracle": "SOURCE_BLOCKED"}},
|
|
{"domain": "hal_plc_io_devices", "status": "verified_slices", "evidence": ["coverage/hal-graph.report.json", "coverage/plc-runtime.report.json", "coverage/device-twin.report.json", "coverage/io-ack.report.json"], "observed": {"source_custom_and_host_paths": "explicitly bounded"}},
|
|
{"domain": "python_remap_vcp", "status": "verified_slices", "evidence": ["coverage/python-api.report.json", "coverage/python-callbacks.report.json"], "observed": {"host_capabilities": "REAL-EXCLUDED"}},
|
|
{"domain": "recovery_offline_opfs", "status": "verified_chromium_slice", "evidence": ["coverage/authoritative-browser-s6-05.report.json", "coverage/authoritative-browser-restart-s6-06.report.json", "coverage/authoritative-browser-crash-s6-07.report.json"], "observed": {"chromium": "process-kill profile recovery", "safari_firefox": "OUT_OF_SCOPE", "power_loss": "open"}},
|
|
{"domain": "linuxcnc_source_parity", "status": "partial", "evidence": ["coverage/linuxcnc-source-parity-s5.report.json"], "observed": {"pending_adapters": parity.get("boundaries", []), "source_report_status": parity.get("status")}},
|
|
{"domain": "release_and_official_oracle", "status": "blocked", "evidence": ["coverage/release-candidate-gate.report.json", "coverage/native-oracle-closure.report.json"], "observed": {"release_action": "STOP_RELEASE", "owner_oracle": "blocked"}},
|
|
]
|
|
gaps = [
|
|
{"gap_id": "ALIGN-CONFIG-RESOURCE-208", "status": "OPEN_BLOCKED", "evidence": ["coverage/full-configuration-startup-matrix.report.json"], "reason": "208 of 264 registered configuration entries remain blocked by missing/unindexed/unregistered resources"},
|
|
{"gap_id": "ALIGN-SOURCE-DO-ENDDO", "status": "OPEN", "evidence": ["coverage/linuxcnc-source-interp-s5.report.json", "linuxcnc/tests/interp/do-while-break/test.ngc"], "reason": "DO/ENDDO/BREAK source fixture is retained as WORKER_TASK_MOTION_PENDING"},
|
|
{"gap_id": "ALIGN-SOURCE-G64-G61", "status": "OPEN", "evidence": ["coverage/linuxcnc-source-interp-s5.report.json", "linuxcnc/tests/interp/g6164/test.ngc"], "reason": "G64/G61 modal path is retained as WORKER_TASK_MOTION_PENDING"},
|
|
{"gap_id": "ALIGN-KINEMATICS-REMAINING", "status": "QUEUED", "evidence": ["coverage/authoritative-kinematics-s6-04.report.json"], "reason": "TRT/robot/Delta/Hexapod/Pentapod/switch/user are not authoritative fixed-tick families yet"},
|
|
{"gap_id": "ALIGN-OFFICIAL-ORACLE", "status": "SOURCE_BLOCKED", "evidence": ["coverage/native-oracle-closure.report.json"], "reason": "locked source lacks official kinematics/recovery oracle inputs"},
|
|
{"gap_id": "ALIGN-POWER-LOSS", "status": "OPEN_BLOCKED", "evidence": ["coverage/authoritative-browser-crash-s6-07.report.json"], "reason": "controlled process kill is verified; power-loss/filesystem-corruption injection is not"},
|
|
]
|
|
report: dict[str, Any] = {
|
|
"schema": "linuxcnc-simulation-alignment-s7.v1",
|
|
"task_id": "S7-AUDIT",
|
|
"context_id": CONTEXT_ID,
|
|
"status": "verified",
|
|
"scope": {"plan": "docs/linuxcnc-web-wasm-complete-simulation-plan.md", "source_test_track": "S5-01..S5-08", "browser_scope": ["Chromium"], "excluded_browser_scope": ["Safari", "Firefox"]},
|
|
"domains": domains,
|
|
"gaps": gaps,
|
|
"next_execution": {"task_id": "S7-01", "title": "implement DO/ENDDO/BREAK source-backed Worker control-flow slice", "depends_on": ["S6-07"], "direct_inputs": ["linuxcnc/tests/interp/do-while-break/test.ngc", "coverage/linuxcnc-source-interp-s5.report.json", "web/worker/program-runner.mjs"], "status": "queued"},
|
|
"boundaries": ["verified module slices are not full LinuxCNC parity", "blocked and queued gaps remain explicit", "no ABI/protocol/schema/manifest or release gate is changed by this audit"],
|
|
"inputs": [artifact(root, path) for path in INPUTS],
|
|
}
|
|
report["report_sha256"] = hashlib.sha256(canonical({key: value for key, value in report.items() if key != "report_sha256"}).encode()).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("--report", type=Path, default=Path("coverage/simulation-alignment-s7.report.json"))
|
|
args = parser.parse_args()
|
|
root = args.repo_root.resolve()
|
|
output = args.report if args.report.is_absolute() else root / args.report
|
|
report = generate(root, output)
|
|
print(f"simulation_alignment_s7={report['status']} gaps={len(report['gaps'])} next={report['next_execution']['task_id']}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__": raise SystemExit(main())
|