@jskit-ai/agent-docs 0.1.282 → 0.1.284

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 (151) hide show
  1. package/guide/agent/app-extras/assistant.md +14 -2
  2. package/guide/agent/app-extras/realtime.md +38 -1
  3. package/guide/agent/app-setup/authentication.md +64 -14
  4. package/guide/agent/app-setup/console.md +8 -0
  5. package/guide/agent/app-setup/database-layer.md +23 -103
  6. package/guide/agent/app-setup/existing-application-migration.md +105 -72
  7. package/guide/agent/app-setup/multi-homing.md +94 -32
  8. package/guide/agent/app-setup/quickstart.md +1 -1
  9. package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
  10. package/guide/agent/app-setup/upgrading-jskit.md +8 -3
  11. package/guide/agent/app-setup/users.md +8 -5
  12. package/guide/agent/framework/application-operations.md +1 -1
  13. package/guide/agent/framework/crud-operations.md +63 -45
  14. package/guide/agent/framework/data-access.md +1031 -0
  15. package/guide/agent/framework/sanity-checks.md +26 -27
  16. package/guide/agent/framework/typed-features.md +114 -247
  17. package/guide/agent/index.md +2 -0
  18. package/package.json +6 -3
  19. package/patterns/feature-package/PATTERN.md +25 -17
  20. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  21. package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  22. package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
  23. package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
  24. package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
  25. package/patterns/feature-package/example/variations/types.js +1 -1
  26. package/patterns/minimal-foundation/example/AGENTS.md +4 -0
  27. package/patterns/minimal-foundation/example/package.json +4 -4
  28. package/patterns/minimal-foundation/example/server.js +4 -1
  29. package/patterns/shell-foundation/example/AGENTS.md +4 -0
  30. package/patterns/shell-foundation/example/package.json +5 -5
  31. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  32. package/patterns/shell-foundation/example/server.js +4 -1
  33. package/reference/autogen/KERNEL_MAP.md +2 -1
  34. package/reference/autogen/PATTERN_INDEX.md +70 -59
  35. package/reference/autogen/README.md +1 -1
  36. package/reference/autogen/packages/agent-docs.md +7 -3
  37. package/reference/autogen/packages/agent-runtime.md +48 -11
  38. package/reference/autogen/packages/architecture-inspector.md +6 -2
  39. package/reference/autogen/packages/assistant-core.md +4 -4
  40. package/reference/autogen/packages/assistant-runtime.md +33 -13
  41. package/reference/autogen/packages/auth-provider-local-core.md +3 -1
  42. package/reference/autogen/packages/auth-provider-local-db-core.md +65 -14
  43. package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
  44. package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
  45. package/reference/autogen/packages/connectors-core.md +22 -2
  46. package/reference/autogen/packages/console-core.md +9 -5
  47. package/reference/autogen/packages/crud-core.md +38 -173
  48. package/reference/autogen/packages/database-runtime.md +8 -32
  49. package/reference/autogen/packages/http-runtime.md +3 -3
  50. package/reference/autogen/packages/http-web.md +12 -0
  51. package/reference/autogen/packages/kernel.md +25 -2
  52. package/reference/autogen/packages/oidc-issuer-core.md +4 -0
  53. package/reference/autogen/packages/payments-core.md +29 -6
  54. package/reference/autogen/packages/realtime.md +15 -6
  55. package/reference/autogen/packages/resource-core.md +30 -5
  56. package/reference/autogen/packages/resource-crud-core.md +30 -34
  57. package/reference/autogen/packages/resource-runtime.md +198 -0
  58. package/reference/autogen/packages/rewarded-core.md +16 -10
  59. package/reference/autogen/packages/shell-web.md +1 -0
  60. package/reference/autogen/packages/users-core.md +26 -14
  61. package/reference/autogen/packages/workspaces-core.md +17 -10
  62. package/reference/autogen/tooling/config-eslint.md +69 -0
  63. package/reference/autogen/tooling/jskit-catalog.md +40 -1
  64. package/reference/autogen/tooling/jskit-release.md +3 -0
  65. package/skills/jskit/SKILL.md +4 -1
  66. package/skills/jskit/references/app-operations.md +1 -1
  67. package/skills/jskit/references/crud-operations.md +62 -44
  68. package/skills/jskit/references/data-access.md +1026 -0
  69. package/skills/jskit/references/database-layer.md +411 -0
  70. package/skills/jskit/references/existing-application-migration.md +105 -72
  71. package/skills/jskit/references/heuristics/INDEX.md +6 -4
  72. package/skills/jskit/references/heuristics/crud-authoring.md +22 -14
  73. package/skills/jskit/references/heuristics/data-access.md +19 -0
  74. package/skills/jskit/references/heuristics/filters.md +6 -6
  75. package/skills/jskit/references/heuristics/row-policies.md +40 -16
  76. package/skills/jskit/references/heuristics/server-search.md +30 -42
  77. package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
  78. package/skills/jskit/references/heuristics/typed-features.md +3 -3
  79. package/skills/jskit/references/multi-homing.md +94 -32
  80. package/skills/jskit/references/pattern-index.md +70 -59
  81. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
  82. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  83. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
  84. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
  85. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  86. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  87. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
  88. package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
  89. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
  90. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
  91. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  92. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  93. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  94. package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
  95. package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
  96. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
  97. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
  98. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
  99. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
  100. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
  101. package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
  102. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
  103. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
  104. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
  105. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
  106. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
  107. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
  108. package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
  109. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
  110. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
  111. package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
  112. package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +117 -0
  113. package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
  114. package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
  115. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  116. package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
  117. package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
  118. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  119. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  120. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
  121. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
  122. package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
  123. package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
  124. package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +59 -10
  125. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
  126. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
  127. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
  128. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
  129. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
  130. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
  131. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
  132. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
  133. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
  134. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
  135. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
  136. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
  137. package/skills/jskit/references/sanity-checks.md +27 -28
  138. package/skills/jskit/references/typed-features.md +114 -247
  139. package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
  140. package/reference/autogen/packages/json-rest-api-core.md +0 -66
  141. package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
  142. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
  143. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
  144. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
  145. package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
  146. package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
  147. package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
  148. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
  149. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
  150. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
  151. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
