Story: Fix codegen template drift
Table of Contents
This page documents a story in Sprint 25. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
Fix four codegen template bugs at their source — the org archetype
under projects/ores.codegen/library/templates/ that each
*.mustache tangles from, never the generated files — so that
regeneration produces correct output and stops re-introducing drift.
The four bugs: the history field mapper template hardcodes the five
provenance labels as string literals instead of the shared
provenance_fields constants; the compound-key is_uuid gating in
the Qt/service templates silently drops trailing key columns; the
has_pagination knob silently defaults to truncation; and the
junction templates carry four follow-up defects (path/namespace
mismatch, trigger current_timestamp collisions, messaging handler
without a matching service, incomplete eventing facets). Each fix is
verified the same way: baseline regen before the edit, a template
change confined to its intended output, and an idempotent
regeneration reviewed like a hand-written PR. This story is the first
foundations unit of the drift-elimination program (see the
coordination story's Decisions, 2026-09-03): template bugs are fixed
before any per-component sweep so a codegen bug found mid-sweep does
not waste the sweep.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 25 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-04 |
3. Acceptance
- The history mapper template emits the five provenance labels as
provenance_fields::*constants, and every entity's*_history_field_mapper.cppacross the 8 components is regenerated and compiles. - The compound-key
is_uuidbranches in the Qt/service templates iterateprimary_key.columnsso no trailing key column is silently dropped, and the affected output is regenerated. - The
has_paginationknob cannot silently default to truncation: when unset, no entity truncates behind pagination-shaped controls without a signal. - The four junction defects are fixed at source and their regenerated output verified.
- Every fix landed with its regression evidence: baseline regen before the edit, per-fix diff confined to the intended output, and byte-identical second regeneration.
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Update history field mapper codegen template to use provenance_fields constants | DONE | 2026-09-04 | 2026-09-04 | cpp_history_field_mapper.cpp.mustache hardcodes audit-column labels ("Modified By", etc.) as string literals, but currency_history_field_mapper.cpp on main uses the shared ores::history::domain::provenance_fields constants instead – the template needs to be updated to match and every entity's generated mapper regenerated. |
| Compound-key is_uuid gating drops trailing columns in Qt/service templates | DONE | 2026-09-04 | 2026-09-04 | cpp_protocol.hpp.mustache and cpp_service.cpp.mustache gate their delete/history/save is_uuid branch on primary_key.is_uuid alone, which reflects only the first key column. A future compound key whose leading column is UUID would silently drop every subsequent key column from these structs/validations. Not exercised today (subject_area's key is two text columns). Raised in PR #1691 review round 1. Fix: make the is_uuid branches loop primary_key.columns like cpp_domain_type_mapper.cpp.mustache already does. |
| has_pagination knob is a silent-failure footgun in the Qt client model template | DONE | 2026-09-04 | 2026-09-04 | The codegen knob gating real offset/limit pagination in a Client entity Model.cpp defaults to off with no compile-time or runtime signal when unset, letting 61 entities silently truncate lists at 100 rows behind fully-functional-looking pagination controls. |
| Junction codegen template follow-ups | DONE | 2026-09-04 | 2026-09-04 | Four codegen template issues for junction entities: path/namespace mismatch between generated and live code, current_timestamp version-collision in insert triggers, messaging handler generated without matching service, and eventing facets produce incomplete output. |
5. Decisions
- 2026-09-04 (task 453FA225): the delete/history/save key gates loop
primary_key.columnswith the per-columnis_uuidform, not a plain column loop, because the delete member name is type-driven: the handler template readsreq->{{delete_request_id_field}}, which the derivation layer resolves toidsfor a uuid key whatever the column name — so a plain loop would rename the instrument family's delete member toinstrument_idsand break its generated handlers. The{{#entity}}(lookup) delete gate and{{#junction}}section stay on the single-column shape: schema primary keys carry nocolumnslist in the loader, so a loop form there would emit nothing for every lookup entity. The key census behind this (every live key is a single uuid or text column, or a compound text key; uuid-leading compound keys are the only mixed shape the save validation can express today) is in the task's Plan. - 2026-09-04 (task 9FF6FAD3): the Qt client model fetch is generated
unconditionally — the
has_paginationfork was deleted rather than set per-entity, because the{{^}}branch never fetched the whole set (it sent the request with default offset=0/limit=100 and reported the returned page's size as the total), so no small-table optimisation was lost, and its shape is strictly lossy past the server's default page of 100. The flag survives only as entity-org documentation: nothing behavioural reads it any more, so no future entity can reintroduce the truncation by forgetting a knob. The regression evidence also exposed that the narrow-fix census (tracking task 914B206F) missed three regenerable flag-off entities — the dq and reporting flips are carried in this PR, and the scheduler one (ClientJobDefinitionModel) waits for its component's drift story because its regeneration bundles unrelated stale drift. Review round 1 (PR #2006) completed that record: the compute→reporting and dq→api migrations left stale duplicate client models compiled underores.qt/compute/srcandores.qt/api/srcthat no generator writes to (their deletion belongs to the migration follow-up), and the workspace model is unregenerable until 836BB4CE. The round also retired thevariability_feature_has_pagination.orgfeature page, which still described the flag as a live behavioural toggle. - 2026-09-04 (task D8301A1C): junction messaging is gated on the
declared read need, in
resolve_targets. A junction whoseleftandrightsides both lack:list_by:resolves none of the four messaging facets (ores.cpp.nats-handler,ores.cpp.nats-sub-registrar,ores.cpp.protocol,ores.cpp.service): the junction sections' only subject-emitting content is the per-side list read, so the stack the pre-gate templates emitted served no subscriber. The org's own:list_by:is the declaration of intent, so no new drawer syntax or enablement key was added, andbadge_mapping's four disable keys stay as the explicit suppression record. Related discovery, recorded for the refdata drift story: org frontmatter#+profile: tenant-scoped-junctionis not read by the junction loader (_ensure_profile_bindingaccepts:profile:only in the root** Flagsdrawer), so those eight orgs regenerate tenant-less. Junction create triggers useclock_timestamp()at the four version/validity sites, mirroring the domain-entity create fix:current_timestampis frozen for the whole transaction, so a same-transaction multi-write to one junction row would collide with itself, whileclock_timestamp()always advances.