From 97b8e96681ff1cb91d4c9c30f10343d5db3630ee Mon Sep 17 00:00:00 2001 From: gziv Date: Wed, 8 Jul 2026 13:27:51 +0300 Subject: [PATCH] feat: add standalone certification.json output Create certification.json alongside scorecard.json for easier consumption by systems that only need certification status. - Add write_certification() to aggregate_scorecard.py - Include certification.json in MinIO upload (publish.py) - Contains: highest_level, per-level passed/checks/failure_reasons Co-authored-by: Cursor --- scripts/aggregate_scorecard.py | 48 ++++++++++++++++++++++++++++++++++ scripts/publish.py | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/scripts/aggregate_scorecard.py b/scripts/aggregate_scorecard.py index b5e4c7c..b07931c 100644 --- a/scripts/aggregate_scorecard.py +++ b/scripts/aggregate_scorecard.py @@ -379,6 +379,53 @@ def write_scorecard(scorecard: Scorecard, reports_dir: Path) -> Path: return output_path +def write_certification(scorecard: Scorecard, reports_dir: Path) -> Path | None: + """Write standalone certification.json file. + + Creates a separate certification.json alongside scorecard.json for easier + consumption by systems that only need certification status. + + Returns: + Path to certification.json, or None if no certification data. + """ + if scorecard.certification is None: + logger.info("No certification data to write") + return None + + reports_dir.mkdir(parents=True, exist_ok=True) + output_path = reports_dir / "certification.json" + + cert_data = { + "submission_name": scorecard.submission_name, + "pipeline_run_id": scorecard.pipeline_run_id, + "highest_level": scorecard.highest_certification.value, + "levels": { + "foundational": { + "passed": scorecard.certification.foundational.passed, + "checks": [c.model_dump() for c in scorecard.certification.foundational.checks], + "failure_reasons": scorecard.certification.foundational.failure_reasons, + }, + "trusted": { + "passed": scorecard.certification.trusted.passed, + "checks": [c.model_dump() for c in scorecard.certification.trusted.checks], + "failure_reasons": scorecard.certification.trusted.failure_reasons, + }, + "certified": { + "passed": scorecard.certification.certified.passed, + "checks": [c.model_dump() for c in scorecard.certification.certified.checks], + "failure_reasons": scorecard.certification.certified.failure_reasons, + }, + }, + "created_at": scorecard.created_at.isoformat(), + } + + with open(output_path, "w") as f: + json.dump(cert_data, f, indent=2) + + logger.info("Wrote certification to %s", output_path) + return output_path + + def main() -> int: parser = argparse.ArgumentParser(description="Aggregate gate results into unified scorecard") parser.add_argument( @@ -446,6 +493,7 @@ def main() -> int: ) write_scorecard(scorecard, args.reports_dir) + write_certification(scorecard, args.reports_dir) if args.output_tekton_results: results_dir = args.output_tekton_results diff --git a/scripts/publish.py b/scripts/publish.py index 92cebba..b133019 100644 --- a/scripts/publish.py +++ b/scripts/publish.py @@ -96,7 +96,7 @@ def upload_reports( uploaded = 0 # Core report files (required) # Support both A/B reports and MCPChecker reports - core_files = ["report.json", "report.md", "mcpchecker-report.json", "scorecard.json"] + core_files = ["report.json", "report.md", "mcpchecker-report.json", "scorecard.json", "certification.json"] for filename in core_files: filepath = report_dir / filename if not filepath.exists():