@jskit-ai/agent-docs 0.1.257 → 0.1.259

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 (230) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +88 -35
  2. package/guide/agent/app-extras/assistant.md +28 -28
  3. package/guide/agent/app-setup/authentication.md +36 -17
  4. package/guide/agent/app-setup/database-layer.md +116 -17
  5. package/guide/agent/app-setup/existing-application-migration.md +29 -25
  6. package/guide/agent/app-setup/multi-homing.md +94 -48
  7. package/guide/agent/app-setup/run-an-accounts-hub.md +6 -4
  8. package/guide/agent/framework/client-features.md +496 -0
  9. package/guide/agent/framework/crud-operations.md +5 -5
  10. package/guide/agent/framework/translation.md +4 -3
  11. package/guide/agent/framework/typed-features.md +1104 -0
  12. package/guide/agent/framework/ui-operations.md +2 -2
  13. package/guide/agent/index.md +6 -1
  14. package/package.json +1 -1
  15. package/patterns/feature-package/PATTERN.md +104 -10
  16. package/patterns/feature-package/example/booking-engine/package.json +9 -4
  17. package/patterns/feature-package/example/booking-engine/src/server/BookingEngineFeature.js +24 -0
  18. package/patterns/feature-package/example/booking-engine/src/server/bookingAvailability.js +10 -0
  19. package/patterns/feature-package/example/booking-engine/src/server/bookingEngine.js +29 -0
  20. package/patterns/feature-package/example/booking-engine/src/server/bookingEngineActions.js +26 -0
  21. package/patterns/feature-package/example/booking-engine/src/server/inputSchemas.js +1 -2
  22. package/patterns/feature-package/example/booking-engine/src/server/types.js +12 -0
  23. package/patterns/feature-package/example/variations/AvailabilityEngineFeature.js +19 -0
  24. package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +23 -0
  25. package/patterns/feature-package/example/variations/availabilityEngineActions.js +26 -0
  26. package/patterns/feature-package/example/variations/customKnexRepository.js +32 -26
  27. package/patterns/feature-package/example/variations/invoiceRollupActions.js +26 -0
  28. package/patterns/feature-package/example/variations/orchestratorService.js +24 -19
  29. package/patterns/feature-package/example/variations/types.js +7 -0
  30. package/patterns/minimal-foundation/PATTERN.md +30 -1
  31. package/patterns/minimal-foundation/example/AGENTS.md +14 -0
  32. package/patterns/minimal-foundation/example/bin/develop.js +10 -5
  33. package/patterns/minimal-foundation/example/eslint.config.mjs +21 -2
  34. package/patterns/minimal-foundation/example/package.json +5 -4
  35. package/patterns/minimal-foundation/example/playwright.config.mjs +1 -1
  36. package/patterns/minimal-foundation/example/server/lib/envFile.js +19 -0
  37. package/patterns/minimal-foundation/example/server/lib/loggerOptions.js +15 -0
  38. package/patterns/minimal-foundation/example/server/lib/runtimeEnv.js +17 -17
  39. package/patterns/minimal-foundation/example/server.js +32 -29
  40. package/patterns/minimal-foundation/example/tests/server/smoke.test.js +2 -1
  41. package/patterns/shell-foundation/PATTERN.md +29 -0
  42. package/patterns/shell-foundation/example/AGENTS.md +14 -0
  43. package/patterns/shell-foundation/example/bin/develop.js +10 -5
  44. package/patterns/shell-foundation/example/eslint.config.mjs +21 -2
  45. package/patterns/shell-foundation/example/package.json +6 -5
  46. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  47. package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +1 -0
  48. package/patterns/shell-foundation/example/playwright.config.mjs +1 -1
  49. package/patterns/shell-foundation/example/server/lib/envFile.js +19 -0
  50. package/patterns/shell-foundation/example/server/lib/loggerOptions.js +15 -0
  51. package/patterns/shell-foundation/example/server/lib/runtimeEnv.js +17 -17
  52. package/patterns/shell-foundation/example/server.js +32 -29
  53. package/patterns/shell-foundation/example/tests/server/smoke.test.js +2 -1
  54. package/reference/autogen/KERNEL_MAP.md +14 -8
  55. package/reference/autogen/PATTERN_INDEX.md +37 -37
  56. package/reference/autogen/README.md +2 -0
  57. package/reference/autogen/packages/agent-docs.md +70 -26
  58. package/reference/autogen/packages/agent-runtime.md +129 -0
  59. package/reference/autogen/packages/agent.md +149 -0
  60. package/reference/autogen/packages/architecture-inspector.md +97 -44
  61. package/reference/autogen/packages/assistant-core.md +786 -292
  62. package/reference/autogen/packages/assistant-runtime.md +182 -57
  63. package/reference/autogen/packages/assistant-voice-sherpa.md +4 -0
  64. package/reference/autogen/packages/assistant-voice.md +54 -8
  65. package/reference/autogen/packages/auth-core.md +44 -13
  66. package/reference/autogen/packages/auth-provider-local-core.md +86 -34
  67. package/reference/autogen/packages/auth-provider-local-db-core.md +1 -2
  68. package/reference/autogen/packages/auth-provider-oidc-core.md +10 -4
  69. package/reference/autogen/packages/auth-provider-supabase-core.md +52 -66
  70. package/reference/autogen/packages/auth-web.md +19 -11
  71. package/reference/autogen/packages/connectors-catalog.md +71 -43
  72. package/reference/autogen/packages/connectors-core.md +42 -4
  73. package/reference/autogen/packages/console-core.md +27 -14
  74. package/reference/autogen/packages/crud-core.md +178 -170
  75. package/reference/autogen/packages/database-runtime-mysql.md +0 -2
  76. package/reference/autogen/packages/database-runtime-postgres.md +0 -2
  77. package/reference/autogen/packages/database-runtime.md +39 -10
  78. package/reference/autogen/packages/http-runtime.md +79 -57
  79. package/reference/autogen/packages/json-rest-api-core.md +11 -8
  80. package/reference/autogen/packages/kernel.md +144 -83
  81. package/reference/autogen/packages/oidc-issuer-core.md +49 -20
  82. package/reference/autogen/packages/payments-core.md +20 -3
  83. package/reference/autogen/packages/realtime.md +47 -22
  84. package/reference/autogen/packages/resource-core.md +1 -1
  85. package/reference/autogen/packages/resource-crud-core.md +64 -26
  86. package/reference/autogen/packages/rewarded-core.md +47 -36
  87. package/reference/autogen/packages/shell-web.md +7 -10
  88. package/reference/autogen/packages/storage-runtime.md +6 -2
  89. package/reference/autogen/packages/uploads-runtime.md +10 -2
  90. package/reference/autogen/packages/users-core.md +73 -71
  91. package/reference/autogen/packages/workspaces-core.md +121 -106
  92. package/reference/autogen/packages/workspaces-web.md +1 -2
  93. package/reference/autogen/tooling/config-eslint.md +302 -0
  94. package/reference/autogen/tooling/jskit-catalog.md +28 -18
  95. package/reference/autogen/tooling/jskit-release.md +35 -20
  96. package/reference/autogen/tooling/testUtils.md +3 -1
  97. package/skills/jskit/SKILL.md +9 -8
  98. package/skills/jskit/references/app-operations.md +1 -1
  99. package/skills/jskit/references/client-features.md +491 -0
  100. package/skills/jskit/references/crud-form-actions.md +1 -1
  101. package/skills/jskit/references/crud-operations.md +7 -7
  102. package/skills/jskit/references/existing-application-migration.md +31 -27
  103. package/skills/jskit/references/heuristics/INDEX.md +5 -1
  104. package/skills/jskit/references/heuristics/child-cruds.md +1 -1
  105. package/skills/jskit/references/heuristics/client-features.md +66 -0
  106. package/skills/jskit/references/heuristics/client-requests.md +1 -1
  107. package/skills/jskit/references/heuristics/crud-authoring.md +12 -2
  108. package/skills/jskit/references/heuristics/crud-links.md +1 -1
  109. package/skills/jskit/references/heuristics/crud-repository-mapping.md +3 -3
  110. package/skills/jskit/references/heuristics/filters.md +1 -1
  111. package/skills/jskit/references/heuristics/live-actions.md +1 -1
  112. package/skills/jskit/references/heuristics/page-redirects.md +1 -1
  113. package/skills/jskit/references/heuristics/page-scaffolding.md +1 -1
  114. package/skills/jskit/references/heuristics/placements.md +1 -1
  115. package/skills/jskit/references/heuristics/row-policies.md +1 -1
  116. package/skills/jskit/references/heuristics/server-search.md +1 -1
  117. package/skills/jskit/references/heuristics/surfaces.md +1 -1
  118. package/skills/jskit/references/heuristics/typed-features.md +100 -0
  119. package/skills/jskit/references/heuristics/ui-contract.md +1 -1
  120. package/skills/jskit/references/heuristics/ui-testing.md +4 -4
  121. package/skills/jskit/references/material-3.md +2 -2
  122. package/skills/jskit/references/multi-homing.md +96 -50
  123. package/skills/jskit/references/pattern-index.md +37 -37
  124. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +30 -1
  125. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +14 -0
  126. package/skills/jskit/references/patterns/app/minimal-foundation/example/bin/develop.js +10 -5
  127. package/skills/jskit/references/patterns/app/minimal-foundation/example/eslint.config.mjs +21 -2
  128. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +5 -4
  129. package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +1 -1
  130. package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/envFile.js +19 -0
  131. package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/loggerOptions.js +15 -0
  132. package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/runtimeEnv.js +17 -17
  133. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +32 -29
  134. package/skills/jskit/references/patterns/app/minimal-foundation/example/tests/server/smoke.test.js +2 -1
  135. package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +29 -0
  136. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +14 -0
  137. package/skills/jskit/references/patterns/app/shell-foundation/example/bin/develop.js +10 -5
  138. package/skills/jskit/references/patterns/app/shell-foundation/example/eslint.config.mjs +21 -2
  139. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +6 -5
  140. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  141. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +1 -0
  142. package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +1 -1
  143. package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/envFile.js +19 -0
  144. package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/loggerOptions.js +15 -0
  145. package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/runtimeEnv.js +17 -17
  146. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +32 -29
  147. package/skills/jskit/references/patterns/app/shell-foundation/example/tests/server/smoke.test.js +2 -1
  148. package/skills/jskit/references/patterns/assistant/assistant-surface/example/src/ApplicationAiFeature.js +4 -2
  149. package/skills/jskit/references/patterns/auth/accounts-hub/PATTERN.md +2 -1
  150. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  151. package/skills/jskit/references/patterns/auth/accounts-hub/example/server/hubPaths.js +9 -3
  152. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +25 -12
  153. package/skills/jskit/references/patterns/auth/auth-surface/example/tools/preview-identity +7 -1
  154. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  155. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  156. package/skills/jskit/references/patterns/connectors/ai-connections/PATTERN.md +16 -1
  157. package/skills/jskit/references/patterns/connectors/ai-connections/example/ai-model.js +12 -3
  158. package/skills/jskit/references/patterns/connectors/ai-connections/example/perplexity-answer.js +15 -4
  159. package/skills/jskit/references/patterns/connectors/api-key-connection/PATTERN.md +9 -1
  160. package/skills/jskit/references/patterns/connectors/api-key-connection/example/connections.js +5 -2
  161. package/skills/jskit/references/patterns/connectors/aws-storage-queries/PATTERN.md +13 -3
  162. package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/s3-transfer.js +14 -3
  163. package/skills/jskit/references/patterns/connectors/calendar-cli/PATTERN.md +8 -0
  164. package/skills/jskit/references/patterns/connectors/calendar-cli/example/knexfile.js +1 -1
  165. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  166. package/skills/jskit/references/patterns/connectors/calendar-cli/example/scripts/calendar.js +8 -4
  167. package/skills/jskit/references/patterns/connectors/event-delivery/PATTERN.md +1 -1
  168. package/skills/jskit/references/patterns/connectors/firebase-messaging/PATTERN.md +10 -0
  169. package/skills/jskit/references/patterns/connectors/firebase-messaging/example/server/notifications.js +8 -0
  170. package/skills/jskit/references/patterns/connectors/google-ads-search/PATTERN.md +18 -7
  171. package/skills/jskit/references/patterns/connectors/google-ads-search/example/ads-setup.js +13 -5
  172. package/skills/jskit/references/patterns/connectors/oauth-connection/PATTERN.md +1 -1
  173. package/skills/jskit/references/patterns/connectors/paddle-catalogue/PATTERN.md +11 -2
  174. package/skills/jskit/references/patterns/connectors/paddle-catalogue/example/create-products.js +5 -2
  175. package/skills/jskit/references/patterns/connectors/public-image/PATTERN.md +10 -1
  176. package/skills/jskit/references/patterns/connectors/public-image/example/logo-url.js +4 -1
  177. package/skills/jskit/references/patterns/connectors/redshift-queries/PATTERN.md +1 -1
  178. package/skills/jskit/references/patterns/connectors/source-scanning/PATTERN.md +13 -2
  179. package/skills/jskit/references/patterns/connectors/source-scanning/example/source-scanner.js +9 -0
  180. package/skills/jskit/references/patterns/crud/json-api-resource-package/PATTERN.md +102 -23
  181. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +3 -2
  182. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/types.js +5 -0
  183. package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +54 -1
  184. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +16 -5
  185. package/skills/jskit/references/patterns/database/mysql-application/example/knexfile.js +2 -2
  186. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  187. package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +1 -1
  188. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +16 -5
  189. package/skills/jskit/references/patterns/database/postgres-application/example/knexfile.js +2 -2
  190. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  191. package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +1 -1
  192. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  193. package/skills/jskit/references/patterns/rewards/google-rewarded/example/GoogleRewardedDeliveryProvider.js +1 -1
  194. package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -11
  195. package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +104 -10
  196. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +9 -4
  197. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/BookingEngineFeature.js +24 -0
  198. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingAvailability.js +10 -0
  199. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingEngine.js +29 -0
  200. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingEngineActions.js +26 -0
  201. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/inputSchemas.js +1 -2
  202. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/types.js +12 -0
  203. package/skills/jskit/references/patterns/server/feature-package/example/variations/AvailabilityEngineFeature.js +19 -0
  204. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +23 -0
  205. package/skills/jskit/references/patterns/server/feature-package/example/variations/availabilityEngineActions.js +26 -0
  206. package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +32 -26
  207. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +26 -0
  208. package/skills/jskit/references/patterns/server/feature-package/example/variations/orchestratorService.js +24 -19
  209. package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +7 -0
  210. package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +16 -1
  211. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +3 -2
  212. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/types.js +5 -0
  213. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +4 -3
  214. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +2 -9
  215. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/types.js +5 -0
  216. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +42 -17
  217. package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/WorkspaceInvitationsFeature.js +27 -0
  218. package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/email/workspaceInviteEmail.js +11 -10
  219. package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/email/workspaceInviteMailer.js +27 -0
  220. package/skills/jskit/references/translation.md +5 -4
  221. package/skills/jskit/references/typed-features.md +1099 -0
  222. package/skills/jskit/references/ui-operations.md +3 -3
  223. package/patterns/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +0 -33
  224. package/patterns/feature-package/example/booking-engine/src/server/actions.js +0 -24
  225. package/patterns/feature-package/example/variations/AvailabilityEngineProvider.js +0 -31
  226. package/patterns/feature-package/example/variations/InvoiceRollupProvider.js +0 -36
  227. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +0 -33
  228. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/actions.js +0 -24
  229. package/skills/jskit/references/patterns/server/feature-package/example/variations/AvailabilityEngineProvider.js +0 -31
  230. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupProvider.js +0 -36
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/INDEX.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/INDEX.md`. -->
2
2
 
3
3
  # JSKIT Heuristics Index
4
4
 
@@ -44,3 +44,7 @@ How to use it:
44
44
  - [crud-repository-mapping.md](./crud-repository-mapping.md)
45
45
  - row policy, permission filtering, visibility before pagination, recursive CTE, descendants, package cycle, `RowPolicyPlugin`
46
46
  - [row-policies.md](./row-policies.md)
47
+ - typed features, server feature, provider, service, repository, collaborator, `typeof`, `= null`, `setProvider`, `register`, `input || {}`, `payload = {}`, `async execute`, `Object.freeze`, `requires`, `optional`, `types.js`, `RuntimeKnex`, `silentLogger`, `create<Name>Actions`, `ActionSpecifications`, request context, `jskit/*` lint findings, `readable-check: allow`, `optional operation:`, `process.env`, `AppError` code, `Object.assign(new Error`, `idempotency`, `…ForTests` export, file or function size
48
+ - [typed-features.md](./typed-features.md)
49
+ - client features, composable, `use<Name>`, Vue component, client provider, props, emits, `inject`, `provide`, `ref`, `reactive`, `watch`, `addEventListener`, `setInterval`, module state, `Object.freeze` on a composable, `?.()` in a template, `jskit/typed-inject`, `jskit/collaborator-props`, `jskit/fixed-result-shape`, `jskit/released-listeners`, `jskit/no-module-state`, `jskit/no-frozen-vue-state`, `clientFeaturesConfig`, `shared resource:`
50
+ - [client-features.md](./client-features.md)
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/child-cruds.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/child-cruds.md`. -->
2
2
 
3
3
  # Child CRUD Layout Patterns
4
4
 
@@ -0,0 +1,66 @@
1
+ <!-- Generated from `packages/agent-docs/heuristics/client-features.md`. -->
2
+
3
+ # Client Feature Heuristics
4
+
5
+ Use when:
6
+
7
+ - writing or reviewing a composable, Vue component, client factory or client provider
8
+ - a `jskit/*` or `vue/*` finding from `clientFeaturesConfig` is reported
9
+ - a prop, injected value or option is probed, defaulted to nothing, or patched on after construction
10
+
11
+ [Client features](../client-features.md) is the one guide
12
+ for client code: each rule says what to do, why, and shows good and bad code.
13
+ Server code follows [typed features](../typed-features.md).
14
+
15
+ Simple to reason about comes first. Write plain functions and direct calls. If
16
+ a rule seems to need a layer, a registry, a wrapper, a computed API or a split
17
+ into tiny modules, the reading is wrong: take the direct form that meets the
18
+ rule's intent, and report the case.
19
+
20
+ ## Keyword map
21
+
22
+ - a layer, wrapper, `Proxy`, `Object.fromEntries` API, one-use factory, `utils2`, `part1`, a file split only to fit a size limit
23
+ - [Simple to reason about](../client-features.md#simple-to-reason-about)
24
+ - `ReturnType<typeof useX>` repeated, an adapter shape nobody wrote down, `./client/types`, `@jskit-ai/kernel/client/types`
25
+ - [Client types](../client-features.md#client-types)
26
+ - `useX(options)` read in the body, an untyped composable option (`jskit/typed-dependencies`)
27
+ - [Typed options](../client-features.md#typed-options)
28
+ - `= null`, `= {}`, `typeof x === "function"`, `x?.()` on an option every caller passes (`jskit/required-collaborators`)
29
+ - [Required options](../client-features.md#required-options)
30
+ - `onX = () => {}`, `optional operation:`, a getter-backed binding, `?.()` in a template, `typeof source === "function" ? source() : source.value`, an upload result of several shapes
31
+ - [Absent options](../client-features.md#absent-options)
32
+ - `typeof window`, `matchMedia`, `requestAnimationFrame`, `hasWindowFunction`, `platform object:`
33
+ - [Platform probes](../client-features.md#platform-probes)
34
+ - `defineProvider` on the client, a module `Map` of components, `register<Name>()`
35
+ - [Client providers](../client-features.md#client-providers)
36
+ - `inject("key")`, `inject(KEY, null)`, `inject(KEY, {})` (`jskit/typed-inject`)
37
+ - [Injected values](../client-features.md#injected-values)
38
+ - `type: Object, default: null` on a prop the component calls, `adapter: Object` (`jskit/collaborator-props`, `vue/require-default-prop`)
39
+ - [Collaborator props](../client-features.md#collaborator-props)
40
+ - an event emitted but not declared (`vue/require-explicit-emits`)
41
+ - [Declared emits](../client-features.md#declared-emits)
42
+ - a long `<script setup>`, requests or timers in a component, `<style scoped src>`
43
+ - [Presenting scripts](../client-features.md#presenting-scripts)
44
+ - `runtime.x = …` after `useX()`, `Object.assign` onto a result, `...useX()` in a result, `Object.freeze` on a composable (`jskit/fixed-result-shape`, `jskit/no-frozen-vue-state`, `jskit/no-factory-spread`)
45
+ - [Fixed result shape](../client-features.md#fixed-result-shape)
46
+ - `watch()` moved into a helper composable while splitting
47
+ - [Effect order](../client-features.md#effect-order)
48
+ - `addEventListener` without `removeEventListener`, `setInterval`, `ResizeObserver`, `.on()` without `.off()` (`jskit/released-listeners`)
49
+ - [Released listeners](../client-features.md#released-listeners)
50
+ - a module-level `let`, `ref()` or `reactive()`, `shared resource:` (`jskit/no-module-state`)
51
+ - [Module state](../client-features.md#module-state)
52
+ - a copied `isRecord` or `normalizeText` in client code, a client copy of a server helper (`jskit/no-duplicate-helper`)
53
+ - [One helper per package](../client-features.md#one-helper-per-package)
54
+ - a `.vue` file over 750 lines (`max-lines`)
55
+ - [Size limits](../client-features.md#size-limits)
56
+ - `clientFeaturesConfig`, which globs to list, an existing application's client tree
57
+ - [Running the checks](../client-features.md#running-the-checks)
58
+
59
+ Avoid:
60
+
61
+ - probing a prop, option or injected value before calling it
62
+ - freezing a composable's result or anything holding refs
63
+ - patching members onto a result after it is built
64
+ - listeners, intervals and observers that outlive their component
65
+ - module-level state that is not one per page on purpose
66
+ - abstractions added only to satisfy a rule
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/client-requests.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/client-requests.md`. -->
2
2
 
3
3
  # Client Request Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/crud-authoring.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/crud-authoring.md`. -->
2
2
 
3
3
  # CRUD authoring
4
4
 
@@ -33,13 +33,19 @@ Read these package-owned source patterns:
33
33
  The migration and resource are both authored contracts. A live schema is useful
34
34
  for understanding an imported system, but it is never the sole source of truth.
35
35
 
36
+ Write a field's `operations` only where it differs from the default (output,
37
+ create required as the field is, patch; none for `primary: true` or
38
+ `hidden: true`; output only for `readOnly: true`), and write a lookup field as `lookup: "<namespace>"`.
39
+ The `crud/resource-contract` pattern lists what each expands to.
40
+
36
41
  ## Ownership and access
37
42
 
38
43
  - `public` means every record is intentionally public. It cannot be combined
39
44
  with user or workspace ownership.
40
45
  - `user` requires a real user ownership column and negative cross-user tests.
41
46
  - `workspace` and `workspace_user` require an explicit workspace route/action
42
- scope plus named permission policy.
47
+ scope plus named permission policy. The scope is workspaces-core's
48
+ `WORKSPACE_CRUD_SCOPE`; never copy its literal into a feature.
43
49
  - Domain relationships such as `recipient_user_id` are not ownership merely
44
50
  because they point to a user.
45
51
 
@@ -68,6 +74,10 @@ or rewrite a historical baseline.
68
74
  - `actions` exposes non-CRUD service methods through normal validated,
69
75
  permissioned, audited JSKIT actions and optional explicit HTTP routes.
70
76
 
77
+ A resource whose records carry a version number names it `lockVersionField`
78
+ and patches through the repository's `patchDocumentIfLockVersion`; see the
79
+ database layer guide's Optimistic locking.
80
+
71
81
  Keep database reads and writes in repositories. Services and lifecycle hooks
72
82
  orchestrate repositories; they do not issue raw database queries. Put external
73
83
  side effects after commit, or write a durable outbox record inside the
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/crud-links.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/crud-links.md`. -->
2
2
 
3
3
  # CRUD Link Resolution Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/crud-repository-mapping.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/crud-repository-mapping.md`. -->
2
2
 
3
3
  # CRUD Repository Mapping
4
4
 
@@ -61,7 +61,7 @@ remainingBatchWeight: {
61
61
  - `storage: { virtual: true }` fields must not appear in create/patch write schemas
62
62
 
63
63
  Repository pattern:
64
- - build the runtime with `createCrudResourceRuntime(resource, { ... })`
64
+ - build the runtime with `createCrudResourceRuntime(resource, knex, { ... })`, passing the application's Knex instance
65
65
  - register computed projections in `virtualFields`
66
66
  - keep SQL in the repository, not in shared metadata
67
67
  - let generic CRUD runtime apply `storage.writeSerializer` during create/update writes
@@ -69,7 +69,7 @@ Repository pattern:
69
69
  Example:
70
70
 
71
71
  ```js
72
- const repositoryRuntime = createCrudResourceRuntime(resource, {
72
+ const repositoryRuntime = createCrudResourceRuntime(resource, knex, {
73
73
  context: "receivals repository",
74
74
  list: LIST_CONFIG,
75
75
  virtualFields: {
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/filters.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/filters.md`. -->
2
2
 
3
3
  # Structured Filters
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/live-actions.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/live-actions.md`. -->
2
2
 
3
3
  # Live Action Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/page-redirects.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/page-redirects.md`. -->
2
2
 
3
3
  # Page Redirect Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/page-scaffolding.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/page-scaffolding.md`. -->
2
2
 
3
3
  # Page Scaffolding Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/placements.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/placements.md`. -->
2
2
 
3
3
  # Placement Patterns
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/row-policies.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/row-policies.md`. -->
2
2
 
3
3
  # Row Policies
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/server-search.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/server-search.md`. -->
2
2
 
3
3
  # Server Search
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/surfaces.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/surfaces.md`. -->
2
2
 
3
3
  # Surface Patterns
4
4
 
@@ -0,0 +1,100 @@
1
+ <!-- Generated from `packages/agent-docs/heuristics/typed-features.md`. -->
2
+
3
+ # Typed Feature Heuristics
4
+
5
+ Use when:
6
+
7
+ - writing or reviewing a server feature, provider, action, service, repository, route, error or test
8
+ - a `jskit/*` lint rule from `@jskit-ai/config-eslint` reports a finding
9
+ - a collaborator is probed, defaulted to nothing, or filled in after construction
10
+
11
+ [Typed features](../typed-features.md) is the one guide:
12
+ each rule there says what to do, why, and shows good and bad code. This map
13
+ only sends a keyword or a finding to its section. Every lint message already
14
+ ends with the same link.
15
+
16
+ ## Keyword map
17
+
18
+ - `typeof x === "function"`, `x?.method?.()`, `received?.()`, `= null`, `= {}` or `= () => {}` on a collaborator, "x is required" guard (`jskit/required-collaborators`)
19
+ - [Required collaborators](../typed-features.md#required-collaborators)
20
+ - `optional operation:` on a method every implementer has, a feature-gated operation
21
+ - [Required collaborators](../typed-features.md#required-collaborators)
22
+ - `create<Name>(dependencies)` with `dependencies.x` in the body, no `@param` type, a pure helper passed in (`jskit/typed-dependencies`)
23
+ - [Typed collaborators](../typed-features.md#typed-collaborators)
24
+ - `(...args: any[]) => Promise<any>`, `ReturnType<…>` or `X["service"]` repeated, a missing `types.js` (`jskit/named-repeated-type`)
25
+ - [Capability types](../typed-features.md#capability-types)
26
+ - `capability.service || capability`, `x?.value ?? x`, "tests pass the bare service" (`jskit/no-dual-shape`)
27
+ - [One shape per value](../typed-features.md#one-shape-per-value)
28
+ - `env = process.env`, `env || process.env`, `.env` loaded twice (`jskit/no-env-default`)
29
+ - [Environment from the caller](../typed-features.md#environment-from-the-caller)
30
+ - `setProvider`, `attach*`, any `set<Name>()` that fills a slot, `X | null` below the hook, `deferred until boot: <reason>` (`jskit/no-slot-setter`)
31
+ - [Optional tokens](../typed-features.md#optional-tokens)
32
+ - a module-level `register<Name>()`, `let x` or a `WeakMap` filled in `boot()` (`jskit/no-module-registry`)
33
+ - [Owned state](../typed-features.md#owned-state)
34
+ - `shutdown()` with `?.()` or `?? []`
35
+ - [Shutdown mirrors setup](../typed-features.md#shutdown-mirrors-setup)
36
+ - `logger = null`, `if (!logger) return`, `logger = console`, `logger?.warn`, a log line built from `error.message`
37
+ - [Loggers](../typed-features.md#loggers)
38
+ - a value built inline in `setup()`, inline `actions() { return [{ … }] }`, a `defineFeature` in `*Provider.js`
39
+ - [Feature files](../typed-features.md#feature-files)
40
+ - an action outside `create<Name>Actions`, `ActionSpecifications`, `run(service, input, context)`, positional `create<Name>Actions(service)` (`jskit/action-creator`)
41
+ - [Action creators](../typed-features.md#action-creators)
42
+ - `async execute`, `execute(input) { … }`, `await` or an envelope in an action, `input || {}`, `input?.` (`jskit/one-line-action`, `jskit/action-execute-shape`)
43
+ - [One-line execute](../typed-features.md#one-line-execute)
44
+ - `actionDefaults`, `channels` and `surfaces` repeated in every literal
45
+ - [Shared action fields](../typed-features.md#shared-action-fields)
46
+ - `mode: "patch"` on a command, a service guard that repeats `required`
47
+ - [Strict action inputs](../typed-features.md#strict-action-inputs)
48
+ - `idempotency:` (`jskit/no-idempotency`)
49
+ - [No idempotency](../typed-features.md#no-idempotency)
50
+ - a `with<Rule>(definition)` wrapper around every action
51
+ - [The action policy](../typed-features.md#the-action-policy)
52
+ - `context?.actor`, `options.context.actor`, `requireRequestContext`, `{ request, context }` beside `{ context }`
53
+ - [Request context](../typed-features.md#request-context)
54
+ - `payload = {}`, `query = {}`, `input?.x` in a service, a second parse of validated input
55
+ - [Validated input as is](../typed-features.md#validated-input-as-is)
56
+ - `options?.` or `options || {}` after `options = {}`, `fetchImpl = null` below the feature, `skip*` flags, a boolean mode parameter (`jskit/no-optional-after-default`)
57
+ - [Options and defaults](../typed-features.md#options-and-defaults)
58
+ - `return { method() { … } }` from a `create*`, an unfrozen list (`jskit/frozen-factory-result`)
59
+ - [Frozen results](../typed-features.md#frozen-results)
60
+ - `...factory(…)` in a token value or a returned API (`jskit/no-factory-spread`)
61
+ - [Named members](../typed-features.md#named-members)
62
+ - a copied helper, function or arrow, a local `normalizeText` (`jskit/no-duplicate-helper`)
63
+ - [One helper per package](../typed-features.md#one-helper-per-package)
64
+ - `options?.trx || options?.transaction`, a hand-read actor, cursor or created id
65
+ - [One reader per fact](../typed-features.md#one-reader-per-fact)
66
+ - a route that queries storage, `boot*.js` holding routes
67
+ - [Routes](../typed-features.md#routes)
68
+ - `new AppError(status, message)` with no code, `new NotFoundError("…")`, one code for two sentences (`jskit/app-error-code`)
69
+ - [Refusals carry their own code](../typed-features.md#refusals-carry-their-own-code)
70
+ - `AppError(500, …)` for an internal invariant
71
+ - [Internal failures are plain errors](../typed-features.md#internal-failures-are-plain-errors)
72
+ - `Object.assign(new Error(…), { statusCode })`, `error.status = 404` (`jskit/no-patched-error`)
73
+ - [One construction per error](../typed-features.md#one-construction-per-error)
74
+ - `.catch(() => {})` around a collaborator call
75
+ - [Failures are logged, not swallowed](../typed-features.md#failures-are-logged-not-swallowed)
76
+ - a double that copies an id format or a stream frame, production code shaped around a double
77
+ - [Doubles use documented seams](../typed-features.md#doubles-use-documented-seams)
78
+ - a parameter, option or default only tests pass
79
+ - [Tests are not callers](../typed-features.md#tests-are-not-callers)
80
+ - `…ForTests`, `__reset…`, `testOnly…` exports (`jskit/no-test-only-export`)
81
+ - [No test-only exports](../typed-features.md#no-test-only-exports)
82
+ - a file over 750 lines, a function over 600 (`max-lines`, `max-lines-per-function`), a data module
83
+ - [Size limits](../typed-features.md#size-limits), [Data modules](../typed-features.md#data-modules)
84
+ - two properties on one line in `Object.freeze({ … })` or a token map (`jskit/one-property-per-line`)
85
+ - [Formatter](../typed-features.md#formatter)
86
+ - `build*` factories, `create*` pure builders, files not named for what they hold
87
+ - [Names](../typed-features.md#names)
88
+ - a `//` description before a `/** */` block, a comment that says "was", "now", "until" or "tests" (`jskit/jsdoc-description-inside`)
89
+ - [Comments](../typed-features.md#comments)
90
+ - `readable-check: allow` without a reason prefix, a reason that cites tests (`jskit/allow-comment-form`)
91
+ - [Exceptions](../typed-features.md#exceptions)
92
+
93
+ Avoid:
94
+
95
+ - probing a collaborator before calling it
96
+ - defaulting a collaborator to `null`, `{}` or a no-op
97
+ - setters that fill a slot after construction
98
+ - module-level registries
99
+ - refusals without their own code
100
+ - excusing a finding without a reason in its `readable-check: allow` comment
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/ui-contract.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/ui-contract.md`. -->
2
2
 
