@flowdular/sdk 0.1.0

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 (883) hide show
  1. package/.ai/agents/README.md +27 -0
  2. package/.ai/agents/module-executor.md +36 -0
  3. package/.ai/agents/reviewer.md +23 -0
  4. package/.ai/agents/sandbox/agentic-engineer.md +31 -0
  5. package/.ai/agents/sandbox/backend-engineer.md +36 -0
  6. package/.ai/agents/sandbox/business-manager.md +23 -0
  7. package/.ai/agents/sandbox/frontend-engineer.md +27 -0
  8. package/.ai/agents/sandbox/ux-designer.md +23 -0
  9. package/.ai/agents/spec-author.md +29 -0
  10. package/.ai/blueprints/add-migration/README.md +5 -0
  11. package/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  12. package/.ai/blueprints/add-migration/blueprint.json +14 -0
  13. package/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  14. package/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  15. package/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  16. package/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  17. package/.ai/blueprints/add-migration/gates.yaml +30 -0
  18. package/.ai/blueprints/add-migration/input.schema.json +23 -0
  19. package/.ai/blueprints/add-migration/plan.schema.json +54 -0
  20. package/.ai/blueprints/add-migration/required-files.yaml +18 -0
  21. package/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  22. package/.ai/blueprints/add-migration/steps.yaml +62 -0
  23. package/.ai/blueprints/author-spec/README.md +5 -0
  24. package/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  25. package/.ai/blueprints/author-spec/blueprint.json +14 -0
  26. package/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  27. package/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  28. package/.ai/blueprints/author-spec/gates.yaml +13 -0
  29. package/.ai/blueprints/author-spec/input.schema.json +20 -0
  30. package/.ai/blueprints/author-spec/plan.schema.json +14 -0
  31. package/.ai/blueprints/author-spec/required-files.yaml +6 -0
  32. package/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  33. package/.ai/blueprints/author-spec/steps.yaml +28 -0
  34. package/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  35. package/.ai/blueprints/bug-fix/README.md +5 -0
  36. package/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  37. package/.ai/blueprints/bug-fix/blueprint.json +14 -0
  38. package/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  39. package/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  40. package/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  41. package/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  42. package/.ai/blueprints/bug-fix/gates.yaml +30 -0
  43. package/.ai/blueprints/bug-fix/input.schema.json +25 -0
  44. package/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  45. package/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  46. package/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  47. package/.ai/blueprints/bug-fix/steps.yaml +51 -0
  48. package/.ai/blueprints/core-extend/README.md +5 -0
  49. package/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  50. package/.ai/blueprints/core-extend/blueprint.json +14 -0
  51. package/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  52. package/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  53. package/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  54. package/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  55. package/.ai/blueprints/core-extend/gates.yaml +16 -0
  56. package/.ai/blueprints/core-extend/input.schema.json +54 -0
  57. package/.ai/blueprints/core-extend/plan.schema.json +39 -0
  58. package/.ai/blueprints/core-extend/required-files.yaml +36 -0
  59. package/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  60. package/.ai/blueprints/core-extend/steps.yaml +54 -0
  61. package/.ai/blueprints/edit-module/README.md +9 -0
  62. package/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  63. package/.ai/blueprints/edit-module/blueprint.json +20 -0
  64. package/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  65. package/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  66. package/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  67. package/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  68. package/.ai/blueprints/edit-module/gates.yaml +30 -0
  69. package/.ai/blueprints/edit-module/input.schema.json +31 -0
  70. package/.ai/blueprints/edit-module/plan.schema.json +65 -0
  71. package/.ai/blueprints/edit-module/required-files.yaml +80 -0
  72. package/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  73. package/.ai/blueprints/edit-module/steps.yaml +115 -0
  74. package/.ai/blueprints/new-module/README.md +7 -0
  75. package/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  76. package/.ai/blueprints/new-module/blueprint.json +20 -0
  77. package/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  78. package/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  79. package/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  80. package/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  81. package/.ai/blueprints/new-module/gates.yaml +30 -0
  82. package/.ai/blueprints/new-module/input.schema.json +21 -0
  83. package/.ai/blueprints/new-module/plan.schema.json +58 -0
  84. package/.ai/blueprints/new-module/required-files.yaml +73 -0
  85. package/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  86. package/.ai/blueprints/new-module/steps.yaml +138 -0
  87. package/.ai/blueprints/release/README.md +5 -0
  88. package/.ai/blueprints/release/allowed-paths.yaml +19 -0
  89. package/.ai/blueprints/release/blueprint.json +14 -0
  90. package/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  91. package/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  92. package/.ai/blueprints/release/examples/valid/input.json +5 -0
  93. package/.ai/blueprints/release/examples/valid/plan.json +20 -0
  94. package/.ai/blueprints/release/gates.yaml +20 -0
  95. package/.ai/blueprints/release/input.schema.json +24 -0
  96. package/.ai/blueprints/release/plan.schema.json +46 -0
  97. package/.ai/blueprints/release/required-files.yaml +19 -0
  98. package/.ai/blueprints/release/spec-requirements.yaml +8 -0
  99. package/.ai/blueprints/release/steps.yaml +47 -0
  100. package/.ai/blueprints/security-review/README.md +5 -0
  101. package/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  102. package/.ai/blueprints/security-review/blueprint.json +14 -0
  103. package/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  104. package/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  105. package/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  106. package/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  107. package/.ai/blueprints/security-review/gates.yaml +22 -0
  108. package/.ai/blueprints/security-review/input.schema.json +20 -0
  109. package/.ai/blueprints/security-review/plan.schema.json +65 -0
  110. package/.ai/blueprints/security-review/required-files.yaml +6 -0
  111. package/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  112. package/.ai/blueprints/security-review/steps.yaml +38 -0
  113. package/.ai/policies/capabilities.yaml +164 -0
  114. package/.ai/policies/model-routing.yaml +72 -0
  115. package/.ai/policies/path-ownership.yaml +65 -0
  116. package/.ai/policies/task-budgets.yaml +37 -0
  117. package/.ai/references/catalog/LICENSE +21 -0
  118. package/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  119. package/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  120. package/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  121. package/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  122. package/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  123. package/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  124. package/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  125. package/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  126. package/.ai/references/catalog/migrations/README.md +3 -0
  127. package/.ai/references/catalog/module.json +27 -0
  128. package/.ai/references/catalog/package.json +49 -0
  129. package/.ai/references/catalog/spec/module.yaml +86 -0
  130. package/.ai/references/catalog/src/acl/permissions.ts +6 -0
  131. package/.ai/references/catalog/src/agent/tools.ts +164 -0
  132. package/.ai/references/catalog/src/api/endpoints.ts +243 -0
  133. package/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  134. package/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  135. package/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  136. package/.ai/references/catalog/src/client/api.ts +111 -0
  137. package/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  138. package/.ai/references/catalog/src/client/index.ts +18 -0
  139. package/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  140. package/.ai/references/catalog/src/client/state.ts +24 -0
  141. package/.ai/references/catalog/src/domain/types.ts +32 -0
  142. package/.ai/references/catalog/src/domain/variables.ts +111 -0
  143. package/.ai/references/catalog/src/index.ts +31 -0
  144. package/.ai/references/catalog/src/platform.ts +35 -0
  145. package/.ai/references/catalog/src/server/index.ts +4 -0
  146. package/.ai/references/catalog/src/server/runtime.ts +86 -0
  147. package/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  148. package/.ai/references/catalog/src/services/database-repository.ts +440 -0
  149. package/.ai/references/catalog/src/services/index.ts +4 -0
  150. package/.ai/references/catalog/src/services/migration.ts +171 -0
  151. package/.ai/references/catalog/src/services/repository.ts +36 -0
  152. package/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  153. package/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  154. package/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  155. package/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  156. package/.ai/references/catalog/tests/migrations.test.ts +149 -0
  157. package/.ai/references/catalog/tests/module.test.ts +271 -0
  158. package/.ai/references/catalog/tests/support/database.ts +76 -0
  159. package/.ai/references/catalog/translations/en.json +101 -0
  160. package/.ai/references/catalog/translations/pl.json +101 -0
  161. package/.ai/references/catalog/tsconfig.json +15 -0
  162. package/.ai/references/catalog/vitest.config.ts +16 -0
  163. package/.ai/references/catalog.provenance.json +55 -0
  164. package/.ai/skills/README.md +36 -0
  165. package/.ai/skills/agent-tool-design/SKILL.md +209 -0
  166. package/.ai/skills/auth-security-review/SKILL.md +96 -0
  167. package/.ai/skills/auto-review/SKILL.md +112 -0
  168. package/.ai/skills/bug-hunt/SKILL.md +110 -0
  169. package/.ai/skills/business-agent-design/SKILL.md +188 -0
  170. package/.ai/skills/cli-extension/SKILL.md +114 -0
  171. package/.ai/skills/core-extend/SKILL.md +104 -0
  172. package/.ai/skills/database-adapter/SKILL.md +204 -0
  173. package/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  174. package/.ai/skills/migration-authoring/SKILL.md +167 -0
  175. package/.ai/skills/module-new/SKILL.md +180 -0
  176. package/.ai/skills/module-update/SKILL.md +100 -0
  177. package/.ai/skills/perf-audit/SKILL.md +105 -0
  178. package/.ai/skills/release-eject-pr/SKILL.md +113 -0
  179. package/.ai/skills/spec-approval/SKILL.md +112 -0
  180. package/.ai/skills/test-hardening/SKILL.md +86 -0
  181. package/.ai/skills/translations-i18n/SKILL.md +85 -0
  182. package/.ai/skills/ux-design/SKILL.md +97 -0
  183. package/.ai/skills/variables/SKILL.md +164 -0
  184. package/.ai/skills/workflow-development/SKILL.md +199 -0
  185. package/AGENTS.md +59 -0
  186. package/LICENSE +21 -0
  187. package/README.md +24 -0
  188. package/docs/agent-contract.md +45 -0
  189. package/docs/design-system.md +217 -0
  190. package/modules/agents/migrations/0001_agents_core.down.sql +4 -0
  191. package/modules/agents/migrations/0001_agents_core.up.sql +106 -0
  192. package/modules/agents/migrations/0002_provider_connections.down.sql +1 -0
  193. package/modules/agents/migrations/0002_provider_connections.up.sql +34 -0
  194. package/modules/agents/migrations/0003_resource_audit.down.sql +1 -0
  195. package/modules/agents/migrations/0003_resource_audit.up.sql +25 -0
  196. package/modules/agents/migrations/0004_run_grants.down.sql +1 -0
  197. package/modules/agents/migrations/0004_run_grants.up.sql +19 -0
  198. package/modules/agents/migrations/0005_long_running_limits.down.sql +2 -0
  199. package/modules/agents/migrations/0005_long_running_limits.up.sql +14 -0
  200. package/modules/agents/migrations/0006_agent_skills.down.sql +3 -0
  201. package/modules/agents/migrations/0006_agent_skills.up.sql +44 -0
  202. package/modules/agents/migrations/0007_model_readiness.down.sql +1 -0
  203. package/modules/agents/migrations/0007_model_readiness.up.sql +18 -0
  204. package/modules/agents/migrations/0008_output_limits.down.sql +2 -0
  205. package/modules/agents/migrations/0008_output_limits.up.sql +8 -0
  206. package/modules/agents/migrations/0009_agent_audit_v3.down.sql +1 -0
  207. package/modules/agents/migrations/0009_agent_audit_v3.up.sql +25 -0
  208. package/modules/agents/migrations/0012_agent_run_costs.down.sql +3 -0
  209. package/modules/agents/migrations/0012_agent_run_costs.up.sql +32 -0
  210. package/modules/agents/migrations/0013_workflow_prerequisites.down.sql +3 -0
  211. package/modules/agents/migrations/0013_workflow_prerequisites.up.sql +97 -0
  212. package/modules/agents/migrations/0014_agent_action_audit.down.sql +1 -0
  213. package/modules/agents/migrations/0014_agent_action_audit.up.sql +25 -0
  214. package/modules/agents/migrations/0015_agent_run_actors.down.sql +1 -0
  215. package/modules/agents/migrations/0015_agent_run_actors.up.sql +17 -0
  216. package/modules/agents/migrations/0016_module_owned_agents.down.sql +6 -0
  217. package/modules/agents/migrations/0016_module_owned_agents.up.sql +68 -0
  218. package/modules/agents/migrations/0017_agent_authorization_subjects.down.sql +2 -0
  219. package/modules/agents/migrations/0017_agent_authorization_subjects.up.sql +18 -0
  220. package/modules/agents/migrations/0018_agent_child_tenant_isolation.down.sql +12 -0
  221. package/modules/agents/migrations/0018_agent_child_tenant_isolation.up.sql +88 -0
  222. package/modules/agents/migrations/0019_background_recovery_grants.down.sql +6 -0
  223. package/modules/agents/migrations/0019_background_recovery_grants.up.sql +17 -0
  224. package/modules/agents/migrations/0020_provider_summary_role.down.sql +2 -0
  225. package/modules/agents/migrations/0020_provider_summary_role.up.sql +17 -0
  226. package/modules/agents/migrations/0021_agents_agent_reconciliation_role.down.sql +4 -0
  227. package/modules/agents/migrations/0021_agents_agent_reconciliation_role.up.sql +7 -0
  228. package/modules/agents/migrations/README.md +39 -0
  229. package/modules/agents/module.json +38 -0
  230. package/modules/agents/package.json +56 -0
  231. package/modules/agents/spec/module.yaml +271 -0
  232. package/modules/agents/spec/procedures-compatibility.md +63 -0
  233. package/modules/agents/src/acl/permissions.ts +13 -0
  234. package/modules/agents/src/api/endpoints.ts +1122 -0
  235. package/modules/agents/src/cli/commands.json +29 -0
  236. package/modules/agents/src/cli/index.ts +162 -0
  237. package/modules/agents/src/client/AgentDashboardWidget.tsrx +32 -0
  238. package/modules/agents/src/client/AgentDefinitionForm.tsrx +362 -0
  239. package/modules/agents/src/client/AgentDefinitionList.tsrx +127 -0
  240. package/modules/agents/src/client/AgentPlaygroundView.tsrx +246 -0
  241. package/modules/agents/src/client/AgentProcedureForm.tsrx +182 -0
  242. package/modules/agents/src/client/AgentProcedureList.tsrx +118 -0
  243. package/modules/agents/src/client/AgentProceduresView.tsrx +253 -0
  244. package/modules/agents/src/client/AgentProvidersView.tsrx +248 -0
  245. package/modules/agents/src/client/AgentRunsView.tsrx +304 -0
  246. package/modules/agents/src/client/AgentUsageView.tsrx +233 -0
  247. package/modules/agents/src/client/AgentsView.tsrx +339 -0
  248. package/modules/agents/src/client/ModuleAgentBindingForm.tsrx +239 -0
  249. package/modules/agents/src/client/ModuleAgentList.tsrx +122 -0
  250. package/modules/agents/src/client/ProviderConnectionForm.tsrx +273 -0
  251. package/modules/agents/src/client/ProviderConnectionList.tsrx +229 -0
  252. package/modules/agents/src/client/ProviderModelEditor.tsrx +313 -0
  253. package/modules/agents/src/client/RunTimeline.tsrx +212 -0
  254. package/modules/agents/src/client/agents.css +349 -0
  255. package/modules/agents/src/client/api.ts +373 -0
  256. package/modules/agents/src/client/contribution.tsrx +181 -0
  257. package/modules/agents/src/client/index.ts +21 -0
  258. package/modules/agents/src/client/presentation.ts +162 -0
  259. package/modules/agents/src/client/state.ts +100 -0
  260. package/modules/agents/src/domain/context-variables.ts +58 -0
  261. package/modules/agents/src/domain/run-timeline.ts +145 -0
  262. package/modules/agents/src/domain/types.ts +481 -0
  263. package/modules/agents/src/index.ts +88 -0
  264. package/modules/agents/src/platform.ts +82 -0
  265. package/modules/agents/src/server/action-execution.ts +811 -0
  266. package/modules/agents/src/server/define-agent.ts +170 -0
  267. package/modules/agents/src/server/index.ts +67 -0
  268. package/modules/agents/src/server/run-execution.ts +231 -0
  269. package/modules/agents/src/server/run-queue.ts +96 -0
  270. package/modules/agents/src/server/runtime.ts +448 -0
  271. package/modules/agents/src/server/tools.ts +18 -0
  272. package/modules/agents/src/services/agent-service.ts +2167 -0
  273. package/modules/agents/src/services/credential-vault.ts +133 -0
  274. package/modules/agents/src/services/database-repository.ts +2786 -0
  275. package/modules/agents/src/services/index.ts +33 -0
  276. package/modules/agents/src/services/migration.ts +1152 -0
  277. package/modules/agents/src/services/module-agent-preflight.ts +76 -0
  278. package/modules/agents/src/services/outbound-policy.ts +136 -0
  279. package/modules/agents/src/services/provider-broker.ts +80 -0
  280. package/modules/agents/src/services/provider-repository.ts +589 -0
  281. package/modules/agents/src/services/provider-service.ts +968 -0
  282. package/modules/agents/src/services/repository.ts +294 -0
  283. package/modules/agents/src/services/run-grant.ts +305 -0
  284. package/modules/agents/src/services/usage-service.ts +119 -0
  285. package/modules/agents/src/services/worker.ts +442 -0
  286. package/modules/agents/src/settings.ts +244 -0
  287. package/modules/agents/translations/en.json +403 -0
  288. package/modules/agents/translations/pl.json +403 -0
  289. package/modules/auth/README.md +215 -0
  290. package/modules/auth/migrations/0001_auth_core.up.sql +108 -0
  291. package/modules/auth/migrations/0002_bundled_module_scopes.up.sql +26 -0
  292. package/modules/auth/migrations/0003_agent_core_scopes.up.sql +12 -0
  293. package/modules/auth/migrations/0004_owner_only_development_scopes.up.sql +8 -0
  294. package/modules/auth/migrations/0005_agent_provider_scopes.up.sql +13 -0
  295. package/modules/auth/migrations/0006_tenant_slug.up.sql +6 -0
  296. package/modules/auth/migrations/0007_sandbox_scopes.up.sql +13 -0
  297. package/modules/auth/migrations/0008_api_tokens.up.sql +43 -0
  298. package/modules/auth/migrations/0009_module_settings.up.sql +24 -0
  299. package/modules/auth/migrations/0010_sign_in_lockout.up.sql +11 -0
  300. package/modules/auth/migrations/0011_session_ids_and_password_reset.up.sql +7 -0
  301. package/modules/auth/migrations/0012_roles.up.sql +31 -0
  302. package/modules/auth/migrations/0013_audit.up.sql +27 -0
  303. package/modules/auth/migrations/0014_audit_actor.up.sql +4 -0
  304. package/modules/auth/migrations/0015_account_security.up.sql +81 -0
  305. package/modules/auth/module.json +26 -0
  306. package/modules/auth/package.json +61 -0
  307. package/modules/auth/spec/module.yaml +190 -0
  308. package/modules/auth/src/acl/scopes.ts +86 -0
  309. package/modules/auth/src/api/attempt-limiter.ts +42 -0
  310. package/modules/auth/src/api/client-address.ts +14 -0
  311. package/modules/auth/src/api/cookies.ts +52 -0
  312. package/modules/auth/src/api/origin.ts +40 -0
  313. package/modules/auth/src/cli/commands.json +74 -0
  314. package/modules/auth/src/cli/database.ts +51 -0
  315. package/modules/auth/src/cli/greenfield.ts +173 -0
  316. package/modules/auth/src/cli/index.ts +184 -0
  317. package/modules/auth/src/cli/provisioning.ts +211 -0
  318. package/modules/auth/src/client/AuthBackdrop.tsrx +137 -0
  319. package/modules/auth/src/client/AuthenticationCore.tsrx +630 -0
  320. package/modules/auth/src/client/PasswordChangeRequired.tsrx +145 -0
  321. package/modules/auth/src/client/PublicAuthFlows.tsrx +379 -0
  322. package/modules/auth/src/client/api.ts +464 -0
  323. package/modules/auth/src/client/audit/AuditView.tsrx +357 -0
  324. package/modules/auth/src/client/audit/api.ts +164 -0
  325. package/modules/auth/src/client/audit/audit-columns.tsrx +47 -0
  326. package/modules/auth/src/client/audit/audit-row.ts +38 -0
  327. package/modules/auth/src/client/auth.css +483 -0
  328. package/modules/auth/src/client/contribution.tsrx +100 -0
  329. package/modules/auth/src/client/index.ts +32 -0
  330. package/modules/auth/src/client/security/SecurityView.tsrx +253 -0
  331. package/modules/auth/src/client/settings/PreferencesCard.tsrx +65 -0
  332. package/modules/auth/src/client/settings/SettingsView.tsrx +212 -0
  333. package/modules/auth/src/client/settings/WorkspaceCard.tsrx +59 -0
  334. package/modules/auth/src/client/settings/api.ts +115 -0
  335. package/modules/auth/src/client/slug.ts +26 -0
  336. package/modules/auth/src/client/state.ts +115 -0
  337. package/modules/auth/src/client/tokens/ApiTokenForm.tsrx +153 -0
  338. package/modules/auth/src/client/tokens/ApiTokenSecret.tsrx +35 -0
  339. package/modules/auth/src/client/tokens/ApiTokensView.tsrx +216 -0
  340. package/modules/auth/src/client/tokens/api.ts +59 -0
  341. package/modules/auth/src/client/tokens/state.ts +26 -0
  342. package/modules/auth/src/client/tokens/token-columns.tsrx +64 -0
  343. package/modules/auth/src/client/translations.ts +23 -0
  344. package/modules/auth/src/domain/types.ts +152 -0
  345. package/modules/auth/src/index.ts +42 -0
  346. package/modules/auth/src/middleware/authentication.ts +97 -0
  347. package/modules/auth/src/platform.ts +0 -0
  348. package/modules/auth/src/server/actor.ts +12 -0
  349. package/modules/auth/src/server/audit-endpoints.ts +51 -0
  350. package/modules/auth/src/server/composition.ts +9 -0
  351. package/modules/auth/src/server/endpoints.ts +893 -0
  352. package/modules/auth/src/server/http.ts +153 -0
  353. package/modules/auth/src/server/index.ts +37 -0
  354. package/modules/auth/src/server/roles-endpoints.ts +120 -0
  355. package/modules/auth/src/server/runtime.ts +705 -0
  356. package/modules/auth/src/server/session-endpoints.ts +86 -0
  357. package/modules/auth/src/server/session-security.ts +62 -0
  358. package/modules/auth/src/server/token-endpoints.ts +106 -0
  359. package/modules/auth/src/services/auth-service-error.ts +11 -0
  360. package/modules/auth/src/services/auth-service.ts +2309 -0
  361. package/modules/auth/src/services/database-repository.ts +1825 -0
  362. package/modules/auth/src/services/mail-delivery.ts +27 -0
  363. package/modules/auth/src/services/migration.ts +591 -0
  364. package/modules/auth/src/services/password.ts +115 -0
  365. package/modules/auth/src/services/repository.ts +395 -0
  366. package/modules/auth/src/services/settings-store.ts +92 -0
  367. package/modules/auth/src/services/totp.ts +135 -0
  368. package/modules/auth/src/services/validation.ts +186 -0
  369. package/modules/auth/src/settings.ts +150 -0
  370. package/modules/auth/translations/en.json +263 -0
  371. package/modules/auth/translations/pl.json +263 -0
  372. package/modules/automations/migrations/0001_automations_core.down.sql +10 -0
  373. package/modules/automations/migrations/0001_automations_core.up.sql +76 -0
  374. package/modules/automations/migrations/0002_automations_targets.down.sql +9 -0
  375. package/modules/automations/migrations/0002_automations_targets.up.sql +37 -0
  376. package/modules/automations/migrations/0003_automations_scheduler_role.down.sql +3 -0
  377. package/modules/automations/migrations/0003_automations_scheduler_role.up.sql +19 -0
  378. package/modules/automations/migrations/0004_automations_trigger_routing_role.down.sql +3 -0
  379. package/modules/automations/migrations/0004_automations_trigger_routing_role.up.sql +18 -0
  380. package/modules/automations/migrations/README.md +3 -0
  381. package/modules/automations/module.json +31 -0
  382. package/modules/automations/package.json +55 -0
  383. package/modules/automations/spec/module.yaml +93 -0
  384. package/modules/automations/src/acl/permissions.ts +10 -0
  385. package/modules/automations/src/api/endpoints.ts +480 -0
  386. package/modules/automations/src/client/AutomationScheduleForm.tsrx +218 -0
  387. package/modules/automations/src/client/AutomationSchedulesView.tsrx +339 -0
  388. package/modules/automations/src/client/AutomationTriggerForm.tsrx +190 -0
  389. package/modules/automations/src/client/AutomationTriggersView.tsrx +329 -0
  390. package/modules/automations/src/client/api.ts +148 -0
  391. package/modules/automations/src/client/contribution.tsrx +70 -0
  392. package/modules/automations/src/client/index.ts +20 -0
  393. package/modules/automations/src/client/presentation.ts +38 -0
  394. package/modules/automations/src/client/state.ts +40 -0
  395. package/modules/automations/src/client/target-selection.ts +44 -0
  396. package/modules/automations/src/domain/cadence.ts +44 -0
  397. package/modules/automations/src/domain/types.ts +110 -0
  398. package/modules/automations/src/domain/variables.ts +178 -0
  399. package/modules/automations/src/index.ts +39 -0
  400. package/modules/automations/src/platform.ts +73 -0
  401. package/modules/automations/src/server/execution.ts +77 -0
  402. package/modules/automations/src/server/index.ts +39 -0
  403. package/modules/automations/src/server/runtime.ts +218 -0
  404. package/modules/automations/src/server/targets.ts +116 -0
  405. package/modules/automations/src/services/automations-service.ts +10 -0
  406. package/modules/automations/src/services/database-repository.ts +763 -0
  407. package/modules/automations/src/services/index.ts +5 -0
  408. package/modules/automations/src/services/migration.ts +223 -0
  409. package/modules/automations/src/services/repository.ts +119 -0
  410. package/modules/automations/src/services/schedule-service.ts +837 -0
  411. package/modules/automations/src/services/secret-vault.ts +124 -0
  412. package/modules/automations/src/services/trigger-service.ts +648 -0
  413. package/modules/automations/src/settings.ts +69 -0
  414. package/modules/automations/translations/en.json +126 -0
  415. package/modules/automations/translations/pl.json +126 -0
  416. package/modules/automations-workflows-integration/module.json +27 -0
  417. package/modules/automations-workflows-integration/package.json +48 -0
  418. package/modules/automations-workflows-integration/spec/module.yaml +93 -0
  419. package/modules/automations-workflows-integration/src/index.ts +14 -0
  420. package/modules/automations-workflows-integration/src/platform.ts +24 -0
  421. package/modules/automations-workflows-integration/src/server/adapter.ts +272 -0
  422. package/modules/automations-workflows-integration/src/server/index.ts +11 -0
  423. package/modules/automations-workflows-integration/src/server/tools.ts +0 -0
  424. package/modules/automations-workflows-integration/translations/en.json +21 -0
  425. package/modules/automations-workflows-integration/translations/pl.json +21 -0
  426. package/modules/profile/migrations/0001_profile_core.down.sql +1 -0
  427. package/modules/profile/migrations/0001_profile_core.up.sql +13 -0
  428. package/modules/profile/migrations/0002_profile_language.down.sql +1 -0
  429. package/modules/profile/migrations/0002_profile_language.up.sql +12 -0
  430. package/modules/profile/migrations/README.md +3 -0
  431. package/modules/profile/module.json +23 -0
  432. package/modules/profile/package.json +55 -0
  433. package/modules/profile/spec/module.yaml +136 -0
  434. package/modules/profile/src/acl/permissions.ts +5 -0
  435. package/modules/profile/src/api/endpoints.ts +184 -0
  436. package/modules/profile/src/client/DisplayNameCard.tsrx +59 -0
  437. package/modules/profile/src/client/LanguageCard.tsrx +89 -0
  438. package/modules/profile/src/client/PasswordCard.tsrx +79 -0
  439. package/modules/profile/src/client/ProfileView.tsrx +192 -0
  440. package/modules/profile/src/client/SessionsCard.tsrx +158 -0
  441. package/modules/profile/src/client/api.ts +134 -0
  442. package/modules/profile/src/client/contribution.tsrx +61 -0
  443. package/modules/profile/src/client/index.ts +15 -0
  444. package/modules/profile/src/client/password.ts +41 -0
  445. package/modules/profile/src/client/sessions-api.ts +52 -0
  446. package/modules/profile/src/client/state.ts +23 -0
  447. package/modules/profile/src/domain/types.ts +21 -0
  448. package/modules/profile/src/index.ts +21 -0
  449. package/modules/profile/src/platform.ts +23 -0
  450. package/modules/profile/src/server/index.ts +7 -0
  451. package/modules/profile/src/server/runtime.ts +84 -0
  452. package/modules/profile/src/services/database-repository.ts +154 -0
  453. package/modules/profile/src/services/index.ts +6 -0
  454. package/modules/profile/src/services/migration.ts +58 -0
  455. package/modules/profile/src/services/profile-service.ts +114 -0
  456. package/modules/profile/src/services/repository.ts +13 -0
  457. package/modules/profile/translations/en.json +58 -0
  458. package/modules/profile/translations/pl.json +58 -0
  459. package/modules/sandbox/migrations/0001_sandbox_core.down.sql +6 -0
  460. package/modules/sandbox/migrations/0001_sandbox_core.up.sql +70 -0
  461. package/modules/sandbox/migrations/0002_sandbox_session_lifecycle.down.sql +36 -0
  462. package/modules/sandbox/migrations/0002_sandbox_session_lifecycle.up.sql +37 -0
  463. package/modules/sandbox/migrations/README.md +7 -0
  464. package/modules/sandbox/module.json +31 -0
  465. package/modules/sandbox/package.json +56 -0
  466. package/modules/sandbox/spec/module.yaml +111 -0
  467. package/modules/sandbox/src/acl/permissions.ts +18 -0
  468. package/modules/sandbox/src/api/endpoints.ts +439 -0
  469. package/modules/sandbox/src/cli/commands.json +84 -0
  470. package/modules/sandbox/src/cli/index.ts +450 -0
  471. package/modules/sandbox/src/client/SandboxAccessView.tsrx +191 -0
  472. package/modules/sandbox/src/client/SandboxGrantForm.tsrx +134 -0
  473. package/modules/sandbox/src/client/SandboxGrantList.tsrx +83 -0
  474. package/modules/sandbox/src/client/SandboxSessionList.tsrx +56 -0
  475. package/modules/sandbox/src/client/api.ts +87 -0
  476. package/modules/sandbox/src/client/contribution.tsrx +47 -0
  477. package/modules/sandbox/src/client/index.ts +18 -0
  478. package/modules/sandbox/src/client/presentation.ts +47 -0
  479. package/modules/sandbox/src/client/state.ts +20 -0
  480. package/modules/sandbox/src/domain/types.ts +125 -0
  481. package/modules/sandbox/src/index.ts +33 -0
  482. package/modules/sandbox/src/platform.ts +28 -0
  483. package/modules/sandbox/src/server/index.ts +20 -0
  484. package/modules/sandbox/src/server/runtime.ts +119 -0
  485. package/modules/sandbox/src/services/database-repository.ts +567 -0
  486. package/modules/sandbox/src/services/directory.ts +33 -0
  487. package/modules/sandbox/src/services/index.ts +19 -0
  488. package/modules/sandbox/src/services/migration.ts +145 -0
  489. package/modules/sandbox/src/services/repository.ts +55 -0
  490. package/modules/sandbox/src/services/sandbox-service-error.ts +10 -0
  491. package/modules/sandbox/src/services/sandbox-service.ts +533 -0
  492. package/modules/sandbox/translations/en.json +77 -0
  493. package/modules/sandbox/translations/pl.json +77 -0
  494. package/modules/system/module.json +18 -0
  495. package/modules/system/package.json +56 -0
  496. package/modules/system/spec/module.yaml +104 -0
  497. package/modules/system/src/acl/permissions.ts +10 -0
  498. package/modules/system/src/client/ModuleSettingRow.tsrx +191 -0
  499. package/modules/system/src/client/ModuleSettingsSection.tsrx +155 -0
  500. package/modules/system/src/client/ModulesView.tsrx +241 -0
  501. package/modules/system/src/client/api.ts +36 -0
  502. package/modules/system/src/client/contribution.tsrx +48 -0
  503. package/modules/system/src/client/index.ts +26 -0
  504. package/modules/system/src/client/module-columns.tsrx +66 -0
  505. package/modules/system/src/client/overview-api.ts +30 -0
  506. package/modules/system/src/client/setting-copy.ts +30 -0
  507. package/modules/system/src/client/settings-api.ts +57 -0
  508. package/modules/system/src/client/state.ts +16 -0
  509. package/modules/system/src/index.ts +11 -0
  510. package/modules/system/src/platform.ts +11 -0
  511. package/modules/system/src/server/endpoints.ts +372 -0
  512. package/modules/system/src/server/index.ts +11 -0
  513. package/modules/system/src/server/module-catalog.ts +117 -0
  514. package/modules/system/translations/en.json +64 -0
  515. package/modules/system/translations/pl.json +64 -0
  516. package/modules/users/migrations/README.md +3 -0
  517. package/modules/users/module.json +27 -0
  518. package/modules/users/package.json +54 -0
  519. package/modules/users/spec/module.yaml +79 -0
  520. package/modules/users/src/acl/permissions.ts +6 -0
  521. package/modules/users/src/api/endpoints.ts +222 -0
  522. package/modules/users/src/client/InviteMemberForm.tsrx +62 -0
  523. package/modules/users/src/client/MemberDrawer.tsrx +298 -0
  524. package/modules/users/src/client/RoleForm.tsrx +140 -0
  525. package/modules/users/src/client/RolesView.tsrx +288 -0
  526. package/modules/users/src/client/UserAccountForm.tsrx +86 -0
  527. package/modules/users/src/client/UsersView.tsrx +486 -0
  528. package/modules/users/src/client/api.ts +172 -0
  529. package/modules/users/src/client/contribution.tsrx +92 -0
  530. package/modules/users/src/client/index.ts +19 -0
  531. package/modules/users/src/client/member-columns.tsrx +52 -0
  532. package/modules/users/src/client/roles-api.ts +90 -0
  533. package/modules/users/src/client/state.ts +42 -0
  534. package/modules/users/src/index.ts +27 -0
  535. package/modules/users/src/platform.ts +11 -0
  536. package/modules/users/src/services/index.ts +2 -0
  537. package/modules/users/src/services/users-service.ts +142 -0
  538. package/modules/users/translations/en.json +133 -0
  539. package/modules/users/translations/pl.json +133 -0
  540. package/modules/workflows/migrations/0001_workflows_core.down.sql +19 -0
  541. package/modules/workflows/migrations/0001_workflows_core.up.sql +228 -0
  542. package/modules/workflows/migrations/0002_workflows_authorization_subject.down.sql +1 -0
  543. package/modules/workflows/migrations/0002_workflows_authorization_subject.up.sql +9 -0
  544. package/modules/workflows/migrations/0003_workflows_worker_role.up.sql +34 -0
  545. package/modules/workflows/migrations/README.md +7 -0
  546. package/modules/workflows/module.json +31 -0
  547. package/modules/workflows/package.json +55 -0
  548. package/modules/workflows/spec/module.yaml +227 -0
  549. package/modules/workflows/src/acl/permissions.ts +10 -0
  550. package/modules/workflows/src/api/endpoints.ts +852 -0
  551. package/modules/workflows/src/client/WorkflowCanvas.tsrx +819 -0
  552. package/modules/workflows/src/client/WorkflowCreateForm.tsrx +111 -0
  553. package/modules/workflows/src/client/WorkflowEditor.tsrx +443 -0
  554. package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +169 -0
  555. package/modules/workflows/src/client/WorkflowFixtureDrawer.tsrx +206 -0
  556. package/modules/workflows/src/client/WorkflowInputForm.tsrx +125 -0
  557. package/modules/workflows/src/client/WorkflowInputSchemaEditor.tsrx +185 -0
  558. package/modules/workflows/src/client/WorkflowInspector.tsrx +471 -0
  559. package/modules/workflows/src/client/WorkflowLiveConfirmation.tsrx +67 -0
  560. package/modules/workflows/src/client/WorkflowMappingEditor.tsrx +283 -0
  561. package/modules/workflows/src/client/WorkflowNodePalette.tsrx +50 -0
  562. package/modules/workflows/src/client/WorkflowPointerField.tsrx +178 -0
  563. package/modules/workflows/src/client/WorkflowRevisionReview.tsrx +49 -0
  564. package/modules/workflows/src/client/WorkflowRunInspector.tsrx +143 -0
  565. package/modules/workflows/src/client/WorkflowTestPanel.tsrx +109 -0
  566. package/modules/workflows/src/client/WorkflowValidationPanel.tsrx +139 -0
  567. package/modules/workflows/src/client/WorkflowsView.tsrx +1060 -0
  568. package/modules/workflows/src/client/api.ts +312 -0
  569. package/modules/workflows/src/client/canvas-model.ts +472 -0
  570. package/modules/workflows/src/client/connected-node.ts +71 -0
  571. package/modules/workflows/src/client/contribution.tsrx +57 -0
  572. package/modules/workflows/src/client/create-model.ts +15 -0
  573. package/modules/workflows/src/client/draft-repair.ts +86 -0
  574. package/modules/workflows/src/client/index.ts +19 -0
  575. package/modules/workflows/src/client/input-model.ts +124 -0
  576. package/modules/workflows/src/client/mapping-model.ts +110 -0
  577. package/modules/workflows/src/client/pointer-model.ts +123 -0
  578. package/modules/workflows/src/client/presentation.ts +62 -0
  579. package/modules/workflows/src/client/run-actions.ts +19 -0
  580. package/modules/workflows/src/client/state.ts +57 -0
  581. package/modules/workflows/src/client/viewport.ts +107 -0
  582. package/modules/workflows/src/client/workflows.css +899 -0
  583. package/modules/workflows/src/domain/events.ts +399 -0
  584. package/modules/workflows/src/domain/graph.ts +1136 -0
  585. package/modules/workflows/src/domain/types.ts +563 -0
  586. package/modules/workflows/src/index.ts +27 -0
  587. package/modules/workflows/src/platform.ts +49 -0
  588. package/modules/workflows/src/server/index.ts +13 -0
  589. package/modules/workflows/src/server/runtime.ts +266 -0
  590. package/modules/workflows/src/services/cursor-codec.ts +62 -0
  591. package/modules/workflows/src/services/database-repository.ts +2407 -0
  592. package/modules/workflows/src/services/index.ts +6 -0
  593. package/modules/workflows/src/services/migration.ts +375 -0
  594. package/modules/workflows/src/services/payload-codec.ts +264 -0
  595. package/modules/workflows/src/services/repository.ts +285 -0
  596. package/modules/workflows/src/services/simulation.ts +359 -0
  597. package/modules/workflows/src/services/worker.ts +1196 -0
  598. package/modules/workflows/src/services/workflows-service.ts +1395 -0
  599. package/modules/workflows/translations/en.json +348 -0
  600. package/modules/workflows/translations/pl.json +348 -0
  601. package/modules.json +41 -0
  602. package/package.json +125 -0
  603. package/packages/ai-provider/package.json +46 -0
  604. package/packages/ai-provider/src/catalog.ts +102 -0
  605. package/packages/ai-provider/src/errors.ts +110 -0
  606. package/packages/ai-provider/src/index.ts +33 -0
  607. package/packages/ai-provider/src/model.ts +198 -0
  608. package/packages/ai-provider/src/pricing.ts +73 -0
  609. package/packages/cli-protocol/package.json +40 -0
  610. package/packages/cli-protocol/src/index.ts +113 -0
  611. package/packages/client/package.json +46 -0
  612. package/packages/client/src/ApplicationShell.tsrx +374 -0
  613. package/packages/client/src/contributions.ts +172 -0
  614. package/packages/client/src/i18n/index.ts +30 -0
  615. package/packages/client/src/i18n/locale.ts +25 -0
  616. package/packages/client/src/i18n/locales/en.json +66 -0
  617. package/packages/client/src/i18n/locales/pl.json +66 -0
  618. package/packages/client/src/i18n/runtime.ts +201 -0
  619. package/packages/client/src/i18n/translations.ts +88 -0
  620. package/packages/client/src/index.ts +55 -0
  621. package/packages/client/src/routing.ts +9 -0
  622. package/packages/client/src/shell/BrandMark.tsrx +10 -0
  623. package/packages/client/src/shell/CommandPalette.tsrx +79 -0
  624. package/packages/client/src/shell/ContributionOutlets.tsrx +31 -0
  625. package/packages/client/src/shell/DashboardView.tsrx +60 -0
  626. package/packages/client/src/shell/MobileHeader.tsrx +24 -0
  627. package/packages/client/src/shell/OrganizationSwitcher.tsrx +100 -0
  628. package/packages/client/src/shell/OverviewCharts.tsrx +167 -0
  629. package/packages/client/src/shell/PrimaryNavigation.tsrx +75 -0
  630. package/packages/client/src/shell/Sidebar.tsrx +76 -0
  631. package/packages/client/src/shell/Topbar.tsrx +44 -0
  632. package/packages/client/src/shell/UserMenu.tsrx +103 -0
  633. package/packages/client/src/shell/WorkspaceContent.tsrx +30 -0
  634. package/packages/client/src/shell/identity.ts +12 -0
  635. package/packages/client/src/shell/navigation.ts +68 -0
  636. package/packages/client/src/shell/shell.css +734 -0
  637. package/packages/client/src/shell/types.ts +29 -0
  638. package/packages/client/src/state.ts +63 -0
  639. package/packages/client/src/web.ts +39 -0
  640. package/packages/coding-agent/package.json +44 -0
  641. package/packages/coding-agent/src/drivers/byok.ts +322 -0
  642. package/packages/coding-agent/src/drivers/claude-code.ts +290 -0
  643. package/packages/coding-agent/src/drivers/codex.ts +281 -0
  644. package/packages/coding-agent/src/index.ts +56 -0
  645. package/packages/coding-agent/src/registry.ts +119 -0
  646. package/packages/coding-agent/src/roles/contract.ts +60 -0
  647. package/packages/coding-agent/src/roles/defaults.ts +134 -0
  648. package/packages/coding-agent/src/roles/handoff.ts +41 -0
  649. package/packages/coding-agent/src/roles/registry.ts +156 -0
  650. package/packages/coding-agent/src/roles/skills.ts +72 -0
  651. package/packages/coding-agent/src/roles/sync.ts +88 -0
  652. package/packages/coding-agent/src/types.ts +102 -0
  653. package/packages/coding-agent/src/workspace.ts +275 -0
  654. package/packages/contracts/package.json +38 -0
  655. package/packages/contracts/schemas/blueprint.schema.json +48 -0
  656. package/packages/contracts/schemas/cli-extension.schema.json +90 -0
  657. package/packages/contracts/schemas/module-artifact.schema.json +82 -0
  658. package/packages/contracts/schemas/module-catalog.schema.json +48 -0
  659. package/packages/contracts/schemas/module-spec.schema.json +111 -0
  660. package/packages/contracts/schemas/module.schema.json +156 -0
  661. package/packages/contracts/schemas/platform-spec.schema.json +17 -0
  662. package/packages/contracts/schemas/project.schema.json +270 -0
  663. package/packages/contracts/src/index.ts +191 -0
  664. package/packages/contracts/src/module-distribution.ts +47 -0
  665. package/packages/contracts/src/schemas.ts +18 -0
  666. package/packages/contracts/src/variables.ts +156 -0
  667. package/packages/contracts/src/web.ts +60 -0
  668. package/packages/database/package.json +43 -0
  669. package/packages/database/src/contracts.ts +399 -0
  670. package/packages/database/src/index.ts +112 -0
  671. package/packages/database/src/migrations.ts +444 -0
  672. package/packages/database/src/postgresql.ts +517 -0
  673. package/packages/database/src/provider.ts +639 -0
  674. package/packages/database/src/record-history.ts +201 -0
  675. package/packages/database/src/registry.ts +265 -0
  676. package/packages/database/src/reset.ts +87 -0
  677. package/packages/database-pglite/package.json +42 -0
  678. package/packages/database-pglite/src/driver.ts +168 -0
  679. package/packages/database-pglite/src/index.ts +2 -0
  680. package/packages/database-testing/package.json +44 -0
  681. package/packages/database-testing/src/index.ts +8 -0
  682. package/packages/database-testing/src/pglite.ts +124 -0
  683. package/packages/database-testing/src/postgres.ts +184 -0
  684. package/packages/database-testing/src/provider.ts +43 -0
  685. package/packages/database-testing/src/vitest-preset.ts +23 -0
  686. package/packages/dev-console/README.md +6 -0
  687. package/packages/dev-console/package.json +38 -0
  688. package/packages/dev-console/src/index.mjs +220 -0
  689. package/packages/harness/package.json +47 -0
  690. package/packages/harness/schemas/task-packet.schema.json +48 -0
  691. package/packages/harness/src/catalog.ts +24 -0
  692. package/packages/harness/src/errors.ts +9 -0
  693. package/packages/harness/src/index.ts +53 -0
  694. package/packages/harness/src/preamble.ts +31 -0
  695. package/packages/harness/src/runtime.ts +833 -0
  696. package/packages/harness/src/task-packet.ts +86 -0
  697. package/packages/harness/src/tool-adapters.ts +106 -0
  698. package/packages/harness/src/tool-contract.ts +242 -0
  699. package/packages/harness/src/vercel-ai-provider.ts +198 -0
  700. package/packages/kernel/package.json +44 -0
  701. package/packages/kernel/src/acl.ts +20 -0
  702. package/packages/kernel/src/actor.ts +139 -0
  703. package/packages/kernel/src/agent-registry.ts +81 -0
  704. package/packages/kernel/src/capability-registry.ts +28 -0
  705. package/packages/kernel/src/errors.ts +9 -0
  706. package/packages/kernel/src/index.ts +90 -0
  707. package/packages/kernel/src/legacy-local-state.ts +103 -0
  708. package/packages/kernel/src/module-compatibility.ts +70 -0
  709. package/packages/kernel/src/module-registry.ts +124 -0
  710. package/packages/kernel/src/module-settings.ts +403 -0
  711. package/packages/kernel/src/record-history.ts +146 -0
  712. package/packages/kernel/src/runtime-config.ts +51 -0
  713. package/packages/kernel/src/tool-registry.ts +25 -0
  714. package/packages/kernel/src/variable-registry.ts +339 -0
  715. package/packages/sandbox/README.md +691 -0
  716. package/packages/sandbox/bin/flowdular-sandbox.mjs +393 -0
  717. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +11 -0
  718. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +1 -0
  719. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +3 -0
  720. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  721. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  722. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  723. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  724. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  725. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  726. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  727. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  728. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  729. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  730. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  731. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  732. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  733. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  734. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  735. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  736. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  737. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  738. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  739. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  740. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  741. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  742. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  743. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  744. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  745. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  746. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +4 -0
  747. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +3 -0
  748. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +1 -0
  749. package/packages/sandbox/dist/client/favicon.svg +1 -0
  750. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +12785 -0
  751. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +4125 -0
  752. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +24 -0
  753. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +388 -0
  754. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +55 -0
  755. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +3 -0
  756. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +494 -0
  757. package/packages/sandbox/dist/server/entry.js +91671 -0
  758. package/packages/sandbox/dist/server/favicon.svg +1 -0
  759. package/packages/sandbox/dist/server/index.html +161 -0
  760. package/packages/sandbox/index.html +159 -0
  761. package/packages/sandbox/octane.config.ts +36 -0
  762. package/packages/sandbox/package.json +83 -0
  763. package/packages/sandbox/public/favicon.svg +1 -0
  764. package/packages/sandbox/public/og.png +0 -0
  765. package/packages/sandbox/src/App.tsrx +924 -0
  766. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +39 -0
  767. package/packages/sandbox/src/client/ChatPane.tsrx +694 -0
  768. package/packages/sandbox/src/client/ConnectView.tsrx +86 -0
  769. package/packages/sandbox/src/client/DiffPane.tsrx +150 -0
  770. package/packages/sandbox/src/client/EjectModal.tsrx +571 -0
  771. package/packages/sandbox/src/client/GateResults.tsrx +60 -0
  772. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +219 -0
  773. package/packages/sandbox/src/client/NewSessionForm.tsrx +214 -0
  774. package/packages/sandbox/src/client/SessionBar.tsrx +151 -0
  775. package/packages/sandbox/src/client/SessionPicker.tsrx +472 -0
  776. package/packages/sandbox/src/client/SpecEditor.tsrx +109 -0
  777. package/packages/sandbox/src/client/SpecReviewCard.tsrx +321 -0
  778. package/packages/sandbox/src/client/WorkCard.tsrx +128 -0
  779. package/packages/sandbox/src/client/WorkbenchModal.tsrx +161 -0
  780. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +100 -0
  781. package/packages/sandbox/src/client/api.ts +797 -0
  782. package/packages/sandbox/src/client/attachments.ts +73 -0
  783. package/packages/sandbox/src/client/dashboard.ts +46 -0
  784. package/packages/sandbox/src/client/github-settings.ts +48 -0
  785. package/packages/sandbox/src/client/highlight.ts +105 -0
  786. package/packages/sandbox/src/client/i18n.ts +32 -0
  787. package/packages/sandbox/src/client/locales/en.json +466 -0
  788. package/packages/sandbox/src/client/locales/pl.json +466 -0
  789. package/packages/sandbox/src/client/session-labels.ts +77 -0
  790. package/packages/sandbox/src/client/state.ts +108 -0
  791. package/packages/sandbox/src/index.ts +10 -0
  792. package/packages/sandbox/src/preview/PreviewHost.tsrx +373 -0
  793. package/packages/sandbox/src/preview/load-module.ts +16 -0
  794. package/packages/sandbox/src/server/attachments.ts +308 -0
  795. package/packages/sandbox/src/server/auto-review.ts +181 -0
  796. package/packages/sandbox/src/server/checkpoints.ts +90 -0
  797. package/packages/sandbox/src/server/config.ts +460 -0
  798. package/packages/sandbox/src/server/dashboard.ts +245 -0
  799. package/packages/sandbox/src/server/delivery/configuration.ts +193 -0
  800. package/packages/sandbox/src/server/delivery/git-pr.ts +1473 -0
  801. package/packages/sandbox/src/server/delivery/index.ts +83 -0
  802. package/packages/sandbox/src/server/delivery/local.ts +151 -0
  803. package/packages/sandbox/src/server/delivery/official-modules.ts +516 -0
  804. package/packages/sandbox/src/server/delivery/plan.ts +218 -0
  805. package/packages/sandbox/src/server/delivery/policies.ts +239 -0
  806. package/packages/sandbox/src/server/delivery/record.ts +41 -0
  807. package/packages/sandbox/src/server/delivery/steps.ts +464 -0
  808. package/packages/sandbox/src/server/delivery/types.ts +158 -0
  809. package/packages/sandbox/src/server/dependencies.ts +88 -0
  810. package/packages/sandbox/src/server/diff.ts +230 -0
  811. package/packages/sandbox/src/server/gates.ts +388 -0
  812. package/packages/sandbox/src/server/index.ts +139 -0
  813. package/packages/sandbox/src/server/path-guard.ts +282 -0
  814. package/packages/sandbox/src/server/planning.ts +749 -0
  815. package/packages/sandbox/src/server/platform-client.ts +189 -0
  816. package/packages/sandbox/src/server/preview-database-host.ts +340 -0
  817. package/packages/sandbox/src/server/preview-database-protocol.ts +163 -0
  818. package/packages/sandbox/src/server/preview-database-proxy.ts +356 -0
  819. package/packages/sandbox/src/server/preview-database.ts +109 -0
  820. package/packages/sandbox/src/server/preview-ipc.ts +15 -0
  821. package/packages/sandbox/src/server/preview-modules.ts +83 -0
  822. package/packages/sandbox/src/server/preview-revision.ts +44 -0
  823. package/packages/sandbox/src/server/preview-runtime.ts +467 -0
  824. package/packages/sandbox/src/server/preview-worker-manager.ts +436 -0
  825. package/packages/sandbox/src/server/preview-worker.ts +168 -0
  826. package/packages/sandbox/src/server/reference.ts +251 -0
  827. package/packages/sandbox/src/server/routes.ts +2258 -0
  828. package/packages/sandbox/src/server/runtime.ts +188 -0
  829. package/packages/sandbox/src/server/session-owner.ts +23 -0
  830. package/packages/sandbox/src/server/sessions.ts +849 -0
  831. package/packages/sandbox/src/server/spec.ts +471 -0
  832. package/packages/sandbox/src/server/turn-lifecycle.ts +56 -0
  833. package/packages/sandbox/src/server/turns.ts +993 -0
  834. package/packages/sandbox/src/server/workspace-install.ts +266 -0
  835. package/packages/sandbox/src/server/workspace-root.ts +73 -0
  836. package/packages/sandbox/src/styles.css +1573 -0
  837. package/packages/sandbox/vite.config.ts +133 -0
  838. package/packages/server/package.json +43 -0
  839. package/packages/server/src/application-routes.ts +21 -0
  840. package/packages/server/src/composition.ts +48 -0
  841. package/packages/server/src/endpoint.ts +142 -0
  842. package/packages/server/src/http.ts +128 -0
  843. package/packages/server/src/index.ts +46 -0
  844. package/packages/server/src/response-lifetime.ts +56 -0
  845. package/packages/server/src/security-headers.ts +130 -0
  846. package/packages/server/src/web-html.ts +55 -0
  847. package/packages/server/src/web.ts +361 -0
  848. package/packages/ui/package.json +46 -0
  849. package/packages/ui/src/brand/BrandMark.tsrx +66 -0
  850. package/packages/ui/src/brand/mark.ts +44 -0
  851. package/packages/ui/src/components/Alert.tsrx +17 -0
  852. package/packages/ui/src/components/Avatar.tsrx +18 -0
  853. package/packages/ui/src/components/Button.tsrx +29 -0
  854. package/packages/ui/src/components/Chart.tsrx +274 -0
  855. package/packages/ui/src/components/CheckGrid.tsrx +93 -0
  856. package/packages/ui/src/components/ConfirmDialog.tsrx +63 -0
  857. package/packages/ui/src/components/Drawer.tsrx +71 -0
  858. package/packages/ui/src/components/EmptyState.tsrx +26 -0
  859. package/packages/ui/src/components/Filters.tsrx +39 -0
  860. package/packages/ui/src/components/FormField.tsrx +27 -0
  861. package/packages/ui/src/components/Kpi.tsrx +36 -0
  862. package/packages/ui/src/components/PageHeader.tsrx +26 -0
  863. package/packages/ui/src/components/ScopeSummary.tsrx +76 -0
  864. package/packages/ui/src/components/SearchField.tsrx +24 -0
  865. package/packages/ui/src/components/SettingRow.tsrx +50 -0
  866. package/packages/ui/src/components/Switch.tsrx +21 -0
  867. package/packages/ui/src/components/Table.tsrx +267 -0
  868. package/packages/ui/src/components/TableCard.tsrx +52 -0
  869. package/packages/ui/src/components/Tag.tsrx +28 -0
  870. package/packages/ui/src/components/VariableField.tsrx +284 -0
  871. package/packages/ui/src/components/VariableInput.tsrx +7 -0
  872. package/packages/ui/src/components/VariableSelect.tsrx +72 -0
  873. package/packages/ui/src/components/VariableTextarea.tsrx +9 -0
  874. package/packages/ui/src/components/variable-field.ts +61 -0
  875. package/packages/ui/src/components/variable-select.ts +69 -0
  876. package/packages/ui/src/icons/Icon.tsrx +56 -0
  877. package/packages/ui/src/index.ts +81 -0
  878. package/packages/ui/src/initials.ts +10 -0
  879. package/packages/ui/src/styles/base.css +71 -0
  880. package/packages/ui/src/styles/components.css +1859 -0
  881. package/packages/ui/src/styles/index.css +3 -0
  882. package/packages/ui/src/styles/tokens.css +124 -0
  883. package/tsconfig.base.json +18 -0
