Task: Find why so much SQL is hand-written and generate what codegen can

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 new entity reaches the database without a hand-edited file. Every hand-written SQL file has a recorded reason to be hand-written. What codegen can own, it generates. What stays hand-written is checked by a gate that fails the build.

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

  • Every hand-written file under projects/ores.sql/create, drop and populate is classified by why it is hand-written, and the classification and the command that produced it are in an evidence/ file.
  • Each class has a decision: generate it, keep it hand-written with a reason, or delete it.
  • The component create and drop aggregates are generated from the models, or the task records why they cannot be.
  • The WIRE_001 wiring check, and the other parser warnings, fail CI rather than print.
  • Generated drop files carry the same generated marker as the create files, so a count of hand-written files is possible. Both templates write it now, and all 537 generated drop files carry it. A marker alone still cannot count exactly, because 44 files keep one after their generator's input was deleted, so the census script renders the generator's output as well.

4. Plan

No single method fits. The census is an investigation, the CI gate is a feature, and generating the aggregates would be a refactoring whose evidence is an unchanged recreate. This plan sequences them.

Phase Produces Check
1. Census every SQL file under create, drop and populate, labelled entity codegen, other generator, or hand-written the labels sum to the tree's file count; every file codegen renders exists in the tree
2. Reasons for each hand-written file, why no generator produces it every file lands in exactly one class; one file per class is read to confirm
3. Aggregates whether a component's create and drop order can be derived from its models a derived order for one component satisfies every dependency the hand order encodes, or the gap is named
4. Gates the parser fails CI on a warning; generated drop files carry a marker the parser passes --strict on main; drift is clean after the template change
5. Decisions a decision per class, an evidence file, and follow-up tasks each class has generate, keep with a reason, or delete

The census uses the drift checker's own rendering, not file markers: it renders every catalogue component into a scratch root, and a file is entity codegen output exactly when that root contains it.

Stop and ask before generating any aggregate or moving a hand-written file to codegen. Phase 5 decides, and the operator approves, before that work starts. Stop phase 3 at a recommendation if the hand order encodes dependencies the models do not hold.

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
SQL files by origin: entity codegen, other generator, hand-written 1119, 164, 517 of 1800 render_all.py then classify.py, in the census 8107e1f421
Create files that are not entity codegen, by reason class 192 in 12 classes reasons.py 8107e1f421
Files no script includes, any suffix create 16, drop 7, populate 5 reach.py 8107e1f421
Dead legacy notify triggers invisible to WIRE_001 15 reach.py plus a code search 8107e1f421
Stale WIRE_001 ignore entries 4 of 9 reach.py against validation_ignore.txt 8107e1f421
Tables whose soft-FK parent is created later 88 of 372 order.py 8107e1f421
Hand-written RLS policies on a table with a model but no flag, plain tenant isolation about 129 of 251 keyword scan, rough 8107e1f421
Parser warnings with --strict after removing stale entries 0 plantuml_er_parse_sql.py ... --strict working tree
Files WIRE_001 reports once widened to every suffix and the populate tree, before the fixes 22 plantuml_er_parse_sql.py ... --warn working tree
Parser warnings with --strict after the fixes 0 plantuml_er_parse_sql.py ... --strict working tree
Full drop/drop.sql against a recreated database completes, no error; 0 tables and 115 functions left compass sql -- -v ON_ERROR_STOP=1 -f projects/ores.sql/drop/drop.sql working tree
Database recreate after the deletions and the wiring succeeds in 27 s, 372 tables compass db recreate -y -k working tree
Generated drop files with the generated marker 537 of 537, from 0 regenerate --all --address ores.sql.schema then the census working tree
Census at the end of the task 1119 codegen, 148 other generator, 517 hand-written of 1784 census_sql_origin.py working tree

The marker-based counts this task was scaffolded with (144 of 754 create scripts, 741 of 741 drop files) are superseded: a marker is missing from every generated drop file and present on 44 files whose generator input no longer exists.

6. Notes

Found while reviewing PR #2500 (today's market round trip). Adding five entities passed the build, the model drift check and every test, and the entities still did not reach the database until two aggregate files were edited by hand.

The aggregates are hand-written in every component. All 22 create/<component>/<component>_create.sql files and their drop twins are written by hand. They hold the \ir includes in dependency order, with prose per block. This is a project-wide convention, not something PR #2500 started.

A check exists and does not fail. validate_component_wiring in projects/ores.codegen/src/plantuml_er_parse_sql.py reports rule WIRE_001 for any create or drop file the root script does not reach. The codegen-drift.yml step runs the parser with --warn and without --strict, so the report only prints. With the five today's market RLS_001 warnings fixed, the tree has zero warnings, so --strict costs nothing today.

