feat: add Reachability Center and Why Drawer components with tests
Some checks failed
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
Docs CI / lint-and-preview (push) Has been cancelled

- 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:
master
2025-12-12 18:50:35 +02:00
parent efaf3cb789
commit 3f3473ee3a
320 changed files with 10635 additions and 3677 deletions

View 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`

View 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`

View 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`

View 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`

View 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`

View 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`

View 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"
}

View 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"}

View 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"
}
}
}
}

View 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"]
}
}

View 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\""
}

View 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."}}

View 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"
}

View 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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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\""
}

View 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"
}
}

View 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"
}

View 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\""
}

View 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"
}

View 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`