# signals.fact.updated event contract (SIGNALS-24-005 prep) **Purpose**: replace the in-memory logger used during Signals development with a real event bus contract so reachability caches can be invalidated and downstream consumers (Policy Engine, Notifications, Console) can subscribe deterministically. ## Topic / channel - Primary topic: `signals.fact.updated.v1` - Dead-letter topic: `signals.fact.updated.dlq` - Delivery: at-least-once; consumers must de-duplicate using `event_id`. ## Message envelope ```jsonc { "event_id": "uuid-v4", // stable across retries; used for idempotency "emitted_at": "2025-11-20T00:00:00Z", // UTC, RFC3339 "tenant": "acme", // required; lower-case "subject_key": "sbom:sha256:…" , // subject of facts (asset, sbom, host). Deterministic model key. "fact_kind": "callgraph" | "runtime" | "reachability" | "signal", // enums mapped from Signals domain "fact_version": 1, // monotonically increasing per subject_key + fact_kind "digest": "sha256:…", // CAS digest of canonical fact document "content_type": "application/json", // or application/vnd.stellaops.ndjson when chunked "producer": "StellaOps.Signals", // emitting service "source": { "pipeline": "signals", // consistent with Observability tags "release": "0.4.0-alpha" // optional }, "trace": { "trace_id": "…", // pass-through if available "span_id": "…" } } ``` ## Routing / partitions - Partition key: `tenant` to keep per-tenant ordering. - Retry policy: exponential backoff up to 5 minutes; move to DLQ thereafter with `dlq_reason` header. ## Consumer expectations - De-duplicate on `event_id` and `digest`. - Fetch fact body from CAS using `digest`; avoid embedding large payloads in the message. - If consumer cannot resolve CAS, treat as transient and retry later (do not drop). ## Security / air-gap posture - No PII; tenant id only. - Works offline when bus is intra-cluster (e.g., NATS/Redis Streams); external exporters disabled in sealed mode. ## Provenance - This contract supersedes the temporary log-based publisher referenced in Signals sprint 0143 Execution Log (2025-11-18). Aligns with `signals.fact.updated@v1` payload shape already covered by unit tests. - Implementation: `Signals.Events` defaults to Redis Streams (`signals.fact.updated.v1` with `signals.fact.updated.dlq`), emitting envelopes that include `event_id`, `fact_version`, and deterministic `fact.digest` (sha256) generated by the reachability fact hasher. - Router transport: set `Signals.Events.Driver=router` to POST envelopes to the StellaOps Router gateway (`BaseUrl` + `Path`, default `/router/events/signals.fact.updated`) with optional API key/headers. This path should forward to downstream consumers registered in Router; Redis remains mandatory for reachability cache but not for event fan-out when router is enabled.