Files
FreeCADWasmDev/tools/implementation_first.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

209 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""Shared state for the implementation-first, deferred-validation scheduler mode."""
from __future__ import annotations
import json
import hashlib
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
CHECKPOINT_PATH = ROOT / "docs/current/continuation-state.json"
REGISTRY_PATH = ROOT / "docs/current/implementation-first-registry.json"
RUNTIME_ISSUE_PATH = ROOT / "docs/current/runtime-issue-ledger.json"
CAPABILITY_PATH = ROOT / "docs/current/feature-capability-registry.json"
EXECUTION_POLICY_PATH = ROOT / "docs/current/execution-policy.json"
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {}
return value if isinstance(value, dict) else {}
def implementation_first_enabled() -> bool:
"""Return the strategy without making environment-only state authoritative."""
if user_validation_deferred():
return True
override = os.environ.get("FREECAD_IMPLEMENTATION_FIRST")
if override is not None:
return override.strip().lower() in {"1", "true", "yes", "on"}
checkpoint = _read_json(CHECKPOINT_PATH)
strategy = checkpoint.get("implementationStrategy")
return isinstance(strategy, dict) and str(strategy.get("mode", "")) == "feature-first-deferred-validation"
def user_validation_deferred() -> bool:
policy = _read_json(EXECUTION_POLICY_PATH)
return (policy.get("mode") == "implementation-only-user-validation"
and policy.get("automaticValidation") is False)
def implementation_scope_complete() -> bool:
from business_acceptance import requirements
rows = requirements()
records = load_registry().get("records", {})
ready = {key for key, row in records.items()
if row.get("status") in {"implementation_ready", "implemented_pending_use"}}
ready.update(row["id"] for row in rows if row.get("status") == "passed")
unrouted = {row["id"] for row in rows if row.get("implementationStatus", "routed") != "routed"}
pending = _read_json(ROOT / "docs/current/module-build-pending.json").get("targets", {})
issues = _read_json(RUNTIME_ISSUE_PATH).get("issues", [])
return bool(rows and not unrouted and {row["id"] for row in rows} <= ready and not any(pending.values())
and not any(row.get("status", "open") in {"open", "confirmed", "reopened"} for row in issues))
def capability_source_fingerprint() -> str:
digest = hashlib.sha256()
for path in (CAPABILITY_PATH, RUNTIME_ISSUE_PATH, ROOT / "docs/current/scheduler-progress.json"):
digest.update(str(path.relative_to(ROOT)).encode("utf-8"))
try:
digest.update(path.read_bytes())
except OSError:
digest.update(b"<missing>")
return digest.hexdigest()
def load_registry() -> dict[str, Any]:
value = _read_json(REGISTRY_PATH)
if value.get("schema") != "freecad-web-implementation-first-registry/v1":
return {
"schema": "freecad-web-implementation-first-registry/v1",
"mode": "feature-first-deferred-validation",
"updatedAt": None,
"batches": {},
"records": {},
}
value.setdefault("batches", {})
value.setdefault("records", {})
return value
def implementation_ready_ids() -> set[str]:
"""Return records with an implementation receipt, not validation evidence."""
if not implementation_first_enabled():
return set()
records = load_registry().get("records", {})
if not isinstance(records, dict):
return set()
allowed = {"implementation_ready", "implementation_pending_build", "implemented_pending_use"}
receipt_ids = {
str(record_id)
for record_id, entry in records.items()
if isinstance(entry, dict) and str(entry.get("status", "")) in allowed
}
capabilities = _read_json(CAPABILITY_PATH)
capability_rows = capabilities.get("records", []) if isinstance(capabilities.get("records"), list) else []
currently_routed = {
str(row.get("id"))
for row in capability_rows
if isinstance(row, dict) and row.get("implementationStatus") == "routed"
}
return receipt_ids & currently_routed
def all_implementation_ready_ids() -> set[str]:
"""Return every current implementation receipt, including API/object batches."""
if not implementation_first_enabled():
return set()
records = load_registry().get("records", {})
if not isinstance(records, dict):
return set()
allowed = {"implementation_ready", "implementation_pending_build", "implemented_pending_use"}
return {
str(record_id)
for record_id, entry in records.items()
if isinstance(entry, dict) and str(entry.get("status", "")) in allowed
}
def record_implementation_batch(
batch: dict[str, Any],
*,
target: str,
build_tree: str,
artifacts: list[str],
source: str = "outer-build",
status: str = "implementation_ready",
require_routed_capability: bool = True,
) -> dict[str, Any]:
"""Persist a build/接线 receipt while leaving API dispositions untouched."""
registry = load_registry()
batch_id = str(batch.get("batchId") or "")
selected = batch.get("selected") if isinstance(batch.get("selected"), dict) else {}
rows = batch.get("records") if isinstance(batch.get("records"), list) else []
selected_ids = [str(row[0]) for row in rows if isinstance(row, list) and row and row[0]]
capabilities = _read_json(CAPABILITY_PATH)
capability_rows = capabilities.get("records", []) if isinstance(capabilities.get("records"), list) else []
ready_ids = {
str(row.get("id"))
for row in capability_rows
if isinstance(row, dict) and row.get("implementationStatus") == "routed"
}
record_ids = (
[record_id for record_id in selected_ids if record_id in ready_ids]
if require_routed_capability
else selected_ids
)
now = datetime.now(timezone.utc).isoformat()
receipt = {
"batchId": batch_id,
"module": selected.get("module"),
"tier": selected.get("tier"),
"family": selected.get("family"),
"recordIds": record_ids,
"recordCount": len(record_ids),
"selectedRecordCount": len(selected_ids),
"remainingRecordCount": len(selected_ids) - len(record_ids),
"status": status,
"validation": "user_during_use" if user_validation_deferred() else "deferred_until_runtime_issue_or_explicit_full_validation",
"target": target,
"buildTree": build_tree,
"artifacts": artifacts,
"source": source,
"scope": "feature-capability" if require_routed_capability else "business-family",
"updatedAt": now,
}
batches = registry.setdefault("batches", {})
records = registry.setdefault("records", {})
if isinstance(batches, dict) and batch_id:
batches[batch_id] = receipt
if isinstance(records, dict):
for record_id in record_ids:
records[record_id] = {
"batchId": batch_id,
"module": selected.get("module"),
"tier": selected.get("tier"),
"family": selected.get("family"),
"status": status,
"scope": "feature-capability" if require_routed_capability else "business-family",
"updatedAt": now,
}
registry["updatedAt"] = now
REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
temporary = REGISTRY_PATH.with_suffix(".tmp")
temporary.write_text(json.dumps(registry, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
temporary.replace(REGISTRY_PATH)
issue_ledger = _read_json(RUNTIME_ISSUE_PATH)
issues = issue_ledger.get("issues", []) if isinstance(issue_ledger.get("issues"), list) else []
changed = False
for issue in issues:
if not isinstance(issue, dict) or str(issue.get("capabilityId") or "") not in record_ids:
continue
if status == "implementation_ready" and str(issue.get("status", "open")) not in {"resolved", "closed", "fix_deployed_pending_use"}:
issue["status"] = "fix_deployed_pending_use"
issue["deployedAt"] = now
issue["implementationBatchId"] = batch_id
changed = True
if changed:
issue_ledger["updatedAt"] = now
issue_temporary = RUNTIME_ISSUE_PATH.with_suffix(".tmp")
issue_temporary.write_text(json.dumps(issue_ledger, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
issue_temporary.replace(RUNTIME_ISSUE_PATH)
return receipt