Task: Fix entity history versioning across delete+recreate cycles
Table of Contents
This page documents a task in the Consolidate history dialogs onto HistoryDialogBase story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Make entity history versioning robust to delete+recreate cycles.
Found while manually verifying task E4DDC1B9 (generic-history-shell):
currency's version column resets to 1 after a soft-delete
(valid_to) and recreate, so a repeatedly deleted/recreated entity
accumulates several "generations" that all reuse version numbers 1..N.
currency_repository::read_all orders rows by version DESC only,
with no tiebreak between generations, and
ores.history::version_builder::build_entity_history_versions
renumbers the returned rows purely by array position rather than
their actual domain version. The two combine to let the diff
renderer compare two unrelated generations' rows as if they were
adjacent versions — observed as a diff between two version=2 rows
from different generations that showed only Recorded At changed,
hiding the real field changes.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Consolidate history dialogs onto HistoryDialogBase |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-13 |
Acceptance
[X]currency_repository::read_allandcountry_repository::read_allorder history rows deterministically across delete+recreate generations: addedvalid_from DESCas a tiebreak afterversion DESC. Also fixed at the source — the sharedcpp_domain_type_repository.cpp.mustachetemplate — so every future codegen'd repository with audit columns inherits the fix.[ ]version_builderstill keys diffs off array position, not each row's actual domainversion=/=valid_fromadjacency — deferred, see Notes.[X]Regression: verified manually that within one generation (no interleaved delete/recreate),history --diffcorrectly diffs each version against its true predecessor (see PR #1547's manual verification).
Plan
Investigated scoping read_all to only the current (non-deleted)
generation, which would have let version_builder's existing
position-based renumbering stay correct without further changes. Ruled
out for this task: domain types (e.g. currency) deliberately don't
carry valid_from=/=valid_to — they're persistence-only audit columns
stripped at the mapper layer — and sqlgen (the query DSL in use) has
no subquery support to scope by "rows since the last version=1 row" in
SQL directly. Scoping would need either exposing temporal columns on
every domain type (a broad, cross-cutting change) or per-row
post-filtering in the repository layer before mapping, which is more
surgery than this task's "mop up" scope warrants.
Landed the narrower, still-real fix instead: deterministic ordering.
read_all previously ordered by version DESC alone, so multiple
generations' same-numbered rows had undefined tie order — now broken
by valid_from DESC, so the query always returns the newest
occurrence of each version number first. Applied by hand to currency
and country (the two entities on the generic history path today) and
at the source template so it's inherited by every future entity's
read_all without a separate fix per entity.
Notes
The remaining gap — repeated delete+recreate cycles still produce
several "generations" that reuse version numbers 1..N, and
version_builder renumbers purely by array position — is only
reachable via delete+recreate, which today's real usage doesn't do
(entities are deleted rarely, and re-adding under the same key after a
genuine delete is an edge case, not the common path). If it resurfaces
as a real problem, the fix is either: (a) scope read_all to the
current generation by exposing valid_from=/=valid_to on domain
version types, or (b) have version_builder match predecessors by
searching for the row whose actual version equals target.version -
1 (as ores.shell's render_history_diff() already does) instead of
trusting array-adjacent positions — cheaper, but still vulnerable to
ties within one query result if two generations coincidentally have
adjacent version numbers at the same moment (extremely unlikely in
practice).
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 |
|---|---|
Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
Result
Added valid_from DESC as a tiebreak after version DESC in
read_all for currency and country, and in the shared
cpp_domain_type_repository.cpp.mustache codegen template so every
future entity's read_all inherits deterministic ordering across
delete+recreate generations by construction. Deferred the deeper
"scope to current generation only" fix (would require exposing
valid_from=/=valid_to on domain version types, a broad change) —
documented as a Note for if/when this resurfaces as a real problem.