Restructure solution layout by module
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -1,121 +1,121 @@ | ||||
| # Scanner Orchestrator Events (ORCH-SVC-38-101) | ||||
|  | ||||
| Last updated: 2025-10-26 | ||||
|  | ||||
| The Notifications Studio initiative (NOTIFY-SVC-38-001) and orchestrator backlog (ORCH-SVC-38-101) standardise how platform services emit lifecycle events. This document describes the Scanner WebService contract for the new **orchestrator envelopes** (`scanner.event.*`) and how they supersede the legacy Redis-backed `scanner.report.ready` / `scanner.scan.completed` events. | ||||
|  | ||||
| ## 1. Envelope overview | ||||
|  | ||||
| Orchestrator events share a deterministic JSON envelope: | ||||
|  | ||||
| | Field | Type | Notes | | ||||
| |-------|------|-------| | ||||
| | `eventId` | `uuid` | Globally unique identifier generated per occurrence. | | ||||
| | `kind` | `string` | Event identifier; Scanner emits `scanner.event.report.ready` and `scanner.event.scan.completed`. | | ||||
| | `version` | `integer` | Schema version. Initial release uses `1`. | | ||||
| | `tenant` | `string` | Tenant that owns the scan/report. Mirrors Authority claims. | | ||||
| | `occurredAt` | `date-time` | UTC instant when the underlying state transition happened (e.g., report persisted). | | ||||
| | `recordedAt` | `date-time` | UTC instant when the event was durably written. Optional but recommended. | | ||||
| | `source` | `string` | Producer identifier (`scanner.webservice`). | | ||||
| | `idempotencyKey` | `string` | Deterministic key for duplicate suppression (see §4). | | ||||
| | `correlationId` | `string` | Maps back to the API request or scan identifier. | | ||||
| | `traceId` / `spanId` | `string` | W3C trace context propagated into downstream telemetry. | | ||||
| | `scope` | `object` | Describes the affected artefact. Requires `repo` and `digest`; optional `namespace`, `component`, `image`. | | ||||
| | `attributes` | `object` | Flat string map for frequently queried metadata (e.g., policy revision). | | ||||
| | `payload` | `object` | Event-specific body (see §2). | | ||||
|  | ||||
| Canonical schemas live under `docs/events/scanner.event.*@1.json`. Samples that round-trip through `NotifyCanonicalJsonSerializer` are stored in `docs/events/samples/`. | ||||
|  | ||||
| ## 2. Event kinds and payloads | ||||
|  | ||||
| ### 2.1 `scanner.event.report.ready` | ||||
|  | ||||
| Emitted once a signed report is persisted and attested. Payload highlights: | ||||
|  | ||||
| - `reportId` / `scanId` — identifiers for the persisted report and originating scan. Until Scan IDs are surfaced by the API, `scanId` mirrors `reportId` so downstream correlators can stabilise on a single key. | ||||
| - **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict` — pre-sorted for deterministic routing. | ||||
| - **Links:** | ||||
|   - `ui` → `/ui/reports/{reportId}` on the current host. | ||||
|   - `report` → `{apiBasePath}/{reportsSegment}/{reportId}` (defaults to `/api/v1/reports/{reportId}`). | ||||
|   - `policy` → `{apiBasePath}/{policySegment}/revisions/{revisionId}` when a revision is present. | ||||
|   - `attestation` → `/ui/attestations/{reportId}` when a DSSE envelope is included. | ||||
| - `imageDigest` — OCI image digest associated with the analysis. | ||||
| - `generatedAt` — report generation timestamp (ISO-8601 UTC). | ||||
| - `verdict` — `pass`, `warn`, or `fail` after policy evaluation. | ||||
| - `summary` — blocked/warned/ignored/quieted counters (all non-negative integers). | ||||
| - `delta` — newly critical/high counts and optional `kev` array. | ||||
| - `quietedFindingCount` — mirrors `summary.quieted`. | ||||
| - `policy` — revision metadata (`digest`, `revisionId`) surfaced for routing. | ||||
| - `links` — UI/report/policy URLs suitable for operators. | ||||
| - `dsse` — embedded DSSE envelope (payload, type, signature list). | ||||
| - `report` — canonical report document; identical to the DSSE payload. | ||||
|  | ||||
| Schema: `docs/events/scanner.event.report.ready@1.json`   | ||||
| Sample: `docs/events/samples/scanner.event.report.ready@1.sample.json` | ||||
|  | ||||
| ### 2.2 `scanner.event.scan.completed` | ||||
|  | ||||
| Emitted after scan execution finishes (success or policy failure). Payload highlights: | ||||
|  | ||||
| - `reportId` / `scanId` / `imageDigest` — identifiers mirroring the report-ready event. As with the report-ready payload, `scanId` currently mirrors `reportId` as a temporary shim. | ||||
| - **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict`. | ||||
| - **Links:** same as above (`ui`, `report`, `policy`) with `attestation` populated when DSSE metadata exists. | ||||
| - `verdict`, `summary`, `delta`, `policy` — same semantics as above. | ||||
| - `findings` — array of surfaced findings with `id`, `severity`, optional `cve`, `purl`, and `reachability`. | ||||
| - `links`, `dsse`, `report` — same structure as §2.1 (allows Notifier to reuse signatures). | ||||
|  | ||||
| Schema: `docs/events/scanner.event.scan.completed@1.json`   | ||||
| Sample: `docs/events/samples/scanner.event.scan.completed@1.sample.json` | ||||
|  | ||||
| ### 2.3 Relationship to legacy events | ||||
|  | ||||
| | Legacy Redis event | Replacement orchestrator event | Notes | | ||||
| |--------------------|-------------------------------|-------| | ||||
| | `scanner.report.ready` | `scanner.event.report.ready` | Adds versioning, idempotency, trace context. Payload is a superset of the legacy fields. | | ||||
| | `scanner.scan.completed` | `scanner.event.scan.completed` | Same data plus explicit scan identifiers and orchestrator metadata. | | ||||
|  | ||||
| Legacy schemas remain for backwards-compatibility during migration, but new integrations **must** target the orchestrator variants. | ||||
|  | ||||
| ## 3. Deterministic serialization | ||||
|  | ||||
| - Producers must serialise events using `NotifyCanonicalJsonSerializer` to guarantee consistent key ordering and whitespace. | ||||
| - Timestamps (`occurredAt`, `recordedAt`, `payload.generatedAt`) use `DateTimeOffset.UtcDateTime.ToString("O")`. | ||||
| - Payload arrays (`delta.kev`, `findings`) should be pre-sorted (e.g., alphabetical CVE order) so hash-based consumers remain stable. | ||||
| - Optional fields are omitted rather than emitted as `null`. | ||||
|  | ||||
| ## 4. Idempotency and correlation | ||||
|  | ||||
| Idempotency keys dedupe repeated publishes and align with the orchestrator’s outbox pattern: | ||||
|  | ||||
| | Event kind | Idempotency key template | | ||||
| |------------|-------------------------| | ||||
| | `scanner.event.report.ready` | `scanner.event.report.ready:<tenant>:<reportId>` | | ||||
| | `scanner.event.scan.completed` | `scanner.event.scan.completed:<tenant>:<scanId>` | | ||||
|  | ||||
| Keys are ASCII lowercase; components should be trimmed and validated before concatenation. Retries must reuse the same key. | ||||
|  | ||||
| `correlationId` should match the scan identifier that appears in REST responses (`scanId`). Re-using the same value across the pair of events allows Notifier and orchestrator analytics to stitch lifecycle data together. | ||||
|  | ||||
| ## 5. Versioning and evolution | ||||
|  | ||||
| - Increment the `version` field and the `@<version>` suffix for **breaking** changes (field removals, type changes, semantic shifts). | ||||
| - Additive optional fields may remain within version 1; update the JSON schema and samples accordingly. | ||||
| - When introducing `@2`, keep the `@1` schema/docs in place until orchestrator subscribers confirm migration. | ||||
|  | ||||
| ## 6. Consumer checklist | ||||
|  | ||||
| 1. Validate incoming payloads against the schema for the targeted version. | ||||
| 2. Use `idempotencyKey` for dedupe, not `eventId`. | ||||
| 3. Map `traceId`/`spanId` into telemetry spans to preserve causality. | ||||
| 4. Prefer `payload.report` → `policy.revisionId` when populating templates; the top-level `attributes` are convenience duplicates for quick routing. | ||||
| 5. Reserve the legacy Redis events for transitional compatibility only; downstream systems should subscribe to the orchestrator bus exposed by ORCH-SVC-38-101. | ||||
|  | ||||
| ## 7. Implementation status and next actions | ||||
|  | ||||
| - **Scanner WebService** — `SCANNER-EVENTS-16-301` (blocked) and `SCANNER-EVENTS-16-302` (doing) track the production of these envelopes. The remaining blocker is the .NET 10 preview OpenAPI/Auth dependency drift that currently breaks `dotnet test`. Once Gateway and Notifier owners land the replacement packages, rerun the full test suite and capture fresh fixtures under `docs/events/samples/`. | ||||
| - **Gateway/Notifier consumers** — subscribe to the orchestrator stream documented in ORCH-SVC-38-101. When the Scanner tasks unblock, regenerate notifier contract tests against the sample events included here. | ||||
| - **Docs cadence** — update this file and the matching JSON schemas whenever payload fields change. Use the rehearsal checklist in `docs/ops/launch-cutover.md` to confirm downstream validation before the production cutover. Record gaps or newly required fields in `docs/ops/launch-readiness.md` so they land in the launch checklist. | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Imposed rule reminder:** work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
| # Scanner Orchestrator Events (ORCH-SVC-38-101) | ||||
|  | ||||
| Last updated: 2025-10-26 | ||||
|  | ||||
| The Notifications Studio initiative (NOTIFY-SVC-38-001) and orchestrator backlog (ORCH-SVC-38-101) standardise how platform services emit lifecycle events. This document describes the Scanner WebService contract for the new **orchestrator envelopes** (`scanner.event.*`) and how they supersede the legacy Redis-backed `scanner.report.ready` / `scanner.scan.completed` events. | ||||
|  | ||||
| ## 1. Envelope overview | ||||
|  | ||||
| Orchestrator events share a deterministic JSON envelope: | ||||
|  | ||||
| | Field | Type | Notes | | ||||
| |-------|------|-------| | ||||
| | `eventId` | `uuid` | Globally unique identifier generated per occurrence. | | ||||
| | `kind` | `string` | Event identifier; Scanner emits `scanner.event.report.ready` and `scanner.event.scan.completed`. | | ||||
| | `version` | `integer` | Schema version. Initial release uses `1`. | | ||||
| | `tenant` | `string` | Tenant that owns the scan/report. Mirrors Authority claims. | | ||||
| | `occurredAt` | `date-time` | UTC instant when the underlying state transition happened (e.g., report persisted). | | ||||
| | `recordedAt` | `date-time` | UTC instant when the event was durably written. Optional but recommended. | | ||||
| | `source` | `string` | Producer identifier (`scanner.webservice`). | | ||||
| | `idempotencyKey` | `string` | Deterministic key for duplicate suppression (see §4). | | ||||
| | `correlationId` | `string` | Maps back to the API request or scan identifier. | | ||||
| | `traceId` / `spanId` | `string` | W3C trace context propagated into downstream telemetry. | | ||||
| | `scope` | `object` | Describes the affected artefact. Requires `repo` and `digest`; optional `namespace`, `component`, `image`. | | ||||
| | `attributes` | `object` | Flat string map for frequently queried metadata (e.g., policy revision). | | ||||
| | `payload` | `object` | Event-specific body (see §2). | | ||||
|  | ||||
| Canonical schemas live under `docs/events/scanner.event.*@1.json`. Samples that round-trip through `NotifyCanonicalJsonSerializer` are stored in `docs/events/samples/`. | ||||
|  | ||||
| ## 2. Event kinds and payloads | ||||
|  | ||||
| ### 2.1 `scanner.event.report.ready` | ||||
|  | ||||
| Emitted once a signed report is persisted and attested. Payload highlights: | ||||
|  | ||||
| - `reportId` / `scanId` — identifiers for the persisted report and originating scan. Until Scan IDs are surfaced by the API, `scanId` mirrors `reportId` so downstream correlators can stabilise on a single key. | ||||
| - **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict` — pre-sorted for deterministic routing. | ||||
| - **Links:** | ||||
|   - `ui` → `/ui/reports/{reportId}` on the current host. | ||||
|   - `report` → `{apiBasePath}/{reportsSegment}/{reportId}` (defaults to `/api/v1/reports/{reportId}`). | ||||
|   - `policy` → `{apiBasePath}/{policySegment}/revisions/{revisionId}` when a revision is present. | ||||
|   - `attestation` → `/ui/attestations/{reportId}` when a DSSE envelope is included. | ||||
| - `imageDigest` — OCI image digest associated with the analysis. | ||||
| - `generatedAt` — report generation timestamp (ISO-8601 UTC). | ||||
| - `verdict` — `pass`, `warn`, or `fail` after policy evaluation. | ||||
| - `summary` — blocked/warned/ignored/quieted counters (all non-negative integers). | ||||
| - `delta` — newly critical/high counts and optional `kev` array. | ||||
| - `quietedFindingCount` — mirrors `summary.quieted`. | ||||
| - `policy` — revision metadata (`digest`, `revisionId`) surfaced for routing. | ||||
| - `links` — UI/report/policy URLs suitable for operators. | ||||
| - `dsse` — embedded DSSE envelope (payload, type, signature list). | ||||
| - `report` — canonical report document; identical to the DSSE payload. | ||||
|  | ||||
| Schema: `docs/events/scanner.event.report.ready@1.json`   | ||||
| Sample: `docs/events/samples/scanner.event.report.ready@1.sample.json` | ||||
|  | ||||
| ### 2.2 `scanner.event.scan.completed` | ||||
|  | ||||
| Emitted after scan execution finishes (success or policy failure). Payload highlights: | ||||
|  | ||||
| - `reportId` / `scanId` / `imageDigest` — identifiers mirroring the report-ready event. As with the report-ready payload, `scanId` currently mirrors `reportId` as a temporary shim. | ||||
| - **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict`. | ||||
| - **Links:** same as above (`ui`, `report`, `policy`) with `attestation` populated when DSSE metadata exists. | ||||
| - `verdict`, `summary`, `delta`, `policy` — same semantics as above. | ||||
| - `findings` — array of surfaced findings with `id`, `severity`, optional `cve`, `purl`, and `reachability`. | ||||
| - `links`, `dsse`, `report` — same structure as §2.1 (allows Notifier to reuse signatures). | ||||
|  | ||||
| Schema: `docs/events/scanner.event.scan.completed@1.json`   | ||||
| Sample: `docs/events/samples/scanner.event.scan.completed@1.sample.json` | ||||
|  | ||||
| ### 2.3 Relationship to legacy events | ||||
|  | ||||
| | Legacy Redis event | Replacement orchestrator event | Notes | | ||||
| |--------------------|-------------------------------|-------| | ||||
| | `scanner.report.ready` | `scanner.event.report.ready` | Adds versioning, idempotency, trace context. Payload is a superset of the legacy fields. | | ||||
| | `scanner.scan.completed` | `scanner.event.scan.completed` | Same data plus explicit scan identifiers and orchestrator metadata. | | ||||
|  | ||||
| Legacy schemas remain for backwards-compatibility during migration, but new integrations **must** target the orchestrator variants. | ||||
|  | ||||
| ## 3. Deterministic serialization | ||||
|  | ||||
| - Producers must serialise events using `NotifyCanonicalJsonSerializer` to guarantee consistent key ordering and whitespace. | ||||
| - Timestamps (`occurredAt`, `recordedAt`, `payload.generatedAt`) use `DateTimeOffset.UtcDateTime.ToString("O")`. | ||||
| - Payload arrays (`delta.kev`, `findings`) should be pre-sorted (e.g., alphabetical CVE order) so hash-based consumers remain stable. | ||||
| - Optional fields are omitted rather than emitted as `null`. | ||||
|  | ||||
| ## 4. Idempotency and correlation | ||||
|  | ||||
| Idempotency keys dedupe repeated publishes and align with the orchestrator’s outbox pattern: | ||||
|  | ||||
| | Event kind | Idempotency key template | | ||||
| |------------|-------------------------| | ||||
| | `scanner.event.report.ready` | `scanner.event.report.ready:<tenant>:<reportId>` | | ||||
| | `scanner.event.scan.completed` | `scanner.event.scan.completed:<tenant>:<scanId>` | | ||||
|  | ||||
| Keys are ASCII lowercase; components should be trimmed and validated before concatenation. Retries must reuse the same key. | ||||
|  | ||||
| `correlationId` should match the scan identifier that appears in REST responses (`scanId`). Re-using the same value across the pair of events allows Notifier and orchestrator analytics to stitch lifecycle data together. | ||||
|  | ||||
| ## 5. Versioning and evolution | ||||
|  | ||||
| - Increment the `version` field and the `@<version>` suffix for **breaking** changes (field removals, type changes, semantic shifts). | ||||
| - Additive optional fields may remain within version 1; update the JSON schema and samples accordingly. | ||||
| - When introducing `@2`, keep the `@1` schema/docs in place until orchestrator subscribers confirm migration. | ||||
|  | ||||
| ## 6. Consumer checklist | ||||
|  | ||||
| 1. Validate incoming payloads against the schema for the targeted version. | ||||
| 2. Use `idempotencyKey` for dedupe, not `eventId`. | ||||
| 3. Map `traceId`/`spanId` into telemetry spans to preserve causality. | ||||
| 4. Prefer `payload.report` → `policy.revisionId` when populating templates; the top-level `attributes` are convenience duplicates for quick routing. | ||||
| 5. Reserve the legacy Redis events for transitional compatibility only; downstream systems should subscribe to the orchestrator bus exposed by ORCH-SVC-38-101. | ||||
|  | ||||
| ## 7. Implementation status and next actions | ||||
|  | ||||
| - **Scanner WebService** — `SCANNER-EVENTS-16-301` (blocked) and `SCANNER-EVENTS-16-302` (doing) track the production of these envelopes. The remaining blocker is the .NET 10 preview OpenAPI/Auth dependency drift that currently breaks `dotnet test`. Once Gateway and Notifier owners land the replacement packages, rerun the full test suite and capture fresh fixtures under `docs/events/samples/`. | ||||
| - **Gateway/Notifier consumers** — subscribe to the orchestrator stream documented in ORCH-SVC-38-101. When the Scanner tasks unblock, regenerate notifier contract tests against the sample events included here. | ||||
| - **Docs cadence** — update this file and the matching JSON schemas whenever payload fields change. Use the rehearsal checklist in `docs/ops/launch-cutover.md` to confirm downstream validation before the production cutover. Record gaps or newly required fields in `docs/ops/launch-readiness.md` so they land in the launch checklist. | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Imposed rule reminder:** work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|   | ||||
| @@ -1,93 +1,93 @@ | ||||
| { | ||||
|   "eventId": "6d2d1b77-f3c3-4f70-8a9d-6f2d0c8801ab", | ||||
|   "kind": "scanner.event.report.ready", | ||||
|   "version": 1, | ||||
|   "tenant": "tenant-alpha", | ||||
|   "occurredAt": "2025-10-19T12:34:56Z", | ||||
|   "recordedAt": "2025-10-19T12:34:57Z", | ||||
|   "source": "scanner.webservice", | ||||
|   "idempotencyKey": "scanner.event.report.ready:tenant-alpha:report-abc", | ||||
|   "correlationId": "report-abc", | ||||
|   "traceId": "0af7651916cd43dd8448eb211c80319c", | ||||
|   "spanId": "b7ad6b7169203331", | ||||
|   "scope": { | ||||
|     "namespace": "acme/edge", | ||||
|     "repo": "api", | ||||
|     "digest": "sha256:feedface" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "reportId": "report-abc", | ||||
|     "policyRevisionId": "rev-42", | ||||
|     "policyDigest": "digest-123", | ||||
|     "verdict": "blocked" | ||||
|   }, | ||||
|   "payload": { | ||||
|     "reportId": "report-abc", | ||||
|     "scanId": "report-abc", | ||||
|     "imageDigest": "sha256:feedface", | ||||
|     "generatedAt": "2025-10-19T12:34:56Z", | ||||
|     "verdict": "fail", | ||||
|     "summary": { | ||||
|       "total": 1, | ||||
|       "blocked": 1, | ||||
|       "warned": 0, | ||||
|       "ignored": 0, | ||||
|       "quieted": 0 | ||||
|     }, | ||||
|     "delta": { | ||||
|       "newCritical": 1, | ||||
|       "kev": [ | ||||
|         "CVE-2024-9999" | ||||
|       ] | ||||
|     }, | ||||
|     "quietedFindingCount": 0, | ||||
|     "policy": { | ||||
|       "digest": "digest-123", | ||||
|       "revisionId": "rev-42" | ||||
|     }, | ||||
|   "links": { | ||||
|     "ui": "https://scanner.example/ui/reports/report-abc", | ||||
|     "report": "https://scanner.example/api/v1/reports/report-abc", | ||||
|     "policy": "https://scanner.example/api/v1/policy/revisions/rev-42", | ||||
|     "attestation": "https://scanner.example/ui/attestations/report-abc" | ||||
|   }, | ||||
|     "dsse": { | ||||
|       "payloadType": "application/vnd.stellaops.report+json", | ||||
|       "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=", | ||||
|       "signatures": [ | ||||
|         { | ||||
|           "keyId": "test-key", | ||||
|           "algorithm": "hs256", | ||||
|           "signature": "signature-value" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "report": { | ||||
|       "reportId": "report-abc", | ||||
|       "generatedAt": "2025-10-19T12:34:56Z", | ||||
|       "imageDigest": "sha256:feedface", | ||||
|       "policy": { | ||||
|         "digest": "digest-123", | ||||
|         "revisionId": "rev-42" | ||||
|       }, | ||||
|       "summary": { | ||||
|         "total": 1, | ||||
|         "blocked": 1, | ||||
|         "warned": 0, | ||||
|         "ignored": 0, | ||||
|         "quieted": 0 | ||||
|       }, | ||||
|       "verdict": "blocked", | ||||
|       "verdicts": [ | ||||
|         { | ||||
|           "findingId": "finding-1", | ||||
|           "status": "Blocked", | ||||
|           "score": 47.5, | ||||
|           "sourceTrust": "NVD", | ||||
|           "reachability": "runtime" | ||||
|         } | ||||
|       ], | ||||
|       "issues": [] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "eventId": "6d2d1b77-f3c3-4f70-8a9d-6f2d0c8801ab", | ||||
|   "kind": "scanner.event.report.ready", | ||||
|   "version": 1, | ||||
|   "tenant": "tenant-alpha", | ||||
|   "occurredAt": "2025-10-19T12:34:56Z", | ||||
|   "recordedAt": "2025-10-19T12:34:57Z", | ||||
|   "source": "scanner.webservice", | ||||
|   "idempotencyKey": "scanner.event.report.ready:tenant-alpha:report-abc", | ||||
|   "correlationId": "report-abc", | ||||
|   "traceId": "0af7651916cd43dd8448eb211c80319c", | ||||
|   "spanId": "b7ad6b7169203331", | ||||
|   "scope": { | ||||
|     "namespace": "acme/edge", | ||||
|     "repo": "api", | ||||
|     "digest": "sha256:feedface" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "reportId": "report-abc", | ||||
|     "policyRevisionId": "rev-42", | ||||
|     "policyDigest": "digest-123", | ||||
|     "verdict": "blocked" | ||||
|   }, | ||||
|   "payload": { | ||||
|     "reportId": "report-abc", | ||||
|     "scanId": "report-abc", | ||||
|     "imageDigest": "sha256:feedface", | ||||
|     "generatedAt": "2025-10-19T12:34:56Z", | ||||
|     "verdict": "fail", | ||||
|     "summary": { | ||||
|       "total": 1, | ||||
|       "blocked": 1, | ||||
|       "warned": 0, | ||||
|       "ignored": 0, | ||||
|       "quieted": 0 | ||||
|     }, | ||||
|     "delta": { | ||||
|       "newCritical": 1, | ||||
|       "kev": [ | ||||
|         "CVE-2024-9999" | ||||
|       ] | ||||
|     }, | ||||
|     "quietedFindingCount": 0, | ||||
|     "policy": { | ||||
|       "digest": "digest-123", | ||||
|       "revisionId": "rev-42" | ||||
|     }, | ||||
|   "links": { | ||||
|     "ui": "https://scanner.example/ui/reports/report-abc", | ||||
|     "report": "https://scanner.example/api/v1/reports/report-abc", | ||||
|     "policy": "https://scanner.example/api/v1/policy/revisions/rev-42", | ||||
|     "attestation": "https://scanner.example/ui/attestations/report-abc" | ||||
|   }, | ||||
|     "dsse": { | ||||
|       "payloadType": "application/vnd.stellaops.report+json", | ||||
|       "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=", | ||||
|       "signatures": [ | ||||
|         { | ||||
|           "keyId": "test-key", | ||||
|           "algorithm": "hs256", | ||||
|           "signature": "signature-value" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "report": { | ||||
|       "reportId": "report-abc", | ||||
|       "generatedAt": "2025-10-19T12:34:56Z", | ||||
|       "imageDigest": "sha256:feedface", | ||||
|       "policy": { | ||||
|         "digest": "digest-123", | ||||
|         "revisionId": "rev-42" | ||||
|       }, | ||||
|       "summary": { | ||||
|         "total": 1, | ||||
|         "blocked": 1, | ||||
|         "warned": 0, | ||||
|         "ignored": 0, | ||||
|         "quieted": 0 | ||||
|       }, | ||||
|       "verdict": "blocked", | ||||
|       "verdicts": [ | ||||
|         { | ||||
|           "findingId": "finding-1", | ||||
|           "status": "Blocked", | ||||
|           "score": 47.5, | ||||
|           "sourceTrust": "NVD", | ||||
|           "reachability": "runtime" | ||||
|         } | ||||
|       ], | ||||
|       "issues": [] | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,99 +1,99 @@ | ||||
| { | ||||
|   "eventId": "08a6de24-4a94-4d14-8432-9d14f36f6da3", | ||||
|   "kind": "scanner.event.scan.completed", | ||||
|   "version": 1, | ||||
|   "tenant": "tenant-alpha", | ||||
|   "occurredAt": "2025-10-19T12:34:56Z", | ||||
|   "recordedAt": "2025-10-19T12:34:57Z", | ||||
|   "source": "scanner.webservice", | ||||
|   "idempotencyKey": "scanner.event.scan.completed:tenant-alpha:report-abc", | ||||
|   "correlationId": "report-abc", | ||||
|   "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", | ||||
|   "scope": { | ||||
|     "namespace": "acme/edge", | ||||
|     "repo": "api", | ||||
|     "digest": "sha256:feedface" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "reportId": "report-abc", | ||||
|     "policyRevisionId": "rev-42", | ||||
|     "policyDigest": "digest-123", | ||||
|     "verdict": "blocked" | ||||
|   }, | ||||
|   "payload": { | ||||
|     "reportId": "report-abc", | ||||
|     "scanId": "report-abc", | ||||
|     "imageDigest": "sha256:feedface", | ||||
|     "verdict": "fail", | ||||
|     "summary": { | ||||
|       "total": 1, | ||||
|       "blocked": 1, | ||||
|       "warned": 0, | ||||
|       "ignored": 0, | ||||
|       "quieted": 0 | ||||
|     }, | ||||
|     "delta": { | ||||
|       "newCritical": 1, | ||||
|       "kev": [ | ||||
|         "CVE-2024-9999" | ||||
|       ] | ||||
|     }, | ||||
|     "policy": { | ||||
|       "digest": "digest-123", | ||||
|       "revisionId": "rev-42" | ||||
|     }, | ||||
|     "findings": [ | ||||
|       { | ||||
|         "id": "finding-1", | ||||
|         "severity": "Critical", | ||||
|         "cve": "CVE-2024-9999", | ||||
|         "purl": "pkg:docker/acme/edge-api@sha256-feedface", | ||||
|         "reachability": "runtime" | ||||
|       } | ||||
|     ], | ||||
|     "links": { | ||||
|       "ui": "https://scanner.example/ui/reports/report-abc", | ||||
|       "report": "https://scanner.example/api/v1/reports/report-abc", | ||||
|       "policy": "https://scanner.example/api/v1/policy/revisions/rev-42", | ||||
|       "attestation": "https://scanner.example/ui/attestations/report-abc" | ||||
|     }, | ||||
|     "dsse": { | ||||
|       "payloadType": "application/vnd.stellaops.report+json", | ||||
|       "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=", | ||||
|       "signatures": [ | ||||
|         { | ||||
|           "keyId": "test-key", | ||||
|           "algorithm": "hs256", | ||||
|           "signature": "signature-value" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "report": { | ||||
|       "reportId": "report-abc", | ||||
|       "generatedAt": "2025-10-19T12:34:56Z", | ||||
|       "imageDigest": "sha256:feedface", | ||||
|       "policy": { | ||||
|         "digest": "digest-123", | ||||
|         "revisionId": "rev-42" | ||||
|       }, | ||||
|       "summary": { | ||||
|         "total": 1, | ||||
|         "blocked": 1, | ||||
|         "warned": 0, | ||||
|         "ignored": 0, | ||||
|         "quieted": 0 | ||||
|       }, | ||||
|       "verdict": "blocked", | ||||
|       "verdicts": [ | ||||
|         { | ||||
|           "findingId": "finding-1", | ||||
|           "status": "Blocked", | ||||
|           "score": 47.5, | ||||
|           "sourceTrust": "NVD", | ||||
|           "reachability": "runtime" | ||||
|         } | ||||
|       ], | ||||
|       "issues": [] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "eventId": "08a6de24-4a94-4d14-8432-9d14f36f6da3", | ||||
|   "kind": "scanner.event.scan.completed", | ||||
|   "version": 1, | ||||
|   "tenant": "tenant-alpha", | ||||
|   "occurredAt": "2025-10-19T12:34:56Z", | ||||
|   "recordedAt": "2025-10-19T12:34:57Z", | ||||
|   "source": "scanner.webservice", | ||||
|   "idempotencyKey": "scanner.event.scan.completed:tenant-alpha:report-abc", | ||||
|   "correlationId": "report-abc", | ||||
|   "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", | ||||
|   "scope": { | ||||
|     "namespace": "acme/edge", | ||||
|     "repo": "api", | ||||
|     "digest": "sha256:feedface" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "reportId": "report-abc", | ||||
|     "policyRevisionId": "rev-42", | ||||
|     "policyDigest": "digest-123", | ||||
|     "verdict": "blocked" | ||||
|   }, | ||||
|   "payload": { | ||||
|     "reportId": "report-abc", | ||||
|     "scanId": "report-abc", | ||||
|     "imageDigest": "sha256:feedface", | ||||
|     "verdict": "fail", | ||||
|     "summary": { | ||||
|       "total": 1, | ||||
|       "blocked": 1, | ||||
|       "warned": 0, | ||||
|       "ignored": 0, | ||||
|       "quieted": 0 | ||||
|     }, | ||||
|     "delta": { | ||||
|       "newCritical": 1, | ||||
|       "kev": [ | ||||
|         "CVE-2024-9999" | ||||
|       ] | ||||
|     }, | ||||
|     "policy": { | ||||
|       "digest": "digest-123", | ||||
|       "revisionId": "rev-42" | ||||
|     }, | ||||
|     "findings": [ | ||||
|       { | ||||
|         "id": "finding-1", | ||||
|         "severity": "Critical", | ||||
|         "cve": "CVE-2024-9999", | ||||
|         "purl": "pkg:docker/acme/edge-api@sha256-feedface", | ||||
|         "reachability": "runtime" | ||||
|       } | ||||
|     ], | ||||
|     "links": { | ||||
|       "ui": "https://scanner.example/ui/reports/report-abc", | ||||
|       "report": "https://scanner.example/api/v1/reports/report-abc", | ||||
|       "policy": "https://scanner.example/api/v1/policy/revisions/rev-42", | ||||
|       "attestation": "https://scanner.example/ui/attestations/report-abc" | ||||
|     }, | ||||
|     "dsse": { | ||||
|       "payloadType": "application/vnd.stellaops.report+json", | ||||
|       "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=", | ||||
|       "signatures": [ | ||||
|         { | ||||
|           "keyId": "test-key", | ||||
|           "algorithm": "hs256", | ||||
|           "signature": "signature-value" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "report": { | ||||
|       "reportId": "report-abc", | ||||
|       "generatedAt": "2025-10-19T12:34:56Z", | ||||
|       "imageDigest": "sha256:feedface", | ||||
|       "policy": { | ||||
|         "digest": "digest-123", | ||||
|         "revisionId": "rev-42" | ||||
|       }, | ||||
|       "summary": { | ||||
|         "total": 1, | ||||
|         "blocked": 1, | ||||
|         "warned": 0, | ||||
|         "ignored": 0, | ||||
|         "quieted": 0 | ||||
|       }, | ||||
|       "verdict": "blocked", | ||||
|       "verdicts": [ | ||||
|         { | ||||
|           "findingId": "finding-1", | ||||
|           "status": "Blocked", | ||||
|           "score": 47.5, | ||||
|           "sourceTrust": "NVD", | ||||
|           "reachability": "runtime" | ||||
|         } | ||||
|       ], | ||||
|       "issues": [] | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,36 +1,36 @@ | ||||
| { | ||||
|   "eventId": "4d33c19c-1c8a-44d1-9954-1d5e98b2af71", | ||||
|   "kind": "scheduler.graph.job.completed", | ||||
|   "tenant": "tenant-alpha", | ||||
|   "ts": "2025-10-26T12:00:45Z", | ||||
|   "payload": { | ||||
|     "jobType": "build", | ||||
|     "status": "completed", | ||||
|     "occurredAt": "2025-10-26T12:00:45Z", | ||||
|     "job": { | ||||
|       "schemaVersion": "scheduler.graph-build-job@1", | ||||
|       "id": "gbj_20251026a", | ||||
|       "tenantId": "tenant-alpha", | ||||
|       "sbomId": "sbom_20251026", | ||||
|       "sbomVersionId": "sbom_ver_20251026", | ||||
|       "sbomDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | ||||
|       "graphSnapshotId": "graph_snap_20251026", | ||||
|       "status": "completed", | ||||
|       "trigger": "sbom-version", | ||||
|       "attempts": 1, | ||||
|       "cartographerJobId": "carto_job_42", | ||||
|       "correlationId": "evt_svc_987", | ||||
|       "createdAt": "2025-10-26T12:00:00+00:00", | ||||
|       "startedAt": "2025-10-26T12:00:05+00:00", | ||||
|       "completedAt": "2025-10-26T12:00:45+00:00", | ||||
|       "metadata": { | ||||
|         "sbomEventId": "sbom_evt_20251026" | ||||
|       } | ||||
|     }, | ||||
|     "resultUri": "oras://cartographer/offline/tenant-alpha/graph_snap_20251026" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "cartographerCluster": "offline-kit", | ||||
|     "plannerShard": "graph-builders-01" | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "eventId": "4d33c19c-1c8a-44d1-9954-1d5e98b2af71", | ||||
|   "kind": "scheduler.graph.job.completed", | ||||
|   "tenant": "tenant-alpha", | ||||
|   "ts": "2025-10-26T12:00:45Z", | ||||
|   "payload": { | ||||
|     "jobType": "build", | ||||
|     "status": "completed", | ||||
|     "occurredAt": "2025-10-26T12:00:45Z", | ||||
|     "job": { | ||||
|       "schemaVersion": "scheduler.graph-build-job@1", | ||||
|       "id": "gbj_20251026a", | ||||
|       "tenantId": "tenant-alpha", | ||||
|       "sbomId": "sbom_20251026", | ||||
|       "sbomVersionId": "sbom_ver_20251026", | ||||
|       "sbomDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | ||||
|       "graphSnapshotId": "graph_snap_20251026", | ||||
|       "status": "completed", | ||||
|       "trigger": "sbom-version", | ||||
|       "attempts": 1, | ||||
|       "cartographerJobId": "carto_job_42", | ||||
|       "correlationId": "evt_svc_987", | ||||
|       "createdAt": "2025-10-26T12:00:00+00:00", | ||||
|       "startedAt": "2025-10-26T12:00:05+00:00", | ||||
|       "completedAt": "2025-10-26T12:00:45+00:00", | ||||
|       "metadata": { | ||||
|         "sbomEventId": "sbom_evt_20251026" | ||||
|       } | ||||
|     }, | ||||
|     "resultUri": "oras://cartographer/offline/tenant-alpha/graph_snap_20251026" | ||||
|   }, | ||||
|   "attributes": { | ||||
|     "cartographerCluster": "offline-kit", | ||||
|     "plannerShard": "graph-builders-01" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,164 +1,164 @@ | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scanner.event.report.ready@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scanner orchestrator event – report ready (v1)", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": [ | ||||
|     "eventId", | ||||
|     "kind", | ||||
|     "version", | ||||
|     "tenant", | ||||
|     "occurredAt", | ||||
|     "source", | ||||
|     "idempotencyKey", | ||||
|     "payload" | ||||
|   ], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier for this occurrence." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scanner.event.report.ready", | ||||
|       "description": "Event kind identifier consumed by orchestrator subscribers." | ||||
|     }, | ||||
|     "version": { | ||||
|       "const": 1, | ||||
|       "description": "Schema version for orchestrator envelopes." | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant that owns the scan/report." | ||||
|     }, | ||||
|     "occurredAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the report transitioned to ready." | ||||
|     }, | ||||
|     "recordedAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the event was persisted. Optional." | ||||
|     }, | ||||
|     "source": { | ||||
|       "type": "string", | ||||
|       "description": "Producer identifier, e.g. `scanner.webservice`." | ||||
|     }, | ||||
|     "idempotencyKey": { | ||||
|       "type": "string", | ||||
|       "minLength": 8, | ||||
|       "description": "Deterministic key used to deduplicate events downstream." | ||||
|     }, | ||||
|     "correlationId": { | ||||
|       "type": "string", | ||||
|       "description": "Correlation identifier that ties this event to a request or workflow." | ||||
|     }, | ||||
|     "traceId": { | ||||
|       "type": "string", | ||||
|       "description": "W3C trace ID (32 hex chars) for distributed tracing." | ||||
|     }, | ||||
|     "spanId": { | ||||
|       "type": "string", | ||||
|       "description": "Optional span identifier associated with traceId." | ||||
|     }, | ||||
|     "scope": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["repo", "digest"], | ||||
|       "properties": { | ||||
|         "namespace": {"type": "string"}, | ||||
|         "repo": {"type": "string"}, | ||||
|         "digest": {"type": "string"}, | ||||
|         "component": {"type": "string"}, | ||||
|         "image": {"type": "string"} | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "String attributes for downstream correlation (policy revision, scan id, etc.).", | ||||
|       "additionalProperties": {"type": "string"} | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": true, | ||||
|       "required": ["reportId", "verdict", "summary", "links", "report"], | ||||
|       "properties": { | ||||
|         "reportId": {"type": "string"}, | ||||
|         "scanId": {"type": "string"}, | ||||
|         "imageDigest": {"type": "string"}, | ||||
|         "generatedAt": {"type": "string", "format": "date-time"}, | ||||
|         "verdict": {"enum": ["pass", "warn", "fail"]}, | ||||
|         "summary": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["total", "blocked", "warned", "ignored", "quieted"], | ||||
|           "properties": { | ||||
|             "total": {"type": "integer", "minimum": 0}, | ||||
|             "blocked": {"type": "integer", "minimum": 0}, | ||||
|             "warned": {"type": "integer", "minimum": 0}, | ||||
|             "ignored": {"type": "integer", "minimum": 0}, | ||||
|             "quieted": {"type": "integer", "minimum": 0} | ||||
|           } | ||||
|         }, | ||||
|         "delta": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "newCritical": {"type": "integer", "minimum": 0}, | ||||
|             "newHigh": {"type": "integer", "minimum": 0}, | ||||
|             "kev": { | ||||
|               "type": "array", | ||||
|               "items": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "quietedFindingCount": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "policy": { | ||||
|           "type": "object", | ||||
|           "description": "Policy revision metadata surfaced alongside the report." | ||||
|         }, | ||||
|         "links": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "ui": {"type": "string", "format": "uri"}, | ||||
|             "report": {"type": "string", "format": "uri"}, | ||||
|             "policy": {"type": "string", "format": "uri"}, | ||||
|             "attestation": {"type": "string", "format": "uri"} | ||||
|           } | ||||
|         }, | ||||
|         "dsse": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["payloadType", "payload", "signatures"], | ||||
|           "properties": { | ||||
|             "payloadType": {"type": "string"}, | ||||
|             "payload": {"type": "string"}, | ||||
|             "signatures": { | ||||
|               "type": "array", | ||||
|               "items": { | ||||
|                 "type": "object", | ||||
|                 "additionalProperties": false, | ||||
|                 "required": ["keyId", "algorithm", "signature"], | ||||
|                 "properties": { | ||||
|                   "keyId": {"type": "string"}, | ||||
|                   "algorithm": {"type": "string"}, | ||||
|                   "signature": {"type": "string"} | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "report": { | ||||
|           "type": "object", | ||||
|           "description": "Canonical scanner report document that aligns with the DSSE payload." | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scanner.event.report.ready@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scanner orchestrator event – report ready (v1)", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": [ | ||||
|     "eventId", | ||||
|     "kind", | ||||
|     "version", | ||||
|     "tenant", | ||||
|     "occurredAt", | ||||
|     "source", | ||||
|     "idempotencyKey", | ||||
|     "payload" | ||||
|   ], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier for this occurrence." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scanner.event.report.ready", | ||||
|       "description": "Event kind identifier consumed by orchestrator subscribers." | ||||
|     }, | ||||
|     "version": { | ||||
|       "const": 1, | ||||
|       "description": "Schema version for orchestrator envelopes." | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant that owns the scan/report." | ||||
|     }, | ||||
|     "occurredAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the report transitioned to ready." | ||||
|     }, | ||||
|     "recordedAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the event was persisted. Optional." | ||||
|     }, | ||||
|     "source": { | ||||
|       "type": "string", | ||||
|       "description": "Producer identifier, e.g. `scanner.webservice`." | ||||
|     }, | ||||
|     "idempotencyKey": { | ||||
|       "type": "string", | ||||
|       "minLength": 8, | ||||
|       "description": "Deterministic key used to deduplicate events downstream." | ||||
|     }, | ||||
|     "correlationId": { | ||||
|       "type": "string", | ||||
|       "description": "Correlation identifier that ties this event to a request or workflow." | ||||
|     }, | ||||
|     "traceId": { | ||||
|       "type": "string", | ||||
|       "description": "W3C trace ID (32 hex chars) for distributed tracing." | ||||
|     }, | ||||
|     "spanId": { | ||||
|       "type": "string", | ||||
|       "description": "Optional span identifier associated with traceId." | ||||
|     }, | ||||
|     "scope": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["repo", "digest"], | ||||
|       "properties": { | ||||
|         "namespace": {"type": "string"}, | ||||
|         "repo": {"type": "string"}, | ||||
|         "digest": {"type": "string"}, | ||||
|         "component": {"type": "string"}, | ||||
|         "image": {"type": "string"} | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "String attributes for downstream correlation (policy revision, scan id, etc.).", | ||||
|       "additionalProperties": {"type": "string"} | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": true, | ||||
|       "required": ["reportId", "verdict", "summary", "links", "report"], | ||||
|       "properties": { | ||||
|         "reportId": {"type": "string"}, | ||||
|         "scanId": {"type": "string"}, | ||||
|         "imageDigest": {"type": "string"}, | ||||
|         "generatedAt": {"type": "string", "format": "date-time"}, | ||||
|         "verdict": {"enum": ["pass", "warn", "fail"]}, | ||||
|         "summary": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["total", "blocked", "warned", "ignored", "quieted"], | ||||
|           "properties": { | ||||
|             "total": {"type": "integer", "minimum": 0}, | ||||
|             "blocked": {"type": "integer", "minimum": 0}, | ||||
|             "warned": {"type": "integer", "minimum": 0}, | ||||
|             "ignored": {"type": "integer", "minimum": 0}, | ||||
|             "quieted": {"type": "integer", "minimum": 0} | ||||
|           } | ||||
|         }, | ||||
|         "delta": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "newCritical": {"type": "integer", "minimum": 0}, | ||||
|             "newHigh": {"type": "integer", "minimum": 0}, | ||||
|             "kev": { | ||||
|               "type": "array", | ||||
|               "items": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "quietedFindingCount": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "policy": { | ||||
|           "type": "object", | ||||
|           "description": "Policy revision metadata surfaced alongside the report." | ||||
|         }, | ||||
|         "links": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "ui": {"type": "string", "format": "uri"}, | ||||
|             "report": {"type": "string", "format": "uri"}, | ||||
|             "policy": {"type": "string", "format": "uri"}, | ||||
|             "attestation": {"type": "string", "format": "uri"} | ||||
|           } | ||||
|         }, | ||||
|         "dsse": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["payloadType", "payload", "signatures"], | ||||
|           "properties": { | ||||
|             "payloadType": {"type": "string"}, | ||||
|             "payload": {"type": "string"}, | ||||
|             "signatures": { | ||||
|               "type": "array", | ||||
|               "items": { | ||||
|                 "type": "object", | ||||
|                 "additionalProperties": false, | ||||
|                 "required": ["keyId", "algorithm", "signature"], | ||||
|                 "properties": { | ||||
|                   "keyId": {"type": "string"}, | ||||
|                   "algorithm": {"type": "string"}, | ||||
|                   "signature": {"type": "string"} | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "report": { | ||||
|           "type": "object", | ||||
|           "description": "Canonical scanner report document that aligns with the DSSE payload." | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,174 +1,174 @@ | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scanner.event.scan.completed@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scanner orchestrator event – scan completed (v1)", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": [ | ||||
|     "eventId", | ||||
|     "kind", | ||||
|     "version", | ||||
|     "tenant", | ||||
|     "occurredAt", | ||||
|     "source", | ||||
|     "idempotencyKey", | ||||
|     "payload" | ||||
|   ], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier for this occurrence." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scanner.event.scan.completed", | ||||
|       "description": "Event kind identifier consumed by orchestrator subscribers." | ||||
|     }, | ||||
|     "version": { | ||||
|       "const": 1, | ||||
|       "description": "Schema version for orchestrator envelopes." | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant that owns the scan." | ||||
|     }, | ||||
|     "occurredAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the scan completed." | ||||
|     }, | ||||
|     "recordedAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the event was persisted. Optional." | ||||
|     }, | ||||
|     "source": { | ||||
|       "type": "string", | ||||
|       "description": "Producer identifier, e.g. `scanner.webservice`." | ||||
|     }, | ||||
|     "idempotencyKey": { | ||||
|       "type": "string", | ||||
|       "minLength": 8, | ||||
|       "description": "Deterministic key used to deduplicate events downstream." | ||||
|     }, | ||||
|     "correlationId": { | ||||
|       "type": "string", | ||||
|       "description": "Correlation identifier tying this event to a request or workflow." | ||||
|     }, | ||||
|     "traceId": { | ||||
|       "type": "string", | ||||
|       "description": "W3C trace ID (32 hex chars) for distributed tracing." | ||||
|     }, | ||||
|     "spanId": { | ||||
|       "type": "string", | ||||
|       "description": "Optional span identifier associated with traceId." | ||||
|     }, | ||||
|     "scope": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["repo", "digest"], | ||||
|       "properties": { | ||||
|         "namespace": {"type": "string"}, | ||||
|         "repo": {"type": "string"}, | ||||
|         "digest": {"type": "string"}, | ||||
|         "component": {"type": "string"}, | ||||
|         "image": {"type": "string"} | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "String attributes for downstream correlation (policy revision, scan id, etc.).", | ||||
|       "additionalProperties": {"type": "string"} | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": true, | ||||
|       "required": ["reportId", "scanId", "imageDigest", "verdict", "summary", "report"], | ||||
|       "properties": { | ||||
|         "reportId": {"type": "string"}, | ||||
|         "scanId": {"type": "string"}, | ||||
|         "imageDigest": {"type": "string"}, | ||||
|         "verdict": {"enum": ["pass", "warn", "fail"]}, | ||||
|         "summary": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["total", "blocked", "warned", "ignored", "quieted"], | ||||
|           "properties": { | ||||
|             "total": {"type": "integer", "minimum": 0}, | ||||
|             "blocked": {"type": "integer", "minimum": 0}, | ||||
|             "warned": {"type": "integer", "minimum": 0}, | ||||
|             "ignored": {"type": "integer", "minimum": 0}, | ||||
|             "quieted": {"type": "integer", "minimum": 0} | ||||
|           } | ||||
|         }, | ||||
|         "delta": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "newCritical": {"type": "integer", "minimum": 0}, | ||||
|             "newHigh": {"type": "integer", "minimum": 0}, | ||||
|             "kev": { | ||||
|               "type": "array", | ||||
|               "items": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "policy": { | ||||
|           "type": "object", | ||||
|           "description": "Policy revision metadata surfaced alongside the report." | ||||
|         }, | ||||
|         "findings": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "object", | ||||
|             "additionalProperties": false, | ||||
|             "required": ["id"], | ||||
|             "properties": { | ||||
|               "id": {"type": "string"}, | ||||
|               "severity": {"type": "string"}, | ||||
|               "cve": {"type": "string"}, | ||||
|               "purl": {"type": "string"}, | ||||
|               "reachability": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "links": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "ui": {"type": "string", "format": "uri"}, | ||||
|             "report": {"type": "string", "format": "uri"}, | ||||
|             "policy": {"type": "string", "format": "uri"}, | ||||
|             "attestation": {"type": "string", "format": "uri"} | ||||
|           } | ||||
|         }, | ||||
|         "dsse": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["payloadType", "payload", "signatures"], | ||||
|           "properties": { | ||||
|             "payloadType": {"type": "string"}, | ||||
|             "payload": {"type": "string"}, | ||||
|             "signatures": { | ||||
|               "type": "array", | ||||
|               "items": { | ||||
|                 "type": "object", | ||||
|                 "additionalProperties": false, | ||||
|                 "required": ["keyId", "algorithm", "signature"], | ||||
|                 "properties": { | ||||
|                   "keyId": {"type": "string"}, | ||||
|                   "algorithm": {"type": "string"}, | ||||
|                   "signature": {"type": "string"} | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "report": { | ||||
|           "type": "object", | ||||
|           "description": "Canonical scanner report document that aligns with the DSSE payload." | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scanner.event.scan.completed@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scanner orchestrator event – scan completed (v1)", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": [ | ||||
|     "eventId", | ||||
|     "kind", | ||||
|     "version", | ||||
|     "tenant", | ||||
|     "occurredAt", | ||||
|     "source", | ||||
|     "idempotencyKey", | ||||
|     "payload" | ||||
|   ], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier for this occurrence." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scanner.event.scan.completed", | ||||
|       "description": "Event kind identifier consumed by orchestrator subscribers." | ||||
|     }, | ||||
|     "version": { | ||||
|       "const": 1, | ||||
|       "description": "Schema version for orchestrator envelopes." | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant that owns the scan." | ||||
|     }, | ||||
|     "occurredAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the scan completed." | ||||
|     }, | ||||
|     "recordedAt": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "Timestamp (UTC) when the event was persisted. Optional." | ||||
|     }, | ||||
|     "source": { | ||||
|       "type": "string", | ||||
|       "description": "Producer identifier, e.g. `scanner.webservice`." | ||||
|     }, | ||||
|     "idempotencyKey": { | ||||
|       "type": "string", | ||||
|       "minLength": 8, | ||||
|       "description": "Deterministic key used to deduplicate events downstream." | ||||
|     }, | ||||
|     "correlationId": { | ||||
|       "type": "string", | ||||
|       "description": "Correlation identifier tying this event to a request or workflow." | ||||
|     }, | ||||
|     "traceId": { | ||||
|       "type": "string", | ||||
|       "description": "W3C trace ID (32 hex chars) for distributed tracing." | ||||
|     }, | ||||
|     "spanId": { | ||||
|       "type": "string", | ||||
|       "description": "Optional span identifier associated with traceId." | ||||
|     }, | ||||
|     "scope": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["repo", "digest"], | ||||
|       "properties": { | ||||
|         "namespace": {"type": "string"}, | ||||
|         "repo": {"type": "string"}, | ||||
|         "digest": {"type": "string"}, | ||||
|         "component": {"type": "string"}, | ||||
|         "image": {"type": "string"} | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "String attributes for downstream correlation (policy revision, scan id, etc.).", | ||||
|       "additionalProperties": {"type": "string"} | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": true, | ||||
|       "required": ["reportId", "scanId", "imageDigest", "verdict", "summary", "report"], | ||||
|       "properties": { | ||||
|         "reportId": {"type": "string"}, | ||||
|         "scanId": {"type": "string"}, | ||||
|         "imageDigest": {"type": "string"}, | ||||
|         "verdict": {"enum": ["pass", "warn", "fail"]}, | ||||
|         "summary": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["total", "blocked", "warned", "ignored", "quieted"], | ||||
|           "properties": { | ||||
|             "total": {"type": "integer", "minimum": 0}, | ||||
|             "blocked": {"type": "integer", "minimum": 0}, | ||||
|             "warned": {"type": "integer", "minimum": 0}, | ||||
|             "ignored": {"type": "integer", "minimum": 0}, | ||||
|             "quieted": {"type": "integer", "minimum": 0} | ||||
|           } | ||||
|         }, | ||||
|         "delta": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "newCritical": {"type": "integer", "minimum": 0}, | ||||
|             "newHigh": {"type": "integer", "minimum": 0}, | ||||
|             "kev": { | ||||
|               "type": "array", | ||||
|               "items": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "policy": { | ||||
|           "type": "object", | ||||
|           "description": "Policy revision metadata surfaced alongside the report." | ||||
|         }, | ||||
|         "findings": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "object", | ||||
|             "additionalProperties": false, | ||||
|             "required": ["id"], | ||||
|             "properties": { | ||||
|               "id": {"type": "string"}, | ||||
|               "severity": {"type": "string"}, | ||||
|               "cve": {"type": "string"}, | ||||
|               "purl": {"type": "string"}, | ||||
|               "reachability": {"type": "string"} | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "links": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "properties": { | ||||
|             "ui": {"type": "string", "format": "uri"}, | ||||
|             "report": {"type": "string", "format": "uri"}, | ||||
|             "policy": {"type": "string", "format": "uri"}, | ||||
|             "attestation": {"type": "string", "format": "uri"} | ||||
|           } | ||||
|         }, | ||||
|         "dsse": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": false, | ||||
|           "required": ["payloadType", "payload", "signatures"], | ||||
|           "properties": { | ||||
|             "payloadType": {"type": "string"}, | ||||
|             "payload": {"type": "string"}, | ||||
|             "signatures": { | ||||
|               "type": "array", | ||||
|               "items": { | ||||
|                 "type": "object", | ||||
|                 "additionalProperties": false, | ||||
|                 "required": ["keyId", "algorithm", "signature"], | ||||
|                 "properties": { | ||||
|                   "keyId": {"type": "string"}, | ||||
|                   "algorithm": {"type": "string"}, | ||||
|                   "signature": {"type": "string"} | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "report": { | ||||
|           "type": "object", | ||||
|           "description": "Canonical scanner report document that aligns with the DSSE payload." | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,196 +1,196 @@ | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scheduler.graph.job.completed@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scheduler Graph Job Completed Event", | ||||
|   "description": "Legacy scheduler event emitted when a graph build or overlay job reaches a terminal state. Consumers validate downstream caches and surface overlay freshness.", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": ["eventId", "kind", "tenant", "ts", "payload"], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier per event." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scheduler.graph.job.completed" | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant identifier scoped to the originating job." | ||||
|     }, | ||||
|     "ts": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "UTC timestamp when the job reached a terminal state." | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["jobType", "job", "status", "occurredAt"], | ||||
|       "properties": { | ||||
|         "jobType": { | ||||
|           "type": "string", | ||||
|           "enum": ["build", "overlay"], | ||||
|           "description": "Job flavour, matches the CLR type of the serialized job payload." | ||||
|         }, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["completed", "failed", "cancelled"], | ||||
|           "description": "Terminal status recorded for the job." | ||||
|         }, | ||||
|         "occurredAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time", | ||||
|           "description": "UTC timestamp of the terminal transition, mirrors job.CompletedAt." | ||||
|         }, | ||||
|         "job": { | ||||
|           "oneOf": [ | ||||
|             {"$ref": "#/definitions/graphBuildJob"}, | ||||
|             {"$ref": "#/definitions/graphOverlayJob"} | ||||
|           ], | ||||
|           "description": "Canonical serialized representation of the finished job." | ||||
|         }, | ||||
|         "resultUri": { | ||||
|           "type": "string", | ||||
|           "description": "Optional URI pointing to Cartographer snapshot or overlay bundle (if available)." | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "Optional correlation bag for downstream consumers.", | ||||
|       "additionalProperties": { | ||||
|         "type": "string" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "definitions": { | ||||
|     "graphBuildJob": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": [ | ||||
|         "schemaVersion", | ||||
|         "id", | ||||
|         "tenantId", | ||||
|         "sbomId", | ||||
|         "sbomVersionId", | ||||
|         "sbomDigest", | ||||
|         "status", | ||||
|         "trigger", | ||||
|         "attempts", | ||||
|         "createdAt" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "schemaVersion": { | ||||
|           "const": "scheduler.graph-build-job@1" | ||||
|         }, | ||||
|         "id": {"type": "string"}, | ||||
|         "tenantId": {"type": "string"}, | ||||
|         "sbomId": {"type": "string"}, | ||||
|         "sbomVersionId": {"type": "string"}, | ||||
|         "sbomDigest": { | ||||
|           "type": "string", | ||||
|           "pattern": "^sha256:[a-f0-9]{64}$" | ||||
|         }, | ||||
|         "graphSnapshotId": {"type": "string"}, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["pending", "queued", "running", "completed", "failed", "cancelled"] | ||||
|         }, | ||||
|         "trigger": { | ||||
|           "type": "string", | ||||
|           "enum": ["sbom-version", "backfill", "manual"] | ||||
|         }, | ||||
|         "attempts": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "cartographerJobId": {"type": "string"}, | ||||
|         "correlationId": {"type": "string"}, | ||||
|         "createdAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "startedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "completedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "error": {"type": "string"}, | ||||
|         "metadata": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": {"type": "string"} | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "graphOverlayJob": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": [ | ||||
|         "schemaVersion", | ||||
|         "id", | ||||
|         "tenantId", | ||||
|         "graphSnapshotId", | ||||
|         "overlayKind", | ||||
|         "overlayKey", | ||||
|         "status", | ||||
|         "trigger", | ||||
|         "attempts", | ||||
|         "createdAt" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "schemaVersion": { | ||||
|           "const": "scheduler.graph-overlay-job@1" | ||||
|         }, | ||||
|         "id": {"type": "string"}, | ||||
|         "tenantId": {"type": "string"}, | ||||
|         "graphSnapshotId": {"type": "string"}, | ||||
|         "buildJobId": {"type": "string"}, | ||||
|         "overlayKind": { | ||||
|           "type": "string", | ||||
|           "enum": ["policy", "advisory", "vex"] | ||||
|         }, | ||||
|         "overlayKey": {"type": "string"}, | ||||
|         "subjects": { | ||||
|           "type": "array", | ||||
|           "items": {"type": "string"}, | ||||
|           "uniqueItems": true | ||||
|         }, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["pending", "queued", "running", "completed", "failed", "cancelled"] | ||||
|         }, | ||||
|         "trigger": { | ||||
|           "type": "string", | ||||
|           "enum": ["policy", "advisory", "vex", "sbom-version", "manual"] | ||||
|         }, | ||||
|         "attempts": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "correlationId": {"type": "string"}, | ||||
|         "createdAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "startedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "completedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "error": {"type": "string"}, | ||||
|         "metadata": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": {"type": "string"} | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "$id": "https://stella-ops.org/schemas/events/scheduler.graph.job.completed@1.json", | ||||
|   "$schema": "http://json-schema.org/draft-07/schema#", | ||||
|   "title": "Scheduler Graph Job Completed Event", | ||||
|   "description": "Legacy scheduler event emitted when a graph build or overlay job reaches a terminal state. Consumers validate downstream caches and surface overlay freshness.", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|   "required": ["eventId", "kind", "tenant", "ts", "payload"], | ||||
|   "properties": { | ||||
|     "eventId": { | ||||
|       "type": "string", | ||||
|       "format": "uuid", | ||||
|       "description": "Globally unique identifier per event." | ||||
|     }, | ||||
|     "kind": { | ||||
|       "const": "scheduler.graph.job.completed" | ||||
|     }, | ||||
|     "tenant": { | ||||
|       "type": "string", | ||||
|       "description": "Tenant identifier scoped to the originating job." | ||||
|     }, | ||||
|     "ts": { | ||||
|       "type": "string", | ||||
|       "format": "date-time", | ||||
|       "description": "UTC timestamp when the job reached a terminal state." | ||||
|     }, | ||||
|     "payload": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": ["jobType", "job", "status", "occurredAt"], | ||||
|       "properties": { | ||||
|         "jobType": { | ||||
|           "type": "string", | ||||
|           "enum": ["build", "overlay"], | ||||
|           "description": "Job flavour, matches the CLR type of the serialized job payload." | ||||
|         }, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["completed", "failed", "cancelled"], | ||||
|           "description": "Terminal status recorded for the job." | ||||
|         }, | ||||
|         "occurredAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time", | ||||
|           "description": "UTC timestamp of the terminal transition, mirrors job.CompletedAt." | ||||
|         }, | ||||
|         "job": { | ||||
|           "oneOf": [ | ||||
|             {"$ref": "#/definitions/graphBuildJob"}, | ||||
|             {"$ref": "#/definitions/graphOverlayJob"} | ||||
|           ], | ||||
|           "description": "Canonical serialized representation of the finished job." | ||||
|         }, | ||||
|         "resultUri": { | ||||
|           "type": "string", | ||||
|           "description": "Optional URI pointing to Cartographer snapshot or overlay bundle (if available)." | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "attributes": { | ||||
|       "type": "object", | ||||
|       "description": "Optional correlation bag for downstream consumers.", | ||||
|       "additionalProperties": { | ||||
|         "type": "string" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "definitions": { | ||||
|     "graphBuildJob": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": [ | ||||
|         "schemaVersion", | ||||
|         "id", | ||||
|         "tenantId", | ||||
|         "sbomId", | ||||
|         "sbomVersionId", | ||||
|         "sbomDigest", | ||||
|         "status", | ||||
|         "trigger", | ||||
|         "attempts", | ||||
|         "createdAt" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "schemaVersion": { | ||||
|           "const": "scheduler.graph-build-job@1" | ||||
|         }, | ||||
|         "id": {"type": "string"}, | ||||
|         "tenantId": {"type": "string"}, | ||||
|         "sbomId": {"type": "string"}, | ||||
|         "sbomVersionId": {"type": "string"}, | ||||
|         "sbomDigest": { | ||||
|           "type": "string", | ||||
|           "pattern": "^sha256:[a-f0-9]{64}$" | ||||
|         }, | ||||
|         "graphSnapshotId": {"type": "string"}, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["pending", "queued", "running", "completed", "failed", "cancelled"] | ||||
|         }, | ||||
|         "trigger": { | ||||
|           "type": "string", | ||||
|           "enum": ["sbom-version", "backfill", "manual"] | ||||
|         }, | ||||
|         "attempts": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "cartographerJobId": {"type": "string"}, | ||||
|         "correlationId": {"type": "string"}, | ||||
|         "createdAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "startedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "completedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "error": {"type": "string"}, | ||||
|         "metadata": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": {"type": "string"} | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "graphOverlayJob": { | ||||
|       "type": "object", | ||||
|       "additionalProperties": false, | ||||
|       "required": [ | ||||
|         "schemaVersion", | ||||
|         "id", | ||||
|         "tenantId", | ||||
|         "graphSnapshotId", | ||||
|         "overlayKind", | ||||
|         "overlayKey", | ||||
|         "status", | ||||
|         "trigger", | ||||
|         "attempts", | ||||
|         "createdAt" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "schemaVersion": { | ||||
|           "const": "scheduler.graph-overlay-job@1" | ||||
|         }, | ||||
|         "id": {"type": "string"}, | ||||
|         "tenantId": {"type": "string"}, | ||||
|         "graphSnapshotId": {"type": "string"}, | ||||
|         "buildJobId": {"type": "string"}, | ||||
|         "overlayKind": { | ||||
|           "type": "string", | ||||
|           "enum": ["policy", "advisory", "vex"] | ||||
|         }, | ||||
|         "overlayKey": {"type": "string"}, | ||||
|         "subjects": { | ||||
|           "type": "array", | ||||
|           "items": {"type": "string"}, | ||||
|           "uniqueItems": true | ||||
|         }, | ||||
|         "status": { | ||||
|           "type": "string", | ||||
|           "enum": ["pending", "queued", "running", "completed", "failed", "cancelled"] | ||||
|         }, | ||||
|         "trigger": { | ||||
|           "type": "string", | ||||
|           "enum": ["policy", "advisory", "vex", "sbom-version", "manual"] | ||||
|         }, | ||||
|         "attempts": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "correlationId": {"type": "string"}, | ||||
|         "createdAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "startedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "completedAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "error": {"type": "string"}, | ||||
|         "metadata": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": {"type": "string"} | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user