Files
git.stella-ops.org/docs/api/concelier/concelier-lnm.yaml

414 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

openapi: 3.1.0
info:
title: StellaOps Concelier Link-Not-Merge Policy APIs
version: "1.0.0"
description: |
Fact-only advisory/linkset retrieval for Policy Engine consumers.
## Philosophy
Link-Not-Merge (LNM) provides raw advisory data with full provenance:
- **Link**: Observations from multiple sources are linked via shared identifiers.
- **Not Merge**: Conflicting data is preserved rather than collapsed.
- **Surface, Don't Resolve**: Conflicts are clearly marked for consumers.
## Authentication
All endpoints require the `X-Stella-Tenant` header for multi-tenant isolation.
## Pagination
List endpoints support cursor-based pagination with `page` and `pageSize` parameters.
Maximum page size is 200 items.
## Documentation
See `/docs/modules/concelier/api/` for detailed examples and conflict resolution strategies.
servers:
- url: /
description: Relative base path (API Gateway rewrites in production).
tags:
- name: Linksets
description: Link-Not-Merge linkset retrieval
paths:
/v1/lnm/linksets:
get:
summary: List linksets
tags: [Linksets]
parameters:
- $ref: '#/components/parameters/Tenant'
- name: includeConflicts
in: query
required: false
schema: { type: boolean, default: true }
- name: includeObservations
in: query
required: false
schema: { type: boolean, default: false }
- $ref: '#/components/parameters/purl'
- $ref: '#/components/parameters/cpe'
- $ref: '#/components/parameters/ghsa'
- $ref: '#/components/parameters/cve'
- $ref: '#/components/parameters/advisoryId'
- $ref: '#/components/parameters/source'
- $ref: '#/components/parameters/severityMin'
- $ref: '#/components/parameters/severityMax'
- $ref: '#/components/parameters/publishedSince'
- $ref: '#/components/parameters/modifiedSince'
- $ref: '#/components/parameters/page'
- $ref: '#/components/parameters/pageSize'
- $ref: '#/components/parameters/sort'
responses:
"200":
description: Deterministically ordered list of linksets
content:
application/json:
schema:
$ref: '#/components/schemas/PagedLinksets'
examples:
single-linkset:
summary: Single linkset result
value:
items:
- advisoryId: "CVE-2021-23337"
source: "nvd"
purl: ["pkg:npm/lodash@4.17.20"]
cpe: ["cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:node.js:*:*"]
summary: "Lodash Command Injection vulnerability"
publishedAt: "2021-02-15T13:15:00Z"
modifiedAt: "2024-08-04T19:16:00Z"
severity: "high"
provenance:
ingestedAt: "2025-11-20T10:30:00Z"
connectorId: "nvd-osv-connector"
evidenceHash: "sha256:a1b2c3d4e5f6"
conflicts: []
cached: false
page: 1
pageSize: 50
total: 1
with-conflicts:
summary: Linkset with severity conflict
value:
items:
- advisoryId: "CVE-2024-1234"
source: "aggregated"
purl: ["pkg:npm/example@1.0.0"]
cpe: []
severity: "high"
provenance:
ingestedAt: "2025-11-20T10:30:00Z"
connectorId: "multi-source"
conflicts:
- field: "severity"
reason: "severity-mismatch"
observedValue: "critical"
observedAt: "2025-11-18T08:00:00Z"
evidenceHash: "sha256:conflict-hash"
cached: false
page: 1
pageSize: 50
total: 1
"400":
description: Invalid request parameters
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
example:
type: "https://stellaops.io/errors/validation-failed"
title: "Validation Failed"
status: 400
detail: "The 'pageSize' parameter exceeds the maximum allowed value."
error:
code: "ERR_PAGE_SIZE_EXCEEDED"
message: "Page size must be between 1 and 200."
target: "pageSize"
/v1/lnm/linksets/{advisoryId}:
get:
summary: Get linkset by advisory ID
tags: [Linksets]
parameters:
- $ref: '#/components/parameters/Tenant'
- name: advisoryId
in: path
required: true
schema:
type: string
- name: source
in: query
required: false
schema: { type: string }
- name: includeConflicts
in: query
required: false
schema: { type: boolean, default: true }
- name: includeObservations
in: query
required: false
schema: { type: boolean, default: false }
responses:
"200":
description: Linkset with provenance and conflicts
content:
application/json:
schema:
$ref: '#/components/schemas/Linkset'
"404":
description: Not found
/v1/lnm/linksets/search:
post:
summary: Search linksets (body filters)
tags: [Linksets]
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LinksetSearchRequest'
responses:
"200":
description: Deterministically ordered search results
content:
application/json:
schema:
$ref: '#/components/schemas/PagedLinksets'
components:
parameters:
Tenant:
name: Tenant
in: header
required: true
schema:
type: string
description: Tenant identifier (required).
purl:
name: purl
in: query
schema:
type: array
items: { type: string }
style: form
explode: true
cpe:
name: cpe
in: query
schema: { type: string }
ghsa:
name: ghsa
in: query
schema: { type: string }
cve:
name: cve
in: query
schema: { type: string }
advisoryId:
name: advisoryId
in: query
schema: { type: string }
source:
name: source
in: query
schema:
type: string
severityMin:
name: severityMin
in: query
schema:
type: number
format: float
severityMax:
name: severityMax
in: query
schema:
type: number
format: float
publishedSince:
name: publishedSince
in: query
schema:
type: string
format: date-time
modifiedSince:
name: modifiedSince
in: query
schema:
type: string
format: date-time
page:
name: page
in: query
schema:
type: integer
minimum: 1
default: 1
pageSize:
name: pageSize
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
sort:
name: sort
in: query
schema:
type: string
enum:
- modifiedAt desc
- modifiedAt asc
- publishedAt desc
- publishedAt asc
- severity desc
- severity asc
- source
- advisoryId
description: Default modifiedAt desc; ties advisoryId asc, source asc.
schemas:
LinksetSearchRequest:
type: object
properties:
purl: { type: array, items: { type: string } }
cpe: { type: array, items: { type: string } }
ghsa: { type: string }
cve: { type: string }
advisoryId: { type: string }
source: { type: string }
severityMin: { type: number }
severityMax: { type: number }
publishedSince: { type: string, format: date-time }
modifiedSince: { type: string, format: date-time }
includeTimeline: { type: boolean, default: false }
includeObservations: { type: boolean, default: false }
includeConflicts: { type: boolean, default: true }
page: { type: integer, minimum: 1, default: 1 }
pageSize: { type: integer, minimum: 1, maximum: 200, default: 50 }
sort: { type: string, enum: [modifiedAt desc, modifiedAt asc, publishedAt desc, publishedAt asc, severity desc, severity asc, source, advisoryId] }
PagedLinksets:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/Linkset' }
page: { type: integer }
pageSize: { type: integer }
total: { type: integer }
Linkset:
type: object
required: [advisoryId, source, purl, cpe, provenance]
properties:
advisoryId: { type: string }
source: { type: string }
purl: { type: array, items: { type: string } }
cpe: { type: array, items: { type: string } }
summary: { type: string }
publishedAt: { type: string, format: date-time }
modifiedAt: { type: string, format: date-time }
severity: { type: string, description: Source-native severity label }
status: { type: string }
provenance: { $ref: '#/components/schemas/LinksetProvenance' }
conflicts:
type: array
items: { $ref: '#/components/schemas/LinksetConflict' }
timeline:
type: array
items: { $ref: '#/components/schemas/LinksetTimeline' }
normalized:
type: object
properties:
aliases: { type: array, items: { type: string } }
purl: { type: array, items: { type: string } }
cpe: { type: array, items: { type: string } }
versions: { type: array, items: { type: string } }
ranges: { type: array, items: { type: object } }
severities: { type: array, items: { type: object } }
cached:
type: boolean
description: True if served from cache; provenance.evidenceHash present for integrity.
remarks:
type: array
items: { type: string }
observations:
type: array
items: { type: string }
LinksetProvenance:
type: object
properties:
ingestedAt: { type: string, format: date-time }
connectorId: { type: string }
evidenceHash: { type: string }
dsseEnvelopeHash: { type: string }
LinksetConflict:
type: object
properties:
field: { type: string }
reason: { type: string }
observedValue: { type: string }
observedAt: { type: string, format: date-time }
evidenceHash: { type: string }
LinksetTimeline:
type: object
properties:
event: { type: string }
at: { type: string, format: date-time }
evidenceHash: { type: string }
ErrorEnvelope:
type: object
description: RFC 7807 Problem Details with StellaOps extensions
properties:
type:
type: string
format: uri
description: URI identifying the problem type
title:
type: string
description: Short, human-readable summary
status:
type: integer
description: HTTP status code
detail:
type: string
description: Specific explanation of the problem
instance:
type: string
format: uri
description: URI of the specific occurrence
traceId:
type: string
description: Distributed trace identifier
error:
$ref: '#/components/schemas/ErrorDetail'
ErrorDetail:
type: object
description: Machine-readable error information
properties:
code:
type: string
description: Machine-readable error code (e.g., ERR_VALIDATION_FAILED)
message:
type: string
description: Human-readable error message
target:
type: string
description: Field or resource that caused the error
metadata:
type: object
additionalProperties: true
description: Additional contextual data
innerErrors:
type: array
items:
$ref: '#/components/schemas/ValidationError'
description: Nested validation errors
ValidationError:
type: object
properties:
field:
type: string
description: Field path (e.g., "data.severity")
code:
type: string
description: Error code for this field
message:
type: string
description: Human-readable message