create-flowdular 0.2.4 → 0.2.6

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 (271) 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 +40 -2
  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/modules/example/package.json +2 -2
  268. package/template/default/package.json +6 -2
  269. package/template/default/platform/octane.config.ts +17 -6
  270. package/template/default/platform/package.json +3 -2
  271. package/template/default/pnpm-workspace.yaml +1 -0
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: variables
3
+ description: >-
4
+ Build variable-aware fields and templates on the {{ }} contract, the scope
5
+ mask, and server-side resolution, with agents.core as the worked example.
6
+ roles:
7
+ - frontend-engineer
8
+ - ux-designer
9
+ - agentic-engineer
10
+ - backend-engineer
11
+ - module-executor
12
+ when: A field must let a value embed {{ variable }} tokens filled from other fields, the request context, or another module.
13
+ ---
14
+
15
+ # Variables (templating and linked fields)
16
+
17
+ A variable field lets a stored value embed `{{ key }}` tokens that are filled at
18
+ run time from context or another module's data. The contract is pure and lives
19
+ in `@flowdular/sdk/contracts` (`packages/contracts/src/variables.ts`); the fields are
20
+ presentational primitives in `@flowdular/sdk/ui`; resolution happens on the server
21
+ before the consumer sees the text. `agents.core` is the worked example: an agent
22
+ author writes instructions as a template and the run snapshot carries the
23
+ resolved text.
24
+
25
+ ## The `{{ }}` contract
26
+
27
+ `VariableDefinition { key, label, kind, scope?, sample?, description? }` is one
28
+ offerable variable. `kind` is `text | number | date | money | identifier`.
29
+ `key` is a dot path whose segments start lowercase and may continue in
30
+ camelCase (`context.user.displayName`), matched by `VARIABLE_KEY_PATTERN` /
31
+ `isVariableKey`.
32
+
33
+ - `extractVariables(template)`: the distinct trimmed `{{ key }}` tokens, in
34
+ first-seen order.
35
+ - `validateTemplate(template, available, allowedScopes?)`: `{ unknown, forbidden }`.
36
+ `unknown` are tokens not in `available`; `forbidden` are tokens whose def
37
+ declares a `scope` not present in `allowedScopes` (omit `allowedScopes` to skip
38
+ the scope check).
39
+ - `resolveTemplate(template, values, { onMissing })`: substitutes each
40
+ `{{ key }}` with `values[key]`. `onMissing` is `keep` (default, leave the token
41
+ verbatim) or `blank`.
42
+ - `tokenizeTemplate(template)`: the segments the UI overlay highlights; the
43
+ `text` fields concatenate back to the exact input.
44
+
45
+ Rules the resolver guarantees: tokens are `{{ key }}` with optional inner spaces;
46
+ `\{{` outputs a literal `{{`; substitution is a single pass, so a value that
47
+ itself looks like a token is emitted verbatim (never recursive); only own,
48
+ string keys resolve, so prototype keys (`__proto__`, `toString`) never resolve.
49
+ No eval, no expressions, only key substitution.
50
+
51
+ ## The scope mask
52
+
53
+ `VariableDefinition.scope` is the permission required to read the source. A
54
+ variable is offered and resolved only when the principal holds that scope:
55
+
56
+ - The UI fields never fetch and never check scopes. The caller passes
57
+ `variables` already filtered to what this principal may use, so a variable the
58
+ principal cannot read is simply absent from the menu and highlights as an error
59
+ pill if typed.
60
+ - On the server, filter the definition list by the principal's scopes before
61
+ building `values`, or call `validateTemplate(..., allowedScopes)` and refuse a
62
+ template whose `forbidden` is non-empty. A scope-less variable
63
+ (`context.*`) is always allowed.
64
+
65
+ ## The UI fields (`@flowdular/sdk/ui`)
66
+
67
+ `VariableTextarea` (multiline), `VariableInput` (single line), and
68
+ `VariableSelect` (one literal option or one variable token) are presentational.
69
+ All take `value`, `onInput`, `variables: readonly VariableDefinition[]`, optional
70
+ `sampleValues?: Record<string,string>`, required translated `label` (accessible
71
+ name), `name` (so a `FormData` submit still captures it), `required`, `disabled`,
72
+ and `error`. Input and textarea also require translated `insertLabel`,
73
+ `variablesLabel`, and `emptyLabel`; the shared primitive has no English copy to
74
+ fall back to. Select takes `options: readonly VariableSelectLiteralOption[]` and
75
+ requires translated `literalGroupLabel` and `variablesGroupLabel`. A caller also
76
+ localizes every `VariableDefinition.label` before passing the definitions, since
77
+ that label is visible in the picker.
78
+
79
+ - The `braces` affordance (a `{}` icon in `ICON_PATHS`) opens a menu of the
80
+ available variables with label, key, and current or sample value; picking one
81
+ inserts `{{ key }}` at the caret. Typing `{{` opens the same menu filtered by
82
+ what follows; ArrowUp/Down and Enter pick, Escape closes.
83
+ - Tokens are highlighted by an overlay layer (`tokenizeTemplate`) sitting behind
84
+ a transparent control, so `{{ key }}` reads as a pill while the real value
85
+ stays plain text; an unknown or forbidden token gets the error pill. All
86
+ color comes from tokens; measurement is client-only in an effect, so SSR is
87
+ safe. See `packages/ui/src/components/VariableField.tsrx` and its wrappers.
88
+ - `VariableSelect` stays a native `<select>`. Literal values and allowed
89
+ `{{ key }}` tokens are real `<option>` values, so keyboard navigation,
90
+ validation, disabled state, accessible naming, and `FormData` submission keep
91
+ browser semantics. Samples appear only in option labels. The component never
92
+ resolves the selected token.
93
+
94
+ Keep the fields presentational: the caller supplies `variables` and
95
+ `sampleValues`, the component never fetches.
96
+
97
+ The platform variable registry (`@flowdular/sdk/kernel`) registers definitions and
98
+ their execution-time resolvers. Reach the shared instance with
99
+ `platformVariableRegistry(context.capabilities)`. `list(scopes)` requires an
100
+ explicit permission snapshot and is the only
101
+ definition list a server sends to a field. `resolve(template, request)` takes a
102
+ trusted tenant id, actor, immutable permission snapshot, `AbortSignal`, explicit
103
+ record bindings and optional values owned by the consumer. It validates the
104
+ whole template before invoking a source. An unknown token, missing scope,
105
+ missing binding, aborted request, unavailable record, or source failure is a
106
+ refusal. Source exceptions are replaced with a generic error so SQL, provider,
107
+ and record details do not cross the module boundary.
108
+
109
+ The source declares `requiredBindings` per variable. For example, `party.name`
110
+ requires `partyId`; the resolver receives that id explicitly and asks the
111
+ parties public capability or read tool under `request.tenantId`. It never infers
112
+ a record from browser state and never reads the parties database. Local form
113
+ values go in `request.values`, while cross-module values must come from the
114
+ registered resolver. The resolved text is returned to the server consumer only;
115
+ the raw template remains stored.
116
+
117
+ ## Server-side resolution rule
118
+
119
+ Resolve the template before the consumer sees it, and keep the raw template
120
+ stored. The stored record keeps the `{{ }}` template; the run or send snapshot
121
+ carries the resolved text. Never resolve in the client and never store the
122
+ resolved text back onto the definition.
123
+
124
+ Worked example in `agents.core`:
125
+
126
+ - `modules/agents/src/domain/context-variables.ts` declares
127
+ `AGENT_CONTEXT_VARIABLES` (`context.tenantName`, `context.today`,
128
+ `context.user.displayName`, `context.user.email`, all scope-less) and
129
+ `agentContextValues(input)` that builds their values from the run's
130
+ tenant/principal/date.
131
+ - `AgentService.enqueueRun` (`services/agent-service.ts`) resolves
132
+ `agent.instructions` with `resolveTemplate` against those values before it
133
+ builds the instruction snapshot; the stored definition is untouched. The
134
+ endpoint (`api/endpoints.ts`) supplies the tenant name and principal from the
135
+ request; `today` comes from the queue timestamp.
136
+ - `AgentDefinitionForm.tsrx` feeds `VariableTextarea` the context variable list
137
+ and sample values, so an author gets the menu and highlighting.
138
+
139
+ ## Adding a variable source via a capability or tool
140
+
141
+ Business-data variables (for example `{{ party.name }}`) resolve through a
142
+ public capability or an agent read tool owned by the source module:
143
+
144
+ 1. Declare the `VariableDefinition` with `scope` equal to the source tool's
145
+ `requiredPermissions` (`AgentTool` in `packages/harness/src/runtime.ts`). The
146
+ tool's permission is the mask: one source of truth, no parallel table.
147
+ 2. Register the source on the shared registry during module composition. Declare
148
+ the record id in `requiredBindings`; do not accept a tenant binding.
149
+ 3. Offer it only through `registry.list(principal.scopes)`. The UI fields take
150
+ this already-filtered list and never fetch.
151
+ 4. Call `registry.resolve` on the server with the trusted tenant, actor,
152
+ permission snapshot, signal, and explicit bindings. The source invokes only
153
+ its owning public capability or read tool and maps the bounded result to
154
+ strings. The registry refuses before the source runs if the scope or binding
155
+ is absent.
156
+
157
+ `automations.core` is the live cross-module example. `agent.name` requires the
158
+ schedule's explicit `agentId`, and its resolver calls the `agents.run-queue`
159
+ capability with the active tenant. A binding containing an agent from another
160
+ tenant resolves to no record and the run is refused. The schedule keeps the raw
161
+ template; only the queued run receives the resolved input.
162
+
163
+ Register a new tool with the `agent-tool-design` skill; this skill covers only
164
+ how its output becomes a resolvable variable.
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: workflow-development
3
+ description: >-
4
+ Build, publish, invoke, and test a workflows.core DAG through its typed graph
5
+ and public execution capability without bypassing agent, action, tenant, or
6
+ audit boundaries.
7
+ roles:
8
+ - agentic-engineer
9
+ - backend-engineer
10
+ - frontend-engineer
11
+ - module-executor
12
+ when: A brief asks for a workflow, pipeline, canvas node, workflow action, or a module feature that starts a workflow.
13
+ ---
14
+
15
+ # Build and integrate an agentic workflow
16
+
17
+ `workflows.core` owns durable directed acyclic workflows. A workflow coordinates
18
+ pinned agent revisions, deterministic gates, schema validators, registered
19
+ module actions, data mappings, and terminal output. It does not own schedules or
20
+ webhook secrets. Those remain optional concerns of `automations.core`.
21
+
22
+ Read `docs/adr/0006-agentic-workflows.md`, the approved
23
+ `modules/workflows/spec/module.yaml`, and the contracts in
24
+ `modules/workflows/src/domain/types.ts` before changing a workflow surface.
25
+
26
+ ## Pick the correct extension point
27
+
28
+ - A workflow definition belongs in `workflows.core` and is edited through its
29
+ API or canvas. Do not hardcode a tenant workflow in source.
30
+ - A business operation that a workflow may call is a versioned agent action.
31
+ Register it through the agents action catalog. If missing, implement it in a
32
+ separate `agent-tool-design` phase with permission, input, output, timeout,
33
+ idempotency and audit tests before returning to workflow integration.
34
+ - A business module that starts a workflow resolves
35
+ `workflows.execution.v1` from `context.capabilities`. It never imports a
36
+ workflow repository or database.
37
+ - A schedule or signed webhook remains in `automations.core`. Its optional
38
+ bridge invokes the workflow capability with a service actor and a separate
39
+ schedule or webhook origin.
40
+ - If the workflow module is absent, the capability registry returns `null`.
41
+ Hide an optional feature or return a clear stable refusal.
42
+
43
+ ## Graph contract
44
+
45
+ Version one is a bounded DAG. The graph contains:
46
+
47
+ - `input`: accepts the invocation envelope.
48
+ - `agent`: calls one exact immutable agent revision and validates structured
49
+ output.
50
+ - `agent-decision`: produces one schema-valid `pass` or `fail` outcome.
51
+ - `gate`: evaluates the versioned allowlisted logic language.
52
+ - `validator`: validates an envelope against a pinned JSON schema.
53
+ - `action`: calls one exact registered action contract version.
54
+ - `merge`: waits for all declared incoming paths.
55
+ - `output`: settles the workflow with a typed result.
56
+
57
+ Every port names a schema. Every edge connects compatible ports. Mappings are
58
+ declarative literals, JSON pointer paths, or templates with explicit variable
59
+ bindings. Never add JavaScript, dynamic imports, shell commands, downloaded
60
+ code, arbitrary expressions, or hidden provider decisions to graph data.
61
+
62
+ The hard limits live in `WORKFLOW_LIMITS` in
63
+ `modules/workflows/src/domain/types.ts`. Validation must reject a cycle,
64
+ dangling edge, unreachable node, missing terminal output, incompatible port,
65
+ missing exact dependency, oversized graph, or unsupported action risk before
66
+ publication.
67
+
68
+ ## Revisions and publication
69
+
70
+ Draft saves use optimistic concurrency through `expectedRevision`. A successful
71
+ save creates the next draft revision. A conflict never overwrites another
72
+ editor.
73
+
74
+ Publication:
75
+
76
+ 1. Validates and compiles the graph.
77
+ 2. Resolves exact agent revisions and exact action contract versions.
78
+ 3. Rejects missing, archived, incompatible, external, or destructive
79
+ dependencies.
80
+ 4. Stores an immutable content-addressed published revision.
81
+ 5. Leaves earlier revisions and their run evidence unchanged.
82
+
83
+ Never replace a pinned dependency with its latest version during execution.
84
+ Editing after publication creates another draft.
85
+
86
+ ## Execution modes
87
+
88
+ Use the smallest mode that proves the change:
89
+
90
+ - Dry-run validates a draft and returns issues, compiled order, references,
91
+ permissions, checksum, and limits. It creates no run and invokes nothing.
92
+ - Simulation persists a run history but uses fixtures for nondeterministic
93
+ nodes. It advances virtual time without sleeping and never calls a provider,
94
+ action, or business mutation.
95
+ - Live runs only published revisions. It may call pinned agents and approved
96
+ read or workspace-write actions under the initiating permission snapshot.
97
+
98
+ Do not disguise simulation as live execution. Do not use live mode to test an
99
+ invalid draft.
100
+
101
+ ## Invoke a published workflow from a module
102
+
103
+ Resolve the capability at request or service call time, after platform
104
+ composition has completed:
105
+
106
+ ```ts
107
+ import {
108
+ WORKFLOW_EXECUTION_CAPABILITY,
109
+ type WorkflowExecutionCapability,
110
+ } from '@flowdular/sdk/modules/workflows/server';
111
+
112
+ const workflows = context.capabilities.get<WorkflowExecutionCapability>(
113
+ WORKFLOW_EXECUTION_CAPABILITY,
114
+ );
115
+ if (!workflows) throw new ModuleError('WORKFLOWS_UNAVAILABLE');
116
+
117
+ const accepted = await workflows.enqueue(
118
+ {
119
+ workflowKey: 'catalog-enrichment',
120
+ input: { itemId },
121
+ idempotencyKey: `catalog:${itemId}:${version}`,
122
+ },
123
+ {
124
+ tenantId,
125
+ actor,
126
+ origin: {
127
+ kind: 'module',
128
+ moduleId: 'catalog.core',
129
+ operationId: 'catalog.enrichment.start',
130
+ },
131
+ permissionSnapshot,
132
+ },
133
+ );
134
+ ```
135
+
136
+ The module manifest declares `workflows.core` only when workflow support is a
137
+ required feature. An optional integration belongs in a small bridge module that
138
+ depends on both sides. Do not duplicate the capability interface locally to
139
+ avoid a dependency declaration.
140
+
141
+ Trusted context and business input are separate. Tenant, actor, origin, and
142
+ permission snapshot never come from the request body. The idempotency key is
143
+ stable for one logical operation. Reusing it with different input is a
144
+ conflict, not a second run.
145
+
146
+ ## Actor and permission rules
147
+
148
+ - A user action uses the real user actor.
149
+ - A tool invoked by an agent uses the real agent actor and child run
150
+ correlation supplied by `AgentToolContext`.
151
+ - A schedule or webhook uses a service actor whose `configuredBy` is the real
152
+ user who configured it. Origin stays `schedule` or `webhook`.
153
+ - A workflow definition grants no scope. Live execution intersects the caller
154
+ snapshot with each node's agent or action requirements.
155
+ - A cross-tenant id, foreign cursor, missing scope, absent dependency, or
156
+ mismatched action version is refused before data or provider work.
157
+
158
+ ## History, recovery, and cancellation
159
+
160
+ The browser observes execution. It never owns execution. Enqueue persists the
161
+ run before returning. Workers use leases and recover expired work from stored
162
+ node state, child ids, and stable side-effect idempotency keys.
163
+
164
+ Every transition appends an ordered schema-versioned event. Run, node,
165
+ attempt, and edge states are separate projections. `pass` and `fail` are normal
166
+ outcome ports, not technical statuses.
167
+
168
+ Cancellation is durable and cooperative. It prevents new nodes, asks the
169
+ current child agent or action to cancel, records whether it acknowledged, and
170
+ ignores late output for routing while keeping its safe evidence.
171
+
172
+ History responses contain redacted bounded evidence. They never expose provider
173
+ credentials, session tokens, hidden reasoning, encrypted payload blobs, or
174
+ unrestricted request bodies.
175
+
176
+ ## Required tests
177
+
178
+ For a graph or runtime change, prove:
179
+
180
+ 1. Deterministic compile order and rejection of cycles, dangling edges,
181
+ incompatible ports, unreachable nodes, and missing output.
182
+ 2. Tenant isolation plus one unauthenticated and one unscoped refusal for every
183
+ endpoint group.
184
+ 3. Exact agent and action revision refusal with no latest-version fallback.
185
+ 4. Dry-run produces no run, provider call, action call, or business write.
186
+ 5. Simulation uses fixtures and virtual duration with no real wait.
187
+ 6. Live enqueue is idempotent and persists before acceptance.
188
+ 7. Recovery before and after child enqueue does not duplicate work.
189
+ 8. Retry records the chosen delay before waiting and reuses the side-effect
190
+ idempotency key.
191
+ 9. Cancellation prevents downstream work and records late results safely.
192
+ 10. Event replay, cursor binding, payload redaction, retention, usage, cost,
193
+ and audit hash-chain integrity.
194
+ 11. The canvas shows validation, loading, empty, error, denied, simulation,
195
+ live, cancelled, and recovered states, including small-screen read mode.
196
+
197
+ Run the module typecheck and tests, `pnpm flowdular module validate`, then the
198
+ full `pnpm verify`. For a new module integration, update its approved spec and
199
+ move `specVersion`, `module.json` version, and package version together.
@@ -0,0 +1,203 @@
1
+ ---
2
+ name: agent-tool-design
3
+ description: >-
4
+ Register an API or CLI tool that lets business agents act on a module, with
5
+ the real harness, permission, idempotency, audit, and test contract.
6
+ ---
7
+ # Design and register an agent tool
8
+
9
+ A module lets agents act on it by registering tools during composition. A tool
10
+ wraps one service operation, takes the tenant from the run context, reuses the
11
+ service validation, bounds its output, and declares the exact permission the
12
+ matching endpoint requires. `parties.core` and `catalog.core` are the reference
13
+ implementations.
14
+
15
+ This skill designs tools, not business-agent behavior. When a module should
16
+ also ship a ready business agent through `defineAgent()`, read
17
+ `business-agent-design` and register only the exact tools that agent needs.
18
+
19
+ ## 1. The contract in code
20
+
21
+ - Composition: `PlatformServerContext` (`modules/auth/src/server/composition.ts`) carries `agentTools: PlatformToolRegistry` (`register(tools)`, `list()`; `packages/kernel/src/tool-registry.ts`; a duplicate tool id throws at boot), `settings: ModuleSettingsRuntime`, and `capabilities: PlatformCapabilityRegistry` (`register(id, service)`, `get(id)`, `has(id)`; `packages/kernel/src/capability-registry.ts`). `platform/octane.config.ts` creates the registries, passes them to every module's `createServerComposition`, declares each `settings`, owns each `dispose`, then calls each `start`.
22
+ - Ordering is a non-issue: `agents.core` (`modules/agents/src/platform.ts`) passes `tools: () => context.agentTools.list()` into `createAgentRuntime`, and the harness is built lazily in `start()`, which runs after every module has composed. Tools any module registers during its own compose are therefore visible, whatever the module order.
23
+ - Helpers: import `defineApiAgentTool` from `@flowdular/sdk/harness/tool-adapters` and the types `AgentTool`, `AgentToolContext` from `@flowdular/sdk/harness/runtime`. Both subpaths are free of the Vercel AI SDK; only the harness root (`@flowdular/sdk/harness`) and `@flowdular/sdk/modules/agents/server` pull it. `defineApiAgentTool` returns a frozen `AgentTool { id, transport: 'api', target, description, requiredPermissions, inputSchema?, execute }`. `defineCliAgentTool({ id, capability: { id, risk }, ... })` wraps a CLI capability and throws at definition time for `external` or `destructive` risk.
24
+ - Skills inside `agents.core` are tenant database records behind `agents.skills.*`, appended to agent instructions. They are unrelated to `.ai/skills/**`, which are files for coding agents.
25
+ - A read tool's output can also become a resolvable `{{ variable }}` for variable-aware fields: the tool's `requiredPermissions` is the variable's scope mask. Register a source on `platformVariableRegistry(context.capabilities)`, require an explicit record binding, and invoke the tool with the trusted tenant, actor permission snapshot, and signal. See the `variables` skill for the complete refusal contract.
26
+ - ADR 0002 (`docs/adr/0002-durable-agent-execution.md`): instructions are data, tools are registered by the composition, each tool records an endpoint id or a CLI capability id plus its required permissions, runs are durable with leases.
27
+
28
+ ## 2. Tool shape
29
+
30
+ ```ts
31
+ // src/agent/tools.ts
32
+ import { defineApiAgentTool } from '@flowdular/sdk/harness/tool-adapters';
33
+ import type { AgentTool } from '@flowdular/sdk/harness/runtime';
34
+ import { PARTY_PERMISSIONS } from '../acl/permissions.ts';
35
+ import type { PartyKind } from '../domain/types.ts';
36
+ import type { PartiesRuntime } from '../server/runtime.ts';
37
+
38
+ const MAX_TOOL_ROWS = 200;
39
+
40
+ export function partiesAgentTools(
41
+ runtime: PartiesRuntime,
42
+ ): readonly AgentTool[] {
43
+ return [
44
+ defineApiAgentTool({
45
+ id: 'parties.customer.list', // ^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$
46
+ endpointId: 'parties.records.list', // the read endpoint this wraps
47
+ description: 'List customers and suppliers of the active tenant.',
48
+ requiredPermissions: [PARTY_PERMISSIONS.read],
49
+ inputSchema: {
50
+ type: 'object',
51
+ additionalProperties: false,
52
+ properties: {
53
+ status: { type: 'string', enum: ['active', 'archived'] },
54
+ query: { type: 'string', maxLength: 120 },
55
+ },
56
+ },
57
+ execute: async (input, context) => {
58
+ const value = (input ?? {}) as Record<string, unknown>;
59
+ const query =
60
+ typeof value.query === 'string'
61
+ ? value.query.trim().toLocaleLowerCase('en-US')
62
+ : '';
63
+ return runtime
64
+ .service()
65
+ .list(context.tenantId) // tenant from the run, never from input
66
+ .filter(
67
+ (party) =>
68
+ query === '' ||
69
+ party.name.toLocaleLowerCase('en-US').includes(query),
70
+ )
71
+ .slice(0, MAX_TOOL_ROWS); // bound output
72
+ },
73
+ }),
74
+ defineApiAgentTool({
75
+ id: 'parties.customer.create',
76
+ endpointId: 'parties.records.create',
77
+ description: 'Create a customer or supplier owned by the active tenant.',
78
+ requiredPermissions: [PARTY_PERMISSIONS.manage],
79
+ inputSchema: {
80
+ type: 'object',
81
+ additionalProperties: false,
82
+ required: ['name', 'kind'],
83
+ properties: {
84
+ name: { type: 'string', maxLength: 160 },
85
+ kind: { type: 'string', enum: ['customer', 'supplier', 'both'] },
86
+ vatId: { type: 'string', maxLength: 20 },
87
+ },
88
+ },
89
+ execute: async (input, context) => {
90
+ const value = (input ?? {}) as Record<string, unknown>;
91
+ // The service revalidates every field, so a tool cannot persist
92
+ // what the endpoint would reject.
93
+ return runtime.service().create(context.tenantId, {
94
+ name: String(value.name ?? ''),
95
+ kind: value.kind as PartyKind,
96
+ vatId: typeof value.vatId === 'string' ? value.vatId : null,
97
+ });
98
+ },
99
+ }),
100
+ ];
101
+ }
102
+ ```
103
+
104
+ ```ts
105
+ // src/platform.ts, inside createServerComposition, before the return.
106
+ // register takes readonly unknown[], so no cast is needed.
107
+ context.agentTools.register(partiesAgentTools(runtime));
108
+ ```
109
+
110
+ Export the factory from `src/server/index.ts` so tests and the composition reach it.
111
+
112
+ Dependencies: `package.json` gets `"@flowdular/sdk/harness": "workspace:*"`. You do not import `@flowdular/sdk/modules/agents` and you do not add `agents.core` to `module.json`: registration flows through the platform-provided registry on the composition context, not an import of agents.core. Adding a scenario bumps `spec/module.yaml` `specVersion` and `module.json` `version` together.
113
+
114
+ ## 3. Execution model you design against (`packages/harness/src/runtime.ts`, `AgentHarness.execute`)
115
+
116
+ - A tool is offered only when the agent definition lists it in `allowedTools`, the run's explicit `toolGrants` include it, every `requiredPermissions` entry is in the enqueue-time permission ceiling, and the initiating user still holds every permission when the tool is called. The auth runtime reauthorizes the trusted actor and tenant before every call. A stored snapshot never becomes future authority, newly granted scopes do not elevate an old run, and a service actor stays tool-less until its owning module supplies an explicit revocable policy. Otherwise the harness emits `tool.denied` and throws a stable refusal. `assertToolsRegistered` rejects a new run whose agent names an unregistered tool, while an exact idempotent retry returns its already persisted run before consulting mutable definitions or registries.
117
+ - `invokeTool` validates `input` against `inputSchema` first, using a small JSON Schema subset (`type`, `enum`, `required`, `properties`, `additionalProperties: false`, `items`; `packages/harness/src/tool-contract.ts` `validateToolInput`). A violation emits `tool.denied` (reason `TOOL_INPUT_INVALID`) before `execute` runs. The subset does not check string length or format, so the tool enforces those by passing input through the module service.
118
+ - `execute(input, { runId, tenantId, requestedBy, actor, idempotencyKey, permissions, signal })`. Take the tenant from `context.tenantId`. `permissions` is the intersection of the original ceiling and live authorization. `actor` describes the agent run for record history. `additionalProperties: false` already makes the harness refuse a stray `tenantId` field, but never read one anyway.
119
+ - A mutating tool with `idempotency: 'required'` is executable only after the target module implements a durable ledger and the definition declares `idempotencyProtection: 'target-ledger'`. The harness derives a stable key from the durable run id and deterministic tool-call ordinal. The target ledger binds `(tenant, tool id, key)` to a canonical input hash and the first result. A replay returns that result without another mutation; the same key with another tool or input fails closed. Provider tool-call ids are audit metadata only. Never add the declaration before the target migration, repository transaction, and crash-recovery test exist.
120
+ - Each call has a deadline (`tool.timeoutMs`, default 30 s, range 250 to 600000) and the harness caps serialized output at 32 KB (`boundToolOutput`), marking `truncated`. Still page or limit your rows so one call cannot dominate the run window.
121
+ - Events per call land in the run's persisted audit chain: `tool.started`, then `tool.completed` (metadata `tool`, `outputCharacters`, `truncated`) on success, `tool.failed` (reason) on error, or `tool.denied` (reason) when not granted or input-invalid.
122
+ - Runs are enqueued by `POST /api/agent-runs` behind `agents.runs.execute`, claimed by `AgentWorker` with a lease, observed through `GET /api/agent-runs` and the SSE stream. The registered tool ids surface in `GET /api/agents` `tools`, which the Agents form reads to build the allowed-tools grid. Never make a tool block on user input.
123
+
124
+ ## 4. Deliverables for a module
125
+
126
+ 1. Spec: one acceptance scenario per tool group (`PARTIES-AGENT-TOOL`): endpoint wrapped, permission required, validated input, what it refuses (no tenant input; a `manage` tool needs an explicit scenario; bounded output). Bump `specVersion` and `module.json` `version` together.
127
+ 2. `src/agent/tools.ts`: every tool calls the module service through the runtime (never a repository, database handle, filesystem or shell), takes `context.tenantId`, and reuses the service validation.
128
+ 3. The `register` line in `src/platform.ts`, and the factory exported from `src/server/index.ts`.
129
+ 4. For a mutating tool, a numbered migration and target-side idempotency ledger, with the migration mirrored byte for byte in `src/services/migration.ts`. The service commits the business mutation and ledger result in one transaction.
130
+ 5. Tests, below, including a replay of the complete harness execution with the same run id and no second business row.
131
+ 6. In the Agents screen an agent definition lists the tool id in its allowed tools, the request carries it in `toolGrants`, and the initiating principal still holds the permission. Without all three the tool stays invisible to the model.
132
+
133
+ ## 4b. Worked example
134
+
135
+ Spec scenario:
136
+
137
+ ```yaml
138
+ acceptanceScenarios:
139
+ - id: PARTIES-AGENT-TOOL
140
+ given: An agent run holds parties.records.manage in its permission snapshot and the tool parties.customer.create in its grants.
141
+ when: The agent invokes the tool with a valid party, and a run without the manage scope invokes the same tool.
142
+ then: The scoped run creates a tenant-owned party from the run tenant and the harness denies the unscoped run before any write.
143
+ ```
144
+
145
+ Module-local test (`tests/agent-tools.test.ts`) drives `execute` directly. It does
146
+ not assert on `context.permissions`: RBAC is the harness's job, not the tool's.
147
+
148
+ ```ts
149
+ import type { AgentToolContext } from '@flowdular/sdk/harness/runtime';
150
+ import { describe, expect, it } from 'vitest';
151
+ import { partiesAgentTools } from '../src/agent/tools.ts';
152
+ import { createPartiesRuntime } from '../src/server/runtime.ts';
153
+
154
+ const context = (tenantId: string): AgentToolContext => ({
155
+ runId: 'run-1',
156
+ tenantId,
157
+ requestedBy: 'account-1',
158
+ permissions: new Set<string>(),
159
+ signal: new AbortController().signal,
160
+ });
161
+
162
+ it('creates under the run tenant and ignores a tenant in the input', async () => {
163
+ const runtime = createPartiesRuntime({ databasePath: ':memory:' });
164
+ const [, create] = partiesAgentTools(runtime);
165
+ await create!.execute(
166
+ { name: 'Acme', kind: 'customer', tenantId: 'tenant-b' },
167
+ context('tenant-a'),
168
+ );
169
+ expect(runtime.service().list('tenant-a')).toHaveLength(1);
170
+ expect(runtime.service().list('tenant-b')).toHaveLength(0);
171
+ });
172
+
173
+ it('reuses the service validation so a tool cannot bypass the endpoint', async () => {
174
+ const [, create] = partiesAgentTools(
175
+ createPartiesRuntime({ databasePath: ':memory:' }),
176
+ );
177
+ await expect(
178
+ create!.execute(
179
+ { name: 'Acme', kind: 'customer', vatId: 'PL-123' },
180
+ context('tenant-a'),
181
+ ),
182
+ ).rejects.toMatchObject({ code: 'INVALID_VAT_ID' });
183
+ });
184
+ ```
185
+
186
+ Prove RBAC through the harness (in `modules/agents/tests` with a fake provider,
187
+ where the create tool runs against a `:memory:` repository): a run holding the
188
+ `manage` scope creates the row and emits `tool.started`/`tool.completed`; a run
189
+ whose snapshot lacks it emits `tool.denied` (`TOOL_NOT_GRANTED`) and writes
190
+ nothing.
191
+
192
+ ## 5. Settings a tool may depend on
193
+
194
+ Declare `settings: defineModuleSettings({...})` (from `@flowdular/sdk/kernel`) by returning it from the composition, keep a reference to `PlatformServerContext.settings` in the tool factory, and read it per call as `settings.get<number>(context.tenantId, '<module>.core', 'key')` at request time, never at boot. Declared settings render in the module's drawer under Administration, Modules automatically.
195
+
196
+ ## Pitfalls
197
+
198
+ - A tool id equal to an endpoint id is a convention, not a requirement; keep them parallel for traceability. A read-by-id tool with no dedicated endpoint reuses the read endpoint id under the same permission.
199
+ - `requiredPermissions` must be exactly the endpoint's permission; a weaker list lets a run bypass the endpoint's ACL because the tool calls the service directly.
200
+ - Register once per composition; a duplicate id throws in the registry at boot and the platform does not start.
201
+ - Import the helpers from `@flowdular/sdk/harness/tool-adapters` and `@flowdular/sdk/harness/runtime`; never import the harness root or `@flowdular/sdk/modules/agents` from `src/index.ts` or the client, which would pull the Vercel AI SDK into the client bundle.
202
+ - The harness validates only the schema subset; deep validation is the service's job. Pass input through the service so a tool cannot persist what the endpoint would reject.
203
+ - Playground runs use the tenant's readiness-probed provider; the local simulation provider performs no network call and is the only provider in a fresh install.