Finish the eventing payload migration, or read the legacy shape
Table of Contents
This page is a capture in the inbox bucket of the product backlog — a pre-sprint idea, not yet pulled into a sprint as a story.
1. What
Every database change notification must be readable by the process that turns it into a NATS event. Today 123 of the 209 generated triggers emit a payload the reader rejects.
projects/ores.eventing/core/src/service/postgres_event_source.cpp reads each
notification with rfl::json::read<domain::entity_event_notification>(payload),
which is strict: the struct requires event_id, entity, key, action,
version, occurred_at and tenant_id, and a missing field fails the read. On
failure the callback logs and returns, so the notification is dropped rather than
converted.
The generated triggers emit two shapes. 86 of the 209
projects/ores.sql/create/**/*_notify_trigger_create.sql files build the
canonical one; the other 123 still build the older
{"entity": "...", "timestamp": "...", "entity_ids": ["..."], "tenant_id": "..."}
so their channels publish nothing at all. The code's own comment says the legacy
shape is meant to be converted rather than parsed as an entity change event —
but the conversion is unreachable, because the parse fails first and the function
returns before the channel_entities_ lookup.
Either finish the migration (regenerate the remaining 123 triggers from the canonical template) or make the reader fall back to the legacy shape and convert it, which is what the comment already promises. The first is the smaller change and the one the model generator already supports.
2. Why
Found on 2026-09-26 while re-verifying the ores.analytics clean pass on the
rebased base. The full suite fell from 69 of 70 to 66 of 71, and every new
failure was the same assertion — !(received.empty()) in a
*_eventing_integration_tests case — in ores.trading.core.tests,
ores.ore.core.tests, ores.reporting.core.tests and
ores.marketdata.core.tests, plus several in ores.synthetic.core.tests.
The trading service log carries the cause verbatim, 322 times in one run:
[ERROR] [ores.eventing.service.postgres_event_source] Failed to deserialize
notification payload (total failures: 322): {"entity":
"ores.trading.fx_forward_instrument", "tenant_id": "...", "timestamp": "...",
"entity_ids": ["..."]}
The trigger fires and the listener delivers; the payload cannot be read. The entities affected are exactly the ones whose migrations have not yet moved them to the canonical trigger, which is why whole components fail rather than single cases.
Two things make this worth fixing promptly:
- It is silent in production. A change commits, the UI's notification path receives nothing, and the only trace is an ERROR line in a service log. Nothing in CI can see it: the assertion needs a live fleet and a database, which no workflow has.
- It is a half-finished migration, so it grows. Every component still to be converted adds more entities whose events are dropped, and the failure is invisible until someone runs the live suite.
3. References
projects/ores.eventing/core/src/service/postgres_event_source.cpp— the strict read, the early return and the unreachable conversion.projects/ores.eventing/api/include/ores.eventing.api/domain/entity_event.hpp—entity_event_notificationand the fields it requires.projects/ores.sql/create/trading/trading_equity_swap_instruments_notify_trigger_create.sql— one of the 123 legacy triggers.projects/ores.eventinghistory:d6701b43b6and0597a51525changed the struct; the trigger migration followed only for the components converted since.
4. See also
- Bring ores.analytics to the clean standard — where this was found, and the V02 limitation it is recorded as.