@jskit-ai/agent-docs 0.1.87 → 0.1.90

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.
@@ -197,11 +197,15 @@ In JSKIT persistence code, **visibility** means "which rows should this request
197
197
  - `public`
198
198
  - the record is not scoped by owner columns
199
199
  - `workspace`
200
- - the record belongs to one workspace, usually through `workspace_id`
200
+ - the record belongs to one workspace through the exact reserved column `workspace_id`
201
201
  - `user`
202
- - the record belongs to one user, usually through `user_id`
202
+ - the record belongs to one user through the exact reserved column `user_id`
203
203
  - `workspace_user`
204
- - the record belongs to one workspace and one user together
204
+ - the record belongs to one workspace and one user through both reserved columns
205
+
206
+ Only `workspace_id` and `user_id` carry this standard ownership contract. Specifically named foreign keys such as `recipient_user_id`, `created_by_user_id`, and `assignee_user_id` describe domain relationships; they are not alternate owner columns. Keep both fields when a row has an owner and a separate related actor, and never rename the relationship to an owner column merely to make a tool accept the schema.
207
+
208
+ The selected ownership filter must match the direct reserved columns exactly. A table with only `workspace_id` is `workspace`; a table with only `user_id` is `user`; and a table with both is `workspace_user`. A declaration cannot override or ignore either column.
205
209
 
206
210
  That is why the shared helpers exist. Repositories should not have to re-implement the same ownership rules by hand every time they filter a query or build an insert payload.
207
211
 
@@ -33,7 +33,8 @@ npx jskit generate crud-server-generator scaffold \
33
33
  --namespace contacts \
34
34
  --surface admin \
35
35
  --ownership-filter workspace \
36
- --table-name contacts
36
+ --table-name contacts \
37
+ --grant-role member
37
38
 
38
39
  npx jskit generate crud-ui-generator crud \
39
40
  w/[workspaceSlug]/admin/contacts \
@@ -140,6 +141,8 @@ So the distinction is:
140
141
 
141
142
  That is why `--internal` is not a permissions shortcut and not a UI setting. It is a server-route exposure choice on top of the same CRUD ownership model.
142
143
 
144
+ The workspace role-grant decision is separate too. Every workspace-required generation must choose `--grant-role <role-id>` or `--no-role-grant`; there is no implicit role. An application that assigns generated CRUD permissions to `member` uses `--grant-role member`. In particular, `--internal` does not imply `--no-role-grant`.
145
+
143
146
  ### Ownership controls which owner columns are expected
144
147
 
145
148
  The repository layer ultimately applies visibility through the standard owner columns:
@@ -147,6 +150,12 @@ The repository layer ultimately applies visibility through the standard owner co
147
150
  - `workspace_id`
148
151
  - `user_id`
149
152
 
153
+ Those names are exact and reserved. `workspace_id` is workspace ownership; `user_id` is user ownership; both together are workspace-user ownership. Other foreign keys such as `recipient_user_id`, `created_by_user_id`, and `assignee_user_id` remain domain relationships and must not be treated as ownership aliases.
154
+
155
+ The generated ownership filter must match that exact set of reserved columns. Neither an explicit filter nor generated metadata can override what those direct columns mean.
156
+
157
+ If a row is workspace-owned but refers to a recipient, keep both concepts explicit: use `workspace_id` for ownership and `recipient_user_id` for the relationship. Renaming the relationship to `user_id` would opt the field into JSKIT's hidden owner-field and user-filtering behavior.
158
+
150
159
  That means the generated CRUD behaves like this:
151
160
 
152
161
  - `public`
@@ -99,6 +99,27 @@ The supported values are:
99
99
  | `workspace` | rows belong to one workspace | `workspace_id` |
100
100
  | `workspace_user` | rows belong to one workspace and one user together | `workspace_id` and `user_id` |
101
101
 
