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.cpp across the 8 components is regenerated and compiles.
  • The compound-key is_uuid branches in the Qt/service templates iterate primary_key.columns so no trailing key column is silently dropped, and the affected output is regenerated.
  • The has_pagination knob 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.columns with the per-column is_uuid form, not a plain column loop, because the delete member name is type-driven: the handler template reads req->{{delete_request_id_field}}, which the derivation layer resolves to ids for a uuid key whatever the column name — so a plain loop would rename the instrument family's delete member to instrument_ids and break its generated handlers. The {{#entity}} (lookup) delete gate and {{#junction}} section stay on the single-column shape: schema primary keys carry no columns list 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_pagination fork 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 under ores.qt/compute/src and ores.qt/api/src that no generator writes to (their deletion belongs to the migration follow-up), and the workspace model is unregenerable until 836BB4CE. The round also retired the variability_feature_has_pagination.org feature 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 whose left and right sides 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, and badge_mapping's four disable keys stay as the explicit suppression record. Related discovery, recorded for the refdata drift story: org frontmatter #+profile: tenant-scoped-junction is not read by the junction loader (_ensure_profile_binding accepts :profile: only in the root ** Flags drawer), so those eight orgs regenerate tenant-less. Junction create triggers use clock_timestamp() at the four version/validity sites, mirroring the domain-entity create fix: current_timestamp is frozen for the whole transaction, so a same-transaction multi-write to one junction row would collide with itself, while clock_timestamp() always advances.

6. Out of scope

Emacs 29.3 (Org mode 9.6.15)