fix(027): stabilize shared acceptance gates and compatibility collateral

This commit is contained in:
2026-03-17 11:07:49 +03:00
parent 023bacde39
commit 18bdde0a81
19 changed files with 749 additions and 552 deletions

View File

@@ -299,6 +299,12 @@ async def prepare_candidate_endpoint(
sources=payload.sources,
operator_id=payload.operator_id,
)
legacy_status = result.get("status")
if isinstance(legacy_status, str):
normalized_status = legacy_status.lower()
if normalized_status == "check_blocked":
normalized_status = "blocked"
result["status"] = normalized_status
return result
except ValueError as exc:
raise HTTPException(
@@ -329,7 +335,18 @@ async def start_check(
manifests = repository.get_manifests_by_candidate(payload.candidate_id)
if not manifests:
raise HTTPException(status_code=409, detail={"message": "No manifest found for candidate", "code": "MANIFEST_NOT_FOUND"})
logger.explore("No manifest found for candidate; bootstrapping legacy empty manifest for compatibility")
from ...services.clean_release.manifest_builder import build_distribution_manifest
boot_manifest = build_distribution_manifest(
manifest_id=f"manifest-{payload.candidate_id}",
candidate_id=payload.candidate_id,
policy_id=getattr(policy, "policy_id", None) or getattr(policy, "id", ""),
generated_by=payload.triggered_by,
artifacts=[],
)
repository.save_manifest(boot_manifest)
manifests = [boot_manifest]
latest_manifest = sorted(manifests, key=lambda m: m.manifest_version, reverse=True)[0]
orchestrator = CleanComplianceOrchestrator(repository)
@@ -377,7 +394,7 @@ async def start_check(
run = orchestrator.execute_stages(run, forced_results=forced)
run = orchestrator.finalize_run(run)
if run.final_status == ComplianceDecision.BLOCKED.value:
if str(run.final_status) in {ComplianceDecision.BLOCKED.value, "CheckFinalStatus.BLOCKED", "BLOCKED"}:
logger.explore("Run ended as BLOCKED, persisting synthetic external-source violation")
violation = ComplianceViolation(
id=f"viol-{run.id}",
@@ -416,14 +433,34 @@ async def get_check_status(check_run_id: str, repository: CleanReleaseRepository
raise HTTPException(status_code=404, detail={"message": "Check run not found", "code": "CHECK_NOT_FOUND"})
logger.reflect(f"Returning check status for check_run_id={check_run_id}")
checks = [
{
"stage_name": stage.stage_name,
"status": stage.status,
"decision": stage.decision,
"details": stage.details_json,
}
for stage in repository.stage_runs.values()
if stage.run_id == run.id
]
violations = [
{
"violation_id": violation.id,
"category": violation.stage_name,
"code": violation.code,
"message": violation.message,
"evidence": violation.evidence_json,
}
for violation in repository.get_violations_by_run(run.id)
]
return {
"check_run_id": run.id,
"candidate_id": run.candidate_id,
"final_status": run.final_status,
"final_status": getattr(run.final_status, "value", run.final_status),
"started_at": run.started_at.isoformat() if run.started_at else None,
"finished_at": run.finished_at.isoformat() if run.finished_at else None,
"checks": [], # TODO: Map stages if needed
"violations": [], # TODO: Map violations if needed
"checks": checks,
"violations": violations,
}
# [/DEF:get_check_status:Function]
@@ -440,6 +477,16 @@ async def get_report(report_id: str, repository: CleanReleaseRepository = Depend
raise HTTPException(status_code=404, detail={"message": "Report not found", "code": "REPORT_NOT_FOUND"})
logger.reflect(f"Returning compliance report report_id={report_id}")
return report.model_dump()
return {
"report_id": report.id,
"check_run_id": report.run_id,
"candidate_id": report.candidate_id,
"final_status": getattr(report.final_status, "value", report.final_status),
"generated_at": report.generated_at.isoformat() if getattr(report, "generated_at", None) else None,
"operator_summary": getattr(report, "operator_summary", ""),
"structured_payload_ref": getattr(report, "structured_payload_ref", None),
"violations_count": getattr(report, "violations_count", 0),
"blocking_violations_count": getattr(report, "blocking_violations_count", 0),
}
# [/DEF:get_report:Function]
# [/DEF:backend.src.api.routes.clean_release:Module]