Domain entity evaluation checklist
Table of Contents
Summary
A fully commissioned domain entity spans twelve layers: DB, codegen, domain, repository, service, messaging (JSON IO), Qt UI, shell REPL, CLI, HTTP routes, Wt web UI, and manual. Each layer has a fixed set of criteria that determines whether the entity is complete.
This checklist is not specific to ores.refdata. The criteria describe the standard pattern
for any ORE Studio domain entity; the examples use ores.refdata/currency as the reference
specimen because it is the most complete entity and was the first to be evaluated.
Two meta-types exist with different expected coverage:
- Domain entity — a primary entity with its own full CRUD stack, Qt MDI window, shell
and CLI commands, HTTP routes, and a manual chapter. Example:
currency. - Auxiliary type — a small lookup table referenced by one or more domain entities as a
soft-FK. Has the same DB, codegen, and service layers but a narrower UI surface: typically
a dropdown in the parent entity's detail dialog rather than its own MDI workflow.
Example:
rounding_type,monetary_nature,currency_market_tier.
Each checklist table has columns: Item | Description | Domain | Aux | What to look for.
Values in Domain/Aux: Always / Usually / If applicable / N/A.
Use alongside the Entity Coverage Matrix when commissioning or auditing any entity.
Detail
DB layer
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Table exists | DDL file exists for the entity | Always | Always | projects/ores.sql/create/refdata/refdata_<entity>s_create.sql |
tenant_id column |
Multi-tenancy isolation | Usually | Always | tenant_id uuid not null — omit only for truly global domain entities (currencies, countries) |
version column |
Optimistic locking | Always | Always | version integer not null |
modified_by column |
Audit: who edited | Always | Always | modified_by text not null |
performed_by column |
Audit: which service performed it | Always | Always | performed_by text not null; set by trigger to ores_iam_current_service_fn() |
change_reason_code column |
Change tracking | Always | Always | change_reason_code text not null |
change_commentary column |
Change tracking | Always | Always | change_commentary text not null |
valid_from / valid_to |
Temporal history | Always | Always | valid_from timestamp with time zone not null, valid_to timestamp with time zone not null |
| CHECK: temporal order | Prevents invalid ranges | Always | Always | check ("valid_from" < "valid_to") |
| CHECK: natural key not empty | Data integrity | Always | Always | check ("iso_code" <> '') or equivalent |
| GIST exclusion constraint | Prevents temporal overlaps | Always | Always | exclude using gist (tenant_id WITH =, <key> WITH =, tstzrange(valid_from, valid_to) WITH &&) |
| Primary key | Uniqueness | Always | Always | (tenant_id, <natural_key>, valid_from, valid_to) |
| Version unique index | Fast lookup of current version | Always | Always | UNIQUE (tenant_id, <key>, version) WHERE valid_to = ores_utility_infinity_timestamp_fn() |
| Natural key unique index | Fast lookup of current record | Always | Always | UNIQUE (tenant_id, <key>) WHERE valid_to = ores_utility_infinity_timestamp_fn() |
| Tenant index | Tenant-scoped scans | Always | Always | INDEX (tenant_id) WHERE valid_to = ores_utility_infinity_timestamp_fn() |
display_order column |
Dropdown ordering | N/A | Always | display_order integer not null default 0 |
description column |
Human-readable meaning of code | N/A | Always | description text not null |
| Insert trigger | Version management + validation | Always | Always | create or replace function ores_refdata_<entity>s_insert_fn() |
Insert trigger: security definer + set search_path |
Prevents search-path injection attacks | Always | Always | Function header must carry security definer and set search_path = public, pg_temp. Without both, a service role with a manipulated search path can shadow ores_*_tbl references and bypass validation. See Commission: country § Analysis; rule is in PostgreSQL architecture § Insert trigger patterns. |
| Soft-delete rule | History-preserving delete | Always | Always | on delete to ... do instead update ... set valid_to = current_timestamp |
| Validation function | Allows parent entities to validate soft-FK | Always | Always | create or replace function ores_refdata_validate_<entity>_fn(p_tenant_id uuid, p_value text) |
Validation function: security definer + set search_path |
Independently secure for direct-call use | Always | Always | Validate functions must carry both attributes independently of their callers. security definer does not propagate from caller to callee. See Commission: country § Analysis. |
| Validation function: bootstrap checks active rows | Bootstrap pass-through must use valid_to filter |
Always | Always | The not exists bootstrap guard must include and valid_to = ores_utility_infinity_timestamp_fn(). Without it, a table with only soft-deleted rows skips the pass-through and fails with a misleading "Must be one of: (empty list)" error. See Commission: country § Analysis. |
| Entity-specific soft-FK validations | Trigger validates referenced lookup values | Always | N/A | new.rounding_type : ores_refdata_validate_rounding_type_fn(…)= |
party_id soft-reference |
Party-scoped entity | If applicable | N/A | Domain only; check party_id uuid not null and trigger validation |
workspace_id soft-reference |
Workspace-scoped entity | If applicable | N/A | Domain only |
coding_scheme_code column |
Optional DQ coding scheme link | If applicable | N/A | coding_scheme_code text (nullable); validated in trigger if present |
Codegen layer
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Domain entity model | Codegen model for the C++ domain struct | Always | Always | projects/ores.codegen/models/refdata/<entity>_domain_entity.json |
| Entity model | Codegen model for the ODB entity + mapper | Always | Always | projects/ores.codegen/models/refdata/<entity>_entity.json |
| Both models consistent with DB schema | Model fields match DB columns | Always | Always | Compare model field names/types against DDL columns |
| No hand-crafted divergence | Generated files match what codegen would produce | Always | Always | If a generated file has been edited manually, flag as drift risk |
Domain layer (C++)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Domain struct | Plain-data C++ struct | Always | Always | projects/ores.refdata.api/include/ores.refdata.api/domain/<entity>.hpp |
| All business columns represented | No column missing from struct | Always | Always | Compare struct fields against DDL columns (temporal managed by trigger, not struct) |
display_order field |
Present for aux types | N/A | Always | int display_order = 0; |
description field |
Present for aux types | N/A | Always | std::string description; |
recorded_at or equivalent |
Exposes insert timestamp (maps to DB valid_from) |
Always | Always | std::chrono::system_clock::time_point recorded_at; |
| Canonical C++ types | No raw pointers, no inheritance | Always | Always | std::string, std::optional<T>, boost::uuids::uuid, int, bool |
| No logic in struct | Plain data only | Always | Always | No methods beyond constructors; no business logic |
Repository layer (C++)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Entity class | ODB-annotated wrapper | Always | Always | ores.refdata.core/include/.../repository/<entity>_entity.hpp |
| Mapper class | DB row ↔ domain struct conversion | Always | Always | repository/<entity>_mapper.hpp |
| Repository class | Data access object | Always | Always | repository/<entity>_repository.hpp |
| list, count, save, delete, get | Core CRUD operations | Always | Always | All five methods present in repository header |
| get_history | Temporal history query | Always | Always | Method returning all versions for a given key |
| list_for_party | Party-scoped list | If applicable | N/A | Only for party-scoped domain entities |
| Tenant-scoped queries | Queries filtered by tenant_id | Usually | Always | WHERE clause includes tenant_id condition |
Service layer (C++)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Service class | Business logic + authorization wrapper | Always | Always | ores.refdata.core/include/.../service/<entity>_service.hpp |
| list, count, save, delete, get, get_history | Full CRUD + history | Always | Always | All six methods present in service header |
| list_for_party | Party-scoped list | If applicable | N/A | Domain only |
| Authorization checks | Caller permission validated | Always | Always | Authorization call before data access |
| NATS event firing on mutation | Events published on save and delete | Always | Always | NATS publish calls in save and delete implementations |
Messaging / JSON IO layer (C++)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| JSON struct | rfl-based serialisation struct | Always | Always | ores.refdata.api/include/.../domain/<entity>_json.hpp |
| IO handler | domain ↔ JSON conversion | Always | Always | domain/<entity>_json_io.hpp |
| Protocol header | NATS message type definitions | Always | Always | messaging/<entity>_protocol.hpp |
| Mutation message types | Save/delete request + saved/deleted event | Always | Always | save_<entity>, delete_<entity>, <entity>_saved, <entity>_deleted in protocol header |
| Query message types | List, count, get, history | Always | Always | list_<entity>s, count_<entity>s, get_<entity>, get_<entity>_history in protocol header |
Qt layer
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| MDI list window | Top-level window in MDI area | Always | If applicable | ores.qt.refdata/include/ores.qt/<Entity>MdiWindow.hpp |
| Detail dialog | Single-record edit/view | Always | If applicable | <Entity>DetailDialog.hpp |
| History dialog | Temporal history for a record | Always | If applicable | <Entity>HistoryDialog.hpp |
| Controller | Wires windows to service | Always | If applicable | <Entity>Controller.hpp |
| Client model | Qt model/view data model | Always | If applicable | Client<Entity>Model.hpp |
| Dropdown populated in parent dialog | Aux type appears as a combo box choice | N/A | Always | Check parent entity's DetailDialog.cpp for a combo box loading this type |
| Import dialog | Bulk import support | If applicable | N/A | Import<Entity>Dialog.hpp — not all entities have this |
| List window loads post-NATS | Manual verification | Always | If applicable | Open app, navigate to entity MDI window, records appear |
| Detail dialog edit + save round-trip | Manual verification | Always | If applicable | Edit a field, save, reopen — change persisted |
| History dialog shows correct history | Manual verification | Always | If applicable | After edit, history dialog shows two versions |
| Delete preserves history | Manual verification | Always | If applicable | Delete record; history dialog still shows prior versions |
| NATS eventing cross-session | Manual verification | Always | If applicable | Mutation in client A appears in client B without refresh |
Shell layer (ores.shell interactive REPL)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Submenu registered | Entity has a dedicated submenu in the REPL | Always | If applicable | projects/ores.shell/src/app/commands/<entity>s_commands.cpp registered in menu |
list command |
Lists records from service | Always | If applicable | list case in the commands file |
add / save command |
Creates or updates a record | Always | If applicable | add or save case |
remove / delete command |
Deletes a record | Always | If applicable | remove or delete case |
history command |
Shows temporal history | Always | If applicable | history case |
| Post-NATS service wiring | Commands call service via NATS, not directly | Always | If applicable | Service calls use NATS client, not direct repository |
CLI layer (ores.cli non-interactive)
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Options config struct | CLI argument struct for add/save | Always | If applicable | projects/ores.cli/include/ores.cli/config/add_<entity>_options.hpp |
list subcommand |
Non-interactive list | Always | If applicable | Subcommand registered and implemented |
add / save subcommand |
Non-interactive create/update | Always | If applicable | Subcommand registered and implemented |
remove / delete subcommand |
Non-interactive delete | Always | If applicable | Subcommand registered and implemented |
| Post-NATS service wiring | Commands call service via NATS | Always | If applicable | Same as shell |
HTTP layer
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| GET list route | REST list endpoint | Always | If applicable | GET /refdata/<entity>s in routes file |
| POST save route | REST create/update endpoint | Always | If applicable | POST /refdata/<entity>s |
| DELETE batch route | REST batch delete endpoint | Always | If applicable | DELETE /refdata/<entity>s |
| GET history route | REST history endpoint | Always | If applicable | GET /refdata/<entity>s/:id/history |
| Routes registered | Routes wired into the HTTP server | Always | If applicable | Entry in projects/ores.http.core/src/routes/ |
Wt web UI layer
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| List widget | Wt list component | Always | If applicable | projects/ores.wt.service/include/.../app/<entity>_list_widget.hpp |
| Detail dialog | Wt edit component | Always | If applicable | <entity>_dialog.hpp |
| Wired into Wt app | Widget registered in application | Always | If applicable | Reference in Wt app class |
| List + edit round-trip | Manual verification | Always | If applicable | Open Wt app, list entity, edit, save — change persisted |
Manual
| Item | Description | Domain | Aux | What to look for |
|---|---|---|---|---|
| Entity chapter | Dedicated chapter in user guide | Always | If applicable | doc/manual/user_guide/<entity>.org or equivalent |
| What and why | Entity described for a user, not a developer | Always | If applicable | First section: what this entity is, when to use it |
| Qt MDI window documented | List window walkthrough | Always | If applicable | Screenshot or description of columns and actions |
| Qt detail dialog documented | Field-by-field documentation | Always | If applicable | Table of fields with type, validation, and meaning |
| Qt history dialog documented | History usage explained | Always | If applicable | How to view and interpret change history |
| Shell commands documented | All shell commands with examples | Always | If applicable | #+begin_example blocks showing command + output |
| CLI commands documented | All CLI subcommands with flags | Always | If applicable | #+begin_example blocks showing invocation |
| Aux type documented in parent chapter | Valid values listed and explained | N/A | Always | Subsection in parent entity chapter: table of codes + descriptions |
| Indexed from manual root | Chapter reachable from table of contents | Always | If applicable | Entry in doc/manual/user_guide/index.org or equivalent |
| Site builds cleanly | No broken links or missing files | Always | Always | make site passes without error |
See also
- Entity Coverage Matrix — canonical entity inventory (Y/N per layer per entity)
- Commission: currency — reference domain entity used to derive these criteria
- Commission: country — extended the DB-layer criteria:
security defineron insert and validate functions; active-rows bootstrap guard; full architectural rationale in the story's* Analysissection - PostgreSQL: Database Architecture and Conventions — canonical rules for insert trigger patterns, validate function security, and bootstrap pass-through
- Entity evaluation skill — LLM skill for running this checklist against any entity