create-flowdular 0.2.4 → 0.2.5

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 (269) hide show
  1. package/README.md +11 -0
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
  4. package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
  5. package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
  6. package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
  7. package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
  8. package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
  9. package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
  10. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  11. package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
  12. package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
  13. package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
  14. package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
  15. package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
  16. package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
  17. package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
  18. package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
  19. package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
  20. package/agent-template/.agents/skills/variables/SKILL.md +156 -0
  21. package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
  22. package/agent-template/.ai/README.md +62 -0
  23. package/agent-template/.ai/agents/README.md +27 -0
  24. package/agent-template/.ai/agents/module-executor.md +36 -0
  25. package/agent-template/.ai/agents/reviewer.md +23 -0
  26. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
  27. package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
  28. package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
  29. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
  30. package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
  31. package/agent-template/.ai/agents/spec-author.md +29 -0
  32. package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
  33. package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  34. package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
  35. package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  36. package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  37. package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  38. package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  39. package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
  40. package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
  41. package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
  42. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
  43. package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  44. package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
  45. package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
  46. package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  47. package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
  48. package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  49. package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  50. package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
  51. package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
  52. package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
  53. package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
  54. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  55. package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
  56. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  57. package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
  58. package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  59. package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
  60. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  61. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  62. package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  63. package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  64. package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
  65. package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
  66. package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  67. package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  68. package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  69. package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
  70. package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
  71. package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  72. package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
  73. package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  74. package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  75. package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  76. package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  77. package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
  78. package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
  79. package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
  80. package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
  81. package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  82. package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
  83. package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
  84. package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  85. package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
  86. package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  87. package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  88. package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  89. package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  90. package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
  91. package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
  92. package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
  93. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
  94. package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  95. package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
  96. package/agent-template/.ai/blueprints/new-module/README.md +7 -0
  97. package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  98. package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
  99. package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  100. package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  101. package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  102. package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  103. package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
  104. package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
  105. package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
  106. package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
  107. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  108. package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
  109. package/agent-template/.ai/blueprints/release/README.md +5 -0
  110. package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
  111. package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
  112. package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  113. package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  114. package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
  115. package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
  116. package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
  117. package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
  118. package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
  119. package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
  120. package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
  121. package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
  122. package/agent-template/.ai/blueprints/security-review/README.md +5 -0
  123. package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  124. package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
  125. package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  126. package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  127. package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  128. package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  129. package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
  130. package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
  131. package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
  132. package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
  133. package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  134. package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
  135. package/agent-template/.ai/examples/README.md +8 -0
  136. package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
  137. package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
  138. package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
  139. package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
  140. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
  141. package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
  142. package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
  143. package/agent-template/.ai/examples/client-contribution/README.md +11 -0
  144. package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
  145. package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
  146. package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
  147. package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
  148. package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
  149. package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
  150. package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
  151. package/agent-template/.ai/guides/application-development.md +97 -0
  152. package/agent-template/.ai/policies/capabilities.yaml +164 -0
  153. package/agent-template/.ai/policies/model-routing.yaml +72 -0
  154. package/agent-template/.ai/policies/path-ownership.yaml +65 -0
  155. package/agent-template/.ai/policies/task-budgets.yaml +37 -0
  156. package/agent-template/.ai/references/catalog/LICENSE +21 -0
  157. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  158. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  159. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  160. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  161. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  162. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  163. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  164. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  165. package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
  166. package/agent-template/.ai/references/catalog/module.json +27 -0
  167. package/agent-template/.ai/references/catalog/package.json +49 -0
  168. package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
  169. package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
  170. package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
  171. package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
  172. package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  173. package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  174. package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  175. package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
  176. package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  177. package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
  178. package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  179. package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
  180. package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
  181. package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
  182. package/agent-template/.ai/references/catalog/src/index.ts +31 -0
  183. package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
  184. package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
  185. package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
  186. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  187. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
  188. package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
  189. package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
  190. package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
  191. package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  192. package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  193. package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  194. package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  195. package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
  196. package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
  197. package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
  198. package/agent-template/.ai/references/catalog/translations/en.json +101 -0
  199. package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
  200. package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
  201. package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
  202. package/agent-template/.ai/references/catalog.provenance.json +55 -0
  203. package/agent-template/.ai/rules/flowdular.md +86 -0
  204. package/agent-template/.ai/skills/README.md +36 -0
  205. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
  206. package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
  207. package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
  208. package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
  209. package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
  210. package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
  211. package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
  212. package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
  213. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  214. package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
  215. package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
  216. package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
  217. package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
  218. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
  219. package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
  220. package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
  221. package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
  222. package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
  223. package/agent-template/.ai/skills/variables/SKILL.md +164 -0
  224. package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
  225. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
  226. package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
  227. package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
  228. package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
  229. package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
  230. package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
  231. package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
  232. package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
  233. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  234. package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
  235. package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
  236. package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
  237. package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
  238. package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
  239. package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
  240. package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
  241. package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
  242. package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
  243. package/agent-template/.claude/skills/variables/SKILL.md +156 -0
  244. package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
  245. package/agent-template/AGENTS.md +77 -0
  246. package/agent-template/CLAUDE.md +77 -0
  247. package/agent-template/docs/adr/0001-development-reload.md +16 -0
  248. package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
  249. package/agent-template/docs/adr/0003-module-settings.md +22 -0
  250. package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
  251. package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
  252. package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
  253. package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
  254. package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
  255. package/agent-template/docs/agent-contract.md +45 -0
  256. package/agent-template/docs/configuration.md +122 -0
  257. package/agent-template/docs/database-adapters.md +346 -0
  258. package/agent-template/docs/design-system.md +217 -0
  259. package/agent-template/docs/modules.md +146 -0
  260. package/agent-template/platform/scripts/build.mjs +38 -0
  261. package/agent-template/rulesync.jsonc +11 -0
  262. package/dist/bin.js +3 -1
  263. package/package.json +3 -2
  264. package/template/default/.prettierignore +9 -0
  265. package/template/default/README.md +12 -0
  266. package/template/default/flowdular.json +3 -3
  267. package/template/default/package.json +6 -2
  268. package/template/default/platform/octane.config.ts +17 -6
  269. package/template/default/platform/package.json +2 -1
