Task: Build ORE's comma-joined calendar string only at the XML export boundary

Table of Contents

This page documents a task in the Finish integrating calendar reference data: XML export and DQ publish story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

ores.ore's conventions_mapper (or successor) should build ORE's comma-joined calendar-name string from the new FK relationship(s) only at the XML export boundary — the domain model itself must never store the joined string, only the individual calendar FK(s).

2. Status

Field Value
State DONE
Parent story Finish integrating calendar reference data: XML export and DQ publish
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-07-23

3. Acceptance

  • [X] Exporting a currency_pair_convention to ORE XML produces the correct comma-joined calendar string, derived from the junction table at export time.
  • [X] No code path writes a comma-joined string back into the domain model.
  • [X] Round-trip (import → export) tested against a real conventions.xml fragment.

4. Plan

Investigated ores.ore's conventions_mapper first: the mapper-level goal ("build the joined string only at export time, never store it") was already fully satisfied by an earlier sprint-23 task (Migrate currency_pair_convention.advance_calendar onto the calendar model) — reverse_fx() already joins mapped_fx.advance_calendars (a std::vector<std::string>, never persisted on the domain model) only at the XML export boundary, and an existing round-trip test (xml_fx_convention_mapper_roundtrip_tests.cpp) already verifies this against the real conventions.xml example file, field-by-field.

The actual gap was one level down: ores.cli's import_conventions parsed AdvanceCalendar's individual calendar codes into mapped_fx.advance_calendars correctly, but never persisted them anywhere — the calendar FK relationship was silently dropped on import, never reaching the currency_pair_convention_calendar junction table. Fixed in projects/ores.cli/src/app/application.cpp: import_conventions now writes one junction row per (pair_code, calendar_code) pair, deduplicated (the real example file lists some pairs, e.g. EUR/USD, more than once with different SpotDays, which would otherwise send a duplicate key into the same batch insert and fail it atomically).

Added a repository-level test to ores.refdata.core.tests (repository_currency_pair_convention_calendar_repository_tests.cpp) proving the junction table correctly round-trips a full multi-calendar set for a pair (write N calendars linked to one pair, read back exactly those N codes) — this is the piece that's genuinely "derived from the junction table at export time".

Considered and backed out of a cross-component integration test (ores.ore mapper + a real Postgres junction read/write together): it needed real tenant provisioning to satisfy the junction table's tenant_id validation trigger, which is expensive (see the fast-minimal-test-tenant-provisioning capture filed along the way — ~193s just for ores.refdata.core.tests's existing tenant provisioning/deprovisioning overhead). Adding the same machinery to ores.ore.core.tests (previously DB-free, 480 tests in 5.85s) to support one test ballooned it to ~499s. Splitting the concern in two made the expensive test unnecessary: the DB-only piece (junction correctness) belongs in ores.refdata.core.tests where the provisioning cost is already paid; the mapper-only piece (correct joining) was already covered without any DB dependency.

Also attempted an ores.cli-level integration test (import_conventions_persists_advance_calendars_to_junction_table in app_application_import_tests.cpp) exercising the fix end-to-end via a real import of conventions.xml. It passed reliably when run standalone (confirmed via direct SQL inspection that the fix persists correct data) but failed reliably under ctest specifically, for a cause not identified despite ruling out environment-variable content, CMake env staleness, database role/user, and tenant-ID matching (all confirmed identical between the passing and failing runs). Rather than continue chasing an apparent ctest-vs-direct-execution discrepancy unrelated to the fix's correctness, dropped that test and relied on the ores.refdata.core.tests repository-level test instead.

5. Notes

The ctest-specific test flakiness above may be worth a follow-up investigation if it recurs elsewhere — reliably reproducible (3/3 ctest runs failed, 2/2 direct runs of the identical binary passed) but not something to hold this task on, since the underlying fix was independently verified correct via direct SQL inspection.

6. PRs

PR Title
#1679 [cli,refdata,agile] Persist AdvanceCalendar codes to the junction table on import

7. Review

# Comment summary File Decision Notes
1 Junction rows unioned across duplicate pair entries instead of following the convention row's last-one-wins semantics application.cpp Accepted Now group by pair_code first and only emit calendars for the last fx entry per pair
2 Repository constructed inside the file loop instead of once above it application.cpp Accepted Hoisted alongside the other repositories
3 total_slots bumped 10 -> 12 without the new test needing indices past 10 repository_currency_pair_convention_calendar_repository_tests.cpp Accepted Reverted to 10

8. Result

The mapper-level goal was already satisfied by a prior sprint-23 task; the real gap was ores.cli's import_conventions silently dropping AdvanceCalendar's individual calendar codes instead of persisting them to the currency_pair_convention_calendar junction table. Fixed, with dedup for the real example file's duplicate-pair-code entries. Added a repository-level test to ores.refdata.core.tests proving full multi-calendar round-trip correctness through the junction table; relied on the existing ores.ore mapper round-trip test (unchanged) for the XML-joining half. Filed a follow-up capture for the expensive test-tenant provisioning discovered along the way. Full local build clean; ores.refdata.core.tests (148 tests, includes the new case) passes reliably under ctest.

Emacs 29.3 (Org mode 9.6.15)