Component Clean Standard

Table of Contents

1. Summary

A component is clean when every type it puts on the wire comes from an org model, every model is on the current format and bound to a variability profile, regeneration reproduces the checked-in tree byte for byte, no hand-written duplicate or legacy fragment remains, and every codegen gate passes with the component under test. ores.iam set this standard in sprint 25 and ores.refdata followed it; this page writes down what they did, item by item, so that the other components can follow it and so that the work can be automated. Each item names the check that proves it, where one exists. A component reaches the standard when every item that applies to it passes, and it then joins COMPONENTS_UNDER_TEST in projects/ores.codegen/scripts/component_registry.py.

2. Detail

2.1. How to read the checklist

Each item has an identifier, the kind of component it applies to, and the evidence that proves it. The kinds are:

  • All: every C++ component.
  • Protocol: a component that serves or sends NATS messages.
  • Entity: a component with entity, junction or operation models, and so with SQL, a repository and a service.

An item that does not apply to a component is recorded as "not applicable" with the reason, never skipped in silence. An item that cannot pass is recorded as an accepted exception: the component joins the registry, every gate runs against it, and the item it does not pass is named in ACCEPTED_EXCEPTIONS in projects/ores.codegen/scripts/component_registry.py with its reason and the person who accepted it.

Keeping the component out until every item passes is the alternative, and it withholds every gate from the component in the meantime. A component with one unverifiable item is then checked by nothing at all, which is worse than the gap it was excluded for. So the registry records the gap instead, and check_registry_exceptions.py refuses an exception that names an item the standard does not define, states no reason, names nobody or carries no date, against the standard's own item list. What it cannot do is decide whether a reason is good enough; that is the reviewer's, and the record is written so the reviewer can see it.

Take an exception only when the item cannot be made to pass where the work is done – a check that needs a live fleet, a tool that cannot draw what the item asks for. An item that is merely unfinished is not an exception; it is unfinished work, and the component waits.

Work in the order of the sections. Fix every defect at its source: a template, org_loader.py, a profile or a model. Never edit generated output, and never carry a shim for backwards compatibility: the database is recreated from scratch, so there is no migration to protect.

Work one entity at a time. Regenerate, build and test after each one. Sprint 25 found five generator defects that a diff, the drift check and a typecheck all missed; only a build and a test run found them.

2.2. 1. Baseline and inventory

Id Applies to Item Evidence
B01 All Record the baseline before any edit: regenerate with nothing changed and record every pre-existing difference. A later difference is attributable only against this baseline. check_component_drift.py --component <c> --dry-run and check_model_drift.py output, kept on the task
B02 All Classify every model file by metatype: entity, junction, operation, message, field group, module, component. Record and exclude the metatypes that carry no variability. Survey table on the task
B03 All Classify every hand-written C++ file (no generated marker): generatable (domain, repository, mapper, service, handler, registrar, protocol, generator, table and JSON I/O), infrastructure that stays hand-written, or dead. Survey table on the task
B04 Protocol List every NATS subject the component serves or sends, every protocol header, and every raw subject string outside a protocol header. Survey table on the task
B05 All Read the component's open captures and in-flight branches, so that the work does not collide with them. Links on the task
B06 All Classify every public symbol by its consumer: production, test-only, or none. A test-only consumer is dead code, because a test is not a consumer. Name the intended consumer on the task, or delete the symbol with its test. git grep over projects/ for each public symbol, excluding the component and its own tests

2.3. 2. Models

Id Applies to Item Evidence
M01 Entity Every model is on the current org format. No legacy JSON model remains, and every lookup_entity is migrated to domain_entity. Survey; check_model_drift.py
M02 Entity Every entity is bound to a variability profile, or recorded as a no-match with its actual feature values and the reason. A forced bind to the nearest profile is not allowed. Survey table; the compass-codegen-fix-drift method
M03 Entity No model states a property its profile already fixes to the same value. Genuine overrides stay. check_model_drift.py
M04 Entity Features sit in the namespace the loader reads (for example has_parent_id at the entity root). A misplaced assignment is inert without any error. Regeneration with the bind shows the expected change
M05 Entity An entity describes its table. A joined or denormalised shape is a message, not an entity. A table with no history uses the current-state entity shape. Model review
M06 Protocol Every wire type the component exchanges is modelled: entities, junctions, operations, and the extra messages an entity declares in its * Messages section. No hand-written wire type remains. check_protocol_twin_coverage.py; no hand-written protocol header
M07 Entity Junctions name their singular, give each key column its real type, and declare :read_only:, :client_read_only: and :party_id_is_target: where they apply. Junction row-level security is tenant scoped. Model review; generated junction stack
M08 Entity Every custom C++ type is bound to the header that defines it. SQL type spellings are the canonical ones. Flags are booleans, not integers. Model review; regeneration
M09 All No legacy fragment remains in any model: no Qt or Wt presentation drawer or property, no property that names a retired message type, no feature the canonical protocol retired, no dead history property. grep over the modeling directory for the retired keywords
M10 All The component model (component_overview.org) is current, declares its parts, and a composite has its group model so that codegen generates its root CMakeLists.txt. validate_docs.sh; regeneration

