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:

FieldTypeNotes
eventIduuidGlobally unique identifier generated per occurrence.
kindstringEvent identifier; Scanner emits scanner.event.report.ready and scanner.event.scan.completed.
versionintegerSchema version. Initial release uses 1.
tenantstringTenant that owns the scan/report. Mirrors Authority claims.
occurredAtdate-timeUTC instant when the underlying state transition happened (e.g., report persisted).
recordedAtdate-timeUTC instant when the event was durably written. Optional but recommended.
sourcestringProducer identifier (scanner.webservice).
idempotencyKeystringDeterministic key for duplicate suppression (see §4).
correlationIdstringMaps back to the API request or scan identifier.
traceId / spanIdstringW3C trace context propagated into downstream telemetry.
scopeobjectDescribes the affected artefact. Requires repo and digest; optional namespace, component, image.
attributesobjectFlat string map for frequently queried metadata (e.g., policy revision).
payloadobjectEvent-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:
    • report.ui/ui/reports/{reportId} on the current host.
    • report.api{apiBasePath}/{reportsSegment}/{reportId} (defaults to /api/v1/reports/{reportId}).
    • policy.ui/ui/policy/revisions/{revisionId} when a revision is present.
    • policy.api{apiBasePath}/{policySegment}/revisions/{revisionId} when a revision is present.
    • attestation.ui/ui/attestations/{reportId} when a DSSE envelope is included.
    • attestation.api{apiBasePath}/{reportsSegment}/{reportId}/attestation when a DSSE envelope is included.
    • UI routes honour the configurable scanner:console options (basePath, reportsSegment, policySegment, attestationsSegment) so operators can move links under /console without code changes.
  • imageDigest — OCI image digest associated with the analysis.
  • generatedAt — report generation timestamp (ISO-8601 UTC).
  • verdictpass, 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 (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 eventReplacement orchestrator eventNotes
scanner.report.readyscanner.event.report.readyAdds versioning, idempotency, trace context. Payload is a superset of the legacy fields.
scanner.scan.completedscanner.event.scan.completedSame 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 kindIdempotency key template
scanner.event.report.readyscanner.event.report.ready:<tenant>:<reportId>
scanner.event.scan.completedscanner.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.reportpolicy.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 WebServiceSCANNER-EVENTS-16-301 (blocked) and SCANNER-EVENTS-16-302 (done) track the production of these envelopes. Dispatcher link customisation landed and samples updated; full dotnet test suite now succeeds after Surface cache ctor drift was patched and DSSE fixtures re-synced (2025-11-06).
  • 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/modules/devops/runbooks/launch-cutover.md to confirm downstream validation before the production cutover. Record gaps or newly required fields in docs/modules/devops/runbooks/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.