3
3
  # JSKIT UI contract
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/heuristics/ui-testing.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/heuristics/ui-testing.md`. -->
2
2
 
3
3
  # UI Testing Pattern
4
4
 
@@ -26,7 +26,7 @@ Rules:
26
26
  reach the form before reload, then assert the original list URL; direct entry
27
27
  checks the configured fallback separately. Do not replace this wait with sleeps.
28
28
  - A managed runner supplies `PLAYWRIGHT_BASE_URL`. When it is set, JSKIT does not start another app server.
29
- - Vibe64 supplies an authenticated context through `VIBE64_PLAYWRIGHT_STORAGE_STATE`. Treat that file as a temporary secret: do not commit it, print it, or retain it after the run.
29
+ - A managed runner may supply an authenticated context through `PLAYWRIGHT_STORAGE_STATE`. Treat that file as a temporary secret: do not commit it, print it, or retain it after the run.
30
30
  - Do not install a browser when the environment provides a managed browser runner.
31
31
  - Shared CRUD lists retain both card and table markup and hide the inactive
32
32
  layout with CSS. Select visible record text with
@@ -134,7 +134,7 @@ A managed preview must not expose `AUTH_DEV_BYPASS_SECRET` to project code or fo
134
134
 
135
135
  ```bash
136
136
  PLAYWRIGHT_BASE_URL=https://managed-preview.example.test \
