Task: Pin the rates instruments to the anchor's key
Table of Contents
This page documents a task in the Rework the rates instruments to the instrument table rule story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Every rates instrument row, the header and the facts alike, is pinned to the trade anchor by a composite key over the tenant, the trade, the party and the trade type, so a row cannot disagree with the anchor it belongs to.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Rework the rates instruments to the instrument table rule |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-09 |
3. Acceptance
- ores_trading_trades_tbl carries the unique index the composite key needs.
- Every rates instrument row, the header included, carries a foreign key to the anchor over the tenant, the trade, the party and the trade type.
- A row whose party or trade type disagrees with the anchor's is refused, and a case proves it.
- The generated models, the SQL and the repositories agree on the key.
- The change to the trade model is recorded where the other families can join it.
4. Plan
- The anchor's index. Add the unique index the composite key needs to the
trade model and regenerate. This is a change to
ores_trading_trades_tbl, not to this family, so record it where the other families can join it. - The instruments' key. Declare the composite foreign key on the header and on each of the eight fact tables, over the tenant, the trade, the party and the trade type.
- The fallout. The repositories key on the trade id today; the composite key adds the party and the type, so the generated key parameters, the services and the shell surfaces follow, and the tests that build a row without the anchor's party will start to fail — that is the check working.
- A refusing case. A repository case that writes a row whose party or trade type disagrees with the anchor's, and expects the store to refuse it.
- Before branching. The trade anchor is shared with every other family and with in-flight work; check the fleet for what else is touching it before starting.
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 |
|---|---|---|---|
| Trading core suite after the pin | 143 cases, all passed | compass test run -- -R trading.core |
uncommitted (working tree over efa0aab1c2) |
| ORE core suite after the pin | all passed | compass test run -- -R ore.core |
same |
| Component documentation audit | 41 of 41 components pass | projects/ores.codegen/validate_docs.sh |
same |
6. Notes
- This is the only unit of the arc that changes the trade model, so it is the one that needs a fleet check and possibly a coordination note before it starts.
- Story acceptance clause 2 is this task; it is the last clause that a structural change, rather than a proof, can satisfy.
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 |
|---|---|
| #3032 | [trading,sql] Pin the rates header to the trade anchor |
9. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | CHECK_THROWS accepts any exception | repository_rate_instrument_cascade_tests.cpp | Accepted | The case now matches the pin's message text. |
| 2 | Say whether the anchor's trade type can change | ores.trading.rate_instruments.org | Accepted | The model says the anchor documents it as fixed but its write path does not enforce it; the gap is open and belongs to the trade model. |
| 3 | Name which of party and trade type disagreed | trading_rate_instruments_create.sql | Declined | The generated pin takes one message with one placeholder. |
| 4 | Confirm CI runs the drift check | none | Noted | Drift is clean locally for trade and trading-cpp. |
10. Result
The rates header is pinned to the trade anchor on the party and the trade type. The fleet check found one overlap: merry newton adds two columns to the trades table, which does not touch the pin.
The task's premise was out of date, so the delivery differs from the plan:
- The anchor is a versioned table now (
Make every trading entity a regular, versioned table), and its unique keys were removed because nothing referenced them (Stop the trade's children copying its party). A database composite foreign key cannot name a versioned table, and a unique index would serve nothing. No index was added. The pin is a codegenPinned keysrow on the header, which generates an insert-trigger check against the anchor's current row over(id, party_id, trade_type). - The fact and child tables carry neither
party_idnortrade_type_code(design decision D3), so the header is the only row of the family that can disagree with the anchor. They are not pinned. - The trigger stamps
party_idfrom the session and row-level security hides other parties' anchors, so the observable refusal is the trade type. The party column is held to the anchor as well. - Because the check runs at insert, a later amendment of the anchor does not re-check the header. The trade model's owner decides whether an amendment re-pins its children.
Tests: the cascade test now books its anchor as a ForwardRateAgreement, and a new case refuses a header whose trade type differs from the anchor's.