Task: Decide the FX discriminators and the doubled vocabularies

Table of Contents

+#+blocked_since: +#+created: 2026-10-16 +#+updated: 2026-10-16 +#+environment: +#+todo: DISCOVERED BACKLOG STARTED BLOCKED | DONE ABANDONED

This page documents a task in the Rework the FX instruments to the instrument table rule story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Settle the three Minor findings the FX review left open. Each one is a choice the model states in two places, or states in prose and refuses in a check. This unit decides each and makes the tree enforce the decision.

2. Status

Field Value
State DONE
Parent story Rework the FX instruments to the instrument table rule
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-18

3. Acceptance

  • fx_barrier_option_instrument states which barrier levels the barrier_type engages, and a check refuses a row that fills a level its type does not use.
  • fx_digital_option_instrument states the same for strike, lower_barrier and upper_barrier, or records why no discriminator is possible.
  • fx_vanilla_option_instrument names one closed exercise-style set in its prose and admits the same set in its check. Bermuda is either admitted by both or refused by both, with the reason recorded.
  • The four closed vocabularies that are stated in a table and in a check are kept equal by a mechanism, or the duplicate is removed and the reason recorded.
  • Every decision this unit makes is distilled into the parent story's * Decisions.

4. Decisions

  • The exercise style admits Bermuda. ORE's exerciseStyle is the closed set European, Bermudan and American, the lookup already seeds all three, and the model's prose states all three, so the check is the one that is wrong. The check is removed with the other duplicates, which leaves the lookup as the statement of the set, and the prose states it once.
  • The lookup table is the single statement of each closed vocabulary. Five vocabularies (long_short, option_type, barrier_type, exercise_style, moment_type) are stated in a lookup and again in the table's check. The foreign-key unit keyed every one of them to its lookup, so the check is the copy that goes; the lookup is what a reader edits and what the store enforces.
  • A single barrier's level sits in the column its direction names. An Up... or FixingCap style engages the upper level, a Down... or FixingFloor style the lower one, and the other column stays null. The two columns are positional today: the mapper writes the document's first level into lower_barrier whatever the style, so an UpAndOut option cannot be stated without inventing a lower level.
  • A product that states two levels is the exception, and its trade type says so. A double or generic barrier holds a level per side, so the directional check applies to trade_type_code = 'FxBarrierOption' alone; the double and generic codes require the two levels they read.
  • The digital option keys its levels on the trade type. FxDigitalOption states a strike and no barrier, FxTouchOption one barrier and no strike, FxDoubleTouchOption both barriers and no strike, and FxDigitalBarrierOption a strike and a barrier. The table is shared by the four codes, so the check reads trade_type_code.

5. Plan

The analysis found these disagreements between prose, check and mapper:

Model Prose says The check admits The mapper writes
vanilla option European or American, then the closed ORE set of three European, American the document's value
barrier option the style names the direction any of the ten styles with any level the first level into lower_barrier whatever the style
digital option the four codes state different fields nothing about levels one level into lower_barrier
  1. Widen the vanilla check to the three ORE styles and state the set once.
  2. Delete the five in (...) checks whose vocabulary is now a lookup.
  3. Make lower_barrier nullable, add the directional checks keyed on trade_type_code, and place the document's level by direction in fill_barrier and in the two hand-written forward functions.
  4. Add the digital option's type-keyed checks.
  5. Regenerate, recreate the database, and update the barrier repository test that writes UpAndIn with a lower level.
  6. Add a case per decision that refuses the shape it should refuse.

6. 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
Duplicated vocabulary checks removed 10 =git diff origin/main – 'projects/ores.trading/modeling/*.org' \ grep -c '^-.* in (', in =evidence/fx-vocabularies.txt the PR head
Level checks on the barrier table 6 of its 13 grep -c 'check (' projects/ores.sql/create/trading/trading_fx_barrier_option_instruments_create.sql gives 13; the six level checks are the ones naming a barrier column, listed in evidence/fx-vocabularies.txt the PR head
Digital checks keyed on the trade type 4 grep -c 'check (("trade_type_code"' projects/ores.sql/create/trading/trading_fx_digital_option_instruments_create.sql, in evidence/fx-vocabularies.txt the PR head
Trading core suite pass compass test run --preset linux-clang-release-ninja -- -R ores.trading.core.tests the PR head
ORE core suite pass the same command with ores.ore.core.tests the PR head
Refusing cases 2 (an Up style with a lower level, a double barrier with one level) an_up_style_refuses_a_lower_level in projects/ores.trading/core/tests/repository_fx_barrier_option_instrument_repository_tests.cpp the PR head
Economic-digest suite rotted, not wired =grep -rn digest_tests –include=CMakeLists.txt projects/ \ wc -l= returns 0, so the suite is outside the build; a local wiring run failed four cases on an empty digest, recorded in evidence/fx-digest-suite.txt the PR head
       

7. Notes

  • The evidence for this task is Investigation: FX family data model review, findings 6, 8 and 9.
  • These are decisions, not defects with one right answer. A decision to keep the current shape is a valid outcome if it is recorded.

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

9. PRs

PR Title
#3531 [agile] Close the FX rework story
#3530 [agile] Close the discriminators unit
#3528 [trading] Key the FX vocabularies and barrier levels on one statement
#3527 [agile] Scope the FX discriminator decisions

10. Review

Comment summary File Decision Notes
       

11. Result

12. Findings

  • The economic-digest suite is rotted, and not by this unit. No CMakeLists adds projects/ores.trading/core/digest_tests, so the suite is outside the build. Wiring it in and running it fails four cases, one of them for ScriptedTrade, a family this unit never touches, because the digest column is empty before and after the write. The suite was therefore left unwired and is recorded as its own defect.
  • The wire twins and the goldens are corrected here. Both TS files now type the lower level as string | null, and the barrier golden records the level in upper_barrier with a note that says so.
  • The direction is a second list. The check names the Up and Down styles beside the lookup that holds them. Encoding the direction on ores_trading_barrier_types_tbl is the right shape and is captured as its own work.

The duplicated vocabularies are gone. Ten in (...) checks named a set the lookup table now validates, so the checks were the second copy and are removed. The vanilla option's check was one of them, which settles the exercise style: the lookup holds European, Bermudan and American, so the store admits all three and the prose states that set once instead of contradicting it.

The barrier option now states which level its style engages. A single FxBarrierOption with an Up style or a cap holds its level in upper_barrier and leaves lower_barrier null; a Down style or a floor does the reverse; the styles that name no direction state one of the two. lower_barrier became nullable for that reason, and the mapper places the document's level by direction instead of by position, so an UpAndOut option no longer needs a lower level invented for it. A double or generic barrier holds a level per side and is exempt, because its trade type says so.

The digital option keys its checks on the trade type. Each of its four codes states the fields it engages: a plain digital a strike, a touch one barrier, a double touch both barriers, and a digital barrier a strike and a barrier. The checks require those fields and tolerate another code's, because the one synthetic generator fills a strike and both barriers on its single row, so a check that refused a field the code does not engage would refuse that row.

The decisions are in the parent story's * Decisions.

Emacs 29.3 (Org mode 9.6.15)