137
- VIBE64_PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
137
+ PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
138
138
  playwright test tests/e2e/contacts.spec.ts
139
139
  ```
140
140
 
@@ -152,7 +152,7 @@ test into a successful command. For an unmanaged project, for example:
152
152
  npx playwright test tests/e2e/contacts.spec.ts -g filters --reporter=dot > /tmp/contacts-test.log 2>&1
153
153
  ```
154
154
 
155
- On Vibe64, use `vibe64-playwright --target <declared-test-target> test ...`
155
+ On a managed host, run the host's launcher for the declared test target
156
156
  with the same file/case selection and output redirection. Do not replace the
157
157
  managed launcher with the unmanaged example.
158
158
 
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/site/guide/framework/material-3.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/site/guide/framework/material-3.md`. -->
2
2
 
3
3
  # Material 3
4
4
 
@@ -97,7 +97,7 @@ Run this behavior-preserving audit over every affected screen:
97
97
  3. Check hierarchy, token use, component purpose, responsive presentation,
98
98
  interaction states, keyboard/focus behavior, labels, targets, contrast, and
99
99
  reduced motion.
100
- 4. Audit Back on every affected page using [Back navigation](./ui-operations.md#back-navigation):
100
+ 4. Audit Back on every affected page using [Back navigation](ui-operations.md#back-navigation):
101
101
  verify its presence or justified omission, actual previous-page destination,
102
102
  safe direct-entry fallback, and retained origin context. Inspect handlers and
103
103
  route bindings; a hardcoded parent/list link is an explicit exception, not
@@ -1,4 +1,4 @@
1
- <!-- Generated from `packages/agent-docs/site/guide/app-setup/multi-homing.md` by `npm run agent-docs:build`. -->
1
+ <!-- Generated from `packages/agent-docs/site/guide/app-setup/multi-homing.md`. -->
2
2
 
3
3
  # Workspace tenancy
4
4
 
@@ -58,13 +58,32 @@ config.tenancyPolicy = {
58
58
  Provisioning runs in the users profile-sync lifecycle after registration and
59
59
  sign-in. The first run creates the personal workspace, its owner membership and
60
60
  its settings; later runs reuse it. The default name is the user's display name,
61
- or email, followed by "'s Workspace". Set `personalWorkspaceName` in
62
- `config/server.js` to choose another; it receives the synchronized profile
63
- (`id`, `displayName`, `email`, `username`), and an empty result keeps the default:
61
+ or email, followed by "'s Workspace". To choose another, provide the
62
+ `workspaces.personal-workspace-name` capability from an application feature. It
63
+ receives the synchronized profile (`id`, `displayName`, `email`, `username`), and
64
+ an empty result keeps the default:
64
65
 
65
66
  ```js
