Task: Bind ores.reporting entities to profiles; verify zero-diff regen
Table of Contents
- 1. Goal
- 2. Status
- 3. Acceptance
- 4. Plan
- 5. Notes
- 5.1. Step 1 baseline (2026-09-17)
- 5.2. Step 2 survey
- 5.3. Steps 3 and 4: classification
- 5.4. The new-profile candidate
- 5.5. Un-modelled entities (scope added 2026-09-17)
- 5.6. Workspace ID is already present (2026-09-17)
- 5.7. Two product decisions (2026-09-17)
- 5.8. The Qt estate for reporting is generated but not built (2026-09-17)
- 5.9. Drift gate false positive (2026-09-17)
- 5.10. Part 2, family 1: risk_report_config is modelled (2026-09-17)
- 5.10.1. The physical space table narrows the API surface
- 5.10.2. A column the entity template cannot express is now refused
- 5.10.3. sqlgen cannot bind a Postgres array
- 5.10.4. Nine columns needed an explicit C++ default
- 5.10.5. Four generator blocks wrote std::nullopt into a std::string
- 5.10.6. The generated SQL gains four things and renames one index
- 5.11. Three entities stay hand-written: recorded exceptions (2026-09-17)
- 5.12. Exceptions revisited, second pass (2026-09-17)
- 5.13. Step 7 finding: the generated registrars have no caller (2026-09-17)
- 5.14. Separate finding: eight nullable scalars have no C++ default (2026-09-17)
- 5.15. The regenerated SQL was applied and rolled back (2026-09-17)
- 5.16. Follow-up: the new entity has no test file (2026-09-17)
- 5.17. Out of scope: report_definition_template mirrors a DQ table (2026-09-17)
- 5.18. Verification: build, regeneration, and CMake lists (2026-09-17)
- 6. Test Scenarios
- 7. PRs
- 8. Review
- 9. Result
This page documents a task in the Entity classification and drift baseline: ores.reporting story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Survey every ores.reporting entity against the profile catalogue, rebind matches, regenerate, and require zero additional diff against the pre-rebind checked-in output.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Entity classification and drift baseline: ores.reporting |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-17 |
3. Acceptance
- Every entity discovered via the reporting catalogue entry is surveyed against all Variability Profiles.
- Every matching entity is rebound and regenerated with zero additional diff versus its pre-rebind checked-in output.
- No-match entities are recorded with their actual feature values, not silently dropped.
- Every entity that carries a hand-written copy of a generated shape gets an org model. The regeneration replaces the hand-written code, and the hand-written copy is deleted.
- Any new profile the survey justifies is authored under
projects/modeling/and added to the profile index. - Full local build and ctest green after regeneration.
4. Plan
Method: the compass-codegen-fix-drift skill, run at automation level 0
(manual, per fix). Findings from steps 1 to 4 are in * Notes. The work
divides into three parts.
- Settle the two profile binds. Neither bind needs a new profile; the two
entities do not resolve to one shape after all.
report_definitionkeepsuuid-identified-lookupand its explicithas_workspace_id: trueoverride. The override is deliberate: the product wants report definitions to record the live workspace.report_instancekeepsfk-scoped-childand gainsparent_entity_singular: report_definition, so a report definition explorer can open an instance nested under it. The profile'shas_explorer_apihalf was already materialised.
Bring the un-modelled entities into codegen where the archetypes allow it.
risk_report_confighad SQL, hand-written C++, and no org model. It now has a model, and regeneration replaced the hand-written copies.Three entities stay hand-written, as recorded exceptions:
report_input_bundleand the tworisk_report_configjunction tables. The archetypes cannot express their shape. The reasons are in* Notes. No exclusion was added, so the drift check still sees them.- Verify composition, then the gate. Check that the registrar families the
regeneration rewrites have a caller at the composition points. Confirm
check_component_drift.py --allstill passes.
Order: part 1 first. It is small and it proves the profile mechanism. Part 2 carries the bulk. Part 3 closes.
5. Notes
5.1. Step 1 baseline (2026-09-17)
Regenerated all 5 models of reporting against an untouched tree. The output
is byte-identical. There is no pre-existing drift.
reporting is already in the KNOWN_DRIFT_FREE registry. The story names
reporting-cpp, but the catalogue holds no such entry. --address selects
the technical space, and the component is called reporting.
5.2. Step 2 survey
Four entity models carry variability: concurrency_policy, report_type,
report_definition, report_instance. The module and the component overview
are structurally not entities, so exclude both. No junction and no legacy
lookup_entity exists, so no migration is needed.
5.3. Steps 3 and 4: classification
All four entities already bind a profile. Two binds are clean:
| Entity | Profile | Verdict |
|---|---|---|
| concurrency_policy | simple-lookup | Clean. |
| report_type | simple-lookup | Clean. |
Two binds are wrong.
report_definition binds uuid-identified-lookup and sets
:has_workspace_id: true. The profile fixes has_workspace_id to false. The
generated SQL confirms the override: the table carries a workspace_id
column, a workspace index, and a workspace validation call.
report_instance binds fk-scoped-child. That profile requires
parent_entity_singular and fixes has_explorer_api to true. The entity sets
neither. No explorer artefact exists for any reporting entity. The entity has
its own top-level MDI window instead, which contradicts the profile.
5.4. The new-profile candidate
Both wrong binds resolve to the same shape: UUID primary key, tenant and workspace scope, standard Qt tier, no parent. No existing profile covers it. The skill allows a new profile once a second entity repeats the combination. Here two entities repeat it.
5.5. Un-modelled entities (scope added 2026-09-17)
A classification of all 206 reporting source files by the
AUTO-GENERATED FILE - DO NOT EDIT MANUALLY marker gives 148 generated and
58 hand-written.
Two entity families in the hand-written set have SQL tables but no org model:
| Family | Table | C++ artefacts |
|---|---|---|
| risk_report_config | ores_reporting_risk_report_configs_tbl | domain type, entity, mapper, repo |
| report_input_bundle | ores_reporting_report_input_bundles_tbl | entity, repository |
| risk_report_config_books | ores_reporting_risk_report_config_books_tbl | none |
| risk_report_config_portfolios | ores_reporting_risk_report_config_portfolios_tbl | none |
Both families mirror the generated shape exactly, minus the marker. Both use a
stale include guard of the form ORES_REPORTING_REPOSITORY_..., where the
generator emits ORES_REPORTING_CORE_REPOSITORY_.... The component
description advertises "risk report configurations", but the module lists only
four entities.
5.6. Workspace ID is already present (2026-09-17)
No model change was needed. Both entities already carry workspace_id in all
three layers:
| Layer | report_definition | report_instance |
|---|---|---|
| SQL | column, index, and validation call | same |
| Entity | std::string workspace_id; |
same |
| Domain | boost::uuids::uuid workspace_id = ... |
same |
Both default to utility::uuid::live_workspace_id(), so a new definition
already lands in the live workspace. report_definition sets the flag
explicitly; report_instance inherits it from fk-scoped-child.
5.7. Two product decisions (2026-09-17)
report_definition keeps has_workspace_id: true against its profile. This
is deliberate and recorded here, not drift.
report_instance gains parent_entity_singular: report_definition plus
parent_id_field_camel: definitionId and parent_id_field: definition_id.
The last two are needed because the resolver derives
parent_report_definition_id by default, and the column is definition_id.
Regeneration adds openAddWithParent() and a prefill block. Output is
idempotent.
5.8. The Qt estate for reporting is generated but not built (2026-09-17)
projects/ores.qt/reporting/ holds 36 generated files. It is the only
ores.qt/* directory with no CMakeLists.txt and no add_subdirectory
entry. projects/ores.qt/compute/ instead builds 40 hand-written files with
the same class names, the same ores::qt namespace, and the same
ores.qt/... include paths.
All 16 shared files differ. The hand-written set has four typed history dialogs the generated set lacks; the generated set has the explorer API and version-history plumbing the hand-written set lacks. Neither is a superset.
Consequence: codegen for reporting's Qt produces output the application never compiles. PRs #1904 and #1918 are where the two sets diverged.
Decision (2026-09-17): two steps. Step 1, this task, lets the model edits
regenerate into ores.qt/reporting and leaves the build alone. Step 2, a
separate task, brings the templates to parity, wires the plugin, and deletes
the hand-written copies. Until step 2 the generated controller changes do not
reach the application, so no tree widget appears yet.
The unused qt_component knob in core.py exists for exactly this split. No
entity in the repo sets it.
5.9. Drift gate false positive (2026-09-17)
check_component_drift.py exits 1 whenever any file in the tree is dirty,
because it runs git diff over the whole repository rather than over the
component's generated paths. A dirty agile doc is enough to fail it. Verified:
with only the two agile org files modified, the regeneration is byte-identical
and the run still exits 1.
The call is at line 115 of projects/ores.codegen/scripts/check_component_drift.py:
subprocess.run(["git", "diff"], cwd=REPO_ROOT, ...), with no pathspec. The
script's own docstring describes the intent, which is to catch a hand-edited
generated file.
Consequence: --all can only be run against a committed tree. Run it after
the commit that lands the regeneration, not before. Scoping the diff to the
component's generated paths would remove the false positive and make the check
usable before a commit.
5.10. Part 2, family 1: risk_report_config is modelled (2026-09-17)
The model is projects/ores.reporting/modeling/ores.reporting.risk_report_config.org.
It was reverse-engineered from the hand-written C++, because no model existed.
Regeneration replaced 15 files and refreshed two component_files.cmake lists.
5.10.1. The physical space table narrows the API surface
The model carries a * Physical space table that disables ten addresses:
ores.cpp.service, ores.cpp.protocol, ores.cpp.nats-handler,
ores.cpp.nats-sub-registrar, ores.cpp.nats-event-registrar,
ores.cpp.nats-eventing, ores.cpp.nats-event-cache,
ores.cpp.history-provider-registrar, ores.cpp.presentation, and
ores.cpp.eventing-integration-test.
Reason: nothing writes a risk report config today. The only consumer is
report_execution_handler. ores.reporting/core/src/messaging/registrar.cpp
is a hand-written fan-out, not a per-entity registrar aggregator. A generated
registrar has nowhere to join. This is the step 7 finding, recorded below.
5.10.2. A column the entity template cannot express is now refused
While reversing the hand-written header, one column vanished from the generated entity with no error. The entity template gives a column its struct member by switching on one of nine type flags. A column that matches none of them is dropped in silence. The SQL column and the domain member both still read as present, so no other layer notices, and the field quietly stops being on the wire.
Two shapes reach that state: a nullable column whose C++ type is outside the
string and numeric sets, and a nullable scoped enum. Only var_quantiles
matched either one repo-wide.
core.py now raises when a column matches no flag. The model declares the
intended omission with :sql_only: true, which keeps the column in the schema
and keeps it out of every C++ layer. The two templates that render columns
honour the flag. A regression test covers all four cases:
projects/ores.codegen/tests/test_entity_struct_column_coverage.py.
Precedent: commit 09c6c4a03a, which refuses a domain group model that would
lose a field.
5.10.3. sqlgen cannot bind a Postgres array
var_quantiles is numeric(5,4)[]. sqlgen has no array binding, so no C++
layer can carry it. The column stays in the schema. Remove :sql_only: and
regenerate once sqlgen, or a replacement, can bind numeric[].
5.10.4. Nine columns needed an explicit C++ default
:default: is SQL-side only. For a plain scalar, core.py auto-supplies a
C++ default only for a non-nullable bool or int. Nine columns of the
hand-written header carried a meaningful default that regeneration would have
dropped, so the model now sets :default_value: on each:
observation_model, n_threads, market_data_type, npv_enabled,
cashflow_enabled, xva_quantile, xva_dim_quantile,
xva_dim_horizon_calendar_days, xva_dim_regression_order, and var_method.
5.10.5. Four generator blocks wrote std::nullopt into a std::string
The hand-written generator blocks for market_data_date, var_method,
simm_version, and simm_calculation_currency returned std::nullopt into a
plain std::string member. That does not compile. The blocks now return a
string literal.
5.10.6. The generated SQL gains four things and renames one index
The create script was regenerated. Behaviour is unchanged except for one index
name: risk_report_configs_definition_uniq_idx becomes
risk_report_configs_report_definition_id_uniq_idx. Four genuine improvements
land with it: clock_timestamp() instead of current_timestamp,
set search_path = public, pg_temp, a call to
ores_dq_validate_change_reason_fn, and consistent NEW.=/=OLD. casing.
var_quantiles is retained.
The notify-trigger script changes one declaration: changed_id becomes uuid
instead of text. The drop script loses four drop index statements, because
the generated form drops the table and its indexes together.
5.11. Three entities stay hand-written: recorded exceptions (2026-09-17)
The archetypes cannot express these tables. No exclusion was added, so the drift check still sees them.
| Entity | Gap in the archetype |
|---|---|
| risk_report_config_books | sql_schema_junction_create.mustache emits version plus four audit columns unconditionally. The table has neither. |
| risk_report_config_portfolios | Same gap. |
| report_input_bundle | sql_schema_domain_entity_create.mustache emits valid_from=/=valid_to and puts both in the primary key unconditionally. The table is immutable: primary key (tenant_id, id) and a plain created_at. |
:has_tenant_id: false does not rescue the two junctions. The template ties
the tenant_id column to the same flag, so the column the tables do carry
would disappear.
The report_input_bundle gap is not confined to the SQL. The entity, mapper,
and domain templates carry the same assumption. Its hand-written entity
declares sqlgen::PrimaryKey<std::string> id and a bare db_timestamp
created_at, with no valid_from, valid_to, version, or audit members.
Generating it would add seven columns to the table and change its primary
key. That is a schema decision, not a codegen detail.
The junction gap is wider than one flag. Wrapping the version and audit block
in a flag is necessary, and not sufficient: the generated junction also puts
tenant_id in the primary key and in the exclusion constraint, and names the
left and right indexes from the column names rather than the hand-chosen
..._config_idx. Three template capabilities, not one.
Nothing writes either junction table today. A repo-wide search finds only
readers: reporting_resolve_book_ids_fn, the RLS policies, and the two
hand-written scope queries in risk_report_config_repository.cpp. No INSERT
exists in C++, SQL, or seed data. That is why the gap is not urgent.
5.12. Exceptions revisited, second pass (2026-09-17)
Two claims from the first pass did not survive checking. The corrections change the recommendation for both families.
The immutable archetype is not new; it is orphaned. The retired JSON-model
pipeline had sql_schema_non_temporal_create.mustache, and three checked-in
files still name it:
| File | Owner |
|---|---|
projects/ores.sql/create/workflow/workflow_workflow_instance_create.sql |
workflow |
projects/ores.sql/create/workflow/workflow_workflow_step_create.sql |
workflow |
projects/ores.sql/create/database/database_database_infos_create.sql |
database |
The template does not exist. projects/ores.codegen/generate_workflow_schema.sh
still points at models/workflow/*_domain_entity.json and src/generator.py,
both retired. So the marker on those three files lies: nothing regenerates
them. Neither workflow nor database is in KNOWN_DRIFT_FREE, so the gate
never looks. This is a separate finding from the three exceptions, and it is
the one a reader is most likely to be misled by.
The generated shape in those files matches report_input_bundles closely:
uuid id, tenant_id, a plain created_at, no temporal columns, and no
partial-index predicate. One difference is instructive: workflow_instance
uses primary key (id) while report_input_bundles uses primary key
(tenant_id, id).
The bundle is live; the junctions are not. report_input_bundle is written
today by report_execution_handler.cpp (the assemble_bundle step) through
report_input_bundle_repository::create, and read by find_by_instance_id.
Its hand-written surface is five files and two methods. The entity archetype
generates a bitemporal, versioned, audited, eventing-enabled, history-enabled,
paginated stack. Forcing the bundle through it would add seven columns to a
live table, move the primary key, and emit far more code than the two methods
that exist.
The gap is therefore not a missing SQL branch. It is a lifecycle the archetype does not model: write once, never update, never delete, keep no history.
Revised recommendation. Hold both families as recorded exceptions, and record the trigger to revisit each:
- The two junctions: revisit when something first writes them. At that point conform them to the conventional junction shape by hand and generate them, or close the three template gaps. Order does not matter while no writer exists, because the audit columns would stay empty either way.
report_input_bundle: revisit when a second write-once entity appears. One entity does not justify a new archetype. The same rule the skill applies to profiles applies here: a novel combination earns its own name only once a second entity repeats it.
The orphaned sql_schema_non_temporal_create.mustache output is a separate
task. It belongs with the ores.dq artefact tier finding, not with this one.
The legacy archetype references are deleted (2026-09-17)
The archetype itself was already gone. The template is absent from
projects/ores.codegen/library/templates/. Only its references remained.
Three changes remove them.
projects/ores.codegen/generate_workflow_schema.shis deleted. The script calledsrc/generator.pywithmodels/workflow/*_domain_entity.json. Both are absent. Its template is absent. Nothing could run it.- The three SQL files lost their false
AUTO-GENERATEDblock. Only comment lines were removed, so the DDL is unchanged and the recreated schema is unaffected. Thedatabase_infofile also lost a meaningless* Tabletitle, an artifact of its empty entity title. ores_schema.pumlis regenerated, along with theores_schema.pngandores_schema.cmapxartifacts the same script renders. The three notes no longer carry the stale text.
git grep now finds zero references under projects/. The remaining hits
are past-sprint org files, which are history and stay unchanged.
Two findings came out of this.
The whole family of generator scripts is dead. src/generator.py and
models/ are both absent, so every generate_*.sh script in
projects/ores.codegen/ calls a module that does not exist. A backlog story
already scopes the cleanup: Decommission legacy ores.codegen bash scripts
(doc/agile/product_backlog/next/decommission_codegen_bash_scripts/story.org,
ID 1C659EB3-5227-46B6-8B5C-68E9F98C10D1). It is in state BACKLOG with one
unstarted task. This task deleted only the script the archetype referenced.
The ER diagram was six weeks stale. Regenerating it changed 1786 lines. The last generation was 2026-08-07, so the diff is real drift across every component, not noise from this change. The repository convention is to regenerate and commit the diagram with SQL work, so this is overdue rather than wrong. It is one commit's worth of unrelated churn and can be split out.
5.13. Step 7 finding: the generated registrars have no caller (2026-09-17)
ores.reporting/core/src/messaging/registrar.cpp is hand-written. It builds
each *_handler directly, so the generated per-entity registrar classes have
nowhere to join. Two families are orphaned today, four classes each:
| Family | Classes |
|---|---|
*_registrar |
report_definition, report_instance, report_type, concurrency_policy |
*_history_provider_registrar |
same four |
Nothing calls either family. The *_event_registrar family is composed:
ores.reporting/service/src/app/application.cpp calls all four
register_*_event_mapping functions, so it is not part of the finding.
The skill allows two end states: composed at the composition point, or recorded as a deliberately unwired subject. These are the second state.
The physical space table in the risk_report_config model follows from this
finding. Generating a registrar with no caller adds dead code.
5.14. Separate finding: eight nullable scalars have no C++ default (2026-09-17)
Recorded, not fixed. A nullable plain scalar with no :default_value: leaves
its C++ member uninitialised, so default construction reads indeterminate
values. Eight columns across three components have this shape:
| Component | Columns |
|---|---|
| ores.compute | pgmq_msg_id (std::int64_t), outcome (int) |
| ores.trading | repo_rate (double), funding_rate (double) |
| ores.reporting | xva_quantile, xva_dim_quantile, xva_dim_horizon_calendar_days, xva_dim_regression_order |
The four ores.reporting columns are fixed in this task's model. The four
outside ores.reporting are left alone: fixing them changes generated output
for ores.compute and ores.trading, which breaks their drift gates. They
belong to a separate codegen-gap task.
5.15. The regenerated SQL was applied and rolled back (2026-09-17)
reporting_create.sql and reporting_drop.sql were both run inside one
transaction against the development database, then rolled back. The whole
reporting component was exercised, not only the changed file. Both directions
completed with no error.
One migration note. The live database still carries
risk_report_configs_definition_uniq_idx. The regenerated create script does
not create that name, and the drop script does not drop it. A fresh database
from compass db recreate is unaffected. An upgraded database keeps the stale
index until someone drops it by hand.
5.16. Follow-up: the new entity has no test file (2026-09-17)
The four older reporting entities each have a hand-written
api/tests/domain_<entity>_tests.cpp. Tests are not generated in this
component, so no test appeared for risk_report_config.
The template is already covered four times over in this component, and the
regenerated header was diffed line by line against the hand-written one. The
gap is recorded rather than filled: the four hand-written repository methods
(find_by_definition_id, resolve_book_ids, get_book_scope,
get_portfolio_scope) have no coverage either, and they are the part that a
test should target.
5.17. Out of scope: report_definition_template mirrors a DQ table (2026-09-17)
ores.reporting.api/domain/report_definition_template.hpp and its
report_definition_template_json_io pair are hand-written. They are a read
model for a row of ores_dq_report_definitions_artefact_tbl.
The type is not a reporting entity and has no table of its own, so no
reporting model can generate it. Its true home is the ores.dq artefact
tier, which is un-modelled: the repository holds about fifty
dq_*_artefact_create.sql tables and one model, ores.dq.artefact_type.
Reported here for the backlog. Fixing it means modelling the DQ artefact tier, which is a different component and a different task.
5.18. Verification: build, regeneration, and CMake lists (2026-09-17)
Three checks passed.
- Build.
./compass.sh buildexited 0 at 100 percent with zeroerror:lines. All seven newrisk_report_configtranslation units compiled, andores.reporting.api,ores.reporting.core,.service, and every test target linked. - Regeneration is idempotent.
./compass.sh codegen regenerate --component reporting --address oresran against the already-regenerated tree. Thegit diffhash, the untracked-file hash, andgit status --shortare all unchanged. The same command insidecheck_component_drift.pyran a third time and again changed nothing. Skill step 5 is satisfied. - CMake source lists.
regenerate_cmake_component_files.py --all --checkexits 0. Everycomponent_files.cmakein the repository is current.
The component test target compiles; four of its tests need a running NATS.
./compass.sh build test_ores.reporting.core.tests --preset
linux-clang-debug-make built and linked the target at 100 percent, then ran
18 test cases. Four failed, all with the same message: NATS connect failed:
No server available for connection. The other 14 passed.
The failure is the environment, not the code under test. The four failures
are the eventing integration tests, and no ORE Studio service was running for
this worktree at the time. compass services status showed 23 of 23 units
stopped, and the NATS broker for this worktree listens on port 21605 only
while nats-server-clever_dijkstra is up. Starting the services fixed the
cause; the ctest result recorded below is the run made after that start.
Two services, marketdata and synthetic, failed to start for the same
reason described next.
The database was twenty days stale. That was the root cause.
The first full rat run failed. Two targets did not pass:
ores.compute.core.tests (7 of 13) and ores.analytics.core.tests (2 of 34).
The compute failures read column "version" of relation
"ores_compute_app_version_platforms_tbl" does not exist. The rat target
aborted there, so it never reached a clean reporting result.
compass db status gave the cause. The schema was built from commit
fdbdf2f98d-d on 2026-08-28 and reported drift: stale, twenty days behind
HEAD. compass db recreate -y -k rebuilt it in 16 minutes and 32 seconds.
The status now reads drift: current and built from: bcbcb8e380-d, which is
this task's own commit. The database therefore holds the new
risk_report_config schema.
After the recreate all 23 services started, including marketdata and
synthetic. No service fails now. The two earlier failures were stale-schema
symptoms, not defects in those components.
The full suite is green. The acceptance is met.
./compass.sh build rat --preset linux-clang-debug-make ran 72 test suites
and exited 0. It reports zero failed: lines, and 62 suites print "All tests
passed". The acceptance clause "Full local build and ctest green after
regeneration" is therefore satisfied.
ores.reporting.core.tests passes all 18 test cases with 58 assertions in
this run. The four eventing integration tests that failed on the stopped
environment now pass. The two targets that failed on the stale database,
ores.compute.core.tests and ores.analytics.core.tests, both pass.
The drift check passes once the tree is committed.
Both claims above were confirmed by running the check twice more. Before the commit it exited 1, printing this task's own change set. After the commit it printed "No drift: regenerated output matches the checked-in tree" and exited
--allover theKNOWN_DRIFT_FREEregistry then exited 0 as well, which
retires skill step 8 for this component.
6. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) 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 |
|---|---|---|
7. PRs
| PR | Title |
|---|---|
| #2094 | [reporting,codegen] Bind the reporting entities to profiles and generate risk_report_config |
8. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | The sql_only continue is the last statement in the column loop, so it silently decides the fate of any step added after it. |
projects/ores.codegen/src/codegen/core.py |
Accept | The skip is correct: an SQL-only column reaches no C++ layer. The comment now says so and states that the continue stays last. |
| 2 | The new guard test is not wired into an automated runner. Nothing in CI runs projects/ores.codegen/tests. |
projects/ores.codegen/tests/test_entity_struct_column_coverage.py |
Decline | Pre-existing and repo-wide: the 38 tests in that directory share the gap. It needs its own change. The drift gate enforces the generated output today. |
| 3 | The index rename leaves the old index in place on a database that is upgraded, not recreated. | projects/ores.sql/create/reporting/reporting_risk_report_configs_create.sql |
Decline | Recorded in this task. compass db recreate is the documented route, and the drop script drops the table with its indexes. |
| 4 | ores.qt/reporting is regenerated, but no CMakeLists.txt builds it, so the Qt output is not compiled or tested. |
projects/ores.qt/reporting/src/ReportInstanceController.cpp |
Decline | Deliberate. Step 1 of the two-step plan in the story. Carried as a testing limitation. |
| 5 | codegen-drift.yml runs on pull requests for the paths this change touches, so the "CI runs no PR gate" premise is wrong here. |
.github/workflows/codegen-drift.yml |
Decline | Process finding, not a code defect, and out of scope for this change — the same two checks run locally instead. Fixed later on this branch by the commit that corrected the premise in the two skills and in the CI knowledge doc it came from. |
| 6 | misspell-fixer . reports three files. |
doc/agile/versions/v0/sprint_25/shell-command-codegen/task_generate-the-trading-command-units.org (+2) |
Decline | All three quote the upstream identifier LOAD_PROFILE_1_EXPLICT inside Review tables that record the decision not to correct it. No file this change touches. |
| 7 | The {{^sql_only}} guards were written into the tangle output, not the org template sources, so compass build --direct codegen_templates stripped them and both .mustache files stopped being reproducible. |
projects/ores.codegen/library/templates/ores.cpp.domain.class_header.org |
Accept | Fixed. The guards now live in the org sources, and the re-tangle reproduces both .mustache files byte for byte. The codegen-drift check found this, not a reading of the diff. |
| 8 | A nullable numeric column maps to a plain domain field through a 0 sentinel: value_or(0) on read and = 0 ? nullopt : value on write, so a caller that sets the field to exactly 0 persists NULL. |
projects/ores.codegen/library/templates/cpp_domain_type_mapper.cpp.mustache |
Decline | Pre-existing and repo-wide: this change does not touch the template, and every nullable numeric column in the tree shares the convention. The two values 0 and NULL are conflated, so a deliberate 0 is written back as NULL. Not a regression here — the template deserves its own change. |
| 9 | var_quantiles is unreachable from C++ once the struct drops it; confirm that nothing downstream still expects the field. |
projects/ores.reporting/api/modeling/ores.reporting.api.puml |
Accept | Nothing in C++ reads the field, and this task records why: sqlgen cannot bind numeric[]. But the hand-written API diagram still declared it, so it claimed a member the struct does not have. The member is removed and the PNG regenerated. It was the only divergence across the diagram's 36 members. |
9. Result
Done. The bind is complete and the drift gate holds.
What shipped. Eight commits on
feature/bind-reporting-entities-to-profiles.
report_definitionkeepsuuid-identified-lookupand its explicithas_workspace_id: trueoverride. The override is deliberate.report_instancekeepsfk-scoped-childand gainsparent_entity_singular: report_definition.risk_report_configwas hand-written. It now has an org model atprojects/ores.reporting/modeling/ores.reporting.risk_report_config.org, and codegen generates its API, core repository, and SQL.- The retired non-temporal SQL archetype is gone: the dead script deleted,
three false
AUTO-GENERATEDheaders removed, and the ER diagram regenerated. - The
sql_onlyguards on the domain class and generator templates moved from the tangle output into theorgsources they are tangled from. Both.mustachefiles are reproducible again fromcompass build --direct codegen_templates. See the Review table, finding 7.
Acceptance. Five of the six clauses are met outright.
- The survey covers every entity in the reporting catalogue entry.
- Every matching entity regenerates with zero added diff.
- The no-match entities are recorded with their feature values.
- No new profile was needed.
- The full
ratsuite is green: 73 suites, 0 failures.
The fourth clause, "every entity that carries a hand-written copy of a
generated shape gets an org model", is met for risk_report_config alone.
Three entities stay hand-written as recorded exceptions. The record names
the archetype gap that blocks each one and a revisit trigger. This follows
the skill's recorded-exception path. It is not a silent omission. See
* Notes.
The drift gate. reporting-cpp is in KNOWN_DRIFT_FREE.
check_component_drift.py --component reporting exits 1 before a commit and
0 after, where it prints "No drift: regenerated output matches the
checked-in tree". --all over the registry exits 0. This retires skill
step 8.
Environment. The database was twenty days stale. The stale schema caused
two suites to fail on a missing column. compass db recreate -y -k rebuilt
it in 16m 32s. db status then reported drift: current. All 23 services
start.
The rebase onto origin/main made the database stale a second time. Main's
nine commits added ores_refdata_series_subclass_codes_tbl and
retaxonomised the asset classes, and the database predated both.
ores.refdata.core.tests and ores.marketdata.core.tests failed on the
missing relation, and on asset_class_code: interest_rates. These are
stale-schema symptoms, not defects in this change: the branch touches no
refdata or marketdata file. A second compass db recreate -y -k cleared
both. Both suites now pass: ores.refdata.core.tests in 263.78s and
ores.marketdata.core.tests in 4.25s.
The suite ran as ctest -I index-range chunks, not one run. Three other
worktrees were building and testing on this host at the same time, and the
memory guard stopped each long background run. The chunks cover tests 1 to
73 with no gap and no repeat: 1-15, 16-30, 31-40, 41-44, 45-60, 61-73.
Every chunk reported "100% tests passed".