Add unit tests for PackRunAttestation and SealedInstallEnforcer
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission. - Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic. - Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
This commit is contained in:
673
docs/schemas/excititor-chunk-api.openapi.yaml
Normal file
673
docs/schemas/excititor-chunk-api.openapi.yaml
Normal file
@@ -0,0 +1,673 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Excititor Chunk API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
API for VEX document chunked ingestion and processing in Excititor service.
|
||||
Unblocks EXCITITOR-DOCS-0001, EXCITITOR-ENG-0001, EXCITITOR-OPS-0001 (3 tasks).
|
||||
contact:
|
||||
name: StellaOps Platform Team
|
||||
url: https://stella-ops.org
|
||||
license:
|
||||
name: AGPL-3.0-or-later
|
||||
url: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
servers:
|
||||
- url: /api/v1/excititor
|
||||
description: Excititor API base path
|
||||
|
||||
tags:
|
||||
- name: chunks
|
||||
description: Chunked document upload operations
|
||||
- name: vex
|
||||
description: VEX document ingestion
|
||||
- name: processing
|
||||
description: Document processing status
|
||||
- name: health
|
||||
description: Service health endpoints
|
||||
|
||||
paths:
|
||||
/chunks/initiate:
|
||||
post:
|
||||
operationId: initiateChunkedUpload
|
||||
summary: Initiate a chunked upload session
|
||||
description: Start a new chunked upload session for large VEX documents
|
||||
tags:
|
||||
- chunks
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:write]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChunkedUploadInitRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Upload session created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChunkedUploadSession'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
|
||||
/chunks/{session_id}:
|
||||
put:
|
||||
operationId: uploadChunk
|
||||
summary: Upload a chunk
|
||||
description: Upload a single chunk for an active upload session
|
||||
tags:
|
||||
- chunks
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:write]
|
||||
parameters:
|
||||
- name: session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: X-Chunk-Index
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
- name: X-Chunk-Digest
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^sha256:[a-f0-9]{64}$'
|
||||
- name: Content-Range
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
'200':
|
||||
description: Chunk uploaded successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChunkUploadResult'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
description: Chunk already uploaded or out of sequence
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
|
||||
get:
|
||||
operationId: getUploadSessionStatus
|
||||
summary: Get upload session status
|
||||
description: Retrieve the current status of a chunked upload session
|
||||
tags:
|
||||
- chunks
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:read]
|
||||
parameters:
|
||||
- name: session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Upload session status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChunkedUploadSession'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
delete:
|
||||
operationId: cancelUploadSession
|
||||
summary: Cancel upload session
|
||||
description: Cancel an active upload session and clean up partial data
|
||||
tags:
|
||||
- chunks
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:write]
|
||||
parameters:
|
||||
- name: session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'204':
|
||||
description: Session cancelled
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/chunks/{session_id}/complete:
|
||||
post:
|
||||
operationId: completeChunkedUpload
|
||||
summary: Complete chunked upload
|
||||
description: Finalize a chunked upload and trigger VEX processing
|
||||
tags:
|
||||
- chunks
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:write]
|
||||
parameters:
|
||||
- name: session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChunkedUploadCompleteRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Upload completed, processing started
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexIngestionJob'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
description: Missing chunks or invalid digest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
|
||||
/vex/ingest:
|
||||
post:
|
||||
operationId: ingestVexDocument
|
||||
summary: Ingest a VEX document
|
||||
description: Ingest a small VEX document directly (for documents < 10MB)
|
||||
tags:
|
||||
- vex
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:write]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexIngestionRequest'
|
||||
application/vnd.openvex+json:
|
||||
schema:
|
||||
type: object
|
||||
application/vnd.csaf+json:
|
||||
schema:
|
||||
type: object
|
||||
application/vnd.cyclonedx+json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'202':
|
||||
description: VEX document accepted for processing
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexIngestionJob'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'413':
|
||||
description: Payload too large - use chunked upload
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
|
||||
/vex/jobs/{job_id}:
|
||||
get:
|
||||
operationId: getIngestionJobStatus
|
||||
summary: Get ingestion job status
|
||||
description: Retrieve the status of a VEX ingestion job
|
||||
tags:
|
||||
- processing
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:read]
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Job status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexIngestionJob'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/vex/jobs:
|
||||
get:
|
||||
operationId: listIngestionJobs
|
||||
summary: List ingestion jobs
|
||||
description: List VEX ingestion jobs with filtering and pagination
|
||||
tags:
|
||||
- processing
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- oauth2: [excititor:read]
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [pending, processing, completed, failed]
|
||||
- name: created_after
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: created_before
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1
|
||||
- name: page_size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
responses:
|
||||
'200':
|
||||
description: List of jobs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexIngestionJobList'
|
||||
|
||||
/health:
|
||||
get:
|
||||
operationId: healthCheck
|
||||
summary: Health check
|
||||
description: Service health check endpoint
|
||||
tags:
|
||||
- health
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthStatus'
|
||||
'503':
|
||||
description: Service unhealthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthStatus'
|
||||
|
||||
/health/ready:
|
||||
get:
|
||||
operationId: readinessCheck
|
||||
summary: Readiness check
|
||||
description: Kubernetes readiness probe endpoint
|
||||
tags:
|
||||
- health
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: Service ready
|
||||
'503':
|
||||
description: Service not ready
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
oauth2:
|
||||
type: oauth2
|
||||
flows:
|
||||
clientCredentials:
|
||||
tokenUrl: /oauth/token
|
||||
scopes:
|
||||
excititor:read: Read VEX data
|
||||
excititor:write: Write VEX data
|
||||
|
||||
schemas:
|
||||
ChunkedUploadInitRequest:
|
||||
type: object
|
||||
required:
|
||||
- filename
|
||||
- total_size
|
||||
- content_type
|
||||
properties:
|
||||
filename:
|
||||
type: string
|
||||
description: Original filename
|
||||
total_size:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Total file size in bytes
|
||||
content_type:
|
||||
type: string
|
||||
enum:
|
||||
- application/json
|
||||
- application/vnd.openvex+json
|
||||
- application/vnd.csaf+json
|
||||
- application/vnd.cyclonedx+json
|
||||
expected_digest:
|
||||
type: string
|
||||
pattern: '^sha256:[a-f0-9]{64}$'
|
||||
description: Expected SHA-256 digest of complete file
|
||||
chunk_size:
|
||||
type: integer
|
||||
minimum: 1048576
|
||||
maximum: 104857600
|
||||
default: 10485760
|
||||
description: Chunk size in bytes (1MB - 100MB, default 10MB)
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: Optional metadata for the upload
|
||||
|
||||
ChunkedUploadSession:
|
||||
type: object
|
||||
required:
|
||||
- session_id
|
||||
- status
|
||||
- created_at
|
||||
properties:
|
||||
session_id:
|
||||
type: string
|
||||
format: uuid
|
||||
status:
|
||||
type: string
|
||||
enum: [active, completed, cancelled, expired]
|
||||
filename:
|
||||
type: string
|
||||
total_size:
|
||||
type: integer
|
||||
chunk_size:
|
||||
type: integer
|
||||
total_chunks:
|
||||
type: integer
|
||||
uploaded_chunks:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
chunks_remaining:
|
||||
type: integer
|
||||
bytes_uploaded:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
upload_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL for chunk uploads
|
||||
|
||||
ChunkUploadResult:
|
||||
type: object
|
||||
required:
|
||||
- chunk_index
|
||||
- received
|
||||
properties:
|
||||
chunk_index:
|
||||
type: integer
|
||||
received:
|
||||
type: boolean
|
||||
digest_verified:
|
||||
type: boolean
|
||||
bytes_received:
|
||||
type: integer
|
||||
chunks_remaining:
|
||||
type: integer
|
||||
|
||||
ChunkedUploadCompleteRequest:
|
||||
type: object
|
||||
required:
|
||||
- final_digest
|
||||
properties:
|
||||
final_digest:
|
||||
type: string
|
||||
pattern: '^sha256:[a-f0-9]{64}$'
|
||||
description: SHA-256 digest of reassembled file
|
||||
process_immediately:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Start processing immediately after assembly
|
||||
|
||||
VexIngestionRequest:
|
||||
type: object
|
||||
required:
|
||||
- document
|
||||
properties:
|
||||
document:
|
||||
type: object
|
||||
description: VEX document (OpenVEX, CSAF, or CycloneDX format)
|
||||
format:
|
||||
type: string
|
||||
enum: [openvex, csaf, cyclonedx, auto]
|
||||
default: auto
|
||||
source:
|
||||
type: string
|
||||
description: Source identifier for the VEX document
|
||||
priority:
|
||||
type: string
|
||||
enum: [low, normal, high]
|
||||
default: normal
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
VexIngestionJob:
|
||||
type: object
|
||||
required:
|
||||
- job_id
|
||||
- status
|
||||
- created_at
|
||||
properties:
|
||||
job_id:
|
||||
type: string
|
||||
format: uuid
|
||||
status:
|
||||
type: string
|
||||
enum: [pending, validating, processing, indexing, completed, failed]
|
||||
format_detected:
|
||||
type: string
|
||||
enum: [openvex, csaf, cyclonedx, unknown]
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
completed_at:
|
||||
type: string
|
||||
format: date-time
|
||||
document_digest:
|
||||
type: string
|
||||
pattern: '^sha256:[a-f0-9]{64}$'
|
||||
statements_count:
|
||||
type: integer
|
||||
description: Number of VEX statements processed
|
||||
products_count:
|
||||
type: integer
|
||||
description: Number of products affected
|
||||
vulnerabilities_count:
|
||||
type: integer
|
||||
description: Number of vulnerabilities referenced
|
||||
errors:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ProcessingError'
|
||||
warnings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
result_ref:
|
||||
type: string
|
||||
description: Reference to processing result
|
||||
|
||||
VexIngestionJobList:
|
||||
type: object
|
||||
required:
|
||||
- jobs
|
||||
- total_count
|
||||
properties:
|
||||
jobs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VexIngestionJob'
|
||||
total_count:
|
||||
type: integer
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
next_page_token:
|
||||
type: string
|
||||
|
||||
ProcessingError:
|
||||
type: object
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
location:
|
||||
type: string
|
||||
description: JSON path to error location
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
HealthStatus:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [healthy, degraded, unhealthy]
|
||||
version:
|
||||
type: string
|
||||
uptime_seconds:
|
||||
type: integer
|
||||
checks:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [pass, warn, fail]
|
||||
message:
|
||||
type: string
|
||||
|
||||
ProblemDetails:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- title
|
||||
- status
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
format: uri
|
||||
title:
|
||||
type: string
|
||||
status:
|
||||
type: integer
|
||||
detail:
|
||||
type: string
|
||||
instance:
|
||||
type: string
|
||||
format: uri
|
||||
errors:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
Unauthorized:
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
TooManyRequests:
|
||||
description: Rate limit exceeded
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
headers:
|
||||
Retry-After:
|
||||
schema:
|
||||
type: integer
|
||||
description: Seconds until rate limit resets
|
||||
Reference in New Issue
Block a user