Files
llm/scripts/create_1c_extension_validation_evidence.py
T

161 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Create manual evidence templates for a 1C extension validation plan."""
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def evidence_root(plan: dict[str, Any], override: Path | None = None) -> Path:
if override:
return override
configured = ((plan.get("runner_config") or {}).get("evidence_root")) or ((plan.get("evidence") or {}).get("root"))
if not configured:
raise SystemExit("Validation plan has no evidence root.")
return Path(str(configured))
def file_template(name: str, plan: dict[str, Any]) -> str:
if name.endswith(".json"):
payload: dict[str, Any] = {
"schema": "onec_extension_validation_manual_evidence.v1",
"status": "pending",
"created_at_utc": datetime.now(timezone.utc).isoformat(),
"validation_plan": plan.get("schema"),
"staging_dir": plan.get("staging_dir"),
"notes": "",
}
if name == "changed-objects-smoke.json":
payload["objects"] = [
{
**item,
"status": "pending",
"notes": "",
}
for item in ((plan.get("details") or {}).get("changed_objects") or [])
]
return json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
if name.endswith(".md"):
return "\n".join(
[
f"# {name}",
"",
"Status: pending",
"",
"Replace the status line with `Status: passed` only after the disposable-base check is complete.",
"Record what was checked in the disposable 1C base.",
"Do not include passwords, tokens, production connection strings, or personal data.",
"",
]
)
return "\n".join(
[
f"{name}",
f"Created UTC: {datetime.now(timezone.utc).isoformat()}",
"Status: pending",
"",
"Replace the status line with `Status: passed` only after the disposable-base check is complete.",
"Paste disposable 1C validation log here.",
"Do not include passwords, tokens, production connection strings, or personal data.",
"",
]
)
def create_evidence(plan_path: Path, output_root: Path | None, *, force: bool) -> dict[str, Any]:
plan = load_json(plan_path)
if plan.get("schema") != "onec_extension_validation_plan.v1":
raise SystemExit(f"Unsupported validation plan schema: {plan.get('schema')}")
if plan.get("status") != "ready_for_disposable_validation":
raise SystemExit(f"Validation plan is not ready for disposable validation: {plan.get('status')}")
root = evidence_root(plan, output_root)
root.mkdir(parents=True, exist_ok=True)
files = []
for name in (plan.get("evidence") or {}).get("required_files") or []:
relative = Path(str(name).replace("\\", "/"))
if relative.is_absolute() or ".." in relative.parts or not str(relative):
raise SystemExit(f"Unsafe evidence file name: {name}")
path = root / relative
existed = path.exists()
if existed and not force:
files.append({"path": str(path), "created": False, "skipped_existing": True})
continue
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(file_template(str(name), plan), encoding="utf-8")
files.append({"path": str(path), "created": True, "skipped_existing": False})
manifest = {
"schema": "onec_extension_validation_evidence_manifest.v1",
"created_at_utc": datetime.now(timezone.utc).isoformat(),
"status": "pending_manual_validation",
"plan_path": str(plan_path),
"plan_status": plan.get("status"),
"staging_dir": plan.get("staging_dir"),
"evidence_root": str(root),
"files": files,
"counts": {
"files": len(files),
"created": sum(1 for item in files if item.get("created")),
"skipped_existing": sum(1 for item in files if item.get("skipped_existing")),
},
}
write_json(root / "_codex_validation_evidence_manifest.json", manifest)
(root / "README.md").write_text(render_readme(manifest, plan), encoding="utf-8")
return manifest
def render_readme(manifest: dict[str, Any], plan: dict[str, Any]) -> str:
lines = [
"# 1C Extension Validation Evidence",
"",
f"Status: `{manifest.get('status')}`",
f"Validation plan: `{manifest.get('plan_path')}`",
f"Staging: `{manifest.get('staging_dir')}`",
"",
"## Rules",
"",
"- Fill these files only with evidence from a disposable 1C base.",
"- Do not include passwords, tokens, production connection strings, or personal data.",
"- This folder does not prove validation passed until the evidence checker passes.",
"",
"## Required Checks",
"",
]
for check in plan.get("checks") or []:
files = ", ".join(f"`{name}`" for name in check.get("expected_evidence") or [])
lines.append(f"- `{check.get('id')}`: {files}")
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description="Create manual evidence templates for a 1C extension validation plan.")
parser.add_argument("--plan", type=Path, required=True)
parser.add_argument("--output-root", type=Path)
parser.add_argument("--force", action="store_true")
parser.add_argument("--output", type=Path)
args = parser.parse_args()
result = create_evidence(args.plan, args.output_root, force=args.force)
if args.output:
write_json(args.output, result)
print(json.dumps({"output": str(args.output) if args.output else None, "status": result["status"], "counts": result["counts"]}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())