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:

  1. SQL notify trigger – ores.sql.schema.notify_trigger archetype: pg_notify on insert/update/delete, payload \{entity, timestamp, entity_ids, tenant_id\}.
  2. A changed_event type + event_traits specialisation – the ores.cpp.nats-eventing facet.
  3. A server-side event-mapping registrar wiring the Postgres channel to the NATS subject – the ores.cpp.nats-event-registrar facet.
  4. 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_notify on insert/update/delete of a junction row. The payload's entity_ids field 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 how junction.left=/=junction.right are already named in the model.
  • A junction-flavoured changed_event type + event_traits generator exists (mirroring ores.cpp.nats-eventing but reading from the junction section of the model instead of the entity's primary key).
  • A junction-aware nats-event-registrar facet variant exists, wiring the Postgres channel to the NATS subject the same way ores.cpp.nats-event-registrar does for entities.
  • badge_mapping is regenerated as the first consumer (mirroring how party_cache was the first consumer of the entity-mirror cache facet): it gets a real badge_mapping_changed_event, and BadgeCache (see How does a Qt client cache stay fresh?) subscribes to it the same way it already subscribes to badge_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_currency or party_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-registrar for entities.

Emacs 29.3 (Org mode 9.6.15)