117 lines
4.3 KiB
Python
117 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the full read-only preflight for a 1C patch workspace."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from check_1c_patch_source_freshness import check_workspace_sources
|
|
from check_1c_patch_workspace_integrity import check_workspace
|
|
from diff_1c_patch_workspace import build_diff
|
|
from validate_1c_patch_workspace_semantics import validate_workspace
|
|
|
|
|
|
def load_json(path: Path) -> dict[str, Any]:
|
|
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
|
|
|
|
def safety_from_workspace(workspace: Path) -> dict[str, Any]:
|
|
path = workspace / "safety.json"
|
|
if not path.exists():
|
|
return {
|
|
"schema": "onec_change_proposal_safety_check.v1",
|
|
"passed": False,
|
|
"findings": [{"severity": "error", "code": "missing_safety_json", "message": f"Missing {path}"}],
|
|
"counts": {"errors": 1, "warnings": 0},
|
|
}
|
|
return load_json(path)
|
|
|
|
|
|
def status_from_checks(safety: dict[str, Any], integrity: dict[str, Any], freshness: dict[str, Any], semantic: dict[str, Any], diff: dict[str, Any]) -> str:
|
|
if not safety.get("passed") or not integrity.get("passed") or not freshness.get("passed") or not semantic.get("passed") or not diff.get("passed"):
|
|
return "blocked"
|
|
modified = (diff.get("counts") or {}).get("modified", 0)
|
|
if modified:
|
|
return "ready_for_review"
|
|
return "ready_for_editing"
|
|
|
|
|
|
def collect_gate(name: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
return {
|
|
"name": name,
|
|
"schema": data.get("schema"),
|
|
"passed": data.get("passed"),
|
|
"counts": data.get("counts"),
|
|
}
|
|
|
|
|
|
def next_actions(status: str) -> list[str]:
|
|
if status == "blocked":
|
|
return [
|
|
"Inspect failed gates and recreate the workspace if source files changed.",
|
|
"Do not generate, package, or apply patches until all gates pass.",
|
|
]
|
|
if status == "ready_for_editing":
|
|
return [
|
|
"Edit only files under working/.",
|
|
"Run preflight again after edits; BSL/Form.xml semantic validation is included.",
|
|
]
|
|
return [
|
|
"Review the workspace diff.",
|
|
"Run external BSL/1C validation in a disposable base before packaging or applying.",
|
|
]
|
|
|
|
|
|
def build_preflight(workspace: Path, *, max_patch_chars: int) -> dict[str, Any]:
|
|
safety = safety_from_workspace(workspace)
|
|
integrity = check_workspace(workspace)
|
|
freshness = check_workspace_sources(workspace)
|
|
semantic = validate_workspace(workspace)
|
|
diff = build_diff(workspace, max_patch_chars=max_patch_chars)
|
|
status = status_from_checks(safety, integrity, freshness, semantic, diff)
|
|
return {
|
|
"schema": "onec_patch_preflight.v1",
|
|
"workspace": str(workspace),
|
|
"status": status,
|
|
"passed": status != "blocked",
|
|
"gates": [
|
|
collect_gate("proposal_safety", safety),
|
|
collect_gate("workspace_integrity", integrity),
|
|
collect_gate("source_freshness", freshness),
|
|
collect_gate("workspace_semantic_validation", semantic),
|
|
collect_gate("workspace_diff", diff),
|
|
],
|
|
"diff_summary": diff.get("counts"),
|
|
"next_actions": next_actions(status),
|
|
"details": {
|
|
"safety": safety,
|
|
"integrity": integrity,
|
|
"freshness": freshness,
|
|
"semantic": semantic,
|
|
"diff": diff,
|
|
},
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Run 1C patch workspace preflight.")
|
|
parser.add_argument("--workspace", type=Path, required=True)
|
|
parser.add_argument("--max-patch-chars", type=int, default=200000)
|
|
parser.add_argument("--output", type=Path)
|
|
args = parser.parse_args()
|
|
|
|
result = build_preflight(args.workspace, max_patch_chars=args.max_patch_chars)
|
|
output = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
|
|
if args.output:
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(output, encoding="utf-8")
|
|
print(json.dumps({"output": str(args.output) if args.output else None, "passed": result["passed"], "status": result["status"], "diff": result["diff_summary"]}, ensure_ascii=False))
|
|
return 0 if result["passed"] else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|