Task: Give the party memberships their versions
Table of Contents
This page documents a task in the Close the tenor journey and the remaining refdata gaps — the remaining work story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Journey 6 shows when and by whom a membership was closed, and reads a membership's own versions without the client reconstructing them.
2. Status
| Field | Value |
|---|---|
| State | BACKLOG |
| Parent story | Close the tenor journey and the remaining refdata gaps — the remaining work |
| Now | The design question is settled. The codegen change is next. |
| Waiting on | Nothing. |
| Next | Add the opt-in flag, extend the three junction template blocks, regenerate. |
| Last touched | 2026-10-09 |
3. Acceptance
- The party_countries and party_currencies junctions serve a versions read keyed by the link the caller holds.
- The codegen change is opt-in: a junction that does not ask for versions does not get one, and no other junction's protocol changes.
- Whether the journey wants the versions read or the generic diff route is decided and recorded, with the reason.
- Journey 6's membership-versions row is corrected to name the subject that serves it.
4. Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
5. Measurements
Every number this task claims about a tree, with the command that produced it
and the commit it was measured at. A number without both is not a measurement,
because a clean-up record describes a tree and trunk moves under a branch: a
consumer count, a gate's component count and a suite's size all change with no
code changing, and the reader of a bare number cannot tell which tree it came
from. Where the raw output matters, put it in an evidence/ file beside the
task and name that file in the Command column.
| What | Value | Command | Commit |
|---|---|---|---|
6. Notes
Split out of Give the party memberships their versions, and filter the business units. That task's other two parts — the business-unit party filter and the level-rule refusal — shipped on their own. This part is a codegen change, and it carries a design question the other two did not, so it is its own task.
The design question, settled 2026-10-15. The journey wants the versions sub-resource, not the generic diff route.
Journey 6 asks the screen to show "when and by whom a membership was closed".
The prototype answers which read serves that, and says so on the screen itself:
its memberships step draws a gap note reading "The junction tables keep closed
versions, but no operation reads them … The candidate is a versions read for
the two junctions, in the shape the entity models already have"
(doc/prototypes/parties/keep-a-partys-details-current/prototype.js).
A versions row already carries the whole answer: version, modified_by,
valid_from and valid_to. "When" is the validity window and "by whom" is
modified_by, so no field-level diff is needed to satisfy the journey.
The generic route cannot serve it in any case. refdata.v1.history.get takes a
single string id, and a junction's identity is the pair, so serving it would
require deciding a composite-id representation first – a larger decision than
this task, and one the journey does not need. The sub-resource is keyed by the
link the caller holds, which is the correct key.
The mechanical change, which is well understood. A junction is an entity whose
key spans the two sides it links, and its table already keeps the records: the
DDL carries version, modified_by, valid_from and valid_to, the insert
trigger does version management, and the delete rule closes valid_to rather
than removing the row (sql_schema_junction_create.mustache). What is missing
is the derivation. junction_entity_shape(), in
projects/ores.codegen/src/codegen/org_loader.py, returns a fixed literal that
sets current_state: True and never sets has_audit_columns. The versions
sub-resource is gated on has_audit_columns, and the service's
get_<entity>_history block sits inside {{^current_state}}. Its docstring
claims a junction "carries no valid_from=/=valid_to axis", which the DDL
contradicts.
The recommended shape is an opt-in drawer flag, because the alternative of
setting has_audit_columns in the shape would give the versions sub-resource to
all 20 junction models and change 20 components' protocols:
junction_entity_shape(): readjunction.get("list_versions", False)intohas_audit_columns, and correct the docstring. A new* Flagskey needs no catalogue work — root Flags are copied to the junction dict verbatim (org_loader.py, the junction loader), andcheck_model_drift.pyonly compares features a bound profile fixes.:list_versions: truein the* Flagsofores.refdata.party_country_junction.organdores.refdata.party_currency_junction.org.- The tangled template sources, whose junction blocks have no versions bodies.
These are the real work:
ores.cpp.repository.repository_header.org(junction block),ores.cpp.repository.repository_impl.org(junction block) andores.cpp.service.service_impl.org(junction block). The junction repository has noread_alland noread_at_version, and the junction service block has no{{#list_versions_operations}}=/={{#get_version_operations}}loop, so the header would declare methods the handler calls and nothing would define them. There are no paste points inside those blocks, so the templates themselves must change. - Tangle with
./compass.sh build --direct codegen_templates, then regenerate refdata, then re-tangle the shell scripts and refresh the two inventories.
Everything else is free: the protocol header, the TypeScript twin, the handler,
the registrar, the shell commands and their tests, the shell recipe, the service
header and the domain type all walk {{#messages}}=/={{#operations}}, and the
shell plan already defines list_versions and get_version
(org_loader.py, the shell operation map). refdata::party_countries:read is
already seeded, so no new permission is needed. No CMake file changes, because
no new file appears.
The derived shapes are keyed by the link, which is the right key: the versions
list by party_country_key (both sides), and get by
party_country_version_key { party_country; version }. The facet_stem is the
singular, so the subjects are exactly refdata.v1.party_countries_versions.list,
.get and refdata.v1.party_currencies_versions.list, .get.
Two pinned codegen tests would break only under the unconditional variant, not
under the flag: test_junction_protocol_verbs.py's exact message-list assertion
and its offset=/=limit counts. A new case asserting the flag yields the six
version messages belongs beside them.
7. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario)
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 |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|