66
- // config/server.js
67
- config.personalWorkspaceName = (user) => `${user.displayName || user.email}'s home`;
67
+ import { defineFeature } from "@jskit-ai/kernel/server/features";
68
+ import { homeWorkspaceName } from "./homeWorkspaceName.js";
69
+
70
+ const AppPersonalWorkspaceNameFeature = defineFeature({
71
+ id: "app.personal-workspace-name",
72
+ provides: {
73
+ personalWorkspaceName: "workspaces.personal-workspace-name"
74
+ },
75
+ setup: () => ({ personalWorkspaceName: homeWorkspaceName })
76
+ });
77
+ ```
78
+
79
+ ```js
80
+ // homeWorkspaceName.js
81
+ /** @type {import("@jskit-ai/workspaces-core/server/types").PersonalWorkspaceName} */
82
+ function homeWorkspaceName(user) {
83
+ return `${user.displayName || user.email}'s home`;
84
+ }
85
+
86
+ export { homeWorkspaceName };
68
87
  ```
69
88
 
70
89
  ### Development sign-in
@@ -103,8 +122,13 @@ before an operation discloses records or record existence.
103
122
 
104
123
  For a standard CRUD resource owned by a workspace, give its table the
105
124
  `workspace_id` column, set the resource's `autofilter: "workspace"`, and bind
106
- `defineCrudJsonApiFeature()` with `ownershipFilter: "workspace"` on a workspace
107
- surface. JSKIT then filters `workspaceId` by the workspace in the route.
125
+ `defineCrudJsonApiFeature()` with `ownershipFilter: "workspace"` and `scope:
126
+ WORKSPACE_CRUD_SCOPE` (from `@jskit-ai/workspaces-core/server/support/workspaceCrud`)
127
+ on a workspace surface. JSKIT then filters `workspaceId` by the workspace in the
128
+ route. A repository method you add reads the workspace id with
129
+ `resolveWorkspaceScopeValue(options.context)`
130
+ (`@jskit-ai/json-rest-api-core/server/jsonRestApiHost`), which throws when the
131
+ context names two workspaces.
108
132
  `workspace_user` also filters by the signed-in user. See the CRUD operations
109
133
  guide.
110
134
 
@@ -152,14 +176,31 @@ capability provides:
152
176
 
153
177
  ### Invitation email
154
178
 
155
- To send invitation email, set a mailer in `config/server.js`. Without one,
156
- invitations are still created and delivery reports `mailer_unconfigured`.
157
- `workspaceInviteMailer` is an object whose `sendWorkspaceInvite(message,
158
- options)` receives `{ email, inviteUrl, workspace, inviter, roleSid, expiresAt,
159
- message }`, where the inner `message` is the rendered `{ subject, text, html }`.
160
- `workspaceInviteEmailTemplate` is an optional function that renders that inner
161
- message; it receives `{ email, inviteUrl, workspace, inviter, roleSid,
162
- roleLabel, expiresAt }` (`roleLabel` only when configured). Invitation links use `/invite/:token` unless
179
+ To send invitation email, provide the optional `workspaces.invite-mailer`
180
+ capability from one of the application's providers. Without one, invitations
181
+ are still created and delivery reports `mailer_unconfigured`. The mailer, typed
182
+ `WorkspaceInviteMailer` in `@jskit-ai/workspaces-core/server/types`, is an
183
+ object whose `sendWorkspaceInvite(message, options)` receives `{ email,
184
+ inviteUrl, workspace, inviter, roleSid, expiresAt, message }`, where the inner
185
+ `message` is the rendered `{ subject, text, html }`:
186
+
187
+ ```js
188
+ const InviteMailerProvider = defineProvider({
189
+ id: "app.invite-mailer",
190
+ provides: {
191
+ inviteMailer: "workspaces.invite-mailer"
192
+ },
193
+ setup() {
194
+ return { inviteMailer: createAppInviteMailer() };
195
+ }
196
+ });
197
+ ```
198
+
199
+ The optional `workspaces.invite-email-template` capability is a function that
200
+ renders that inner message; it receives `{ email, inviteUrl, workspace, inviter,
201
+ roleSid, roleLabel, expiresAt, locale, t }` (`roleLabel` only when configured).
202
+ Provide it from an application feature, as for the mailer; without it the
203
+ built-in template renders the message. Invitation links use `/invite/:token` unless
163
204
  `workspaceInvitations.invitePath` names another path. The
164
205
  `workspaces/workspace-server` pattern includes an example renderer.
165
206
 
@@ -252,8 +293,8 @@ authorization and role checks. Do not pass arbitrary HTTP input as options.
252
293
  | `workspaces.repositories.workspaceInvites.lockWorkspaceForInvitations(workspaceId, { trx })` | Locks the workspace before application receipt/draft rows when composing preparation. Requires an active transaction. |
253
294
  | `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. |
