Task: Handle compound keys in the generated eventing artefacts

Table of Contents

This page documents a task in the Complete the market series classification story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Make the generated eventing artefacts carry a compound natural key faithfully. Today the changed event names its key vector after the first key column, and the generated eventing test compares that vector as a set, so both the name and the match are only right for a single-column key.

Two defects, one template area, and both surface on series_classification_rule, whose key is (series_type, metric).

2. Status

Field Value
State ABANDONED
Parent story Complete the market series classification
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-23

3. Acceptance

  • The changed event carries a key member per key column, each named after the column it holds, so a consumer reads the parts rather than guessing which slot is which.
  • The generated eventing test matches a notification on the exact key, so a permutation of the key parts no longer matches.
  • Every model with a compound key that opts into eventing is regenerated, and the models are named in the result.
  • The suites that hold the regenerated eventing tests are re-run green.

4. Plan

(Implementation strategy. Written when work starts; key decisions are distilled into the parent story's * Decisions at close, but the plan itself stays — it is the historical record of what we did.)

5. Notes

Found during the review round of register-the-unmapped-series-types, which added the first compound-key model to opt into generated eventing.

The event shape comes from projects/ores.codegen/library/templates/cpp_nats_changed_event.hpp.mustache, whose line reads std::vector<std::string> {{primary_key.column}}s;. The template takes the first key column and pluralises it, so the generated event for the classification rule declares std::vector<std::string> series_types while the notify trigger fills it from every key column:

entity_ids = jsonb_build_array(changed_series_type, changed_metric)

The member therefore holds the type and the metric under a name that claims to hold types alone. It is not wrong for the single-column keys every other opted-in model has, which is why it has gone unnoticed.

The test side is a weaker check than it looks. The generated case matches with std::all_of over a std::find into the notification's key vector, which is set membership rather than key equality: a notification arriving for (type, metric) would satisfy a test that expects (metric, type). The fresh tenant the case runs in emits only the notification it wrote, so the permutation is unreachable today.

Both fixes reach generated output across components, so the work is a template change plus a tree-wide regeneration rather than an edit to the classification rule artefacts. Nothing in this task changes the classifier or the import path.

6. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario, run through the QA Validation Runner panel) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

7. PRs

PR Title
   

8. Review

Comment summary File Decision Notes
       

9. Result

Abandoned at sprint 25 close. The Qt client was removed, and the codegen work goes to the sprint 26 per-module codegen pass.

Emacs 29.3 (Org mode 9.6.15)