# advisory.linkset.updated@1 · Event contract Purpose: unblock CONCELIER-LNM-21-005 by freezing the platform event shape for linkset changes emitted by Concelier. This is the only supported event for linkset churn; downstreams subscribe for graph overlays, policy evaluations, and replay bundles. ## Envelope & transport - Subject: `concelier.advisory.linkset.updated.v1` - Type/version: `advisory.linkset.updated@1` - Transport: NATS (primary), Redis Stream `concelier:advisory.linkset.updated:v1` (fallback). Both carry the same DSSE envelope. - DSSE payloadType: `application/vnd.stellaops.advisory.linkset.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. | | `linksetId` | string (ObjectId) | Mongo `_id` of the linkset document. | | `advisoryId` | string | Upstream advisory identifier (e.g., CVE, GHSA, vendor id). | | `source` | string | Linkset source/adapter identifier (lowercase). | | `observationIds` | string[] | Array of observation ObjectIds included in this linkset; sorted ASCII. | | `delta` | object | Change description: `{ type, observationsAdded, observationsRemoved, confidenceChanged, conflictsChanged }`. | | `confidence` | number (0–1, nullable) | Correlation confidence score; null if not computed. | | `conflicts` | object[] | Array of `{ field, reason, sourceIds[] }` conflict summaries; sorted by field then reason. | | `provenance` | object | `{ observationHashes[], toolVersion?, policyHash? }` for replay/audit. | | `createdAt` | string (ISO-8601 UTC) | Timestamp when linkset was built. | | `replayCursor` | string | Monotone cursor for offline bundle ordering (tick from createdAt). | | `builtByJobId` | string (optional) | Job ID that built this linkset. | | `traceId` | string (optional) | Propagated from ingest job/request; aids join with logs/metrics. | ### Delta object | Field | Type | Rules | | --- | --- | --- | | `type` | string | `"created"` or `"updated"`. | | `observationsAdded` | string[] | Observation IDs added since previous version. | | `observationsRemoved` | string[] | Observation IDs removed since previous version. | | `confidenceChanged` | boolean | True if confidence score changed. | | `conflictsChanged` | boolean | True if conflicts array changed. | ### Determinism & ordering - Arrays sorted ASCII; objects field-sorted when hashing. - `eventId` + `replayCursor` provide exactly-once consumer handling; duplicates must be ignored when observation hashes unchanged. - No judgments: only raw facts, delta descriptions, and provenance 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 linkset id. ## Sample payload ```json { "eventId": "550e8400-e29b-41d4-a716-446655440000", "tenantId": "urn:tenant:acme-corp", "linksetId": "6744abcd1234567890abcdef", "advisoryId": "CVE-2024-1234", "source": "nvd", "observationIds": ["674400001234567890abcdef", "674400001234567890abcde0"], "delta": { "type": "created", "observationsAdded": ["674400001234567890abcdef", "674400001234567890abcde0"], "observationsRemoved": [], "confidenceChanged": true, "conflictsChanged": false }, "confidence": 0.85, "conflicts": [], "provenance": { "observationHashes": ["sha256:abc123", "sha256:def456"], "toolVersion": "1.0.0", "policyHash": null }, "createdAt": "2025-11-27T12:00:00.000Z", "replayCursor": "638688000000000000", "builtByJobId": "job-12345", "traceId": "trace-67890" } ``` ## Schema `advisory.linkset.updated@1.schema.json` provides a JSON Schema (draft 2020-12) for runtime validation; any additional fields are rejected. ## Implementation (LNM-21-005) - Event type defined in `StellaOps.Concelier.Core.Linksets.AdvisoryLinksetUpdatedEvent`. - Publisher interface: `IAdvisoryLinksetEventPublisher`. - Outbox interface: `IAdvisoryLinksetEventOutbox`. - Configuration: `AdvisoryLinksetEventPublisherOptions`. ## Change control - Add-only. Adjusting delta types or conflict codes requires new version `advisory.linkset.updated@2` and a sprint note.