Story: Split the trade into an anchor and its components
Table of Contents
This page documents a story in Sprint 26. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
Implement sections 1 to 3, 6, 9, 10 and 13 of the trade shape investigation: the anchor, the booking, the state, identifiers, party roles and additional fields, with ORE import resolving to entities and export regenerating them.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 26 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-05 |
3. Acceptance
- trade is an immutable anchor keyed by tenant and id, carrying party, counterparty, trade type, the two classification values and origin.
- trade_booking and trade_state are temporal components keyed by the trade; every identifier scheme, ORE's Trade/@id included, is a trade_identifier row.
- external_id, product_type, the five field groups and the trade envelope tables are gone; trade_additional_field holds AdditionalFields.
- ORE import resolves the envelope to entities and rejects what does not resolve; export regenerates it; the round trip stays lossless.
- No trading entity carries a workspace.
- The trade-type check lists and the routing from trade type to instrument table are generated from the trade-type catalogue, not written by hand in each model.
- The anchor is the first immutable entity, so its build compiles the generated insert-only repository write, and the booking operation maps a second write of the same trade id to a domain error rather than a raw unique-key violation.
- A tenant purge that removes trades has a sanctioned path past the anchor's immutability guard, in the manner of the version-replace session switch; nothing else may update, delete or truncate an anchor.
- The drop and tenant purge scripts remove the tables that reference the anchor with an enforced foreign key before the anchor itself.
- A booking names a virtual book only while its trade is a pre-agreement draft, a test or a hypothetical; going live needs a real book in the same write.
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Add the immutable trade anchor | DONE | 2026-10-03 | 2026-10-03 | Add trade_anchor beside the wide trade: an immutable, tenant-keyed row with party, counterparty, trade type, counterparty scope, booking nature and entry channel, and the three closed lookups it references. |
| Add the trade booking and state components | DONE | 2026-10-03 | 2026-10-03 | Add trade_booking and trade_state beside the wide trade: temporal components keyed by the anchor through a database foreign key, the netting set pinned to the anchor's party and counterparty, and the virtual-book rule. |
| Book a trade in one write | DONE | 2026-10-03 | Add the booking operation: write a trade's anchor, booking and state in one transaction, and report a second booking of the same trade id as a domain error. | |
| Key identifiers, party roles and additional fields by the anchor | DONE | 2026-10-03 | 2026-10-04 | Reshape trade_identifier and trade_party_role around the anchor and add trade_additional_field: each keyed by the trade with a database foreign key to the anchor, ORE's Trade/@id one identifier scheme among others, and no workspace. |
| Generate trade-type checks and routing from the catalogue | DONE | 2026-10-04 | 2026-10-04 | Make the trade-type catalogue a codegen input: each trade type names the instrument table that holds it, and the generator renders the catalogue's seed, every instrument table's trade_type check and the export routing from it. |
| Import ORE trades onto the anchor and its components | DONE | 2026-10-04 | 2026-10-04 | Make the ORE import book each trade's anchor, booking and state, record ORE's Trade/@id as an identifier and the envelope's additional fields, resolve the envelope's counterparty (rejecting a name that matches none) and its netting set (creating one that does not exist), while still writing the wide trade the instruments reference. |
| Map ORE sample counterparties onto GLEIF banks | DONE | 2026-10-04 | 2026-10-04 | Add an ORE counterparty identifier scheme, so an ORE document's CounterParty name is an alias of a real counterparty, and seed the aliases that map the ORE samples' placeholder names onto GLEIF banks. |
| Publish ORE counterparty aliases to provisioned tenants | DONE | 2026-10-04 | 2026-10-04 | Stage counterparty aliases in DQ and publish them as counterparty identifiers, so a provisioned tenant gets the ORE sample aliases against the GLEIF counterparties it already holds. |
| Give netting sets ORE aliases | DONE | 2026-10-04 | 2026-10-04 | Add a netting set identifier entity, so a netting set answers to the names ORE documents use, as counterparties already do. |
| Publish the ORE sample netting agreements, sets and CSAs | DONE | 2026-10-04 | 2026-10-04 | Stage netting agreements, netting sets, CSAs and netting set aliases in DQ, and publish realistic synthetic data built from ORE's netting definitions to each provisioned party. |
| Catch untangled shell scripts before review | DONE | 2026-10-04 | 2026-10-04 | Make the shell script drift check fail on scripts a recipe produces that are not committed, and add the tangle to the local check list. |
| Export ORE trades from the anchor and its components | DONE | 2026-10-04 | 2026-10-04 | Regenerate an ORE trade's envelope from the anchor and its components: the counterparty and netting set names the document used, the ORE trade identifier and the additional fields. |
| Resolve ORE portfolio ids to portfolios | DONE | 2026-10-04 | 2026-10-04 | Give ORE's Envelope/PortfolioIds a home: a trade_portfolio component that resolves each id to a portfolio of the trade's party, imported and exported from it, with the ORE sample portfolios published to ACME. |
| Reference the anchor from every instrument | DONE | 2026-10-04 | 2026-10-04 | Point every instrument and instrument child table's trade reference at the trade anchor instead of the wide trade. |
| Remove the workspace from every trading entity | DONE | 2026-10-04 | 2026-10-04 | Drop the workspace column from every trading table: the trading profiles, the identity field groups, bond_issue and lifecycle_event stop declaring it, and the code that set it goes. |
| Move the trade export onto the anchor | DONE | 2026-10-04 | 2026-10-04 | Move the portfolio export, the export to storage and the instrument lookup out of the wide trade's model into the trade operations, reading anchors through their bookings. |
| Import ORE trades onto the anchor alone | DONE | 2026-10-04 | 2026-10-05 | Carry an imported trade as its anchor, its booking and its ORE id instead of the wide trade, stop writing the wide trade, and roll back an import by closing the bookings it wrote. |
| Remove the wide trade | DONE | 2026-10-05 | 2026-10-05 | Delete the wide trade model, its five field groups, the three envelope tables and their generated code, the wide-trade SQL functions, populate and tests, and make the tenant purge delete instruments and the anchor's components. |
| Rename the trade anchor to trade | DONE | 2026-10-05 | 2026-10-05 | Now that the wide trade is gone, the immutable anchor takes the name trade: model, table, SQL functions and policies, C++ types, protocol subjects, permissions, TypeScript, shell and docs. |
5. Decisions
- Build beside, then switch (owner's choice): the anchor and its components are new generated entities next to today's wide trade; each consumer moves onto them in its own pull request; the wide trade, its field groups and the envelope tables are removed last. Every pull request builds and passes, so main is never half migrated.
- While the wide trade exists the anchor is named
trade_anchor; it takes the nametradein the last task. - The anchor's origin is
entry_channel(owner's choice): how the trade reached the firm's books —manual,stp,ecnorallocation. The name avoidsbooking_origin, which the data quality origin dimension already uses. The originating system's own id is an identifier scheme and its version belongs to the agreement. entry_channelis a C++ enum backed by a lookup table, with a foreign key (owner's choice). The anchor is immutable and a database foreign key needs an immutable target, so the lookup is an immutable table with no tenant, seeded once with the four codes. The enum and the lookup hold the same closed set: a code with no enum value cannot be added at run time.counterparty_scopeandbooking_natureare closed sets of the same kind and follow the same pattern.- A booking's netting set is pinned to the trade's counterparty and party. A netting set read from an ORE document names neither, so the ORE import task fills the set's parties from the trade it imports.
- An ORE document's CounterParty name is an alias of a real counterparty
(owner's choice), held as a counterparty identifier under an
OREscheme: the ORE samples' placeholder names map onto GLEIF banks, and the import resolves an envelope's counterparty through the alias, rejecting a name that resolves to none. A netting set answers to an ORE id the same way, through a netting set identifier (owner's choice, replacing the earlier choice to create a missing set on import): the ORE sample netting dataset publishes realistic agreements, sets, CSAs and the aliases the samples expect, and an id that resolves to none rejects the trade. - Task order: the anchor; booking and state; the booking operation, which writes all three in one transaction; identifiers, party roles and additional fields; catalogue-generated trade-type checks and routing; ORE import and export; the remaining consumers; removal of the wide trade.
- An import rollback closes the bookings it wrote rather than deleting trades: the trade row is immutable, and a trade with no live booking is in no book and no export. A trade whose later writes fail has its booking closed the same way.
- The tenant purge deletes every tenant-scoped trading table in a loop that retries a table its referrers still hold, disabling each table's delete rules from the catalogue, and passes the trade's guard only through the immutable purge switch.
- pgTAP runs under a role row-level security binds; the owner bypasses it.
6. Out of scope
7. Result
Done: the trade is an immutable row with temporal components, and the wide trade is gone.