mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 13:51:36 +00:00
ci: fix Rank 7 lint + openapi-handler-parity drift on master
Two CI failures from the Rank 7 chain push (#438): Go Build & Test — staticcheck ST1021: internal/service/approval_metrics.go:97 comment for ApprovalDecisionEntry doesn't start with the type name internal/service/approval_metrics.go:130 comment for ApprovalPendingAgeSnapshot doesn't start with the type name Frontend Build — scripts/ci-guards/openapi-handler-parity.sh: 4 router routes have no OpenAPI operationId: GET /api/v1/approvals GET /api/v1/approvals/{id} POST /api/v1/approvals/{id}/approve POST /api/v1/approvals/{id}/reject The Rank 7 commit-3 spec deferred OpenAPI extension to commit 4 with a 'batched alongside the integration changes' note; commit 4 didn't actually add them. This commit closes that gap. Fixes: approval_metrics.go — split the doc comment that was attached to SnapshotApprovalDecisions (the function) but visually preceded ApprovalDecisionEntry (the type), so the type appeared to staticcheck as having a comment that named the function instead of the type. Same fix on ApprovalPendingAgeSnapshot. Now each exported type has its own type-name-leading comment per Go convention. api/openapi.yaml — added 4 new operationIds (listApprovalRequests, getApprovalRequest, approveApprovalRequest, rejectApprovalRequest) + new ApprovalRequest schema component under components/schemas. Inline 401 response (the Unauthorized component does not exist in this spec; the canonical pattern in the rest of the file is inline 'description: Authentication required'). The two-person integrity contract surface is documented in the description of the approve / reject endpoints so external readers see the RBAC contract from the spec alone. Verified locally: go vet ./internal/service/...: exit 0. scripts/ci-guards/openapi-handler-parity.sh: clean (140 ops vs 174 routes, 36 documented exceptions). Third CI failure (image-and-supply-chain) was a transient apt-fetch 'Connection reset by peer' from deb.debian.org while pulling libasan6_10.2.1-6_amd64.deb. Not a code issue; just re-run the workflow. No code change needed.
This commit is contained in:
@@ -2751,6 +2751,165 @@ paths:
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
# ─── Notifications ──────────────────────────────────────────────────
|
||||
/api/v1/approvals:
|
||||
get:
|
||||
tags: [Approvals]
|
||||
summary: List approval requests
|
||||
description: |
|
||||
Rank 7 issuance approval-workflow primitive. Returns paginated approval
|
||||
requests, optionally filtered by ?state= (pending/approved/rejected/expired),
|
||||
?certificate_id=, or ?requested_by=. Empty filters return the unfiltered
|
||||
list (default page=1, per_page=50).
|
||||
operationId: listApprovalRequests
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/page"
|
||||
- $ref: "#/components/parameters/per_page"
|
||||
- name: state
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum: [pending, approved, rejected, expired]
|
||||
- name: certificate_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: requested_by
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Paginated list of approval requests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ApprovalRequest"
|
||||
page:
|
||||
type: integer
|
||||
per_page:
|
||||
type: integer
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/v1/approvals/{id}:
|
||||
get:
|
||||
tags: [Approvals]
|
||||
summary: Get approval request
|
||||
description: Returns a single approval request by ID.
|
||||
operationId: getApprovalRequest
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/resourceId"
|
||||
responses:
|
||||
"200":
|
||||
description: Approval request details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApprovalRequest"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/v1/approvals/{id}/approve:
|
||||
post:
|
||||
tags: [Approvals]
|
||||
summary: Approve a pending approval request
|
||||
description: |
|
||||
Transitions a pending request to approved AND transitions the linked
|
||||
Job from AwaitingApproval to Pending so the scheduler picks it up.
|
||||
RBAC: the authenticated actor extracted via the auth middleware MUST
|
||||
differ from the request's requested_by — a same-actor self-approval
|
||||
returns HTTP 403 with the substring `two-person integrity` in the
|
||||
body. This is the load-bearing two-person integrity contract;
|
||||
compliance auditors (PCI-DSS 6.4.5, NIST 800-53 SA-15, SOC 2 CC6.1)
|
||||
pattern-match against this code path.
|
||||
operationId: approveApprovalRequest
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/resourceId"
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
note:
|
||||
type: string
|
||||
description: Optional reason text for the audit trail.
|
||||
responses:
|
||||
"200":
|
||||
description: Approval recorded; linked Job transitioned to Pending
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
decided_by: { type: string }
|
||||
action: { type: string, enum: [approved] }
|
||||
"401":
|
||||
description: Authentication required
|
||||
"403":
|
||||
description: Same-actor self-approval blocked by two-person integrity contract
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"409":
|
||||
description: Request already decided (terminal state)
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/v1/approvals/{id}/reject:
|
||||
post:
|
||||
tags: [Approvals]
|
||||
summary: Reject a pending approval request
|
||||
description: |
|
||||
Transitions a pending request to rejected AND cancels the linked
|
||||
Job. Same-actor RBAC contract as approve. The job's error_message
|
||||
is populated with the supplied note for audit continuity.
|
||||
operationId: rejectApprovalRequest
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/resourceId"
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
note:
|
||||
type: string
|
||||
description: Optional reason text for the audit trail.
|
||||
responses:
|
||||
"200":
|
||||
description: Rejection recorded; linked Job transitioned to Cancelled
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
decided_by: { type: string }
|
||||
action: { type: string, enum: [rejected] }
|
||||
"401":
|
||||
description: Authentication required
|
||||
"403":
|
||||
description: Same-actor self-rejection blocked by two-person integrity contract
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"409":
|
||||
description: Request already decided (terminal state)
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/v1/notifications:
|
||||
get:
|
||||
tags: [Notifications]
|
||||
@@ -4057,6 +4216,63 @@ components:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
schemas:
|
||||
# ─── Approvals ───────────────────────────────────────────────────
|
||||
ApprovalRequest:
|
||||
type: object
|
||||
description: |
|
||||
Rank 7 issuance approval-workflow primitive. One row per (CertificateID,
|
||||
JobID) pair; the JobID points at the blocked Job whose Status is
|
||||
AwaitingApproval. Lifecycle: pending → approved | rejected | expired.
|
||||
Once terminal, the row is immutable; the audit_events table is the
|
||||
durable record of who decided + why.
|
||||
required:
|
||||
- id
|
||||
- certificate_id
|
||||
- job_id
|
||||
- profile_id
|
||||
- requested_by
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Approval request ID (ar-<slug>).
|
||||
certificate_id:
|
||||
type: string
|
||||
job_id:
|
||||
type: string
|
||||
profile_id:
|
||||
type: string
|
||||
requested_by:
|
||||
type: string
|
||||
description: Actor that triggered the renewal.
|
||||
state:
|
||||
type: string
|
||||
enum: [pending, approved, rejected, expired]
|
||||
decided_by:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Approver identity; null while state=pending.
|
||||
decided_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
decision_note:
|
||||
type: string
|
||||
nullable: true
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Free-form key/value (common_name, sans, issuer_id, severity_tier).
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
# ─── Common ──────────────────────────────────────────────────────
|
||||
ErrorResponse:
|
||||
type: object
|
||||
|
||||
Reference in New Issue
Block a user