openapi: 3.1.0 info: title: Clean Release API version: 0.1.0 description: API-first contract for clean release candidate lifecycle, compliance runs, approvals and publications. servers: - url: /api x-interface-actor-mapping: description: External API request payloads use domain-specific actor fields; interface adapters may accept a unified actor context internally but must persist canonical *_by fields. mappings: candidate_register: created_by manifest_build: created_by compliance_run: requested_by approval_or_reject: decided_by publish: published_by revoke: actor paths: /clean-release/candidates: post: summary: Register a release candidate operationId: registerCleanReleaseCandidate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterCandidateRequest' responses: '201': description: Candidate created content: application/json: schema: $ref: '#/components/schemas/CandidateOverview' get: summary: List release candidates operationId: listCleanReleaseCandidates responses: '200': description: Candidate list content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/CandidateOverview' /clean-release/candidates/{candidate_id}: get: summary: Get candidate overview operationId: getCleanReleaseCandidate parameters: - $ref: '#/components/parameters/CandidateId' responses: '200': description: Candidate overview content: application/json: schema: $ref: '#/components/schemas/CandidateOverview' '404': $ref: '#/components/responses/NotFound' /clean-release/candidates/{candidate_id}/artifacts/import: post: summary: Import candidate artifacts operationId: importCleanReleaseArtifacts parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ImportArtifactsRequest' responses: '200': description: Artifacts imported content: application/json: schema: type: object required: [candidate_id, imported_count, status] properties: candidate_id: type: string imported_count: type: integer status: type: string /clean-release/candidates/{candidate_id}/artifacts: get: summary: List candidate artifacts operationId: listCleanReleaseArtifacts parameters: - $ref: '#/components/parameters/CandidateId' responses: '200': description: Candidate artifacts content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/CandidateArtifact' /clean-release/candidates/{candidate_id}/manifest: post: summary: Build a new manifest snapshot operationId: buildCleanReleaseManifest parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: false content: application/json: schema: type: object properties: requested_by: type: string responses: '201': description: Manifest created content: application/json: schema: $ref: '#/components/schemas/DistributionManifest' /clean-release/candidates/{candidate_id}/manifests: get: summary: List manifests for candidate operationId: listCleanReleaseManifests parameters: - $ref: '#/components/parameters/CandidateId' responses: '200': description: Candidate manifests content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/DistributionManifest' /clean-release/manifests/{manifest_id}: get: summary: Get manifest snapshot operationId: getCleanReleaseManifest parameters: - name: manifest_id in: path required: true schema: type: string responses: '200': description: Manifest snapshot content: application/json: schema: $ref: '#/components/schemas/DistributionManifest' '404': $ref: '#/components/responses/NotFound' /clean-release/candidates/{candidate_id}/compliance-runs: post: summary: Request a compliance run operationId: createCleanReleaseComplianceRun parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateComplianceRunRequest' responses: '202': description: Compliance run accepted content: application/json: schema: $ref: '#/components/schemas/ComplianceRun' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /clean-release/compliance-runs/{run_id}: get: summary: Get compliance run status operationId: getCleanReleaseComplianceRun parameters: - name: run_id in: path required: true schema: type: string responses: '200': description: Compliance run content: application/json: schema: $ref: '#/components/schemas/ComplianceRun' '404': $ref: '#/components/responses/NotFound' /clean-release/compliance-runs/{run_id}/stages: get: summary: List stage results for a run operationId: listCleanReleaseComplianceStages parameters: - name: run_id in: path required: true schema: type: string responses: '200': description: Stage list content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/ComplianceStageRun' /clean-release/compliance-runs/{run_id}/violations: get: summary: List violations for a run operationId: listCleanReleaseComplianceViolations parameters: - name: run_id in: path required: true schema: type: string responses: '200': description: Violation list content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/ComplianceViolation' /clean-release/compliance-runs/{run_id}/report: get: summary: Get final report for a run operationId: getCleanReleaseComplianceReport parameters: - name: run_id in: path required: true schema: type: string responses: '200': description: Compliance report content: application/json: schema: $ref: '#/components/schemas/ComplianceReport' '404': $ref: '#/components/responses/NotFound' /clean-release/candidates/{candidate_id}/approve: post: summary: Approve a candidate using a report operationId: approveCleanReleaseCandidate parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ApprovalRequest' responses: '200': description: Approval created content: application/json: schema: $ref: '#/components/schemas/ApprovalDecision' '409': $ref: '#/components/responses/Conflict' /clean-release/candidates/{candidate_id}/reject: post: summary: Reject a candidate using a report operationId: rejectCleanReleaseCandidate parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ApprovalRequest' responses: '200': description: Rejection created content: application/json: schema: $ref: '#/components/schemas/ApprovalDecision' '409': $ref: '#/components/responses/Conflict' /clean-release/candidates/{candidate_id}/publish: post: summary: Publish an approved candidate operationId: publishCleanReleaseCandidate parameters: - $ref: '#/components/parameters/CandidateId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PublishRequest' responses: '200': description: Publication created content: application/json: schema: $ref: '#/components/schemas/PublicationRecord' '409': $ref: '#/components/responses/Conflict' /clean-release/publications/{publication_id}/revoke: post: summary: Revoke a publication operationId: revokeCleanReleasePublication parameters: - name: publication_id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RevokePublicationRequest' responses: '200': description: Publication revoked content: application/json: schema: $ref: '#/components/schemas/PublicationRecord' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' components: parameters: CandidateId: name: candidate_id in: path required: true schema: type: string responses: NotFound: description: Entity not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Conflict: description: Illegal lifecycle transition or conflicting state content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' schemas: RegisterCandidateRequest: type: object required: [candidate_id, version, source_snapshot_ref, created_by] properties: candidate_id: type: string version: type: string source_snapshot_ref: type: string build_id: type: string created_by: type: string provenance_ref: type: string ImportArtifactsRequest: type: object required: [artifacts] properties: artifacts: type: array items: $ref: '#/components/schemas/CandidateArtifactInput' CandidateArtifactInput: type: object required: [path, sha256, size] properties: path: type: string sha256: type: string size: type: integer declared_category: type: string detected_category: type: string source_uri: type: string source_host: type: string metadata: type: object additionalProperties: true CreateComplianceRunRequest: type: object required: [requested_by] properties: manifest_id: type: string requested_by: type: string ApprovalRequest: type: object required: [report_id, decided_by] properties: report_id: type: string decided_by: type: string comment: type: string PublishRequest: type: object required: [report_id, target_channel, actor] properties: report_id: type: string target_channel: type: string actor: type: string RevokePublicationRequest: type: object required: [actor, reason] properties: actor: type: string reason: type: string CandidateOverview: type: object required: [candidate_id, version, source_snapshot_ref, status] properties: candidate_id: type: string version: type: string source_snapshot_ref: type: string status: type: string latest_manifest_id: type: string latest_manifest_digest: type: string latest_run_id: type: string latest_run_status: type: string latest_report_id: type: string latest_report_final_status: type: string latest_policy_snapshot_id: type: string latest_policy_version: type: string latest_registry_snapshot_id: type: string latest_registry_version: type: string latest_approval_decision: type: string latest_publication_id: type: string latest_publication_status: type: string CandidateArtifact: type: object required: [id, candidate_id, path, sha256, size, metadata] properties: id: type: string candidate_id: type: string path: type: string sha256: type: string size: type: integer detected_category: type: string declared_category: type: string source_uri: type: string source_host: type: string metadata: type: object additionalProperties: true DistributionManifest: type: object required: [id, candidate_id, manifest_version, manifest_digest, artifacts_digest, created_at, created_by, source_snapshot_ref, content_json, immutable] properties: id: type: string candidate_id: type: string manifest_version: type: integer manifest_digest: type: string artifacts_digest: type: string created_at: type: string format: date-time created_by: type: string source_snapshot_ref: type: string content_json: type: object additionalProperties: true immutable: type: boolean ComplianceRun: type: object required: [run_id, candidate_id, status, task_id] properties: run_id: type: string candidate_id: type: string manifest_id: type: string manifest_digest: type: string policy_snapshot_id: type: string registry_snapshot_id: type: string requested_by: type: string requested_at: type: string format: date-time started_at: type: string format: date-time finished_at: type: string format: date-time status: type: string final_status: type: string failure_reason: type: string report_id: type: string task_id: type: string ComplianceStageRun: type: object required: [id, run_id, stage_name, status, details_json] properties: id: type: string run_id: type: string stage_name: type: string status: type: string started_at: type: string format: date-time finished_at: type: string format: date-time decision: type: string details_json: type: object additionalProperties: true ComplianceViolation: type: object required: [id, run_id, stage_name, code, severity, message, evidence_json] properties: id: type: string run_id: type: string stage_name: type: string code: type: string severity: type: string artifact_path: type: string artifact_sha256: type: string message: type: string evidence_json: type: object additionalProperties: true ComplianceReport: type: object required: [report_id, run_id, candidate_id, final_status, summary_json, generated_at, immutable] properties: report_id: type: string run_id: type: string candidate_id: type: string final_status: type: string summary_json: type: object additionalProperties: true generated_at: type: string format: date-time immutable: type: boolean ApprovalDecision: type: object required: [id, candidate_id, report_id, decision, decided_by, decided_at] properties: id: type: string candidate_id: type: string report_id: type: string decision: type: string decided_by: type: string decided_at: type: string format: date-time comment: type: string PublicationRecord: type: object required: [id, candidate_id, report_id, published_by, published_at, target_channel, status] properties: id: type: string candidate_id: type: string report_id: type: string published_by: type: string published_at: type: string format: date-time target_channel: type: string publication_ref: type: string status: type: string ErrorResponse: type: object required: [code, message] properties: code: type: string message: type: string details: type: object additionalProperties: true