254
295
  | `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. |
255
- | `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`; retains the ordinary response shape. Rejects `options.trx`. |
256
- | `workspaces.services.pendingInvitations.registerAcceptanceParticipant(fn)` | Registers one application callback receiving `({ invite, user }, options)`. Invalid, duplicate and late registration reject. |
296
+ | `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`. |
297
+ | `workspaces.invite-acceptance-participant` (optional capability) | One application function receiving `({ invite, user }, options)` inside the acceptance transaction. Without it, acceptance activates the membership only. |
257
298
 
258
299
  `sendInvite` accepts `{ createdInviteId, inviteTokenPreview }` from preparation or
259
300
  only `{ createdInviteId }` for a later attempt. The latter uses JSKIT's existing
@@ -266,30 +307,34 @@ diagnostics and administrative receipts; save the invitation ID in the receipt.
266
307
 
267
308
  ### Preparation, association, commit and controlled delivery
268
309
 
269
- Declare `workspaces: "workspaces.core"` in the application's feature `requires`.
270
- In this example, `reviewedPeople`, `people`, `scopes` and `audit` are application
271
- services. Each forwards the supplied transaction to its repositories.
310
+ The acceptance participant comes from a small application feature that provides
311
+ `workspaces.invite-acceptance-participant` and requires only what its writes need.
312
+ It cannot require `workspaces.core`, which receives it. In this example,
313
+ `reviewedPeople`, `people`, `scopes` and `audit` are application services. Each
314
+ forwards the supplied transaction to its repositories.
272
315
 
273
316
  ```js
