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.
674 lines
17 KiB
YAML
674 lines
17 KiB
YAML
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
|