Story: Junction eventing: extend codegen with notify-trigger, changed-event, and event-registrar facets for the junction model type
Table of Contents
This page documents a story in Product backlog — inbox, carried unfinished from Sprint 24 at close. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
Goal
Give junction tables the same live NATS eventing that regular entities already get, by extending codegen rather than hand-wiring one junction at a time.
Background
Discovered while testing Badge palette swatch rendering + data-driven fallback badge
in Badge colour scheme: visual polish and self-badging fixes: a
badge_mapping row was removed via SQL to verify the new __unmapped__
fallback badge renders, and the running Qt client never picked it up –
not even after reopening the affected list window. Root cause: unlike
badge_definition (edited through the UI, which does propagate live
to every other client via BadgeCache's new subscribe-and-reload fix),
badge_mapping is a #+type: ores.codegen.junction model with no
notify trigger, no changed-event type, and no event-registrar –
there is structurally nothing to subscribe to.
A codebase-wide check confirms this is not badge_mapping-specific: no
junction table in ORE Studio has a notify trigger:
party_currency, party_country, party_counterparty,
currency_currency_group (refdata), account_party (iam),
dataset_bundle_member (dq), app_version_platform (compute).
The eventing pipeline for a regular entity is four generated artefacts, per the Eventing Pipeline knowledge doc and Entity-composed registrars:
- SQL notify trigger – ores.sql.schema.notify_trigger archetype:
pg_notifyon insert/update/delete, payload\{entity, timestamp, entity_ids, tenant_id\}. - A
changed_eventtype +event_traitsspecialisation – theores.cpp.nats-eventingfacet. - A server-side event-mapping registrar wiring the Postgres channel to the NATS subject – the ores.cpp.nats-event-registrar facet.
- A Qt-side subscription (hand-written per controller today, or via the subscribe-and-reload cache pattern).
All three server-side facets (1-3) declare themselves against
#+type: ores.codegen.entity only. The junction model type
(#+type: ores.codegen.junction, see the ores.sql.schema.junction_create
archetype) generates only table DDL – composite primary key over
junction.left=/=junction.right, optional tenant_id, per-side
indexes. Nothing eventing-related exists for it. This also means the
existing "Refdata entity NATS event registrar audit" story (scoped to
"entities with a changed_event type") structurally can't see this gap
– a junction never has a changed_event type to audit in the first
place.
Status
| Field | Value |
|---|---|
| State | BACKLOG |
| Carried from | Sprint 24 (unfinished at close) |
| Now | Not yet started. |
| Waiting on | Nothing. |
| Next | Break the story into tasks. |
| Last touched | 2026-07-26 |
Acceptance
- A junction-flavoured notify trigger archetype exists (parallel to
ores.sql.schema.notify_trigger), firing
pg_notifyon insert/update/delete of a junction row. The payload'sentity_idsfield needs a shape decision up front: the entity-side payload assumes one identifier per changed row, but a junction row is identified by a(left, right)pair, not a scalar – e.g. encode each changed row as a"left:right"composite string, or as a nested[left, right]JSON pair, consistent with howjunction.left=/=junction.rightare already named in the model. - A junction-flavoured
changed_eventtype +event_traitsgenerator exists (mirroringores.cpp.nats-eventingbut reading from thejunctionsection of the model instead of the entity's primary key). - A junction-aware
nats-event-registrarfacet variant exists, wiring the Postgres channel to the NATS subject the same way ores.cpp.nats-event-registrar does for entities. badge_mappingis regenerated as the first consumer (mirroring howparty_cachewas the first consumer of the entity-mirror cache facet): it gets a realbadge_mapping_changed_event, andBadgeCache(see How does a Qt client cache stay fresh?) subscribes to it the same way it already subscribes tobadge_definition_changed, so a mapping edit (through the UI, SQL, or any other path) refreshes every Qt client live, closing the gap the fallback-badge manual QA scenario surfaced.- The originating task's test scenario (Verify data-driven fallback badge and badge eventing) is updated to drop its "log out and back in" workaround for the mapping-removal step once live refresh works, and re-run to confirm.
- At least one other junction (e.g.
party_currencyorparty_country) is regenerated onto the new facets in the same story, to prove the facet generalises and isn't a badge_mapping special case.
Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
Decisions
Out of scope
- Regenerating every junction onto the new facets in this story –
one worked example (
badge_mapping) plus one generalisation check is enough to prove the facet; migrating the remainder (party_counterparty,currency_currency_group,account_party,dataset_bundle_member,app_version_platform) is follow-up work, the same way the entity-side "Refdata entity NATS event registrar audit" story tracks remaining entity migrations separately from the facet's original introduction. - Building a management UI for junctions in general – most junctions
(including
badge_mapping) are explicitly seed-only with "no management UI needed"; this story is about eventing reaching existing/future editors (SQL, admin tooling, or a UI if one is ever built), not about building new UI. - Changing the entity-side eventing facets themselves – this story
adds a parallel junction variant, it does not refactor
ores.cpp.nats-eventing=/=ores.cpp.nats-event-registrarfor entities.