274
- // During application Feature.setup, before serving requests:
275
- const { members, pendingInvitations } = workspaces.services;
276
- const invites = workspaces.repositories.workspaceInvites;
277
-
278
- pendingInvitations.registerAcceptanceParticipant(async ({ invite, user }, options) => {
279
- // Resolve from the exact validated invitation, never a client person ID.
280
- const association = await reviewedPeople.resolveInvitation(invite.id, options);
281
- if (association.kind === "ordinary") return;
282
- await reviewedPeople.requireCurrentReview(association, {
283
- workspaceId: invite.workspaceId,
284
- user,
285
- roleSid: invite.roleSid
286
- }, options);
287
- await people.linkAccountAndTraining(association.personId, user.id, options);
288
- await scopes.applyReviewedAccess(association, user.id, options);
289
- await audit.recordInvitationAcceptance(association, user.id, options);
290
- });
317
+ // An application feature providing "workspaces.invite-acceptance-participant":
318
+ setup: ({ reviewedPeople, people, scopes, audit }) => ({
319
+ inviteAcceptanceParticipant: async ({ invite, user }, options) => {
320
+ // Resolve from the exact validated invitation, never a client person ID.
321
+ const association = await reviewedPeople.resolveInvitation(invite.id, options);
322
+ if (association.kind === "ordinary") return;
323
+ await reviewedPeople.requireCurrentReview(association, {
324
+ workspaceId: invite.workspaceId,
325
+ user,
326
+ roleSid: invite.roleSid
327
+ }, options);
328
+ await people.linkAccountAndTraining(association.personId, user.id, options);
329
+ await scopes.applyReviewedAccess(association, user.id, options);
330
+ await audit.recordInvitationAcceptance(association, user.id, options);
331
+ }
332
+ })
291
333
 
