capscore-agent / api /app /proof_pack.py
mathurinacheisoft's picture
Deploy CAPScore (clean recreate)
2a7f679 verified
Raw
History Blame Contribute Delete
7.2 kB
from __future__ import annotations
import hashlib
import io
import json
import zipfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
from .config import settings
from .models import JobResult, A2ACall
def _canonical_json(obj: Any) -> str:
"""Deterministic JSON serialization."""
return json.dumps(obj, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
def _sha256(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def build_manifest(job: JobResult, a2a_calls: List[A2ACall]) -> Dict[str, Any]:
result_dict = job.scorecard.model_dump() if job.scorecard else {}
canonical = _canonical_json(result_dict)
result_hash = f"sha256:{_sha256(canonical)}"
return {
"job_id": job.job_id,
"cap_order_id": "",
"capability": job.capability,
"created_at": job.created_at.isoformat(),
"completed_at": job.completed_at.isoformat() if job.completed_at else None,
"agent_version": "0.1.0",
"input_hash": "",
"result_hash": result_hash,
"proof_files": [
"result.json",
"result.md",
"result_hash.sha256",
"execution_log.jsonl",
"evidence/sources.json",
"attestation.json",
],
"a2a_calls": [c.model_dump(mode="json") for c in a2a_calls],
"limits": {
"max_job_seconds": settings.max_job_seconds,
"max_repo_mb": settings.max_repo_mb,
"network_enabled": settings.allow_network_repro,
},
}
def build_attestation(result_hash: str, log_hash: str) -> Dict[str, Any]:
return {
"statement": "CAPScore Agent generated this report from the supplied inputs under the recorded runtime constraints.",
"result_hash": result_hash,
"execution_log_hash": log_hash,
"provider_agent": "CAPScore Agent",
"version": "0.1.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"signature": None,
}
def render_result_md(job: JobResult) -> str:
sc = job.scorecard
if not sc:
return f"# CAPScore Report\n\nJob `{job.job_id}` — No scorecard available.\n"
lines = [
f"# CAPScore Report — Job `{job.job_id}`",
f"",
f"**Overall Score: {sc.overall_score:.0f}/100**",
f"",
f"## Judging-Aligned Scorecard",
f"",
f"| Dimension | Score | Notes |",
f"|---|---:|---|",
f"| Technical Execution | {sc.technical_execution.score:.0f}/100 | {sc.technical_execution.notes} |",
f"| A2A Composability | {sc.a2a_composability.score:.0f}/100 | {sc.a2a_composability.notes} |",
f"| Innovation | {sc.innovation.score:.0f}/100 | {sc.innovation.notes} |",
f"| Adoption Readiness | {sc.adoption_readiness.score:.0f}/100 | {sc.adoption_readiness.notes} |",
f"| Presentation Readiness | {sc.presentation_readiness.score:.0f}/100 | {sc.presentation_readiness.notes} |",
f"",
]
if sc.critical_issues:
lines += ["## Critical Issues", ""]
for i, issue in enumerate(sc.critical_issues, 1):
lines.append(f"{i}. {issue}")
lines.append("")
if sc.top_fixes:
lines += ["## Top Fixes", ""]
for i, fix in enumerate(sc.top_fixes, 1):
lines.append(f"{i}. {fix}")
lines.append("")
if job.worker_results and job.worker_results.claims:
lines += ["## Claim Verification", ""]
lines += ["| Claim | Status | Confidence | Suggested Rewrite |", "|---|---|---:|---|"]
for v in job.worker_results.claims.verifications:
status_emoji = {"supported": "✓", "weak": "~", "unsupported": "✗", "misleading": "⚠"}.get(v.status, "?")
lines.append(f"| {v.claim[:60]}... | {status_emoji} {v.status} | {v.confidence:.0%} | {v.suggested_rewrite[:60]} |")
lines.append("")
lines += [
"## Proof",
"",
f"- Result hash: `{job.result_hash or 'pending'}`",
f"- Proof pack: `proof-pack-{job.job_id}.zip`",
f"- A2A calls: {len(job.a2a_calls)}",
f"- Generated: {job.completed_at.isoformat() if job.completed_at else 'pending'}",
"",
"---",
"*Generated by CAPScore Agent v0.1.0 — CROO Hackathon 2026*",
]
return "\n".join(lines)
def build_proof_pack(job: JobResult, execution_log_lines: List[str]) -> bytes:
"""Build a deterministic ZIP proof pack in memory."""
sc = job.scorecard
result_dict = sc.model_dump(mode="json") if sc else {}
canonical = _canonical_json(result_dict)
result_hash = f"sha256:{_sha256(canonical)}"
log_content = "\n".join(execution_log_lines)
log_hash = f"sha256:{_sha256(log_content)}"
manifest = build_manifest(job, job.a2a_calls)
manifest["result_hash"] = result_hash
attestation = build_attestation(result_hash, log_hash)
result_md = render_result_md(job)
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
zf.writestr("manifest.json", _canonical_json(manifest))
zf.writestr("result.json", _canonical_json(result_dict))
zf.writestr("result.md", result_md)
zf.writestr("result_hash.sha256", result_hash)
zf.writestr("execution_log.jsonl", log_content)
zf.writestr("attestation.json", _canonical_json(attestation))
# Evidence folder
evidence: Dict[str, Any] = {}
if job.worker_results:
if job.worker_results.repo:
evidence["repo_files"] = job.worker_results.repo.files_present
evidence["repo_issues"] = job.worker_results.repo.issues
if job.worker_results.security:
evidence["security_findings"] = [f.model_dump() for f in job.worker_results.security.findings]
zf.writestr("evidence/sources.json", _canonical_json(evidence))
return buf.getvalue()
def persist_proof_pack(job: JobResult, execution_log_lines: List[str]) -> Path:
"""Write proof pack to disk and return path."""
runs_dir = settings.runs_dir / job.job_id
runs_dir.mkdir(parents=True, exist_ok=True)
zip_bytes = build_proof_pack(job, execution_log_lines)
zip_path = runs_dir / f"proof-pack-{job.job_id}.zip"
zip_path.write_bytes(zip_bytes)
# Also write individual files for convenience
sc = job.scorecard
result_dict = sc.model_dump(mode="json") if sc else {}
canonical = json.dumps(result_dict, sort_keys=True, ensure_ascii=False, indent=2)
result_hash = f"sha256:{_sha256(json.dumps(result_dict, sort_keys=True, ensure_ascii=False, separators=(',', ':')))}"
(runs_dir / "result.json").write_text(canonical, encoding="utf-8")
(runs_dir / "result.md").write_text(render_result_md(job), encoding="utf-8")
(runs_dir / "result_hash.sha256").write_text(result_hash, encoding="utf-8")
(runs_dir / "execution_log.jsonl").write_text("\n".join(execution_log_lines), encoding="utf-8")
job.result_hash = result_hash
job.proof_pack_url = f"{settings.capscore_public_base_url}/jobs/{job.job_id}/proof-pack.zip"
return zip_path