@@ -110,14 +110,25 @@ The package migrations create and own these tables:
110
110
  | `workspace_invites` | `workspace_id`, `email`, `role_sid`, `status`, `token_hash`, `invited_by_user_id`, `expires_at`, `accepted_at`, `revoked_at` |
111
111
 
112
112
  Application records that belong to a workspace use a non-null `workspace_id`
113
- foreign key to `workspaces.id`. Read and change these tables through the
114
- `workspaces.core` repositories and services, not directly.
113
+ foreign key to `workspaces.id`. Read and change these tables through their
114
+ owners' capabilities (`workspaces`, `workspaces.memberships` and
115
+ `workspaces.core`, below), not directly.
115
116
 
116
117
  ## Server composition
117
118
 
118
119
  Use `workspaces/workspace-server` for role vocabulary, creation policy,
119
120
  invitations, settings, and app-owned invitation email rendering.
120
121
 
122
+ Two server settings are required, and the server refuses to start without
123
+ them: whether new workspaces accept invitations, and how long an invitation
124
+ lasts.
125
+
126
+ ```js
127
+ // config/server.js
128
+ config.workspaceSettings = { defaults: { invitesEnabled: true } };
129
+ config.workspaceMembers = { defaults: { inviteExpiresInMs: 7 * 24 * 60 * 60 * 1000 } };
130
+ ```
131
+
121
132
  Workspace scope must reach repository queries. Parsing a workspace slug or
122
133
  hiding an admin link is not authorization. Membership and role checks happen
123
134
  before an operation discloses records or record existence.
@@ -125,14 +136,12 @@ before an operation discloses records or record existence.
125
136
  ### Workspace-scoped features and routes
126
137
 
127
138
  For a standard CRUD resource owned by a workspace, give its table the
128
- `workspace_id` column, set the resource's `autofilter: "workspace"`, and bind
129
- `defineCrudJsonApiFeature()` with `ownershipFilter: "workspace"` and `scope:
130
- WORKSPACE_CRUD_SCOPE` (from `@jskit-ai/workspaces-core/server/support/workspaceCrud`)
131
- on a workspace surface. JSKIT then filters `workspaceId` by the workspace in the
132
- route. A repository method you add reads the workspace id with
133
- `resolveWorkspaceScopeValue(options.context)`
134
- (`@jskit-ai/json-rest-api-core/server/jsonRestApiHost`), which throws when the
135
- context names two workspaces.
139
+ `workspace_id` column, set its table resource's `ownership: "workspace"`, and bind
140
+ `defineCrudFeature()` with `scope: WORKSPACE_CRUD_SCOPE` (from
141
+ `@jskit-ai/workspaces-core/server/support/workspaceCrud`) on a workspace
142
+ surface. JSKIT then filters `workspaceId` by the workspace in the route. A
143
+ repository method you add passes the call's `options` on to the table's handle,
144
+ which scopes every read and write to the context's workspace.
136
145
  `workspace_user` also filters by the signed-in user. See the CRUD operations