2.4. 3. Protocol

Id Applies to Item Evidence
P01 Protocol Every entity speaks the canonical NATS entity protocol: the canonical list (paged and ordered), get, put, delete, history, and the point-in-time read where the model offers it. Generated protocol headers; the specification
P02 Protocol Every subject the component owns is declared once, in its models. Every raw subject string in code references a generated constant. Record an exception only where the layering forbids the reference. grep for subject literals
P03 Protocol No duplicate subject, no unregistered derived message, and no declared message that nothing serves. Remove what nothing serves. Registrar review; subject inventory
P04 Protocol Operations that are not entity verbs (workflows, orchestrations) are operation models, and their messages generate from them. Operation models; no hand-written protocol header
P05 Protocol Every consumer in other components, the shell and the CLI follows the protocol change in the same piece of work. Nothing is carried for backwards compatibility. Whole-tree build
P06 Protocol An event cache warms through the canonical list and follows the canonical event actions. Generated cache code

2.5. 4. Generation

Id Applies to Item Evidence
G01 Entity Regeneration is byte-identical to the checked-in tree and idempotent: a second run changes nothing. This covers SQL, C++ (domain, repository, mapper, service, handler, registrar, generators, eventing tests), shell units, shell recipes and the CMake source lists. check_component_drift.py --component <c>, run twice
G02 All Every hand-written file that the generation supersedes is deleted, together with dead headers and the hand-written protocol umbrella. Survey B03 re-run: no generatable file left hand-written
G03 All Every generated file carries the generated marker, and no hand-written file carries it. grep for AUTO-GENERATED FILE against the survey
G04 Entity The generated code reads like the component's best hand-written code. A finding is a template defect and is fixed in the template. Review record on the task
G05 All Every generator defect found is fixed at its source with a regression test in the codegen suite. Codegen test suite
G06 All The CMake source lists match the tree. regenerate_cmake_component_files.py --all --check
G07 All No generated artefact is refreshed while its generator has a known defect, because a refresh can delete correct content. Fix the generator at its source with a regression test, then refresh. A diagram refresh also honours the two-pass contract: run the automated pass, redo the manual pass below the sentinel, and verify the sentinel section survived. The generator's own dry run, with its diff read line by line against the committed artefact
G08 All The namespace documentation headers are not dead headers. Every component keeps one for its outermost namespace: include/<component>/<component>.hpp for a single-module component, and include/<component>.<module>/<component>.<module>.hpp for each module of a multi-module one. Where a facet has a header of its own, at include/<component>/<facet>/<component>.<facet>.hpp or include/<component>/<facet>/<facet>.hpp, that documents the sub-namespace and is not dead either; the coverage of facet headers is uneven across the tree today and filling the gaps is separate work, not a condition of this item. Each carries the namespace @brief that doxygen renders, and the outermost one is the description the diagram conventions read. A consumer census does not apply to them: an empty namespace is their whole content, so nothing includes them and nothing should. A claim in one is a document bug, corrected in place, never a reason to delete the file. The diagram conventions; the component's own header list; a census of every component

2.6. 5. Wiring and data

Id Applies to Item Evidence
W01 Entity Every registrar family the regeneration materialises is composed at the component's composition points: the messaging registrar, the history providers and the event mappings. A family that is deliberately unwired is recorded with its reason. Registrar review on the task
W02 Entity Every permission a handler checks is seeded, and the roles that need it are granted it. check_handler_permissions.py
W03 Entity Every name a populate script uses is defined by a populate script. check_populate_references.py
W04 Entity Every SQL script (tables, triggers, notify triggers, row-level security) is reachable from the create and drop aggregators, and the service grants cover every table the service reads. SQL schema validation (no WIRE_001); compass db recreate

2.7. 6. Shell

