Task: Validate junction codegen against tenor_convention_resolution; retire hand-authored code
Table of Contents
This page documents a task in the Retire legacy codegen profile system; add junction support to physical-space codegen story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Retire the hand-authored stopgap code once real junction codegen works,
so tenor_convention_resolution ends up on the same generated-code
footing as every other entity in the codebase, per this story's whole
point.
Hand-authored reference files to diff against and then delete (written while this story was being scoped, as a temporary stand-in — see the Tenor management UI task for the full context of why they exist):
projects/ores.marketdata/core/include/ores.marketdata.core/repository/tenor_convention_resolution_entity.hppprojects/ores.marketdata/core/include/ores.marketdata.core/repository/tenor_convention_resolution_repository.hppprojects/ores.marketdata/core/src/repository/tenor_convention_resolution_repository.cpp
Also hand-authored, and not to be deleted by this task (junction codegen deliberately excludes service/protocol — see story Decisions), but which need repointing from the hand-authored repository to the generated one once it exists:
- Whatever service/protocol/handler the Tenor management UI task ends up writing by hand (files not yet fixed at the time this story was written — check that task's Result for the final file list).
projects/ores.marketdata/core/src/messaging/registrar.cpp— the hand-added subscription forget_tenor_convention_resolutions_requestthere should keep working unchanged; only the handler's internal repository dependency changes.
Steps: regenerate tenor_convention_resolution with the now-working
junction codegen; diff the generated entity/repository against the
hand-authored files above field-by-field (naming, types, nullability);
resolve any discrepancies (prefer the generated shape unless it's
actually wrong, in which case fix the templates, not this task's diff);
delete the hand-authored entity/repository files; update the
hand-authored service to depend on the generated repository class
instead; rebuild and re-run the marketdata test suite plus the Tenor
management UI screen manually to confirm nothing regressed.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Retire legacy codegen profile system; add junction support to physical-space codegen |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-13 |
Acceptance
- The three hand-authored entity/repository files listed above are deleted; the generated repository is used instead.
- The hand-authored service (from the Tenor management UI task) is repointed at the generated repository and still builds.
ores.marketdata.core(and any other affected component) builds clean; the marketdata test suite passes.~The Tenor management UI screen still shows correct, identical data after the swap (manual check).~Skipped: attempting to start the client hit an unrelated build-target naming issue (ores.controller.serviceisn't a real target); the service/protocol/ handler contract (list_resolutions(), the request/response shape) is unchanged by this task, andores.refdata.service.testspassing is reasonable confidence without spending more time chasing the client build here. Deferred to whoever actually runs the Tenor management screen next, per explicit user instruction.
Plan
Note: the entity had moved from ores.marketdata to ores.refdata
since this task was originally scoped (see the Tenor management UI
task's Result) – worked against the current ores.refdata paths
throughout, not the stale ores.marketdata ones in this task's Goal
text.
- Generating the full supported set (
compass codegen entity generate tenor_convention_resolution, no--address) revealed a real architectural fork: the generated domain structores::refdata::domain::tenor_convention_resolutioncollides with a different, hand-authored struct of the exact same fully-qualified name intenor_resolution.hpp(the tenor resolver's own narrow, 5-field read view –tenor_window=/=resolve_end_date=/ =resolve_window=/=windows_overlaplive in that same file and are not duplicated, genuinely unique resolver logic that must stay).tenor_resolution.hpp's own doc comment claimed "a junction has no domain-object identity of its own, so codegen generates no C++ struct" – checked against real production junctions (party_currency,party_country) and found that's false: they do have normal generated domain headers. So the collision was specific to this one entity's hand-authored assumption, not a general junction-codegen limitation. - Surfaced a second real discrepancy: the hand-authored repository is
deliberately read-only (
read_all()only, nowrite=/=remove– "this junction's rows are reference data managed via Foundation-layer SQL provisioning, not user edits through this repository"), but junction codegen unconditionally generates full CRUD. - Both forks were real "prefer the generated shape unless it's
actually wrong, in which case fix the templates" cases per this
task's own Goal text – asked the user for direction on each rather
than picking unilaterally, given they change what "retire the
hand-authored code" even means:
- Domain struct: generate it properly (matching every other
junction) and retire
tenor_resolution.hpp's duplicate struct, repointing its 4 consumers at the generated one, rather than inventing a permanent one-off "reuse an external struct" override mechanism. - Read-only: added a genuine
:read_only: truejunction flag to codegen (org_loader.pyalready lifted* C++ ** Flagskeys onto the junction dict from task 3's fix) rather than accept a full CRUD surface neither the model nor any caller wants. Wrappedwrite=/=remove*in\{\{^read_only\}\}...\{\{/read_only\}\}in bothores.cpp.repository.repository_header.organd...repository_impl.org; the doc comment also branches (Reads ... Read-only: ...vsReads and writes ...). Set the flag ontenor_convention_resolution.org's** Flagsdrawer.
- Domain struct: generate it properly (matching every other
junction) and retire
- Filled in the model's missing
generatorbabel blocks (per-column synthetic-data expressions) –left=/=right(convention_code=/ =tenor_code, real seed codesRATES_SPOT_FORWARD=/=1M) and the three nullableColumns(std::nullopteach, matching the "usually absent" nature described in each column's own prose). Without these the generator template emitsr.x = ;(empty RHS, syntax error) – flagged but deliberately not fixed in the prior task since it's model content, not template logic. - Regenerated the full set; deleted the hand-authored
struct tenor_convention_resolutionfromtenor_resolution.hpp(kepttenor_window=/=resolve_*=/=windows_overlap– genuinely unique, not generated), replaced with#include "...domain/tenor_convention_resolution.hpp". Repointed its 4 consumers:tenor_convention_resolution_service.hpp=/.cpp= (repository construction changed from a per-callcontextparam to a constructor-owned one,read_all(ctx)->read_latest()),tenor_convention_resolution_protocol.hpp(include swap only), anddomain_tenor_resolution_tests.cpp(no change needed – already transitively pulls the type throughtenor_resolution.hpp). The handler (tenor_convention_resolution_handler.hpp) needed no change – its service construction call was already interface-stable. - Also fixed a redundant
order_by(convention_code, convention_code)in the generatedread_latest()query – the model's:order_column:was set to the same column as the left FK; changed totenor_codefor a meaningful secondary sort. - Ran a full
cmake --preset ...reconfigure (new generated.cppfiles need a freshGLOB_RECURSE, not just a rebuild, to be picked up) before the first real build attempt. - Verified:
ores.refdata.core.lib=/.api.lib= build clean;ores.refdata.api.tests=/.core.tests=/=.service.tests= all pass (3/3 suites, including the 126s.core.testsrun covering the retained resolver logic);validate_schemas.shclean (252 tables, 0 warnings);book(domain_entity) still regenerates byte-identical, confirming no regression to the sample used throughout this story.
Notes
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 |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1587 | [codegen,refdata] Retire hand-authored tenor_convention_resolution |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Cross-tenant data leak: junction repository read/remove queries never filtered by tenant_id despite has_tenant_id: true | ores.cpp.repository.repository_impl.org | Accepted | Real bug. Fixed in 389adc0c5 – {{#has_tenant_id}}-gated filtering added to all affected methods, verified against tenor_convention_resolution + party_currency. |
| 2 | Include-guard inconsistency (component_upper vs component_core_upper) between entity_header.org and repository_header.org/mapper_header.org | repository_header.org, mapper_header.org | Already fixed | Fixed upstream in PR #1586 (this branch rebased onto it). |
Result
tenor_convention_resolution is fully generated now (domain,
generator, repository – SQL already worked): the hand-authored
entity/repository files are replaced by generated ones, and
tenor_resolution.hpp's duplicate domain struct is retired in favour
of the generated type (its genuinely unique resolver logic –
tenor_window, resolve_end_date, resolve_window,
windows_overlap – is untouched). Added a real :read_only: true
junction codegen flag so the generated repository matches this
entity's actual (and every future read-only junction's) intended
shape, rather than forcing an unwanted CRUD surface or hand-waving the
diff. ores.refdata.api.lib=/.core.lib= build clean;
ores.refdata.api.tests=/.core.tests=/=.service.tests= all pass (3/3);
validate_schemas.sh clean; book still zero-diff. Manual UI check
(Tenor management screen showing identical data) not yet done – see
next message to the user.