Task: Generate the hand-written RLS policies from model flags
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
Row-level security for a modelled table comes from its model, so a new tenant table cannot reach main without a policy, and the policies cannot drift from the tables they protect.
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-02 |
3. Acceptance
- Codegen writes the policies of each modelled table into a per-component policy file that the RLS aggregate runs last, rather than inline, for every component created before iam.
- Every generated policy is proven identical to the hand-written one it replaces, by comparing the policies in the database catalogue before and after.
- The policies a model flag cannot express stay hand-written, each with its reason.
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
From the hand-written SQL census in Find why so much SQL is hand-written and generate what codegen can.
A rough keyword scan of the fourteen policy files found 251 policies. About 147
are plain tenant isolation, 129 of them on a table whose model exists but does
not set :rls_tenant_isolation:. The rest add system-tenant visibility, party
isolation or per-command policies, which codegen also has flags for. Measure
this properly first; the scan counts a comment that mentions a party.
A policy calls ores_iam_current_tenant_id_fn, defined in iam. dq,
workspace, refdata and trading are created before iam, so an inline
policy there fails at CREATE POLICY. That is why none of their models sets the
flag today, and why rls/rls_create.sql runs last.
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 |
|---|---|
| #2583 | [sql] Generate the refdata and trading RLS policies from model flags |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
Row level security for 161 modelled tables now comes from their models: 111 in
refdata and 49 in trading set :rls_tenant_isolation: (with
:rls_party_isolation: where the hand-written policy had the standard party
policy), and the party counterparty junction sets both. Their hand-written
blocks are deleted.
The plan above assumed an inline policy could not live in a component created
before iam, because policies call ores_iam_current_tenant_id_fn. That
function, ores_iam_visible_party_ids_fn and the three other session readers
read nothing but current_setting, so they moved to
create/iam/iam_session_functions_create.sql, which create.sql runs before
any component's tables. Generated policies then work inline everywhere, and no
late per-component file is needed.
The junction template emitted only the tenant policy, so it now also emits the
party policy when :rls_party_isolation: is set; the catalogue comparison and
party_rls_isolation_tests both caught the junction's missing party policy
before it was fixed.
Proof: the policy catalogue (pg_policies: table, permissive, command, using,
with check, roles) was dumped from a recreated database before and after.
Ignoring policy names, the only differences are three main introduced between
the two dumps: the series key shape table #2577 retired, and the trading
policies #2578 added to tables this change leaves hand-written. Schema
validation passed (2032 files, 0 problems) and ctest passed 90/90.
Left hand-written, each with its reason:
- No model (15): refdata account types, asset classes, asset measures, benchmark rates, business processes, cashflow types, entity classifications, local jurisdictions, party relationships, party roles, person roles, regulatory corporate sectors, reporting regimes, supervisory bodies; dq dataset dependencies. Model the hand-written tables that have a codegen shape covers them.
- Per-command policies the flags cannot express (71): most are party policies
restricted to
for select, whose comments give the reason (for example, a with check would block bulk inserts from the publisher); the dq ones combine system-tenant rows with per-command policies.