Id Applies to Item Evidence
S01 Entity Every verb of every entity and junction has a generated shell command unit and a test. The hand-written units the generation replaces are deleted. Generated units; shell tests
S02 Entity Every command has a generated literate recipe, and the recipe inventory lists them all. A destructive command is declared destructive in its model. regenerate_shell_recipe_inventory.py

2.8. 7. Structure and hygiene

Id Applies to Item Evidence
H01 All The component passes the Component Architecture Audit, and its record is written. The diagram is authored in two passes, never one. The automated pass reads data members and little else, so a class whose content is methods arrives as an empty box, and a facet of free functions does not arrive at all. The manual pass below the sentinel fills those boxes with the API, draws the relationships the automated pass cannot see, and deletes any edge the code does not support. A diagram of empty boxes is a skeleton, not a diagram. Audit record; validate_docs.sh; the rendered image read, not merely regenerated
H02 All No legacy code remains: no Qt or Wt fragment, no dead code, no commented-out code, no hand-written duplicate of a generated artefact. Two hand-written implementations of the same behaviour collapse into one, and the survivor keeps the consumers the pair had. Review record on the task
H03 All Comments follow the project comment rules. compass-code-review-comments pass
H04 All No hand-rolled per-platform shim survives where every supported compiler provides the standard facility. Replace the shim with the standard facility, and delete what existed only to carry it. Review against the three supported compilers: MSVC, AppleClang, and GCC or Clang
H05 All The component has its documentation, and the documentation is complete rather than a skeleton. Every component, and every part of a composite, carries a component_overview.org with the six sections the Component Documentation Guide requires: Summary, Inputs, Outputs, Entry points, Dependencies, See also. Each overview carries a Diagram section, its .puml source and a rendered image committed beside it, and the Diagram section points at that image. A diagram that predates the code it draws is a finding: where the component's code changed, the diagram is refreshed in the same pass, per H01's two passes. A schema-owning component also carries its ER diagram. Placeholder prose and empty bullets do not pass. The overview files and their .puml=/image pairs; =validate_docs.sh; the rendered image read, not merely regenerated

2.9. 8. Verification

Id Applies to Item Evidence
V01 All The whole tree builds, with the component's tests and every consumer. compass build
V02 All The codegen suite passes, and the component's test suites pass with the fleet up. compass test run
V03 Entity The database recreates from scratch, and the fleet starts and serves. compass db recreate -y -k; compass services start
V04 Entity Every generated shell command runs against the live fleet and answers. Record any command that cannot run, with the reason. Recipe run log on the task
V05 All Every codegen gate is green with the component added: component drift, model drift, protocol twin coverage, handler permissions, populate references, recipe inventory and CMake source lists. Each check script
V06 All The component joins COMPONENTS_UNDER_TEST, and check_component_drift.py --all passes with it listed. component_registry.py diff
V07 All No vacuous test. Every test fails when its subject does nothing, so a test that asserts only a length, a non-throw, a truthiness, or one production run against another is a finding. Assert a literal expected value. Stub the subject and re-run. The test must fail
V08 All Every source file has a test that exercises it, or is recorded with the reason it does not. The list of source files no test touches is the coverage work item. Per-file survey under src/, against the component's test run

2.10. What sprint 25 learned

  • A check that writes is a check that damages. Run the drift check with --dry-run while a model is being drafted: an in-place --all run overwrote sixteen hand-written files. Never run a writing gate while a build is reading the tree: a whole-tree build failed with "#include nested too deeply" while the in-place drift check regenerated that component's files under it, and the same file compiled once the tree was still.
  • The drift gate cannot see an unwired registrar, and the eventing tests register their own mappings. Check W01 by reading the composition points.
  • A rename that the templates cannot carry yet is undone, not forced. Rename the hand-written half of a pair before the generated half supersedes it.
  • Only a live run finds some defects: the service grant and the notify trigger wiring were found by a running fleet, not by ctest.
  • An unattended replay of commands can destroy an environment. Declare destructive commands in their models.

2.11. What the component surveys learned

