Story: As-of lookup resolution codegen facet

Table of Contents

This page documents a story in Sprint 24. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

Goal

Give any bitemporal reference/lookup entity a codegen-generated way to answer "what did code X mean as of timepoint t" — a point-in-time query (valid_from < t and valid_to >= t=), distinct from and unrelated to composite parent-child version bumping. Discovered while working through book/book_status: opening a historical book version correctly shows the raw status code it had then (stored inline on the book row), but resolving that code's Name/Description/badge colour today uses a current-only lookup, so a since-deleted or since-renamed code silently fails to resolve for a perfectly valid historical view.

A hand-written precedent already exists (read_at_timepoint on currency_repository=/=country_repository — not business_centre_repository, despite an earlier version of this story claiming otherwise): not codegen-generated, and with no service/NATS/UI caller, though ores.cli export --as-of does call it directly on the repository. This story generalises it into a proper facet (correcting a boundary bug found along the way — see Decisions) and applies it broadly, since resolving a historical FK value correctly is a common need, not a book-specific one.

Status

Field Value
State DONE
Parent sprint Sprint 24
Now All three required tasks done; rollout to remaining refdata entities deferred as a follow-on per Acceptance.
Waiting on Nothing.
Next Nothing.
Last touched 2026-07-25

Acceptance

  • A new codegen facet (e.g. :has_as_of_lookup: true) generates a repository-level read_at_timepoint(ctx, as_of[, key]) method matching the existing hand-written shape, plus a service-level wrapper – consistently, not hand-added per entity.
  • The facet is actually wired to a caller (NATS request/response and/or a Qt read path) for at least the pilot entities – the current currency/country precedent has no service/NATS/UI caller, only a direct repository call from ores.cli.
  • Piloted on book_status and regulatory_book_type (the entities that surfaced the gap) and verified: viewing a historical book version resolves its status/type badge correctly even after that code has since been deleted or renamed.
  • Existing currency_repository=/=country_repository hand-written read_at_timepoint methods (including their valid_to > t= boundary bug) are either replaced by the generated facet or explicitly reconciled with it (not left as a second, inconsistent implementation).
  • Rollout to the remaining refdata lookup entities is scoped as a follow-on, not required for this story's completion.

Tasks

Task State Start End Description
Design the as-of lookup codegen facet DONE 2026-07-23 2026-07-23 Design a new codegen facet (candidate flag: :has_as_of_lookup: true) generating a repository-level read_at_timepoint(ctx, as_of[, key]) method and a service-level wrapper, matching the existing hand-written shape on currency_repository/country_repository/business_centre_repository but generated consistently. Decide: NATS protocol surface (new request/response, or reuse an existing one with an optional as_of param), where the flag lives in the .org model (entity-level, since this is about the entity's own bitemporal window, not a parent/child FK relationship), and how/whether to reconcile the 3 existing hand-written methods with the generated one.
Implement and pilot the as-of lookup facet on book_status/regulatory_book_type DONE 2026-07-23 2026-07-24 Add the designed facet to the codegen templates (SQL/repository/service/protocol as scoped). Apply :has_as_of_lookup: true to book_status.org and regulatory_book_type.org (the entities that surfaced this gap), regenerate, and wire it to an actual caller – at minimum the Book history dialog resolving a historical version's status/type badge correctly, proving the facet isn't dead code like its hand-written predecessor.
Reconcile existing hand-written read_at_timepoint methods DONE 2026-07-24 2026-07-25 currency_repository/country_repository already have a hand-written read_at_timepoint (not business_centre_repository), unwired to any service/NATS/UI caller and with a valid_to >= t boundary bug versus the documented valid_to > t convention. Once the codegen facet lands, either replace these with the generated (corrected) version or explicitly document why they diverge – don't leave two parallel implementations of the same concept.
Roll out as-of lookup facet to remaining refdata entities BACKLOG     Once proven on book_status/regulatory_book_type, apply :has_as_of_lookup: true broadly across refdata lookup/reference entities referenced by FK elsewhere. Deferred follow-on, not required for this story's own completion.

Decisions

  • Corrected this story's own premise: read_at_timepoint is not fully dead code — ores.cli export currencies/countries --as-of already calls it directly on the repository. business_centre_repository never had the method at all (only currency=/=country do); the real gap is service/NATS/UI wiring and test coverage, not "no caller anywhere".
  • The hand-written precedent has a boundary bug: valid_to > t= should be valid_to > t, per the schema's own tstzrange(valid_from, valid_to) exclusion constraint (PostgreSQL default [valid_from, valid_to), exclusive upper) and the documented convention in Time and Timestamps. The generated facet corrects this rather than reproducing it.
  • Facet is entity-level (:has_as_of_lookup: true, not per-column like the existing list_by_as_of, which is a different range-overlap shape for composite parent-child versioning and stays untouched by this story).
  • NATS surface: extend the existing get_..._request=/=response with an optional as_of field rather than minting a new request/response pair — backward compatible, no protocol sprawl.
  • Pilot caller (Book history dialog) resolves a historical book version's status/type badge as-of that book version's own valid_from, not "now".
  • Full design rationale in Design the as-of lookup codegen facet's * Plan.

Out of scope

Emacs 29.3 (Org mode 9.6.15)