The database refuses a value that no value set states

Table of Contents

1. Summary

A value that no set states is refused by a trigger on the table that stores it, not by the service that writes it. Four trigger functions cover the four ways a value reaches a row, and all of them delegate the verdict to one function, ores_reporting_value_in_set_fn. That function asks ores_reporting_value_in_set_item_fn what one value may be, and splits a comma separated list first when the set states cardinality = many. A fifth function, ores_reporting_setting_value_verdict_fn, answers the same question in a form a caller can read: whether the setting names a set at all, which set, and whether the value is in it. The engine settings sign-off path asks that one before a person is asked to sign.

The reason for the database rather than the services is that the writers are plural: the shell, a screen through the BFF, an import job and a publish function all reach the same table, and only a trigger sees all four.

The same guard states what is stored. A set names the form a configuration holds for a value ORE writes several ways, and the guard replaces the value with that form before the row exists, through ores_reporting_canonical_value_fn. A document that says A360 and one that says ACT/360 therefore leave one row, and the rendering never reaches a table at all.

2. The four guards

Trigger function Attached to Resolves the set by
ores_reporting_validate_parameter_value_fn ores_analytics_pricing_model_product_parameters_tbl (scope, name) on the setting link
ores_refdata_validate_curve_values_fn each of the refdata curve configuration tables the column's row in the element map, which names the set
ores_refdata_validate_convention_terms_fn each of the convention and currency pair tables the term's row in the element map, which names the set
ores_reporting_validate_engine_settings_part_fn the fixings, building and execution part tables Setup: and the part's ORE name

Each is a security definer trigger, and each raises an error that names the value, the setting and the set. The message is the interface: a person reading a failed import sees which rule refused them.

Each also stores the canonical form of a value it accepts, so a rendering does not reach a row by any of the four paths. The verdict and the conversion are two questions, and the guard asks them in that order: whether the value is in the set, then which form of it a configuration holds.

The part guard is the one that does not spell its own join. It reads the columns a group holds from ores_reporting_engine_settings_group_settings_tbl, so a column the group does not state is left alone and the guard does not know which table it is attached to (projects/ores.sql/create/reporting/reporting_engine_settings_part_value_guard_create.sql:46).

3. Why the verdict is a definer

A tenant-scoped caller that read the tables itself would meet row-level
security, see nothing, and read a rule that does not exist.

That sentence is from the function's own comment (projects/ores.sql/create/reporting/reporting_ore_parameter_value_validate_fn_create.sql:170), and it is the whole argument. The value sets are shared rows in the system tenant, and the writer is tenant-scoped. A check that ran with the writer's rights would meet row-level security, see no sets, find no rule and admit the value. Running the read as the definer is not a privilege shortcut; it is what makes the rule visible at all.

The verdict function exists in this shape for the same reason: the reporting core suite proves it answers for a tenant caller who cannot see the sets (projects/ores.sql/test/reporting_setting_value_verdict_test.sql, five assertions covering a found setting, a value in its set, a value outside it, a typed value and a setting with no set).

4. Before the write, not only after it

The engine settings proposal service refuses a bad value at the desk. It calls the same verdict function (projects/ores.reporting/service/src/service/engine_settings_proposal.cpp:171) and throws before the approval request exists, because a proposal nobody could apply should not be signed. The comment there states the design rule the whole cluster rests on: no copy of the rule lives in the service to drift from the store's.

The screen standard follows the same shape: a screen reads what the set states and draws it, rather than carrying a copy of the rule.

the_database_refuses_a_value_no_set_states.png

5. The three places a missing rule still passes

All are stated here rather than left for a reader to discover.

  • A leaf the element map states text for is admitted, because text is a rule that admits every value. For all 155 of the 477 leaves that number is a decision an ORE source justifies, and three more take the rule the ORE schema states for them: the extractor reads at least one form for every leaf, and the ratchet in generate_ore_element_map.py (UNREAD_ALLOWED) holds that count at zero so it can fall but not grow unnoticed.
  • A parameter with no set is admitted by the product parameter guard: it returns the row when the link is not found (…reporting_ore_parameter_value_validate_fn_create.sql:231). The engine settings guard does the opposite and refuses, because every engine setting a group states is required to name a set. The shell's completeness gate, ore_parameter_settings check (projects/ores.shell/reporting/src/app/commands/reporting/ore_parameter_settings_commands.cpp:353), is what catches the product case: it fails when a parameter in the contract has no set or a set has no values, and it refuses to judge at all when the contract is not published for the tenant.
  • A set code that names no set admits every value (…reporting_ore_parameter_value_validate_fn_create.sql:87), and the canonical function answers with the value itself for the same reason. Nothing catches a typo in a link's value_set_code; only a foreign key would, and it is still missing.

Until both are closed, "the rule is always enforced" is true for a parameter that has a set and false for one whose set is missing or misspelt. A reader checking a specific value should ask ores_reporting_setting_value_verdict_fn and read found, not is_in_set alone.

6. See also

Emacs 29.3 (Org mode 9.6.15)