137
146
  guide.
138
147
 
@@ -175,9 +184,29 @@ capability provides:
175
184
  | `workspaces.services.members` | Members, roles and invitations |
176
185
  | `workspaces.services.pendingInvitations` | Invitation resolution, redemption and acceptance participants |
177
186
  | `workspaces.services.settings` | Workspace settings |
178
- | `workspaces.repositories.workspaces`, `.workspaceMemberships`, `.workspaceSettings`, `.workspaceInvites` | Repository access inside the application's own transactions |
187
+ | `workspaces.repositories.workspaceSettings`, `.workspaceInvites` | Settings and invitation records inside the application's own transactions |
179
188
  | `workspaces.tenancyProfile` | The resolved tenancy mode and workspace policy |
180
189
 
190
+ The workspaces and their memberships are their own capabilities, for code that
191
+ reads or changes them inside its own transactions, typed `Workspaces` and
192
+ `WorkspacesMemberships` in `@jskit-ai/workspaces-core/server/types`. Every
193
+ method takes the call's `options` last and joins the transaction of the
194
+ `txOptions` it is given.
195
+
196
+ | Capability | Methods |
197
+ | --- | --- |
198
+ | `workspaces` | `findById(id, options)`, `findBySlug(slug, options)` (a deleted workspace is not found, though its slug stays taken), `listSlugsIn`, `findPersonalByOwnerUserId`, `listForUserId(userId, options)`, `insert`, `updateById`, `lock(workspaceId, options)` |
199
+ | `workspaces.memberships` | `findByWorkspaceIdAndUserId`, `ensureOwnerMembership`, `upsertMembership(workspaceId, userId, patch, options)`, `addMember({ workspaceId, userId, roleSid }, options)`, `activeMemberIds(workspaceId, options)`, `listActiveByWorkspaceId`, `findActiveByWorkspaceIdAndUserId`, `listActiveWorkspaceIdsByUserId`, `listActiveWorkspaceIdsByUserIds` |
200
+
201
+ `addMember` makes the user an active member with the role (`member` when none
202
+ is given), adding the membership or reactivating it. `activeMemberIds` answers
203
+ the active members' user ids at the same cost however many members there
204
+ are, without including their profiles. `lock` takes
205
+ the workspace's row lock until the caller's transaction ends, so work that must
206
+ not interleave for one workspace takes turns; it runs only inside a
207
+ transaction, given its `txOptions`, and answers `{ id }`, or `null` when there
208
+ is no such workspace.
209
+
181
210
  To check one person's membership, for example a notification recipient, call
182
211
  `workspaces.services.members.getMember(workspace, userId, options)`. It answers
183
212
  that member's `listMembers` entry, or `null` for a removed or unknown member,
@@ -225,8 +254,8 @@ workspace routes, `workspaces-web` switches Vuetify to the `workspace-light` or
225
254
  colours. Other routes use the application's themes.
226
255
 
227
256
  New workspaces start from JSKIT's blue palette unless `config/server.js` sets
228
- the application's own. Missing fields keep JSKIT's values; an invalid colour
229
- stops the server at startup:
257
+ the application's own beside the required `invitesEnabled`. Missing colours
258
+ keep JSKIT's values; an invalid colour stops the server at startup:
230
259
 