Generation looks possible for the aggregates. The models already hold the foreign keys, so a dependency order can be computed by a topological sort. The open questions:

  • where the prose per block goes;
  • how hand-written files (functions, RLS, service bundles) sit in the same order;
  • whether one generated fragment per component is better than generating the whole aggregate.

Generated drop files carry no marker. For example, drop/analytics/analytics_todays_market_configs_drop.sql is generated but has no "generated" line. So the drop count below cannot tell generated from hand-written.

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
#2515 [sql] Census the hand-written SQL, gate the schema parser, and mark generated drops

9. Review

# Comment summary File Decision Notes
1 Local review: the WIRE_001 docstring narrated history and wrapped a sentence badly plantuml_er_parse_sql.py Accepted It now states the reason only.
2 Local review: the CI comment narrated history codegen-drift.yml Accepted It now states what fails the step and where an exception goes.
3 PR checks: the ores.analytics.service source list missed the five today's market event registrars from PR #2500 component_files.cmake Accepted Regenerated; the five sources now compile. Like the other analytics registrars, nothing calls them yet.
4 PR checks: after the rebase, main's 36 commits added no generated SQL; the ores.sql.schema regeneration changed nothing and every generated drop file carries the marker SQL tree No change Verified with the census.
5 PR review: reachability is not scoped to a tree; a drop file included only from create.sql counts as wired plantuml_er_parse_sql.py Accepted The walk from the bootstrap scripts stops at another tree's root. Test added.
6 PR review: every top-level script counts as a bootstrap root plantuml_er_parse_sql.py Accepted, as a comment Deliberate, because setup_schema.sql and recreate_database.sql include files directly; the comment now says so.
7 PR review: a commented-out \ir counts as wiring plantuml_er_parse_sql.py Accepted Only an \ir at the start of a line counts. Test added. The RLS reachability check shares the collector and gains the same fix. The strict parser still reports no warning.
8 PR review: the census marker matches anywhere in the first 3000 bytes census_sql_origin.py Accepted It matches on comment lines only. The census counts are unchanged.
9 PR review: the 115 leftover functions and the two unwired populate scripts captures No change Already filed.
10 PR review: local clang-format mismatch on trade_service.cpp — No change Local only; CI passes.

10. Result

The hand-written SQL is measured, every class has a decision, and the checks that let two defects reach main are now gates.

The census. Of 1800 SQL files, 1119 are entity codegen output, 164 come from other generators and 517 are hand-written. The method renders the generator's own output rather than trusting markers. Every class, its reason and its decision are in the evidence file, with the scripts.

Decisions.

Class Decision Owner
Component create and drop aggregates Generate the entity part per component; hand-written files stay listed by hand Generate the entity part of each component's SQL aggregates
FpML coded-scheme tables, artefacts and triggers (44 + 15) Model them so codegen owns them Model the FpML coded-scheme tables so codegen owns them
Hand-written RLS policies (251) Generate from model flags into a late per-component file Generate the hand-written RLS policies from model flags
Tables with no model (25) Model those a codegen shape covers Model the hand-written tables that have a codegen shape
Procedural code, views, alter scripts, uncatalogued components, populate seed data, data generators Keep; each reason is in the evidence —
Dead legacy triggers (15), broken FpML populate (1) Deleted this task

Why aggregates can be generated. Soft foreign keys impose no creation order: 88 tables are already created before the parent their trigger checks, and every recreate succeeds. Only a table before its trigger, functions from earlier components and hand files that name tables at creation need order.

Gates. The schema parser fails CI on any warning. WIRE_001 now covers every .sql file in the three trees, reached from their roots and from the bootstrap scripts, which found fifteen dead files and two populate scripts that never ran. Both drop templates now write the generated marker the create templates write, so the 537 generated drop files no longer look hand-written. The five real drop gaps are wired and the full drop script runs clean. The census is committed as a tool.

Recorded, not fixed here. Three captures: ores_iam_auth_events_tbl has a tenant_id and no RLS policy despite an ignore reason calling it tenant-scoped; two populate scripts were never wired, and wiring them changes what a recreate seeds; the drop script leaves 115 functions behind.

Component audit. The task touched ores.codegen and ores.sql. The automated half, validate_docs.sh, passes for all 41 components. It added one script to ores.codegen/scripts beside its peers, deleted sixteen dead SQL files and changed no folder, type or component, so the shape, morphology, naming and registry checks have nothing new to judge.

One operational note. The first recreate hung for minutes in DROP DATABASE on ProcSignalBarrier, with this environment's services still reconnecting. Stopping the services, cancelling the drop and retrying worked.

Emacs 29.3 (Org mode 9.6.15)