Task: Migrate badge_definition/badge_severity/code_domain to standard codegen + DQ publishing
Table of Contents
This page documents a task in the Improve badge colour scheme support story. It captures the goal, current status, acceptance, and any notes or results.
Goal
badge_definition, badge_severity, and code_domain are modeled as ores.codegen.entity but their core/service/handler layer is hand-written (bespoke, combined badge_handler.hpp/badge_service.cpp with no tenant filtering on list reads) and code_domain's Qt layer (CodeDomainController/CodeDomainDetailDialog) is also hand-written rather than generated. Move all three onto the standard generated core/service/handler/repository/Qt stack, then wire them into the standard DQ publish-from-dq bundle mechanism (like report_definitions/LEI parties) so each tenant gets its own published copy of badge catalogue data instead of relying on an ad-hoc no-tenant-filter read of system-tenant rows.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Improve badge colour scheme support |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-16 |
Acceptance
- badge_definition, badge_severity, code_domain have standard generated domain/repository/entity/mapper/generator files (regenerated in place); badge_mapping junction investigated for the same treatment (found not tenant-broken, and its junction codegen output itself drifted – captured separately, not fixed here).
- Each of the four badge datasets (badge_severities, badge_definitions, code_domains, badge_mappings) is wired as a DQ artefact type with a preview + publish_from_dq function pair, dataset registration, and base-bundle membership, following the publication_pipeline.org self-referential (DQ-publishes-to-itself) pattern.
- Barclays Plc (or any tenant) automatically gets its own tenant-scoped copy of all four datasets on standard provisioning (verified via a full barclays_system_provision.ores run) – Code Domains/Badge Definitions/Badge Severities list windows show real data instead of being empty, and BadgeCache continues to resolve badges correctly.
- Deliberately out of scope (moved to a new follow-on story rather than sprawling this task further – see Notes): retiring the hand-written combined badge_handler.hpp/badge_service.cpp/badge_protocol.hpp in favour of the already-generated but currently-unwired per-entity handler/service/protocol/registrar files, and replacing the hand-written Qt controllers/dialogs (ores.qt.admin's BadgeDefinitionController/BadgeSeverityController, ores.qt.refdata's CodeDomainController) with generated ones. This is why Code Domain saves don't currently prompt for a change reason or publish an entity-changed event – both are standard behaviour every fully-generated dialog/handler has, but these three entities' live save path still routes through the old hand-written code.
Plan
- Scoped the migration with
compass codegen entity generate <entity> --diff:badge_definitionandbadge_severityeach produce ~33 new/changed files (they're modeledores.codegen.entitybut their NATS-facing layer – handler/protocol/service/event registrar/registrar – and Qt layer were hand-written, combined across all three badge entities in one bespokebadge_handler.hpp=/=badge_service.cpp=/=badge_protocol.hpp, never actually generated).code_domain's core layer turned out to already be standard (only its Qt layer had diverged – fixed in the blockedbadge_mappingbrowser task);badge_mappingis a junction with no core divergence. - Confirmed via diff that the
badge_severity_repository.cpp(and by extensionbadge_definition_repository.cpp) files, though already tracked/generated-looking, had theirread_latest(ctx)tenant filter hand-removed – the actual mechanism behind badge data being globally readable by any tenant today. Regenerating restores the standard tenant filter. - Ran
compass codegen entity generate badge_severityfor real (first entity, smallest, and a dependency ofbadge_definitionviaseverity_code). Repository/entity/mapper/domain/generator files were regenerated in place (already tracked); handler, protocol, service, event registrar/event type, and Qt controller/dialog/ window files are new. The new Qt files landed in the orphaned, unbuiltprojects/ores.qt/dqscaffold (no CMakeLists.txt) – consistent with thecode_domaindrift found earlier; left as-is for now, to be resolved when the Qt layer is actually wired up. - Verified:
ores.dq.core.lib,ores.dq.service.lib, and the fullores.qt.exebuild clean withbadge_severity's repository now tenant-filtered again – no compile-time fallout, since nothing yet references the new handler/service/Qt files (the old combined hand-writtenbadge_handler.hpp=/=badge_service.cppis still what's registered and running; it now transparently inherits the restored tenant filter on severity reads via the shared repository, which is expected and desired for the eventual end-state, but currently not yet compensated for by the publish-from-dq wiring – so badge_severity list reads for any non-system tenant are broken again until that's completed).ores.dq.core.tests(318 assertions, 104 cases) still green – no repository-level test coverage exercises the tenant-filter behaviour directly. - Committed as a deliberate, isolated first step (generate, commit, then handle fallout next) rather than bundling with handler/registrar rewiring or Qt migration in the same change.
- Investigated whether dropping
:has_tenant_id:from the model (making reads tenant-agnostic, like 18 of 108 otherores.codegen.entitymodels, e.g.tenant_type) was a viable fix. Rejected: it also removes thetenant_idcolumn from the generated SQL schema entirely – a real destructive schema/data change, not just a read-filter tweak – and the one concrete precedent (tenant_type) turned out to be itself drifted (its live table still has the column despite its current model lacking the flag). Filed a capture for that drift rather than either copying it or fixing it as part of this task. - Built the real fix instead: wired
badge_severityinto the DQ publish-from-dq pipeline (publication_pipeline.org), self-referential likecoding_schemes(DQ publishing into its own table, not another service's) –ores_dq_badge_severities_publish_fnwas modeled directly onores_dq_coding_schemes_publish_fn. Hand-crafted (not codegen'd, per that pipeline's own "what's never generated" list) throughout:dq_badge_severities_artefact_create.sql– shadow staging table.dq_badge_severities_population_functions_create.sql– preview- publish functions (
SECURITY DEFINER, reads system-tenant
artefact rows, writes into the target tenant's own
ores_dq_badge_severities_tblrows).- publish functions (
dq_badge_severities_dataset_populate.sql/dq_badge_severities_artefact_populate.sql– registers theore.badge_severitiesdataset (catalogORE, subject areaGeneral, methodologyORE Internal– all pre-existing, reused rather than inventing new ones) and stages the same six severitiesdq_badge_system_populate.sqlalready seeds directly into the system tenant's own row (kept in sync by hand; noted in each file).- Registered as artefact type
badge_severities(target_subjectdq.v1.badge-severities.publish-from-dq) and as abasebundle member (display_order 300) – reachable via the standard Librarian/=dq.v1.bundles.publish= path, not just a raw SQL call. - Wired the new subject into the existing generic
publish_from_dq_handler(subject->function map + registrar queue-subscribe) rather than writing a new handler class –ip2country=/=coding-schemes=/=badge-severitiesall share one handler. - Discovered along the way: two file-inclusion ordering bugs (my
dataset-registration script needs the
OREcatalog, which is populated bycatalogues_populate.sql– itself sequenced afterdq_populate.sqlinpopulate.sql; moved the new\ir=s to =populate.sqldirectly, after the catalogues include, rather than leaving them nested insidedq_populate.sql).
- Verified end-to-end against a freshly recreated database: artefact
table populated (6 rows),
ores_dq_badge_severity_preview_fn=/ =ores_dq_badge_severities_publish_fnboth work directly via SQL, and – after provisioning Barclays Plc viabarclays_system_provision.ores– confirmed Barclays starts with zerobadge_severityrows, then successfully published all six into its own tenant-scoped rows via the publish function. Fullores.dq.core.librebuild clean;ores.dq.core.testsstill green (318 assertions, 104 cases); SQL convention checker (fix_sql_conventions.py) clean. - Repeated the same treatment for
badge_definition: regenerated it onto the standard codegen stack viacompass codegen entity generate badge_definition. This surfaced two real bugs in the model's own hand-written:name generatorsnippets (never caught before becausebadge_definitionhad never actually been regenerated):faker::string::hexadecimal(...)doesn't exist (should befaker::number::hexadecimal, andfaker::HexPrefix::NoPrefixshould befaker::HexPrefix::None), and the nullablecss_classcolumn's generator assignedstd::optional<std::string>{}to what codegen actually generates as a plainstd::stringfield fornullable: true(confirmed against the convention used across the other 15+ nullable-string fields in the codebase, none of which wrap instd::optional). Fixed both inores.dq.badge_definition.orgdirectly; regenerated cleanly after. - Wired
badge_definitioninto the publish-from-dq pipeline the same way asbadge_severity: new artefact table (dq_badge_definitions_artefact_create.sql), preview/publish function pair modeled onores_dq_badge_severities_publish_fn, dataset+artefact-type+bundle-member registration (same catalogORE, subject areaGeneral, methodologyORE Internal,basebundle – consistent withbadge_severity's choices, not reinvented per-entity), and a new subject (dq.v1.badge-definitions.publish-from-dq) added to the same shared genericpublish_from_dq_handler. The 57 seed rows for the artefact populate script were extracted programmatically fromdq_badge_system_populate.sql(regex over the existingores_dq_badge_definitions_upsert_fncalls) rather than hand-transcribed, to avoid transcription errors across that many rows. - Noted (not enforced at the DB level –
severity_codeis a soft FK, no constraint):badge_severitiesshould be published to a tenant beforebadge_definitions, so resolved badges aren't left referencing a severity the tenant doesn't have yet. Documented in the dataset populate script's own header comment. - Verified end-to-end again: freshly recreated database, artefact
table populated (57 rows), publish function works directly via
SQL (57/57 updated against the system tenant's own rows). Then,
on a second fresh recreate + full Barclays provisioning run,
found
badge_severities=/=badge_definitionswere already published for Barclays automatically – confirming thebasebundle-member wiring (Plan item 7) actually fires during real tenant provisioning, not just via a manual SQL call: Barclays ended up with all 6 severities and all 57 definitions, spot- checked (activebadge:#22c55ebackground,successseverity, matches the source seed exactly). Full stack rebuild (ores.dq.core.lib,ores.dq.core.tests,ores.dq.service.lib, the fullores.qt.exe) clean;ores.dq.core.testsstill green (318 assertions, 104 cases). - Repeated the same treatment for
code_domain: regenerated onto the standard codegen stack viacompass codegen entity generate code_domain(no model bugs this time – its generator snippet was already faker-API-correct). This also silently re-broke cross-tenant reads for code domains, same root cause, same fix: artefact table (dq_code_domains_artefact_create.sql), preview/publish function pair, dataset+artefact-type+bundle-member registration (same catalog/subject-area/methodology/=base= bundle choices as the other two), and a newdq.v1.code-domains.publish-from-dqsubject on the shared handler. The 31 seed rows were extracted programmatically the same way asbadge_definition's 57 – this time the extraction regex needed a fix mid-way: the first attempt (reused verbatim frombadge_definition's extraction) silently droppedtenor_unit(30 instead of 31 matches) because its description contains a SQL-escaped apostrophe (tenor''s duration) that the regex's backslash-escape assumption didn't handle; fixed to handle doubled single-quotes (the actual SQL escaping convention) and re-verified the row count against a plaingrep -ccount before proceeding. - Note:
code_domain's hand-written Qt layer (CodeDomainController=/=CodeDomainDetailDialoginprojects/ores.qt/refdata, including theBadgeMappingsTabwired in by the blockedbadge_mappingbrowser task) is unaffected by this regeneration – confirmed in that earlier task that these are hand-maintained, not generated from this model, so no rebuild/rewiring was needed here. - Verified end-to-end: freshly recreated database, artefact table
populated (31 rows), publish function works directly via SQL
(31/31 updated against the system tenant's own rows). Then, on a
fresh recreate + full Barclays provisioning run, confirmed all 31
code domains were published for Barclays automatically via the
basebundle (spot-checkedbook_status: name/display_order match the source seed exactly). Full stack rebuild (ores.dq.core.lib,ores.dq.core.tests,ores.dq.service.lib, the fullores.qt.exe) clean;ores.dq.core.testsstill green (318 assertions, 104 cases). - Finally, wired
badge_mappinginto publish-from-dq too – without regenerating its junction core files. Investigated its current repository first:badge_mapping_repository::read_all()has no tenant filter at all (and there's no RLS policy onores_dq_badge_mappings_tbleither), so unlike the other three entities,badge_mappingwas never actually broken for cross-tenant reads – it already reads globally. Regenerating it anyway would have been purely for parity, andcompass codegen entity generate badge_mapping --diffshowed the junction codegen output lands in a mismatched path/namespace (projects/ores.dq/include/ores.dq/domain/...instead ofores.dq.api, and adomain::badge_mappingtype where the hand-written repository actually usesmessaging::badge_mapping) – separate, pre-existing junction-codegen drift, out of scope to fix here. Skipped regenerating; only added the publish-from-dq wiring (artefact table, preview/publish functions – keyed on the (code_domain_code, entity_code) pair rather than a single code, since that's badge_mapping's natural key – dataset/artefact-type/ bundle-member registration, and the newdq.v1.badge-mappings.publish-from-dqsubject on the shared handler), mirroring the other three's shape wherever it still applied. The 109 seed rows were extracted programmatically the same way as the others (verified count matched a plaingrep -cthis time before writing the file, having been bitten by the escaping bug in Plan item 13). - Documented (dataset populate script's header comment, not
DB-enforced –
code_domain_code=/=badge_codeare soft FKs, no constraints):code_domainsandbadge_definitionsshould be published to a tenant beforebadge_mappings, so mappings aren't left referencing domains/badges the tenant doesn't have yet. - Verified end-to-end: freshly recreated database, artefact table
populated (109 rows), publish function works directly via SQL
(109/109 updated against the system tenant's own rows). Then, on
a fresh recreate + full Barclays provisioning run, confirmed all
109 mappings were published for Barclays automatically via the
basebundle (spot-checkedparty_status/Active -> active, matches the source seed exactly). Full stack rebuild (ores.dq.core.lib,ores.dq.core.tests,ores.dq.service.lib, the fullores.qt.exe) clean;ores.dq.core.testsstill green (318 assertions, 104 cases).
All four badge-related DQ artefacts (badge_severity,
badge_definition, code_domain, badge_mapping) are now published
per-tenant via the standard DQ publish-from-dq/bundle mechanism, and
every tenant provisioned through the standard base bundle (e.g. via
barclays_system_provision.ores) gets its own copy automatically,
with no further action needed. This closes the original tenant-scoping
gap this task exists to fix, and unblocks the badge_mapping browser
UI task.
Notes
- Regenerating
badge_severitysilently re-broke cross-tenant reads for badge severities (same root cause the blockedbadge_mappingbrowser task hit forcode_domain=/=badge_definition) – expected, and the reason this task exists. Fixed by wiringbadge_severityinto the DQ publish-from-dq pipeline (Plan items 6-8) rather than reverting or leaving the gap open. badge_definition,code_domain, andbadge_mappingare now all done too (Plan items 9-18). All three badge-relatedores.codegen.entitymodels are on the standard codegen stack and tenant-scoped via publish-from-dq;badge_mapping(a junction) is wired into the same publish-from-dq mechanism without being regenerated (its junction codegen output is itself drifted – wrong path/namespace – a separate pre-existing issue, not fixed here). This closes the task's core scope.- The orphaned, unbuilt
projects/ores.qt/dqscaffold (no CMakeLists.txt) grew two more entities' worth of generated-but-dead Qt files this round (BadgeDefinitionController=/ =BadgeDefinitionDetailDialog=/=BadgeDefinitionMdiWindow=/ =ClientBadgeDefinitionModel), on top ofbadge_severity's from the prior round. Same pre-existing drift noted forcode_domain; still out of scope to fix here, but worth flagging that it's accumulating across every entity this task regenerates. - The
coding_schemesprecedent this was modeled on is itself only ever published once, unconditionally, to the system tenant at DB build time (per a comment indq_dataset_bundle_member_populate.sql) – it is deliberately not a bundle member and has never actually been exercised as a genuine per-tenant publish target the waybadge_severitiesnow is. The publish-from-dq mechanism itself proved sound in this session's manual Barclays test, butbadge_severitiesis arguably the first real per-tenant use of the self-referential DQ->DQ publish shape – worth keeping in mind if something looks under-exercised later.
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 |
|---|---|---|
| Verify tenant-scoped badge catalogue (severities, definitions, code domains, mappings) | PENDING |
PRs
| PR | Title |
|---|---|
| #1608 | [dq] Migrate badge datasets to standard codegen + publish-from-dq |
Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
Result
Delivered, at the scope described in Acceptance: all four badge
datasets (badge_severity, badge_definition, code_domain,
badge_mapping) regenerated/investigated and wired into the
publish-from-dq pipeline; verified end-to-end via a full Barclays
provisioning run and a manual QA scenario (all steps PASSED).
Closed deliberately half-scoped relative to the original Goal: the
hand-written combined badge_handler.hpp=/=badge_service.cpp=/
=badge_protocol.hpp (server) and the hand-written Qt controllers/
dialogs (ores.qt.admin's Badge Definition/Severity,
ores.qt.refdata's Code Domain) are still live and unchanged – the
already-generated handler/service/protocol/registrar files for these
three entities sit unwired, dead code. This was a deliberate stop
here rather than letting the task sprawl further, per discussion:
retiring hand-written code and wiring in generated code for the
server + Qt layers is real, substantial follow-on work (touches the
main NATS registrar, a brand-new ores.qt.dq library that needs
scaffolding, and the app's menu structure), and is better served by
its own dedicated story than absorbed into this one.
Two real bugs were found via manual QA as a direct consequence of this gap (Code Domain saves don't prompt for a change reason, and don't publish an entity-changed event) – both are standard behaviour every fully-generated dialog/handler already has "for free", and both are captured as acceptance items on the new follow-on story rather than patched in place on the hand-written code.
Follow-on work split into two new sibling stories (scaffolded and PRs raised in this same session):
- Commission ores.qt.dq — full-stack codegen for the DQ component –
retires the hand-written server/Qt code for these three entities
(plus
data_domain=/=subject_area=/=catalog=/=change_reason=/ =change_reason_category, which have no codegen model at all today), stands upores.qt.dqas a real built library, one task per DQ entity. - Clean up application menus – redesigns the menu structure this
new
Data &Qualitymenu (and the wider System/Operations/Reference Data cleanup) needs, one task per top-level menu.
Also filed during this task, unrelated to badges directly but discovered along the way: