# advisory.observation.updated@1 ยท Event contract Purpose: unblock CONCELIER-GRAPH-21-002 by freezing the platform event shape for observation changes emitted by Concelier. This is the only supported event for observation churn; downstreams subscribe for evidence fan-out and replay bundles. ## Envelope & transport - Subject: `concelier.advisory.observation.updated.v1` - Type/version: `advisory.observation.updated@1` - Transport: NATS (primary), Redis Stream `concelier:advisory.observation.updated:v1` (fallback). Both carry the same DSSE envelope. - DSSE payloadType: `application/vnd.stellaops.advisory.observation.updated.v1+json`. - Signature: Ed25519 via Platform Events signer; attach Rekor UUID when available. Offline kits treat the envelope as the source of truth. ## Payload (JSON) | Field | Type | Rules | | --- | --- | --- | | `eventId` | string (uuid) | Generated by publisher; idempotency key. | `tenantId` | string | `urn:tenant:{uuid}`; required for multi-tenant routing. | `observationId` | string (ObjectId) | Mongo `_id` of the observation document. | `advisoryId` | string | Upstream advisory identifier (e.g., CVE, GHSA, vendor id). | `source` | object | `{ vendor, stream, api, collectorVersion }`; lowercase vendor, non-empty. | `linksetSummary` | object | `{ aliases: string[], purls: string[], cpes?: string[], scopes?: string[], relationships?: object[] }` all arrays pre-sorted ASCII. | `supersedesId` | string (ObjectId, optional) | Previous observation `_id` if this is a new revision; omitted otherwise. | `documentSha` | string | SHA-256 of raw upstream document. | `observationHash` | string | Stable hash over canonicalized observation JSON (tenant, source, advisoryId, documentSha, fetchedAt). | `ingestedAt` | string (ISO-8601 UTC) | Timestamp when appended. | `traceId` | string (optional) | Propagated from ingest job/request; aids join with logs/metrics. | `replayCursor` | string | Monotone cursor for offline bundle ordering (tick from change stream resume token). ### Determinism & ordering - Arrays sorted ASCII; objects field-sorted when hashing. - `eventId` + `replayCursor` provide exactly-once consumer handling; duplicates must be ignored when `observationHash` unchanged. - No judgments: only raw facts and hash pointers; any derived severity/merge content is forbidden. ### Error contracts for Scheduler - Retryable NATS/Redis failures use backoff capped at 30s; after 5 attempts, emit `concelier.events.dlq` with the same envelope and `error` field describing transport failure. - Consumers must NACK on schema validation failure; publisher logs `ERR_EVENT_SCHEMA` and quarantines the offending observation id. ## Sample payload See `advisory.observation.updated@1.sample.json` (canonical field order, ASCII sorted arrays). Hashes intentionally short for readability; replace with real values in tests. ## Schema `advisory.observation.updated@1.schema.json` provides a JSON Schema (draft 2020-12) for runtime validation; any additional fields are rejected. ## Compatibility note Sprint tracker referenced `sbom.observation.updated`; this contract standardises on `advisory.observation.updated@1`. If a legacy alias is required for interim consumers, mirror the envelope on subject `surface.sbom.observation.updated.v1` with identical payload.