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