102
+ ### Ownership column names are exact and reserved
103
+
104
+ JSKIT ownership is explicit and materialized. Only these exact database columns carry the standard generated ownership contract:
105
+
106
+ - `workspace_id`
107
+ - `user_id`
108
+
109
+ They are not ordinary foreign-key names. Generated CRUD infrastructure uses them for visibility filtering, create-time owner stamping, and hidden owner-field handling.
110
+
111
+ Other foreign keys are domain relationships, even when their names end in `_user_id` or `_workspace_id`. For example:
112
+
113
+ - `recipient_user_id` identifies a recipient
114
+ - `created_by_user_id` records an author
115
+ - `assignee_user_id` identifies an assignee
116
+
117
+ Those fields do not become ownership aliases merely because they point to a user-owned table.
118
+
119
+ The ownership filter must match the direct reserved columns exactly. A table with only `workspace_id` is `workspace`; one with only `user_id` is `user`; and one with both is `workspace_user`. Neither the CLI declaration nor generated metadata overrides those columns.
120
+
121
+ Decide ownership first, select the matching ownership filter, and then model other actors with specifically named foreign keys. A workspace-owned notification outbox item that targets a user normally has `workspace_id` for ownership and `recipient_user_id` for the recipient relationship. Do not rename `recipient_user_id` to `user_id` merely to satisfy a generator or Doctor check; that would change the resource's ownership semantics.
122
+
102
123
  ### What each value means
103
124
 
104
125
  #### `public`
@@ -257,7 +278,8 @@ npx jskit generate crud-server-generator scaffold \
257
278
  --namespace contacts \
258
279
  --surface admin \
259
280
  --ownership-filter workspace \
260
- --table-name contacts
281
+ --table-name contacts \
282
+ --grant-role member
261
283
  ```
262
284
 
263
285
  This creates an app-local package under `packages/contacts/`.
@@ -278,6 +300,46 @@ Use that when:
278
300
 
279
301
  Do **not** use `--internal` as a substitute for ownership or permissions design. It is only about whether the public HTTP CRUD routes exist.
280
302
 
303
+ ### Choose workspace role grants explicitly
304
+
305
+ Workspace-required CRUD actions use the normal named permission ids:
306
+
307
+ - `crud.<namespace>.list`
308
+ - `crud.<namespace>.view`
309
+ - `crud.<namespace>.create`
310
+ - `crud.<namespace>.update`
311
+ - `crud.<namespace>.delete`
312
+
313
+ The generator never guesses which role should receive all five permissions. Every workspace-required CRUD command must choose one of these options:
314
+
315
+ ```bash
316
+ --grant-role administrator
317
+ ```
318
+
319
+ or:
320
+
321
+ ```bash
322
+ --no-role-grant
323
+ ```
324
+
325
+ `--grant-role` must name a configured role. `--no-role-grant` keeps the generated actions and their permission requirements but leaves the app's role catalog unchanged. The options are mutually exclusive.
326
+
327
+ To assign the generated permissions to `member`, make that decision explicit:
328
+
329
+ ```bash
330
+ --grant-role member
331
+ ```
332
+
333
+ This decision is independent of `--internal`. For an internal, workspace-owned persistence resource whose generated actions should not be assigned to any actor role, use both:
334
+
335
+ ```bash
336
+ --ownership-filter workspace \
337
+ --internal \
338
+ --no-role-grant
339
+ ```
340
+
341
+ If neither explicit grant option is supplied, generation fails during preflight before writing files, even when a `member` role exists.
342
+
281
343
  Before generating anything, decide these with the developer:
282
344
 
283
345
  - which operations are allowed for this CRUD
@@ -420,7 +482,8 @@ npx jskit generate crud-server-generator scaffold \
420
482
  --namespace addresses \
421
483
  --surface admin \
422
484
  --ownership-filter workspace \
423
- --table-name addresses
485
+ --table-name addresses \
486
+ --grant-role member
424
487
  ```
425
488
 
426
489
  Then install the generated local package:
@@ -596,7 +659,8 @@ npx jskit generate crud-server-generator scaffold \
596
659
  --namespace comments \
597
660
  --surface admin \