231
260
  ```js
232
261
  // config/server.js
@@ -301,11 +330,11 @@ authorization and role checks. Do not pass arbitrary HTTP input as options.
301
330
 
302
331
  | Capability method | Contract |
303
332
  | --- | --- |
304
- | `workspaces.repositories.workspaceInvites.withTransaction(work)` | Delegates to the same JSON REST API's managed `transaction(work)`; awaits commit and completion hooks before resolving. Runs `work` again when the database aborts it as a deadlock victim (see [Database layer](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#deadlock-retries)). |
305
- | `workspaces.repositories.workspaceInvites.lockWorkspaceForInvitations(workspaceId, { trx })` | Locks the workspace before application receipt/draft rows when composing preparation. Requires an active transaction. |
306
- | `workspaces.services.members.prepareInvite(workspace, actor, payload, options)` | Requires a managed `options.trx`. Validates the invitation role, replaces pending invitations for that workspace/email, and returns `{ createdInviteId, inviteTokenPreview }`. Never sends. |
307
- | `workspaces.services.members.sendInvite(workspace, actor, prepared, options)` | Requires no `options.trx`. Reloads the committed pending invitation and workspace display data, validates expiry and token correspondence, and sends that invitation without replacement. |
308
- | `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`, and answers with `sendInvite`'s result as JSON:API data (`returnJsonApiData`, read it from `.value`), as the invite action sends it. Rejects `options.trx`. |
333
+ | `workspaces.repositories.workspaceInvites.withTransaction(options, (txOptions) => …)` | The invitations handle's `withTransaction`: joins the caller's transaction or opens one and hands `work` the `txOptions` to pass on; awaits commit and completion hooks before resolving. Runs `work` again when the database aborts it as a deadlock victim (see [data access](https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access#transactions)). `{ ...options, outermost: true }` refuses a call that already runs inside one. |
334
+ | `workspaces.lock(workspaceId, txOptions)` (capability `workspaces`) | Locks the workspace before application receipt/draft rows when composing preparation. Requires an active transaction. |
335
+ | `workspaces.services.members.prepareInvite(workspace, actor, payload, txOptions)` | Runs only inside a transaction, given its `txOptions`. Validates the invitation role, replaces pending invitations for that workspace/email, and returns `{ createdInviteId, inviteTokenPreview }`. Never sends. |
336
+ | `workspaces.services.members.sendInvite(workspace, actor, prepared, options)` | Refuses to run inside a transaction: it delivers after the preparation's transaction commits. Reloads the committed pending invitation and workspace display data, validates expiry and token correspondence, and sends that invitation without replacement. |
337
+ | `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`, and answers with `sendInvite`'s result as JSON:API data (`returnJsonApiData`, read it from `.value`), as the invite action sends it. Refuses to run inside a transaction. |
309
338
  | `workspaces.invite-acceptance-participant` (optional capability) | One application function receiving `({ invite, user }, options)` inside the acceptance transaction. Without it, acceptance activates the membership only. |
310
339
 
311
340
  `sendInvite` accepts `{ createdInviteId, inviteTokenPreview }` from preparation or
@@ -344,21 +373,20 @@ setup: ({ reviewedPeople, people, scopes, audit }) => ({
344
373
  })
345
374
 
346
375
  // Inside an authorized application command, in a feature that requires
347
- // workspaces: "workspaces.core":
376
+ // workspaces: "workspaces.core" and workspaceRecords: "workspaces":
348
377
  const { members } = workspaces.services;
349
378
  const invites = workspaces.repositories.workspaceInvites;
350
- const prepared = await invites.withTransaction(async (trx) => {
351
- const options = { trx, context };
379
+ const prepared = await invites.withTransaction({ context }, async (txOptions) => {
352
380
  // Match acceptance's order: workspace, then application draft/receipt rows.
353
- await invites.lockWorkspaceForInvitations(workspace.id, options);
354
- const reviewed = await reviewedPeople.lockAuthorizedDraft(draftId, workspace.id, actor, options);
355
- const receipt = await reviewedPeople.findMatchingReceipt(reviewed, requestId, options);
381
+ await workspaceRecords.lock(workspace.id, txOptions);
382
+ const reviewed = await reviewedPeople.lockAuthorizedDraft(draftId, workspace.id, actor, txOptions);
383
+ const receipt = await reviewedPeople.findMatchingReceipt(reviewed, requestId, txOptions);
356
384
  if (receipt) return { createdInviteId: receipt.inviteId };
357
385
  const created = await members.prepareInvite(workspace, actor, {
358
386
  email: reviewed.email,
359
387
  roleSid: reviewed.roleSid
360
- }, options);
361
- await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId, options);
388
+ }, txOptions);
389
+ await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId, txOptions);
362
390
  return { createdInviteId: created.createdInviteId };
363
391
  });
364
392
 
@@ -401,8 +429,8 @@ exactly once. Revocation after sending starts cannot recall a message.
401
429
  Register one callback during feature setup to call the required application
402
430
  services. Non-functions throw `TypeError`; duplicate registration throws, and
403
431
  registration closes when acceptance/refusal first begins. The callback's invite,
404
- user and options are shallow frozen. JSKIT owns the transaction and passes its
405
- `trx` and request `context` to the callback.
432
+ user and options are shallow frozen. JSKIT owns the transaction and passes the
433
+ callback the `txOptions` that carry it and the request's `context`.
406
434
 
407
435
  Acceptance checks authentication, recipient email, pending state and expiry under
408
436
  workspace/invitation locks. Before calling the participant, it activates membership
@@ -420,8 +448,8 @@ callback must not commit, roll back, create transactions/savepoints or perform
420
448
  external effects. Participant and later invitation-status failures roll back
421
449
  membership (including its previous role/status), person/training links, scopes,
422
450
  audit and accepted status. Invalid invitations and refusals do not call the
423
- participant. Acceptance/refusal reject borrowed `options.trx`; revocation can
424
- join a managed transaction or create one.
451
+ participant. Acceptance and refusal refuse to run inside the caller's
452
+ transaction; revocation can join one or open its own.
425
453
 
426
454
  Acceptance/refusal return 401 for unauthenticated access, 403 for a recipient
427
455
  email mismatch and 404 for a missing or handled invitation. A matching recipient's
@@ -435,7 +463,7 @@ Preserve `error.transactionOutcome` and `error.cause`: `rolledBack` confirms
435
463
  rollback, `committed` can accompany a failed completion hook, and `unknown`
436
464
  requires resolving the outcome before retrying. Errors after commit cannot undo
437
465
  persistence. See
438
- [managed transaction guidance](./existing-application-migration.md#json-rest-v2-integration).
466
+ [managed transaction guidance](./existing-application-migration.md#data-access-on-table-resources-json-rest-api-3).
439
467
 
440
468
  Invitation mutations lock workspace before invitation rows. A losing acceptance,
441
469
  refusal, revocation or replacement cannot overwrite accepted/revoked state.
@@ -494,7 +522,41 @@ Workspace surfaces put the slug in their pages root, such as
494
522
  `w/[workspaceSlug]/admin` for administration. Keep the default surface
495
523
  (`surfaceDefaultId`, usually `home`) free of a workspace requirement: `/`,
496
524
  invitation links and other unscoped pages belong to it. With
497
- `webRootAllowed: "no"`, `/` opens that surface's root.
525
+ `webRootAllowed: "no"`, `/` opens that surface's root. The foundation
526
+ defines `home` and its `public` access policy, and the `auth/auth-surface`
527
+ pattern the `auth` surface; the workspace surfaces add a policy and two
528
+ definitions:
529
+
530
+ ```js
531
+ // config/surfaceAccessPolicies.js
532
+ surfaceAccessPolicies.workspace_member = { requireAuth: true, requireWorkspaceMembership: true };
533
+
534
+ // config/public.js
535
+ config.surfaceDefinitions.app = {
536
+ id: "app",
537
+ label: "App",
538
+ pagesRoot: "w/[workspaceSlug]",
539
+ requiresAuth: true,
540
+ requiresWorkspace: true,
541
+ accessPolicyId: "workspace_member"
542
+ };
543
+ config.surfaceDefinitions.admin = {
544
+ id: "admin",
545
+ label: "Admin",
546
+ pagesRoot: "w/[workspaceSlug]/admin",
547
+ requiresAuth: true,
548
+ requiresWorkspace: true,
549
+ accessPolicyId: "workspace_member"
550
+ };
551
+ ```
552
+
553
+ An access policy takes `requireAuth`, `requireWorkspaceMembership`,
554
+ `requireAnyPermissions` and `requireAllPermissions` (permission lists), and
555
+ `requireFlagsAny` and `requireFlagsAll` (flags the server's bootstrap sets in
556
+ `surfaceAccess`). Without `requireAuth` or `requireWorkspaceMembership` it
557
+ takes the surface's `requiresAuth` or `requiresWorkspace`. The browser applies
558
+ the whole policy; the server reads only `requireWorkspaceMembership: false`
559
+ from it, and a route's own `auth` and `permission` authorize it there.
498
560
 
499
561
  `workspaces-web` remembers the last workspace each person opened in this
500
562
  browser. The **Your workspaces** page, `WorkspacesClientElement` from
@@ -58,7 +58,7 @@ questions and must not be copied into committed source.
58
58
  ## Build the first product operation
59
59
 
60
60
  Use the narrow relevant pattern and public APIs. For database CRUD, author an
61
- immutable migration and a normal shared resource contract; use framework APIs
61
+ immutable migration, its table resource and, for screens, a CRUD contract; use framework APIs
62
62
  for standard mechanics and app code for product-specific behavior. Do not
63
63
  invoke a source generator or reconstruct one as prompt options.
64
64
 
@@ -9,7 +9,7 @@ hub shows its sign-in page, the app receives an ID token, and the app keeps its
9
9
  own session.
10
10
 
11
11
  ```bash