@@ -0,0 +1,2407 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ runDatabaseMigrations,
4
+ type DatabaseHandle,
5
+ type DatabaseTransaction,
6
+ } from '@flowdular/sdk/database';
7
+ import type { Actor, UserActor } from '@flowdular/sdk/kernel';
8
+ import type {
9
+ JsonValue,
10
+ WorkflowAuditEvent,
11
+ WorkflowAuditVerification,
12
+ WorkflowCostRollupV1,
13
+ WorkflowDefinition,
14
+ WorkflowDefinitionDetail,
15
+ WorkflowEdgeTransfer,
16
+ WorkflowExecutionOrigin,
17
+ WorkflowGraphV1,
18
+ WorkflowNodeAttempt,
19
+ WorkflowNodeExecution,
20
+ WorkflowPayloadEvidenceV1,
21
+ WorkflowPublishedReference,
22
+ WorkflowRevision,
23
+ WorkflowRunDetail,
24
+ WorkflowRunEventTypeV1,
25
+ WorkflowRunEventV1,
26
+ WorkflowRunFilters,
27
+ WorkflowRunPage,
28
+ WorkflowRunStatus,
29
+ WorkflowUsageRollupV1,
30
+ } from '../domain/types.ts';
31
+ import { WORKFLOW_LIMITS } from '../domain/types.ts';
32
+ import { jsonByteSize, jsonHash } from '../domain/graph.ts';
33
+ import {
34
+ projectWorkflowRunEvents,
35
+ WorkflowEventProjectionError,
36
+ } from '../domain/events.ts';
37
+ import { databaseMigrations } from './migration.ts';
38
+ import type { WorkflowPayloadCodec } from './payload-codec.ts';
39
+ import type {
40
+ CreateWorkflowRunWrite,
41
+ SettleAttemptWrite,
42
+ SettleEdgeWrite,
43
+ StartAttemptWrite,
44
+ WorkflowAuditPage,
45
+ WorkflowDefinitionWrite,
46
+ WorkflowRunRecord,
47
+ WorkflowsRepository,
48
+ } from './repository.ts';
49
+
50
+ const TERMINAL_RUNS = new Set<WorkflowRunStatus>([
51
+ 'succeeded',
52
+ 'failed',
53
+ 'refused',
54
+ 'cancelled',
55
+ ]);
56
+
57
+ /* PostgreSQL returns BIGINT as a string, so every integer column is
58
+ normalized on the way out of a row. */
59
+ function integer(value: number | bigint | string): number {
60
+ const normalized = Number(value);
61
+ if (!Number.isSafeInteger(normalized)) {
62
+ throw new Error('The workflows database returned an invalid integer.');
63
+ }
64
+ return normalized;
65
+ }
66
+
67
+ function optionalInteger(
68
+ value: number | bigint | string | null,
69
+ ): number | null {
70
+ return value === null ? null : integer(value);
71
+ }
72
+
73
+ type Int = number | bigint | string;
74
+
75
+ interface DefinitionRow {
76
+ id: string;
77
+ tenant_id: string;
78
+ workflow_key: string;
79
+ name: string;
80
+ description: string;
81
+ status: WorkflowDefinition['status'];
82
+ current_draft_revision: Int;
83
+ published_revision: Int | null;
84
+ created_at: Int;
85
+ updated_at: Int;
86
+ }
87
+
88
+ interface RevisionRow {
89
+ id: string;
90
+ workflow_id: string;
91
+ revision: Int;
92
+ graph_json: string;
93
+ graph_checksum: string;
94
+ compiler_version: Int;
95
+ compiled_order_json: string;
96
+ published_at: Int | null;
97
+ published_actor_json: string | null;
98
+ }
99
+
100
+ interface RunRow {
101
+ id: string;
102
+ tenant_id: string;
103
+ workflow_id: string;
104
+ workflow_key: string;
105
+ workflow_name: string;
106
+ workflow_revision: Int | null;
107
+ graph_checksum: string;
108
+ compiler_version: Int;
109
+ graph_json: string;
110
+ compiled_order_json: string;
111
+ mode: WorkflowRunRecord['mode'];
112
+ status: WorkflowRunStatus;
113
+ actor_json: string;
114
+ authorization_subject_json: string | null;
115
+ origin_json: string;
116
+ permission_snapshot_json: string;
117
+ permission_digest: string;
118
+ input_hash: string;
119
+ input_payload_id: string;
120
+ input_evidence_json: string;
121
+ output_evidence_json: string | null;
122
+ idempotency_key: string | null;
123
+ lease_owner: string | null;
124
+ lease_expires_at: Int | null;
125
+ completed_nodes: Int;
126
+ total_nodes: Int;
127
+ usage_json: string;
128
+ cost_json: string;
129
+ failure_code: string | null;
130
+ queued_at: Int;
131
+ started_at: Int | null;
132
+ completed_at: Int | null;
133
+ cancellation_requested_at: Int | null;
134
+ }
135
+
136
+ interface NodeStateRow {
137
+ node_id: string;
138
+ status: WorkflowNodeExecution['status'];
139
+ latest_attempt: Int;
140
+ selected_outcome_port: string | null;
141
+ next_attempt_at: Int | null;
142
+ ready_at: Int | null;
143
+ started_at: Int | null;
144
+ settled_at: Int | null;
145
+ }
146
+
147
+ interface AttemptRow {
148
+ node_id: string;
149
+ attempt: Int;
150
+ node_type: WorkflowNodeAttempt['nodeType'];
151
+ status: WorkflowNodeAttempt['status'];
152
+ outcome_port: string | null;
153
+ semantic_group: string;
154
+ side_effect_idempotency_key: string;
155
+ input_evidence_json: string;
156
+ output_evidence_json: string;
157
+ child_kind: WorkflowNodeAttempt['childKind'];
158
+ child_id: string | null;
159
+ child_observation_deadline_at: Int | null;
160
+ failure_code: string | null;
161
+ retry_classification: WorkflowNodeAttempt['retryClassification'];
162
+ selected_backoff_ms: Int | null;
163
+ next_attempt_at: Int | null;
164
+ started_at: Int;
165
+ completed_at: Int | null;
166
+ duration_ms: Int | null;
167
+ }
168
+
169
+ interface EdgeRow {
170
+ edge_id: string;
171
+ source_node_id: string;
172
+ source_port: string;
173
+ source_attempt: Int | null;
174
+ target_node_id: string;
175
+ target_port: string;
176
+ state: WorkflowEdgeTransfer['state'];
177
+ reason: string | null;
178
+ evidence_json: string;
179
+ settled_at: Int;
180
+ }
181
+
182
+ interface EventRow {
183
+ event_id: string;
184
+ tenant_id: string;
185
+ run_id: string;
186
+ sequence: Int;
187
+ event_type: WorkflowRunEventTypeV1;
188
+ payload_json: string;
189
+ recorded_at: Int;
190
+ virtual_offset_ms: Int | null;
191
+ }
192
+
193
+ interface AuditRow {
194
+ sequence: Int;
195
+ actor_json: string;
196
+ origin_json: string;
197
+ action: string;
198
+ subject_type: WorkflowAuditEvent['subjectType'];
199
+ subject_id: string;
200
+ metadata_json: string;
201
+ occurred_at: Int;
202
+ previous_hash: string | null;
203
+ event_hash: string;
204
+ }
205
+
206
+ /** Routing data the cross-tenant claim poll is allowed to read. */
207
+ interface RunRoutingRow {
208
+ tenant_id: string;
209
+ id: string;
210
+ }
211
+
212
+ /** Routing data the cross-tenant retention poll is allowed to read. */
213
+ interface PayloadRoutingRow {
214
+ id: string;
215
+ tenant_id: string;
216
+ run_id: string;
217
+ payload_hash: string;
218
+ }
219
+
220
+ function parse<T>(value: string): T {
221
+ return JSON.parse(value) as T;
222
+ }
223
+
224
+ function definitionFromRow(row: DefinitionRow): WorkflowDefinition {
225
+ return {
226
+ id: row.id,
227
+ tenantId: row.tenant_id,
228
+ key: row.workflow_key,
229
+ name: row.name,
230
+ description: row.description,
231
+ status: row.status,
232
+ currentDraftRevision: integer(row.current_draft_revision),
233
+ publishedRevision: optionalInteger(row.published_revision),
234
+ createdAt: integer(row.created_at),
235
+ updatedAt: integer(row.updated_at),
236
+ };
237
+ }
238
+
239
+ function revisionFromRow(row: RevisionRow): WorkflowRevision {
240
+ return {
241
+ id: row.id,
242
+ workflowId: row.workflow_id,
243
+ revision: integer(row.revision),
244
+ graph: parse<WorkflowGraphV1>(row.graph_json),
245
+ graphChecksum: row.graph_checksum,
246
+ compilerVersion: integer(row.compiler_version) as 1,
247
+ compiledOrder: parse<readonly string[]>(row.compiled_order_json),
248
+ publishedAt: optionalInteger(row.published_at),
249
+ publishedBy:
250
+ row.published_actor_json === null
251
+ ? null
252
+ : parse<Actor>(row.published_actor_json),
253
+ };
254
+ }
255
+
256
+ function runFromRow(row: RunRow): WorkflowRunRecord {
257
+ const queuedAt = integer(row.queued_at);
258
+ const completedAt = optionalInteger(row.completed_at);
259
+ return {
260
+ id: row.id,
261
+ tenantId: row.tenant_id,
262
+ workflowId: row.workflow_id,
263
+ workflowKey: row.workflow_key,
264
+ workflowName: row.workflow_name,
265
+ workflowRevision: optionalInteger(row.workflow_revision),
266
+ graphChecksum: row.graph_checksum,
267
+ graph: parse<WorkflowGraphV1>(row.graph_json),
268
+ compiledOrder: parse<readonly string[]>(row.compiled_order_json),
269
+ mode: row.mode,
270
+ status: row.status,
271
+ actor: parse<Actor>(row.actor_json),
272
+ authorizationSubject:
273
+ row.authorization_subject_json === null
274
+ ? null
275
+ : parse<UserActor>(row.authorization_subject_json),
276
+ origin: parse<WorkflowExecutionOrigin>(row.origin_json),
277
+ permissionSnapshot: parse<readonly string[]>(row.permission_snapshot_json),
278
+ permissionDigest: row.permission_digest,
279
+ inputHash: row.input_hash,
280
+ inputPayloadId: row.input_payload_id,
281
+ idempotencyKey: row.idempotency_key,
282
+ leaseOwner: row.lease_owner,
283
+ leaseExpiresAt: optionalInteger(row.lease_expires_at),
284
+ completedNodes: integer(row.completed_nodes),
285
+ totalNodes: integer(row.total_nodes),
286
+ usage: parse<WorkflowUsageRollupV1>(row.usage_json),
287
+ cost: parse<WorkflowCostRollupV1>(row.cost_json),
288
+ failureCode: row.failure_code,
289
+ queuedAt,
290
+ startedAt: optionalInteger(row.started_at),
291
+ completedAt,
292
+ durationMs: completedAt === null ? null : completedAt - queuedAt,
293
+ cancellationRequestedAt: optionalInteger(row.cancellation_requested_at),
294
+ };
295
+ }
296
+
297
+ function attemptFromRow(row: AttemptRow): WorkflowNodeAttempt {
298
+ return {
299
+ nodeId: row.node_id,
300
+ attempt: integer(row.attempt),
301
+ nodeType: row.node_type,
302
+ status: row.status,
303
+ outcomePort: row.outcome_port,
304
+ semanticGroup: row.semantic_group,
305
+ sideEffectIdempotencyKey: row.side_effect_idempotency_key,
306
+ input: parse<WorkflowPayloadEvidenceV1>(row.input_evidence_json),
307
+ output: parse<WorkflowPayloadEvidenceV1>(row.output_evidence_json),
308
+ childKind: row.child_kind,
309
+ childId: row.child_id,
310
+ childObservationDeadlineAt: optionalInteger(
311
+ row.child_observation_deadline_at,
312
+ ),
313
+ failureCode: row.failure_code,
314
+ retryClassification: row.retry_classification,
315
+ selectedBackoffMs: optionalInteger(row.selected_backoff_ms),
316
+ nextAttemptAt: optionalInteger(row.next_attempt_at),
317
+ startedAt: integer(row.started_at),
318
+ completedAt: optionalInteger(row.completed_at),
319
+ durationMs: optionalInteger(row.duration_ms),
320
+ };
321
+ }
322
+
323
+ function eventFromRow(row: EventRow): WorkflowRunEventV1 {
324
+ const virtualOffsetMs = optionalInteger(row.virtual_offset_ms);
325
+ return {
326
+ eventId: row.event_id,
327
+ schemaVersion: 1,
328
+ tenantId: row.tenant_id,
329
+ runId: row.run_id,
330
+ sequence: integer(row.sequence),
331
+ type: row.event_type,
332
+ recordedAt: integer(row.recorded_at),
333
+ ...(virtualOffsetMs === null ? {} : { virtualOffsetMs }),
334
+ payload: parse<Readonly<Record<string, JsonValue>>>(row.payload_json),
335
+ };
336
+ }
337
+
338
+ function auditFromRow(row: AuditRow): WorkflowAuditEvent {
339
+ return {
340
+ sequence: integer(row.sequence),
341
+ actor: parse<Actor>(row.actor_json),
342
+ origin: parse<WorkflowExecutionOrigin>(row.origin_json),
343
+ action: row.action,
344
+ subjectType: row.subject_type,
345
+ subjectId: row.subject_id,
346
+ metadata: parse<Readonly<Record<string, JsonValue>>>(row.metadata_json),
347
+ occurredAt: integer(row.occurred_at),
348
+ previousHash: row.previous_hash,
349
+ eventHash: row.event_hash,
350
+ };
351
+ }
352
+
353
+ function auditHash(event: Omit<WorkflowAuditEvent, 'eventHash'>): string {
354
+ return `sha256:${createHash('sha256')
355
+ .update(
356
+ JSON.stringify({
357
+ sequence: event.sequence,
358
+ actor: event.actor,
359
+ origin: event.origin,
360
+ action: event.action,
361
+ subjectType: event.subjectType,
362
+ subjectId: event.subjectId,
363
+ metadata: event.metadata,
364
+ occurredAt: event.occurredAt,
365
+ previousHash: event.previousHash,
366
+ }),
367
+ )
368
+ .digest('hex')}`;
369
+ }
370
+
371
+ /** Column expression selecting `$.kind` out of a stored JSON document. */
372
+ function jsonKind(column: string): string {
373
+ return `${column}::jsonb ->> 'kind'`;
374
+ }
375
+
376
+ /* Marks a stored evidence document expired without touching its hash or
377
+ byte size, so the audit of what was there survives the redaction. */
378
+ function expire(column: string): string {
379
+ return `((${column}::jsonb - 'preview') || '{"state":"expired","reason":"retention"}'::jsonb)::text`;
380
+ }
381
+
382
+ /** Every statement the repository runs, in PostgreSQL's numbered parameters. */
383
+ const SQL = Object.freeze({
384
+ latestAudit: `SELECT sequence, event_hash FROM workflow_audit_events
385
+ WHERE tenant_id = $1 ORDER BY sequence DESC LIMIT 1`,
386
+ insertAudit: `INSERT INTO workflow_audit_events
387
+ (tenant_id, sequence, actor_json, origin_json, action, subject_type,
388
+ subject_id, metadata_json, occurred_at, previous_hash, event_hash)
389
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
390
+ maxEventSequence: `SELECT max(sequence) AS sequence FROM workflow_run_events
391
+ WHERE tenant_id = $1 AND run_id = $2`,
392
+ insertEvent: `INSERT INTO workflow_run_events
393
+ (event_id, schema_version, tenant_id, run_id, sequence, event_type,
394
+ payload_json, recorded_at, virtual_offset_ms)
395
+ VALUES ($1, 1, $2, $3, $4, $5, $6, $7, $8)`,
396
+ insertPayload: `INSERT INTO workflow_payloads
397
+ (id, tenant_id, run_id, kind, schema_id, payload_hash,
398
+ original_byte_size, ciphertext, encryption_key_id, created_at)
399
+ VALUES ($1, $2, $3, 'execution', $4, $5, $6, $7, $8, $9)`,
400
+ listDefinitions: `SELECT * FROM workflow_definitions WHERE tenant_id = $1
401
+ ORDER BY lower(name), id`,
402
+ findDefinition: `SELECT * FROM workflow_definitions
403
+ WHERE tenant_id = $1 AND id = $2`,
404
+ findDefinitionByKey: `SELECT * FROM workflow_definitions
405
+ WHERE tenant_id = $1 AND workflow_key = $2`,
406
+ findRevision: `SELECT id, workflow_id, revision, graph_json, graph_checksum,
407
+ compiler_version, compiled_order_json, published_at,
408
+ published_actor_json FROM workflow_revisions
409
+ WHERE tenant_id = $1 AND workflow_id = $2 AND revision = $3`,
410
+ listRevisions: `SELECT id, workflow_id, revision, graph_json, graph_checksum,
411
+ compiler_version, compiled_order_json, published_at,
412
+ published_actor_json FROM workflow_revisions
413
+ WHERE tenant_id = $1 AND workflow_id = $2 ORDER BY revision DESC`,
414
+ insertDefinition: `INSERT INTO workflow_definitions
415
+ (id, tenant_id, workflow_key, name, description, status,
416
+ current_draft_revision, published_revision, created_at, updated_at)
417
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
418
+ insertRevision: `INSERT INTO workflow_revisions
419
+ (id, tenant_id, workflow_id, revision, graph_schema_version,
420
+ graph_json, graph_checksum, compiler_version, compiled_order_json,
421
+ published_at, published_actor_json, created_at)
422
+ VALUES ($1, $2, $3, $4, 1, $5, $6, 1, $7, $8, $9, $10)`,
423
+ updateDraft: `UPDATE workflow_definitions
424
+ SET name = $1, description = $2, current_draft_revision = $3, updated_at = $4
425
+ WHERE tenant_id = $5 AND id = $6 AND current_draft_revision = $7`,
426
+ markRevisionPublished: `UPDATE workflow_revisions
427
+ SET published_at = $1, published_actor_json = $2
428
+ WHERE tenant_id = $3 AND workflow_id = $4 AND revision = $5
429
+ AND published_at IS NULL`,
430
+ setPublishedRevision: `UPDATE workflow_definitions SET published_revision = $1, updated_at = $2
431
+ WHERE tenant_id = $3 AND id = $4`,
432
+ archiveDefinition: `UPDATE workflow_definitions SET status = 'archived', updated_at = $1
433
+ WHERE tenant_id = $2 AND id = $3`,
434
+ countRunsOfWorkflow: `SELECT count(*) AS count FROM workflow_runs
435
+ WHERE tenant_id = $1 AND workflow_id = $2`,
436
+ deleteRevisions: `DELETE FROM workflow_revisions WHERE tenant_id = $1 AND workflow_id = $2`,
437
+ deleteDefinition: `DELETE FROM workflow_definitions WHERE tenant_id = $1 AND id = $2`,
438
+ listPublished: `SELECT d.id, d.workflow_key, d.name, d.published_revision,
439
+ r.graph_checksum FROM workflow_definitions d
440
+ JOIN workflow_revisions r ON r.tenant_id = d.tenant_id
441
+ AND r.workflow_id = d.id AND r.revision = d.published_revision
442
+ WHERE d.tenant_id = $1 AND d.status = 'active'
443
+ ORDER BY lower(d.name), d.id`,
444
+ insertRun: `INSERT INTO workflow_runs
445
+ (id, tenant_id, workflow_id, workflow_key, workflow_name,
446
+ workflow_revision, graph_checksum, compiler_version, graph_json,
447
+ compiled_order_json, mode, status, actor_json, authorization_subject_json, origin_json,
448
+ permission_snapshot_json, permission_digest, input_hash,
449
+ input_payload_id, input_evidence_json, output_evidence_json,
450
+ idempotency_key, limits_json, lease_owner, lease_expires_at,
451
+ completed_nodes, total_nodes, usage_json, cost_json, failure_code,
452
+ queued_at, started_at, completed_at, cancellation_requested_at)
453
+ VALUES ($1, $2, $3, $4, $5, $6, $7, 1, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19,
454
+ NULL, $20, $21, NULL, NULL, 0, $22, $23, $24, NULL, $25, NULL, NULL, NULL)`,
455
+ insertNodeState: `INSERT INTO workflow_node_states
456
+ (tenant_id, run_id, node_id, status, latest_attempt)
457
+ VALUES ($1, $2, $3, 'pending', 0)`,
458
+ findRunByIdempotency: `SELECT * FROM workflow_runs
459
+ WHERE tenant_id = $1 AND idempotency_key = $2`,
460
+ getRun: `SELECT * FROM workflow_runs WHERE tenant_id = $1 AND id = $2`,
461
+ /* The one cross-tenant read. It returns routing columns only; the claim
462
+ itself re-reads and re-checks the run under the tenant it named. */
463
+ claimCandidates: `SELECT tenant_id, id FROM workflow_runs
464
+ WHERE mode = 'live'
465
+ AND (
466
+ status = 'queued'
467
+ OR (status IN ('running', 'waiting-agent') AND (lease_expires_at IS NULL OR lease_expires_at <= $1))
468
+ OR (status = 'waiting-retry' AND (lease_expires_at IS NULL OR lease_expires_at <= $2) AND EXISTS (
469
+ SELECT 1 FROM workflow_node_states n
470
+ WHERE n.tenant_id = workflow_runs.tenant_id AND n.run_id = workflow_runs.id
471
+ AND n.status = 'waiting-retry' AND n.next_attempt_at <= $3
472
+ ))
473
+ OR (status = 'cancel-requested' AND (lease_expires_at IS NULL OR lease_expires_at <= $4))
474
+ )
475
+ ORDER BY queued_at, id LIMIT $5`,
476
+ claimRun: `UPDATE workflow_runs SET status = $1, lease_owner = $2, lease_expires_at = $3,
477
+ started_at = coalesce(started_at, $4)
478
+ WHERE tenant_id = $5 AND id = $6
479
+ AND (lease_owner IS NULL OR lease_expires_at <= $7 OR lease_owner = $8)`,
480
+ renewLease: `UPDATE workflow_runs SET lease_expires_at = $1
481
+ WHERE tenant_id = $2 AND id = $3 AND lease_owner = $4
482
+ AND status NOT IN ('succeeded', 'failed', 'refused', 'cancelled')`,
483
+ releaseLease: `UPDATE workflow_runs SET lease_owner = NULL, lease_expires_at = NULL
484
+ WHERE tenant_id = $1 AND id = $2 AND lease_owner = $3`,
485
+ readEvents: `SELECT event_id, tenant_id, run_id, sequence, event_type,
486
+ payload_json, recorded_at, virtual_offset_ms
487
+ FROM workflow_run_events
488
+ WHERE tenant_id = $1 AND run_id = $2 AND sequence > $3
489
+ ORDER BY sequence LIMIT $4`,
490
+ insertAttempt: `INSERT INTO workflow_node_attempts
491
+ (tenant_id, run_id, node_id, attempt, node_type, status,
492
+ semantic_group, side_effect_idempotency_key, input_payload_id,
493
+ input_evidence_json, output_evidence_json, started_at)
494
+ VALUES ($1, $2, $3, $4, $5, 'running', $6, $7, $8, $9, $10, $11)`,
495
+ startNodeState: `UPDATE workflow_node_states
496
+ SET status = 'running', latest_attempt = $1,
497
+ ready_at = coalesce(ready_at, $2), started_at = coalesce(started_at, $3)
498
+ WHERE tenant_id = $4 AND run_id = $5 AND node_id = $6`,
499
+ markAttemptWaitingChild: `UPDATE workflow_node_attempts
500
+ SET status = 'waiting-child', child_kind = $1, child_id = $2,
501
+ child_observation_deadline_at = $3
502
+ WHERE tenant_id = $4 AND run_id = $5 AND node_id = $6 AND attempt = $7
503
+ AND status = 'running'`,
504
+ markNodeWaitingChild: `UPDATE workflow_node_states SET status = 'waiting-child'
505
+ WHERE tenant_id = $1 AND run_id = $2 AND node_id = $3`,
506
+ markRunWaitingAgent: `UPDATE workflow_runs SET status = 'waiting-agent'
507
+ WHERE tenant_id = $1 AND id = $2 AND status = 'running'`,
508
+ getAttempt: `SELECT * FROM workflow_node_attempts
509
+ WHERE tenant_id = $1 AND run_id = $2 AND node_id = $3 AND attempt = $4`,
510
+ settleAttempt: `UPDATE workflow_node_attempts
511
+ SET status = $1, outcome_port = $2, output_payload_id = $3,
512
+ output_evidence_json = $4, failure_code = $5, retry_classification = $6,
513
+ selected_backoff_ms = $7, next_attempt_at = $8, completed_at = $9, duration_ms = $10
514
+ WHERE tenant_id = $11 AND run_id = $12 AND node_id = $13 AND attempt = $14`,
515
+ setRunRollups: `UPDATE workflow_runs SET usage_json = $1, cost_json = $2
516
+ WHERE tenant_id = $3 AND id = $4`,
517
+ settleNodeState: `UPDATE workflow_node_states
518
+ SET status = $1, selected_outcome_port = $2, next_attempt_at = $3,
519
+ settled_at = $4
520
+ WHERE tenant_id = $5 AND run_id = $6 AND node_id = $7`,
521
+ advanceRunProgress: `UPDATE workflow_runs SET status = $1,
522
+ completed_nodes = completed_nodes + $2
523
+ WHERE tenant_id = $3 AND id = $4 AND status NOT IN
524
+ ('cancel-requested', 'succeeded', 'failed', 'refused', 'cancelled')`,
525
+ insertEdgeTransfer: `INSERT INTO workflow_edge_transfers
526
+ (tenant_id, run_id, edge_id, source_node_id, source_port,
527
+ source_attempt, target_node_id, target_port, state, reason,
528
+ schema_id, payload_id, evidence_json, settled_at)
529
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
530
+ skipNodeState: `UPDATE workflow_node_states
531
+ SET status = 'skipped', settled_at = $1
532
+ WHERE tenant_id = $2 AND run_id = $3 AND node_id = $4
533
+ AND status IN ('pending', 'ready')`,
534
+ countNodeSkipped: `UPDATE workflow_runs SET completed_nodes = completed_nodes + 1
535
+ WHERE tenant_id = $1 AND id = $2`,
536
+ settleRun: `UPDATE workflow_runs SET status = $1, failure_code = $2,
537
+ output_evidence_json = $3, usage_json = $4, cost_json = $5,
538
+ completed_at = $6, lease_owner = NULL, lease_expires_at = NULL
539
+ WHERE tenant_id = $7 AND id = $8`,
540
+ expireRunPayloads: `UPDATE workflow_payloads SET expires_at = $1
541
+ WHERE tenant_id = $2 AND run_id = $3 AND kind = 'execution'`,
542
+ requestCancellation: `UPDATE workflow_runs SET status = 'cancel-requested',
543
+ cancellation_requested_at = $1, lease_owner = NULL, lease_expires_at = NULL
544
+ WHERE tenant_id = $2 AND id = $3`,
545
+ readExecutionPayload: `SELECT ciphertext FROM workflow_payloads
546
+ WHERE tenant_id = $1 AND run_id = $2 AND id = $3 AND kind = 'execution'`,
547
+ readEdgePayloadId: `SELECT payload_id FROM workflow_edge_transfers
548
+ WHERE tenant_id = $1 AND run_id = $2 AND edge_id = $3 AND state = 'emitted'`,
549
+ readNodeStates: `SELECT node_id, status, latest_attempt, selected_outcome_port,
550
+ next_attempt_at, ready_at, started_at, settled_at
551
+ FROM workflow_node_states WHERE tenant_id = $1 AND run_id = $2
552
+ ORDER BY node_id`,
553
+ readAttempts: `SELECT node_id, attempt, node_type, status, outcome_port,
554
+ semantic_group, side_effect_idempotency_key, input_evidence_json,
555
+ output_evidence_json, child_kind, child_id,
556
+ child_observation_deadline_at, failure_code,
557
+ retry_classification, selected_backoff_ms, next_attempt_at,
558
+ started_at, completed_at, duration_ms
559
+ FROM workflow_node_attempts WHERE tenant_id = $1 AND run_id = $2
560
+ ORDER BY node_id, attempt`,
561
+ readEdgeTransfers: `SELECT edge_id, source_node_id, source_port, source_attempt,
562
+ target_node_id, target_port, state, reason, evidence_json, settled_at
563
+ FROM workflow_edge_transfers WHERE tenant_id = $1 AND run_id = $2
564
+ ORDER BY edge_id`,
565
+ listAudit: `SELECT sequence, actor_json, origin_json, action, subject_type,
566
+ subject_id, metadata_json, occurred_at, previous_hash, event_hash
567
+ FROM workflow_audit_events WHERE tenant_id = $1
568
+ ORDER BY sequence DESC LIMIT $2`,
569
+ listAuditBefore: `SELECT sequence, actor_json, origin_json, action, subject_type,
570
+ subject_id, metadata_json, occurred_at, previous_hash, event_hash
571
+ FROM workflow_audit_events WHERE tenant_id = $1 AND sequence < $2
572
+ ORDER BY sequence DESC LIMIT $3`,
573
+ auditChain: `SELECT sequence, actor_json, origin_json, action, subject_type,
574
+ subject_id, metadata_json, occurred_at, previous_hash, event_hash
575
+ FROM workflow_audit_events WHERE tenant_id = $1 ORDER BY sequence`,
576
+ /* Cross-tenant, routing columns only: the retention writes that follow
577
+ run under the tenant each row named. */
578
+ retentionCandidates: `SELECT p.id, p.tenant_id, p.run_id, p.payload_hash
579
+ FROM workflow_payloads p
580
+ JOIN workflow_runs r ON r.tenant_id = p.tenant_id AND r.id = p.run_id
581
+ WHERE p.kind = 'execution' AND p.expires_at IS NOT NULL
582
+ AND p.expires_at <= $1
583
+ AND r.status IN ('succeeded', 'failed', 'refused', 'cancelled')
584
+ ORDER BY p.expires_at, p.id LIMIT $2`,
585
+ expireRunInputEvidence: `UPDATE workflow_runs SET input_evidence_json = ${expire('input_evidence_json')}
586
+ WHERE tenant_id = $1 AND id = $2 AND input_payload_id = $3`,
587
+ expireAttemptEvidence: `UPDATE workflow_node_attempts
588
+ SET input_evidence_json = CASE WHEN input_payload_id = $1 THEN ${expire('input_evidence_json')} ELSE input_evidence_json END,
589
+ output_evidence_json = CASE WHEN output_payload_id = $2 THEN ${expire('output_evidence_json')} ELSE output_evidence_json END
590
+ WHERE tenant_id = $3 AND run_id = $4 AND (input_payload_id = $5 OR output_payload_id = $6)`,
591
+ expireEdgeEvidence: `UPDATE workflow_edge_transfers SET evidence_json = ${expire('evidence_json')}
592
+ WHERE tenant_id = $1 AND run_id = $2 AND payload_id = $3`,
593
+ deletePayload: `DELETE FROM workflow_payloads WHERE tenant_id = $1 AND run_id = $2 AND id = $3`,
594
+ countExecutionPayloads: `SELECT count(*) AS count FROM workflow_payloads
595
+ WHERE tenant_id = $1 AND run_id = $2 AND kind = 'execution'`,
596
+ expireRunOutputEvidence: `UPDATE workflow_runs SET output_evidence_json =
597
+ CASE WHEN output_evidence_json IS NULL THEN NULL ELSE ${expire('output_evidence_json')} END
598
+ WHERE tenant_id = $1 AND id = $2`,
599
+ countRuns: `SELECT count(*) AS count FROM workflow_runs WHERE tenant_id = $1`,
600
+ });
601
+
602
+ export async function migrateWorkflowsDatabase(
603
+ database: DatabaseHandle,
604
+ ): Promise<void> {
605
+ await runDatabaseMigrations(database, 'workflows.core', databaseMigrations);
606
+ }
607
+
608
+ export interface WorkflowsDatabaseHandles {
609
+ /** Tenant-scoped handle used by every request-time read and write. */
610
+ readonly runtime: DatabaseHandle;
611
+ /**
612
+ * Cross-tenant read handle for the worker claim poll and payload retention.
613
+ * It reads only what those tables' own FOR SELECT policy grants and writes
614
+ * nothing; the work that follows runs under the tenant of the row it named.
615
+ */
616
+ readonly background: DatabaseHandle;
617
+ }
618
+
619
+ /** A repository over platform-owned PostgreSQL handles. */
620
+ export class DatabaseWorkflowsRepository implements WorkflowsRepository {
621
+ readonly #payloadCodec: WorkflowPayloadCodec;
622
+ readonly #payloadRetentionMs: number;
623
+
624
+ constructor(
625
+ private readonly handles: WorkflowsDatabaseHandles,
626
+ payloadCodec: WorkflowPayloadCodec,
627
+ payloadRetentionMs = 24 * 60 * 60 * 1_000,
628
+ private readonly readyPromise: Promise<void> = Promise.resolve(),
629
+ ) {
630
+ this.#payloadCodec = payloadCodec;
631
+ this.#payloadRetentionMs = Math.max(0, Math.trunc(payloadRetentionMs));
632
+ }
633
+
634
+ /* One unit of work under one tenant. Every multi-statement operation runs
635
+ inside a single transaction, so a partially written run is impossible. */
636
+ async #tx<T>(
637
+ tenantId: string,
638
+ access: 'read' | 'write',
639
+ body: (transaction: DatabaseTransaction) => Promise<T>,
640
+ ): Promise<T> {
641
+ await this.readyPromise;
642
+ return this.handles.runtime.transaction(body, { access, tenantId });
643
+ }
644
+
645
+ async #query<Row extends object>(
646
+ transaction: DatabaseTransaction,
647
+ text: string,
648
+ parameters: readonly unknown[],
649
+ ): Promise<readonly Row[]> {
650
+ const result = await transaction.query<Row>({
651
+ text,
652
+ parameters: parameters as never,
653
+ });
654
+ return result.rows;
655
+ }
656
+
657
+ async #exec(
658
+ transaction: DatabaseTransaction,
659
+ text: string,
660
+ parameters: readonly unknown[],
661
+ ): Promise<number> {
662
+ const result = await transaction.execute({
663
+ text,
664
+ parameters: parameters as never,
665
+ });
666
+ return result.affectedRows;
667
+ }
668
+
669
+ async #appendAudit(
670
+ transaction: DatabaseTransaction,
671
+ tenantId: string,
672
+ actor: Actor,
673
+ origin: WorkflowExecutionOrigin,
674
+ action: string,
675
+ subjectType: WorkflowAuditEvent['subjectType'],
676
+ subjectId: string,
677
+ metadata: Readonly<Record<string, JsonValue>>,
678
+ occurredAt: number,
679
+ ): Promise<WorkflowAuditEvent> {
680
+ const previous = (
681
+ await this.#query<{ sequence: Int; event_hash: string }>(
682
+ transaction,
683
+ SQL.latestAudit,
684
+ [tenantId],
685
+ )
686
+ )[0];
687
+ const base = {
688
+ sequence: (previous ? integer(previous.sequence) : 0) + 1,
689
+ actor,
690
+ origin,
691
+ action,
692
+ subjectType,
693
+ subjectId,
694
+ metadata,
695
+ occurredAt,
696
+ previousHash: previous?.event_hash ?? null,
697
+ };
698
+ const event: WorkflowAuditEvent = { ...base, eventHash: auditHash(base) };
699
+ await this.#exec(transaction, SQL.insertAudit, [
700
+ tenantId,
701
+ event.sequence,
702
+ JSON.stringify(actor),
703
+ JSON.stringify(origin),
704
+ action,
705
+ subjectType,
706
+ subjectId,
707
+ JSON.stringify(metadata),
708
+ occurredAt,
709
+ event.previousHash,
710
+ event.eventHash,
711
+ ]);
712
+ return event;
713
+ }
714
+
715
+ async #appendEvent(
716
+ transaction: DatabaseTransaction,
717
+ tenantId: string,
718
+ runId: string,
719
+ type: WorkflowRunEventTypeV1,
720
+ payload: Readonly<Record<string, JsonValue>>,
721
+ recordedAt: number,
722
+ virtualOffsetMs?: number,
723
+ ): Promise<WorkflowRunEventV1> {
724
+ const previous = (
725
+ await this.#query<{ sequence: Int | null }>(
726
+ transaction,
727
+ SQL.maxEventSequence,
728
+ [tenantId, runId],
729
+ )
730
+ )[0];
731
+ const sequence = (optionalInteger(previous?.sequence ?? null) ?? 0) + 1;
732
+ if (sequence > WORKFLOW_LIMITS.maxRunEvents) {
733
+ throw new Error('WORKFLOW_LIMIT_EXCEEDED');
734
+ }
735
+ const event: WorkflowRunEventV1 = {
736
+ eventId: randomUUID(),
737
+ schemaVersion: 1,
738
+ tenantId,
739
+ runId,
740
+ sequence,
741
+ type,
742
+ recordedAt,
743
+ ...(virtualOffsetMs === undefined ? {} : { virtualOffsetMs }),
744
+ payload,
745
+ };
746
+ await this.#exec(transaction, SQL.insertEvent, [
747
+ event.eventId,
748
+ tenantId,
749
+ runId,
750
+ sequence,
751
+ type,
752
+ JSON.stringify(payload),
753
+ recordedAt,
754
+ virtualOffsetMs ?? null,
755
+ ]);
756
+ return event;
757
+ }
758
+
759
+ async #storePayload(
760
+ transaction: DatabaseTransaction,
761
+ tenantId: string,
762
+ runId: string,
763
+ schemaId: string,
764
+ value: JsonValue,
765
+ createdAt: number,
766
+ ): Promise<string> {
767
+ const id = randomUUID();
768
+ const ciphertext = this.#payloadCodec.encrypt(value, {
769
+ tenantId,
770
+ runId,
771
+ payloadId: id,
772
+ });
773
+ await this.#exec(transaction, SQL.insertPayload, [
774
+ id,
775
+ tenantId,
776
+ runId,
777
+ schemaId,
778
+ jsonHash(value),
779
+ jsonByteSize(value),
780
+ ciphertext,
781
+ this.#payloadCodec.keyId,
782
+ createdAt,
783
+ ]);
784
+ return id;
785
+ }
786
+
787
+ async #definitionIn(
788
+ transaction: DatabaseTransaction,
789
+ tenantId: string,
790
+ workflowId: string,
791
+ ): Promise<WorkflowDefinition | null> {
792
+ const row = (
793
+ await this.#query<DefinitionRow>(transaction, SQL.findDefinition, [
794
+ tenantId,
795
+ workflowId,
796
+ ])
797
+ )[0];
798
+ return row ? definitionFromRow(row) : null;
799
+ }
800
+
801
+ async #runIn(
802
+ transaction: DatabaseTransaction,
803
+ tenantId: string,
804
+ runId: string,
805
+ ): Promise<WorkflowRunRecord | null> {
806
+ const row = (
807
+ await this.#query<RunRow>(transaction, SQL.getRun, [tenantId, runId])
808
+ )[0];
809
+ return row ? runFromRow(row) : null;
810
+ }
811
+
812
+ async #detailIn(
813
+ transaction: DatabaseTransaction,
814
+ tenantId: string,
815
+ workflowId: string,
816
+ ): Promise<WorkflowDefinitionDetail | null> {
817
+ const definition = await this.#definitionIn(
818
+ transaction,
819
+ tenantId,
820
+ workflowId,
821
+ );
822
+ if (!definition) return null;
823
+ const revisions = (
824
+ await this.#query<RevisionRow>(transaction, SQL.listRevisions, [
825
+ tenantId,
826
+ workflowId,
827
+ ])
828
+ ).map(revisionFromRow);
829
+ const draft = revisions.find(
830
+ (entry) => entry.revision === definition.currentDraftRevision,
831
+ );
832
+ if (!draft) throw new Error('WORKFLOW_REVISION_MISSING');
833
+ return { definition, draft, revisions };
834
+ }
835
+
836
+ async #insertRevision(
837
+ transaction: DatabaseTransaction,
838
+ tenantId: string,
839
+ revision: WorkflowRevision,
840
+ createdAt: number,
841
+ ): Promise<void> {
842
+ await this.#exec(transaction, SQL.insertRevision, [
843
+ revision.id,
844
+ tenantId,
845
+ revision.workflowId,
846
+ revision.revision,
847
+ JSON.stringify(revision.graph),
848
+ revision.graphChecksum,
849
+ JSON.stringify(revision.compiledOrder),
850
+ revision.publishedAt,
851
+ revision.publishedBy ? JSON.stringify(revision.publishedBy) : null,
852
+ createdAt,
853
+ ]);
854
+ }
855
+
856
+ async listDefinitions(
857
+ tenantId: string,
858
+ ): Promise<readonly WorkflowDefinition[]> {
859
+ return this.#tx(tenantId, 'read', async (transaction) =>
860
+ (
861
+ await this.#query<DefinitionRow>(transaction, SQL.listDefinitions, [
862
+ tenantId,
863
+ ])
864
+ ).map(definitionFromRow),
865
+ );
866
+ }
867
+
868
+ async findDefinition(
869
+ tenantId: string,
870
+ workflowId: string,
871
+ ): Promise<WorkflowDefinition | null> {
872
+ return this.#tx(tenantId, 'read', (transaction) =>
873
+ this.#definitionIn(transaction, tenantId, workflowId),
874
+ );
875
+ }
876
+
877
+ async findDefinitionByKey(
878
+ tenantId: string,
879
+ workflowKey: string,
880
+ ): Promise<WorkflowDefinition | null> {
881
+ return this.#tx(tenantId, 'read', async (transaction) => {
882
+ const row = (
883
+ await this.#query<DefinitionRow>(transaction, SQL.findDefinitionByKey, [
884
+ tenantId,
885
+ workflowKey,
886
+ ])
887
+ )[0];
888
+ return row ? definitionFromRow(row) : null;
889
+ });
890
+ }
891
+
892
+ async findRevision(
893
+ tenantId: string,
894
+ workflowId: string,
895
+ revision: number,
896
+ ): Promise<WorkflowRevision | null> {
897
+ return this.#tx(tenantId, 'read', async (transaction) => {
898
+ const row = (
899
+ await this.#query<RevisionRow>(transaction, SQL.findRevision, [
900
+ tenantId,
901
+ workflowId,
902
+ revision,
903
+ ])
904
+ )[0];
905
+ return row ? revisionFromRow(row) : null;
906
+ });
907
+ }
908
+
909
+ async definitionDetail(
910
+ tenantId: string,
911
+ workflowId: string,
912
+ ): Promise<WorkflowDefinitionDetail | null> {
913
+ return this.#tx(tenantId, 'read', (transaction) =>
914
+ this.#detailIn(transaction, tenantId, workflowId),
915
+ );
916
+ }
917
+
918
+ async createDefinition(
919
+ write: WorkflowDefinitionWrite,
920
+ ): Promise<WorkflowDefinitionDetail> {
921
+ const { definition, revision } = write;
922
+ return this.#tx(definition.tenantId, 'write', async (transaction) => {
923
+ await this.#exec(transaction, SQL.insertDefinition, [
924
+ definition.id,
925
+ definition.tenantId,
926
+ definition.key,
927
+ definition.name,
928
+ definition.description,
929
+ definition.status,
930
+ definition.currentDraftRevision,
931
+ definition.publishedRevision,
932
+ definition.createdAt,
933
+ definition.updatedAt,
934
+ ]);
935
+ await this.#insertRevision(
936
+ transaction,
937
+ definition.tenantId,
938
+ revision,
939
+ definition.createdAt,
940
+ );
941
+ await this.#appendAudit(
942
+ transaction,
943
+ definition.tenantId,
944
+ write.actor,
945
+ write.origin,
946
+ 'workflow.created',
947
+ 'workflow',
948
+ definition.id,
949
+ { key: definition.key, revision: revision.revision },
950
+ definition.createdAt,
951
+ );
952
+ return { definition, draft: revision, revisions: [revision] };
953
+ });
954
+ }
955
+
956
+ async saveDraft(
957
+ write: WorkflowDefinitionWrite & { readonly expectedRevision: number },
958
+ ): Promise<WorkflowDefinitionDetail | 'conflict'> {
959
+ return this.#tx(write.definition.tenantId, 'write', async (transaction) => {
960
+ const updated = await this.#exec(transaction, SQL.updateDraft, [
961
+ write.definition.name,
962
+ write.definition.description,
963
+ write.revision.revision,
964
+ write.definition.updatedAt,
965
+ write.definition.tenantId,
966
+ write.definition.id,
967
+ write.expectedRevision,
968
+ ]);
969
+ if (updated !== 1) return 'conflict';
970
+ await this.#insertRevision(
971
+ transaction,
972
+ write.definition.tenantId,
973
+ write.revision,
974
+ write.definition.updatedAt,
975
+ );
976
+ await this.#appendAudit(
977
+ transaction,
978
+ write.definition.tenantId,
979
+ write.actor,
980
+ write.origin,
981
+ 'workflow.draft.saved',
982
+ 'workflow',
983
+ write.definition.id,
984
+ { revision: write.revision.revision },
985
+ write.definition.updatedAt,
986
+ );
987
+ return (await this.#detailIn(
988
+ transaction,
989
+ write.definition.tenantId,
990
+ write.definition.id,
991
+ ))!;
992
+ });
993
+ }
994
+
995
+ async publish(
996
+ tenantId: string,
997
+ workflowId: string,
998
+ expectedRevision: number,
999
+ actor: Actor,
1000
+ origin: WorkflowExecutionOrigin,
1001
+ recordedAt: number,
1002
+ ): Promise<WorkflowDefinitionDetail | 'conflict' | null> {
1003
+ return this.#tx(tenantId, 'write', async (transaction) => {
1004
+ const definition = await this.#definitionIn(
1005
+ transaction,
1006
+ tenantId,
1007
+ workflowId,
1008
+ );
1009
+ if (!definition) return null;
1010
+ if (definition.currentDraftRevision !== expectedRevision) {
1011
+ return 'conflict';
1012
+ }
1013
+ const marked = await this.#exec(transaction, SQL.markRevisionPublished, [
1014
+ recordedAt,
1015
+ JSON.stringify(actor),
1016
+ tenantId,
1017
+ workflowId,
1018
+ expectedRevision,
1019
+ ]);
1020
+ if (marked !== 1 && definition.publishedRevision !== expectedRevision) {
1021
+ return 'conflict';
1022
+ }
1023
+ await this.#exec(transaction, SQL.setPublishedRevision, [
1024
+ expectedRevision,
1025
+ recordedAt,
1026
+ tenantId,
1027
+ workflowId,
1028
+ ]);
1029
+ await this.#appendAudit(
1030
+ transaction,
1031
+ tenantId,
1032
+ actor,
1033
+ origin,
1034
+ 'workflow.published',
1035
+ 'workflow',
1036
+ workflowId,
1037
+ { revision: expectedRevision },
1038
+ recordedAt,
1039
+ );
1040
+ return (await this.#detailIn(transaction, tenantId, workflowId))!;
1041
+ });
1042
+ }
1043
+
1044
+ async archive(
1045
+ tenantId: string,
1046
+ workflowId: string,
1047
+ actor: Actor,
1048
+ origin: WorkflowExecutionOrigin,
1049
+ recordedAt: number,
1050
+ ): Promise<WorkflowDefinition | null> {
1051
+ return this.#tx(tenantId, 'write', async (transaction) => {
1052
+ const changed = await this.#exec(transaction, SQL.archiveDefinition, [
1053
+ recordedAt,
1054
+ tenantId,
1055
+ workflowId,
1056
+ ]);
1057
+ if (changed !== 1) return null;
1058
+ await this.#appendAudit(
1059
+ transaction,
1060
+ tenantId,
1061
+ actor,
1062
+ origin,
1063
+ 'workflow.archived',
1064
+ 'workflow',
1065
+ workflowId,
1066
+ {},
1067
+ recordedAt,
1068
+ );
1069
+ return this.#definitionIn(transaction, tenantId, workflowId);
1070
+ });
1071
+ }
1072
+
1073
+ async deleteDraft(
1074
+ tenantId: string,
1075
+ workflowId: string,
1076
+ actor: Actor,
1077
+ origin: WorkflowExecutionOrigin,
1078
+ recordedAt: number,
1079
+ ): Promise<'deleted' | 'not-found' | 'in-use'> {
1080
+ return this.#tx(tenantId, 'write', async (transaction) => {
1081
+ const definition = await this.#definitionIn(
1082
+ transaction,
1083
+ tenantId,
1084
+ workflowId,
1085
+ );
1086
+ if (!definition) return 'not-found';
1087
+ const runs = (
1088
+ await this.#query<{ count: Int }>(
1089
+ transaction,
1090
+ SQL.countRunsOfWorkflow,
1091
+ [tenantId, workflowId],
1092
+ )
1093
+ )[0];
1094
+ if (
1095
+ definition.publishedRevision !== null ||
1096
+ integer(runs?.count ?? 0) > 0
1097
+ ) {
1098
+ return 'in-use';
1099
+ }
1100
+ await this.#appendAudit(
1101
+ transaction,
1102
+ tenantId,
1103
+ actor,
1104
+ origin,
1105
+ 'workflow.deleted',
1106
+ 'workflow',
1107
+ workflowId,
1108
+ { key: definition.key },
1109
+ recordedAt,
1110
+ );
1111
+ await this.#exec(transaction, SQL.deleteRevisions, [
1112
+ tenantId,
1113
+ workflowId,
1114
+ ]);
1115
+ await this.#exec(transaction, SQL.deleteDefinition, [
1116
+ tenantId,
1117
+ workflowId,
1118
+ ]);
1119
+ return 'deleted';
1120
+ });
1121
+ }
1122
+
1123
+ async listPublished(
1124
+ tenantId: string,
1125
+ ): Promise<readonly WorkflowPublishedReference[]> {
1126
+ return this.#tx(tenantId, 'read', async (transaction) =>
1127
+ (
1128
+ await this.#query<{
1129
+ id: string;
1130
+ workflow_key: string;
1131
+ name: string;
1132
+ published_revision: Int;
1133
+ graph_checksum: string;
1134
+ }>(transaction, SQL.listPublished, [tenantId])
1135
+ ).map((row) => ({
1136
+ id: row.id,
1137
+ key: row.workflow_key,
1138
+ name: row.name,
1139
+ revision: integer(row.published_revision),
1140
+ graphChecksum: row.graph_checksum,
1141
+ })),
1142
+ );
1143
+ }
1144
+
1145
+ async createRun(write: CreateWorkflowRunWrite): Promise<WorkflowRunRecord> {
1146
+ const run = write.run;
1147
+ return this.#tx(run.tenantId, 'write', async (transaction) => {
1148
+ const inputPayloadId = await this.#storePayload(
1149
+ transaction,
1150
+ run.tenantId,
1151
+ run.id,
1152
+ 'workflow.input',
1153
+ write.input,
1154
+ run.queuedAt,
1155
+ );
1156
+ await this.#exec(transaction, SQL.insertRun, [
1157
+ run.id,
1158
+ run.tenantId,
1159
+ run.workflowId,
1160
+ run.workflowKey,
1161
+ run.workflowName,
1162
+ run.workflowRevision,
1163
+ run.graphChecksum,
1164
+ JSON.stringify(run.graph),
1165
+ JSON.stringify(run.compiledOrder),
1166
+ run.mode,
1167
+ run.status,
1168
+ JSON.stringify(run.actor),
1169
+ JSON.stringify(run.authorizationSubject),
1170
+ JSON.stringify(run.origin),
1171
+ JSON.stringify(run.permissionSnapshot),
1172
+ run.permissionDigest,
1173
+ run.inputHash,
1174
+ inputPayloadId,
1175
+ JSON.stringify(write.inputEvidence),
1176
+ run.idempotencyKey,
1177
+ JSON.stringify(WORKFLOW_LIMITS),
1178
+ run.totalNodes,
1179
+ JSON.stringify(run.usage),
1180
+ JSON.stringify(run.cost),
1181
+ run.queuedAt,
1182
+ ]);
1183
+ for (const node of run.graph.nodes) {
1184
+ await this.#exec(transaction, SQL.insertNodeState, [
1185
+ run.tenantId,
1186
+ run.id,
1187
+ node.id,
1188
+ ]);
1189
+ }
1190
+ await this.#appendEvent(
1191
+ transaction,
1192
+ run.tenantId,
1193
+ run.id,
1194
+ 'run.queued',
1195
+ {
1196
+ workflowRevision: run.workflowRevision,
1197
+ graphChecksum: run.graphChecksum,
1198
+ actor: run.actor as unknown as JsonValue,
1199
+ origin: run.origin as unknown as JsonValue,
1200
+ mode: run.mode,
1201
+ },
1202
+ run.queuedAt,
1203
+ run.mode === 'simulate' ? 0 : undefined,
1204
+ );
1205
+ await this.#appendAudit(
1206
+ transaction,
1207
+ run.tenantId,
1208
+ run.actor,
1209
+ run.origin,
1210
+ 'workflow-run.enqueued',
1211
+ 'workflow-run',
1212
+ run.id,
1213
+ {
1214
+ workflowId: run.workflowId,
1215
+ mode: run.mode,
1216
+ inputHash: run.inputHash,
1217
+ },
1218
+ run.queuedAt,
1219
+ );
1220
+ return { ...run, inputPayloadId };
1221
+ });
1222
+ }
1223
+
1224
+ async findRunByIdempotency(
1225
+ tenantId: string,
1226
+ key: string,
1227
+ ): Promise<WorkflowRunRecord | null> {
1228
+ return this.#tx(tenantId, 'read', async (transaction) => {
1229
+ const row = (
1230
+ await this.#query<RunRow>(transaction, SQL.findRunByIdempotency, [
1231
+ tenantId,
1232
+ key,
1233
+ ])
1234
+ )[0];
1235
+ return row ? runFromRow(row) : null;
1236
+ });
1237
+ }
1238
+
1239
+ async getRun(
1240
+ tenantId: string,
1241
+ runId: string,
1242
+ ): Promise<WorkflowRunRecord | null> {
1243
+ return this.#tx(tenantId, 'read', (transaction) =>
1244
+ this.#runIn(transaction, tenantId, runId),
1245
+ );
1246
+ }
1247
+
1248
+ async listRuns(
1249
+ tenantId: string,
1250
+ filters: WorkflowRunFilters,
1251
+ ): Promise<WorkflowRunPage> {
1252
+ const limit = Math.min(
1253
+ Math.max(1, Math.trunc(filters.limit ?? 50)),
1254
+ WORKFLOW_LIMITS.maxInteractivePage,
1255
+ );
1256
+ const parameters: Array<string | number> = [];
1257
+ const bind = (value: string | number) => {
1258
+ parameters.push(value);
1259
+ return `$${parameters.length}`;
1260
+ };
1261
+ const clauses = [`tenant_id = ${bind(tenantId)}`];
1262
+ if (filters.workflowId) {
1263
+ clauses.push(`workflow_id = ${bind(filters.workflowId)}`);
1264
+ }
1265
+ if (filters.mode) clauses.push(`mode = ${bind(filters.mode)}`);
1266
+ if (filters.status) clauses.push(`status = ${bind(filters.status)}`);
1267
+ if (filters.actorKind) {
1268
+ clauses.push(`${jsonKind('actor_json')} = ${bind(filters.actorKind)}`);
1269
+ }
1270
+ if (filters.originKind) {
1271
+ clauses.push(`${jsonKind('origin_json')} = ${bind(filters.originKind)}`);
1272
+ }
1273
+ let snapshot: { readonly queuedAt: number; readonly id: string } | null =
1274
+ null;
1275
+ if (filters.cursor) {
1276
+ const [snapshotRaw, positionRaw, extra] = filters.cursor.split('|');
1277
+ const parseBoundary = (raw: string | undefined) => {
1278
+ const separator = raw?.indexOf(':') ?? -1;
1279
+ const queuedAt = Number(raw?.slice(0, separator));
1280
+ const id = raw?.slice(separator + 1) ?? '';
1281
+ if (separator < 1 || !Number.isSafeInteger(queuedAt) || !id) {
1282
+ throw new Error('WORKFLOW_CURSOR_INVALID');
1283
+ }
1284
+ return { queuedAt, id };
1285
+ };
1286
+ if (extra !== undefined) throw new Error('WORKFLOW_CURSOR_INVALID');
1287
+ snapshot = parseBoundary(snapshotRaw);
1288
+ const position = parseBoundary(positionRaw);
1289
+ clauses.push(
1290
+ `(queued_at < ${bind(snapshot.queuedAt)} OR (queued_at = ${bind(
1291
+ snapshot.queuedAt,
1292
+ )} AND id <= ${bind(snapshot.id)}))`,
1293
+ );
1294
+ clauses.push(
1295
+ `(queued_at < ${bind(position.queuedAt)} OR (queued_at = ${bind(
1296
+ position.queuedAt,
1297
+ )} AND id < ${bind(position.id)}))`,
1298
+ );
1299
+ }
1300
+ const text = `SELECT * FROM workflow_runs WHERE ${clauses.join(' AND ')}
1301
+ ORDER BY queued_at DESC, id DESC LIMIT ${bind(limit + 1)}`;
1302
+ const rows = await this.#tx(tenantId, 'read', (transaction) =>
1303
+ this.#query<RunRow>(transaction, text, parameters),
1304
+ );
1305
+ const page = rows.slice(0, limit).map(runFromRow);
1306
+ const first = page[0];
1307
+ const last = page.at(-1);
1308
+ const highWaterMark =
1309
+ snapshot ?? (first ? { queuedAt: first.queuedAt, id: first.id } : null);
1310
+ return {
1311
+ runs: page,
1312
+ nextCursor:
1313
+ rows.length > limit && last && highWaterMark
1314
+ ? `${highWaterMark.queuedAt}:${highWaterMark.id}|${last.queuedAt}:${last.id}`
1315
+ : null,
1316
+ };
1317
+ }
1318
+
1319
+ async runDetail(
1320
+ tenantId: string,
1321
+ runId: string,
1322
+ ): Promise<WorkflowRunDetail | null> {
1323
+ const row = await this.#tx(
1324
+ tenantId,
1325
+ 'read',
1326
+ async (transaction) =>
1327
+ (
1328
+ await this.#query<RunRow>(transaction, SQL.getRun, [tenantId, runId])
1329
+ )[0],
1330
+ );
1331
+ if (!row) return null;
1332
+ const events = await this.readEvents(
1333
+ tenantId,
1334
+ runId,
1335
+ 0,
1336
+ WORKFLOW_LIMITS.maxRunEvents,
1337
+ );
1338
+ const projection = projectWorkflowRunEvents(events);
1339
+ if (projection.status !== row.status) {
1340
+ throw new WorkflowEventProjectionError(
1341
+ 'WORKFLOW_EVENT_TRANSITION_INVALID',
1342
+ `Workflow run projection is ${row.status} but its event stream rebuilds as ${projection.status}.`,
1343
+ );
1344
+ }
1345
+ const nodes = await this.readNodeStates(tenantId, runId);
1346
+ for (const [nodeId, status] of Object.entries(projection.nodeStatuses)) {
1347
+ if (nodes.find((node) => node.nodeId === nodeId)?.status !== status) {
1348
+ throw new WorkflowEventProjectionError(
1349
+ 'WORKFLOW_EVENT_TRANSITION_INVALID',
1350
+ `Workflow node ${nodeId} does not match its event projection.`,
1351
+ );
1352
+ }
1353
+ }
1354
+ for (const projected of Object.values(projection.attempts)) {
1355
+ const actual = nodes
1356
+ .find((node) => node.nodeId === projected.nodeId)
1357
+ ?.attempts.find((attempt) => attempt.attempt === projected.attempt);
1358
+ if (
1359
+ actual?.status !== projected.status ||
1360
+ actual.outcomePort !== projected.outcomePort
1361
+ ) {
1362
+ throw new WorkflowEventProjectionError(
1363
+ 'WORKFLOW_EVENT_TRANSITION_INVALID',
1364
+ `Workflow attempt ${projected.nodeId}:${projected.attempt} does not match its event projection.`,
1365
+ );
1366
+ }
1367
+ }
1368
+ const edges = await this.readEdgeTransfers(tenantId, runId);
1369
+ for (const [edgeId, state] of Object.entries(projection.edgeStates)) {
1370
+ if (edges.find((edge) => edge.edgeId === edgeId)?.state !== state) {
1371
+ throw new WorkflowEventProjectionError(
1372
+ 'WORKFLOW_EVENT_TRANSITION_INVALID',
1373
+ `Workflow edge ${edgeId} does not match its event projection.`,
1374
+ );
1375
+ }
1376
+ }
1377
+ return {
1378
+ run: runFromRow(row),
1379
+ graph: parse<WorkflowGraphV1>(row.graph_json),
1380
+ compiledOrder: parse<readonly string[]>(row.compiled_order_json),
1381
+ nodes,
1382
+ edges,
1383
+ events,
1384
+ input: parse<WorkflowPayloadEvidenceV1>(row.input_evidence_json),
1385
+ output:
1386
+ row.output_evidence_json === null
1387
+ ? {
1388
+ version: 1,
1389
+ state: 'absent',
1390
+ schemaId: 'workflow.output',
1391
+ hash: row.input_hash,
1392
+ originalByteSize: 0,
1393
+ reason: 'not-emitted',
1394
+ }
1395
+ : parse<WorkflowPayloadEvidenceV1>(row.output_evidence_json),
1396
+ };
1397
+ }
1398
+
1399
+ /* The claim crosses tenants to find due work, which is the only way a
1400
+ worker can learn whose run is next. The poll returns routing columns on
1401
+ the read-only background handle; the claim itself re-reads the run under
1402
+ the tenant that row named and re-checks the predicate there, so a run
1403
+ that moved in between is passed over instead of claimed twice. */
1404
+ async claimNext(
1405
+ workerId: string,
1406
+ now: number,
1407
+ leaseExpiresAt: number,
1408
+ ): Promise<WorkflowRunRecord | null> {
1409
+ await this.readyPromise;
1410
+ const candidates = (
1411
+ await this.handles.background.transaction(
1412
+ (transaction) =>
1413
+ transaction.query<RunRoutingRow>({
1414
+ text: SQL.claimCandidates,
1415
+ parameters: [now, now, now, now, 20] as never,
1416
+ }),
1417
+ { access: 'read' },
1418
+ )
1419
+ ).rows;
1420
+ for (const candidate of candidates) {
1421
+ const claimed = await this.#claim(
1422
+ candidate.tenant_id,
1423
+ candidate.id,
1424
+ workerId,
1425
+ now,
1426
+ leaseExpiresAt,
1427
+ );
1428
+ if (claimed) return claimed;
1429
+ }
1430
+ return null;
1431
+ }
1432
+
1433
+ async #claim(
1434
+ tenantId: string,
1435
+ runId: string,
1436
+ workerId: string,
1437
+ now: number,
1438
+ leaseExpiresAt: number,
1439
+ ): Promise<WorkflowRunRecord | null> {
1440
+ return this.#tx(tenantId, 'write', async (transaction) => {
1441
+ const row = (
1442
+ await this.#query<RunRow>(transaction, SQL.getRun, [tenantId, runId])
1443
+ )[0];
1444
+ if (!row) return null;
1445
+ const leaseExpired =
1446
+ row.lease_expires_at === null || integer(row.lease_expires_at) <= now;
1447
+ const claimable =
1448
+ row.mode === 'live' &&
1449
+ (row.status === 'queued' ||
1450
+ (['running', 'waiting-agent', 'cancel-requested'].includes(
1451
+ row.status,
1452
+ ) &&
1453
+ leaseExpired) ||
1454
+ (row.status === 'waiting-retry' && leaseExpired));
1455
+ if (!claimable) return null;
1456
+ const priorLease = row.lease_owner;
1457
+ const recovering = priorLease !== null && row.status !== 'queued';
1458
+ /* A due retry becomes runnable again. Keeping the run in waiting-retry
1459
+ after its node starts would make the new waiting-child projection
1460
+ unreachable by the next claim. */
1461
+ const status =
1462
+ row.status === 'queued' || row.status === 'waiting-retry'
1463
+ ? 'running'
1464
+ : row.status;
1465
+ const changed = await this.#exec(transaction, SQL.claimRun, [
1466
+ status,
1467
+ workerId,
1468
+ leaseExpiresAt,
1469
+ now,
1470
+ tenantId,
1471
+ runId,
1472
+ now,
1473
+ workerId,
1474
+ ]);
1475
+ if (changed !== 1) return null;
1476
+ const actor = parse<Actor>(row.actor_json);
1477
+ const origin = parse<WorkflowExecutionOrigin>(row.origin_json);
1478
+ if (recovering) {
1479
+ await this.#appendEvent(
1480
+ transaction,
1481
+ tenantId,
1482
+ runId,
1483
+ 'run.recovered',
1484
+ { priorLease: priorLease ?? '', workerId, reason: 'lease-expired' },
1485
+ now,
1486
+ );
1487
+ await this.#appendAudit(
1488
+ transaction,
1489
+ tenantId,
1490
+ actor,
1491
+ origin,
1492
+ 'workflow-run.recovered',
1493
+ 'workflow-run',
1494
+ runId,
1495
+ { workerId },
1496
+ now,
1497
+ );
1498
+ } else if (row.status === 'queued') {
1499
+ await this.#appendEvent(
1500
+ transaction,
1501
+ tenantId,
1502
+ runId,
1503
+ 'run.claimed',
1504
+ { workerId, leaseExpiresAt },
1505
+ now,
1506
+ );
1507
+ await this.#appendAudit(
1508
+ transaction,
1509
+ tenantId,
1510
+ actor,
1511
+ origin,
1512
+ 'workflow-run.claimed',
1513
+ 'workflow-run',
1514
+ runId,
1515
+ { workerId },
1516
+ now,
1517
+ );
1518
+ }
1519
+ return this.#runIn(transaction, tenantId, runId);
1520
+ });
1521
+ }
1522
+
1523
+ async renewLease(
1524
+ tenantId: string,
1525
+ runId: string,
1526
+ workerId: string,
1527
+ leaseExpiresAt: number,
1528
+ ): Promise<boolean> {
1529
+ return (
1530
+ (await this.#tx(tenantId, 'write', (transaction) =>
1531
+ this.#exec(transaction, SQL.renewLease, [
1532
+ leaseExpiresAt,
1533
+ tenantId,
1534
+ runId,
1535
+ workerId,
1536
+ ]),
1537
+ )) === 1
1538
+ );
1539
+ }
1540
+
1541
+ async releaseLease(
1542
+ tenantId: string,
1543
+ runId: string,
1544
+ workerId: string,
1545
+ ): Promise<void> {
1546
+ await this.#tx(tenantId, 'write', (transaction) =>
1547
+ this.#exec(transaction, SQL.releaseLease, [tenantId, runId, workerId]),
1548
+ );
1549
+ }
1550
+
1551
+ async appendRunEvent(
1552
+ tenantId: string,
1553
+ runId: string,
1554
+ type: WorkflowRunEventTypeV1,
1555
+ payload: Readonly<Record<string, JsonValue>>,
1556
+ recordedAt: number,
1557
+ virtualOffsetMs?: number,
1558
+ ): Promise<WorkflowRunEventV1> {
1559
+ return this.#tx(tenantId, 'write', (transaction) =>
1560
+ this.#appendEvent(
1561
+ transaction,
1562
+ tenantId,
1563
+ runId,
1564
+ type,
1565
+ payload,
1566
+ recordedAt,
1567
+ virtualOffsetMs,
1568
+ ),
1569
+ );
1570
+ }
1571
+
1572
+ async readEvents(
1573
+ tenantId: string,
1574
+ runId: string,
1575
+ afterSequence: number,
1576
+ limit: number,
1577
+ ): Promise<readonly WorkflowRunEventV1[]> {
1578
+ return this.#tx(tenantId, 'read', async (transaction) =>
1579
+ (
1580
+ await this.#query<EventRow>(transaction, SQL.readEvents, [
1581
+ tenantId,
1582
+ runId,
1583
+ Math.max(0, Math.trunc(afterSequence)),
1584
+ Math.min(
1585
+ Math.max(1, Math.trunc(limit)),
1586
+ WORKFLOW_LIMITS.maxRunEvents,
1587
+ ),
1588
+ ])
1589
+ ).map(eventFromRow),
1590
+ );
1591
+ }
1592
+
1593
+ async startAttempt(
1594
+ write: StartAttemptWrite,
1595
+ actor: Actor,
1596
+ origin: WorkflowExecutionOrigin,
1597
+ ): Promise<WorkflowNodeAttempt> {
1598
+ return this.#tx(write.tenantId, 'write', async (transaction) => {
1599
+ const payloadId = await this.#storePayload(
1600
+ transaction,
1601
+ write.tenantId,
1602
+ write.runId,
1603
+ write.schemaId,
1604
+ write.input,
1605
+ write.recordedAt,
1606
+ );
1607
+ const absent: WorkflowPayloadEvidenceV1 = {
1608
+ version: 1,
1609
+ state: 'absent',
1610
+ schemaId: write.schemaId,
1611
+ hash: write.inputEvidence.hash,
1612
+ originalByteSize: 0,
1613
+ reason: 'not-emitted',
1614
+ };
1615
+ await this.#exec(transaction, SQL.insertAttempt, [
1616
+ write.tenantId,
1617
+ write.runId,
1618
+ write.nodeId,
1619
+ write.attempt,
1620
+ write.nodeType,
1621
+ write.semanticGroup,
1622
+ write.sideEffectIdempotencyKey,
1623
+ payloadId,
1624
+ JSON.stringify(write.inputEvidence),
1625
+ JSON.stringify(absent),
1626
+ write.recordedAt,
1627
+ ]);
1628
+ await this.#exec(transaction, SQL.startNodeState, [
1629
+ write.attempt,
1630
+ write.recordedAt,
1631
+ write.recordedAt,
1632
+ write.tenantId,
1633
+ write.runId,
1634
+ write.nodeId,
1635
+ ]);
1636
+ await this.#appendEvent(
1637
+ transaction,
1638
+ write.tenantId,
1639
+ write.runId,
1640
+ 'node.ready',
1641
+ { nodeId: write.nodeId },
1642
+ write.recordedAt,
1643
+ write.virtualOffsetMs,
1644
+ );
1645
+ if (write.attempt > 1) {
1646
+ await this.#appendEvent(
1647
+ transaction,
1648
+ write.tenantId,
1649
+ write.runId,
1650
+ 'node.retry.started',
1651
+ {
1652
+ nodeId: write.nodeId,
1653
+ attempt: write.attempt,
1654
+ semanticGroup: write.semanticGroup,
1655
+ },
1656
+ write.recordedAt,
1657
+ write.virtualOffsetMs,
1658
+ );
1659
+ }
1660
+ await this.#appendEvent(
1661
+ transaction,
1662
+ write.tenantId,
1663
+ write.runId,
1664
+ 'node.attempt.started',
1665
+ {
1666
+ nodeId: write.nodeId,
1667
+ attempt: write.attempt,
1668
+ semanticGroup: write.semanticGroup,
1669
+ input: write.inputEvidence as unknown as JsonValue,
1670
+ },
1671
+ write.recordedAt,
1672
+ write.virtualOffsetMs,
1673
+ );
1674
+ await this.#appendAudit(
1675
+ transaction,
1676
+ write.tenantId,
1677
+ actor,
1678
+ origin,
1679
+ 'workflow-node.attempt.started',
1680
+ 'workflow-node',
1681
+ `${write.runId}:${write.nodeId}`,
1682
+ { attempt: write.attempt },
1683
+ write.recordedAt,
1684
+ );
1685
+ return {
1686
+ nodeId: write.nodeId,
1687
+ attempt: write.attempt,
1688
+ nodeType: write.nodeType,
1689
+ status: 'running',
1690
+ outcomePort: null,
1691
+ semanticGroup: write.semanticGroup,
1692
+ sideEffectIdempotencyKey: write.sideEffectIdempotencyKey,
1693
+ input: write.inputEvidence,
1694
+ output: absent,
1695
+ childKind: null,
1696
+ childId: null,
1697
+ childObservationDeadlineAt: null,
1698
+ failureCode: null,
1699
+ retryClassification: null,
1700
+ selectedBackoffMs: null,
1701
+ nextAttemptAt: null,
1702
+ startedAt: write.recordedAt,
1703
+ completedAt: null,
1704
+ durationMs: null,
1705
+ };
1706
+ });
1707
+ }
1708
+
1709
+ async markChildWaiting(
1710
+ tenantId: string,
1711
+ runId: string,
1712
+ nodeId: string,
1713
+ attempt: number,
1714
+ childKind: 'agent' | 'action',
1715
+ childId: string,
1716
+ observationDeadlineAt: number,
1717
+ recordedAt: number,
1718
+ ): Promise<void> {
1719
+ await this.#tx(tenantId, 'write', async (transaction) => {
1720
+ const changed = await this.#exec(
1721
+ transaction,
1722
+ SQL.markAttemptWaitingChild,
1723
+ [
1724
+ childKind,
1725
+ childId,
1726
+ observationDeadlineAt,
1727
+ tenantId,
1728
+ runId,
1729
+ nodeId,
1730
+ attempt,
1731
+ ],
1732
+ );
1733
+ if (changed !== 1) throw new Error('WORKFLOW_ATTEMPT_TERMINAL');
1734
+ await this.#exec(transaction, SQL.markNodeWaitingChild, [
1735
+ tenantId,
1736
+ runId,
1737
+ nodeId,
1738
+ ]);
1739
+ if (childKind === 'agent') {
1740
+ await this.#exec(transaction, SQL.markRunWaitingAgent, [
1741
+ tenantId,
1742
+ runId,
1743
+ ]);
1744
+ }
1745
+ await this.#appendEvent(
1746
+ transaction,
1747
+ tenantId,
1748
+ runId,
1749
+ 'node.child.waiting',
1750
+ { nodeId, attempt, childKind, childId, observationDeadlineAt },
1751
+ recordedAt,
1752
+ );
1753
+ });
1754
+ }
1755
+
1756
+ async settleAttempt(
1757
+ write: SettleAttemptWrite,
1758
+ actor: Actor,
1759
+ origin: WorkflowExecutionOrigin,
1760
+ ): Promise<WorkflowNodeAttempt> {
1761
+ return this.#tx(write.tenantId, 'write', async (transaction) => {
1762
+ const prior = (
1763
+ await this.#query<AttemptRow>(transaction, SQL.getAttempt, [
1764
+ write.tenantId,
1765
+ write.runId,
1766
+ write.nodeId,
1767
+ write.attempt,
1768
+ ])
1769
+ )[0];
1770
+ if (!prior || !['running', 'waiting-child'].includes(prior.status)) {
1771
+ throw new Error('WORKFLOW_ATTEMPT_TERMINAL');
1772
+ }
1773
+ const payloadId =
1774
+ write.output === undefined
1775
+ ? null
1776
+ : await this.#storePayload(
1777
+ transaction,
1778
+ write.tenantId,
1779
+ write.runId,
1780
+ write.schemaId,
1781
+ write.output,
1782
+ write.recordedAt,
1783
+ );
1784
+ const duration = Math.max(
1785
+ 0,
1786
+ write.recordedAt - integer(prior.started_at),
1787
+ );
1788
+ await this.#exec(transaction, SQL.settleAttempt, [
1789
+ write.status,
1790
+ write.outcomePort,
1791
+ payloadId,
1792
+ JSON.stringify(write.outputEvidence),
1793
+ write.failureCode,
1794
+ write.retryClassification,
1795
+ write.selectedBackoffMs,
1796
+ write.nextAttemptAt,
1797
+ write.recordedAt,
1798
+ duration,
1799
+ write.tenantId,
1800
+ write.runId,
1801
+ write.nodeId,
1802
+ write.attempt,
1803
+ ]);
1804
+ const retrying = write.nextAttemptAt !== null;
1805
+ if (!retrying && prior.child_kind === 'action') {
1806
+ const run = await this.#runIn(transaction, write.tenantId, write.runId);
1807
+ if (run) {
1808
+ const usage: WorkflowUsageRollupV1 = {
1809
+ ...run.usage,
1810
+ actionInvocations: run.usage.actionInvocations + 1,
1811
+ unpricedActions: run.usage.unpricedActions + 1,
1812
+ };
1813
+ const cost: WorkflowCostRollupV1 = {
1814
+ ...run.cost,
1815
+ unpricedActions: run.cost.unpricedActions + 1,
1816
+ };
1817
+ await this.#exec(transaction, SQL.setRunRollups, [
1818
+ JSON.stringify(usage),
1819
+ JSON.stringify(cost),
1820
+ write.tenantId,
1821
+ write.runId,
1822
+ ]);
1823
+ }
1824
+ }
1825
+ await this.#exec(transaction, SQL.settleNodeState, [
1826
+ retrying ? 'waiting-retry' : write.status,
1827
+ write.outcomePort,
1828
+ write.nextAttemptAt,
1829
+ retrying ? null : write.recordedAt,
1830
+ write.tenantId,
1831
+ write.runId,
1832
+ write.nodeId,
1833
+ ]);
1834
+ await this.#exec(transaction, SQL.advanceRunProgress, [
1835
+ retrying ? 'waiting-retry' : 'running',
1836
+ retrying ? 0 : 1,
1837
+ write.tenantId,
1838
+ write.runId,
1839
+ ]);
1840
+ await this.#appendEvent(
1841
+ transaction,
1842
+ write.tenantId,
1843
+ write.runId,
1844
+ 'node.attempt.settled',
1845
+ {
1846
+ nodeId: write.nodeId,
1847
+ attempt: write.attempt,
1848
+ status: write.status,
1849
+ outcomePort: write.outcomePort,
1850
+ output: write.outputEvidence as unknown as JsonValue,
1851
+ failureCode: write.failureCode,
1852
+ retryClassification: write.retryClassification,
1853
+ },
1854
+ write.recordedAt,
1855
+ write.virtualOffsetMs,
1856
+ );
1857
+ if (retrying) {
1858
+ await this.#appendEvent(
1859
+ transaction,
1860
+ write.tenantId,
1861
+ write.runId,
1862
+ 'node.retry.scheduled',
1863
+ {
1864
+ nodeId: write.nodeId,
1865
+ attempt: write.attempt,
1866
+ classification: write.retryClassification,
1867
+ backoffMs: write.selectedBackoffMs,
1868
+ nextAttemptAt: write.nextAttemptAt,
1869
+ },
1870
+ write.recordedAt,
1871
+ write.virtualOffsetMs,
1872
+ );
1873
+ }
1874
+ await this.#appendAudit(
1875
+ transaction,
1876
+ write.tenantId,
1877
+ actor,
1878
+ origin,
1879
+ retrying
1880
+ ? 'workflow-node.retry.scheduled'
1881
+ : 'workflow-node.attempt.settled',
1882
+ 'workflow-node',
1883
+ `${write.runId}:${write.nodeId}`,
1884
+ {
1885
+ attempt: write.attempt,
1886
+ status: write.status,
1887
+ failureCode: write.failureCode,
1888
+ },
1889
+ write.recordedAt,
1890
+ );
1891
+ return attemptFromRow({
1892
+ ...prior,
1893
+ status: write.status,
1894
+ outcome_port: write.outcomePort,
1895
+ output_evidence_json: JSON.stringify(write.outputEvidence),
1896
+ failure_code: write.failureCode,
1897
+ retry_classification: write.retryClassification,
1898
+ selected_backoff_ms: write.selectedBackoffMs,
1899
+ next_attempt_at: write.nextAttemptAt,
1900
+ completed_at: write.recordedAt,
1901
+ duration_ms: duration,
1902
+ });
1903
+ });
1904
+ }
1905
+
1906
+ async settleEdge(write: SettleEdgeWrite): Promise<WorkflowEdgeTransfer> {
1907
+ const transfer = write.transfer;
1908
+ return this.#tx(write.tenantId, 'write', async (transaction) => {
1909
+ const schemaId = transfer.evidence.schemaId;
1910
+ const payloadId =
1911
+ write.payload === undefined
1912
+ ? null
1913
+ : await this.#storePayload(
1914
+ transaction,
1915
+ write.tenantId,
1916
+ write.runId,
1917
+ schemaId,
1918
+ write.payload,
1919
+ transfer.settledAt,
1920
+ );
1921
+ await this.#exec(transaction, SQL.insertEdgeTransfer, [
1922
+ write.tenantId,
1923
+ write.runId,
1924
+ transfer.edgeId,
1925
+ transfer.sourceNodeId,
1926
+ transfer.sourcePort,
1927
+ transfer.sourceAttempt,
1928
+ transfer.targetNodeId,
1929
+ transfer.targetPort,
1930
+ transfer.state,
1931
+ transfer.reason,
1932
+ schemaId,
1933
+ payloadId,
1934
+ JSON.stringify(transfer.evidence),
1935
+ transfer.settledAt,
1936
+ ]);
1937
+ await this.#appendEvent(
1938
+ transaction,
1939
+ write.tenantId,
1940
+ write.runId,
1941
+ 'edge.settled',
1942
+ {
1943
+ edgeId: transfer.edgeId,
1944
+ state: transfer.state,
1945
+ sourceNodeId: transfer.sourceNodeId,
1946
+ sourcePort: transfer.sourcePort,
1947
+ sourceAttempt: transfer.sourceAttempt,
1948
+ targetNodeId: transfer.targetNodeId,
1949
+ targetPort: transfer.targetPort,
1950
+ reason: transfer.reason,
1951
+ evidence: transfer.evidence as unknown as JsonValue,
1952
+ },
1953
+ transfer.settledAt,
1954
+ write.virtualOffsetMs,
1955
+ );
1956
+ return transfer;
1957
+ });
1958
+ }
1959
+
1960
+ async markNodeSkipped(
1961
+ tenantId: string,
1962
+ runId: string,
1963
+ nodeId: string,
1964
+ reason: string,
1965
+ recordedAt: number,
1966
+ actor: Actor,
1967
+ origin: WorkflowExecutionOrigin,
1968
+ virtualOffsetMs?: number,
1969
+ ): Promise<void> {
1970
+ await this.#tx(tenantId, 'write', async (transaction) => {
1971
+ const changed = await this.#exec(transaction, SQL.skipNodeState, [
1972
+ recordedAt,
1973
+ tenantId,
1974
+ runId,
1975
+ nodeId,
1976
+ ]);
1977
+ if (changed !== 1) return;
1978
+ await this.#exec(transaction, SQL.countNodeSkipped, [tenantId, runId]);
1979
+ await this.#appendEvent(
1980
+ transaction,
1981
+ tenantId,
1982
+ runId,
1983
+ 'node.skipped',
1984
+ { nodeId, reason },
1985
+ recordedAt,
1986
+ virtualOffsetMs,
1987
+ );
1988
+ await this.#appendAudit(
1989
+ transaction,
1990
+ tenantId,
1991
+ actor,
1992
+ origin,
1993
+ 'workflow-node.skipped',
1994
+ 'workflow-node',
1995
+ `${runId}:${nodeId}`,
1996
+ { reason },
1997
+ recordedAt,
1998
+ );
1999
+ });
2000
+ }
2001
+
2002
+ async settleRun(
2003
+ tenantId: string,
2004
+ runId: string,
2005
+ status: Extract<
2006
+ WorkflowRunStatus,
2007
+ 'succeeded' | 'failed' | 'refused' | 'cancelled'
2008
+ >,
2009
+ failureCode: string | null,
2010
+ _output: JsonValue | undefined,
2011
+ outputEvidence: WorkflowPayloadEvidenceV1,
2012
+ usage: WorkflowUsageRollupV1,
2013
+ cost: WorkflowCostRollupV1,
2014
+ recordedAt: number,
2015
+ virtualOffsetMs?: number,
2016
+ ): Promise<WorkflowRunRecord | null> {
2017
+ return this.#tx(tenantId, 'write', async (transaction) => {
2018
+ const before = await this.#runIn(transaction, tenantId, runId);
2019
+ if (!before) return null;
2020
+ if (TERMINAL_RUNS.has(before.status)) return before;
2021
+ await this.#exec(transaction, SQL.settleRun, [
2022
+ status,
2023
+ failureCode,
2024
+ JSON.stringify(outputEvidence),
2025
+ JSON.stringify(usage),
2026
+ JSON.stringify(cost),
2027
+ recordedAt,
2028
+ tenantId,
2029
+ runId,
2030
+ ]);
2031
+ await this.#exec(transaction, SQL.expireRunPayloads, [
2032
+ recordedAt + this.#payloadRetentionMs,
2033
+ tenantId,
2034
+ runId,
2035
+ ]);
2036
+ await this.#appendEvent(
2037
+ transaction,
2038
+ tenantId,
2039
+ runId,
2040
+ `run.${status}` as WorkflowRunEventTypeV1,
2041
+ {
2042
+ failureCode,
2043
+ output: outputEvidence as unknown as JsonValue,
2044
+ usage: usage as unknown as JsonValue,
2045
+ cost: cost as unknown as JsonValue,
2046
+ },
2047
+ recordedAt,
2048
+ before.mode === 'simulate'
2049
+ ? (virtualOffsetMs ?? recordedAt - before.queuedAt)
2050
+ : undefined,
2051
+ );
2052
+ await this.#appendAudit(
2053
+ transaction,
2054
+ tenantId,
2055
+ before.actor,
2056
+ before.origin,
2057
+ `workflow-run.${status}`,
2058
+ 'workflow-run',
2059
+ runId,
2060
+ { failureCode },
2061
+ recordedAt,
2062
+ );
2063
+ return this.#runIn(transaction, tenantId, runId);
2064
+ });
2065
+ }
2066
+
2067
+ async requestCancellation(
2068
+ tenantId: string,
2069
+ runId: string,
2070
+ actor: Actor,
2071
+ origin: WorkflowExecutionOrigin,
2072
+ recordedAt: number,
2073
+ ): Promise<{
2074
+ readonly run: WorkflowRunRecord;
2075
+ readonly requested: boolean;
2076
+ } | null> {
2077
+ return this.#tx(tenantId, 'write', async (transaction) => {
2078
+ const run = await this.#runIn(transaction, tenantId, runId);
2079
+ if (!run) return null;
2080
+ if (TERMINAL_RUNS.has(run.status) || run.status === 'cancel-requested') {
2081
+ return { run, requested: false };
2082
+ }
2083
+ await this.#exec(transaction, SQL.requestCancellation, [
2084
+ recordedAt,
2085
+ tenantId,
2086
+ runId,
2087
+ ]);
2088
+ await this.#appendEvent(
2089
+ transaction,
2090
+ tenantId,
2091
+ runId,
2092
+ 'run.cancel.requested',
2093
+ {
2094
+ requester: actor as unknown as JsonValue,
2095
+ reason: 'operator-requested',
2096
+ requestedAt: recordedAt,
2097
+ },
2098
+ recordedAt,
2099
+ );
2100
+ await this.#appendAudit(
2101
+ transaction,
2102
+ tenantId,
2103
+ actor,
2104
+ origin,
2105
+ 'workflow-run.cancel.requested',
2106
+ 'workflow-run',
2107
+ runId,
2108
+ {},
2109
+ recordedAt,
2110
+ );
2111
+ return {
2112
+ run: (await this.#runIn(transaction, tenantId, runId))!,
2113
+ requested: true,
2114
+ };
2115
+ });
2116
+ }
2117
+
2118
+ async readExecutionPayload(
2119
+ tenantId: string,
2120
+ runId: string,
2121
+ payloadId: string,
2122
+ ): Promise<JsonValue> {
2123
+ const row = await this.#tx(
2124
+ tenantId,
2125
+ 'read',
2126
+ async (transaction) =>
2127
+ (
2128
+ await this.#query<{ ciphertext: string }>(
2129
+ transaction,
2130
+ SQL.readExecutionPayload,
2131
+ [tenantId, runId, payloadId],
2132
+ )
2133
+ )[0],
2134
+ );
2135
+ if (!row) throw new Error('WORKFLOW_PAYLOAD_UNREADABLE');
2136
+ return this.#payloadCodec.decrypt(row.ciphertext, {
2137
+ tenantId,
2138
+ runId,
2139
+ payloadId,
2140
+ });
2141
+ }
2142
+
2143
+ async readEdgePayload(
2144
+ tenantId: string,
2145
+ runId: string,
2146
+ edgeId: string,
2147
+ ): Promise<JsonValue | undefined> {
2148
+ const row = await this.#tx(
2149
+ tenantId,
2150
+ 'read',
2151
+ async (transaction) =>
2152
+ (
2153
+ await this.#query<{ payload_id: string | null }>(
2154
+ transaction,
2155
+ SQL.readEdgePayloadId,
2156
+ [tenantId, runId, edgeId],
2157
+ )
2158
+ )[0],
2159
+ );
2160
+ return row?.payload_id
2161
+ ? this.readExecutionPayload(tenantId, runId, row.payload_id)
2162
+ : undefined;
2163
+ }
2164
+
2165
+ async recordAgentUsage(
2166
+ tenantId: string,
2167
+ runId: string,
2168
+ childRunId: string,
2169
+ usage: {
2170
+ readonly inputTokens: number;
2171
+ readonly outputTokens: number;
2172
+ readonly totalTokens: number;
2173
+ },
2174
+ ): Promise<void> {
2175
+ await this.#tx(tenantId, 'write', async (transaction) => {
2176
+ const run = await this.#runIn(transaction, tenantId, runId);
2177
+ if (!run || run.usage.includedChildRunIds.includes(childRunId)) return;
2178
+ const next: WorkflowUsageRollupV1 = {
2179
+ ...run.usage,
2180
+ inputTokens:
2181
+ run.usage.inputTokens + Math.max(0, Math.trunc(usage.inputTokens)),
2182
+ outputTokens:
2183
+ run.usage.outputTokens + Math.max(0, Math.trunc(usage.outputTokens)),
2184
+ totalTokens:
2185
+ run.usage.totalTokens + Math.max(0, Math.trunc(usage.totalTokens)),
2186
+ includedChildRunIds: [...run.usage.includedChildRunIds, childRunId],
2187
+ unpricedChildRuns: run.usage.unpricedChildRuns + 1,
2188
+ };
2189
+ const cost: WorkflowCostRollupV1 = {
2190
+ ...run.cost,
2191
+ unpricedChildRuns: run.cost.unpricedChildRuns + 1,
2192
+ };
2193
+ await this.#exec(transaction, SQL.setRunRollups, [
2194
+ JSON.stringify(next),
2195
+ JSON.stringify(cost),
2196
+ tenantId,
2197
+ runId,
2198
+ ]);
2199
+ });
2200
+ }
2201
+
2202
+ async readNodeStates(
2203
+ tenantId: string,
2204
+ runId: string,
2205
+ ): Promise<readonly WorkflowNodeExecution[]> {
2206
+ return this.#tx(tenantId, 'read', async (transaction) => {
2207
+ const states = await this.#query<NodeStateRow>(
2208
+ transaction,
2209
+ SQL.readNodeStates,
2210
+ [tenantId, runId],
2211
+ );
2212
+ const attempts = await this.#query<AttemptRow>(
2213
+ transaction,
2214
+ SQL.readAttempts,
2215
+ [tenantId, runId],
2216
+ );
2217
+ return states.map((state) => ({
2218
+ nodeId: state.node_id,
2219
+ status: state.status,
2220
+ latestAttempt: integer(state.latest_attempt),
2221
+ selectedOutcomePort: state.selected_outcome_port,
2222
+ nextAttemptAt: optionalInteger(state.next_attempt_at),
2223
+ readyAt: optionalInteger(state.ready_at),
2224
+ startedAt: optionalInteger(state.started_at),
2225
+ settledAt: optionalInteger(state.settled_at),
2226
+ attempts: attempts
2227
+ .filter((attempt) => attempt.node_id === state.node_id)
2228
+ .map(attemptFromRow),
2229
+ }));
2230
+ });
2231
+ }
2232
+
2233
+ async readEdgeTransfers(
2234
+ tenantId: string,
2235
+ runId: string,
2236
+ ): Promise<readonly WorkflowEdgeTransfer[]> {
2237
+ return this.#tx(tenantId, 'read', async (transaction) =>
2238
+ (
2239
+ await this.#query<EdgeRow>(transaction, SQL.readEdgeTransfers, [
2240
+ tenantId,
2241
+ runId,
2242
+ ])
2243
+ ).map((row) => ({
2244
+ edgeId: row.edge_id,
2245
+ sourceNodeId: row.source_node_id,
2246
+ sourcePort: row.source_port,
2247
+ sourceAttempt: optionalInteger(row.source_attempt),
2248
+ targetNodeId: row.target_node_id,
2249
+ targetPort: row.target_port,
2250
+ state: row.state,
2251
+ reason: row.reason,
2252
+ evidence: parse<WorkflowPayloadEvidenceV1>(row.evidence_json),
2253
+ settledAt: integer(row.settled_at),
2254
+ })),
2255
+ );
2256
+ }
2257
+
2258
+ async listAudit(
2259
+ tenantId: string,
2260
+ limit: number,
2261
+ beforeSequence?: number,
2262
+ ): Promise<WorkflowAuditPage> {
2263
+ const bounded = Math.min(Math.max(1, Math.trunc(limit)), 100);
2264
+ const rows = await this.#tx(tenantId, 'read', (transaction) =>
2265
+ beforeSequence === undefined
2266
+ ? this.#query<AuditRow>(transaction, SQL.listAudit, [
2267
+ tenantId,
2268
+ bounded + 1,
2269
+ ])
2270
+ : this.#query<AuditRow>(transaction, SQL.listAuditBefore, [
2271
+ tenantId,
2272
+ beforeSequence,
2273
+ bounded + 1,
2274
+ ]),
2275
+ );
2276
+ const events = rows.slice(0, bounded).map(auditFromRow);
2277
+ return {
2278
+ events,
2279
+ nextCursor:
2280
+ rows.length > bounded && events.at(-1)
2281
+ ? String(events.at(-1)!.sequence)
2282
+ : null,
2283
+ };
2284
+ }
2285
+
2286
+ async verifyAudit(tenantId: string): Promise<WorkflowAuditVerification> {
2287
+ const rows = await this.#tx(tenantId, 'read', (transaction) =>
2288
+ this.#query<AuditRow>(transaction, SQL.auditChain, [tenantId]),
2289
+ );
2290
+ let previousHash: string | null = null;
2291
+ let last = 0;
2292
+ for (const row of rows) {
2293
+ const event = auditFromRow(row);
2294
+ const expected = auditHash({
2295
+ sequence: event.sequence,
2296
+ actor: event.actor,
2297
+ origin: event.origin,
2298
+ action: event.action,
2299
+ subjectType: event.subjectType,
2300
+ subjectId: event.subjectId,
2301
+ metadata: event.metadata,
2302
+ occurredAt: event.occurredAt,
2303
+ previousHash,
2304
+ });
2305
+ if (event.previousHash !== previousHash || event.eventHash !== expected) {
2306
+ return {
2307
+ valid: false,
2308
+ checkedThroughSequence: Math.max(0, event.sequence - 1),
2309
+ firstBrokenSequence: event.sequence,
2310
+ expectedPreviousHash: previousHash,
2311
+ actualPreviousHash: event.previousHash,
2312
+ };
2313
+ }
2314
+ previousHash = event.eventHash;
2315
+ last = event.sequence;
2316
+ }
2317
+ return { valid: true, checkedThroughSequence: last };
2318
+ }
2319
+
2320
+ /* Retention sweeps every tenant, so the candidate scan runs on the
2321
+ read-only background handle and returns routing columns and a hash. Each
2322
+ redaction then runs in its own transaction under the tenant that owns it. */
2323
+ async applyPayloadRetention(now: number, limit = 100): Promise<number> {
2324
+ await this.readyPromise;
2325
+ const boundedLimit = Math.min(Math.max(1, Math.trunc(limit)), 1_000);
2326
+ const candidates = (
2327
+ await this.handles.background.transaction(
2328
+ (transaction) =>
2329
+ transaction.query<PayloadRoutingRow>({
2330
+ text: SQL.retentionCandidates,
2331
+ parameters: [now, boundedLimit] as never,
2332
+ }),
2333
+ { access: 'read' },
2334
+ )
2335
+ ).rows;
2336
+ for (const row of candidates) {
2337
+ await this.#tx(row.tenant_id, 'write', async (transaction) => {
2338
+ await this.#appendEvent(
2339
+ transaction,
2340
+ row.tenant_id,
2341
+ row.run_id,
2342
+ 'payload.retention.applied',
2343
+ {
2344
+ payloadId: row.id,
2345
+ hash: row.payload_hash,
2346
+ policy: 'terminal-ttl',
2347
+ priorEvidenceState: 'available',
2348
+ },
2349
+ now,
2350
+ );
2351
+ await this.#exec(transaction, SQL.expireRunInputEvidence, [
2352
+ row.tenant_id,
2353
+ row.run_id,
2354
+ row.id,
2355
+ ]);
2356
+ await this.#exec(transaction, SQL.expireAttemptEvidence, [
2357
+ row.id,
2358
+ row.id,
2359
+ row.tenant_id,
2360
+ row.run_id,
2361
+ row.id,
2362
+ row.id,
2363
+ ]);
2364
+ await this.#exec(transaction, SQL.expireEdgeEvidence, [
2365
+ row.tenant_id,
2366
+ row.run_id,
2367
+ row.id,
2368
+ ]);
2369
+ await this.#exec(transaction, SQL.deletePayload, [
2370
+ row.tenant_id,
2371
+ row.run_id,
2372
+ row.id,
2373
+ ]);
2374
+ const remaining = (
2375
+ await this.#query<{ count: Int }>(
2376
+ transaction,
2377
+ SQL.countExecutionPayloads,
2378
+ [row.tenant_id, row.run_id],
2379
+ )
2380
+ )[0];
2381
+ if (integer(remaining?.count ?? 0) === 0) {
2382
+ await this.#exec(transaction, SQL.expireRunOutputEvidence, [
2383
+ row.tenant_id,
2384
+ row.run_id,
2385
+ ]);
2386
+ }
2387
+ });
2388
+ }
2389
+ return candidates.length;
2390
+ }
2391
+
2392
+ async countRuns(tenantId: string): Promise<number> {
2393
+ const row = await this.#tx(
2394
+ tenantId,
2395
+ 'read',
2396
+ async (transaction) =>
2397
+ (
2398
+ await this.#query<{ count: Int }>(transaction, SQL.countRuns, [
2399
+ tenantId,
2400
+ ])
2401
+ )[0],
2402
+ );
2403
+ return integer(row?.count ?? 0);
2404
+ }
2405
+
2406
+ async close(): Promise<void> {}
2407
+ }