consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -2,7 +2,7 @@
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).
This is an interim contract until the gateway is aligned to the Orchestrator OpenAPI (`/openapi/jobengine.json` in the Orchestrator service).
## Security / headers
- `Authorization: Bearer <token>` (or `DPoP` where configured)
@@ -15,26 +15,26 @@ This is an interim contract until the gateway is aligned to the Orchestrator Ope
- `X-Stella-Operator-Ticket: <ticket-id>` (optional but recommended)
## Endpoints
- `GET /orchestrator/sources` — list registered job sources (tenant-scoped).
- `GET /jobengine/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`).
- `GET /jobengine/sources/{sourceId}` — source detail.
- `GET /jobengine/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`).
- `GET /jobengine/quotas/{quotaId}` — quota detail (scope: `orch:quota`).
- `POST /jobengine/quotas` — create quota (scope: `orch:quota`).
- `PUT /jobengine/quotas/{quotaId}` — update quota (scope: `orch:quota`).
- `DELETE /jobengine/quotas/{quotaId}` — delete quota (scope: `orch:quota`).
- `POST /jobengine/quotas/{quotaId}/pause` — pause quota (scope: `orch:quota`).
- `POST /jobengine/quotas/{quotaId}/resume` — resume quota (scope: `orch:quota`).
- `GET /jobengine/quotas/summary` — quota/backpressure metrics summary (scope: `orch:quota`).
- `GET /jobengine/jobs/summary` — job summary counts (scope: `orch:read`).
- `GET /jobengine/deadletter/stats` — deadletter stats and top error clustering (scope: `orch:operate`).
- `GET /jobengine/deadletter/summary` — grouped deadletter summary (scope: `orch:operate`).
- `POST /jobengine/deadletter/{entryId}/replay` — replay a deadletter entry (scope: `orch:backfill`).
- `POST /jobengine/deadletter/replay/batch` — replay a set of entry IDs (scope: `orch:backfill`).
- `POST /jobengine/deadletter/replay/pending` — replay pending entries by filter (scope: `orch:backfill`).
- `POST /jobengine/pack-runs/{packRunId}/cancel` — cancel a pack run (scope: `orch:operate`).
- `POST /jobengine/pack-runs/{packRunId}/retry` — retry a pack run (scope: `orch:backfill`).
## Caching & pagination
- `limit` max: `200`.

View File

@@ -4,7 +4,7 @@ Provides a fast “first meaningful signal” for a run (TTFS), with caching and
## Endpoint
`GET /api/v1/orchestrator/runs/{runId}/first-signal`
`GET /api/v1/jobengine/runs/{runId}/first-signal`
### Required headers
- `X-Tenant-Id`: tenant identifier (string)
@@ -58,7 +58,7 @@ Missing/invalid tenant header or invalid parameters.
## Streaming (SSE)
The run stream emits `first_signal` events when the signal changes:
`GET /api/v1/orchestrator/stream/runs/{runId}`
`GET /api/v1/jobengine/stream/runs/{runId}`
Event type:
- `first_signal`

View File