12
- npm install @jskit-ai/oidc-issuer-core @jskit-ai/database-runtime-mysql
12
+ npm install @jskit-ai/oidc-issuer-core @jskit-ai/database-runtime @jskit-ai/database-runtime-mysql
13
13
  ```
14
14
 
15
15
  Start from the "JSKIT · Accounts hub" template (seed-jskit branch
@@ -62,7 +62,10 @@ npm run jskit:update
62
62
  ```
63
63
 
64
64
  The updater rejects unknown JSKIT packages and private JSKIT overrides during
65
- preflight, before changing any manifest.
65
+ preflight, before changing any manifest. It removes a package a release retired
66
+ (`@jskit-ai/json-rest-api-core`, replaced by `@jskit-ai/resource-runtime`) and
67
+ says what replaced it; while the application's code still imports a retired
68
+ package, it refuses and names the import to use instead.
66
69
 
67
70
  `jskit:update` reads the latest published catalog, discovers the root manifest
68
71
  and npm workspaces, and preflights every declared `@jskit-ai/*` package. It then
@@ -210,8 +213,10 @@ application:
210
213
  `defineConfig(browserTestConfig({ testDir: "./tests/e2e", prepare: "tests/e2e/databases.js" }))`
211
214
  from `@jskit-ai/jskit-catalog/browser-tests`, with `server`, `build`, `env`
