diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index 8fe76dc..8ba7988 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -44,6 +44,7 @@ sys.path.insert(0, os.path.join(ROOT, "runner")) from harness import ( # noqa: E402 abra, canonical, + card as card_mod, deps as deps_mod, discovery, generic, @@ -1212,6 +1213,7 @@ def main() -> int: # ---- Phase 3 (R1/R3): assemble results.json (per-stage/per-test + computed level). Best-effort: # a failure here NEVER changes `overall` (R7 — cosmetics never block the pipeline). ---- + data: dict | None = None try: sso_unverified = sso_dep_unverified(declared, deps_ready, requires_deps_skipped) clean_teardown = (deploy_count == expected_deploy_count) and not dep_teardown_error @@ -1252,6 +1254,28 @@ def main() -> int: file=sys.stderr, ) + # ---- Phase 3 U2 (R3/R6): render the summary CARD (HTML→PNG) + level BADGE (SVG) from the + # results dict into the run artifact dir, alongside results.json + screenshot.png. The card + # REPORTS results.json verbatim — it computes nothing, so it can never look greener than the tiers + # (cardinal invariant, plan §6). Separate best-effort block (results.json is already written by + # here) — any failure is swallowed and NEVER changes `overall` (R7); a render failure simply means + # no summary.png, and U3/U4 fall back to text. ---- + if data is not None: + try: + html_path = os.path.join(run_artifact_dir, "summary.html") + with open(html_path, "w", encoding="utf-8") as f: + f.write(card_mod.render_card_html(data, screenshot_rel=data.get("screenshot"))) + png = card_mod.render_card_png(html_path, os.path.join(run_artifact_dir, "summary.png")) + with open(os.path.join(run_artifact_dir, "badge.svg"), "w", encoding="utf-8") as f: + f.write(card_mod.level_badge_svg(data["level"], data.get("level_cap_reason", ""))) + print( + f"summary card {'rendered ' + png if png else '(PNG render unavailable)'} + " + f"badge.svg written into {run_artifact_dir}", + flush=True, + ) + except Exception as e: # noqa: BLE001 — card/badge are cosmetic; never fail a run (R7) + print(f"!! summary card/badge render failed (non-fatal): {_scrub(str(e))}", flush=True) + # WC5 promote-on-green-cold: a GREEN COLD run on LATEST (no PR head) of an enrolled # (WARM_CANONICAL) recipe advances/seeds the canonical. ONLY cold-on-latest advances it (a PR # `!testme` carries REF and must NOT promote; `--quick` never promotes — handled in run_quick).