292
- // Inside an authorized application command:
334
+ // Inside an authorized application command, in a feature that requires
335
+ // workspaces: "workspaces.core":
336
+ const { members } = workspaces.services;
337
+ const invites = workspaces.repositories.workspaceInvites;
293
338
  const prepared = await invites.withTransaction(async (trx) => {
294
339
  const options = { trx, context };
295
340
  // Match acceptance's order: workspace, then application draft/receipt rows.
@@ -316,23 +361,24 @@ invitation. Association failure rolls back the new invitation, replacement of
316
361
  the previous invitation, receipt and application writes. Send only after the
317
362
  transaction resolves successfully.
318
363
 
319
- With no `workspaceInviteMailer`, delivery returns `status: "mailer_unconfigured"`,
320
- an explanatory `message` and an empty `providerMessageId`; the invitation remains
321
- committed. Use a controlled mailer in tests; SMTP is unnecessary:
364
+ With no provider of `workspaces.invite-mailer`, delivery returns
365
+ `status: "mailer_unconfigured"`, an explanatory `message` and an empty
366
+ `providerMessageId`; the invitation remains committed. Provide a controlled
367
+ mailer in tests; SMTP is unnecessary:
322
368
 
323
369
  ```js
324
- workspaceInviteMailer: {
370
+ const inviteMailer = {
325
371
  async sendWorkspaceInvite(message) {
326
372
  capturedInvites.push(message); // Test-local memory; contains the secret link.
327
373
  return { status: "sent", message: "Captured by test mailer.", providerMessageId: "fixture-1" };
328
374
  }
329
- }
375
+ };
330
376
  ```
331
377
 
332
378
  Renderer/mailer exceptions return `inviteDelivery.status: "failed"` with a fixed
333
379
  message to keep provider errors from exposing tokens. Persistence is unchanged.
334
- Successful mailer results retain their normalized status, message and provider
335
- message ID; the default status is `sent`.
380
+ Successful mailer results retain their normalized status, message, message
381
+ code and provider message ID; the default status is `sent`.
336
382
 
337
383
  A provider may accept a message before throwing or losing its response. The
338
384
  application must track delivery attempts and decide when to retry; email is not
@@ -377,7 +423,7 @@ Preserve `error.transactionOutcome` and `error.cause`: `rolledBack` confirms
377
423
  rollback, `committed` can accompany a failed completion hook, and `unknown`
378
424
  requires resolving the outcome before retrying. Errors after commit cannot undo
379
425
  persistence. See
380
- [managed transaction guidance](./existing-application-migration.md#json-rest-v2-integration).
426
+ [managed transaction guidance](existing-application-migration.md#json-rest-v2-integration).
381
427
 
382
428
  Invitation mutations lock workspace before invitation rows. A losing acceptance,
383
429
  refusal, revocation or replacement cannot overwrite accepted/revoked state.
@@ -411,9 +457,9 @@ npm run verify
411
457
  ```
412
458
 
413
459
  Run the application's `npm run jskit:update`, restart its backend and verify that
414
- `members.prepareInvite`, `members.sendInvite` and
415
- `pendingInvitations.registerAcceptanceParticipant` are functions on the installed
416
- `workspaces.core` capability. The first release containing this contract pairs
460
+ `members.prepareInvite` and `members.sendInvite` are functions on the installed
461
+ `workspaces.core` capability, and that the application feature providing
462
+ `workspaces.invite-acceptance-participant` is installed. The first release containing this contract pairs
417
463
  `@jskit-ai/workspaces-core@0.1.198`, `@jskit-ai/jskit-catalog@0.1.245` and
418
464
  `@jskit-ai/agent-docs@0.1.192`. Upgrade the coordinated dependency graph, then run
419
465
  the application's identity, training, permission, privacy and browser tests.