Task: Scope the configuration documents to a party

Table of Contents

This page documents a task in the Model the ORE report configuration as entities story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

A party sees and writes only its own configuration documents, as it already does its report definitions, and two parties in one tenant can each hold a document with the same names.

2. Status

Field Value
State DONE
Parent story Model the ORE report configuration as entities
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-03

3. Acceptance

  • Every table behind the run document, the reporting registry, pricing engines, today's market, stress test, credit simulation, conventions and curve configuration has party_id and a restrictive party RLS policy.
  • A test per document group proves a party cannot read another party's rows.
  • Two parties can each import the same corpus document.
  • Currencies, currency pairs and calendar adjustments stay per tenant.
  • World conventions (IBOR index, overnight index, currency pair) stay per tenant; the instrument conventions are per party.

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

Decided with the operator on 2026-10-03: every configuration document is per tenant and per party; currencies, currency pairs and calendar adjustments stay per tenant for now. The policies come from model flags wherever codegen can emit them, so the order is:

  1. The run document and reporting registry (seven tables, already flag-generated RLS).
  2. The analytics documents, by flag; the pricing model tables' hand-written policies move to flags.
  3. Generate the hand-written RLS policies from model flags, so components created before iam get generated policies too.
  4. Conventions and curve configuration, by flag; text keys gain the party.

Step 1, decided while building it:

  • A party-scoped table models party_id uuid not null and sets :rls_party_isolation: true beside :rls_tenant_isolation:, as variability.system_setting does.
  • The mappers stay party-unaware. ores::ore::domain::assign_party stamps the party on every row of a mapped document, walking vectors, optionals and structs by reflection; whoever persists a document calls it.
  • The eventing test template stamped the session party only on the row under test, so a party-isolated parent it seeded was invisible to the child's insert trigger. Codegen now marks a party-isolated parent (parent_party_isolated) and the template stamps the session party on it.
  • tests/party_fixture.hpp makes two operational parties of the test tenant, with a context that sees only each, for the per-group isolation tests.

Step 2, decided while building it:

  • Sixteen analytics tables: pricing engines (three), today's market (five), stress test (three) and credit simulation (five). The collection kind lookup is system-tenant reference data and stays as it is.
  • The pricing model tables had hand-written tenant policies with no reason behind them, since analytics is created after iam. They now set :rls_tenant_isolation: and :rls_party_isolation:, and the hand-written blocks are deleted.
  • pricing_model_config and todays_market_config were unique by name per tenant; party_id joins their natural key, so the name is unique per party.
  • The eventing test template also seeds ancestors two or more steps up through _plan_required_seeds; a party-isolated ancestor there now carries the session party too.
  • The analytics core tests link ores.refdata.core, which the generated tests need to create their party, as the reporting tests already did.

Step 4a, the curve configuration, decided while building it:

  • The 34 curve configuration tables are keyed by uuid, so party_id is a plain column with :rls_party_isolation:; they already had the tenant flag from Generate the hand-written RLS policies from model flags.
  • The party comes from the session, as the operator pointed out: the request context is built from the JWT (tenant, party, visible parties); every generated service runs ores::service::messaging::stamp, which overwrites tenant_id and party_id from that context; and the context's session settings are what the RLS policies read. A party-scoped table needs no more than the column and the policy to take part. assign_party serves only code that writes through repositories directly, which today is the tests.

Step 4b, the conventions, decided with the operator: a convention is keyed by its ORE id, and two parties must each be able to hold the same id, so party_id joins the key. Codegen derives the SQL key and the API key from the same columns, and the party comes from the JWT, so the column is marked to join the SQL primary key, unique indexes and exclusion constraint while staying out of the repository and protocol key parameters.

Step 4b, which conventions, decided with the operator on 2026-10-04: the data splits into world data, which every party sees the same, and party configuration, which each party owns (World data versus party configuration). The IBOR index, overnight index and currency pair conventions are world data: the seed takes them from QuantLib, synthetic feeds need them, and the publisher writes them. They stay per tenant with no party_id, and move with the synthetic data rewrite. The other 22 conventions are instrument and curve-building choices, so they are per party. Tenor conventions were never in scope; they are a reference lookup.

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
#2586 [refdata] Scope the instrument conventions to a party
#2584 [refdata] Scope the curve configuration documents to a party
#2580 [analytics] Scope the analytics configuration documents to a party
#2579 [reporting] Scope the run document and reporting registry to a party

9. Review

Comment summary File Decision Notes
       

10. Result

Every configuration document is per tenant and per party. A party reads and writes only its own rows, and the party comes from the session, set by stamp from the JWT.

  • Reporting: the run document and reporting registry, seven tables (#2579).
  • Analytics: pricing engines, today's market, stress test and credit simulation, sixteen tables; the pricing model tables moved from hand-written policies to flags (#2580).
  • RLS generation: the session functions run before any table, so every component takes the generated policies; 161 hand-written policy blocks became model flags, and a catalogue diff proved the policies the same.
  • Curve configuration: 34 tables (#2584).
  • Conventions: the 22 instrument conventions. party_id joins the SQL key through the new :sql_key: column property, so two parties can each hold the same convention ids; the repository and protocol keep the id alone.
  • The IBOR index, overnight index and currency pair conventions stay per tenant as world data, with currencies, currency pairs and calendar adjustments (World data versus party configuration).

Each group has a test that one party cannot see another's rows, and conventions_party_database_tests.cpp imports the Example_62 deposits under two parties.

Emacs 29.3 (Org mode 9.6.15)