@@ -1,282 +1,126 @@
# Score Replay API Reference
# Score API Reference (Platform)
**Sprint:** SPRINT_3401_0002_0001
**Task:** SCORE-REPLAY-014 - Update scanner API docs with replay endpoint
**Module:** Platform WebService
**Base route:** `/api/v1/score`
> Scope note: this page documents the Platform score API.
> Scanner score replay endpoints are implemented separately at:
> - primary: `/api/v1/scans/{scanId}/score/replay|bundle|verify|history`
> - compatibility aliases: `/api/v1/score/{scanId}/replay|bundle|verify|history`
> See `src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs` and `docs/modules/scanner/architecture.md`.
## Overview
The Score Replay API enables deterministic re-scoring of scans using historical manifests. This is essential for auditing, compliance verification, and investigating how scores change with updated advisory feeds.
The score API exposes deterministic score computation, replay verification, and explanation payloads.
All responses are tenant-scoped and wrapped in the standard Platform envelope.
## Base URL
## Authentication and tenant context
- Bearer token authentication is required.
- Required policies:
`platform.score.read`, `platform.score.evaluate`
- Tenant context is resolved from authenticated context/middleware and must be present.
## Response envelope
Single-item responses return:
```json
{
"tenantId": "tenant-a",
"actorId": "user-1",
"dataAsOf": "2026-02-26T12:00:00Z",
"cached": true,
"cacheTtlSeconds": 300,
"item": { }
}
```
/api/v1/score
```
## Authentication
All endpoints require Bearer token authentication:
```http
Authorization: Bearer <token>
```
Required scope: `scanner:replay:read` for GET, `scanner:replay:write` for POST
## Endpoints
### Replay Score
### `POST /api/v1/score/evaluate`
```http
POST /api/v1/score/replay
```
Computes unified score from provided signal inputs.
Re-scores a scan using the original manifest with an optionally different feed snapshot.
Response highlights:
- `unknowns`: deterministic list of missing signal dimensions when snapshot data is available.
- `proof_ref`: deterministic proof locator (`proof://score/<normalized-digest>`).
#### Request Body
### `GET /api/v1/score/history?cve_id=<id>&purl=<optional>&limit=<optional>`
Returns historical score records for the requested CVE and optional PURL.
### `GET /api/v1/score/{scoreId}`
Returns persisted score by score identifier.
### `GET /api/v1/score/{scoreId}/replay`
Returns replay payload for deterministic verification.
### `POST /api/v1/score/verify`
Verifies replay payload and returns deterministic verification status fields.
Verification details:
- `verified` is computed from deterministic comparison checks (`score_matches`, `digest_matches`) and available signature/Rekor checks.
- `differences` includes field-level mismatch reasons (for example `final_score`, `ews_digest`, `signed_replay_log_dsse`).
- malformed replay envelopes return a deterministic `differences` entry rather than synthetic success.
### `GET /api/v1/score/explain/{digest}`
Returns canonical score explanation contract for a persisted replay digest.
Success payload (`item`) schema:
```json
{
"scanId": "scan-12345678-abcd",
"feedSnapshotHash": "sha256:abc123...",
"policyVersion": "1.0.0",
"dryRun": false
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `scanId` | string | Yes | Original scan ID to replay |
| `feedSnapshotHash` | string | No | Feed snapshot to use (defaults to current) |
| `policyVersion` | string | No | Policy version (defaults to original) |
| `dryRun` | boolean | No | If true, calculates but doesn't persist |
#### Response
```json
{
"replayId": "replay-87654321-dcba",
"originalScanId": "scan-12345678-abcd",
"status": "completed",
"feedSnapshotHash": "sha256:abc123...",
"policyVersion": "1.0.0",
"originalManifestHash": "sha256:def456...",
"replayedManifestHash": "sha256:ghi789...",
"scoreDelta": {
"originalScore": 7.5,
"replayedScore": 6.8,
"delta": -0.7
},
"findingsDelta": {
"added": 2,
"removed": 5,
"rescored": 12,
"unchanged": 45
},
"proofBundleRef": "proofs/replays/replay-87654321/bundle.zip",
"duration": {
"ms": 1250
},
"createdAt": "2025-01-15T10:30:00Z"
}
```
#### Example
```bash
# Replay with latest feed
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"scanId": "scan-12345678-abcd"}' \
"https://scanner.example.com/api/v1/score/replay"
# Replay with specific feed snapshot
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scanId": "scan-12345678-abcd",
"feedSnapshotHash": "sha256:abc123..."
}' \
"https://scanner.example.com/api/v1/score/replay"
# Dry run (preview only)
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scanId": "scan-12345678-abcd",
"dryRun": true
}' \
"https://scanner.example.com/api/v1/score/replay"
```
### Get Replay History
```http
GET /api/v1/score/replays
```
Returns history of score replays.
#### Query Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `scanId` | string | - | Filter by original scan |
| `page` | int | 1 | Page number |
| `pageSize` | int | 50 | Items per page |
#### Response
```json
{
"items": [
"contractVersion": "score.explain.v1",
"digest": "sha256:...",
"scoreId": "score_...",
"finalScore": 62,
"bucket": "Investigate",
"computedAt": "2026-02-26T12:00:00Z",
"deterministicInputHash": "sha256:...",
"replayLink": "/api/v1/score/score_x/replay",
"factors": [
{
"replayId": "replay-87654321-dcba",
"originalScanId": "scan-12345678-abcd",
"triggerType": "manual",
"scoreDelta": -0.7,
"findingsAdded": 2,
"findingsRemoved": 5,
"createdAt": "2025-01-15T10:30:00Z"
"name": "reachability",
"weight": 0.25,
"value": 1.0,
"contribution": 0.25
}
],
"pagination": {
"page": 1,
"pageSize": 50,
"totalItems": 12,
"totalPages": 1
}
"sources": [
{
"sourceType": "score_history",
"sourceRef": "score-history:score_x",
"sourceDigest": "sha256:..."
}
]
}
```
### Get Replay Details
## Deterministic error schema (`/explain/{digest}`)
```http
GET /api/v1/score/replays/{replayId}
```
Returns detailed information about a specific replay.
### Get Scan Manifest
```http
GET /api/v1/scans/{scanId}/manifest
```
Returns the scan manifest containing all input hashes.
#### Response
Error payload:
```json
{
"manifestId": "manifest-12345678",
"scanId": "scan-12345678-abcd",
"manifestHash": "sha256:def456...",
"sbomHash": "sha256:aaa111...",
"rulesHash": "sha256:bbb222...",
"feedHash": "sha256:ccc333...",
"policyHash": "sha256:ddd444...",
"scannerVersion": "1.0.0",
"createdAt": "2025-01-15T10:00:00Z"
"code": "not_found | invalid_input | backend_unavailable",
"message": "deterministic human-readable message",
"digest": "sha256:..."
}
```
### Get Proof Bundle
Status mapping:
```http
GET /api/v1/scans/{scanId}/proof-bundle
```
- `400` -> `invalid_input`
- `404` -> `not_found`
- `503` -> `backend_unavailable`
Downloads the proof bundle (ZIP archive) for a scan.
## Client integration notes
#### Response
Returns `application/zip` with the proof bundle containing:
- `manifest.json` - Signed scan manifest
- `ledger.json` - Proof ledger nodes
- `sbom.json` - Input SBOM (hash-verified)
- `findings.json` - Scored findings
- `signature.dsse` - DSSE envelope
## Scheduled Replay
Scans can be automatically replayed when feed snapshots change.
### Configuration
```yaml
# config/scanner.yaml
score_replay:
enabled: true
schedule: "0 4 * * *" # Daily at 4 AM UTC
max_age_days: 30 # Only replay scans from last 30 days
notify_on_delta: true # Send notification if scores change
delta_threshold: 0.5 # Only notify if delta > threshold
```
### Trigger Types
| Type | Description |
|------|-------------|
| `manual` | User-initiated via API |
| `feed_update` | Triggered by new feed snapshot |
| `policy_change` | Triggered by policy version change |
| `scheduled` | Triggered by scheduled job |
## Determinism Guarantees
Score replay guarantees deterministic results when:
1. **Same manifest hash** - All inputs are identical
2. **Same scanner version** - Scoring algorithm unchanged
3. **Same policy version** - Policy rules unchanged
### Manifest Contents
The manifest captures:
- SBOM content hash
- Rules snapshot hash
- Advisory feed snapshot hash
- Policy configuration hash
- Scanner version
### Verification
```bash
# Verify replay determinism
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/scans/{scanId}/manifest" \
| jq '.manifestHash'
# Compare with replay
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/score/replays/{replayId}" \
| jq '.replayedManifestHash'
```
## Error Responses
| Status | Code | Description |
|--------|------|-------------|
| 400 | `INVALID_SCAN_ID` | Scan ID not found |
| 400 | `INVALID_FEED_SNAPSHOT` | Feed snapshot not found |
| 400 | `MANIFEST_NOT_FOUND` | Scan manifest missing |
| 401 | `UNAUTHORIZED` | Invalid token |
| 403 | `FORBIDDEN` | Insufficient permissions |
| 409 | `REPLAY_IN_PROGRESS` | Replay already running for scan |
| 429 | `RATE_LIMITED` | Too many requests |
## Rate Limits
- POST replay: 10 requests/minute
- GET replays: 100 requests/minute
- GET manifest: 100 requests/minute
## Related Documentation
- [Proof Bundle Format](./proof-bundle-format.md)
- [Scanner Architecture](../modules/scanner/architecture.md)
- [Determinism Requirements](../product/advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md)
- CLI and Web clients must treat `score.explain.v1` as the current canonical contract.
- Clients must not synthesize explanation factors when `404` or `503` is returned.
- `digest` values are normalized to lowercase with explicit algorithm prefix (`sha256:`).