Status: Accepted, stated in the project's written engineering rules. This record covers the rule that the screens of Cluster ERP are described by module metadata on the server and drawn by generic client code. It applies fully to the web app in webapp/ and to the record screens of the desktop client. The metadata classes are documented on metadata model.
Context¶
Cluster ERP has 28 modules, each with many entities. The project's written engineering rules state that the web app is fully metadata-driven: screen, form, list, and action behaviour comes from metadata, and rules embedded only in the interface are forbidden. The metadata test records what happened before the rule was enforced: rules "used to hide in the web client" and were moved to metadata, i18n, or data (tests/test_metadata_driven_rules.py:1-5). Further rationale is not determined from code.
Decision¶
- Every module returns a
ModuleMetafromget_metadata(src/cluster_erp/core/module_base.py:73-74).src/cluster_erp/core/metadata.pydefines 45 classes for entities, fields, columns, actions, tabs, facets, and the sidebar (counted withgrep -c "^class "). - The server publishes the metadata of all loaded modules at
/api/v1/_meta(src/cluster_erp/server/app.py:1519-1546). - Generic client code renders lists, forms, and record cards from it. Generic code does not compare against business module names and does not hold module paths or permissions.
- A module-specific screen that metadata cannot express is a custom widget in that module's own gated bundle, declared in metadata (
tests/test_metadata_driven_rules.py:158-180).
The flowchart shows the path from a module to a screen.
flowchart LR mod["Module.get_metadata"] --> eff["effective_module_meta"] eff --> meta["GET /api/v1/_meta"] meta --> web["webapp generic engines"] meta --> desk["desktop MetadataStore"] desk --> gen["widgets/generic views"] meta --> bundle["gated_apps bundle custom widget"]
In prose: the server merges each module's metadata with tenant-defined fields in effective_module_meta and serves the result once per process with an ETag (src/cluster_erp/server/app.py:1531-1544). The web engines gen-list.js, gen-form.js, and gen-detail.js render from it. The desktop client loads the same document into its metadata store (src/cluster_erp/desktop/core/metadata_store.py:21) and builds views in src/cluster_erp/desktop/widgets/generic/. Custom widgets live in gated_apps/<module>/app.js (tests/test_metadata_driven_rules.py:175-180).
Consequences¶
- A new entity screen needs metadata only; see add a screen with metadata.
- Platform features are found by capability, not by module name. The record-documents provider is declared with
provides(src/cluster_erp/core/metadata.py:731-735,tests/test_metadata_driven_rules.py:63-74). - Home widgets are declared by modules with
home_widgetsand rendered by the module's bundle (src/cluster_erp/core/metadata.py:739-740). - Party roles appear on the party card through
FacetMeta; see party unification. - Permission codes are seeded from the entities in metadata at start-up (
src/cluster_erp/server/app.py:642-652).
Where the code enforces it¶
| Mechanism | What it checks | Source |
|---|---|---|
test_generic_web_engines_do_not_branch_on_business_module_names |
Five generic engines compare against no business module name | tests/test_architecture_boundaries.py:75-87 |
test_generic_web_code_names_no_module_paths_permissions_or_language_columns |
Eleven generic files name no module path, module permission, language column, or language branch | tests/test_metadata_driven_rules.py:85-112 |
test_every_is_active_column_declares_the_active_flag_formatter |
List behaviour comes from the column's declared formatter | tests/test_metadata_driven_rules.py:36-48 |
test_home_widgets_are_module_metadata_rendered_by_module_bundles |
Home widgets are module metadata; home.js names none |
tests/test_metadata_driven_rules.py:158-193 |
test_working_calendar_is_the_calendars_modules_custom_tab |
The working calendar is a custom tab, not generic code | tests/test_metadata_driven_rules.py:196-214 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.