598
661
  --ownership-filter workspace \
599
- --table-name comments
662
+ --table-name comments \
663
+ --grant-role member
600
664
  ```
601
665
 
602
666
  Then install the generated local package:
@@ -515,7 +515,7 @@ The `json-rest-api` package owns exhaustive framework tests for pagination, coun
515
515
 
516
516
  ## AnyAPI comes later
517
517
 
518
- `json-rest-api@1.0.25` can apply row policies with `RestApiAnyapiKnexPlugin`. JSKIT's current `internal.json-rest-api` host installs `RestApiKnexPlugin`, so the code in this chapter targets normal tables.
518
+ `json-rest-api@1.0.26` can apply row policies with `RestApiAnyapiKnexPlugin`, including grouped predicates evaluated after the policy hook returns. JSKIT's current `internal.json-rest-api` host installs `RestApiKnexPlugin`, so the code in this chapter targets normal tables.
519
519
 
520
520
  Do not add backend detection, fallback queries, or an `isAnyApi` branch to the normal-table policy in anticipation of a host that does not exist.
521
521
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.87",
3
+ "version": "0.1.90",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -24,6 +24,9 @@ Rules:
24
24
  - If `crud-server-generator` is going to own the CRUD, do not hand-write a separate CRUD migration for that table. The generator installs and manages the CRUD migration scaffold itself.
25
25
  - Do not scaffold CRUD UI, hand-build CRUD routes, or hand-build CRUD endpoints before the server CRUD package and shared resource file exist.
26
26
  - Treat the generated shared resource file as the canonical CRUD contract for later UI scaffolding and CRUD behavior changes.
27
+ - Treat the exact columns `workspace_id` and `user_id` as reserved JSKIT ownership columns. They are the only standard columns used for generated ownership filtering and create-time owner stamping.
28
+ - Treat other foreign keys such as `recipient_user_id`, `created_by_user_id`, `assignee_user_id`, and similarly specific names as domain relationships, not ownership aliases. Do not rename a relationship to `user_id` or `workspace_id` merely to satisfy tooling.
29
+ - Require the resolved ownership filter to match the reserved columns exactly: neither the filter nor the schema may silently add or omit `workspace_id` or `user_id` ownership.
27
30
  - `feature-server-generator` is not the default lane for ordinary persisted entities. Use it for workflows or orchestration that sit on top of CRUD-owned tables, or for rare explicit non-CRUD exceptions.
28
31
  - Generated CRUD UI must be compact-first. Lists need searchable cards on compact widths and tables only for medium/expanded layouts.
29
32
  - Generated CRUD list screens need real loading, empty, and error states. Empty copy should name the resource, such as "No customers yet", and offer the create action when available.
@@ -41,6 +44,14 @@ Meaning of `--internal`:
41
44
  - it keeps the generated repository, service, actions, provider, resource, and CRUD migration ownership chain
42
45
  - it only suppresses public HTTP CRUD route registration
43
46
  - it is not a substitute for ownership columns or action/route permissions
47
+ - it does not suppress generated action permission ids or decide which role receives them
48
+
49
+ Workspace role grants:
50
+
51
+ - every workspace-required CRUD generation must explicitly choose `--grant-role <role-id>` or `--no-role-grant`
52
+ - applications that assign generated CRUD permissions to `member` must say `--grant-role member`; fixed-role applications must name one of their real roles or choose no automatic grant
53
+ - `--grant-role` and `--no-role-grant` are permission-design choices independent of `--internal`
54
+ - never invent a `member` role only to satisfy the generator
44
55
 
45
56
  When a weird-custom persistence lane is proposed:
46
57
 
@@ -21,13 +21,16 @@ Exports
21
21
  - `resolveGenerationSnapshot({ appRoot, tableName, idColumnOption } = {})`
22
22
  - `renderCanonicalResourceFieldSchema(column, { fieldContractEntry = null } = {})`
23
23
  - `buildFieldContractEntries({ outputColumns = [], writableColumns = [], snapshot = {} } = {})`
24
- - `resolveCrudGenerationSurfaceId({ appRoot, options } = {})`
24
+ - `resolveCrudGenerationSurfaceId({ appRoot, options, appConfig = null } = {})`
25
+ - `prepareInstallHook({ appRoot, packageOptions = {} } = {})`
25
26
  - `__testables`
26
27
  Local functions
27
28
  - `resolveAllowedValues(schema = {}, fallbackValues = [])`
28
29
  - `resolveGlobalScaffoldCache()`
29
30
  - `asRecord(value)`
31
+ - `resolveBooleanFlagOption(options = {}, optionName = "")`
30
32
  - `resolveInternalRouteOption(options = {})`
33
+ - `resolveNoRoleGrantOption(options = {})`
31
34
  - `normalizeRequestedOwnershipFilter(value, { strict = false } = {})`
32
35
  - `inferOwnershipFilterFromSnapshot(snapshot)`
33
36
  - `assertOwnershipColumnsForFilter(snapshot, filter)`
@@ -36,7 +39,7 @@ Local functions
36
39
  - `loadEnvFromApp(appRoot)`
37
40
  - `createAppRequire(appRoot)`
38
41
  - `importModuleFromApp(appRequire, moduleId, contextLabel)`
39
- - `resolveCrudSurfaceRequiresWorkspace({ appRoot, options, surface = "" } = {})`
42
+ - `resolveCrudSurfaceRequiresWorkspace({ appRoot, options, surface = "", appConfig = null } = {})`
40
43
  - `loadCrudAppConfig(appRoot = "")`
41
44
  - `resolveSurfaceDefinitions(appConfig = {})`
42
45
  - `resolveDefaultCrudSurfaceIdFromAppConfig(appConfig = {})`
@@ -84,8 +87,11 @@ Local functions
84
87
  - `normalizeFieldMetaUiOptions(rawOptions = [])`
85
88
  - `resolveEnumFieldMetaUiOptions(enumValues = [])`
86
89
  - `buildCrudPermissionIds(namespace = "")`
90
+ - `resolveConfiguredRoleEntries(appConfig = {})`
91
+ - `formatAvailableRoleIds(roleEntries = [])`
92
+ - `resolveCrudPermissionGrantRole(appConfig = {}, options = {}, { requiresNamedPermissions = true } = {})`
87
93
  - `normalizeCrudOperation(operation = "", context = "CRUD operation")`
88
- - `renderRoleCatalogPermissionGrants(namespace = "", { requiresNamedPermissions = true } = {})`
94
+ - `renderRoleCatalogPermissionGrants(namespace = "", { requiresNamedPermissions = true, grantRoleId = "" } = {})`
89
95
  - `renderActionPermissionSupport(namespace = "", { requiresNamedPermissions = true } = {})`
90
96
  - `renderActionPermissionExpression(operation = "", { requiresNamedPermissions = true } = {})`
91
97
  - `renderRouteWorkspaceSupportImports({ surfaceRequiresWorkspace = true } = {})`
@@ -96,7 +102,7 @@ Local functions
96
102
  - `renderObjectSchemaDefinition(lines = [], { mode = "patch" } = {})`
97
103
  - `renderActionInputExpressions({ surfaceRequiresWorkspace = true } = {})`
98
104
  - `renderRouteValidatorConstants({ surfaceRequiresWorkspace = true } = {})`
99
- - `buildReplacementsFromSnapshot({ namespace = "", snapshot, resolvedOwnershipFilter, surfaceRequiresWorkspace = true, surfaceId = "", routeInternal = false })`
105
+ - `buildReplacementsFromSnapshot({ namespace = "", snapshot, resolvedOwnershipFilter, surfaceRequiresWorkspace = true, surfaceId = "", routeInternal = false, permissionGrantRoleId = "" })`
100
106
  - `resolveCrudGenerationTableName(options = {})`
101
107
  - `createCacheKey({ appRoot, options })`
102
108
  - `buildCrudTemplateContext(input = {})`