bin/publish-reports-entrypoint.py

#!/usr/bin/env python3
"""Materialize the meaning-first reports entrypoint in a Runtime web tree.

Rendering stays in Engineering Tools.  Runtime owns the publish location and
the small, explicit projection of runtime evidence into that location.
"""
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path


DEFAULT_RUNTIME_ROOT = Path(__file__).resolve().parents[1]
REPORT_NAMES = (
    "SE-0056-phase-5-validation.json",
    "SE-0056-traceability-validation.json",
    "SE-0069-link-validation-report.json",
    "SE-0069-link-validation-report.md",
    "SE-0070-context-impact-validation.json",
    "SE-0071-regression-evidence.json",
)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--engineering-tools-root", type=Path, required=True)
    parser.add_argument("--runtime-root", type=Path, default=DEFAULT_RUNTIME_ROOT,
                        help="Runtime-Wurzel für Evidenz und Eingabemodell")
    parser.add_argument("--output-root", type=Path, required=True,
                        help="Runtime web/reports directory")
    parser.add_argument("--target-runtime", default="",
                        help="Zielumgebung der veröffentlichten Reports")
    parser.add_argument("--input", type=Path,
                        default=None)
    args = parser.parse_args()

    runtime_root = args.runtime_root.expanduser().resolve()
    tools_root = args.engineering_tools_root.expanduser().resolve()
    publisher = tools_root / "97-documentation" / "publish-semantic-landing.py"
    if not publisher.is_file():
        parser.error(f"Semantischer Publisher fehlt: {publisher}")
    input_path = (args.input or runtime_root / "reports" / "SE-0057-semantic-publisher-input.json").expanduser().resolve()
    if not input_path.is_file():
        parser.error(f"Eingabemodell fehlt: {input_path}")
    work_item = json.loads(input_path.read_text(encoding="utf-8")).get("workItem", "SE-0065")
    if not isinstance(work_item, str) or not work_item:
        parser.error("Eingabemodell enthält kein gültiges Work Item")

    output_root = args.output_root.expanduser().resolve()
    output_root.mkdir(parents=True, exist_ok=True)
    technical = output_root / "technical-reports"
    technical.mkdir(parents=True, exist_ok=True)
    for name in REPORT_NAMES:
        source = runtime_root / "reports" / name
        if not source.is_file():
            parser.error(f"Runtime-Evidenz fehlt: {source}")
        shutil.copyfile(source, technical / name)

    command = [
        sys.executable, str(publisher), str(input_path), str(output_root / "index.html"),
        "--report", str(runtime_root / "reports" / f"{work_item}-reports-entrypoint-validation.json"),
        "--link-report", str(runtime_root / "reports" / f"{work_item}-reports-entrypoint-links.json"),
        "--link-markdown-report", str(runtime_root / "reports" / f"{work_item}-reports-entrypoint-links.md"),
        "--timestamp", "1970-01-01T00:00:00Z",
        "--target-runtime", args.target_runtime,
        "--code-view-directory", str(output_root / "code"),
        "--code-source", f"engineering-tools={tools_root}",
        "--test-source", f"engineering-tools={tools_root / '98-tests'}",
        "--test-source", f"runtime={runtime_root / 'test'}",
    ]
    completed = subprocess.run(command, text=True, capture_output=True)
    if completed.stdout:
        print(completed.stdout, end="")
    if completed.returncode:
        if completed.stderr:
            print(completed.stderr, file=sys.stderr, end="")
        return completed.returncode
    validation_path = runtime_root / "reports" / f"{work_item}-reports-entrypoint-validation.json"
    validation = json.loads(validation_path.read_text(encoding="utf-8"))
    validation["output"] = "web/reports/index.html"
    validation_path.write_text(json.dumps(validation, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    evidence = {
        "schemaVersion": "1.0",
        "workItem": work_item,
        "status": "PASSED",
        "entrypoint": "/reports/",
        "publishedFile": "web/reports/index.html",
        "renderer": "engineering-tools:97-documentation/publish-semantic-landing.py",
        "materializer": "runtime:bin/publish-reports-entrypoint.py",
        "technicalReports": list(REPORT_NAMES),
    }
    (runtime_root / "reports" / f"{work_item}-reports-entrypoint-evidence.json").write_text(
        json.dumps(evidence, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())