@dforge-core/dforge-mcp 0.2.15 → 0.2.17
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.
- package/CHANGELOG.md +192 -0
- package/dist/server.js +157 -9
- package/package.json +3 -3
- package/resources/docs/conventions.md +74 -1
- package/resources/schemas/deps.schema.json +1 -1
- package/resources/schemas/entity.schema.json +80 -2
- package/resources/schemas/folders.schema.json +1 -1
- package/resources/schemas/manifest.schema.json +1 -1
- package/resources/schemas/menus.schema.json +1 -1
- package/resources/schemas/triggers.schema.json +1 -1
- package/skills/dforge-mcp-author/examples/column-security/README.md +67 -0
- package/skills/dforge-mcp-author/examples/column-security/entities/product.json +79 -0
- package/skills/dforge-mcp-author/examples/column-security/manifest.json +23 -0
- package/skills/dforge-mcp-author/examples/column-security/security/roles.json +16 -0
- package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +35 -0
- package/skills/dforge-mcp-author/examples/column-security/ui/data_views.json +20 -0
- package/skills/dforge-mcp-author/examples/column-security/ui/folders.json +23 -0
- package/skills/dforge-mcp-author/examples/column-security/ui/menus.json +21 -0
- package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +8 -12
- package/skills/dforge-mcp-author/references/column-types.md +3 -2
- package/skills/dforge-mcp-author/references/data-views.md +2 -0
- package/skills/dforge-mcp-author/references/flags.md +41 -9
- package/skills/dforge-mcp-author/references/formulas.md +7 -0
- package/skills/dforge-mcp-author/references/security.md +77 -1
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -0
- package/skills/dforge-module-build/SKILL.md +20 -3
|
@@ -213,7 +213,7 @@ Lives in: `ui/folders.json`
|
|
|
213
213
|
|
|
214
214
|
| Property | Type | Description |
|
|
215
215
|
|---|---|---|
|
|
216
|
-
| `viewName` | string |
|
|
216
|
+
| `viewName` | string | Entity view to bind — a key under the entity's `views` (see [Column-level security](#column-level-security-entity-views)), **not** a `ui/data_views.json` code. `"default"` means "no view" (full column set). |
|
|
217
217
|
| `quickAdd` | boolean | Show "+" quick-add button (`true` for main entities, `false` for detail/line items) |
|
|
218
218
|
| `rowFilter` | object | Row-level filter scoping which records are visible in this folder |
|
|
219
219
|
|
|
@@ -226,6 +226,82 @@ Row filters use a compact filter object with:
|
|
|
226
226
|
|
|
227
227
|
This is how **row-level security** works in the module package — subfolders with `rowFilter` restrict which rows are visible. Users assigned to "Central Warehouse" only see stock where `warehouse_id = 2001`.
|
|
228
228
|
|
|
229
|
+
## Column-level security (entity views)
|
|
230
|
+
|
|
231
|
+
> Worked example: `dforge://example/column-security/` — one entity, two folders, two
|
|
232
|
+
> column sets. Its README walks the overrides and the ways a view fails the install.
|
|
233
|
+
|
|
234
|
+
Layer 2 of the model. A **entity view** is a named subset of an entity's columns, declared on the entity itself; a folder binds one per entity. Users working in that folder see **only** the columns the view lists — the rest are neither visible nor queryable there.
|
|
235
|
+
|
|
236
|
+
Two halves, both required:
|
|
237
|
+
|
|
238
|
+
| File | Key | Effect |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `entities/<entity>.json` | `views.<viewName>.columns` | Declares the view and its column set |
|
|
241
|
+
| `ui/folders.json` | `entities.<code>.viewName` | Binds that view for this entity in this folder |
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"fields": { "product_id": {}, "name": {}, "price": {}, "cost": {}, "quantity": {} },
|
|
246
|
+
"views": {
|
|
247
|
+
"accountant": {
|
|
248
|
+
"columns": {
|
|
249
|
+
"product_id": {},
|
|
250
|
+
"name": { "flags": "V" },
|
|
251
|
+
"price": { "flags": "VE" },
|
|
252
|
+
"cost": { "flags": "V" }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"storekeeper": {
|
|
256
|
+
"columns": {
|
|
257
|
+
"product_id": {},
|
|
258
|
+
"name": { "flags": "V" },
|
|
259
|
+
"quantity": { "flags": "VE" }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
"entities": { "product": { "viewName": "accountant", "quickAdd": true } }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The storekeeper folder cannot see `price` or `cost` at all — not on screen, not through a filter, not in an export.
|
|
271
|
+
|
|
272
|
+
**Per-column overrides** (all optional, each falls back to the entity-level value): `flags`, `orderNum`, `isNullable`, `editMask`, `displayFmt`, `refFilter`, `params`, `formula`. `{}` means "expose this column, override nothing".
|
|
273
|
+
|
|
274
|
+
> **An empty override is a value, not an absence.** The runtime merges view over entity with `COALESCE`, which skips NULL only — so `"flags": ""` means "no flags in this view" (a column neither visible nor editable) and `"params": {}` suppresses the entity-level params rather than inheriting them. Omit the key entirely to inherit — `"flags": null` fails schema validation. See `flags.md` for the flag letters.
|
|
275
|
+
|
|
276
|
+
> **`M` works in a view override too**, folding into that override's `isNullable`. Since a view writes no DDL, the requiredness is runtime-only — which is how a column nullable in the database is made mandatory inside one view: `{ "flags": "VEM" }`. The fold is one-directional: dropping `M` does not loosen the column, because `isNullable` still inherits. See `flags.md` → Per-view overrides.
|
|
277
|
+
|
|
278
|
+
`formula` lets one view compute a column differently from the entity — same DSL as a field's `formula`, and **only on a column the entity declares as `columnType: "F"`**; elsewhere the install fails, because there that field holds the SQL default and a view cannot change a physical column's `DEFAULT`. There is no per-view default value for a normal column. See `formulas.md`.
|
|
279
|
+
|
|
280
|
+
### Rules the installer enforces
|
|
281
|
+
|
|
282
|
+
Each of these is an install-time error, and each is silent at runtime if it slips through — which is why `dforge_module_validate` checks them offline:
|
|
283
|
+
|
|
284
|
+
- A column absent from `columns` is **hidden**. That is the mechanism, not an oversight.
|
|
285
|
+
- A view must list the entity's **primary key** — records are addressed by it. To keep it off screen, drop `V` from its flags instead of omitting it.
|
|
286
|
+
- A column code that doesn't exist on the entity, a view with **no columns**, the same column listed twice under different casing, or two views on one entity whose names differ only by case (a folder binds case-insensitively, so only one could ever be reached).
|
|
287
|
+
- Views must be declared on the entity's **own** module. An `extends` extension declaring `views` is rejected — a view enumerates the complete column set, which an extension does not own.
|
|
288
|
+
- A `viewName` that no view declares, or one naming a view with no columns, **fails the install**. It does not fall back: the fallback is the entity's full column set, so a typo would quietly *unrestrict* the folder.
|
|
289
|
+
- Columns removed from a view are reaped on upgrade — that is the security change the author asked for. Whole views are not: `entity_view` also holds tenant views from the card-layout editor, and the installer cannot tell them apart.
|
|
290
|
+
|
|
291
|
+
### `"default"` is the one exempt name
|
|
292
|
+
|
|
293
|
+
`"viewName": "default"` means **no view** (full column set) and declares nothing. It is what every shipped module writes, and what the runtime auto-creates for an entity reached without a folder binding — so it is exempt from the checks above. A module that really declares a view named `default` still gets it bound.
|
|
294
|
+
|
|
295
|
+
### Don't confuse the three "views"
|
|
296
|
+
|
|
297
|
+
| Term | Where | What it is |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| **Entity view** | `entities/*.json` → `views` | Column-level security (this section) |
|
|
300
|
+
| **Data view** | `ui/data_views.json` | A grid / kanban / calendar over an entity (`data-views.md`) |
|
|
301
|
+
| **SQL view** | `entities/*.json` → `isView` + `viewSql` | An entity backed by a SQL view instead of a table |
|
|
302
|
+
|
|
303
|
+
`viewName` in `folders.json` binds the first. Menus' `dataViewCode` references the second.
|
|
304
|
+
|
|
229
305
|
### When to use subfolders
|
|
230
306
|
|
|
231
307
|
- **Multi-location modules** (warehouses, offices, branches) — one subfolder per location, filtered by location FK
|
|
@@ -105,6 +105,8 @@ For each entity:
|
|
|
105
105
|
## Security
|
|
106
106
|
|
|
107
107
|
- [ ] `security/roles.json` exists (at least one role)
|
|
108
|
+
- [ ] Every entity view (`views.<v>.columns` on an entity) lists the entity's **primary key**, names only real columns, and has at least one column — each of these fails the install
|
|
109
|
+
- [ ] Every `viewName` in `ui/folders.json` is either `"default"` (= no view) or a view that entity actually declares — an unresolved name fails the install rather than falling back
|
|
108
110
|
- [ ] Every role has `description` and `rights` (both required; no `label` field in `roles.json` — the `roles.schema.json` sets `additionalProperties: false`, so any extra field including `label` is rejected here). `description` is the English display-name fallback; **localized role labels live in the translation files** (`roles.<code>.label`) and are completeness-enforced — see Translations below.
|
|
109
111
|
- [ ] The field name is `rights`, NOT `entityRights`
|
|
110
112
|
- [ ] Rights strings use only valid letters: `SIUDC` for entities, `E` for actions/reports/folders
|
|
@@ -31,6 +31,7 @@ flags, or column patterns.
|
|
|
31
31
|
| Add a grid / list view | `dforge://reference/data-views`, `dforge://example/ui/data_views.json` | — |
|
|
32
32
|
| Add a matrix (pivot) view | `dforge://reference/data-views` (§Matrix), `dforge://example/matrix-budget/ui/data_views.json`, `dforge://example/matrix-budget/entities/budget_line.json` | — |
|
|
33
33
|
| Add kanban / calendar / tree-grid / master-detail | `dforge://reference/data-views` | `dforge://schema/data-views` |
|
|
34
|
+
| Hide columns from a user type (column-level security) | `dforge://reference/security` (§Column-level security), `dforge://example/column-security/entities/product.json`, `dforge://example/column-security/ui/folders.json` | — |
|
|
34
35
|
| Add a menu | `dforge://reference/menus`, `dforge://example/ui/menus.json` | — |
|
|
35
36
|
| Add an action | `dforge://reference/action-dsl`, `dforge://example/logic/actions/mark_done.dsl`, `dforge://example/ui/actions.json` | `dforge://docs/dsl` |
|
|
36
37
|
| Add a trigger | — | `dforge://schema/triggers`; condition syntax in `dforge://docs/dsl` |
|
|
@@ -186,7 +187,13 @@ before committing, then `dforge_action_add`. One action per turn.
|
|
|
186
187
|
|
|
187
188
|
**2b Triggers** reference actions that must already exist. `async: true` runs
|
|
188
189
|
after the transaction commits (use for anything slow); `async: false` runs
|
|
189
|
-
in-transaction, so a failure rolls back the original change
|
|
190
|
+
in-transaction, so a failure rolls back the original change — **including on
|
|
191
|
+
another module's entity**. A bridge's `async: false` trigger vetoes its host's
|
|
192
|
+
write, exactly as a mandatory extension column already refuses one, so pick it
|
|
193
|
+
to enforce an invariant, not merely to react: a bug in the action blocks the
|
|
194
|
+
host's workflow as effectively as a real violation. Keep such an action to the
|
|
195
|
+
check itself and put everything else behind `try`/`catch` or in an
|
|
196
|
+
`async: true` trigger.
|
|
190
197
|
|
|
191
198
|
**2c Jobs** run as the system user with **no current record** — the action must
|
|
192
199
|
not use `[field]` syntax. Wrap a record-context action in a thin job action that
|
|
@@ -204,8 +211,11 @@ per entity, so you'll often `view_modify` rather than `view_add`.
|
|
|
204
211
|
|
|
205
212
|
View codes are semantic — the entity name (`feedback_item`), a plural
|
|
206
213
|
(`invoices`), or descriptive (`invoices_kanban`). **Never use the literal code
|
|
207
|
-
`default
|
|
208
|
-
|
|
214
|
+
`default`.** Note that `ui/folders.json`'s `viewName` is a different axis
|
|
215
|
+
entirely: it binds an *entity view* (column-level security, declared under
|
|
216
|
+
`views` on the entity file), not a data view code. `"default"` there means "no
|
|
217
|
+
entity view — show the full column set", and any other name the entity does not
|
|
218
|
+
declare **fails the install**. See `references/security.md`.
|
|
209
219
|
|
|
210
220
|
Then wire menus with `dforge_menu_add` — a section node per group, leaves
|
|
211
221
|
pointing at the views.
|
|
@@ -273,6 +283,13 @@ stated trigger; every report's params sit on a dataset.
|
|
|
273
283
|
4. **Show the rights matrix as a table** (rows = entities/actions/reports,
|
|
274
284
|
columns = roles, cells = rights strings), each cell explained by the verb it
|
|
275
285
|
maps to. Get sign-off.
|
|
286
|
+
5. **Column-level security only when the intake asks for it.** Roles grant rights
|
|
287
|
+
per entity, not per field. When a user type must not see specific *columns*
|
|
288
|
+
(salaries, cost prices, personal data), that is an **entity view**: declare
|
|
289
|
+
`views.<name>.columns` on the entity and bind it with `viewName` on the folder
|
|
290
|
+
that user type works in. Don't reach for it otherwise — a view is the complete
|
|
291
|
+
column list, so it is one more list to keep in step with the fields. Rules and
|
|
292
|
+
the worked example: `references/security.md` → Column-level security.
|
|
276
293
|
5. `dforge_role_add` for new roles; `dforge_role_right_set` per grant when
|
|
277
294
|
amending an existing one.
|
|
278
295
|
|