212
215
  or `port` only where its own differ from `node ./bin/server.js`,
213
- `npm run build` and 4173, and `globalTimeout` only for a suite measured to
214
- need more than 20 minutes. Settings a config adds (projects, a globalSetup
216
+ `npm run build` and 4173, `globalTimeout` only for a suite measured to
217
+ need more than 20 minutes, and `actionTimeout` or `navigationTimeout` only
218
+ for an action or page measured to need more than 15 or 30 seconds (a stale
219
+ locator should fail in seconds, not minutes). Settings a config adds (projects, a globalSetup
215
220
  that proves the server's identity, `timezoneId`) spread over the result:
216
221
  `defineConfig({ ...base, projects, use: { ...base.use, timezoneId } })`. A
217
222
  second local Playwright config and any `npm run develop` or `vite`
@@ -44,11 +44,14 @@ in-page banner that moves the form.
44
44
 
45
45
  Use `users/user-administration-server` only when the product needs global or
46
46
  workspace-scoped administration beyond self-service settings. It demonstrates
47
- an app-owned feature package built with `defineCrudJsonApiFeature()` while
48
- keeping profile persistence in `users-core`.
49
-
50
- Do not expose password hashes, provider credentials, or session data through a
51
- user resource. Apply permission and workspace scope before repository access so
47
+ an app-owned feature package built with `defineCrudFeature()` on a CRUD
48
+ contract over users-core's `userProfileTableResource`, while keeping profile
49
+ persistence, and the `users` table's one table resource, in `users-core`: the
50
+ feature names `ownerRepository: "users.profiles"` and serves the table through
51
+ users-core's repository instead of owning it.
52
+
53
+ Do not expose password hashes, provider credentials, or session data through
54
+ the CRUD contract. Apply permission and workspace scope before repository access so
52
55
  record existence is not leaked.
53
56
 
54
57
  ## Verification
@@ -101,7 +101,7 @@ normal order is:
101
101
 
102
102
  1. confirm the product resource, ownership, operations, and fields
103
103
  2. author a migration as normal application source
104
- 3. author the shared resource contract through `resource-crud-core`
104
+ 3. define the table resource (`defineTableResource`) and, for screens, its CRUD contract (`defineCrudContract`)
105
105
  4. use framework APIs for standard repository/service/action/route mechanics
106
106
  5. author product-specific screens from the relevant UI pattern
107
107
  6. run migrations and direct verification
@@ -13,10 +13,15 @@ Take database, surface, access, ownership, operations, and fields from product
13
13
  intent and current source. Ask when a material choice is missing. Do not
14
14
  translate the work into generator options.
15
15
 
16
- Read the narrow package-owned pattern in the [source pattern
17
- library](/patterns/): `crud/resource-contract` for the resource,
18
- `crud/json-api-resource-package` for the server, and `crud/crud-screen-set`
19
- for routed UI. Child-resource and row-policy patterns own those variations.
16
+ Read the package-owned pattern in the [source pattern
17
+ library](/patterns/): `database/table-resource` for the table,
18
+ `crud/crud-contract` for its HTTP surface,
19
+ `crud/crud-feature-package` for the server, and `crud/crud-screen-set`
20
+ for routed UI; the [row
21
+ policies](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/heuristics/row-policies.md)
22
+ and [child
23
+ CRUDs](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/heuristics/child-cruds.md)
24
+ heuristics cover those variations.
20
25
 
21
26
  Normal CRUD tables use one non-null integer primary key. Foreign keys are
22
27
  single-column; composite unique indexes are business constraints, not
@@ -26,26 +31,38 @@ Match visibility to real ownership and test allowed plus cross-owner cases.
26
31
  ## Author the resource normally
27
32
 
28
33
  1. Write an immutable app-owned migration.
29
- 2. Define the shared resource contract through `defineCrudResource()`.
30
- 3. Use `defineCrudJsonApiFeature()` for standard repository, service, action,
31
- permission, resource-host, and route mechanics.
34
+ 2. Define the table resource with `defineTableResource()` and its HTTP surface
35
+ with `defineCrudContract()`, which names its `namespace`: the routes',
36
+ messages' and events' public name, never taken from the resource.
37
+ 3. Use `defineCrudFeature()` for standard table ownership, service, action,
38
+ permission, and route mechanics.
32
39
  4. Customize through `decorateRepository`, `decorateService`,
33
40
  `operationLifecycle`, and named `actions`; do not copy the standard CRUD
34
41
  repository/action/route stack.
35
42
  5. Build routed screens from the matching `http-web`/CRUD UI pattern.
36
43
 
37
- The resource is canonical for fields, operations, validation, transport,
38
- messages, and route parameters. Do not duplicate its schema or serializers.
44
+ The table resource is canonical for fields, columns, ownership and search; the
45
+ CRUD contract for operations, validation, transport, messages and route
46
+ parameters. A contract shows and accepts only the fields its `fields` names (stored
47
+ `createdAt` and `updatedAt` show without an entry; `createdAt: { output:
48
+ false }` hides one), so a column added later stays out of every API until a
49
+ contract names it. A CRUD feature's answers follow the same rule: an unnamed
50
+ relationship is left out, and an included record shows only its id, its
51
+ relation's `labelKey` and the fields the including contract lists in that
52
+ field's `output.includeFields` (`{ breed: ["name"] }` in the list shows a
53
+ record it links to, as deep as listed), never another contract's output.
54
+ A package with a CRUD feature lists `crud.contracts` in its descriptor's
55
+ `capabilities.requires`. Do not duplicate their schema or serializers.
39
56
  Prefer `useCrudListScreen()`, `useCrudViewScreen()`, and
40
57
  `useCrudAddEditScreen()`; use `useCommand()` or `useEndpointResource()` for
41
58
  non-standard operations.
42
59
 
43
60
  Add resource service methods with `decorateService`; expose commands such as
44
- `confirm`, `publish`, or `cancel` through named `actions`. With `operationLifecycle`, mutation `before`,
45
- `execute`, and `after` share one repository transaction, `execute` receives
46
- `standard(nextInput)`, and `afterCommit` follows commit. Repositories persist;
47
- services and hooks orchestrate. Use a transactional outbox for durable
48
- external work.
61
+ `confirm` or `cancel` through named `actions`. With `operationLifecycle`, mutation `before`,
62
+ `execute`, and `after` share one transaction, rerun on deadlock, so they touch
63
+ only the database; `execute` receives `standard(nextInput)`. Outside effects go
64
+ in `afterCommit`, run once after commit, or an outbox when durable.
65
+ Repositories persist; services and hooks orchestrate.
49
66
 
50
67
  ## Choose ownership, API access and client access independently
51
68
 
@@ -53,37 +70,31 @@ These controls answer different questions:
53
70
 
54
71
  | Control | Purpose | Public records | Owner-scoped records |
55
72
  | --- | --- | --- | --- |
56
- | Resource `autofilter` | Which rows belong to the request scope | `"public"`; no ownership column | `"user"`; real non-null `userId` column |
57
- | Resource `apiAccess` | Who may call the API | `"public"` | `"authenticated"` |
58
- | Feature `ownershipFilter` | Server route/action ownership scope | `"public"` | `"user"` |
73
+ | Table resource `ownership` | Which rows belong to the request scope | `"public"`; no ownership column | `"user"`; real non-null `userId` column |
74
+ | CRUD contract `apiAccess` | Who may call the API | `"public"` | `"authenticated"` |
75
+ | Feature route visibility | Server route/action scope: the table's `ownership` unless the feature names `visibility` | `"public"` | `"user"` |
59
76
  | Client `access` | Whether to require bootstrap permissions | Default `"auto"` with no permission requirements | Default `"auto"`; declare permissions when required |
60
77
 
61
- `access` accepts `auto`, `always`, or `never`, not `public`. With `auto`, a
62
- screen with no permission requirements does not require a permissions bootstrap.
63
- `always` does require it. Do not add authentication or workspace machinery to
64
- compensate for an unnecessary `always` override. Server access and ownership
78
+ `access` accepts `auto`, `always`, or `never`, not `public`. `auto` requires a
79
+ permissions bootstrap only for a screen with permission requirements; `always`
80
+ always does. Do not add authentication or workspace machinery to
81
+ make up for an unneeded `always` override. Server access and ownership
65
82
  remain authoritative.
66
83
 
67
- The Books source patterns demonstrate user ownership. For an intentionally
68
- public create/list resource, use the same patterns with this resource contract:
84
+ The Books source patterns show user ownership. For a public create/list
85
+ resource, use them with this table resource and CRUD contract:
69
86
 
70
87
  ```js
71
- const bookResource = defineCrudResource({
72
- namespace: "books",
88
+ const bookResource = defineTableResource({
89
+ name: "books",
73
90
  tableName: "books",
74
- apiAccess: "public",
75
- autofilter: "public",
76
- crudOperations: ["list", "view", "create"],
91
+ ownership: "public",
77
92
  schema: {
78
93
  title: {
79
94
  type: "string",
80
95
  required: true,
81
96
  minLength: 1,
82
- maxLength: 255,
83
- operations: {
84
- output: { required: true },
85
- create: { required: true }
86
- }
97
+ maxLength: 255
87
98
  }
88
99
  },
89
100
  searchSchema: {
@@ -96,20 +107,27 @@ const bookResource = defineCrudResource({
96
107
  }
97
108
  }
98
109
  });
110
+
111
+ const bookCrud = defineCrudContract(bookResource, {
112
+ namespace: "books",
113
+ apiAccess: "public",
114
+ operations: ["list", "view", "create"],
115
+ fields: { title: { output: "required", create: "required" } }
116
+ });
99
117
  ```
100
118
 
101
- Import `defineCrudResource` from
102
- `@jskit-ai/resource-crud-core/shared/crudResource`. Its app-owned migration
103
- creates `books` with an integer primary key `id` and non-null `title`; omit
104
- `userId` and its foreign key for this public variant. `list` requires `view`
105
- in the shared resource contract, even when the UI has no detail page. A create
106
- response uses the resource output contract. Keep operation names such as
107
- `list`, `view` and `create`; do not invent output-operation aliases.
108
-
109
- Bind `defineCrudJsonApiFeature()` with this resource, `ownershipFilter:
110
- "public"`, `relativePath: "/books"` and the application's configured public
111
- surface. Keep the existing package/provider and migration registration from
112
- `crud/json-api-resource-package`.
119
+ Import `defineTableResource` from `@jskit-ai/resource-core/shared/tableResource`
120
+ and `defineCrudContract` from `@jskit-ai/resource-crud-core/shared/crudContract`.
121
+ Its migration creates `books` with an integer `id` and non-null `title`; omit
122
+ `userId` and its foreign key.
123
+ `list` requires `view` in the CRUD contract, even when the UI has no detail
124
+ page. A create response uses the CRUD contract's output. Keep operation names
125
+ such as `list`, `view` and `create`; do not invent output-operation aliases.
126
+
127
+ Bind `defineCrudFeature()` with `crud: bookCrud`, `relativePath: "/books"`
128
+ and the application's configured public surface; the table's `"public"`
129
+ ownership makes its routes public. Keep the existing package/provider and migration registration from
130
+ `crud/crud-feature-package`.
113
131
 
114
132
  For the list, keep `BookListPage.vue` thin: use `resourceNamespace: "books"`
115
133
  in `useCrudListScreen()` on the configured public surface, remove unsupported