A survey of four model-free infrastructure components, ores.platform, ores.utility, ores.security and ores.geo, run on 2026-09-25 by agents that did not write them, added items B06, H04, G07, V07 and V08; the ores.security pass added G08; and a later codegen pass made the documentation requirement explicit as H05. The standard now holds 49 items. The findings behind the new items:

  • A component can pass every gate and still be unclean. All four passed validate_docs.py, carried no leftover artefact and broke no naming rule, and still held dead public API, an overview that contradicts the tree, and tests that cannot fail. The gates check structure, not truth.
  • A consumer census finds what a compile cannot. Code whose only caller is its own test compiles, links and passes forever. In ores.utility six sub-trees, about 1560 lines, exist only to satisfy their own tests.
  • A test that cannot fail is worse than no test, because it reports confidence it did not earn. The ores.geo suite passes whenever its lookup fails for any reason, and a backlog capture had already recorded the bug the suite was meant to catch.
  • A generator defect can make the obvious fix destructive. Refreshing ores.platform's diagram deletes correct content, because the parser loses brace sync on a one-line enum class. Seven components hold a one-line enum class in a header, which is what the parser reads, so the defect reaches all seven. Run the generator's dry run and read its diff before accepting a refresh.
  • An automated diagram pass is a skeleton, so the manual pass is not optional polish. The pass reads data members only. An interface, and any class whose content is methods, arrives as an empty box; a class with state arrives with its fields and no API; and a facet of free functions does not arrive at all. The first ores.platform diagram passed a refresh check, a render and a file review, and still showed an environment_provider with no methods and a class relationship the code does not have. Read the rendered image before calling a diagram done.
  • The same defect recurs across components. 147 component_files.cmake files are stale for one reason: the marker template landed in commit 3a40c9c57e and no component regenerated. Where a defect has one cause, fix it once and record the sweep, rather than 147 times.
  • A registry that admits only model-bearing components excludes the whole infrastructure tier. COMPONENTS_UNDER_TEST lists model-bearing catalogue entries, so a component with no models can never join it, and deleting one leaves the same gap. Six components now share it, the two retired by deletion and the four surveyed.
  • Prefer the standard facility. ORES_NO_UNIQUE_ADDRESS and a hand-rolled unreachable() existed beside [[no_unique_address]] and std::unreachable(), which MSVC, AppleClang and GCC all provide. A shim that outlives its need is dead weight with a portability excuse.
  • A duplicated implementation is one finding, not two. Two near-identical functions that differ only in the shape they return are one behaviour, so the fix is one helper behind both public functions, and the survivor keeps the consumers the pair had. In ores.platform the two MAC enumerators looked test-only from outside, and each was in fact load-bearing through a different production caller, so deleting either half would have broken the telemetry trace identifier.
  • A rule that names no exemption deletes what it should keep. G02 said dead headers go, and two passes read that as licence to delete twelve namespace documentation headers: ores.utility.hpp with its ten facet headers, and ores.security.hpp. Their content is an empty namespace and the @brief doxygen renders, and the outermost one is the description the diagram conventions read, so a consumer census reports them as dead when they are the documentation. G08 states the exemption, and the maintainer caught the second deletion.
  • Verify with an agent that did not write the code, and expect it to fail the record rather than the patch. The deletion this standard's first story performed was sound; what the verifier failed was the record's own claims about it.

2.12. What the ores.scheduler pass learned

ores.scheduler is the first component of kind Entity to run the standard, and the first whose model defines one entity while the rest of the component stays hand-written. What it added:

  • A source that needs a clock, a database and a message bus at once is not untestable; it is welded. Extract the decision into a unit that takes the clock as an argument, test it against fixed instants, and record the I/O that remains. The scheduler loop's two rules – is this job due, and when is the next minute boundary – moved into schedule_decision.cpp, which holds no timer and opens no connection, and the loop was left with one recorded reason instead of being the coverage gap.
  • A class no test can link is a finding, not an internal detail. The one hand-written repository class without the core export macro was the one class with no test, and its generated siblings all carry that macro. Where a hand-written twin of a generated artefact differs from it, the difference is the defect.
  • A cited figure is evidence, so a stale one is a defect. An evidence file kept the core suite at the count it held before the hand-written cases were added, while the record row beside it carried the current count. Re-measure every number at the head the record names, and re-measure the set together.
  • A private constructor is an invariant, not an obstacle. The cron expression admits no unvalidated instance, so its test enters through from_string and asserts the parse, rather than reaching past the guard or befriending it.
  • Model prose is the generated documentation. An instruction to the generator written into a model arrives in the generated header as the entity description, so a model states facts and a task states instructions.
  • A unique index on a nullable tenant does not make NULL one tenant. PostgreSQL treats NULLs as distinct in a unique index unless the index says nulls not distinct, so a tenant-scoped natural key still admits duplicate system-scope rows. The ores.scheduler pass shipped the index, its own verification passed it, and only a live duplicate insert – two NULL-tenant rows with the same name, accepted – and then the reviewer found it. Where a scope value can be NULL, say what NULL means to every constraint that reads it, and prove it against the database rather than the generated text.

3. See also

Emacs 29.3 (Org mode 9.6.15)