@dforge-core/dforge-mcp 0.1.0-rc.9 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +88 -29
  3. package/dist/server.js +2735 -351
  4. package/docs/creating-modules.md +15 -7
  5. package/package.json +11 -6
  6. package/resources/docs/conventions.md +22 -16
  7. package/resources/docs/dsl-reference.md +767 -0
  8. package/resources/schemas/entity.schema.json +8 -1
  9. package/resources/schemas/print-templates.schema.json +82 -0
  10. package/resources/schemas/reports.schema.json +4 -0
  11. package/resources/schemas/triggers.schema.json +59 -0
  12. package/skills/dforge-mcp-author/SKILL.md +284 -73
  13. package/skills/dforge-mcp-author/examples/matrix-budget/README.md +43 -0
  14. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_category.json +24 -0
  15. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_line.json +56 -0
  16. package/skills/dforge-mcp-author/examples/matrix-budget/manifest.json +30 -0
  17. package/skills/dforge-mcp-author/examples/matrix-budget/security/roles.json +9 -0
  18. package/skills/dforge-mcp-author/examples/matrix-budget/seed-data/01-categories.json +8 -0
  19. package/skills/dforge-mcp-author/examples/matrix-budget/ui/data_views.json +42 -0
  20. package/skills/dforge-mcp-author/examples/simple-todo/README.md +38 -0
  21. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +83 -0
  22. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_list.json +43 -0
  23. package/skills/dforge-mcp-author/examples/simple-todo/logic/actions/mark_done.dsl +6 -0
  24. package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +32 -0
  25. package/skills/dforge-mcp-author/examples/simple-todo/security/roles.json +10 -0
  26. package/skills/dforge-mcp-author/examples/simple-todo/seed-data/01-lists.json +17 -0
  27. package/skills/dforge-mcp-author/examples/simple-todo/ui/actions.json +11 -0
  28. package/skills/dforge-mcp-author/examples/simple-todo/ui/data_views.json +35 -0
  29. package/skills/dforge-mcp-author/examples/simple-todo/ui/menus.json +28 -0
  30. package/skills/dforge-mcp-author/references/action-dsl.md +397 -0
  31. package/skills/dforge-mcp-author/references/column-types.md +168 -0
  32. package/skills/dforge-mcp-author/references/conventions.md +177 -0
  33. package/skills/dforge-mcp-author/references/data-migration.md +270 -0
  34. package/skills/dforge-mcp-author/references/data-views.md +245 -0
  35. package/skills/dforge-mcp-author/references/excel-import.md +61 -0
  36. package/skills/dforge-mcp-author/references/field-types.md +144 -0
  37. package/skills/dforge-mcp-author/references/filters.md +326 -0
  38. package/skills/dforge-mcp-author/references/flags.md +73 -0
  39. package/skills/dforge-mcp-author/references/formulas.md +206 -0
  40. package/skills/dforge-mcp-author/references/jobs.md +149 -0
  41. package/skills/dforge-mcp-author/references/manifest.md +123 -0
  42. package/skills/dforge-mcp-author/references/menus.md +164 -0
  43. package/skills/dforge-mcp-author/references/number-sequences.md +117 -0
  44. package/skills/dforge-mcp-author/references/print-templates.md +159 -0
  45. package/skills/dforge-mcp-author/references/queries.md +312 -0
  46. package/skills/dforge-mcp-author/references/reports.md +398 -0
  47. package/skills/dforge-mcp-author/references/schema-import.md +331 -0
  48. package/skills/dforge-mcp-author/references/security.md +244 -0
  49. package/skills/dforge-mcp-author/references/settings.md +120 -0
  50. package/skills/dforge-mcp-author/references/traits.md +153 -0
  51. package/skills/dforge-mcp-author/references/translations.md +158 -0
  52. package/skills/dforge-mcp-author/references/validation-checklist.md +184 -0
  53. package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
