#!/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))