History & Observability
There are two kinds of "history" that are easy to confuse. This page separates them and shows where to look/observe — important for support and debugging.
Two kinds of history — do not confuse them
| Entity audit history | Notification-run history | |
|---|---|---|
| Answers | "What changed on an entity?" | "How was a notification scheduled / sent / skipped / delivered?" |
| Stored in | EntityHistory (registry + HistoryEventKey + upsert) | NotificationJob / AutomationScheduler / OutgoingMessage … |
| Nature | Projection of entity state history | Run record of a notification/automation |
| Page | Platform architecture — History section | This page |
⚠️
NotificationJobis a notification-run record, not an entity audit record. Do not openNotificationJobto inspect "what changed on the entity" — that isEntityHistory.
Lookup by question (which source answers what)
| Question | Source |
|---|---|
| Automation run status / trace | AutomationScheduler (+ InputHash, DispatchResultJson, TraceBlobName) |
| Trace mirror | AutomationJob (Azure Table) |
| Notification event (run) | NotificationJob — PartitionKey = EventKey (random GUID on direct path; deterministic entity-event:... on event-derived path — not Entity.Guid) |
| Entity audit (state change) | EntityHistory — PartitionKey = Entity.Guid, deterministic HistoryEventKey |
| In-app / user state | UserMessage — PartitionKey = user:{User.Id} / employee:{Employee.Guid} |
| Provider delivery | OutgoingMessage — PartitionKey = NotificationJob.PartitionKey/EventKey (transactional) or MessagingJob.Guid (campaign) |
| Report/campaign run | MessagingJob (SQL + blob payload) |
| Work that was skipped/deactivated/UI-only | AutomationScheduler (support lookup) |
Re-key note: changing
notification-historykeys transactional messages by source (for exampleinvoice:{guid},account:{guid}) so history opens where users expect.
"Hybrid-light" strategy
No broad source-link table, no AutomationOutcomeIndex in MVP:
- Main entity history is looked up through Azure key policy (PartitionKey = entity the user opens).
- Skipped / deactivated / UI-only outcomes are looked up through support views on
AutomationScheduler.
Observability
| Signal | Source | Status |
|---|---|---|
| Event-derived job creation log | EntityEventNotificationPostProcessor logs one structured line (EventTypeId/EventKey/BusinessUnitId) | ✅ Exists — enough to count "which type, how many, through worker". |
| Detailed automation trace | AutomationScheduler.TraceBlobName (private blob, streamed through authorized API) | ✅ When support needs it. |
| Dispatch result | AutomationScheduler.DispatchResultJson (SkippedReasonCode/ConflictReasonCode/Summary) | ✅ Small, merged on retry, not deleted. |
| Aggregate metric/dashboard | — | ⏳ Follow-up (requires metrics system). |
Unit test delivery layer (SendXxxNotification) | — | ⏳ Test debt (no harness available). |
"Why did this not send?" — quick checklist
- Is there an
AutomationSchedulerrow? What status (Deactivated/Skipped/Failed/IsOnSupportHold)? - Does
DispatchResultJsonhave aSkippedReasonCode/ConflictReasonCode? - BU Notification Settings: has that type been seeded into
NotificationOption+ has subscriber? - Is there an
OutgoingMessagerow, and what status (Created/Sending/FailedFinal)? - Need deeper trace → read
TraceBlobName.
Related
- Platform architecture — how
EntityHistoryis created (audit). - Automation & Reminders —
AutomationSchedulerlifecycle. - Communication — Common cases — skip/deactivate.