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
Export Center CI / export-ci (push) Has been cancelled
Symbols Server CI / symbols-smoke (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
588 lines
19 KiB
YAML
588 lines
19 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: StellaOps Graph Gateway (draft)
|
|
version: 0.0.3-pre
|
|
description: |
|
|
Draft API surface for graph search/query/paths/diff/export with streaming tiles,
|
|
cost budgets, overlays, and RBAC headers. Aligns with sprint 0207 Wave 1 outline
|
|
(GRAPH-API-28-001..011).
|
|
servers:
|
|
- url: https://gateway.local/api
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
paths:
|
|
/graph/search:
|
|
post:
|
|
summary: Search graph nodes with prefix/exact semantics and filters
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SearchRequest'
|
|
responses:
|
|
'200':
|
|
description: Stream of search tiles (NDJSON)
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
sample:
|
|
summary: Node + cursor tiles
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:component:abc","kind":"component","tenant":"acme","attributes":{"purl":"pkg:npm/lodash@4.17.21"}},"cost":{"limit":1000,"remaining":999,"consumed":1}}
|
|
{"type":"cursor","seq":1,"data":{"token":"cursor-123","resumeUrl":"https://gateway.local/api/graph/query?cursor=cursor-123"}}
|
|
'400': { $ref: '#/components/responses/ValidationError' }
|
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
'429': { $ref: '#/components/responses/BudgetExceeded' }
|
|
responses:
|
|
'200':
|
|
description: Stream of search tiles (NDJSON)
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
sample:
|
|
summary: Node + cursor tiles
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:component:abc","kind":"component","tenant":"acme","attributes":{"purl":"pkg:npm/lodash@4.17.21"}},"cost":{"limit":1000,"remaining":999,"consumed":1}}
|
|
{"type":"cursor","seq":1,"data":{"token":"cursor-123","resumeUrl":"https://gateway.local/api/graph/search?cursor=cursor-123"}}
|
|
headers:
|
|
X-RateLimit-Remaining:
|
|
description: Remaining request budget within the window.
|
|
schema:
|
|
type: integer
|
|
Retry-After:
|
|
description: Seconds until next request is allowed when rate limited.
|
|
schema:
|
|
type: integer
|
|
|
|
/graph/query:
|
|
post:
|
|
summary: Execute graph query with budgeted streaming tiles
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/QueryRequest'
|
|
responses:
|
|
'200':
|
|
description: Stream of query tiles (NDJSON)
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
mixedTiles:
|
|
summary: Node + edge + stats tiles
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:artifact:sha256:...","tenant":"acme","kind":"artifact","attributes":{"sbom_digest":"sha256:abc"}}}
|
|
{"type":"edge","seq":1,"data":{"id":"ge:tenant:CONTAINS:...","sourceId":"gn:tenant:artifact:...","targetId":"gn:tenant:component:...","kind":"CONTAINS"}}
|
|
{"type":"stats","seq":2,"data":{"nodesEmitted":1,"edgesEmitted":1,"depthReached":2,"cacheHitRatio":0.8}}
|
|
'400': { $ref: '#/components/responses/ValidationError' }
|
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
'429': { $ref: '#/components/responses/BudgetExceeded' }
|
|
responses:
|
|
'200':
|
|
description: Stream of query tiles (NDJSON)
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
mixedTiles:
|
|
summary: Node + edge + stats tiles
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:artifact:sha256:...","tenant":"acme","kind":"artifact","attributes":{"sbom_digest":"sha256:abc"}}}
|
|
{"type":"edge","seq":1,"data":{"id":"ge:tenant:CONTAINS:...","sourceId":"gn:tenant:artifact:...","targetId":"gn:tenant:component:...","kind":"CONTAINS"}}
|
|
{"type":"stats","seq":2,"data":{"nodesEmitted":1,"edgesEmitted":1,"depthReached":2,"cacheHitRatio":0.8}}
|
|
headers:
|
|
X-RateLimit-Remaining:
|
|
description: Remaining request budget within the window.
|
|
schema:
|
|
type: integer
|
|
Retry-After:
|
|
description: Seconds until next request is allowed when rate limited.
|
|
schema:
|
|
type: integer
|
|
|
|
/graph/paths:
|
|
post:
|
|
summary: Find constrained paths between node sets (depth ≤ 6)
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PathsRequest'
|
|
responses:
|
|
'200':
|
|
description: Stream of path tiles ordered by hop
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
pathTiles:
|
|
summary: Path tiles grouped by hop
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:component:src","kind":"component","tenant":"acme"}}
|
|
{"type":"edge","seq":1,"data":{"id":"ge:tenant:DEPENDS_ON:1","sourceId":"gn:tenant:component:src","targetId":"gn:tenant:component:dst","kind":"DEPENDS_ON"}}
|
|
{"type":"stats","seq":2,"data":{"nodesEmitted":2,"edgesEmitted":1,"depthReached":1}}
|
|
'400': { $ref: '#/components/responses/ValidationError' }
|
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
'429': { $ref: '#/components/responses/BudgetExceeded' }
|
|
responses:
|
|
'200':
|
|
description: Stream of path tiles ordered by hop
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
pathTiles:
|
|
summary: Path tiles grouped by hop
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:component:src","kind":"component","tenant":"acme","attributes":{"purl":"pkg:npm/demo@1.0.0"},"pathHop":0}}
|
|
{"type":"edge","seq":1,"data":{"id":"ge:tenant:DEPENDS_ON:1","sourceId":"gn:tenant:component:src","targetId":"gn:tenant:component:dst","kind":"DEPENDS_ON","pathHop":1}}
|
|
{"type":"stats","seq":2,"data":{"nodesEmitted":2,"edgesEmitted":1,"depthReached":1}}
|
|
|
|
/graph/diff:
|
|
post:
|
|
summary: Stream diff between two graph snapshots with overlay deltas
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/DiffRequest'
|
|
responses:
|
|
'200':
|
|
description: Stream of diff tiles (added/removed/changed)
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
$ref: '#/components/schemas/TileEnvelope'
|
|
examples:
|
|
diffTiles:
|
|
summary: Added/removed tiles
|
|
value: |
|
|
{"type":"node","seq":0,"data":{"id":"gn:tenant:component:new","kind":"component","tenant":"acme","attributes":{"purl":"pkg:npm/new@1.0.0"}}}
|
|
{"type":"diagnostic","seq":1,"data":{"level":"info","message":"snapshot diff complete"}}
|
|
'400': { $ref: '#/components/responses/ValidationError' }
|
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
'429': { $ref: '#/components/responses/BudgetExceeded' }
|
|
|
|
/graph/export/{jobId}/manifest:
|
|
get:
|
|
summary: Download deterministic checksum manifest for a completed export job
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
- name: jobId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Deterministic manifest
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
files:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
path: { type: string }
|
|
sha256: { type: string }
|
|
size: { type: integer }
|
|
exportId: { type: string }
|
|
'404': { description: Manifest not ready or job missing }
|
|
|
|
/graph/export:
|
|
post:
|
|
summary: Request export job for snapshot or query result
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ExportRequest'
|
|
responses:
|
|
'202':
|
|
description: Export job accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ExportJob'
|
|
'400': { $ref: '#/components/responses/ValidationError' }
|
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
|
|
/graph/export/{jobId}:
|
|
get:
|
|
summary: Check export job status or download manifest
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantHeader'
|
|
- $ref: '#/components/parameters/RequestIdHeader'
|
|
- name: jobId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Job status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ExportJob'
|
|
'404':
|
|
description: Job not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
parameters:
|
|
TenantHeader:
|
|
name: X-Stella-Tenant
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Tenant identifier enforced on all routes.
|
|
RequestIdHeader:
|
|
name: X-Request-Id
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Optional caller-provided correlation id, echoed in responses.
|
|
|
|
schemas:
|
|
OverlayPayload:
|
|
type: object
|
|
description: Overlay content injected into node/edge tiles when requested (policy/vex/advisory).
|
|
properties:
|
|
kind:
|
|
type: string
|
|
enum: [policy, vex, advisory]
|
|
version:
|
|
type: string
|
|
description: Contract version of the overlay payload.
|
|
data:
|
|
type: object
|
|
additionalProperties: true
|
|
required: [kind, version, data]
|
|
|
|
CostBudget:
|
|
type: object
|
|
properties:
|
|
limit:
|
|
type: integer
|
|
minimum: 1
|
|
example: 1000
|
|
remaining:
|
|
type: integer
|
|
minimum: 0
|
|
example: 995
|
|
consumed:
|
|
type: integer
|
|
minimum: 0
|
|
example: 5
|
|
required: [limit, remaining, consumed]
|
|
|
|
TileEnvelope:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum: [node, edge, stats, cursor, diagnostic]
|
|
seq:
|
|
type: integer
|
|
minimum: 0
|
|
example: 0
|
|
cost:
|
|
$ref: '#/components/schemas/CostBudget'
|
|
data:
|
|
description: Payload varies by tile type.
|
|
oneOf:
|
|
- $ref: '#/components/schemas/NodeTile'
|
|
- $ref: '#/components/schemas/EdgeTile'
|
|
- $ref: '#/components/schemas/StatsTile'
|
|
- $ref: '#/components/schemas/CursorTile'
|
|
- $ref: '#/components/schemas/DiagnosticTile'
|
|
required: [type, seq]
|
|
|
|
NodeTile:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
kind: { type: string }
|
|
tenant: { type: string }
|
|
attributes: { type: object }
|
|
pathHop:
|
|
type: integer
|
|
description: Hop depth for path streaming responses.
|
|
overlays:
|
|
type: object
|
|
description: Optional overlay payloads (policy/vex/advisory) keyed by overlay kind.
|
|
additionalProperties:
|
|
$ref: '#/components/schemas/OverlayPayload'
|
|
required: [id, kind, tenant]
|
|
|
|
EdgeTile:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
kind: { type: string }
|
|
sourceId: { type: string }
|
|
targetId: { type: string }
|
|
tenant: { type: string }
|
|
attributes: { type: object }
|
|
pathHop:
|
|
type: integer
|
|
description: Hop depth for path streaming responses.
|
|
overlays:
|
|
type: object
|
|
additionalProperties:
|
|
$ref: '#/components/schemas/OverlayPayload'
|
|
required: [id, kind, sourceId, targetId, tenant]
|
|
|
|
StatsTile:
|
|
type: object
|
|
properties:
|
|
nodesEmitted: { type: integer, minimum: 0 }
|
|
edgesEmitted: { type: integer, minimum: 0 }
|
|
depthReached: { type: integer, minimum: 0 }
|
|
cacheHitRatio: { type: number, minimum: 0, maximum: 1 }
|
|
required: [nodesEmitted, edgesEmitted]
|
|
|
|
CursorTile:
|
|
type: object
|
|
properties:
|
|
token: { type: string }
|
|
resumeUrl: { type: string, format: uri }
|
|
required: [token]
|
|
|
|
DiagnosticTile:
|
|
type: object
|
|
properties:
|
|
level: { type: string, enum: [info, warn, error] }
|
|
message: { type: string }
|
|
details: { type: object }
|
|
required: [level, message]
|
|
|
|
SearchRequest:
|
|
type: object
|
|
properties:
|
|
query:
|
|
type: string
|
|
description: Prefix or exact text; required unless filters present.
|
|
kinds:
|
|
type: array
|
|
items:
|
|
type: string
|
|
limit:
|
|
type: integer
|
|
default: 50
|
|
maximum: 500
|
|
filters:
|
|
type: object
|
|
additionalProperties: true
|
|
ordering:
|
|
type: string
|
|
enum: [relevance, id]
|
|
cursor:
|
|
type: string
|
|
description: Resume token from prior search response.
|
|
required: [kinds]
|
|
|
|
QueryRequest:
|
|
type: object
|
|
properties:
|
|
dsl:
|
|
type: string
|
|
description: DSL expression for graph traversal (mutually exclusive with filter).
|
|
filter:
|
|
type: object
|
|
description: Structured filter alternative to DSL.
|
|
budget:
|
|
type: object
|
|
properties:
|
|
nodeCap: { type: integer }
|
|
edgeCap: { type: integer }
|
|
timeMs: { type: integer }
|
|
overlays:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [policy, vex, advisory]
|
|
explain:
|
|
type: string
|
|
enum: [none, minimal, full]
|
|
default: none
|
|
cursor:
|
|
type: string
|
|
description: Resume token from prior query response.
|
|
anyOf:
|
|
- required: [dsl]
|
|
- required: [filter]
|
|
|
|
PathsRequest:
|
|
type: object
|
|
properties:
|
|
sourceIds:
|
|
type: array
|
|
items: { type: string }
|
|
minItems: 1
|
|
targetIds:
|
|
type: array
|
|
items: { type: string }
|
|
minItems: 1
|
|
maxDepth:
|
|
type: integer
|
|
maximum: 6
|
|
default: 4
|
|
constraints:
|
|
type: object
|
|
properties:
|
|
edgeKinds:
|
|
type: array
|
|
items: { type: string }
|
|
fanoutCap:
|
|
type: integer
|
|
overlays:
|
|
type: array
|
|
items: { type: string }
|
|
required: [sourceIds, targetIds]
|
|
|
|
DiffRequest:
|
|
type: object
|
|
properties:
|
|
snapshotA: { type: string }
|
|
snapshotB: { type: string }
|
|
filters:
|
|
type: object
|
|
additionalProperties: true
|
|
cursor:
|
|
type: string
|
|
description: Resume token from prior diff stream.
|
|
required: [snapshotA, snapshotB]
|
|
|
|
ExportRequest:
|
|
type: object
|
|
properties:
|
|
snapshotId:
|
|
type: string
|
|
queryRef:
|
|
type: string
|
|
formats:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [graphml, csv, ndjson, png, svg]
|
|
includeOverlays:
|
|
type: boolean
|
|
default: false
|
|
anyOf:
|
|
- required: [snapshotId]
|
|
- required: [queryRef]
|
|
required: [formats]
|
|
|
|
ExportJob:
|
|
type: object
|
|
properties:
|
|
jobId: { type: string }
|
|
status: { type: string, enum: [pending, running, succeeded, failed] }
|
|
checksumManifestUrl: { type: string, format: uri }
|
|
downloadUrl: { type: string, format: uri }
|
|
createdAt: { type: string, format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
message: { type: string }
|
|
expiresAt: { type: string, format: date-time, description: "Optional expiry for download links." }
|
|
required: [jobId, status]
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
enum: [GRAPH_BUDGET_EXCEEDED, GRAPH_VALIDATION_FAILED, GRAPH_RATE_LIMITED, GRAPH_UNAUTHORIZED]
|
|
message:
|
|
type: string
|
|
details:
|
|
type: object
|
|
request_id:
|
|
type: string
|
|
required: [error, message]
|
|
|
|
responses:
|
|
ValidationError:
|
|
description: Request failed validation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
Unauthorized:
|
|
description: Missing or invalid credentials
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
BudgetExceeded:
|
|
description: Budget exhausted mid-stream; includes partial cursor details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
headers:
|
|
Retry-After:
|
|
description: Seconds until budgets refresh.
|
|
schema:
|
|
type: integer
|