Files
FreeCADWasmDev/tools/run_p6_python_api_full_gate.py
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

89 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Freeze a terminal, evidence-backed disposition for every Python API row."""
from __future__ import annotations
import hashlib
import json
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
P6 = ROOT / "docs/p6"
MANIFEST = P6 / "python-api-manifest.json"
def sha(value: object) -> str:
payload = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def evidence_files() -> dict[str, dict[str, object]]:
names = {
"substrate": "python-api-substrate-gate.json",
"readonly": "python-api-readonly-gate.json",
"pure": "python-api-pure-gate.json",
"restricted": "python-api-restricted-gate.json",
}
result: dict[str, dict[str, object]] = {}
for key, name in names.items():
path = P6 / name
if not path.exists():
result[key] = {"artifact": f"docs/p6/{name}", "result": "missing"}
continue
raw = path.read_bytes()
value = json.loads(raw)
result[key] = {"artifact": f"docs/p6/{name}", "result": value.get("result", "unknown"), "sha256": hashlib.sha256(raw).hexdigest()}
return result
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
source_rows = manifest.get("records", [])
expected_inventory_records = len(source_rows)
records = []
for row in source_rows:
source_disposition = str(row.get("disposition", "planned"))
if source_disposition == "planned":
disposition = "not_implemented"
detail = {
"code": "adapter_pending",
"message": "Source API is indexed, but no native Python/WASM adapter evidence is registered",
"permission": "denied_without_native_evidence",
"nextAction": "implement the module+tier+family adapter and attach passing native evidence",
}
elif source_disposition in {"exposed", "restricted", "nonportable"}:
disposition = source_disposition
detail = {
"code": "evidence_backed" if source_disposition == "exposed" else "capability_boundary",
"message": row.get("dispositionReason", "native evidence or capability policy is recorded"),
"permission": "allow" if source_disposition == "exposed" else "deny",
}
if row.get("dispositionEvidence"):
detail["evidenceHash"] = sha(row["dispositionEvidence"])
else:
disposition = "not_implemented"
detail = {"code": "invalid_source_disposition", "message": f"unsupported source disposition: {source_disposition}", "permission": "deny"}
records.append({"id": row.get("id"), "module": row.get("module"), "kind": row.get("kind"), "name": row.get("name"), "family": row.get("family"), "tier": row.get("tier"), "disposition": disposition, "detail": detail, "sourceDisposition": source_disposition, "evidence": row.get("evidence")})
counts = Counter(record["disposition"] for record in records)
keys = [record["id"] for record in records]
duplicate_ids = sorted(key for key, count in Counter(keys).items() if count > 1)
terminal = {"exposed", "restricted", "nonportable", "not_implemented"}
invalid = [record["id"] for record in records if record["disposition"] not in terminal]
exposed_without_evidence = [record["id"] for record in records if record["disposition"] == "exposed" and not record["detail"].get("evidenceHash")]
evidence = evidence_files()
report = {
"schema": "freecad-web-p6-python-api-full-gate/v1",
"taskId": "FW-P6-FPA-F",
"result": "pass_with_boundaries" if len(records) == expected_inventory_records and not duplicate_ids and not invalid and not exposed_without_evidence else "fail",
"source": {"manifest": "docs/p6/python-api-manifest.json", "manifestSha256": hashlib.sha256(MANIFEST.read_bytes()).hexdigest(), "inventoryRecords": len(source_rows)},
"coverage": {"inventoryRecords": len(source_rows), "terminalRecords": len(records), "counts": dict(sorted(counts.items())), "uniqueIds": len(set(keys)), "duplicateIds": duplicate_ids},
"permissionBoundary": {"exposedRequires": "dispositionEvidence from a passing native Python/WASM artifact", "restrictedAndNonportable": "denied before Worker/native execution", "notImplemented": "denied with adapter_pending and no fabricated result", "invalidRecords": invalid, "exposedWithoutEvidence": exposed_without_evidence},
"prerequisiteEvidence": evidence,
"records": records,
"boundaries": [f"All {expected_inventory_records} source rows receive a terminal disposition without changing the planning manifest.", "not_implemented records are honest capability denials, not successful Python execution.", "PySide/Pivy/desktop and external modules remain restricted/nonportable; future family cards may upgrade only with native evidence."],
}
(P6 / "python-api-full-gate.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
(ROOT / "docs/freecad-p6-python-api-full-f.log").write_text(json.dumps({"result": report["result"], "coverage": report["coverage"], "permissionBoundary": report["permissionBoundary"]}, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps({"result": report["result"], "coverage": report["coverage"]}, ensure_ascii=False))