feat: add Reachability Center and Why Drawer components with tests
- Implemented ReachabilityCenterComponent for displaying asset reachability status with summary and filtering options. - Added ReachabilityWhyDrawerComponent to show detailed reachability evidence and call paths. - Created unit tests for both components to ensure functionality and correctness. - Updated accessibility test results for the new components.
This commit is contained in:
31
docs/api/gateway/advisories.md
Normal file
31
docs/api/gateway/advisories.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Advisory Gateway Contract (draft v0.1)
|
||||
|
||||
Scope: proxy Advisory surfaces through the Web gateway with tenant scoping, deterministic responses, ETag caching, and offline-friendly pagination.
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- `X-Stella-Trace-Id: <traceId>` (optional; clients SHOULD send one)
|
||||
- Scopes: `advisory:read`
|
||||
|
||||
## Endpoints
|
||||
- `GET /advisories` — list advisories (tenant-scoped).
|
||||
- Query params: `search`, `severity`, `sortBy`, `sortOrder`, `limit`, `continuationToken`
|
||||
- Response: `AdvisoryListResponse` (see sample)
|
||||
- `GET /advisories/{advisoryId}` — advisory detail (tenant-scoped).
|
||||
|
||||
## Caching & pagination
|
||||
- `limit` max: `200`.
|
||||
- Cursor/paging uses `continuationToken` (opaque string).
|
||||
- `ETag` MUST be a stable hash over a sorted payload; clients MAY send `If-None-Match`.
|
||||
- Recommended headers: `Cache-Control: private, max-age=60, stale-if-error=300`.
|
||||
|
||||
## Determinism rules
|
||||
- Ordering: `items` sorted by `(advisoryId asc)` unless `sortBy` is supplied; ties break by `advisoryId`.
|
||||
- Timestamps: ISO-8601 UTC.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/advisories-list.json`
|
||||
- `docs/api/gateway/samples/advisory-detail.json`
|
||||
|
||||
39
docs/api/gateway/advisory-ai.md
Normal file
39
docs/api/gateway/advisory-ai.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Gateway Advisory AI API (Web I)
|
||||
|
||||
**Status:** Interim contract shipped by Web I to unblock SDK/UI work. Align to the authoritative Advisory AI OpenAPI when the gateway routing plugin publishes it.
|
||||
|
||||
## Overview
|
||||
- Gateway routes advisory AI traffic under `/advisory/ai/*`.
|
||||
- Web clients must send tenant + trace headers and should avoid sending raw prompts in telemetry; use prompt hashes instead.
|
||||
|
||||
## Headers
|
||||
- `X-StellaOps-Tenant` (required)
|
||||
- `X-Stella-Project` (optional)
|
||||
- `X-Stella-Trace-Id` (required)
|
||||
- `X-Stella-Request-Id` (required; defaults to trace ID)
|
||||
- `X-StellaOps-AI-Profile` (optional; e.g. `standard`)
|
||||
- `X-StellaOps-Prompt-Hash` (recommended; send a stable hash, not the prompt)
|
||||
|
||||
## Scopes
|
||||
- Read: `advisory-ai:read`
|
||||
- Write/operate: `advisory-ai:write`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `POST /advisory/ai/jobs`
|
||||
Creates a new advisory AI job.
|
||||
|
||||
- Request: `docs/api/gateway/samples/advisory-ai-start-job.json`
|
||||
- Response: `docs/api/gateway/samples/advisory-ai-start-job-response.json`
|
||||
|
||||
### `GET /advisory/ai/jobs/{jobId}`
|
||||
Returns job status and optional results once completed.
|
||||
|
||||
### `POST /advisory/ai/jobs/{jobId}/cancel`
|
||||
Cancels a queued/running job.
|
||||
|
||||
### `GET /advisory/ai/jobs/{jobId}/events`
|
||||
Server-sent events stream (JSON payload per message):
|
||||
|
||||
- Sample NDJSON: `docs/api/gateway/samples/advisory-ai-job-events.ndjson`
|
||||
|
||||
34
docs/api/gateway/exception-events.md
Normal file
34
docs/api/gateway/exception-events.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Exception Events Gateway Contract (draft v0.1)
|
||||
|
||||
Scope: stream exception workflow events (`exception.*`) for Console activity feeds and Notify integrations.
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- Scopes: `exception:read`
|
||||
|
||||
## Endpoint
|
||||
- `GET /exceptions/events` — Server-Sent Events (SSE) stream.
|
||||
- Query params: `tenant`, `traceId`, `projectId` (optional)
|
||||
- Response: `text/event-stream`
|
||||
|
||||
## Event types
|
||||
- `exception.created`
|
||||
- `exception.updated`
|
||||
- `exception.status_changed`
|
||||
- `exception.deleted`
|
||||
|
||||
## Rate limits (proposal)
|
||||
- Max 1 active SSE connection per browser session.
|
||||
- Heartbeat every 30s; server closes idle connections after 60s without reads.
|
||||
- When rate limited: `429` with `Retry-After`.
|
||||
|
||||
## Notify integration
|
||||
Gateways SHOULD forward these events to Notify where configured:
|
||||
- `exception.status_changed` → `notify.event` with severity derived from exception severity and status.
|
||||
- All events include `traceId` for audit correlation.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/exception-events.ndjson`
|
||||
|
||||
60
docs/api/gateway/orchestrator.md
Normal file
60
docs/api/gateway/orchestrator.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Orchestrator Gateway Contract (draft v0.2)
|
||||
|
||||
Scope: expose Orchestrator read + operator control surfaces through the Web gateway (tenant-scoped, deterministic pagination, cache headers) to unblock Console control-plane views.
|
||||
|
||||
This is an interim contract until the gateway is aligned to the Orchestrator OpenAPI (`/openapi/orchestrator.json` in the Orchestrator service).
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- `X-Stella-Trace-Id: <traceId>` (optional; clients SHOULD send one)
|
||||
- Scopes: `orch:read`, `orch:quota`, `orch:operate`, `orch:backfill` (endpoint-specific)
|
||||
- Operator audit headers (required on mutating requests):
|
||||
- `X-Stella-Operator-Reason: <free-text>`
|
||||
- `X-Stella-Operator-Ticket: <ticket-id>` (optional but recommended)
|
||||
|
||||
## Endpoints
|
||||
- `GET /orchestrator/sources` — list registered job sources (tenant-scoped).
|
||||
- Query params: `sourceType`, `enabled`, `limit`, `continuationToken`
|
||||
- `GET /orchestrator/sources/{sourceId}` — source detail.
|
||||
- `GET /orchestrator/quotas` — list quotas (scope: `orch:quota`).
|
||||
- Query params: `jobType`, `paused`, `limit`, `continuationToken`
|
||||
- `GET /orchestrator/quotas/{quotaId}` — quota detail (scope: `orch:quota`).
|
||||
- `POST /orchestrator/quotas` — create quota (scope: `orch:quota`).
|
||||
- `PUT /orchestrator/quotas/{quotaId}` — update quota (scope: `orch:quota`).
|
||||
- `DELETE /orchestrator/quotas/{quotaId}` — delete quota (scope: `orch:quota`).
|
||||
- `POST /orchestrator/quotas/{quotaId}/pause` — pause quota (scope: `orch:quota`).
|
||||
- `POST /orchestrator/quotas/{quotaId}/resume` — resume quota (scope: `orch:quota`).
|
||||
- `GET /orchestrator/quotas/summary` — quota/backpressure metrics summary (scope: `orch:quota`).
|
||||
- `GET /orchestrator/jobs/summary` — job summary counts (scope: `orch:read`).
|
||||
- `GET /orchestrator/deadletter/stats` — deadletter stats and top error clustering (scope: `orch:operate`).
|
||||
- `GET /orchestrator/deadletter/summary` — grouped deadletter summary (scope: `orch:operate`).
|
||||
- `POST /orchestrator/deadletter/{entryId}/replay` — replay a deadletter entry (scope: `orch:backfill`).
|
||||
- `POST /orchestrator/deadletter/replay/batch` — replay a set of entry IDs (scope: `orch:backfill`).
|
||||
- `POST /orchestrator/deadletter/replay/pending` — replay pending entries by filter (scope: `orch:backfill`).
|
||||
- `POST /orchestrator/pack-runs/{packRunId}/cancel` — cancel a pack run (scope: `orch:operate`).
|
||||
- `POST /orchestrator/pack-runs/{packRunId}/retry` — retry a pack run (scope: `orch:backfill`).
|
||||
|
||||
## Caching & pagination
|
||||
- `limit` max: `200`.
|
||||
- Cursor/paging uses `continuationToken` (opaque string).
|
||||
- Recommended headers: `Cache-Control: private, max-age=60, stale-if-error=300`.
|
||||
- `ETag` SHOULD be stable over a sorted payload; clients MAY send `If-None-Match`.
|
||||
|
||||
## Determinism rules
|
||||
- Ordering:
|
||||
- sources: `(name asc, sourceId asc)`
|
||||
- quotas: `(jobType asc, quotaId asc)`
|
||||
- deadletter summary: `(category asc, errorCode asc)`
|
||||
- Timestamps: ISO-8601 UTC.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/orchestrator-sources.json`
|
||||
- `docs/api/gateway/samples/orchestrator-quotas.json`
|
||||
- `docs/api/gateway/samples/orchestrator-quota-summary.json`
|
||||
- `docs/api/gateway/samples/orchestrator-deadletter-stats.json`
|
||||
- `docs/api/gateway/samples/orchestrator-deadletter-summary.json`
|
||||
- `docs/api/gateway/samples/orchestrator-deadletter-replay.json`
|
||||
- `docs/api/gateway/samples/orchestrator-packrun-cancel.json`
|
||||
- `docs/api/gateway/samples/orchestrator-packrun-retry.json`
|
||||
46
docs/api/gateway/policy-evidence.md
Normal file
46
docs/api/gateway/policy-evidence.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Policy + Evidence Composition Contract (draft v0.1)
|
||||
|
||||
Scope: provide a single Console-friendly response that combines policy evaluation output with related advisory and VEX evidence linksets for the same findings/component(s).
|
||||
|
||||
This contract is intended to reduce UI round-trips by composing existing gateway surfaces:
|
||||
- Policy + Exceptions (`POST /policy/effective`)
|
||||
- Advisories (`GET /advisories`)
|
||||
- VEX Evidence (`GET /vex/statements`)
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- `X-Stella-Trace-Id: <traceId>` (optional; clients SHOULD send one)
|
||||
- Scopes:
|
||||
- `policy:read` AND `exception:read`
|
||||
- `advisory:read`
|
||||
- `vex:read`
|
||||
|
||||
## Endpoint
|
||||
- `POST /policy/evidence/component` — compose policy result + evidence linksets for the supplied findings.
|
||||
|
||||
## Request/response notes
|
||||
- Request shape reuses `PolicyFindingRef` fields from the Policy + Exceptions contract (`docs/api/gateway/policy-exceptions.md`).
|
||||
- Response includes:
|
||||
- `policy` (Policy effective view; deterministic ordering by `findingId`)
|
||||
- `advisories` (summaries; deterministic ordering by `advisoryId`)
|
||||
- `vexStatements` (summaries; deterministic ordering by `statementId`)
|
||||
- `linksets` mapping each `findingId` to the related `advisoryIds` and `vexStatementIds`
|
||||
- Implementations MUST NOT invent verdicts; this is a pure composition surface.
|
||||
|
||||
## Caching & limits
|
||||
- Composition responses SHOULD be cacheable for a short TTL when inputs are identical.
|
||||
- Recommended headers: `Cache-Control: private, max-age=30, stale-if-error=120`.
|
||||
- Recommended caps (UI/Gateway): findings max `500`, advisories max `200`, VEX statements max `200`.
|
||||
|
||||
## Determinism rules
|
||||
- `findings` sorted by `(findingId asc)` before evaluation.
|
||||
- `policy.items` sorted by `(findingId asc)`.
|
||||
- `advisories` sorted by `(advisoryId asc)`.
|
||||
- `vexStatements` sorted by `(statementId asc)`.
|
||||
- Timestamps: ISO-8601 UTC.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/policy-evidence-component.json`
|
||||
|
||||
26
docs/api/gateway/policy-exceptions.md
Normal file
26
docs/api/gateway/policy-exceptions.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Policy + Exceptions Gateway Contract (draft v0.1)
|
||||
|
||||
Scope: expose policy evaluation results that include exception metadata, plus simulation endpoints that accept exception overrides for what-if analysis.
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- `X-Stella-Trace-Id: <traceId>` (optional; clients SHOULD send one)
|
||||
- Scopes:
|
||||
- `POST /policy/effective`: `policy:read` AND `exception:read`
|
||||
- `POST /policy/simulate`: `policy:simulate` AND `exception:read`
|
||||
|
||||
## Endpoints
|
||||
- `POST /policy/effective` — deterministic effective policy view over a list of findings.
|
||||
- `POST /policy/simulate` — simulate policy result changes with exception overrides.
|
||||
|
||||
## Request/response notes
|
||||
- Requests MUST remain deterministic: stable ordering, ISO-8601 UTC timestamps only.
|
||||
- Pagination uses `limit` (max `200`) and `continuationToken` (opaque string).
|
||||
- Exception metadata SHOULD reuse the Exception schema (`docs/api/console/exception-schema.md`) but MAY omit large fields like audit trails.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/policy-effective-sample.json`
|
||||
- `docs/api/gateway/samples/policy-simulate-sample.json`
|
||||
|
||||
31
docs/api/gateway/samples/advisories-list.json
Normal file
31
docs/api/gateway/samples/advisories-list.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"advisoryId": "CVE-2024-12345",
|
||||
"source": "cve",
|
||||
"title": "Example advisory for offline demo",
|
||||
"severity": "high",
|
||||
"publishedAt": "2025-12-01T00:00:00Z",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"cveIds": ["CVE-2024-12345"],
|
||||
"affectedPurls": ["pkg:npm/example@1.2.3"],
|
||||
"etag": "\"adv-CVE-2024-12345-v1\""
|
||||
},
|
||||
{
|
||||
"advisoryId": "GHSA-aaaa-bbbb-cccc",
|
||||
"source": "ghsa",
|
||||
"title": "Example GHSA advisory",
|
||||
"severity": "critical",
|
||||
"publishedAt": "2025-11-20T00:00:00Z",
|
||||
"updatedAt": "2025-12-05T00:00:00Z",
|
||||
"cveIds": ["CVE-2025-00001"],
|
||||
"affectedPurls": ["pkg:maven/com.example/demo@0.9.0"],
|
||||
"etag": "\"adv-GHSA-aaaa-bbbb-cccc-v1\""
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"continuationToken": null,
|
||||
"etag": "\"advisories-sample-v1\"",
|
||||
"traceId": "trace-sample-1"
|
||||
}
|
||||
|
||||
6
docs/api/gateway/samples/advisory-ai-job-events.ndjson
Normal file
6
docs/api/gateway/samples/advisory-ai-job-events.ndjson
Normal file
@@ -0,0 +1,6 @@
|
||||
{"jobId":"aiai-job-0001","kind":"status","at":"2025-12-03T00:00:00Z","status":"queued","progressPercent":0}
|
||||
{"jobId":"aiai-job-0001","kind":"status","at":"2025-12-03T00:00:01Z","status":"running","progressPercent":10}
|
||||
{"jobId":"aiai-job-0001","kind":"chunk","at":"2025-12-03T00:00:02Z","chunkIndex":0,"chunk":"Chunk 1: Input normalized."}
|
||||
{"jobId":"aiai-job-0001","kind":"chunk","at":"2025-12-03T00:00:03Z","chunkIndex":1,"chunk":"Chunk 2: Evidence gathered."}
|
||||
{"jobId":"aiai-job-0001","kind":"done","at":"2025-12-03T00:00:04Z","status":"completed","message":"Done"}
|
||||
|
||||
26
docs/api/gateway/samples/advisory-ai-start-job-response.json
Normal file
26
docs/api/gateway/samples/advisory-ai-start-job-response.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"jobId": "aiai-job-0001",
|
||||
"status": "queued",
|
||||
"traceId": "trace-fixture-aiai",
|
||||
"createdAt": "2025-12-03T00:00:00Z",
|
||||
"guardrail": {
|
||||
"blocked": false,
|
||||
"state": "ok",
|
||||
"violations": [],
|
||||
"metadata": {
|
||||
"blockedPhraseFile": "configs/guardrails/blocked-phrases.json",
|
||||
"blocked_phrase_count": 0,
|
||||
"promptLength": 0,
|
||||
"planFromCache": false,
|
||||
"promptHash": "sha256:0000000000000000",
|
||||
"telemetryCounters": {
|
||||
"advisory_ai_guardrail_blocks_total": 0,
|
||||
"advisory_ai_chunk_cache_hits_total": 0
|
||||
},
|
||||
"links": {
|
||||
"logs": "/audit/advisory-ai/runs/2025-12-03T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
docs/api/gateway/samples/advisory-ai-start-job.json
Normal file
14
docs/api/gateway/samples/advisory-ai-start-job.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"profile": "standard",
|
||||
"prompt": "<redacted>",
|
||||
"maxTokens": 2048,
|
||||
"dryRun": false,
|
||||
"context": {
|
||||
"sbomDigests": [
|
||||
"sha256:6c81f2bbd8bd7336f197f3f68fba2f76d7287dd1a5e2a0f0e9f14f23f3c2f917"
|
||||
],
|
||||
"vulnIds": ["CVE-2021-44228"],
|
||||
"purls": ["pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1"]
|
||||
}
|
||||
}
|
||||
|
||||
20
docs/api/gateway/samples/advisory-detail.json
Normal file
20
docs/api/gateway/samples/advisory-detail.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"advisoryId": "CVE-2024-12345",
|
||||
"source": "cve",
|
||||
"title": "Example advisory for offline demo",
|
||||
"severity": "high",
|
||||
"publishedAt": "2025-12-01T00:00:00Z",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"cveIds": ["CVE-2024-12345"],
|
||||
"affectedPurls": ["pkg:npm/example@1.2.3"],
|
||||
"description": "Deterministic sample advisory payload for Console and SDK development.",
|
||||
"references": [
|
||||
{
|
||||
"label": "Vendor bulletin",
|
||||
"url": "https://example.invalid/advisories/CVE-2024-12345"
|
||||
}
|
||||
],
|
||||
"advisoryUrl": "https://example.invalid/advisories/CVE-2024-12345",
|
||||
"etag": "\"adv-CVE-2024-12345-v1\""
|
||||
}
|
||||
|
||||
3
docs/api/gateway/samples/exception-events.ndjson
Normal file
3
docs/api/gateway/samples/exception-events.ndjson
Normal file
@@ -0,0 +1,3 @@
|
||||
{"type":"exception.created","tenantId":"tenant-default","exceptionId":"exc-001","timestamp":"2025-12-10T00:00:00Z","actor":"user:demo","traceId":"trace-evt-1","metadata":{"message":"Draft created"}}
|
||||
{"type":"exception.status_changed","tenantId":"tenant-default","exceptionId":"exc-001","timestamp":"2025-12-10T00:05:00Z","actor":"user:approver","previousStatus":"pending_review","newStatus":"approved","traceId":"trace-evt-2","metadata":{"comment":"Approved for demo tenant."}}
|
||||
|
||||
24
docs/api/gateway/samples/orchestrator-deadletter-replay.json
Normal file
24
docs/api/gateway/samples/orchestrator-deadletter-replay.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"success": true,
|
||||
"newJobId": "dddddddd-dddd-dddd-dddd-dddddddddddd",
|
||||
"errorMessage": null,
|
||||
"updatedEntry": {
|
||||
"entryId": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
|
||||
"originalJobId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
|
||||
"runId": null,
|
||||
"sourceId": null,
|
||||
"jobType": "export",
|
||||
"status": "Replayed",
|
||||
"errorCode": "quota_exceeded",
|
||||
"failureReason": "Quota exceeded for job type export.",
|
||||
"remediationHint": "Increase quota or retry later.",
|
||||
"category": "quota_exceeded",
|
||||
"isRetryable": true,
|
||||
"canReplay": false,
|
||||
"failedAt": "2025-12-10T00:00:00Z",
|
||||
"createdAt": "2025-12-10T00:00:00Z",
|
||||
"expiresAt": "2026-01-10T00:00:00Z",
|
||||
"resolvedAt": null
|
||||
},
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
24
docs/api/gateway/samples/orchestrator-deadletter-stats.json
Normal file
24
docs/api/gateway/samples/orchestrator-deadletter-stats.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"totalEntries": 4,
|
||||
"pendingEntries": 2,
|
||||
"replayingEntries": 0,
|
||||
"replayedEntries": 1,
|
||||
"resolvedEntries": 1,
|
||||
"exhaustedEntries": 0,
|
||||
"expiredEntries": 0,
|
||||
"retryableEntries": 3,
|
||||
"byCategory": {
|
||||
"quota_exceeded": 2,
|
||||
"upstream_error": 2
|
||||
},
|
||||
"topErrorCodes": {
|
||||
"invalid_payload": 1,
|
||||
"quota_exceeded": 2,
|
||||
"upstream_timeout": 1
|
||||
},
|
||||
"topJobTypes": {
|
||||
"export": 2,
|
||||
"pack-run": 2
|
||||
},
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"errorCode": "quota_exceeded",
|
||||
"category": "quota_exceeded",
|
||||
"entryCount": 2,
|
||||
"retryableCount": 2,
|
||||
"oldestEntry": "2025-12-10T00:00:00Z",
|
||||
"sampleReason": "Quota exceeded for job type export."
|
||||
},
|
||||
{
|
||||
"errorCode": "upstream_timeout",
|
||||
"category": "upstream_error",
|
||||
"entryCount": 1,
|
||||
"retryableCount": 1,
|
||||
"oldestEntry": "2025-12-11T00:00:00Z",
|
||||
"sampleReason": "Upstream service timeout."
|
||||
}
|
||||
],
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"packRunId": "99999999-9999-9999-9999-999999999999",
|
||||
"status": "canceled",
|
||||
"reason": "Operator requested cancellation.",
|
||||
"canceledAt": "2025-12-12T00:00:00Z",
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
7
docs/api/gateway/samples/orchestrator-packrun-retry.json
Normal file
7
docs/api/gateway/samples/orchestrator-packrun-retry.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"originalPackRunId": "99999999-9999-9999-9999-999999999999",
|
||||
"newPackRunId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"status": "scheduled",
|
||||
"createdAt": "2025-12-12T00:00:00Z",
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
25
docs/api/gateway/samples/orchestrator-quota-summary.json
Normal file
25
docs/api/gateway/samples/orchestrator-quota-summary.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"totalQuotas": 2,
|
||||
"pausedQuotas": 1,
|
||||
"averageTokenUtilization": 0.5625,
|
||||
"averageConcurrencyUtilization": 0.6,
|
||||
"quotas": [
|
||||
{
|
||||
"quotaId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
||||
"jobType": "export",
|
||||
"tokenUtilization": 0.75,
|
||||
"concurrencyUtilization": 1,
|
||||
"hourlyUtilization": 0.9667,
|
||||
"paused": true
|
||||
},
|
||||
{
|
||||
"quotaId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"jobType": "pack-run",
|
||||
"tokenUtilization": 0.375,
|
||||
"concurrencyUtilization": 0.2,
|
||||
"hourlyUtilization": 0.09,
|
||||
"paused": false
|
||||
}
|
||||
],
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
44
docs/api/gateway/samples/orchestrator-quotas.json
Normal file
44
docs/api/gateway/samples/orchestrator-quotas.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"quotaId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"tenantId": "tenant-default",
|
||||
"jobType": "pack-run",
|
||||
"maxActive": 10,
|
||||
"maxPerHour": 200,
|
||||
"burstCapacity": 20,
|
||||
"refillRate": 1,
|
||||
"currentTokens": 12.5,
|
||||
"currentActive": 2,
|
||||
"currentHourCount": 18,
|
||||
"paused": false,
|
||||
"pauseReason": null,
|
||||
"quotaTicket": null,
|
||||
"createdAt": "2025-11-01T00:00:00Z",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"updatedBy": "user:demo"
|
||||
},
|
||||
{
|
||||
"quotaId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
||||
"tenantId": "tenant-default",
|
||||
"jobType": "export",
|
||||
"maxActive": 3,
|
||||
"maxPerHour": 60,
|
||||
"burstCapacity": 6,
|
||||
"refillRate": 0.5,
|
||||
"currentTokens": 1.5,
|
||||
"currentActive": 3,
|
||||
"currentHourCount": 58,
|
||||
"paused": true,
|
||||
"pauseReason": "Maintenance window",
|
||||
"quotaTicket": "OPS-1234",
|
||||
"createdAt": "2025-11-15T00:00:00Z",
|
||||
"updatedAt": "2025-12-11T00:00:00Z",
|
||||
"updatedBy": "user:ops"
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"continuationToken": null,
|
||||
"etag": "\"orch-quotas-v1\"",
|
||||
"traceId": "trace-sample"
|
||||
}
|
||||
33
docs/api/gateway/samples/orchestrator-sources.json
Normal file
33
docs/api/gateway/samples/orchestrator-sources.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"sourceId": "11111111-1111-1111-1111-111111111111",
|
||||
"name": "concelier-ingest",
|
||||
"sourceType": "concelier",
|
||||
"enabled": true,
|
||||
"paused": false,
|
||||
"pauseReason": null,
|
||||
"pauseTicket": null,
|
||||
"createdAt": "2025-11-01T00:00:00Z",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"updatedBy": "user:demo"
|
||||
},
|
||||
{
|
||||
"sourceId": "22222222-2222-2222-2222-222222222222",
|
||||
"name": "export-center",
|
||||
"sourceType": "export",
|
||||
"enabled": true,
|
||||
"paused": true,
|
||||
"pauseReason": "Maintenance window",
|
||||
"pauseTicket": "OPS-1234",
|
||||
"createdAt": "2025-11-15T00:00:00Z",
|
||||
"updatedAt": "2025-12-11T00:00:00Z",
|
||||
"updatedBy": "user:ops"
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"continuationToken": null,
|
||||
"etag": "\"orch-sources-sample-1\"",
|
||||
"traceId": "trace-sample-orch-1"
|
||||
}
|
||||
|
||||
43
docs/api/gateway/samples/policy-effective-sample.json
Normal file
43
docs/api/gateway/samples/policy-effective-sample.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"policyVersion": "sha256:policy-demo",
|
||||
"items": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "affected",
|
||||
"severityBand": "High",
|
||||
"severityScore": 7.5,
|
||||
"exceptions": [
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"exceptionId": "exc-001",
|
||||
"tenantId": "tenant-default",
|
||||
"name": "temporary-risk-acceptance",
|
||||
"displayName": "Temporary Risk Acceptance",
|
||||
"description": "Deterministic stub exception used for policy/effective and policy/simulate demos.",
|
||||
"status": "approved",
|
||||
"severity": "high",
|
||||
"scope": {
|
||||
"type": "component",
|
||||
"componentPurls": ["pkg:npm/example@1.2.3"],
|
||||
"vulnIds": ["CVE-2024-12345"]
|
||||
},
|
||||
"justification": {
|
||||
"template": "risk-accepted",
|
||||
"text": "Approved for demo tenant while remediation is planned."
|
||||
},
|
||||
"timebox": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-31T23:59:59Z"
|
||||
},
|
||||
"createdBy": "user:demo",
|
||||
"createdAt": "2025-12-01T00:00:00Z",
|
||||
"updatedBy": "user:demo",
|
||||
"updatedAt": "2025-12-10T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"continuationToken": null,
|
||||
"traceId": "trace-sample-4"
|
||||
}
|
||||
|
||||
86
docs/api/gateway/samples/policy-evidence-component.json
Normal file
86
docs/api/gateway/samples/policy-evidence-component.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"findings": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"componentPurl": "pkg:npm/example@1.2.3",
|
||||
"assetId": "asset::registry.local/ops/auth"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"policyVersion": "sha256:policy-demo",
|
||||
"items": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "affected",
|
||||
"severityBand": "High",
|
||||
"severityScore": 7.5,
|
||||
"exceptions": [
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"exceptionId": "exc-001",
|
||||
"tenantId": "tenant-default",
|
||||
"name": "temporary-risk-acceptance",
|
||||
"displayName": "Temporary Risk Acceptance",
|
||||
"status": "approved",
|
||||
"severity": "high",
|
||||
"scope": {
|
||||
"type": "component",
|
||||
"componentPurls": ["pkg:npm/example@1.2.3"],
|
||||
"vulnIds": ["CVE-2024-12345"]
|
||||
},
|
||||
"justification": {
|
||||
"template": "risk-accepted",
|
||||
"text": "Approved for demo tenant while remediation is planned."
|
||||
},
|
||||
"timebox": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-31T23:59:59Z"
|
||||
},
|
||||
"createdBy": "user:demo",
|
||||
"createdAt": "2025-12-01T00:00:00Z",
|
||||
"updatedBy": "user:demo",
|
||||
"updatedAt": "2025-12-10T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"continuationToken": null,
|
||||
"traceId": "trace-sample-6"
|
||||
},
|
||||
"advisories": [
|
||||
{
|
||||
"advisoryId": "CVE-2024-12345",
|
||||
"source": "cve",
|
||||
"title": "Example advisory for offline demo",
|
||||
"severity": "high",
|
||||
"publishedAt": "2025-12-01T00:00:00Z",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"cveIds": ["CVE-2024-12345"],
|
||||
"affectedPurls": ["pkg:npm/example@1.2.3"],
|
||||
"etag": "\"adv-CVE-2024-12345-v1\""
|
||||
}
|
||||
],
|
||||
"vexStatements": [
|
||||
{
|
||||
"statementId": "vex::tenant-default::CVE-2024-12345::001",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"productId": "asset::registry.local/ops/auth",
|
||||
"status": "not_affected",
|
||||
"justification": "Component not present in runtime image.",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"etag": "\"vex-001-v1\""
|
||||
}
|
||||
],
|
||||
"linksets": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"advisoryIds": ["CVE-2024-12345"],
|
||||
"vexStatementIds": ["vex::tenant-default::CVE-2024-12345::001"]
|
||||
}
|
||||
],
|
||||
"traceId": "trace-sample-6",
|
||||
"etag": "\"policy-evidence-sample-1\""
|
||||
}
|
||||
|
||||
75
docs/api/gateway/samples/policy-simulate-sample.json
Normal file
75
docs/api/gateway/samples/policy-simulate-sample.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"request": {
|
||||
"findings": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"componentPurl": "pkg:npm/example@1.2.3",
|
||||
"assetId": "asset::registry.local/ops/auth"
|
||||
}
|
||||
],
|
||||
"exceptionOverrides": [
|
||||
{
|
||||
"mode": "apply",
|
||||
"exception": {
|
||||
"name": "temporary-risk-acceptance",
|
||||
"severity": "high",
|
||||
"status": "draft",
|
||||
"scope": {
|
||||
"type": "component",
|
||||
"componentPurls": ["pkg:npm/example@1.2.3"],
|
||||
"vulnIds": ["CVE-2024-12345"]
|
||||
},
|
||||
"justification": {
|
||||
"template": "risk-accepted",
|
||||
"text": "What-if analysis for planned remediation."
|
||||
},
|
||||
"timebox": {
|
||||
"startDate": "2025-12-12T00:00:00Z",
|
||||
"endDate": "2025-12-19T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"response": {
|
||||
"policyVersion": "sha256:policy-demo",
|
||||
"items": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "quieted",
|
||||
"severityBand": "None",
|
||||
"severityScore": 0,
|
||||
"exceptions": [
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"exceptionId": "exc-001",
|
||||
"tenantId": "tenant-default",
|
||||
"name": "temporary-risk-acceptance",
|
||||
"displayName": "Temporary Risk Acceptance",
|
||||
"status": "approved",
|
||||
"severity": "high",
|
||||
"scope": {
|
||||
"type": "component",
|
||||
"componentPurls": ["pkg:npm/example@1.2.3"],
|
||||
"vulnIds": ["CVE-2024-12345"]
|
||||
},
|
||||
"justification": {
|
||||
"template": "risk-accepted",
|
||||
"text": "Approved for demo tenant while remediation is planned."
|
||||
},
|
||||
"timebox": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-31T23:59:59Z"
|
||||
},
|
||||
"createdBy": "user:demo",
|
||||
"createdAt": "2025-12-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"continuationToken": null,
|
||||
"traceId": "trace-sample-5"
|
||||
}
|
||||
}
|
||||
|
||||
16
docs/api/gateway/samples/vex-evidence-list.json
Normal file
16
docs/api/gateway/samples/vex-evidence-list.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"statementId": "vex::tenant-default::CVE-2024-12345::001",
|
||||
"items": [
|
||||
{
|
||||
"evidenceId": "evidence::001",
|
||||
"kind": "document",
|
||||
"url": "https://example.invalid/evidence/001.json?sig=mock",
|
||||
"sha256": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"expiresAt": "2025-12-31T23:59:59Z"
|
||||
}
|
||||
],
|
||||
"count": 1,
|
||||
"etag": "\"vex-evidence-sample-v1\"",
|
||||
"traceId": "trace-sample-3"
|
||||
}
|
||||
|
||||
18
docs/api/gateway/samples/vex-statement-detail.json
Normal file
18
docs/api/gateway/samples/vex-statement-detail.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"statementId": "vex::tenant-default::CVE-2024-12345::001",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"productId": "asset::registry.local/ops/auth",
|
||||
"status": "not_affected",
|
||||
"justification": "Component not present in runtime image.",
|
||||
"issuer": "vendor:example",
|
||||
"affectedPurls": ["pkg:npm/example@1.2.3"],
|
||||
"references": [
|
||||
{
|
||||
"label": "Analysis report",
|
||||
"url": "https://example.invalid/vex/report/001"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"etag": "\"vex-001-v1\""
|
||||
}
|
||||
|
||||
18
docs/api/gateway/samples/vex-statements-list.json
Normal file
18
docs/api/gateway/samples/vex-statements-list.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"statementId": "vex::tenant-default::CVE-2024-12345::001",
|
||||
"vulnId": "CVE-2024-12345",
|
||||
"productId": "asset::registry.local/ops/auth",
|
||||
"status": "not_affected",
|
||||
"justification": "Component not present in runtime image.",
|
||||
"updatedAt": "2025-12-10T00:00:00Z",
|
||||
"etag": "\"vex-001-v1\""
|
||||
}
|
||||
],
|
||||
"count": 1,
|
||||
"continuationToken": null,
|
||||
"etag": "\"vex-statements-sample-v1\"",
|
||||
"traceId": "trace-sample-2"
|
||||
}
|
||||
|
||||
43
docs/api/gateway/vex-evidence.md
Normal file
43
docs/api/gateway/vex-evidence.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# VEX Evidence Gateway Contract (draft v0.1)
|
||||
|
||||
Scope: expose read-only VEX statement and evidence routes through the Web gateway with tenant scoping, deterministic ordering, and export helpers for offline bundles.
|
||||
|
||||
## Security / headers
|
||||
- `Authorization: Bearer <token>` (or `DPoP` where configured)
|
||||
- `X-StellaOps-Tenant: <tenantId>` (required)
|
||||
- `X-Stella-Project: <projectId>` (optional)
|
||||
- `X-Stella-Trace-Id: <traceId>` (optional; clients SHOULD send one)
|
||||
- Scopes:
|
||||
- `vex:read` for list/detail/evidence
|
||||
- `vex:export` for export handlers
|
||||
|
||||
## Endpoints
|
||||
- `GET /vex/statements` — list statements (tenant-scoped).
|
||||
- Query params: `vulnId`, `status`, `search`, `limit`, `continuationToken`
|
||||
- `GET /vex/statements/{statementId}` — statement detail.
|
||||
- `GET /vex/statements/{statementId}/evidence` — evidence links (signed URLs, optional DSSE).
|
||||
- `GET /vex/statements/{statementId}/export?format=json|ndjson|spdx|cyclonedx` — export helper returning a signed URL and checksums.
|
||||
|
||||
## Error codes
|
||||
Gateway maps upstream/validation errors to stable codes for SDK/UI:
|
||||
- `ERR_AGG_BAD_REQUEST` (400)
|
||||
- `ERR_AGG_UNAUTHORIZED` (401/403)
|
||||
- `ERR_AGG_NOT_FOUND` (404)
|
||||
- `ERR_AGG_RATE_LIMIT` (429)
|
||||
- `ERR_AGG_UPSTREAM` (5xx)
|
||||
- `ERR_AGG_UNKNOWN` (fallback)
|
||||
|
||||
## Caching & pagination
|
||||
- `limit` max: `200`.
|
||||
- Cursor/paging uses `continuationToken` (opaque string).
|
||||
- `ETag` MUST be stable over sorted payload; clients MAY send `If-None-Match`.
|
||||
|
||||
## Determinism rules
|
||||
- Ordering: list responses sorted by `(statementId asc)` unless specified otherwise; ties break by `statementId`.
|
||||
- Timestamps: ISO-8601 UTC.
|
||||
|
||||
## Samples
|
||||
- `docs/api/gateway/samples/vex-statements-list.json`
|
||||
- `docs/api/gateway/samples/vex-statement-detail.json`
|
||||
- `docs/api/gateway/samples/vex-evidence-list.json`
|
||||
|
||||
Reference in New Issue
Block a user