Story: Close systemic codegen gaps

Table of Contents

This page documents a story in Sprint 25. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

1. Goal

Close two systemic codegen gaps where no template exists at all, so a regenerated entity never needs hand-wiring afterwards:

  1. RLS policies: no template emits ENABLE ROW LEVEL SECURITY / CREATE POLICY for tables with a tenant or party scope column. Every policy today is hand-written into a per-component *_rls_policies_create.sql file; the model layer has no signal for which table wants which policy shape, so the step is manual, easy to forget, and propped up by 53 RLS_001/RLS_002 ignore entries.
  2. Create/drop aggregator wiring: codegen writes each entity's *_create.sql=/=_drop.sql file but the component aggregator's \\ir include list is hand-maintained. A new entity's tables silently do not exist after compass db recreate until someone wires them in by hand; no validator catches a missed \\ir line.

Both gaps get model-driven codegen ownership: an entity declares the policy shapes it wants, the generated per-entity SQL carries them, and reachability through the aggregator chain is enforced. ores.marketdata is the adoption-proof component; the other 16 components adopt the machinery inside their own drift stories.

2. Status

Field Value
State DONE
Parent sprint Sprint 25
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-04

3. Acceptance

  • An entity model can declare its RLS policy shapes (tenant isolation, party isolation, system-tenant-visible tenant isolation), as first-class variability features with catalogue entries and profile defaults.
  • The per-entity SQL create emission carries the declared policies as a generated trailer, and the per-entity drop emission carries their drops, so a regenerated table owns its whole SQL lifecycle. The emitted blocks match the dominant hand-written block shapes verbatim.
  • ores.marketdata is the adoption proof: its five tables' policies migrate from marketdata_rls_policies_create.sql to the generated trailers, the now-empty hand file and its wiring are removed, and a second regeneration is byte-identical.
  • Reachability enforcement: a validation rule fails when a generated SQL file is not \\ir-reachable from its component aggregator chain (create and drop); it passes the current census with the deliberate exception classes encoded (RLS-layer files, top-level wired bundles).
  • compass db recreate + validate_schemas.sh stay green throughout; the RLS_001/RLS_002 ignore entries for the migrated population are removed where the policy now exists, with the remainder audited and recorded per table.
  • The history-provider registrar task (second task) is untouched by this one.

4. Tasks

Task State Start End Description
Codegen leaves new entities unwired: RLS policies and the create/drop aggregators DONE 2026-09-04 2026-09-04 No template in ores.codegen emits ENABLE ROW LEVEL SECURITY/CREATE POLICY for entities with :has_tenant_id: true, and generated per-table SQL is never linked into the component's hand-maintained *_create.sql/_drop.sql aggregator (the \ir include list) – both are manual steps every entity author has to remember, and both were missed while adding the CRM config tables.
Codegen the history-provider registrar wiring DONE 2026-09-04 2026-09-04 refdata's registrar.cpp is a monolithic hand-maintained file of near-identical register_history_provider() blocks per entity; this should be a codegen archetype instead.

5. Decisions

  • 2026-09-04 (task 2DFEF8F6): the per-entity register_<entity>_history_provider() surface is a codegen archetype pair; the component registrar stays a hand-written assembler. A whole-file registrar.cpp template was rejected: the file is a true assembler (NATS wiring, queue groups, the function-local history_registry() static, inline no-sub-registrar handlers), not a per-entity shape, so templating it would force every future structural change through mustache for no per-entity gain. Registrar.cpp keeps its current 53 calls, so the registered key set is unchanged, and the 11 mapper-bearing entities without a provider stay unregistered with identical runtime behaviour until their own wiring work adds them.
  • 2026-09-04 (task 2DFEF8F6): emission follows physical-space admission, the key follows the SQL side. The facet gates exactly like the presentation facet it renders through: #+model_types: domain_entity, default-enabled, so emission equals the mapper set by construction for any component that regenerates. The provider key is composed product.component.entity_singular, identically to the notify-trigger SQL facet's entity_name, so the server-side dispatch key and the row-change side of the history pipeline cannot drift apart. The service method spelling comes from entity_singular_short, the same model property that already drives the irregular get_type_history / get_centre_history spellings on the generated services.

6. Out of scope

Emacs 29.3 (Org mode 9.6.15)