@@ -0,0 +1,120 @@
1
+ # Module Settings Reference
2
+
3
+ Module settings are **configurable values** scoped to a module. Tenant admins set them; module code reads them at runtime. Values inherit through the folder tree (child folder overrides parent, parent overrides module default).
4
+
5
+ Lives in: `settings.json` at the module root.
6
+
7
+ ## Structure
8
+
9
+ Settings use the **same field type system as entity columns**. Each setting is like a single column with a default value.
10
+
11
+ ```json
12
+ {
13
+ "company_name": {
14
+ "fieldTypeCd": "text",
15
+ "baseDatatypeCd": "string",
16
+ "description": "Company name displayed on documents",
17
+ "defaultValue": "Your Company Name"
18
+ },
19
+ "invoice_prefix": {
20
+ "fieldTypeCd": "text",
21
+ "baseDatatypeCd": "string",
22
+ "description": "Prefix for auto-generated invoice numbers",
23
+ "defaultValue": "INV-"
24
+ },
25
+ "default_currency": {
26
+ "fieldTypeCd": "dropdown",
27
+ "baseDatatypeCd": "string",
28
+ "description": "Default currency code for documents",
29
+ "defaultValue": "USD",
30
+ "params": {
31
+ "options": ["USD", "EUR", "GBP", "CHF"]
32
+ }
33
+ },
34
+ "allow_negative_stock": {
35
+ "fieldTypeCd": "checkbox",
36
+ "baseDatatypeCd": "bool",
37
+ "description": "Allow operations that result in negative stock",
38
+ "defaultValue": false
39
+ },
40
+ "company_logo": {
41
+ "fieldTypeCd": "image",
42
+ "description": "Shown on printed documents"
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Reading settings
48
+
49
+ From formulas, reference settings with `$[setting_name]`:
50
+
51
+ ```
52
+ [total] * (1 + $[vat_rate] / 100)
53
+ ```
54
+
55
+ From DSL actions (check your dForge version for exact syntax):
56
+
57
+ ```
58
+ execute:
59
+ rate = settings.vat_rate
60
+ tax = [total] * rate / 100
61
+ ```
62
+
63
+ From number sequence patterns:
64
+
65
+ ```
66
+ "pattern": "$[invoice_prefix]-{yyyy}-{seq:4}"
67
+ ```
68
+
69
+ ## Folder-scoped inheritance
70
+
71
+ Settings resolve at runtime through the folder hierarchy:
72
+
73
+ 1. The folder the user is currently in → its override (if any)
74
+ 2. Parent folder → override (if any)
75
+ 3. ... up the tree ...
76
+ 4. Module default (from `settings.json`)
77
+
78
+ This means different folders can have different VAT rates, different prefixes, different currencies — without any code changes.
79
+
80
+ ## Field types supported in settings
81
+
82
+ Any field type that makes sense for a single scalar value:
83
+
84
+ - `text`, `textarea`, `richtext`, `code`
85
+ - `number`, `currency`, `percent`
86
+ - `checkbox`
87
+ - `date`, `datetime`, `time`
88
+ - `dropdown`, `flags`, `color`, `tags`
89
+ - `image`, `file`
90
+ - `json`
91
+ - `user` (if you want to store a reference to a specific user)
92
+
93
+ **Not supported** as settings:
94
+
95
+ - `lookup` (reference to an entity row) — use a `text` setting holding a code instead
96
+ - `grid` (set) — settings are single values
97
+
98
+ ## Params
99
+
100
+ Settings support the same `params` as entity columns:
101
+
102
+ - `options` (for `dropdown` and `flags`)
103
+ - `min`, `max` (for `number`)
104
+ - `currency` (for `currency`)
105
+ - `maxLen` (for `text`)
106
+
107
+ ## Declaring setting access in roles
108
+
109
+ By default, only the module/tenant admin can change settings. If you want specific roles to read or modify settings, grant appropriate rights on the settings objects in `security/roles.json` (check your dForge version for the exact naming).
110
+
111
+ ## Common mistakes
112
+
113
+ - Forgetting to declare `defaultValue` (the key is `defaultValue`, not `default`) — missing setting values cause runtime errors when formulas reference them.
114
+ - Using `lookup` as a setting field type — unsupported.
115
+ - Hardcoding values that should be settings — makes the module non-configurable. Any string/number that might vary per deployment is a good candidate for a setting.
116
+ - Reading a setting that doesn't exist — returns null, which causes formula errors. Always provide a default.
117
+
118
+ ## Reference
119
+
120
+ This file covers the module package format for settings. If you need details on the runtime resolution API (`settings.get`, `settings.list`, `settings.set`, `settings.clear`), ask the user to check their dForge version's settings documentation.
@@ -0,0 +1,153 @@
1
+ # Entity Traits Reference
2
+
3
+ Traits are **reusable column bundles** that entities inherit. They eliminate boilerplate — instead of declaring PK columns, timestamps, and audit tracking on every entity, you declare `traits: ["identity", "audit"]` and dForge mixes them in automatically.
4
+
5
+ Declared on an entity:
6
+
7
+ ```json
8
+ {
9
+ "description": "Contacts",
10
+ "dbObject": "contact",
11
+ "toString": "{first_name} {last_name}",
12
+ "traits": ["identity", "audit"],
13
+ "fields": {
14
+ "first_name": { /* ... */ }
15
+ // No need to declare contact_id, created_date, last_updated, etc.
16
+ }
17
+ }
18
+ ```
19
+
20
+ Source of truth: `server/database/system-modules/metadata/traits.json`
21
+
22
+ ## All traits
23
+
24
+ ### `identity`
25
+
26
+ Adds the primary key column named `{entity}_id`.
27
+
28
+ - Column: `{dbObject}_id` (e.g. `contact_id` for `dbObject: "contact"`)
29
+ - `dbDatatype: "cuid"`, `flags: "I"`, `isPk: true`, `isIdentity: true`
30
+ - Auto-generated on insert
31
+
32
+ **FK columns pointing to an `identity`-trait entity must use `dbDatatype: "cuid"`.** Do not use `bigint`, `int`, `int8`, or `integer` — `cuid` is the platform's type name even though it is physically stored as int8. Using any other value causes an `ENTITY_COLUMN_IMPORT_ERROR` type-mismatch on install.
33
+
34
+ **Most entities should use `identity`.** Exceptions: junction tables with composite PKs, extension entities that share a PK with their parent, or entities using the `period` trait (which provides its own PK).
35
+
36
+ ### `audit`
37
+
38
+ Adds **two timestamp columns** (no user tracking):
39
+
40
+ - `created_date` (`timestamptz`, formula `NOW()`, auto-set on insert)
41
+ - `last_updated` (`timestamptz`, formula `NOW()`, auto-set on insert and update)
42
+
43
+ Both columns have `flags: "I"` (internal — not shown in UI by default, managed by the platform).
44
+
45
+ **Important**: `audit` does NOT add `created_by` / `updated_by` user columns. For user tracking, use `audit-full`.
46
+
47
+ ### `audit-full`
48
+
49
+ **Includes everything from `audit`** (timestamps) plus adds **four user-tracking columns**:
50
+
51
+ - `created_by` (cuid, FK to user)
52
+ - `created_by_user` (Reference column, lookup to user — for UI display)
53
+ - `last_updated_by` (cuid, FK to user)
54
+ - `last_updated_by_user` (Reference column, lookup to user — for UI display)
55
+
56
+ Also adds FK constraints: `FK_{Entity}_CreatedBy` and `FK_{Entity}_LastUpdatedBy`.
57
+
58
+ Use `audit-full` when you need to know **who** created/modified a record, not just **when**. All four user columns have `flags: "I"` (internal).
59
+
60
+ **You don't need to declare both `audit` and `audit-full`** — `audit-full` inherits from `audit` automatically.
61
+
62
+ > **⚠️ `audit-full` + seed data needs handling.** `created_by` and `last_updated_by` are
63
+ > **required** (NOT NULL) cuid FKs to `user`, with **no default formula**. At runtime the platform
64
+ > fills them from the current user, but at **seed** time there is no current user, so a seeded
65
+ > `audit-full` entity fails install: *"Seed data for '<entity>': required column 'created_by'
66
+ > (cuid) is not present in seed records."* (The `audit` timestamps are fine — they default to
67
+ > `NOW()`.) `audit-full` is fully supported — when an entity uses it, pick one of two fixes:
68
+ >
69
+ > 1. **Don't seed that entity (preferred).** Keep `audit-full`, ship it with no seed file. This
70
+ > matches dForge-core — none of its modules seed user-tracked columns — and suits transactional
71
+ > entities (orders, movements) that don't need demo rows.
72
+ > 2. **Seed with the System user.** Set `"created_by": 0` and `"last_updated_by": 0` on every seed
73
+ > record. `user_id = 0` ('System') is a real, always-present row in every tenant (inserted by the
74
+ > `admin` dependency), so the FK resolves. Use this when you genuinely need `audit-full` **and**
75
+ > seed data on the same entity:
76
+ > ```json
77
+ > { "entityCode": "purchase_order", "records": [
78
+ > { "purchase_order_id": 1001, "order_number": "PO-2026-0001", "supplier_id": 2001,
79
+ > "created_by": 0, "last_updated_by": 0 }
80
+ > ] }
81
+ > ```
82
+
83
+ ### `soft-delete`
84
+
85
+ Adds a soft-delete flag:
86
+
87
+ - `active` (boolean, default `true`, formula `"true"`)
88
+
89
+ When records are "deleted," they're marked `active = false` instead of being physically removed. This preserves FK integrity and allows recovery.
90
+
91
+ ### `sorting`
92
+
93
+ Adds a manual sort-order column:
94
+
95
+ - `order_num` (int4, number, `flags: "VEM"`)
96
+
97
+ Use for entities where display order matters (menu items, checklist items, FAQ entries).
98
+
99
+ ### `postable`
100
+
101
+ **Marker trait — adds no columns.** Indicates the entity supports document posting (e.g. invoices that transition from draft to posted state). The actual posting state is managed by `A`-type or `L`-type columns (accumulation/ledger column types).
102
+
103
+ Use in combination with `accumulation` or `ledger` traits for accounting/registry entities.
104
+
105
+ ### `accumulation`
106
+
107
+ **Marker trait — adds no columns.** Indicates the entity is an accumulation register. Configuration lives in `A`-type column params (balance entity, dimensions, resources).
108
+
109
+ Advanced feature for accounting/registry modules.
110
+
111
+ ### `ledger`
112
+
113
+ **Marker trait — adds no columns.** Indicates the entity uses double-entry bookkeeping. Configuration lives in `L`-type column params (lines, movement entity, balance entity).
114
+
115
+ Advanced feature for accounting modules.
116
+
117
+ ### `period`
118
+
119
+ Adds a complete period-entity template with its own PK:
120
+
121
+ - `period_key` (varchar 20, `isPk: true`, `flags: "VEM"`) — the period identifier (e.g. "2026-Q1")
122
+ - `description` (varchar 100, `flags: "VEM"`)
123
+ - `closed` (boolean, default `false`, `flags: "VEM"`) — whether the period is closed
124
+ - `start_date` (date, `flags: "VEM"`)
125
+ - `end_date` (date, `flags: "VEM"`)
126
+
127
+ **Do not combine with `identity`** — the `period` trait provides its own PK (`period_key`).
128
+
129
+ ## Which traits to use
130
+
131
+ | Entity type | Recommended traits |
132
+ |---|---|
133
+ | Most business entities (contacts, orders, products) | `["identity", "audit"]` |
134
+ | Documents / approval workflows (`purchase_order`, `invoice`) | `["identity", "audit"]` by default — dForge-core seeds these with plain `audit` |
135
+ | Entities that need per-row user attribution (who created/changed it) | `["identity", "audit-full"]` — if also seeded, set `created_by`/`last_updated_by: 0` per record (or don't seed it) |
136
+ | Lookup/enum tables (read-only after seeding) | `["identity"]` (no audit needed) |
137
+ | Entities with manual ordering (menu items, steps) | `["identity", "audit", "sorting"]` |
138
+ | Soft-deletable entities (users, accounts) | `["identity", "audit", "soft-delete"]` |
139
+ | Financial documents (invoices) | `["identity", "audit", "postable"]` |
140
+ | Accounting periods (fiscal quarters) | `["period"]` (no `identity` — has its own PK) |
141
+ | Junction/bridge tables | `["identity", "audit"]` |
142
+
143
+ **Default to `audit`.** Reach for `audit-full` when the user explicitly needs to track *who* changed a row. If such an entity is **also** seeded, handle its required `created_by`/`last_updated_by` with one of the two options above (don't seed it, or set both to the System user `0`).
144
+
145
+ ## Rules
146
+
147
+ 1. **Always declare `traits` as a JSON array**, not a string or object.
148
+ 2. **Always include `identity` unless** the entity uses `period` (own PK) or has a manually-declared PK.
149
+ 3. **Default to `audit`; use `audit-full` only when the user explicitly needs per-row user attribution.** `audit` is lighter (just timestamps). `audit-full` adds four user columns, two of which (`created_by`, `last_updated_by`) are **required** with no default. If an `audit-full` entity is **seeded**, every seed record must set `created_by` and `last_updated_by` to the System user `0` — otherwise install fails on the missing columns. (Prefer not seeding `audit-full` entities at all; see the `audit-full` section.)
150
+ 4. **Don't redefine trait-provided columns.** If you declare `created_date` manually alongside `traits: ["audit"]`, the installer will error.
151
+ 5. **Order doesn't matter** inside the `traits` array.
152
+ 6. **`postable`, `accumulation`, `ledger` are marker traits** — they don't add columns, they flag the entity for advanced accounting behaviour. Only use them if you're building accounting/registry modules and understand the `A`/`L` column types.
153
+ 7. **Don't invent trait names.** Only use the 9 listed above. Check `server/database/system-modules/metadata/traits.json` if in doubt.
@@ -0,0 +1,158 @@
1
+ # Translations Reference
2
+
3
+ dForge modules support multi-language translations for user-facing labels — entity names, field labels, view titles, folder names, menu items, action labels and params, and report params/dataset captions.
4
+
5
+ Lives in: `translations/<locale>.json` (e.g. `translations/en-US.json`, `translations/de-DE.json`)
6
+
7
+ Locale codes use the `ll-CC` format (language-country): `en-US`, `de-DE`, `fr-FR`, `es-ES`, etc.
8
+
9
+ ## Structure
10
+
11
+ Translation files mirror the module's content structure. Each translatable object has a `label` (and optionally `desc` for description):
12
+
13
+ ```json
14
+ {
15
+ "entities": {
16
+ "contact": {
17
+ "label": "Contact",
18
+ "desc": "People at accounts",
19
+ "fields": {
20
+ "contact_id": { "label": "Contact ID" },
21
+ "first_name": { "label": "First Name" },
22
+ "last_name": { "label": "Last Name" },
23
+ "email": { "label": "Email" },
24
+ "phone": { "label": "Phone" },
25
+ "account": { "label": "Account" },
26
+ "owner_id": { "label": "Owner" },
27
+ "created_date": { "label": "Created Date" },
28
+ "last_updated": { "label": "Last Updated" },
29
+ "activities": { "label": "Activities" }
30
+ }
31
+ }
32
+ },
33
+ "folders": {
34
+ "crm": { "label": "Sales CRM" }
35
+ },
36
+ "views": {
37
+ "contacts": { "label": "Contacts" },
38
+ "contacts_list": { "label": "Contact List" },
39
+ "contacts_gallery": { "label": "Contact Cards" }
40
+ },
41
+ "menus": {
42
+ "crm_menu": {
43
+ "label": "Sales CRM",
44
+ "items": {
45
+ "customers": { "label": "Customers" },
46
+ "contacts": { "label": "Contacts" }
47
+ }
48
+ }
49
+ },
50
+ "actions": {
51
+ "create_quote_from_opp": {
52
+ "label": "Create Quote",
53
+ "desc": "Create a draft quote from this opportunity",
54
+ "params": {
55
+ "discount_pct": { "label": "Discount %" }
56
+ }
57
+ }
58
+ },
59
+ "reports": {
60
+ "sales_pipeline": {
61
+ "datasets": {
62
+ "opportunities": { "caption": "Open Opportunities" }
63
+ },
64
+ "params": {
65
+ "min_amount": { "label": "Minimum Amount" }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Top-level sections
73
+
74
+ | Section | Translates | Keys are |
75
+ |---|---|---|
76
+ | `entities` | Entity labels (`label`, `desc`) and all field labels | Entity codes → `{ label, desc, fields: { field_code: { label } } }` |
77
+ | `folders` | Folder labels | Folder codes (root folder key from `ui/folders.json`) |
78
+ | `views` | Data view labels | View codes (keys from `ui/data_views.json`) |
79
+ | `menus` | Menu node labels (root + nested items) | Menu root key → `{ label, items: { item_code: { label } } }` matching `ui/menus.json` structure |
80
+ | `actions` | Action labels, descriptions, and param labels | Action codes (keys from `ui/actions.json`) → `{ label, desc, params: { param_cd: { label } } }` |
81
+ | `reports` | Report dataset captions and param labels | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label } } }` |
82
+
83
+ ### Sections that are NOT translated (write-time only)
84
+
85
+ - `roles` — role rows have no `res_id` column. The `roles` key may appear in shipped translation files (e.g. `modules/crm/translations/de-DE.json`) but is **explicitly skipped** by the installer. Role display name comes from `security/roles.json` `description` only. (Comment in [`TranslationRegistrar.cs:248`](../../../server/src/dForge.Admin/Services/ModuleInstall/TranslationRegistrar.cs#L248): "reserved for future use.")
86
+ - `settings` — completeness *is* checked by the validator when listed in `manifest.supportedLocales` (so you'll be forced to ship `settings.<cd>.label` for each declared locale), but the registrar doesn't display those translated labels at runtime. The English `label` in `settings.json` is what users see today.
87
+ - `print_templates` — not currently consumed by the registrar. Template labels come from `ui/print_templates.json`.
88
+
89
+ If you include these sections, the install succeeds (they're silently ignored) — but you won't see localized output for them, so don't expect it.
90
+
91
+ ## German example (de-DE)
92
+
93
+ ```json
94
+ {
95
+ "entities": {
96
+ "contact": {
97
+ "label": "Kontakt",
98
+ "desc": "Personen bei Konten",
99
+ "fields": {
100
+ "contact_id": { "label": "Kontakt-ID" },
101
+ "first_name": { "label": "Vorname" },
102
+ "last_name": { "label": "Nachname" },
103
+ "email": { "label": "E-Mail" },
104
+ "phone": { "label": "Telefon" },
105
+ "account": { "label": "Konto" },
106
+ "owner_id": { "label": "Besitzer" },
107
+ "created_date": { "label": "Erstellt am" },
108
+ "last_updated": { "label": "Zuletzt aktualisiert" }
109
+ }
110
+ }
111
+ },
112
+ "folders": {
113
+ "crm": { "label": "Vertrieb CRM" }
114
+ },
115
+ "views": {
116
+ "contacts": { "label": "Kontakte" }
117
+ },
118
+ "menus": {
119
+ "crm_menu": {
120
+ "label": "Vertrieb CRM",
121
+ "items": {
122
+ "customers": { "label": "Kunden" },
123
+ "contacts": { "label": "Kontakte" }
124
+ }
125
+ }
126
+ },
127
+ "actions": {
128
+ "create_quote_from_opp": { "label": "Angebot erstellen" }
129
+ }
130
+ }
131
+ ```
132
+
133
+ ## Manifest declaration
134
+
135
+ Non-English locales are declared in `supportedLocales` (an array of `ll-CC` tags) — **not** a `translations` object. The manifest schema has no `translations` key and rejects it (`additionalProperties: false`). Translation files are auto-discovered at `translations/<locale>.json`. Ship `translations/en-US.json` as the English base, but do **not** list `en`/`en-US` in `supportedLocales` — it covers the non-English locales only (the schema rejects `en`/`en-US` entries).
136
+
137
+ ```json
138
+ "supportedLocales": ["de-DE", "fr-FR"]
139
+ ```
140
+
141
+ Every listed locale MUST have a matching `translations/<locale>.json` with a label for each translatable resource, or install fails completeness validation.
142
+
143
+ ## Rules
144
+
145
+ 1. **Always provide `en-US`** as the base language. Other languages fall back to it for missing keys.
146
+ 2. **Locale format is `ll-CC`** (e.g. `en-US`, `de-DE`, `fr-FR`) — language code + country code, hyphen-separated.
147
+ 3. **Translate everything user-visible**: entity labels, every field label (including trait-provided fields like `created_date`, `last_updated`), view names, menu items, action labels, role descriptions, setting labels, folder names.
148
+ 4. **Include virtual columns** (references, sets, formulas) in field translations — they appear in the UI just like physical columns.
149
+ 5. **Menu translations mirror the `ui/menus.json` structure** — root key → `items` → nested items.
150
+ 6. **Missing keys fall back** to the `en-US` value, then to the raw code name. So it's safe to ship partial translations — untranslated items show in English rather than breaking.
151
+ 7. **Don't translate column codes** — only the `label` values. Codes stay as-is in all languages.
152
+ 8. **File naming**: `translations/<locale>.json` (e.g. `de-DE.json`) — each non-English file must match a locale listed in `supportedLocales`.
153
+
154
+ ## When to create translations
155
+
156
+ - **Always** create `translations/en-US.json` — even for English-only modules. It ensures all labels are explicitly declared rather than derived from column codes.
157
+ - **On request** create additional languages. The LLM can generate translations from the `en-US` file — the structure is identical, only `label` and `desc` values change.
158
+ - **Include all fields** in each language file, including trait-provided fields (`created_date`, `last_updated`, `created_by`, etc.) — users see these columns and they need labels.
@@ -0,0 +1,184 @@
1
+ # Validation Checklist
2
+
3
+ Run through this checklist **before** declaring a module complete. Always start with `dforge_module_validate` when the MCP server is connected; if you are outside MCP, use the current CLI equivalent: `dforge-cli module validate <moduleDir>`.
4
+
5
+ ## Top install-blockers (scan first)
6
+
7
+ These five have each caused a real install failure. Check them before the section-by-section pass:
8
+
9
+ - [ ] **DSL dates** — `execute:` blocks use lowercase `now()`, never `TODAY()`/`NOW()` (formula-only; otherwise install fails `'TODAY' is not defined`)
10
+ - [ ] **Roll-up totals** — a sum over a child set is a Formula (`F`) column with `SUM([set].[field])`, **not** a Generated (`G`) column over a virtual `F`/`R`/`S` child (otherwise `db_error: column old.<field> does not exist`)
11
+ - [ ] **Rights keys** — actions/reports/folders use a **colon** (`action:x`, `report:x`, `folder:x`); entities bare or cross-module-dotted; deny by omitting the key, never `""`
12
+ - [ ] **Manifest** — no `translations` key (auto-discovered; non-English locales go in `supportedLocales`)
13
+ - [ ] **Column defaults** — set via `formula` / `numberSequence` / DSL, never a `defaultValue` key on an entity field
14
+ - [ ] **Seed + traits** — seeded `audit-full` entities set `created_by`/`last_updated_by: 0` (System user) in every record (or use `audit` / don't seed them); otherwise install fails on the missing user columns
15
+
16
+ ## Manifest
17
+
18
+ - [ ] `manifest.json` exists at the module root
19
+ - [ ] `packageFormat` is set (usually `1`)
20
+ - [ ] `moduleId` is a valid UUID and unique (not copied from another module)
21
+ - [ ] `code` is lowercase, snake_case, letters+digits+underscores only
22
+ - [ ] `version` is valid semver
23
+ - [ ] `dbSchemaVersion` is valid semver
24
+ - [ ] `displayName` and `description` are present
25
+ - [ ] `dependencies` includes `admin` (almost always required)
26
+ - [ ] Every `entities` entry points to a file that exists
27
+ - [ ] Every other content pointer (`dataViews`, `menus`, `security`, etc.) points to existing files
28
+
29
+ ## Entities
30
+
31
+ For each entity:
32
+
33
+ - [ ] Has `description`, `dbObject`, `toString`
34
+ - [ ] Has `traits: ["identity", "audit"]` (or a justified exception)
35
+ - [ ] `toString` template uses only column names that exist on the entity
36
+ - [ ] Every column has `fieldTypeCd` (except for formula columns which use `baseDatatypeCd` + `columnType: "F"`)
37
+ - [ ] Every `fieldTypeCd` value is from the official catalog (see `field-types.md`)
38
+ - [ ] Every column has `flags` (valid letters: V, I, E, M, H only — no U, S, or P; see `flags.md` for meanings)
39
+ - [ ] Every column has `orderNum`
40
+ - [ ] Every column has `description`
41
+
42
+ ### FK+Reference pattern
43
+
44
+ - [ ] Every reference column (`columnType: "R"`) has a paired hidden FK column
45
+ - [ ] The hidden FK column has `flags: "EM"` (not `V`, not `I`)
46
+ - [ ] The reference column has `flags: "VEM"`, `fieldTypeCd: "lookup"`, and a `link` object
47
+ - [ ] `link.entity` points to an entity that exists (in this module or a dependency)
48
+ - [ ] `link.thisKey` matches the FK column name on this entity
49
+ - [ ] `link.otherKey` matches the PK column name on the target entity
50
+ - [ ] The entity's `references` block declares the FK constraint
51
+
52
+ ### Formula columns
53
+
54
+ - [ ] `columnType: "F"`
55
+ - [ ] Has `baseDatatypeCd` (required)
56
+ - [ ] Has `formula`
57
+ - [ ] Uses only known formula functions (see `formulas.md`)
58
+ - [ ] Does NOT have `dbDatatype` (formula columns are virtual)
59
+ - [ ] Flags is usually `"V"` (read-only)
60
+
61
+ ### Set columns
62
+
63
+ - [ ] `columnType: "S"`
64
+ - [ ] `fieldTypeCd: "grid"` (or another set renderer)
65
+ - [ ] Has `link` with `entity`, `thisKey`, `otherKey`
66
+ - [ ] `link.otherKey` points to a real FK column on the target entity
67
+
68
+ ## Data views
69
+
70
+ - [ ] Every data view has `label` and `dataSources` (`viewType` is optional — omit for grid; it defaults to `grid` at runtime)
71
+ - [ ] `dataSources` is an array, not an object
72
+ - [ ] Each source has `entityCode` and `columns`
73
+ - [ ] `entityCode` points to an entity that exists
74
+ - [ ] Column codes in `columns` all exist on the entity
75
+ - [ ] If set, `viewType` is from the supported list (`grid`, `list`, `kanban`, `calendar`, `gallery`, `tree-grid`, `diagram`, `master-detail`, `library`, `matrix`)
76
+ - [ ] A `matrix` view has a `viewConfig` with `rowAxis`, `colAxis`, and `cell` (cell `entity` matches the primary `dataSources` entity; `rowKey`/`colKey` are real cell columns)
77
+ - [ ] Sort uses the view-def-root `order` key — a `string[]` like `["-created_date", "name"]` (leading `-` = descending), NOT `sort` / `[{column_cd, direction}]` (that object shape belongs to queries & reports, not data views)
78
+
79
+ ## Menus
80
+
81
+ - [ ] Menus are **nested dictionaries** with `children`, NOT arrays
82
+ - [ ] Every node has `label`
83
+ - [ ] Leaf items have `itemType` (`V`, `R`, `D`, or `A`)
84
+ - [ ] Folder nodes do **not** have `itemType`
85
+ - [ ] Leaf items with `itemType: "V"` have `dataViewCode` (NOT `viewCode`)
86
+ - [ ] Every `dataViewCode` points to a data view that exists
87
+ - [ ] Every `reportCode` points to a report that exists
88
+ - [ ] `orderNum` is set on each node for deterministic ordering
89
+
90
+ ## Security
91
+
92
+ - [ ] `security/roles.json` exists (at least one role)
93
+ - [ ] Every role has `description` and `rights` (both required; no `label` field — display name comes from `description`). The `roles.schema.json` sets `additionalProperties: false`, so any extra field (including `label`) is rejected at install time.
94
+ - [ ] The field name is `rights`, NOT `entityRights`
95
+ - [ ] Rights strings use only valid letters: `SIUDC` for entities, `E` for actions/reports/folders
96
+ - [ ] Entity codes in `rights` all reference real entities (in this module or dependencies)
97
+ - [ ] At least one role has `SIUDC` on every entity (typically an admin role)
98
+
99
+ ## Actions
100
+
101
+ For each action:
102
+
103
+ - [ ] Registered in `ui/actions.json` with `label`, `script`, `executionMode`, `entityCode`
104
+ - [ ] DSL file exists at `logic/actions/<script>.dsl`, where `<script>` is the bare filename in the action's `script` field (no path, no `.dsl` extension)
105
+ - [ ] DSL file has `params:`, `canExecute:`, and `execute:` blocks
106
+ - [ ] `canExecute:` is a valid formula expression
107
+ - [ ] `execute:` uses only documented built-in functions
108
+ - [ ] Referenced entities exist
109
+ - [ ] Referenced parameters are declared in `params:`
110
+
111
+ ## Settings
112
+
113
+ - [ ] `settings.json` exists if the module has any settings
114
+ - [ ] Every setting has `fieldTypeCd`
115
+ - [ ] Every setting has a `defaultValue` (or is explicitly nullable) — note the field is `defaultValue`, not `default`
116
+ - [ ] Field types used are from the supported subset (no `lookup`, no `grid`)
117
+
118
+ ## Seed data
119
+
120
+ - [ ] Seed files are in `seed-data/` with numbered prefixes (`01-`, `02-`, …)
121
+ - [ ] Seed files are ordered by FK dependency (parents before children)
122
+ - [ ] Seeded `audit-full` entities supply `created_by`/`last_updated_by` — `audit-full` adds these as required (cuid, NOT NULL) columns. Either set both to the System user `0` in every seed record, switch the entity to `audit`, or don't seed it. (The `audit` timestamps need no value — they default to `NOW()`.)
123
+ - [ ] Each seed file has `entityCode` and `records`
124
+ - [ ] Every record has explicit values for NOT NULL columns
125
+ - [ ] PKs are explicit **numeric integers** (e.g. 1001, 1002 — NOT UUID strings, `cuid` is int8)
126
+
127
+ ## Translations
128
+
129
+ - [ ] `translations/en-US.json` exists
130
+ - [ ] Has `entities` section with `label`, `desc`, and `fields` for every entity
131
+ - [ ] Every field (including trait-provided: `created_date`, `last_updated`, etc.) has a `label`
132
+ - [ ] Has `views` section with labels for every data view
133
+ - [ ] Has `menus` section matching the `ui/menus.json` structure
134
+ - [ ] Has `actions` section with labels for every action
135
+ - [ ] Has `roles` section with labels for every role
136
+ - [ ] Has `settings` section with labels for every setting (if settings exist)
137
+ - [ ] Has `folders` section with label for the root folder
138
+ - [ ] Additional language files (if any) have the same structure as `en-US.json`
139
+ - [ ] Manifest `supportedLocales` lists every non-English locale that has a `translations/<locale>.json` file (English is not listed)
140
+
141
+ ## Versioning
142
+
143
+ - [ ] `version` in manifest is bumped from previous release (or `"0.1.0"` for new modules)
144
+ - [ ] `dbSchemaVersion` is bumped if any DB schema changed (new entity, new column, changed type)
145
+ - [ ] `dbSchemaVersion` is NOT bumped if only views/menus/actions/translations changed
146
+
147
+ ## Cross-entity consistency
148
+
149
+ - [ ] Every menu item's `dataViewCode` has a matching entry in `ui/data_views.json`
150
+ - [ ] Every data view's `entityCode` has a matching entity in the module (or a dependency)
151
+ - [ ] Every role's `rights` object codes match real entities/actions/reports
152
+ - [ ] Every action's `entityCode` matches a real entity
153
+ - [ ] Every FK constraint in `references` blocks matches a real target entity
154
+
155
+ ## After validation
156
+
157
+ - [ ] Run `dforge_module_validate` and fix every `error` in `files["_validate.json"]`
158
+ - [ ] Run `dforge_module_pack` to build the `.dforge` file — it should succeed
159
+ - [ ] Run `dforge_module_install` against a real test tenant — this is the full server-side validator
160
+ - [ ] If `dforge_module_install` returns `ok: false`, read the returned `output`, fix the module defect it reports, then repeat validate → pack → install
161
+ - [ ] Stop and ask the user only for environment/tooling failures: missing CLI, missing/expired token, unreachable tenant/API, permissions, or a path outside the workspace
162
+
163
+ ## Red flags during review
164
+
165
+ If you see any of these, stop and investigate:
166
+
167
+ - An entity with no `traits` and no explicit PK column
168
+ - A reference column without a paired FK column (or vice versa)
169
+ - A column with `fieldTypeCd: "integer"` / `"datePicker"` / `"money"` / `"autocomplete"` / `"boolean"` (wrong names)
170
+ - Flags containing `U`, `S`, or `P` (not valid flag letters)
171
+ - Menus with `children: [...]` (array, not dict)
172
+ - Roles with `entityRights` (wrong key)
173
+ - Data views with root-level `entityCode` (should be inside `dataSources`)
174
+ - Formula columns without `baseDatatypeCd`
175
+ - DSL actions with JavaScript/Python syntax
176
+ - Seed data files without numbered prefixes
177
+ - Seed data using `"entity"` instead of `"entityCode"` (silent failure — installer reads empty entity code)
178
+ - Seed data PKs as UUID strings instead of numeric integers
179
+ - Duplicate column codes in the same entity
180
+ - `orderNum` missing or duplicated across columns
181
+
182
+ ## When the checklist passes
183
+
184
+ Tell the user what you built, summarize the entity model (e.g. "7 entities: contact, account, opportunity with its line items, quote with lines, activity, product"), list the data views and actions, describe the security roles, and run the Phase 6 install loop. The module is not complete until `dforge_module_install` succeeds against a real tenant.