@@ -0,0 +1,105 @@
1
+ # Adapter setup and delivery matrix
2
+
3
+ Read this reference when adding a driver adapter, exposing database setup, or
4
+ preparing an adapter-backed module for eject or a pull request.
5
+
6
+ ## Adapter descriptor
7
+
8
+ A platform adapter descriptor owns the complete first-run contract:
9
+
10
+ - an open `adapterId` and `dialectId`;
11
+ - a machine-readable configuration schema;
12
+ - UI hints such as labels, descriptions, groups, order, and input kind;
13
+ - an explicit list of secret fields;
14
+ - configuration validation without opening module databases;
15
+ - a connectivity probe with bounded time and sanitized errors;
16
+ - optional provisioning for infrastructure the adapter actually owns;
17
+ - connection creation that returns the shared adapter contract;
18
+ - advertised database capabilities.
19
+
20
+ The registry is open. Core code resolves a descriptor by id and negotiates its
21
+ capabilities rather than switching over known adapter ids, so an adapter package
22
+ can join without changing module business ports or the setup screen.
23
+
24
+ Configuration UI is generated by the platform from descriptor metadata. A
25
+ module never implements database setup UI and never asks for a DSN.
26
+
27
+ ## First run
28
+
29
+ The embedded PostgreSQL is the zero-configuration local default: PGlite runs in
30
+ process, so a workstation starts with nothing installed and still exercises the
31
+ same forced row-level security a deployment enforces. Its data root comes from
32
+ the platform provider, not from each module.
33
+
34
+ Before selecting another adapter, setup:
35
+
36
+ 1. validates descriptor configuration and required secrets;
37
+ 2. probes the target with a strict timeout and no secret echo;
38
+ 3. lists every enabled database-owning module;
39
+ 4. verifies that each module declares an implementation for the target
40
+ `dialectId` and that the adapter supplies its required capabilities;
41
+ 5. refuses activation with the exact incompatible modules and capabilities;
42
+ 6. provisions only after explicit operator confirmation when provisioning
43
+ changes external state;
44
+ 7. runs migrations through a migration lease before runtime leases are served;
45
+ 8. verifies readiness under the non-bypass runtime role.
46
+
47
+ Secret values live in environment-backed or encrypted platform secret storage.
48
+ `flowdular.json`, module manifests, setup responses, logs, audit metadata, run
49
+ snapshots, and pull request descriptions contain only secret references or
50
+ redacted presence state.
51
+
52
+ ## Switching an adapter with existing data
53
+
54
+ Changing an adapter id never points modules at an empty database and never
55
+ copies storage files. The only supported switch is an explicit operation:
56
+
57
+ 1. stop writes and acquire an export snapshot;
58
+ 2. export through module-owned portable records, preserving ids and versions;
59
+ 3. provision and migrate the target;
60
+ 4. import in dependency-safe batches under tenant context;
61
+ 5. verify counts, checksums, referential expectations, and module invariants;
62
+ 6. probe reads and writes through the target runtime role;
63
+ 7. activate the target only after verification succeeds;
64
+ 8. retain the source for rollback until the operator closes the window.
65
+
66
+ A failed import or verification leaves the source active. There is no silent
67
+ fallback to another database, because that would split writes.
68
+
69
+ ## Module declarations
70
+
71
+ Each database-owning module declares:
72
+
73
+ - supported open dialect ids;
74
+ - the required capability set, such as transactions, transactional DDL,
75
+ schema introspection, returning values, or an isolation level;
76
+ - its module-owned persistence implementation and migrations;
77
+ - one adapter-neutral repository behavior suite.
78
+
79
+ The platform compatibility check consumes these declarations before first run,
80
+ adapter change, sandbox eject, and deployment validation.
81
+
82
+ ## Delivery matrix
83
+
84
+ Every turn runs against a real PostgreSQL, because the embedded one starts in
85
+ process. The sandbox and the test suites use `createTestDatabaseProvider()` from
86
+ `@flowdular/sdk/database-testing`, which brings the `coreloom_runtime` and
87
+ `coreloom_background` roles and forced row-level security with it.
88
+
89
+ A target run covers tenant A and B fixtures, operations without tenant context,
90
+ forged cross-tenant inserts, direct row-security bypass probes, concurrent
91
+ writes, and cleanup. Where a module polls across tenants, it also proves the
92
+ background role reads only the routing columns.
93
+
94
+ Before eject or a pull request the matrix additionally runs the same suite
95
+ against a server PostgreSQL when one is configured, using a unique schema and a
96
+ role that holds neither `SUPERUSER` nor `BYPASSRLS`. Test credentials are
97
+ short-lived and never enter the transcript. If that target is unavailable the
98
+ session may continue editing, and delivery states the exact missing target.
99
+
100
+ Server selection is explicit: `FD_TEST_DATABASE_ADAPTER=postgresql`,
101
+ `FD_TEST_POSTGRES_URL` (migrator), `FD_TEST_POSTGRES_RUNTIME_URL` and
102
+ `FD_TEST_POSTGRES_BACKGROUND_URL`. All three URLs are required. Missing or
103
+ unreachable configuration never falls back to PGlite. `.github/workflows/ci.yml`
104
+ runs this matrix, including auth and agents. Keep role separation intact in
105
+ fixtures: even the migration owner must supply tenant context for tenant data.
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: migration-authoring
3
+ description: >-
4
+ Add an immutable PostgreSQL module migration through @flowdular/sdk/database,
5
+ with the namespaced v2 ledger, safe schema adoption, forced row-level
6
+ security, and tenant isolation tests.
7
+ ---
8
+ # Author a database migration
9
+
10
+ An adapter conversion is a separate phase using `database-adapter`. For this phase, read
11
+ `packages/database/src/migrations.ts` and the converted `modules/profile`
12
+ migration as the reference. Flowdular has a migration runner. Do not add
13
+ constructor-owned `DatabaseSync.exec()` guards around it.
14
+
15
+ ## 1. Source layout and compatibility
16
+
17
+ Migration SQL is checked in and immutable after release:
18
+
19
+ - `migrations/NNNN_<module>_<name>.up.sql` holds the PostgreSQL source and is
20
+ byte-exact. Never move, renumber or reformat a released file.
21
+ - `src/services/migration.ts` mirrors every `.up.sql` file as a literal and
22
+ exports `databaseMigrations: readonly DatabaseMigration[]`, one entry per
23
+ file. No code translates or rewrites SQL.
24
+ - `.down.sql` documents a reverse operation. Flowdular never executes it.
25
+
26
+ ## 2. What the v2 runner guarantees
27
+
28
+ `runDatabaseMigrations(database, namespace, databaseMigrations)` uses the
29
+ namespaced `_coreloom_migrations_v2` ledger. A row records namespace, migration
30
+ id, dialect id, checksum, and applied time. The checksum covers the selected
31
+ dialect's exact SQL.
32
+
33
+ Before applying outstanding work, the runner checks every existing ledger row.
34
+ It refuses checksum drift, a missing script, duplicate ids, a wrong ledger
35
+ dialect, partial adoption, and adapters without transactional DDL. It opens one
36
+ serializable transaction, takes a transaction advisory lock, and commits DDL
37
+ plus ledger rows together. Dry run writes nothing.
38
+
39
+ The runner selects scripts by the provider's open `dialectId`. Do not add a core
40
+ switch over known dialects. A new driver advertises capabilities and a module
41
+ opts into it by supplying reviewed SQL and tests.
42
+
43
+ ## 3. Explicit adoption proof
44
+
45
+ Every migration that may predate v2 defines `inspectExisting(database)`. Use
46
+ adapter-owned, capability-checked schema introspection such as `hasTable`,
47
+ `hasColumn`, and `hasIndex` with fixed identifiers.
48
+
49
+ Return `complete` only when every table, column, index, constraint, data effect,
50
+ and security policy exists. Return `absent` only when none exists. Return
51
+ `partial` for every mixed state. If an adapter cannot inspect a required object,
52
+ extend its introspection capability or supply a narrow module-owned inspection.
53
+ Never guess `complete` and never parse another dialect's catalog directly in
54
+ shared code.
55
+
56
+ ## 4. SQL rules shared by dialects
57
+
58
+ - Tenant tables carry `tenant_id TEXT NOT NULL`.
59
+ - Tenant uniqueness and lookup indexes start with `tenant_id`; ordered indexes
60
+ end with `id` for stable results.
61
+ - IDs are text UUIDs generated by the service. Times are integer milliseconds.
62
+ - Money is integer minor units plus a currency code, never floating point.
63
+ - Enums and ranges have database check constraints.
64
+ - Cross-module foreign keys do not exist. Use the owner's capability or API.
65
+ - Additive changes are the default. Destructive or locking changes need an
66
+ operator-approved rollout, compatibility window, and rollback plan.
67
+
68
+ Write PostgreSQL directly: its types, conflict syntax, indexes and policies.
69
+ Never rewrite placeholders or DDL text.
70
+
71
+ ## 5. PostgreSQL row-level security
72
+
73
+ Every PostgreSQL tenant table includes:
74
+
75
+ ```sql
76
+ ALTER TABLE inventory_locations ENABLE ROW LEVEL SECURITY;
77
+ ALTER TABLE inventory_locations FORCE ROW LEVEL SECURITY;
78
+ CREATE POLICY inventory_locations_tenant_policy ON inventory_locations
79
+ USING (tenant_id = current_setting('coreloom.tenant_id', true))
80
+ WITH CHECK (tenant_id = current_setting('coreloom.tenant_id', true));
81
+ ```
82
+
83
+ The runtime role is not a superuser and has no `BYPASSRLS`. DDL and policy
84
+ ownership use `purpose: 'migration'`. Runtime repository calls use
85
+ `database.transaction(operation, { tenantId, access })`; the adapter sets
86
+ transaction-local `coreloom.tenant_id` on the pinned connection. Queries still
87
+ include `WHERE tenant_id = ...` as defense in depth.
88
+
89
+ ## 6. Add one migration
90
+
91
+ Scaffold instead of writing from memory:
92
+
93
+ ```bash
94
+ pnpm flowdular migration new <name> --module <id> # dry run
95
+ pnpm flowdular migration new <name> --module <id> --apply # writes both files
96
+ ```
97
+
98
+ The scaffold emits the `.up.sql` and `.down.sql` pair with the tenant table, its
99
+ index, and the `ENABLE` + `FORCE` + policy block already correct. Replace the
100
+ placeholder columns with the real schema; keep the row-security block unless the
101
+ table is not tenant owned.
102
+
103
+ 1. Never renumber released files. The scaffold picks the next id.
104
+ 2. Mirror every `.up.sql` byte for byte and append one `DatabaseMigration` with
105
+ the same id.
106
+ 3. Add exact `inspectExisting` logic with `migrationObjectState` and
107
+ `postgresTenantTableState` from `@flowdular/sdk/database`. Pass thunks, never
108
+ ready promises: adoption runs inside a transaction pinned to one connection,
109
+ and eager promises issue overlapping queries on it.
110
+ 4. Extend repository SQL, row mapping, service validation, endpoint, client,
111
+ approved spec scenario, and all three module versions.
112
+
113
+ ### Column types that bite
114
+
115
+ PostgreSQL returns `BIGINT` as a string. Normalize every integer read in the
116
+ repository through a local `integer()` helper; a raw value concatenates where it
117
+ should add, and `enabled === 1` is false against `'1'`.
118
+
119
+ Widen timestamps, durations and sequences to `BIGINT`. Leave boolean flags,
120
+ counters and version columns `INTEGER`, or every comparison against them has to
121
+ be normalized too.
122
+
123
+ Money is integer minor units in `BIGINT` plus a currency code.
124
+
125
+ Never test against a workspace database. `createTestDatabaseProvider()` from
126
+ `@flowdular/sdk/database-testing` gives the suite its own PGlite by default or an
127
+ isolated PostgreSQL schema in server CI. Tenant fixture reads and writes still
128
+ need transaction-local tenant context, including on the migration connection.
129
+
130
+ ## 7. Tests and gates
131
+
132
+ `pnpm flowdular migration verify` checks the ledger and every module's migration
133
+ set: a missing script, a tenant table without forced row security, and a policy
134
+ lost to a table rebuild.
135
+
136
+ The migration suite proves byte parity and id order, empty apply, complete
137
+ adoption without row loss, partial refusal, checksum refusal before later SQL,
138
+ dry run without writes, and a clean second start.
139
+
140
+ Every gate runs against a real PostgreSQL, because the embedded one starts in
141
+ process. Cover tenant A and B isolation, a forged tenant `WITH CHECK` refusal, a
142
+ missing-context `TENANT_CONTEXT_REQUIRED` test, and direct row-security bypass
143
+ probes under a role that holds neither `SUPERUSER` nor `BYPASSRLS`. Where the
144
+ module polls across tenants, prove the background role reads only the routing
145
+ columns.
146
+
147
+ ## Refuse
148
+
149
+ - Editing, moving, reordering, or removing released migration bytes.
150
+ - A PostgreSQL tenant table without enabled and forced RLS plus both policy
151
+ clauses.
152
+ - A runtime role with superuser or `BYPASSRLS`.
153
+ - DDL through a runtime lease or request data through `executeScript()`.
154
+ - Automatic SQL translation or a closed core switch over known dialects.
155
+ - Forcing past checksum or partial-adoption refusal.
156
+ - A production-support claim based on a fake driver alone.
157
+
158
+ ## Verification
159
+
160
+ Run the module typecheck and tests, module validation,
161
+ `pnpm flowdular migration verify`, and `pnpm verify`.
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: module-new
3
+ description: >-
4
+ Create a Flowdular module from an approved spec, from scaffold to enabled and
5
+ granted, with the file set and APIs .ai/references/catalog uses.
6
+ ---
7
+ # Create a module
8
+
9
+ The reference module is `.ai/references/catalog` (in a sandbox session: `reference/example-module`). When this skill and the code disagree, the code wins; tell the operator.
10
+
11
+ Two ways to land the same module: the sandbox (a brief, specialist turns, gates after every turn, preview, eject) or the direct path (this skill in your own coding tool, the gates by hand, `pnpm verify`, a pull request). The sections below mark the differences.
12
+
13
+ ## 1. Preconditions
14
+
15
+ - `pnpm flowdular doctor --json` reports `status: healthy` (repository root only; the sandbox runs gates for you).
16
+ - `modules/<dir>/spec/module.yaml` exists, validates (`pnpm flowdular spec validate --all --json`) and has `status: approved`. `pnpm flowdular module new` refuses a draft with `A module can only be created from an approved specification.` In the sandbox the operator approves the spec after the business manager's turn, and the orchestrator runs the scaffold itself. Outside the sandbox, a host agent may record approval only after an explicit current user request and only through `spec-approval`.
17
+ - Ids: module id and every permission id match `^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$`. `inventory.core` lives in `modules/inventory` as `@flowdular/module-inventory` (`packages/cli/src/module-scaffold.ts`, `packageSuffix`). Module environment variables use the upper-case directory (`modules/auth` reads `FD_AUTH_SECURE_COOKIE`); the database is platform-owned, so a module never gets one of its own.
18
+
19
+ ## 2. Scaffold
20
+
21
+ ```bash
22
+ pnpm flowdular module new inventory.core --spec modules/inventory/spec/module.yaml # dry run, lists files
23
+ pnpm flowdular module new inventory.core --spec modules/inventory/spec/module.yaml --apply
24
+ ```
25
+
26
+ `packages/cli/src/module-templates.ts` (`planScaffold`) writes a module the generated composition can import, formatted with the workspace Prettier: `module.json` with `platform.{server,client}` from the capabilities, `package.json` with the `.`, `./client`, `./server`, `./platform` exports and pinned versions, `tsconfig.json` with `types: ["node"]`, the spec copy, `src/index.ts`, `src/acl/permissions.ts` (`X_PERMISSIONS` built from the spec `permissions`), `src/domain/types.ts`, `src/services/{repository,<suffix>-service,index}.ts`, `src/api/endpoints.ts`, and then by capability: `database` gives `src/services/{migration,database-repository}.ts` plus the PostgreSQL `migrations/0001_<snake>_core.{up,down}.sql`, otherwise `src/services/memory-repository.ts`; `api` gives `src/server/{runtime,index}.ts` and `src/platform.ts` with `createServerComposition`; `client` gives `src/client/{index.ts,contribution.tsrx,<Pascal>View.tsrx}` plus `api.ts` and `state.ts` when a read permission exists; `cli` gives `src/cli/{commands.json,index.ts}`; always `tests/module.test.ts` (identity and tenant isolation) and `translations/<locale>.json` (`pl` gets the placeholder `Moduł <name>`).
27
+
28
+ The scaffold is PostgreSQL from the first commit. `database-repository.ts`
29
+ exports `Database<Name>Repository` and `migrate<Name>Database`, takes a
30
+ `DatabaseHandle`, wraps every async method in
31
+ `database.transaction(..., { tenantId, access })`, binds `$1`, `$2` parameters,
32
+ and normalizes integer columns through a local `integer()` helper because
33
+ PostgreSQL returns `BIGINT` as a string. `migrations/0001_<snake>_core.up.sql`
34
+ creates the table with forced row-level security and a tenant policy. The
35
+ runtime takes `context.databases`, acquires a `migration` lease, runs the
36
+ migrations, releases it, then acquires the runtime lease requiring
37
+ `DATABASE_DIALECT_IDS.postgresql`. `tests/module.test.ts` runs on
38
+ `createPgliteTestProvider()` and asserts tenant isolation. The business repository port
39
+ stays async and database-agnostic, and persistence stays on
40
+ `@flowdular/sdk/database`.
41
+
42
+ The first entity is the middle segment of the first permission id (`inventory.locations.read` gives `locations`, table `inventory_locations`, type `InventoryLocation`); it gets the list endpoint (`.read`), the create endpoint (`.manage`), the table, the view and the tests. Every other permission becomes a constant in `X_PERMISSIONS` only; its entity is `module-update` work. A directory that already holds `spec/module.yaml` and `translations/**` (the business manager's files) is extended, and those files win over the scaffold's; a directory with sources is refused. In the sandbox the orchestrator runs the scaffold once the spec is approved.
43
+
44
+ What is still yours after the scaffold: the real fields of the entity beyond `name`, validation bounds and stable error codes, uniqueness rules and their indexes, further endpoints and entities, screen columns and the drawer form, tests beyond identity and isolation. A business agent is a separate phase using `business-agent-design`; the scaffold does not invent one.
45
+
46
+ ## 3. Server file set
47
+
48
+ Copy the HTTP and domain layout of `.ai/references/catalog/src`, and the persistence shape of `modules/profile/src/services/database-repository.ts`. Keep three layers: an async database-agnostic repository port, module-owned persistence and PostgreSQL SQL, and the platform-owned driver provider. Exact endpoint signatures:
49
+
50
+ ```ts
51
+ // src/api/endpoints.ts
52
+ import {
53
+ defineEndpoint,
54
+ HttpProblem,
55
+ jsonResponse,
56
+ problemResponse,
57
+ readJsonObject,
58
+ requiredInteger,
59
+ requiredString,
60
+ } from '@flowdular/sdk/server';
61
+ import type { AuthRuntime } from '@flowdular/sdk/modules/auth/server';
62
+ import {
63
+ endpointIdentityFromContext,
64
+ principalFromContext,
65
+ sessionMutationDenial,
66
+ } from '@flowdular/sdk/modules/auth/server';
67
+
68
+ const create = defineEndpoint({
69
+ id: 'inventory.locations.create',
70
+ path: '/api/inventory/locations',
71
+ methods: ['POST'],
72
+ access: { kind: 'permission', permission: INVENTORY_PERMISSIONS.manage },
73
+ resolveIdentity: endpointIdentityFromContext,
74
+ handler: async ({ octane }) => {
75
+ const denial = sessionMutationDenial(octane, auth);
76
+ if (denial) return denial;
77
+ try {
78
+ const value = await readJsonObject(octane.request);
79
+ const input = { code: requiredString(value, 'code', { max: 32 }) };
80
+ const tenantId = principalFromContext(octane)!.tenantId;
81
+ return jsonResponse(
82
+ { location: await runtime.service().create(tenantId, input) },
83
+ 201,
84
+ );
85
+ } catch (error) {
86
+ return failure(error);
87
+ }
88
+ },
89
+ });
90
+ ```
91
+
92
+ `createXRoutes(auth: AuthRuntime, runtime: XRuntime)` returns `[list.serverRoute, create.serverRoute] as const`. `src/platform.ts` (the scaffold writes this; extend it):
93
+
94
+ ```ts
95
+ import type {
96
+ PlatformServerComposition,
97
+ PlatformServerContext,
98
+ } from '@flowdular/sdk/modules/auth/server';
99
+ export function createServerComposition(
100
+ context: PlatformServerContext,
101
+ ): PlatformServerComposition {
102
+ const runtime = createInventoryRuntime({ databases: context.databases });
103
+ return {
104
+ routes: createInventoryRoutes(context.auth, runtime),
105
+ dispose: () => runtime.dispose(),
106
+ };
107
+ }
108
+ ```
109
+
110
+ `PlatformServerContext` also carries `databases: DatabaseProvider`, `settings: ModuleSettingsRuntime`, `agentTools: PlatformToolRegistry`, `agentDefinitions: PlatformAgentRegistry`, and `capabilities: PlatformCapabilityRegistry`. The runtime shares one lazy database initialization, uses separate migration and runtime leases, and releases the runtime lease from `dispose()`; `prepare()` remains read-only. A composition may return module settings, lifecycle hooks, agent registrations, and typed cross-module capabilities as described in the focused skills.
111
+
112
+ Schema: write PostgreSQL SQL in `migrations/0001_inventory_core.up.sql` and mirror it byte for byte in `databaseMigrations` as `sql: { postgresql: ... }` with an `inspectExisting` built from `postgresTenantTableState(...)`. Tenant tables enable and force row-level security with an `<table>_tenant_policy` whose `USING` and `WITH CHECK` compare `tenant_id` with `current_setting('coreloom.tenant_id', true)`; the runtime role has no superuser or `BYPASSRLS`. Repository operations use `database.transaction(..., { tenantId, access })` and retain explicit tenant predicates. Details in `migration-authoring` and `database-adapter`.
113
+
114
+ Errors: `{ error: { code, message } }`; service errors `class XServiceError extends Error { constructor(readonly code: string, message: string, readonly status = 400) }`; a `failure(error)` helper routes them to `jsonResponse(..., error.status)` and everything else to `problemResponse(error, 'The <module> operation failed.')`.
115
+
116
+ ## 4. Client file set
117
+
118
+ `src/client/{index.ts,contribution.tsrx,api.ts,state.ts,XView.tsrx,XForm.tsrx}`. The canonical entry:
119
+
120
+ ```ts
121
+ // src/client/index.ts
122
+ import type {
123
+ ModuleClientContext,
124
+ ModuleClientContribution,
125
+ } from '@flowdular/sdk/client';
126
+ import { createInventoryClientContribution as canonicalContribution } from './contribution.tsrx';
127
+ export function createClientContribution(
128
+ context: ModuleClientContext,
129
+ ): ModuleClientContribution {
130
+ return canonicalContribution({ csrfToken: context.csrfToken });
131
+ }
132
+ ```
133
+
134
+ Contribution rules (`packages/client/src/contributions.ts`): `navigation[].group` in `Workspace`, `Operations`, `Agents`, `Administration`, `Development` (`Agents` only with an `agents.core` dependency, `Development` is owner-only in the shell); `widgets[].slot` in `WORKSPACE_SLOTS` (`dashboard.metrics`, `dashboard.main`, `dashboard.aside`, `topbar.actions`); `glyph` an `ICON_PATHS` key (`packages/ui/src/icons/Icon.tsrx`, list in `ux-design`); ids `<module>.navigation`, `<module>.dashboard.<name>`, view id equals the URL slug; `accountMenu` for personal screens (`modules/profile`). Duplicate ids or a navigation entry pointing at a missing view throw at boot.
135
+
136
+ State and data: `useMemo(() => createXClientState(), [])` per component, `cell<T>()` for typed fields, `const [items] = useValue(state.items)`, `store.act((transaction) => transaction.set(state.items, records), 'inventory/loaded')`. Mutations send `content-type: application/json`, `x-csrf-token`, `credentials: 'same-origin'`. `Kpi.value` is a string. Screen and form pattern: `ux-design`.
137
+
138
+ ## 5. Tests and local gates
139
+
140
+ `tests/module.test.ts` (vitest): identity, tenant isolation and uniqueness against a `createPgliteTestProvider()` lease, and one denial per endpoint through `route.handler(createContext(request, {}))` (`test-hardening`). Then, from the repository root:
141
+
142
+ ```bash
143
+ pnpm --filter @flowdular/module-inventory typecheck # tsrx-tsc --noEmit -p tsconfig.json
144
+ pnpm --filter @flowdular/module-inventory test # vitest run
145
+ pnpm flowdular module validate --json
146
+ pnpm flowdular spec validate --all --json
147
+ pnpm format:check
148
+ ```
149
+
150
+ `module validate` (`packages/cli/src/module-validate.ts`) also checks the composition contract: `PLATFORM_SERVER_ENTRY_MISSING` and `PLATFORM_EXPORT_MISSING` (no `src/platform.ts` or `./platform` export behind `platform.server`), `PLATFORM_CLIENT_ENTRY_MISSING` and `PLATFORM_CLIENT_EXPORT_MISSING`, `PACKAGE_NAME_MISMATCH`, `SPEC_ID_MISMATCH`, `TRANSLATION_FILE_MISSING`, `TRANSLATION_KEYS_MISMATCH`, and the warnings `SPEC_VERSION_DRIFT` and `LOCALE_NOT_IN_PROJECT`.
151
+
152
+ Sandbox gates (`packages/sandbox/src/server/gates.ts`): `spec-schema` and `module-schema` once per session workspace; `dependencies`, `typecheck`, `tests` (`vitest run --passWithNoTests`, so no tests still passes) and `format` (`prettier --check .`) once per draft module with the module's own binaries. The session workspace is a real pnpm workspace that installs what each draft `package.json` declares, so an undeclared import fails the `dependencies` gate, which runs after every turn that changed files. A driver with a shell may run the same commands from the module directory; the sandbox runs them again after the turn and feeds a failure back into the fix prompt.
153
+
154
+ ## 6. Enable
155
+
156
+ ```bash
157
+ pnpm flowdular module enable inventory.core --apply
158
+ ```
159
+
160
+ One command (`packages/cli/src/runner.ts`, `module enable`): adds the id to `flowdular.json` `modules.enabled`, adds the package to `platform/package.json`, runs `pnpm install` when the package is not linked, regenerates `platform/src/generated/modules.{server,client}.ts`, and then runs `auth sync-scopes` for the module, reporting the grant as `scopes` in the result; a failed grant is `MODULE_SCOPES_SYNC_FAILED` with the module already enabled. Never edit those files by hand. `pnpm flowdular auth sync-scopes --module inventory.core --apply` stays the way to re-grant later (a new permission, a new owner, a deployment database): it grants the spec's `permissions[].id` to the owners of every tenant (`modules/auth/src/services/auth-service.ts`, `grantModuleScopes`). Members never receive new scopes automatically; a module bundled with the platform also adds its scopes to `BUNDLED_MODULE_SCOPES` and, for read scopes, `MEMBER_SCOPES` in `modules/auth/src/acl/scopes.ts` (a core change). The sandbox eject runs enable for each new module and sync-scopes for each module (`packages/sandbox/src/server/delivery/local.ts`).
161
+
162
+ ## Pitfalls
163
+
164
+ - 415 on every POST: the client did not send `content-type: application/json` (`readJsonObject`).
165
+ - 403 `CSRF_REJECTED` or `ORIGIN_REQUIRED`: `x-csrf-token` missing or the request is not same-origin.
166
+ - Module enabled but no navigation: scopes not granted, or `platform.client` missing.
167
+ - Routes 404: `platform.server` missing, no `./platform` export, or `src/platform.ts` absent; `pnpm flowdular module validate` names it (`PLATFORM_*`).
168
+ - `Kpi` typecheck error: `value` must be a string.
169
+ - Register every `translations/*.json` bundle in the client contribution, put all user-facing copy there with matching key sets, and resolve it with `t()` as described by `translations-i18n`.
170
+ - Every relative import needs its `.ts` or `.tsrx` extension.
171
+ - `module.json` `version`, `spec` `specVersion` and `package.json` `version` are one number.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: module-update
3
+ description: >-
4
+ Change an existing module (endpoint, table, screen, permission, widget) with
5
+ the fixed touch list per change class and the version bump rules.
6
+ ---
7
+ # Update an existing module
8
+
9
+ ## 1. Read first
10
+
11
+ Read the whole module before changing it: `spec/module.yaml`, `src/index.ts`, `src/acl/permissions.ts`, `src/api/endpoints.ts`, `src/services/*`, `src/client/*`, `tests/`. Keep every exported name in `src/index.ts`, `src/server/index.ts` and `src/client/index.ts` stable: other modules import them (`modules/users` uses `AuthRuntime` from `@flowdular/sdk/modules/auth/server`), and the generated composition imports `createServerComposition` and `createClientContribution`.
12
+
13
+ Sandbox facts for an edit session (`packages/sandbox/src/server/sessions.ts`): the module is copied to `workspace/modules/<dir>` and a pristine copy to `base/modules/<dir>`; the diff shown to the operator and the eject plan compare the two. The workspace is a pnpm workspace of its own (declared dependencies install for real; a `package.json` change triggers a reinstall that counts as the `dependencies` gate). The business manager updates the spec before implementation. The operator approves the exact spec hash for every affected module; editing that spec, requesting changes, or adding another module reopens its approval gate. A sandbox specialist never writes `status: approved`; a host agent may invoke approval only after an explicit current user request through `spec-approval`. Delivery checks the recorded hash again.
14
+
15
+ ## 2. Classify the change and use its touch list
16
+
17
+ Change classes: endpoint, table, column, screen, widget, permission, setting, cross-module read, agent tool, business agent, fix.
18
+
19
+ New endpoint:
20
+
21
+ 1. `src/services/<name>-service.ts`: the method with validation and a stable error code.
22
+ 2. `src/services/repository.ts` and `database-repository.ts`: the async interface method and SQL with `$1`, `$2` parameters, `WHERE tenant_id = $1` on every tenant-owned query, inside `database.transaction(..., { tenantId, access })`.
23
+ 3. `src/api/endpoints.ts`: `defineEndpoint` with `access`, `resolveIdentity: endpointIdentityFromContext`, `sessionMutationDenial(octane, auth)` first on mutations, `readJsonObject` plus `requiredString`/`requiredInteger`/`optionalString`; add the route to the returned tuple and its id to `endpoints`.
24
+ 4. `src/client/api.ts`: the fetch (`content-type: application/json`, `x-csrf-token`, `credentials: 'same-origin'`).
25
+ 5. `tests/module.test.ts`: service rule tests plus a 401 and a 403 through `route.handler(createContext(...))`, and a tenant isolation case.
26
+ 6. `spec/module.yaml`: an acceptance scenario, `specVersion` bump.
27
+
28
+ New column or table:
29
+
30
+ 1. Write `migrations/000N_<module>_<name>.up.sql` first and its documented reverse in `.down.sql`. Never edit, reorder, or remove a migration that shipped. A new table uses `CREATE TABLE IF NOT EXISTS`; a new column uses `ALTER TABLE ... ADD COLUMN` once under the ledger.
31
+ 2. `src/services/migration.ts`: append `X_MIGRATION_00N` mirroring the `.up.sql` file byte for byte and append its `{ id, sql: { postgresql: X_MIGRATION_00N }, inspectExisting }` entry to `databaseMigrations`. Build `inspectExisting` from `postgresTenantTableState(...)` so a mixed state returns `partial`.
32
+ 3. `database-repository.ts`: extend the row interface and `fromRow`, the `INSERT`, `UPDATE` and `SELECT` lists, and the `integer()` normalization for a new integer column. Migrations run from the runtime's `migration` lease, not from the repository. Add the migration tests required by `migration-authoring`.
33
+ 4. `src/domain/types.ts`, service, endpoint validation, client form and table column.
34
+ 5. Tests against the module's `tests/support/database.ts` provider for the new rule; `spec/module.yaml` invariant or scenario, `specVersion` bump.
35
+
36
+ New permission:
37
+
38
+ 1. `spec/module.yaml` `permissions`: the new `{ id, description }`.
39
+ 2. `src/acl/permissions.ts`: the constant with the same string.
40
+ 3. Endpoint `access.permission` and client `scope` on the navigation entry, widget or `canManage` flag.
41
+ 4. After eject or enable: `pnpm flowdular auth sync-scopes --module <id> --apply` grants it to owners. Members and bundled defaults require a core change in `modules/auth/src/acl/scopes.ts` (`BUNDLED_MODULE_SCOPES`, `MEMBER_SCOPES`); say so in the handoff instead of editing another module.
42
+
43
+ New screen or widget:
44
+
45
+ 1. `src/client/XView.tsrx` (and `XForm.tsrx` for a drawer) following the pattern in `ux-design`.
46
+ 2. `src/client/contribution.tsrx`: a `views` entry, a `navigation` entry with a unique id, `viewId`, `group`, `glyph` from `ICON_PATHS`, `scope`, `order`; or a `widgets` entry with a `WORKSPACE_SLOTS` slot. Widget state is its own store instance.
47
+ 3. `src/client/index.ts`: re-export the view.
48
+ 4. Add user-facing copy to every declared `translations/*.json` bundle and resolve it with fully qualified `t()` keys. Navigation copy uses getters because contributions exist before bundles are registered.
49
+
50
+ New setting:
51
+
52
+ 1. `src/settings.ts`: `export const X_MODULE_SETTINGS = defineModuleSettings({ moduleId: '<module>.core', settings: { key: { type: 'string' | 'number' | 'boolean', defaultValue, visibility: 'private' | 'shared', client: boolean, scope: 'tenant' | 'platform', labelKey, label, descriptionKey, description, min?, max?, enum?, secret? } } })` from `@flowdular/sdk/kernel` (`packages/kernel/src/module-settings.ts`; setting keys match `^[a-z][a-zA-Z0-9]*$`). `labelKey` and `descriptionKey` are fully qualified module translation keys present in every locale. Keep the English literals as compatibility fallbacks; values, ids and secrets are never translated.
53
+ 2. `src/platform.ts`: return `settings: X_MODULE_SETTINGS` next to `routes`; the platform declares it at boot and Administration, Modules renders it in the module's drawer (`modules/system/src/client/ModuleSettingsSection.tsrx`, behind `system.settings.read` and `system.settings.manage`; the API is `GET /api/settings` and `POST /api/settings/update` in `modules/system/src/server/endpoints.ts`).
54
+ 3. Read it live where it is used: `context.settings.get<number>(tenantId, '<module>.core', 'key')` at request time, never cached at boot; pass `context.settings` into the runtime or service that needs it (`modules/agents/src/settings.ts`, `agentSettings`, shows the pattern with an environment fallback).
55
+ 4. `spec/module.yaml`: an invariant or scenario naming the setting and its bounds; `specVersion` bump. Cross-module reads of a setting need `visibility: 'shared'` and a declared dependency.
56
+
57
+ Cross-module read: import the other module's runtime or service type from its public entry (`@flowdular/module-<x>` or `@flowdular/module-<x>/server`), declare `{ "id": "<x>.core", "range": "^0.1.0" }` in `module.json` `dependencies` and the spec, and add the package to `package.json`. Never open its database or import from its `src/` path.
58
+
59
+ Agent tool: use `agent-tool-design` as a separate phase; add the approved scenario, `src/agent/tools.ts`, the `context.agentTools.register(...)` call, target-side idempotency for writes, and denial tests.
60
+
61
+ Business agent: use `business-agent-design` as a separate phase; add the approved behavior and refusal scenarios, declare the `agents.core` module and package dependencies, define it in `src/agent/agents.ts`, and register it with `context.agentDefinitions.register(...)`. A code definition owns behavior and a maximum exact tool allowlist. Provider, model, active state, and the reduced enabled tools remain tenant binding data.
62
+
63
+ ## 3. Versions and spec
64
+
65
+ Bump `spec/module.yaml` `specVersion`, `module.json` `version` and `package.json` `version` together (patch for a fix, minor for a new endpoint, screen or column). Add an acceptance scenario for every new behaviour and an invariant for every new rule; the scenario id matches `^[A-Z][A-Z0-9-]+$`. In the sandbox the business manager leaves the changed spec in `draft` or `in-review`; only the operator approval route records the approved hash and permits implementation.
66
+
67
+ ## 4. Gates
68
+
69
+ Sandbox: the role's gates run after the turn. Repository root:
70
+
71
+ ```bash
72
+ pnpm --filter @flowdular/module-<dir> typecheck
73
+ pnpm --filter @flowdular/module-<dir> test
74
+ pnpm flowdular spec validate --all --json
75
+ pnpm flowdular module validate --json
76
+ pnpm format:check
77
+ ```
78
+
79
+ The `dependencies` gate (sandbox) scans imports under `src/`; declare any new package before you import it.
80
+
81
+ ## 5. Landing
82
+
83
+ Sandbox: eject runs the gates per module, copies added and changed files over the workspace copy and removes the files the session deleted (`packages/sandbox/src/server/delivery/steps.ts`, `removeModuleFiles`), runs `pnpm install`, `auth sync-scopes` for the module, and a platform typecheck; any failed step stops the delivery there. `module enable` runs only for a new module. A session may carry several modules (`modules[]` in `session.json`); each is diffed against its own base and delivered in the same eject. Repository root: `pnpm verify`, then a PR (`release-eject-pr`).
84
+
85
+ ## Pitfalls
86
+
87
+ - Renaming `createServerComposition`, `createClientContribution` or a permission constant breaks the platform typecheck or another module.
88
+ - Editing an applied `.up.sql` file, even only its whitespace, changes its checksum and blocks startup. Add a new numbered migration.
89
+ - `ORDER BY` on a new list must be covered by a `(tenant_id, <column>, id)` index.
90
+ - A new `Tag` tone or `Icon` name must exist in `@flowdular/sdk/ui`; there is no fallback warning.
91
+ - Editing `platform/**`, `flowdular.json`, or another module from a module change is out of scope; hand off with the exact core change needed.
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: perf-audit
3
+ description: >-
4
+ Find the hot paths of a module or platform package, state their cost, and
5
+ change only what a measurement justifies.
6
+ ---
7
+ # Performance audit
8
+
9
+ Measure first. A micro-rewrite without a number is not a performance change and does not belong in the diff.
10
+
11
+ ## 1. Inventory the hot paths
12
+
13
+ Server (per request):
14
+
15
+ - Queries are asynchronous and pooled, so the cost is round trips, not a blocked event loop. A query inside a loop, an N+1 read after a list, or one transaction per row multiplies the round trip by the row count; do the work in one statement. A list endpoint that returns a tenant's whole table is still O(rows) per request, so paginate or filter in SQL, never in JavaScript after the rows arrive.
16
+ - A lease or a transaction held longer than the work needs starves the pool. Open the transaction around the statements it protects and release it; never hold one across a fetch, an agent call, or a sleep.
17
+ - `list(tenantId)` orders by a column: the index must cover `(tenant_id, <order column>, id)` (`.ai/references/catalog/src/services/migration.ts` has `catalog_items_tenant_sku_idx`). Without it PostgreSQL adds a sort node over the tenant's rows on every call.
18
+ - `readJsonObject` caps bodies at 16 KB and reads the whole text once; do not raise the cap for one field, add an endpoint.
19
+ - `defineEndpoint` allocates a request id and a Set of permissions per request through `endpointIdentityFromContext` (`new Set(principal.scopes)`); this is fine at current sizes and not a target.
20
+ - The runtime checks the migration ledger once, behind a short `purpose: 'migration'` lease, and then holds one runtime lease for the repository (`src/server/runtime.ts` shares a single initialization promise). Acquiring a lease or building a repository per request adds a ledger read and a pool acquisition to every request.
21
+
22
+ Client (per render):
23
+
24
+ - `items.filter(...)` and `toLocaleLowerCase` run on every render in `CatalogView.tsrx`. With a few hundred rows this is invisible; past that, derive once with `store.derive((get) => ...)` from `segment-state` or filter in the effect that loads data.
25
+ - One store per component (`useMemo(() => createXClientState(), [])`) is the intended shape; a shared module-level store would leak between screens.
26
+ - Widgets in `dashboard.metrics` each fetch on mount. Five widgets are five requests on the dashboard; a widget that needs a count should not load the whole list once an endpoint can count.
27
+
28
+ Bundle:
29
+
30
+ - `packages/ui/src/index.ts` imports the Plex fonts and `styles/index.css` at the top, so every consumer of `@flowdular/sdk/ui` pulls them once. A module must not import fonts or global CSS again.
31
+ - Module CSS is allowed only for module-specific composites (`modules/agents/src/client/agents.css`).
32
+
33
+ Agent runtime (`modules/agents`, `packages/harness`):
34
+
35
+ - Bounds that exist: `maxSteps` 1 to 32 and `timeoutMs` 250 to 86400000 per agent definition (`modules/agents/src/services/agent-service.ts`), worker concurrency `FD_AGENT_WORKER_CONCURRENCY` (1 to 16, default 2) and lease `FD_AGENT_WORKER_LEASE_MS` (`modules/agents/src/server/runtime.ts`).
36
+ - Tool calls have a deadline (`timeoutMs`, default 30 seconds, bounded from 250 to 600000 ms) and serialized output is capped at 32 KB by the harness. A list tool must still page or limit rows so useful data fits inside that cap.
37
+
38
+ ## 2. Measure
39
+
40
+ - Server: a vitest `bench` or a script against the module's `tests/support/database.ts` provider seeded with 10k rows for one tenant and 10k for another; time `list(tenantId)` before and after an index. `await database.query({ text: 'EXPLAIN (ANALYZE, BUFFERS) SELECT ...' })` shows an `Index Scan` or the `Seq Scan` plus `Sort` pair that means the index is not covering the order.
41
+ - Client: count renders with a counter in the component during development, or `store.stats()` for commit counts. Remove the instrumentation before the handoff.
42
+ - Bundle: `pnpm --filter @flowdular/platform build` prints chunk sizes.
43
+
44
+ Record the number, the input size and the machine in the handoff or PR body.
45
+
46
+ ## 2b. Bench recipe
47
+
48
+ ```ts
49
+ // tests/list.bench.ts (vitest bench; run with: pnpm --filter @flowdular/module-catalog exec vitest bench)
50
+ import { bench, describe } from 'vitest';
51
+ import { CatalogService } from '../src/services/catalog-service.ts';
52
+ import { createCatalogTestDatabase } from './support/database.ts';
53
+
54
+ const database = await createCatalogTestDatabase();
55
+ const service = new CatalogService(database.repository);
56
+ for (let index = 0; index < 10_000; index += 1) {
57
+ for (const tenant of ['tenant-a', 'tenant-b']) {
58
+ await service.create(tenant, {
59
+ sku: `SKU-${index}`,
60
+ name: `Item ${index}`,
61
+ kind: 'product',
62
+ unit: 'each',
63
+ basePriceMinor: index,
64
+ currency: 'EUR',
65
+ });
66
+ }
67
+ }
68
+
69
+ describe('catalog list', () => {
70
+ bench('list one tenant (10k of 20k rows)', async () => {
71
+ await service.list('tenant-a');
72
+ });
73
+ });
74
+ ```
75
+
76
+ Keep bench files out of `tests/**/*.test.ts` so the `tests` gate does not run them; name them `*.bench.ts`. Delete the file or keep it only when the number is worth tracking.
77
+
78
+ ## 2c. Report template
79
+
80
+ ```text
81
+ Path: GET /api/catalog/items -> CatalogService.list -> DatabaseCatalogRepository.list
82
+ Complexity: O(rows of tenant) time and space per request; ORDER BY covered by catalog_items_tenant_sku_idx
83
+ Measurement: 10k rows per tenant, embedded PGlite, Node 24: 3.1 ms per call before, 3.0 ms after (no change)
84
+ Decision: no code change; add pagination when a tenant exceeds ~50k items
85
+ ```
86
+
87
+ ## 3. Change only what the number justifies
88
+
89
+ Allowed without a benchmark: adding a missing covering index; moving a filter from JavaScript into the SQL `WHERE`; removing a duplicate fetch. Everything else (loop style, hoisting, memoization of cheap values, replacing `Array.prototype` calls) needs a before and after measurement in the same environment.
90
+
91
+ Complexity to state in the review: for each new data structure and loop on a request or render path, its time and space in terms of rows, tenants, or items. Unbounded growth (a Map keyed by tenant that is never pruned, a list of listeners never detached) is a defect even when each entry is small.
92
+
93
+ ## Pitfalls
94
+
95
+ - `LIKE` or `=` against `lower(column)` cannot use a plain `(tenant_id, column)` index; store a normalized column (`sku_normalized`) as `.ai/references/catalog` does, or add an expression index on `lower(column)`.
96
+ - `ORDER BY lower(name)` (`Flowdular/official-modules`, `modules/parties`) cannot use the `(tenant_id, name, id)` index for the sort; acceptable at current sizes, name it if parties grow.
97
+ - A `Kpi` that shows `items.length` after loading the full list is O(rows) network per dashboard load.
98
+ - Never change behaviour in a performance commit; keep the functional tests green and add none that assert internal call counts.