Task: Regenerate the trade service layer and Qt estate

Table of Contents

This page documents a task in the Redesign ores.trading on data-oriented principles story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Regenerating the trade entity produces the whole estate, so the identity collapse can be made in the model rather than by hand.

2. Status

Field Value
State DONE
Parent story Redesign ores.trading on data-oriented principles
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-17

3. Acceptance

  • The protocol adopts the generated shapes, keeping a vector-valued save and moving versions to history.
  • The bespoke handler, service, repository and mapper members are org-declared through the implements mechanism rather than hand-written.
  • The nine ores.qt Trade files regenerate, or are recorded as a deliberate exception with the reason.
  • Regenerating trade is idempotent and leaves the tree clean; the full build and ctest stay green against a live environment.

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. Notes

6. 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
     

7. PRs

PR Title
#2096 [codegen] Generate the trade service layer

8. Review

Round 1, PR #2096.

# Comment summary File Decision Notes
1 Service stamped the whole struct, not each domain group, so no group was audited ores.cpp.service.service_impl.org Accept Template now emits one stamp per group; the batch loop was also missing its braces
2 Mapper dropped execution_timestamp in both directions ores.cpp.repository.mapper_impl.org Accept The mapper had no branch for a string domain field against a timestamp column. Added the conversion: str() out, emplace in
3 Generator emitted an unseeded status_id and an activity code that does not exist ores.trading.trade.org Accept Emits new_booking and a nil status for the trigger to resolve
4 Mapper wrote a default-constructed enum instead of NULL ores.cpp.repository.mapper_impl.org Accept To-entity enum branch maps the default value to nullopt
5 No activity type named capture, execute, unexpire or uncancel, so draft was unreachable and both endings were terminal trading_activity_types_populate.sql Accept Added draft_capture, execution, maturity_reversal and cancellation_reversal; verified all eight transitions now reachable
6 status_id was validated before the trigger resolved it, so every write failed ores.trading.trade.org Accept The FK now carries skip_check: the transition resolution is what validates the status
7 FSM resolution duplicated across the two trigger branches ores.trading.trade.org Accept Extracted ores_trading_resolve_trade_transition_fn; the branches keep only their guards
8 Amend branch re-reads a row the version select already read trading_trades_create.sql Decline The row is already locked, so this is one extra index probe. Removing it needs the shared version-select template to take entity-specific columns, which is more grammar than the saving justifies
9 _plan_required_seeds set no parent_component_include/core and the docstring still claimed cross-component parents were skipped core.py Accept Seed items now carry their own component; a cross-component grandparent was getting the parent's headers
10 Dead trade_status_service::resolve_status and trade_handler::fetch_fsm_transitions trade_status_service.*, ores.trading.trade.org Accept Both deleted; the trigger owns status resolution now
11 End-of-line ///< comments violate the comment placement rule ores.trading.trade.org Accept Moved above the members they document

Working the round surfaced five further defects, none of them raised by the reviewer and none catchable by any existing test, because nothing in the estate wrote a trade. Four of the five only appeared once the eventing test was switched back on.

# Finding File Notes
A trade_identity_field_group.org never declared workspace_id, yet the committed header carried it: the header was ahead of its model ores.trading.trade_identity_field_group.org Declared it. Regenerating the group from its own model had been dropping the field
B The seeder gave a party only to a direct parent. An ancestor deeper in the chain – the portfolio a book hangs off – was written with a generated party_id and rejected core.py, nats_integration_test.org Seed items carry requires_party and the ancestor loop seeds a party the same way
C portfolio_id was cross-checked against the book's parent rather than derived from it, so a generic seeder could not satisfy the invariant domain_entity_create.org, ores.trading.trade.org The trigger now derives portfolio_id alongside party_id. Both are settled by the book, so neither is the caller's to send
D The round-trip test amended with the booking activity, which names a machine-starting transition and is illegal on a row that already has a state nats_integration_test.org, ores.trading.trade.org The model declares amend_activity_code and the test uses it
E The trade derives party_id from its book, but the seeder gave the book a fresh party, so the written row was owned by a party the session could not see and every read returned nothing core.py, nats_integration_test.org The book now carries the session party

9. Result

The trade estate is generated from the model. The protocol adopts the generated shapes, keeping the vector-valued save and moving versions to history. The handler, service, repository and mapper members that used to be hand-written are org-declared through the paste-block mechanism, so regenerating trade no longer clobbers them.

Trade is the first entity to carry domain groups, which drove most of the codegen work: a member prefix resolved per field rather than a identity. literal, cross-component seeding, and a trigger that derives what the book settles and guards what the state machine owns.

Acceptance on the nine ores.qt Trade files is met by the second branch of the bullet, not the first. They are a recorded exception here: regenerating them is a separate change with its own review surface, and folding it in would have put a Qt rewrite inside a PR whose subject is the service layer. Four hand-written Qt files also read execution_timestamp as a string, which is why the domain type stayed a string rather than becoming a time point. That work is tracked as its own task.

The full build and ctest are green against a live environment: 73 of 73 with a database recreated from the merged tree and all 23 services up. The codegen drift gate is clean across the nine registered components, and the codegen suite is 208 of 208.

Two rounds of review. The first raised five blocking findings and five smaller items; all are recorded in * Review with their decisions. The round that fixed them found five further defects that no existing test could have caught, because nothing in the estate wrote a trade. Turning the generated eventing test back on is what found them, and it is the one durable lesson here: the test was disabled after a single error message, and that decision is what let the first four findings through.

Emacs 29.3 (Org mode 9.6.15)