@acorex-platform/framework-server 0.0.38 → 0.0.48

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 (446) hide show
  1. package/core/src/index.d.ts +1 -0
  2. package/core/src/index.d.ts.map +1 -1
  3. package/core/src/index.js +1 -0
  4. package/core/src/index.js.map +1 -1
  5. package/core/src/lib/filters/http-exception.filter.d.ts +1 -4
  6. package/core/src/lib/filters/http-exception.filter.d.ts.map +1 -1
  7. package/core/src/lib/filters/http-exception.filter.js +6 -11
  8. package/core/src/lib/filters/http-exception.filter.js.map +1 -1
  9. package/core/src/lib/hook/axp-hook-registry.service.d.ts +10 -0
  10. package/core/src/lib/hook/axp-hook-registry.service.d.ts.map +1 -0
  11. package/core/src/lib/hook/axp-hook-registry.service.js +35 -0
  12. package/core/src/lib/hook/axp-hook-registry.service.js.map +1 -0
  13. package/core/src/lib/hook/axp-hook.module.d.ts +3 -0
  14. package/core/src/lib/hook/axp-hook.module.d.ts.map +1 -0
  15. package/core/src/lib/hook/axp-hook.module.js +21 -0
  16. package/core/src/lib/hook/axp-hook.module.js.map +1 -0
  17. package/core/src/lib/hook/index.d.ts +3 -0
  18. package/core/src/lib/hook/index.d.ts.map +1 -0
  19. package/core/src/lib/hook/index.js +6 -0
  20. package/core/src/lib/hook/index.js.map +1 -0
  21. package/core/src/lib/runtime/entity-runtime.handlers.d.ts +32 -0
  22. package/core/src/lib/runtime/entity-runtime.handlers.d.ts.map +1 -0
  23. package/core/src/lib/runtime/entity-runtime.handlers.js +124 -0
  24. package/core/src/lib/runtime/entity-runtime.handlers.js.map +1 -0
  25. package/core/src/lib/runtime/index.d.ts +1 -0
  26. package/core/src/lib/runtime/index.d.ts.map +1 -1
  27. package/core/src/lib/runtime/index.js +1 -0
  28. package/core/src/lib/runtime/index.js.map +1 -1
  29. package/core/src/lib/runtime/runtime-dispatcher.service.d.ts +4 -1
  30. package/core/src/lib/runtime/runtime-dispatcher.service.d.ts.map +1 -1
  31. package/core/src/lib/runtime/runtime-dispatcher.service.js +60 -50
  32. package/core/src/lib/runtime/runtime-dispatcher.service.js.map +1 -1
  33. package/core/src/lib/runtime/runtime.module.d.ts.map +1 -1
  34. package/core/src/lib/runtime/runtime.module.js +3 -0
  35. package/core/src/lib/runtime/runtime.module.js.map +1 -1
  36. package/domain/abstract/src/index.d.ts +1 -0
  37. package/domain/abstract/src/index.d.ts.map +1 -1
  38. package/domain/abstract/src/index.js +1 -0
  39. package/domain/abstract/src/index.js.map +1 -1
  40. package/domain/abstract/src/lib/definitions/entity-definition.types.d.ts +2 -0
  41. package/domain/abstract/src/lib/definitions/entity-definition.types.d.ts.map +1 -1
  42. package/domain/abstract/src/lib/queries/server-query-fallback.token.d.ts +15 -0
  43. package/domain/abstract/src/lib/queries/server-query-fallback.token.d.ts.map +1 -0
  44. package/domain/abstract/src/lib/queries/server-query-fallback.token.js +12 -0
  45. package/domain/abstract/src/lib/queries/server-query-fallback.token.js.map +1 -0
  46. package/domain/abstract/src/lib/queries/server-query-handler.decorator.d.ts +1 -0
  47. package/domain/abstract/src/lib/queries/server-query-handler.decorator.d.ts.map +1 -1
  48. package/domain/abstract/src/lib/queries/server-query-handler.decorator.js +2 -1
  49. package/domain/abstract/src/lib/queries/server-query-handler.decorator.js.map +1 -1
  50. package/domain/providers/src/lib/middlewares/child-count.middleware.d.ts.map +1 -1
  51. package/domain/providers/src/lib/middlewares/child-count.middleware.js +13 -2
  52. package/domain/providers/src/lib/middlewares/child-count.middleware.js.map +1 -1
  53. package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.d.ts.map +1 -1
  54. package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js +4 -2
  55. package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js.map +1 -1
  56. package/domain/providers/src/lib/server-command.registry.d.ts +3 -1
  57. package/domain/providers/src/lib/server-command.registry.d.ts.map +1 -1
  58. package/domain/providers/src/lib/server-command.registry.js +17 -7
  59. package/domain/providers/src/lib/server-command.registry.js.map +1 -1
  60. package/domain/providers/src/lib/server-query.registry.d.ts +8 -3
  61. package/domain/providers/src/lib/server-query.registry.d.ts.map +1 -1
  62. package/domain/providers/src/lib/server-query.registry.js +33 -6
  63. package/domain/providers/src/lib/server-query.registry.js.map +1 -1
  64. package/domain/providers/src/lib/utils/entity-relation-join.planner.d.ts +8 -1
  65. package/domain/providers/src/lib/utils/entity-relation-join.planner.d.ts.map +1 -1
  66. package/domain/providers/src/lib/utils/entity-relation-join.planner.js +46 -6
  67. package/domain/providers/src/lib/utils/entity-relation-join.planner.js.map +1 -1
  68. package/domain/providers/src/lib/utils/in-memory-query.util.d.ts +2 -2
  69. package/domain/providers/src/lib/utils/in-memory-query.util.d.ts.map +1 -1
  70. package/domain/providers/src/lib/utils/in-memory-query.util.js +16 -10
  71. package/domain/providers/src/lib/utils/in-memory-query.util.js.map +1 -1
  72. package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.d.ts +5 -0
  73. package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.d.ts.map +1 -0
  74. package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.js +56 -0
  75. package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.js.map +1 -0
  76. package/domain/providers/src/lib/utils/merge-detail-query.util.d.ts +1 -0
  77. package/domain/providers/src/lib/utils/merge-detail-query.util.d.ts.map +1 -1
  78. package/domain/providers/src/lib/utils/merge-detail-query.util.js +1 -1
  79. package/domain/providers/src/lib/utils/merge-detail-query.util.js.map +1 -1
  80. package/domain/seed/src/lib/axp-entity-to-definition.converter.d.ts.map +1 -1
  81. package/domain/seed/src/lib/axp-entity-to-definition.converter.js +20 -1
  82. package/domain/seed/src/lib/axp-entity-to-definition.converter.js.map +1 -1
  83. package/jql/abstract/src/index.d.ts +3 -0
  84. package/jql/abstract/src/index.d.ts.map +1 -1
  85. package/jql/abstract/src/index.js +3 -0
  86. package/jql/abstract/src/index.js.map +1 -1
  87. package/jql/abstract/src/lib/cli-seed-process.d.ts +3 -0
  88. package/jql/abstract/src/lib/cli-seed-process.d.ts.map +1 -0
  89. package/jql/abstract/src/lib/cli-seed-process.js +8 -0
  90. package/jql/abstract/src/lib/cli-seed-process.js.map +1 -0
  91. package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.d.ts.map +1 -1
  92. package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.js +3 -2
  93. package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.js.map +1 -1
  94. package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.d.ts +7 -0
  95. package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.d.ts.map +1 -1
  96. package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.js +4 -3
  97. package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.js.map +1 -1
  98. package/jql/abstract/src/lib/entity-store/entity-store-request.util.d.ts.map +1 -1
  99. package/jql/abstract/src/lib/entity-store/entity-store-request.util.js +2 -0
  100. package/jql/abstract/src/lib/entity-store/entity-store-request.util.js.map +1 -1
  101. package/jql/abstract/src/lib/entity-store/identity-membership.util.d.ts +11 -0
  102. package/jql/abstract/src/lib/entity-store/identity-membership.util.d.ts.map +1 -0
  103. package/jql/abstract/src/lib/entity-store/identity-membership.util.js +138 -0
  104. package/jql/abstract/src/lib/entity-store/identity-membership.util.js.map +1 -0
  105. package/jql/abstract/src/lib/entity-store/query-sort-locale.util.d.ts +6 -0
  106. package/jql/abstract/src/lib/entity-store/query-sort-locale.util.d.ts.map +1 -0
  107. package/jql/abstract/src/lib/entity-store/query-sort-locale.util.js +23 -0
  108. package/jql/abstract/src/lib/entity-store/query-sort-locale.util.js.map +1 -0
  109. package/jql/abstract/src/lib/jql.types.d.ts +5 -0
  110. package/jql/abstract/src/lib/jql.types.d.ts.map +1 -1
  111. package/jql/abstract/src/lib/jql.types.js.map +1 -1
  112. package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.d.ts +8 -0
  113. package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.d.ts.map +1 -0
  114. package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.js +57 -0
  115. package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.js.map +1 -0
  116. package/jql/mongodb/src/lib/compile/multilanguage-sort.util.d.ts +14 -0
  117. package/jql/mongodb/src/lib/compile/multilanguage-sort.util.d.ts.map +1 -0
  118. package/jql/mongodb/src/lib/compile/multilanguage-sort.util.js +135 -0
  119. package/jql/mongodb/src/lib/compile/multilanguage-sort.util.js.map +1 -0
  120. package/jql/mongodb/src/lib/jql-mongodb.engine.d.ts +1 -0
  121. package/jql/mongodb/src/lib/jql-mongodb.engine.d.ts.map +1 -1
  122. package/jql/mongodb/src/lib/jql-mongodb.engine.js +32 -21
  123. package/jql/mongodb/src/lib/jql-mongodb.engine.js.map +1 -1
  124. package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.d.ts.map +1 -1
  125. package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.js +29 -0
  126. package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.js.map +1 -1
  127. package/jql/schema/src/lib/resolve-storage-data-type.util.d.ts +5 -0
  128. package/jql/schema/src/lib/resolve-storage-data-type.util.d.ts.map +1 -1
  129. package/jql/schema/src/lib/resolve-storage-data-type.util.js +3 -2
  130. package/jql/schema/src/lib/resolve-storage-data-type.util.js.map +1 -1
  131. package/jql/seed/src/index.d.ts +10 -5
  132. package/jql/seed/src/index.d.ts.map +1 -1
  133. package/jql/seed/src/index.js +10 -5
  134. package/jql/seed/src/index.js.map +1 -1
  135. package/jql/seed/src/lib/jql-multi-seed-runner.service.d.ts +1 -1
  136. package/jql/seed/src/lib/jql-multi-seed-runner.service.d.ts.map +1 -1
  137. package/jql/seed/src/lib/jql-multi-seed-runner.service.js +26 -16
  138. package/jql/seed/src/lib/jql-multi-seed-runner.service.js.map +1 -1
  139. package/jql/seed/src/lib/jql-release-ledger.service.d.ts +24 -0
  140. package/jql/seed/src/lib/jql-release-ledger.service.d.ts.map +1 -0
  141. package/jql/seed/src/lib/jql-release-ledger.service.js +197 -0
  142. package/jql/seed/src/lib/jql-release-ledger.service.js.map +1 -0
  143. package/jql/seed/src/lib/jql-release-migrate.options.d.ts +9 -0
  144. package/jql/seed/src/lib/jql-release-migrate.options.d.ts.map +1 -0
  145. package/jql/seed/src/lib/jql-release-migrate.options.js +96 -0
  146. package/jql/seed/src/lib/jql-release-migrate.options.js.map +1 -0
  147. package/jql/seed/src/lib/jql-release-migrate.service.d.ts +17 -0
  148. package/jql/seed/src/lib/jql-release-migrate.service.d.ts.map +1 -0
  149. package/jql/seed/src/lib/jql-release-migrate.service.js +110 -0
  150. package/jql/seed/src/lib/jql-release-migrate.service.js.map +1 -0
  151. package/jql/seed/src/lib/jql-release-migrate.tokens.d.ts +5 -0
  152. package/jql/seed/src/lib/jql-release-migrate.tokens.d.ts.map +1 -0
  153. package/jql/seed/src/lib/jql-release-migrate.tokens.js +8 -0
  154. package/jql/seed/src/lib/jql-release-migrate.tokens.js.map +1 -0
  155. package/jql/seed/src/lib/jql-release-migrate.types.d.ts +65 -0
  156. package/jql/seed/src/lib/jql-release-migrate.types.d.ts.map +1 -0
  157. package/jql/seed/src/lib/jql-release-migrate.types.js +15 -0
  158. package/jql/seed/src/lib/jql-release-migrate.types.js.map +1 -0
  159. package/jql/seed/src/lib/jql-release-migrate.util.d.ts +35 -0
  160. package/jql/seed/src/lib/jql-release-migrate.util.d.ts.map +1 -0
  161. package/jql/seed/src/lib/jql-release-migrate.util.js +172 -0
  162. package/jql/seed/src/lib/jql-release-migrate.util.js.map +1 -0
  163. package/jql/seed/src/lib/jql-release-migration.runner.d.ts +17 -0
  164. package/jql/seed/src/lib/jql-release-migration.runner.d.ts.map +1 -0
  165. package/jql/seed/src/lib/jql-release-migration.runner.js +69 -0
  166. package/jql/seed/src/lib/jql-release-migration.runner.js.map +1 -0
  167. package/jql/seed/src/lib/jql-release-seed-reconcile.service.d.ts +19 -0
  168. package/jql/seed/src/lib/jql-release-seed-reconcile.service.d.ts.map +1 -0
  169. package/jql/seed/src/lib/jql-release-seed-reconcile.service.js +175 -0
  170. package/jql/seed/src/lib/jql-release-seed-reconcile.service.js.map +1 -0
  171. package/jql/seed/src/lib/jql-release-version-plan.service.d.ts +32 -0
  172. package/jql/seed/src/lib/jql-release-version-plan.service.d.ts.map +1 -0
  173. package/jql/seed/src/lib/jql-release-version-plan.service.js +286 -0
  174. package/jql/seed/src/lib/jql-release-version-plan.service.js.map +1 -0
  175. package/jql/seed/src/lib/jql-release-version-plan.util.d.ts +57 -0
  176. package/jql/seed/src/lib/jql-release-version-plan.util.d.ts.map +1 -0
  177. package/jql/seed/src/lib/jql-release-version-plan.util.js +146 -0
  178. package/jql/seed/src/lib/jql-release-version-plan.util.js.map +1 -0
  179. package/jql/seed/src/lib/jql-seed-api.types.d.ts +0 -6
  180. package/jql/seed/src/lib/jql-seed-api.types.d.ts.map +1 -1
  181. package/jql/seed/src/lib/jql-seed-api.types.js +0 -8
  182. package/jql/seed/src/lib/jql-seed-api.types.js.map +1 -1
  183. package/jql/seed/src/lib/jql-seed-dependency.util.js +1 -1
  184. package/jql/seed/src/lib/jql-seed-dependency.util.js.map +1 -1
  185. package/jql/seed/src/lib/jql-seed-diff.service.d.ts +9 -3
  186. package/jql/seed/src/lib/jql-seed-diff.service.d.ts.map +1 -1
  187. package/jql/seed/src/lib/jql-seed-diff.service.js +38 -18
  188. package/jql/seed/src/lib/jql-seed-diff.service.js.map +1 -1
  189. package/jql/seed/src/lib/jql-seed-options.resolve.js +2 -2
  190. package/jql/seed/src/lib/jql-seed-options.resolve.js.map +1 -1
  191. package/jql/seed/src/lib/jql-seed-plan.util.d.ts.map +1 -1
  192. package/jql/seed/src/lib/jql-seed-plan.util.js +5 -0
  193. package/jql/seed/src/lib/jql-seed-plan.util.js.map +1 -1
  194. package/jql/seed/src/lib/jql-seed-progress.util.d.ts +27 -0
  195. package/jql/seed/src/lib/jql-seed-progress.util.d.ts.map +1 -0
  196. package/jql/seed/src/lib/jql-seed-progress.util.js +146 -0
  197. package/jql/seed/src/lib/jql-seed-progress.util.js.map +1 -0
  198. package/jql/seed/src/lib/jql-seed-runner.service.d.ts +2 -2
  199. package/jql/seed/src/lib/jql-seed-runner.service.d.ts.map +1 -1
  200. package/jql/seed/src/lib/jql-seed-runner.service.js +2 -2
  201. package/jql/seed/src/lib/jql-seed-runner.service.js.map +1 -1
  202. package/jql/seed/src/lib/jql-seed-selection.util.js +8 -8
  203. package/jql/seed/src/lib/jql-seed-selection.util.js.map +1 -1
  204. package/jql/seed/src/lib/jql-seed.module.d.ts +3 -0
  205. package/jql/seed/src/lib/jql-seed.module.d.ts.map +1 -1
  206. package/jql/seed/src/lib/jql-seed.module.js +21 -3
  207. package/jql/seed/src/lib/jql-seed.module.js.map +1 -1
  208. package/jql/seed/src/lib/jql-seed.tokens.d.ts +0 -2
  209. package/jql/seed/src/lib/jql-seed.tokens.d.ts.map +1 -1
  210. package/jql/seed/src/lib/jql-seed.tokens.js +1 -3
  211. package/jql/seed/src/lib/jql-seed.tokens.js.map +1 -1
  212. package/observability/abstract/src/index.d.ts +1 -6
  213. package/observability/abstract/src/index.d.ts.map +1 -1
  214. package/observability/abstract/src/index.js +1 -6
  215. package/observability/abstract/src/index.js.map +1 -1
  216. package/observability/abstract/src/lib/entity-observability-skip.util.d.ts +4 -1
  217. package/observability/abstract/src/lib/entity-observability-skip.util.d.ts.map +1 -1
  218. package/observability/abstract/src/lib/entity-observability-skip.util.js +4 -1
  219. package/observability/abstract/src/lib/entity-observability-skip.util.js.map +1 -1
  220. package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.d.ts.map +1 -1
  221. package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.js +15 -3
  222. package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.js.map +1 -1
  223. package/observability/abstract/src/lib/observability.config.d.ts +3 -0
  224. package/observability/abstract/src/lib/observability.config.d.ts.map +1 -1
  225. package/observability/abstract/src/lib/observability.config.js.map +1 -1
  226. package/observability/abstract/src/lib/observability.tokens.d.ts +0 -3
  227. package/observability/abstract/src/lib/observability.tokens.d.ts.map +1 -1
  228. package/observability/abstract/src/lib/observability.tokens.js +1 -4
  229. package/observability/abstract/src/lib/observability.tokens.js.map +1 -1
  230. package/observability/abstract/src/lib/structured-log.writer.d.ts +12 -0
  231. package/observability/abstract/src/lib/structured-log.writer.d.ts.map +1 -0
  232. package/observability/abstract/src/lib/structured-log.writer.js +19 -0
  233. package/observability/abstract/src/lib/structured-log.writer.js.map +1 -0
  234. package/observability/providers/src/index.d.ts +0 -1
  235. package/observability/providers/src/index.d.ts.map +1 -1
  236. package/observability/providers/src/index.js +0 -1
  237. package/observability/providers/src/index.js.map +1 -1
  238. package/observability/providers/src/lib/config/observability-config.resolve.d.ts.map +1 -1
  239. package/observability/providers/src/lib/config/observability-config.resolve.js +2 -0
  240. package/observability/providers/src/lib/config/observability-config.resolve.js.map +1 -1
  241. package/observability/providers/src/lib/context/resolve-request-observability-entity.util.d.ts.map +1 -1
  242. package/observability/providers/src/lib/context/resolve-request-observability-entity.util.js +13 -0
  243. package/observability/providers/src/lib/context/resolve-request-observability-entity.util.js.map +1 -1
  244. package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.d.ts +4 -4
  245. package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.d.ts.map +1 -1
  246. package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.js +111 -26
  247. package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.js.map +1 -1
  248. package/observability/providers/src/lib/http/http-exception-log.util.d.ts +2 -3
  249. package/observability/providers/src/lib/http/http-exception-log.util.d.ts.map +1 -1
  250. package/observability/providers/src/lib/http/http-exception-log.util.js +6 -21
  251. package/observability/providers/src/lib/http/http-exception-log.util.js.map +1 -1
  252. package/observability/providers/src/lib/http/http-metrics.interceptor.d.ts +1 -2
  253. package/observability/providers/src/lib/http/http-metrics.interceptor.d.ts.map +1 -1
  254. package/observability/providers/src/lib/http/http-metrics.interceptor.js +10 -9
  255. package/observability/providers/src/lib/http/http-metrics.interceptor.js.map +1 -1
  256. package/observability/providers/src/lib/http/observability-context.interceptor.d.ts +4 -2
  257. package/observability/providers/src/lib/http/observability-context.interceptor.d.ts.map +1 -1
  258. package/observability/providers/src/lib/http/observability-context.interceptor.js +44 -8
  259. package/observability/providers/src/lib/http/observability-context.interceptor.js.map +1 -1
  260. package/observability/providers/src/lib/http/register-express-observability.middleware.d.ts.map +1 -1
  261. package/observability/providers/src/lib/http/register-express-observability.middleware.js +2 -0
  262. package/observability/providers/src/lib/http/register-express-observability.middleware.js.map +1 -1
  263. package/observability/providers/src/lib/observability.bootstrap.d.ts.map +1 -1
  264. package/observability/providers/src/lib/observability.bootstrap.js +12 -6
  265. package/observability/providers/src/lib/observability.bootstrap.js.map +1 -1
  266. package/observability/providers/src/lib/observability.module.d.ts.map +1 -1
  267. package/observability/providers/src/lib/observability.module.js +0 -23
  268. package/observability/providers/src/lib/observability.module.js.map +1 -1
  269. package/observability/providers/src/lib/pino/pino-structured-log.writer.d.ts +5 -0
  270. package/observability/providers/src/lib/pino/pino-structured-log.writer.d.ts.map +1 -0
  271. package/observability/providers/src/lib/pino/pino-structured-log.writer.js +16 -0
  272. package/observability/providers/src/lib/pino/pino-structured-log.writer.js.map +1 -0
  273. package/observability/providers/src/lib/pino/pino.factory.d.ts.map +1 -1
  274. package/observability/providers/src/lib/pino/pino.factory.js +69 -25
  275. package/observability/providers/src/lib/pino/pino.factory.js.map +1 -1
  276. package/package.json +2 -2
  277. package/realtime/src/lib/auth/auth-security-alert-realtime.listener.d.ts.map +1 -1
  278. package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js +19 -0
  279. package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js.map +1 -1
  280. package/realtime/src/lib/topics/realtime-topics.d.ts +1 -0
  281. package/realtime/src/lib/topics/realtime-topics.d.ts.map +1 -1
  282. package/realtime/src/lib/topics/realtime-topics.js +2 -1
  283. package/realtime/src/lib/topics/realtime-topics.js.map +1 -1
  284. package/realtime/src/lib/types/realtime.types.d.ts +13 -1
  285. package/realtime/src/lib/types/realtime.types.d.ts.map +1 -1
  286. package/realtime/src/lib/types/realtime.types.js.map +1 -1
  287. package/report/src/lib/report-definition.registry.d.ts +11 -15
  288. package/report/src/lib/report-definition.registry.d.ts.map +1 -1
  289. package/report/src/lib/report-definition.registry.js +25 -101
  290. package/report/src/lib/report-definition.registry.js.map +1 -1
  291. package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.d.ts.map +1 -1
  292. package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.js +56 -18
  293. package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.js.map +1 -1
  294. package/search/providers/src/lib/quick-search-index.service.d.ts.map +1 -1
  295. package/search/providers/src/lib/quick-search-index.service.js +29 -14
  296. package/search/providers/src/lib/quick-search-index.service.js.map +1 -1
  297. package/search/providers/src/lib/quick-search-provider.module.d.ts.map +1 -1
  298. package/search/providers/src/lib/quick-search-provider.module.js +19 -12
  299. package/search/providers/src/lib/quick-search-provider.module.js.map +1 -1
  300. package/search/providers/src/lib/quick-search-reindex.service.d.ts +5 -1
  301. package/search/providers/src/lib/quick-search-reindex.service.d.ts.map +1 -1
  302. package/search/providers/src/lib/quick-search-reindex.service.js +23 -9
  303. package/search/providers/src/lib/quick-search-reindex.service.js.map +1 -1
  304. package/security/src/index.d.ts +5 -0
  305. package/security/src/index.d.ts.map +1 -1
  306. package/security/src/index.js +5 -0
  307. package/security/src/index.js.map +1 -1
  308. package/security/src/lib/auth/auth-runtime.handlers.d.ts +18 -4
  309. package/security/src/lib/auth/auth-runtime.handlers.d.ts.map +1 -1
  310. package/security/src/lib/auth/auth-runtime.handlers.js +60 -1
  311. package/security/src/lib/auth/auth-runtime.handlers.js.map +1 -1
  312. package/security/src/lib/auth/auth-two-factor-gate.d.ts +38 -0
  313. package/security/src/lib/auth/auth-two-factor-gate.d.ts.map +1 -0
  314. package/security/src/lib/auth/auth-two-factor-gate.js +33 -0
  315. package/security/src/lib/auth/auth-two-factor-gate.js.map +1 -0
  316. package/security/src/lib/auth/auth.events.d.ts +1 -1
  317. package/security/src/lib/auth/auth.events.d.ts.map +1 -1
  318. package/security/src/lib/auth/auth.events.js.map +1 -1
  319. package/security/src/lib/auth/auth.service.d.ts.map +1 -1
  320. package/security/src/lib/auth/auth.service.js +2 -0
  321. package/security/src/lib/auth/auth.service.js.map +1 -1
  322. package/security/src/lib/auth/auth.types.d.ts +6 -0
  323. package/security/src/lib/auth/auth.types.d.ts.map +1 -1
  324. package/security/src/lib/auth/auth.types.js.map +1 -1
  325. package/security/src/lib/auth/device-session.service.d.ts +14 -3
  326. package/security/src/lib/auth/device-session.service.d.ts.map +1 -1
  327. package/security/src/lib/auth/device-session.service.js +129 -20
  328. package/security/src/lib/auth/device-session.service.js.map +1 -1
  329. package/security/src/lib/auth/token.service.d.ts.map +1 -1
  330. package/security/src/lib/auth/token.service.js +6 -2
  331. package/security/src/lib/auth/token.service.js.map +1 -1
  332. package/security/src/lib/entity-storage/entity-access-policy.loader.d.ts.map +1 -1
  333. package/security/src/lib/entity-storage/entity-access-policy.loader.js +3 -0
  334. package/security/src/lib/entity-storage/entity-access-policy.loader.js.map +1 -1
  335. package/security/src/lib/entity-storage/visibility-filter.middleware.d.ts +1 -2
  336. package/security/src/lib/entity-storage/visibility-filter.middleware.d.ts.map +1 -1
  337. package/security/src/lib/entity-storage/visibility-filter.middleware.js +2 -75
  338. package/security/src/lib/entity-storage/visibility-filter.middleware.js.map +1 -1
  339. package/security/src/lib/oauth/oidc-provider-engine.service.js +3 -3
  340. package/security/src/lib/oauth/oidc-provider-engine.service.js.map +1 -1
  341. package/security/src/lib/security.module.d.ts.map +1 -1
  342. package/security/src/lib/security.module.js +1 -0
  343. package/security/src/lib/security.module.js.map +1 -1
  344. package/workflow/src/index.d.ts +2 -0
  345. package/workflow/src/index.d.ts.map +1 -1
  346. package/workflow/src/index.js +8 -0
  347. package/workflow/src/index.js.map +1 -1
  348. package/workflow/src/lib/activities/workflow-request-session.adapter.d.ts +10 -4
  349. package/workflow/src/lib/activities/workflow-request-session.adapter.d.ts.map +1 -1
  350. package/workflow/src/lib/activities/workflow-request-session.adapter.js +11 -4
  351. package/workflow/src/lib/activities/workflow-request-session.adapter.js.map +1 -1
  352. package/workflow/src/lib/jobs/workflow-job-dispatcher.service.d.ts +10 -1
  353. package/workflow/src/lib/jobs/workflow-job-dispatcher.service.d.ts.map +1 -1
  354. package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js +74 -9
  355. package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js.map +1 -1
  356. package/workflow/src/lib/jobs/workflow-maintenance.scheduler.d.ts +3 -3
  357. package/workflow/src/lib/jobs/workflow-maintenance.scheduler.d.ts.map +1 -1
  358. package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js +6 -4
  359. package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js.map +1 -1
  360. package/workflow/src/lib/jobs/workflow-start-idempotency.d.ts +14 -0
  361. package/workflow/src/lib/jobs/workflow-start-idempotency.d.ts.map +1 -0
  362. package/workflow/src/lib/jobs/workflow-start-idempotency.js +33 -0
  363. package/workflow/src/lib/jobs/workflow-start-idempotency.js.map +1 -0
  364. package/workflow/src/lib/middlewares/workflow-trigger-guard.d.ts +6 -0
  365. package/workflow/src/lib/middlewares/workflow-trigger-guard.d.ts.map +1 -1
  366. package/workflow/src/lib/middlewares/workflow-trigger-guard.js +14 -0
  367. package/workflow/src/lib/middlewares/workflow-trigger-guard.js.map +1 -1
  368. package/workflow/src/lib/middlewares/workflow-trigger.middleware.d.ts.map +1 -1
  369. package/workflow/src/lib/middlewares/workflow-trigger.middleware.js +2 -1
  370. package/workflow/src/lib/middlewares/workflow-trigger.middleware.js.map +1 -1
  371. package/workflow/src/lib/runtime/jql-workflow-execution-store.service.d.ts +6 -1
  372. package/workflow/src/lib/runtime/jql-workflow-execution-store.service.d.ts.map +1 -1
  373. package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js +15 -1
  374. package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js.map +1 -1
  375. package/workflow/src/lib/runtime/workflow-engine.service.d.ts +19 -0
  376. package/workflow/src/lib/runtime/workflow-engine.service.d.ts.map +1 -1
  377. package/workflow/src/lib/runtime/workflow-engine.service.js +1070 -795
  378. package/workflow/src/lib/runtime/workflow-engine.service.js.map +1 -1
  379. package/workflow/src/lib/runtime/workflow-execution-store.service.d.ts +12 -2
  380. package/workflow/src/lib/runtime/workflow-execution-store.service.d.ts.map +1 -1
  381. package/workflow/src/lib/runtime/workflow-execution-store.service.js +11 -1
  382. package/workflow/src/lib/runtime/workflow-execution-store.service.js.map +1 -1
  383. package/jql/seed/src/lib/jql-seed-analyze.command.d.ts +0 -9
  384. package/jql/seed/src/lib/jql-seed-analyze.command.d.ts.map +0 -1
  385. package/jql/seed/src/lib/jql-seed-analyze.command.js +0 -40
  386. package/jql/seed/src/lib/jql-seed-analyze.command.js.map +0 -1
  387. package/jql/seed/src/lib/jql-seed-apply.command.d.ts +0 -9
  388. package/jql/seed/src/lib/jql-seed-apply.command.d.ts.map +0 -1
  389. package/jql/seed/src/lib/jql-seed-apply.command.js +0 -53
  390. package/jql/seed/src/lib/jql-seed-apply.command.js.map +0 -1
  391. package/jql/seed/src/lib/jql-seed-command.bootstrap.d.ts +0 -9
  392. package/jql/seed/src/lib/jql-seed-command.bootstrap.d.ts.map +0 -1
  393. package/jql/seed/src/lib/jql-seed-command.bootstrap.js +0 -29
  394. package/jql/seed/src/lib/jql-seed-command.bootstrap.js.map +0 -1
  395. package/jql/seed/src/lib/jql-seed-command.registrar.d.ts +0 -12
  396. package/jql/seed/src/lib/jql-seed-command.registrar.d.ts.map +0 -1
  397. package/jql/seed/src/lib/jql-seed-command.registrar.js +0 -29
  398. package/jql/seed/src/lib/jql-seed-command.registrar.js.map +0 -1
  399. package/jql/seed/src/lib/jql-seed-list-catalog.command.d.ts +0 -9
  400. package/jql/seed/src/lib/jql-seed-list-catalog.command.d.ts.map +0 -1
  401. package/jql/seed/src/lib/jql-seed-list-catalog.command.js +0 -30
  402. package/jql/seed/src/lib/jql-seed-list-catalog.command.js.map +0 -1
  403. package/jql/seed/src/lib/jql-seed-run.command.d.ts +0 -9
  404. package/jql/seed/src/lib/jql-seed-run.command.d.ts.map +0 -1
  405. package/jql/seed/src/lib/jql-seed-run.command.js +0 -43
  406. package/jql/seed/src/lib/jql-seed-run.command.js.map +0 -1
  407. package/observability/abstract/src/lib/logger/axp-logger.interface.d.ts +0 -11
  408. package/observability/abstract/src/lib/logger/axp-logger.interface.d.ts.map +0 -1
  409. package/observability/abstract/src/lib/logger/axp-logger.interface.js +0 -5
  410. package/observability/abstract/src/lib/logger/axp-logger.interface.js.map +0 -1
  411. package/observability/abstract/src/lib/metrics/axp-metrics.interface.d.ts +0 -19
  412. package/observability/abstract/src/lib/metrics/axp-metrics.interface.d.ts.map +0 -1
  413. package/observability/abstract/src/lib/metrics/axp-metrics.interface.js +0 -5
  414. package/observability/abstract/src/lib/metrics/axp-metrics.interface.js.map +0 -1
  415. package/observability/abstract/src/lib/noop/noop-logger.adapter.d.ts +0 -11
  416. package/observability/abstract/src/lib/noop/noop-logger.adapter.d.ts.map +0 -1
  417. package/observability/abstract/src/lib/noop/noop-logger.adapter.js +0 -30
  418. package/observability/abstract/src/lib/noop/noop-logger.adapter.js.map +0 -1
  419. package/observability/abstract/src/lib/noop/noop-metrics.adapter.d.ts +0 -16
  420. package/observability/abstract/src/lib/noop/noop-metrics.adapter.d.ts.map +0 -1
  421. package/observability/abstract/src/lib/noop/noop-metrics.adapter.js +0 -33
  422. package/observability/abstract/src/lib/noop/noop-metrics.adapter.js.map +0 -1
  423. package/observability/abstract/src/lib/noop/noop-tracer.adapter.d.ts +0 -7
  424. package/observability/abstract/src/lib/noop/noop-tracer.adapter.d.ts.map +0 -1
  425. package/observability/abstract/src/lib/noop/noop-tracer.adapter.js +0 -32
  426. package/observability/abstract/src/lib/noop/noop-tracer.adapter.js.map +0 -1
  427. package/observability/abstract/src/lib/tracer/axp-tracer.interface.d.ts +0 -17
  428. package/observability/abstract/src/lib/tracer/axp-tracer.interface.d.ts.map +0 -1
  429. package/observability/abstract/src/lib/tracer/axp-tracer.interface.js +0 -5
  430. package/observability/abstract/src/lib/tracer/axp-tracer.interface.js.map +0 -1
  431. package/observability/providers/src/lib/observability.service.d.ts +0 -9
  432. package/observability/providers/src/lib/observability.service.d.ts.map +0 -1
  433. package/observability/providers/src/lib/observability.service.js +0 -26
  434. package/observability/providers/src/lib/observability.service.js.map +0 -1
  435. package/observability/providers/src/lib/otel/otel-metrics.adapter.d.ts +0 -11
  436. package/observability/providers/src/lib/otel/otel-metrics.adapter.d.ts.map +0 -1
  437. package/observability/providers/src/lib/otel/otel-metrics.adapter.js +0 -83
  438. package/observability/providers/src/lib/otel/otel-metrics.adapter.js.map +0 -1
  439. package/observability/providers/src/lib/otel/otel-tracer.adapter.d.ts +0 -8
  440. package/observability/providers/src/lib/otel/otel-tracer.adapter.d.ts.map +0 -1
  441. package/observability/providers/src/lib/otel/otel-tracer.adapter.js +0 -80
  442. package/observability/providers/src/lib/otel/otel-tracer.adapter.js.map +0 -1
  443. package/observability/providers/src/lib/pino/pino-logger.adapter.d.ts +0 -19
  444. package/observability/providers/src/lib/pino/pino-logger.adapter.d.ts.map +0 -1
  445. package/observability/providers/src/lib/pino/pino-logger.adapter.js +0 -45
  446. package/observability/providers/src/lib/pino/pino-logger.adapter.js.map +0 -1
@@ -16,6 +16,7 @@ const workflow_expression_scope_service_1 = require("./workflow-expression-scope
16
16
  const workflow_execution_store_service_1 = require("./workflow-execution-store.service");
17
17
  const entity_workflow_info_sync_1 = require("./entity-workflow-info.sync");
18
18
  const sync_task_current_assignee_util_1 = require("./sync-task-current-assignee.util");
19
+ const workflow_trigger_guard_1 = require("../middlewares/workflow-trigger-guard");
19
20
  const core_1 = require("@acorex-platform/framework-server/core");
20
21
  /** Task context key for composed human-task titles (workflow-management). */
21
22
  const AXP_WORKFLOW_TASK_CONTEXT_TITLE_PARTS_KEY = '_taskTitleParts';
@@ -64,6 +65,21 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
64
65
  }
65
66
  return { id: userId, title: userId, name: userId };
66
67
  }
68
+ /**
69
+ * Tenant/app stamp for engine upserts. `insertOneAsEngine` skips visibility
70
+ * create-stamp, so instances would otherwise be invisible to tenant queries.
71
+ */
72
+ getSessionScopeInfo() {
73
+ const tenantId = this.sessionService?.tenantId?.trim();
74
+ const applicationId = this.sessionService?.applicationId?.trim();
75
+ if (!tenantId && !applicationId) {
76
+ return undefined;
77
+ }
78
+ return {
79
+ ...(tenantId ? { tenantId } : {}),
80
+ ...(applicationId ? { applicationId } : {}),
81
+ };
82
+ }
67
83
  //#endregion
68
84
  //#region ---- Definition Loading ----
69
85
  /**
@@ -316,176 +332,244 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
316
332
  entityRefId: entityRefId || null,
317
333
  entityRefType: entityRefType || null,
318
334
  };
319
- await this.store.addInstance(instance);
320
- console.log(`[BackendWorkflowEngineService] 💾 Stored workflow instance:`, {
321
- id: instance.id,
322
- definitionId: instance.definitionId,
323
- name: instance.name,
324
- correlationId: instance.correlationId,
325
- entityRefId: instance.entityRefId,
326
- entityRefType: instance.entityRefType,
327
- status: instance.status,
328
- subStatus: instance.subStatus,
329
- });
330
- // 🎯 Also store in entity service for entity-based access
331
- await this.saveInstanceToEntityService(instance);
332
- // Convert to instance state for response (async - needs bookmark lookup)
333
- let state = await this.instanceToInstanceState(instance);
334
- // 🎯 KEY LOGIC: Execute all backend activities until we reach a frontend activity
335
- let currentActivity = firstActivity;
336
- let executionCount = 0;
337
- const maxExecutions = 500; // Prevent infinite loops
338
- // Only last activity output is kept; each step overwrites it. Expression scope uses outputs = lastActivityOutput.
339
- let lastActivityOutput = state.lastActivityOutput;
340
- while (currentActivity && executionCount < maxExecutions) {
341
- executionCount++;
342
- const activityType = currentActivity.name;
343
- if (request.workflowId === 'documentReview') {
344
- console.log('[WFT-ENG] documentReview step', executionCount, 'activityId=', currentActivity.id, 'type=', activityType);
345
- }
346
- // Determine execution mode (async)
347
- const executionMode = await this.determineExecutionMode(activityType, currentActivity);
348
- // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
349
- const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
350
- let taskType = currentActivity.metadata?.['taskType'] ??
351
- activityDef?.taskType ??
352
- 'ui-activity';
353
- // Defensive: human-task and cartable must suspend for task board
354
- if (this.isTaskBoardSuspendGraphActivity(activityType)) {
355
- if (activityDef?.taskType === 'human-task') {
356
- taskType = 'human-task';
357
- }
358
- if (executionMode === 'backend') {
359
- console.warn(`[BackendWorkflowEngineService] ⚠️ ${activityType} had executionMode=backend (definition or cache). Forcing frontend so workflow suspends at this step.`, {
360
- activityId: currentActivity.id,
361
- definitionExecutionMode: activityDef?.executionMode,
362
- definitionTaskType: activityDef?.taskType,
363
- });
364
- }
365
- }
366
- const effectiveExecutionMode = taskType === 'human-task' && executionMode === 'backend'
367
- ? 'frontend'
368
- : this.isTaskBoardSuspendGraphActivity(activityType) &&
369
- (activityDef?.taskType === 'human-task' ||
370
- taskType === 'human-task')
371
- ? executionMode === 'backend'
335
+ let stored = false;
336
+ try {
337
+ return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
338
+ await this.store.addInstance(instance);
339
+ stored = true;
340
+ console.log(`[BackendWorkflowEngineService] 💾 Stored workflow instance:`, {
341
+ id: instance.id,
342
+ definitionId: instance.definitionId,
343
+ name: instance.name,
344
+ correlationId: instance.correlationId,
345
+ entityRefId: instance.entityRefId,
346
+ entityRefType: instance.entityRefType,
347
+ status: instance.status,
348
+ subStatus: instance.subStatus,
349
+ });
350
+ // Instance row is inserted once after the step loop, not on every activity.
351
+ // Convert to instance state for response (async - needs bookmark lookup)
352
+ let state = await this.instanceToInstanceState(instance);
353
+ // 🎯 KEY LOGIC: Execute all backend activities until we reach a frontend activity
354
+ let currentActivity = firstActivity;
355
+ let executionCount = 0;
356
+ const maxExecutions = 500; // Prevent infinite loops
357
+ // Only last activity output is kept; each step overwrites it. Expression scope uses outputs = lastActivityOutput.
358
+ let lastActivityOutput = state.lastActivityOutput;
359
+ while (currentActivity && executionCount < maxExecutions) {
360
+ executionCount++;
361
+ const activityType = currentActivity.name;
362
+ if (request.workflowId === 'documentReview') {
363
+ console.log('[WFT-ENG] documentReview step', executionCount, 'activityId=', currentActivity.id, 'type=', activityType);
364
+ }
365
+ // Determine execution mode (async)
366
+ const executionMode = await this.determineExecutionMode(activityType, currentActivity);
367
+ // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
368
+ const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
369
+ let taskType = currentActivity.metadata?.['taskType'] ??
370
+ activityDef?.taskType ??
371
+ 'ui-activity';
372
+ // Defensive: human-task and cartable must suspend for task board
373
+ if (this.isTaskBoardSuspendGraphActivity(activityType)) {
374
+ if (activityDef?.taskType === 'human-task') {
375
+ taskType = 'human-task';
376
+ }
377
+ if (executionMode === 'backend') {
378
+ console.warn(`[BackendWorkflowEngineService] ⚠️ ${activityType} had executionMode=backend (definition or cache). Forcing frontend so workflow suspends at this step.`, {
379
+ activityId: currentActivity.id,
380
+ definitionExecutionMode: activityDef?.executionMode,
381
+ definitionTaskType: activityDef?.taskType,
382
+ });
383
+ }
384
+ }
385
+ const effectiveExecutionMode = taskType === 'human-task' && executionMode === 'backend'
372
386
  ? 'frontend'
373
- : executionMode
374
- : executionMode;
375
- // If frontend activity → stop and return as pendingTask
376
- if (effectiveExecutionMode === 'frontend' ||
377
- effectiveExecutionMode === 'both') {
378
- const pendingTask = await this.createTaskFromActivity(currentActivity, state);
379
- // Use local taskType (may be forced to human-task for workflow-activity:human-task) so bookmark and UI see correct type
380
- const resolvedTaskTypeForBookmark = taskType === 'human-task' || pendingTask.taskType === 'human-task'
381
- ? 'human-task'
382
- : pendingTask.taskType;
383
- // Extract assignment and task metadata from activity inputs (for human-task activities)
384
- let bookmarkPayload = {
385
- activityType,
386
- activityName: currentActivity.name,
387
- taskToken: pendingTask.taskToken, // Store taskToken for resume
388
- taskType: resolvedTaskTypeForBookmark,
389
- };
390
- // For human-task activities, extract assignment and task metadata from inputs
391
- if (resolvedTaskTypeForBookmark === 'human-task' && pendingTask.input) {
392
- const inputs = pendingTask.input;
393
- // Assignment metadata (normalize to array so task board filter works; expression may return single id)
394
- bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
395
- bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
396
- bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
397
- // Task metadata
398
- bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
399
- bookmarkPayload['description'] = inputs['description'] || null;
400
- bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
401
- bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
402
- // UI configuration
403
- bookmarkPayload['layout'] = inputs['layout'] || null;
404
- bookmarkPayload['actions'] = inputs['actions'] || null;
405
- bookmarkPayload['context'] = inputs['context'] || {};
406
- }
407
- if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
408
- bookmarkPayload['assignedUserIds'] = [];
409
- }
410
- if (resolvedTaskTypeForBookmark === 'human-task') {
411
- const htInputs = pendingTask.input;
412
- this.logHumanTaskBookmarkAssignmentTrace({
413
- phase: 'engine-start',
387
+ : this.isTaskBoardSuspendGraphActivity(activityType) &&
388
+ (activityDef?.taskType === 'human-task' ||
389
+ taskType === 'human-task')
390
+ ? executionMode === 'backend'
391
+ ? 'frontend'
392
+ : executionMode
393
+ : executionMode;
394
+ // If frontend activity → stop and return as pendingTask
395
+ if (effectiveExecutionMode === 'frontend' ||
396
+ effectiveExecutionMode === 'both') {
397
+ const pendingTask = await this.createTaskFromActivity(currentActivity, state);
398
+ // Use local taskType (may be forced to human-task for workflow-activity:human-task) so bookmark and UI see correct type
399
+ const resolvedTaskTypeForBookmark = taskType === 'human-task' || pendingTask.taskType === 'human-task'
400
+ ? 'human-task'
401
+ : pendingTask.taskType;
402
+ // Extract assignment and task metadata from activity inputs (for human-task activities)
403
+ let bookmarkPayload = {
404
+ activityType,
405
+ activityName: currentActivity.name,
406
+ taskToken: pendingTask.taskToken, // Store taskToken for resume
407
+ taskType: resolvedTaskTypeForBookmark,
408
+ };
409
+ // For human-task activities, extract assignment and task metadata from inputs
410
+ if (resolvedTaskTypeForBookmark === 'human-task' && pendingTask.input) {
411
+ const inputs = pendingTask.input;
412
+ // Assignment metadata (normalize to array so task board filter works; expression may return single id)
413
+ bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
414
+ bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
415
+ bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
416
+ // Task metadata
417
+ bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
418
+ bookmarkPayload['description'] = inputs['description'] || null;
419
+ bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
420
+ bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
421
+ // UI configuration
422
+ bookmarkPayload['layout'] = inputs['layout'] || null;
423
+ bookmarkPayload['actions'] = inputs['actions'] || null;
424
+ bookmarkPayload['context'] = inputs['context'] || {};
425
+ }
426
+ if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
427
+ bookmarkPayload['assignedUserIds'] = [];
428
+ }
429
+ if (resolvedTaskTypeForBookmark === 'human-task') {
430
+ const htInputs = pendingTask.input;
431
+ this.logHumanTaskBookmarkAssignmentTrace({
432
+ phase: 'engine-start',
433
+ instanceId,
434
+ workflowDefinitionName: workflow.name,
435
+ activityId: currentActivity.id,
436
+ activityType,
437
+ rawAssignedUserIds: htInputs?.['assignedUserIds'],
438
+ normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
439
+ variables: state.variables,
440
+ });
441
+ }
442
+ const bookmarkId = await this.createBookmark(instanceId, currentActivity.id, 'user-task', bookmarkPayload);
443
+ const isTaskBoardSuspend = this.isTaskBoardSuspendGraphActivity(activityType);
444
+ if (isTaskBoardSuspend) {
445
+ console.log('[WFT-ENG] task-board bookmark created instanceId=', instanceId, 'activityId=', currentActivity.id, 'bookmarkId=', bookmarkId, 'type=', activityType);
446
+ }
447
+ const newStatus = isTaskBoardSuspend ? 'suspended' : 'running';
448
+ state = {
449
+ ...state,
450
+ lastActivityOutput,
451
+ status: newStatus,
452
+ currentStepId: currentActivity.id,
453
+ };
454
+ await this.updateInstanceFromState(instanceId, state);
455
+ return {
456
+ instanceId: instanceId,
457
+ state,
458
+ pendingTask,
459
+ lastActivityOutput,
460
+ };
461
+ }
462
+ const parallelWrites = await this.runIndependentEntityWriteBatch({
463
+ workflow,
464
+ connections,
465
+ graph,
414
466
  instanceId,
415
- workflowDefinitionName: workflow.name,
416
- activityId: currentActivity.id,
417
- activityType,
418
- rawAssignedUserIds: htInputs?.['assignedUserIds'],
419
- normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
420
- variables: state.variables,
467
+ activity: currentActivity,
468
+ state,
469
+ lastActivityOutput,
421
470
  });
471
+ if (parallelWrites) {
472
+ state = parallelWrites.state;
473
+ lastActivityOutput = parallelWrites.lastActivityOutput;
474
+ executionCount += parallelWrites.executedCount - 1;
475
+ if (!parallelWrites.nextActivity) {
476
+ return {
477
+ instanceId,
478
+ state,
479
+ pendingTask: null,
480
+ lastActivityOutput,
481
+ };
482
+ }
483
+ currentActivity = parallelWrites.nextActivity;
484
+ continue;
485
+ }
486
+ // Backend activity → execute it
487
+ const lastOutputBeforeBackend = lastActivityOutput;
488
+ const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
489
+ lastActivityOutput = executionResult.output;
490
+ await this.maybeLinkCreatedEntityRecord(instanceId, currentActivity, state, lastOutputBeforeBackend, executionResult.output);
491
+ // set-variable writes workflow variables directly into output map (no outputToVariables).
492
+ const isSetVariableRun = currentActivity.name === 'workflow-activity:set-variable';
493
+ const variableUpdates = isSetVariableRun
494
+ ? executionResult.output
495
+ : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
496
+ const outcome = Object.keys(executionResult.outcomes)[0] || 'Done';
497
+ const sessionUser = this.getSessionUser();
498
+ const backendStepAudit = {
499
+ [`${currentActivity.id}_outcome`]: outcome,
500
+ [`${currentActivity.id}_activityOutput`]: executionResult.output,
501
+ };
502
+ if (sessionUser?.id) {
503
+ backendStepAudit[`${currentActivity.id}_completedByUserId`] =
504
+ sessionUser.id;
505
+ backendStepAudit[`${currentActivity.id}_completedByLabel`] =
506
+ sessionUser.title || sessionUser.name || sessionUser.id;
507
+ }
508
+ else {
509
+ backendStepAudit[`${currentActivity.id}_completedByLabel`] = 'System';
510
+ }
511
+ state = {
512
+ ...state,
513
+ variables: {
514
+ ...state.variables,
515
+ ...variableUpdates,
516
+ ...backendStepAudit,
517
+ },
518
+ lastActivityOutput,
519
+ currentStepId: currentActivity.id,
520
+ lastUpdated: new Date(),
521
+ };
522
+ await this.updateInstanceFromState(instanceId, state);
523
+ // Find next activity based on outcome
524
+ const nextConnection = this.findWorkflowConnection(connections, currentActivity.id, outcome);
525
+ if (!nextConnection) {
526
+ // No next activity → workflow completed
527
+ state = {
528
+ ...state,
529
+ status: 'completed',
530
+ currentStepId: undefined,
531
+ output: state.variables,
532
+ };
533
+ await this.updateInstanceFromState(instanceId, state);
534
+ return {
535
+ instanceId: instanceId,
536
+ state,
537
+ pendingTask: null,
538
+ lastActivityOutput,
539
+ };
540
+ }
541
+ // Find next activity
542
+ const nextActivity = this.findActivityInGraph(graph, nextConnection.target.activtyName);
543
+ if (!nextActivity) {
544
+ console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
545
+ state = {
546
+ ...state,
547
+ status: 'error',
548
+ currentStepId: undefined,
549
+ };
550
+ await this.updateInstanceFromState(instanceId, state);
551
+ throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
552
+ }
553
+ currentActivity = nextActivity;
422
554
  }
423
- const bookmarkId = await this.createBookmark(instanceId, currentActivity.id, 'user-task', bookmarkPayload);
424
- const isTaskBoardSuspend = this.isTaskBoardSuspendGraphActivity(activityType);
425
- if (isTaskBoardSuspend) {
426
- console.log('[WFT-ENG] task-board bookmark created instanceId=', instanceId, 'activityId=', currentActivity.id, 'bookmarkId=', bookmarkId, 'type=', activityType);
555
+ // If we exit loop due to max executions, mark as error
556
+ if (executionCount >= maxExecutions) {
557
+ console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
558
+ state = {
559
+ ...state,
560
+ status: 'error',
561
+ currentStepId: undefined,
562
+ };
563
+ await this.updateInstanceFromState(instanceId, state);
564
+ throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
427
565
  }
428
- const newStatus = isTaskBoardSuspend ? 'suspended' : 'running';
429
- state = {
430
- ...state,
431
- lastActivityOutput,
432
- status: newStatus,
433
- currentStepId: currentActivity.id,
434
- };
435
- await this.updateInstanceFromState(instanceId, state);
436
- return {
437
- instanceId: instanceId,
438
- state,
439
- pendingTask,
440
- lastActivityOutput,
441
- };
442
- }
443
- // Backend activity → execute it
444
- const lastOutputBeforeBackend = lastActivityOutput;
445
- const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
446
- lastActivityOutput = executionResult.output;
447
- await this.maybeLinkCreatedEntityRecord(instanceId, currentActivity, state, lastOutputBeforeBackend, executionResult.output);
448
- // set-variable writes workflow variables directly into output map (no outputToVariables).
449
- const isSetVariableRun = currentActivity.name === 'workflow-activity:set-variable';
450
- const variableUpdates = isSetVariableRun
451
- ? executionResult.output
452
- : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
453
- const outcome = Object.keys(executionResult.outcomes)[0] || 'Done';
454
- const sessionUser = this.getSessionUser();
455
- const backendStepAudit = {
456
- [`${currentActivity.id}_outcome`]: outcome,
457
- [`${currentActivity.id}_activityOutput`]: executionResult.output,
458
- };
459
- if (sessionUser?.id) {
460
- backendStepAudit[`${currentActivity.id}_completedByUserId`] =
461
- sessionUser.id;
462
- backendStepAudit[`${currentActivity.id}_completedByLabel`] =
463
- sessionUser.title || sessionUser.name || sessionUser.id;
464
- }
465
- else {
466
- backendStepAudit[`${currentActivity.id}_completedByLabel`] = 'System';
467
- }
468
- state = {
469
- ...state,
470
- variables: {
471
- ...state.variables,
472
- ...variableUpdates,
473
- ...backendStepAudit,
474
- },
475
- lastActivityOutput,
476
- currentStepId: currentActivity.id,
477
- lastUpdated: new Date(),
478
- };
479
- await this.updateInstanceFromState(instanceId, state);
480
- // Find next activity based on outcome
481
- const nextConnection = this.findWorkflowConnection(connections, currentActivity.id, outcome);
482
- if (!nextConnection) {
483
- // No next activity → workflow completed
566
+ // If we exit loop without frontend activity, workflow is complete
484
567
  state = {
485
568
  ...state,
486
569
  status: 'completed',
487
570
  currentStepId: undefined,
488
571
  output: state.variables,
572
+ lastActivityOutput,
489
573
  };
490
574
  await this.updateInstanceFromState(instanceId, state);
491
575
  return {
@@ -494,47 +578,16 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
494
578
  pendingTask: null,
495
579
  lastActivityOutput,
496
580
  };
497
- }
498
- // Find next activity
499
- const nextActivity = this.findActivityInGraph(graph, nextConnection.target.activtyName);
500
- if (!nextActivity) {
501
- console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
502
- state = {
503
- ...state,
504
- status: 'error',
505
- currentStepId: undefined,
506
- };
507
- await this.updateInstanceFromState(instanceId, state);
508
- throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
509
- }
510
- currentActivity = nextActivity;
581
+ }));
511
582
  }
512
- // If we exit loop due to max executions, mark as error
513
- if (executionCount >= maxExecutions) {
514
- console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
515
- state = {
516
- ...state,
517
- status: 'error',
518
- currentStepId: undefined,
519
- };
520
- await this.updateInstanceFromState(instanceId, state);
521
- throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
583
+ finally {
584
+ if (stored) {
585
+ const current = await this.store.getInstance(instanceId);
586
+ if (current) {
587
+ await this.writeInstanceProjection(current, workflow, true);
588
+ }
589
+ }
522
590
  }
523
- // If we exit loop without frontend activity, workflow is complete
524
- state = {
525
- ...state,
526
- status: 'completed',
527
- currentStepId: undefined,
528
- output: state.variables,
529
- lastActivityOutput,
530
- };
531
- await this.updateInstanceFromState(instanceId, state);
532
- return {
533
- instanceId: instanceId,
534
- state,
535
- pendingTask: null,
536
- lastActivityOutput,
537
- };
538
591
  }
539
592
  /**
540
593
  * Terminates a non-finished workflow instance (e.g. superseded lifecycle run).
@@ -798,286 +851,357 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
798
851
  * 7. Returns either pendingTask (with new secure token) or completed status
799
852
  */
800
853
  async resume(request) {
801
- console.log(`[BackendWorkflowEngineService] 🔄 Resuming workflow: ${request.instanceId}`, {
802
- stepId: request.stepId,
803
- outcome: request.outcome,
804
- });
805
- // Get current state
806
- let state = await this.getState({
807
- instanceId: request.instanceId,
808
- });
809
- if (!state) {
810
- throw new Error(`Workflow instance not found: ${request.instanceId}`);
811
- }
812
- if (state.status !== 'suspended') {
813
- throw new Error(`Cannot resume workflow with status: ${state.status}. Expected: suspended`);
814
- }
815
- // Validate task token (secure validation). If token missing/expired, allow resume when instance is suspended and bookmark for this step exists (task board tasks may wait days).
816
- const tokenValid = request.taskToken &&
817
- this.validateTaskToken(request.taskToken, request.instanceId, request.stepId);
818
- if (!tokenValid) {
819
- const allowedByBookmark = await this.canResumeByBookmark(request.instanceId, request.stepId);
820
- if (!allowedByBookmark) {
821
- throw new Error(request.taskToken
822
- ? 'Invalid or expired task token'
823
- : 'Missing taskToken for resume operation');
824
- }
825
- console.log(`[BackendWorkflowEngineService] ✅ Resume allowed by bookmark (token expired/missing): ${request.instanceId}:${request.stepId}`);
826
- }
827
- // Verify stepId matches current step
828
- if (state.currentStepId !== request.stepId) {
829
- throw new Error(`Step mismatch: expected '${state.currentStepId}', got '${request.stepId}'`);
830
- }
831
- // Enforce claim / assignee for task-board bookmarks
832
- const resumeBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.stepId, true);
833
- const resumePayloadSource = resumeBookmarks[0];
834
- const resumePayload = resumePayloadSource
835
- ? this.parseBookmarkPayloadJson(resumePayloadSource.payloadJson)
836
- : null;
837
- if (resumePayload) {
838
- const payloadActivityType = String(resumePayload['activityType'] ?? '');
839
- const assigned = this.normalizeUserIds(resumePayload['assignedUserIds']) ?? [];
840
- const candidates = this.normalizeUserIds(resumePayload['candidateUserIds']) ?? [];
841
- const isCartablePayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
842
- const isPooledHumanPayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask &&
843
- assigned.length === 0 &&
844
- candidates.length > 0;
845
- if (isCartablePayload || isPooledHumanPayload) {
846
- if (assigned.length === 0) {
847
- throw new workflow_engine_errors_1.AXPWorkflowEngineError('Claim the task before completing it', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.CLAIM_REQUIRED);
854
+ let dirty = false;
855
+ let workflowForFlush = null;
856
+ try {
857
+ return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
858
+ console.log(`[BackendWorkflowEngineService] 🔄 Resuming workflow: ${request.instanceId}`, {
859
+ stepId: request.stepId,
860
+ outcome: request.outcome,
861
+ });
862
+ // Get current state
863
+ let state = await this.getState({
864
+ instanceId: request.instanceId,
865
+ });
866
+ if (!state) {
867
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
848
868
  }
849
- }
850
- if (assigned.length > 0) {
851
- const uid = this.getSessionUser()?.id;
852
- if (uid && !assigned.includes(String(uid))) {
853
- throw new workflow_engine_errors_1.AXPWorkflowEngineError('Only the assigned user can complete this task', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.TASK_NOT_ASSIGNEE, { assignedUserIds: assigned });
869
+ if (state.status !== 'suspended') {
870
+ throw new Error(`Cannot resume workflow with status: ${state.status}. Expected: suspended`);
854
871
  }
855
- }
856
- }
857
- // Mark bookmark as consumed
858
- await this.consumeBookmark(request.instanceId, request.stepId);
859
- // Remove task token after validation (prevent replay); no-op if token was already expired/missing
860
- if (request.taskToken) {
861
- this.removeTaskToken(request.taskToken);
862
- }
863
- // Load workflow definition using definition loader (queries by name field)
864
- const workflow = await this.getDefinition(state.workflowId);
865
- if (!workflow) {
866
- console.error(`[BackendWorkflowRuntime] ❌ Workflow definition not found: ${state.workflowId}`);
867
- throw new Error(`Workflow definition not found: ${state.workflowId}`);
868
- }
869
- const connections = workflow.graph.connections || [];
870
- // Resolve allowed outcomes from workflow definition (e.g. human-task from instance inputs.actions)
871
- const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.stepId);
872
- const outcome = this.normalizeOutcome(request.outcome ?? 'Done', allowedOutcomes, request.stepId);
873
- // Apply frontend output: last activity output for expressions (outputs.*); outcome/output/completer go into variables for audit UI
874
- const userInput = request.userInput || {};
875
- const lastActivityOutput = userInput;
876
- const variableUpdatesResume = this.getVariableUpdatesFromOutput(workflow, request.stepId, userInput);
877
- const sessionUser = this.getSessionUser();
878
- const completerId = sessionUser?.id;
879
- const completerLabel = sessionUser?.title ?? sessionUser?.name ?? completerId;
880
- const resumeVariablePatch = {
881
- ...variableUpdatesResume,
882
- [`${request.stepId}_outcome`]: outcome,
883
- [`${request.stepId}_activityOutput`]: userInput,
884
- };
885
- if (completerId) {
886
- resumeVariablePatch[`${request.stepId}_completedByUserId`] = completerId;
887
- }
888
- if (completerLabel) {
889
- resumeVariablePatch[`${request.stepId}_completedByLabel`] =
890
- completerLabel;
891
- }
892
- else {
893
- resumeVariablePatch[`${request.stepId}_completedByLabel`] = completerId
894
- ? String(completerId)
895
- : 'System';
896
- }
897
- state = {
898
- ...state,
899
- variables: {
900
- ...state.variables,
901
- ...resumeVariablePatch,
902
- },
903
- lastActivityOutput,
904
- lastUpdated: new Date(),
905
- };
906
- // Find next activity based on outcome from frontend (use normalized outcome)
907
- const nextConnection = this.findWorkflowConnection(connections, request.stepId, outcome);
908
- if (!nextConnection) {
909
- // No next activity → workflow completed
910
- console.log('[AXCWorkflowExecutionService] ✅ No next activity found - workflow completed');
911
- state = {
912
- ...state,
913
- status: 'completed',
914
- currentStepId: undefined,
915
- output: state.variables,
916
- lastActivityOutput,
917
- };
918
- console.log('[AXCWorkflowExecutionService] 📝 Updating instance to completed state');
919
- await this.updateInstanceFromState(request.instanceId, state);
920
- return {
921
- output: { outcome, userInput: request.userInput },
922
- outcomes: { [outcome]: true },
923
- nextTask: null,
924
- state,
925
- };
926
- }
927
- // 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
928
- let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
929
- let lastActivityOutputResume = lastActivityOutput;
930
- let executionCount = 0;
931
- const maxExecutions = 500;
932
- if (!currentActivity) {
933
- console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
934
- state = {
935
- ...state,
936
- status: 'error',
937
- currentStepId: undefined,
938
- };
939
- await this.updateInstanceFromState(request.instanceId, state);
940
- throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
941
- }
942
- while (currentActivity && executionCount < maxExecutions) {
943
- executionCount++;
944
- // Map activity name (registry key) to activity type
945
- const activityType = currentActivity.name;
946
- // Determine execution mode (async)
947
- const executionMode = await this.determineExecutionMode(activityType, currentActivity);
948
- // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
949
- const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
950
- let taskType = currentActivity.metadata?.['taskType'] ??
951
- activityDef?.taskType ??
952
- 'ui-activity';
953
- // Defensive: human-task and cartable must suspend (same as run())
954
- if (this.isTaskBoardSuspendGraphActivity(activityType)) {
955
- if (activityDef?.taskType === 'human-task')
956
- taskType = 'human-task';
957
- }
958
- const effectiveExecutionModeResume = taskType === 'human-task' && executionMode === 'backend'
959
- ? 'frontend'
960
- : this.isTaskBoardSuspendGraphActivity(activityType) &&
961
- (activityDef?.taskType === 'human-task' ||
962
- taskType === 'human-task')
963
- ? executionMode === 'backend'
964
- ? 'frontend'
965
- : executionMode
966
- : executionMode;
967
- // If frontend activity → stop and return as pendingTask
968
- if (effectiveExecutionModeResume === 'frontend' ||
969
- effectiveExecutionModeResume === 'both') {
970
- const pendingTask = await this.createTaskFromActivity(currentActivity, state);
971
- // Extract assignment and task metadata from activity inputs (for human-task activities)
972
- const resolvedTaskType = taskType === 'human-task' || pendingTask.taskType === 'human-task'
973
- ? 'human-task'
974
- : pendingTask.taskType;
975
- let bookmarkPayload = {
976
- activityType,
977
- activityName: currentActivity.name,
978
- taskToken: pendingTask.taskToken, // Store taskToken for resume
979
- taskType: resolvedTaskType, // Add taskType to payload
872
+ // Validate task token (secure validation). If token missing/expired, allow resume when instance is suspended and bookmark for this step exists (task board tasks may wait days).
873
+ const tokenValid = request.taskToken &&
874
+ this.validateTaskToken(request.taskToken, request.instanceId, request.stepId);
875
+ if (!tokenValid) {
876
+ const allowedByBookmark = await this.canResumeByBookmark(request.instanceId, request.stepId);
877
+ if (!allowedByBookmark) {
878
+ throw new Error(request.taskToken
879
+ ? 'Invalid or expired task token'
880
+ : 'Missing taskToken for resume operation');
881
+ }
882
+ console.log(`[BackendWorkflowEngineService] ✅ Resume allowed by bookmark (token expired/missing): ${request.instanceId}:${request.stepId}`);
883
+ }
884
+ // Verify stepId matches current step
885
+ if (state.currentStepId !== request.stepId) {
886
+ throw new Error(`Step mismatch: expected '${state.currentStepId}', got '${request.stepId}'`);
887
+ }
888
+ // Enforce claim / assignee for task-board bookmarks
889
+ const resumeBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.stepId, true);
890
+ const resumePayloadSource = resumeBookmarks[0];
891
+ const resumePayload = resumePayloadSource
892
+ ? this.parseBookmarkPayloadJson(resumePayloadSource.payloadJson)
893
+ : null;
894
+ if (resumePayload) {
895
+ const payloadActivityType = String(resumePayload['activityType'] ?? '');
896
+ const assigned = this.normalizeUserIds(resumePayload['assignedUserIds']) ?? [];
897
+ const candidates = this.normalizeUserIds(resumePayload['candidateUserIds']) ?? [];
898
+ const isCartablePayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
899
+ const isPooledHumanPayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask &&
900
+ assigned.length === 0 &&
901
+ candidates.length > 0;
902
+ if (isCartablePayload || isPooledHumanPayload) {
903
+ if (assigned.length === 0) {
904
+ throw new workflow_engine_errors_1.AXPWorkflowEngineError('Claim the task before completing it', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.CLAIM_REQUIRED);
905
+ }
906
+ }
907
+ if (assigned.length > 0) {
908
+ const uid = this.getSessionUser()?.id;
909
+ if (uid && !assigned.includes(String(uid))) {
910
+ throw new workflow_engine_errors_1.AXPWorkflowEngineError('Only the assigned user can complete this task', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.TASK_NOT_ASSIGNEE, { assignedUserIds: assigned });
911
+ }
912
+ }
913
+ }
914
+ // Mark bookmark as consumed
915
+ dirty = true;
916
+ await this.consumeBookmark(request.instanceId, request.stepId);
917
+ // Remove task token after validation (prevent replay); no-op if token was already expired/missing
918
+ if (request.taskToken) {
919
+ this.removeTaskToken(request.taskToken);
920
+ }
921
+ // Load workflow definition using definition loader (queries by name field)
922
+ const workflow = await this.getDefinition(state.workflowId);
923
+ workflowForFlush = workflow;
924
+ if (!workflow) {
925
+ console.error(`[BackendWorkflowRuntime] ❌ Workflow definition not found: ${state.workflowId}`);
926
+ throw new Error(`Workflow definition not found: ${state.workflowId}`);
927
+ }
928
+ const connections = workflow.graph.connections || [];
929
+ // Resolve allowed outcomes from workflow definition (e.g. human-task from instance inputs.actions)
930
+ const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.stepId);
931
+ const outcome = this.normalizeOutcome(request.outcome ?? 'Done', allowedOutcomes, request.stepId);
932
+ // Apply frontend output: last activity output for expressions (outputs.*); outcome/output/completer go into variables for audit UI
933
+ const userInput = request.userInput || {};
934
+ const lastActivityOutput = userInput;
935
+ const variableUpdatesResume = this.getVariableUpdatesFromOutput(workflow, request.stepId, userInput);
936
+ const sessionUser = this.getSessionUser();
937
+ const completerId = sessionUser?.id;
938
+ const completerLabel = sessionUser?.title ?? sessionUser?.name ?? completerId;
939
+ const resumeVariablePatch = {
940
+ ...variableUpdatesResume,
941
+ [`${request.stepId}_outcome`]: outcome,
942
+ [`${request.stepId}_activityOutput`]: userInput,
980
943
  };
981
- // For human-task activities, extract assignment and task metadata from inputs
982
- if (resolvedTaskType === 'human-task' && pendingTask.input) {
983
- const inputs = pendingTask.input;
984
- // Assignment metadata (normalize to array; expression may return single id e.g. {{ variables.managerId }})
985
- bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
986
- bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
987
- bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
988
- // Task metadata
989
- bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
990
- bookmarkPayload['description'] = inputs['description'] || null;
991
- bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
992
- bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
993
- // UI configuration
994
- bookmarkPayload['layout'] = inputs['layout'] || null;
995
- bookmarkPayload['actions'] = inputs['actions'] || null;
996
- bookmarkPayload['context'] = inputs['context'] || {};
944
+ if (completerId) {
945
+ resumeVariablePatch[`${request.stepId}_completedByUserId`] = completerId;
997
946
  }
998
- if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
999
- bookmarkPayload['assignedUserIds'] = [];
947
+ if (completerLabel) {
948
+ resumeVariablePatch[`${request.stepId}_completedByLabel`] =
949
+ completerLabel;
1000
950
  }
1001
- if (resolvedTaskType === 'human-task') {
1002
- const htInputs = pendingTask.input;
1003
- this.logHumanTaskBookmarkAssignmentTrace({
1004
- phase: 'engine-resume',
1005
- instanceId: request.instanceId,
1006
- workflowDefinitionName: workflow.name,
1007
- activityId: currentActivity.id,
1008
- activityType,
1009
- rawAssignedUserIds: htInputs?.['assignedUserIds'],
1010
- normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
1011
- variables: state.variables,
1012
- });
951
+ else {
952
+ resumeVariablePatch[`${request.stepId}_completedByLabel`] = completerId
953
+ ? String(completerId)
954
+ : 'System';
1013
955
  }
1014
- // Create bookmark for frontend activity
1015
- // Store all metadata in bookmark payload (replaces Work Item)
1016
- await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
1017
- // Note: Work Item creation removed - all metadata stored in bookmark payload
1018
- // Set status: task-board activities → suspended, else running (form/popup)
1019
- const isTaskBoardSuspendResume = this.isTaskBoardSuspendGraphActivity(activityType);
1020
956
  state = {
1021
957
  ...state,
1022
- lastActivityOutput: lastActivityOutputResume,
1023
- status: isTaskBoardSuspendResume ? 'suspended' : 'running',
1024
- currentStepId: currentActivity.id,
1025
- };
1026
- await this.updateInstanceFromState(request.instanceId, state);
1027
- console.log(`[BackendWorkflowEngineService] ✅ Workflow suspended at frontend activity: ${activityType} (taskType: ${taskType}, status: ${state.status})`);
1028
- return {
1029
- output: { outcome, userInput: request.userInput },
1030
- outcomes: { [outcome]: true },
1031
- nextTask: pendingTask,
1032
- state,
958
+ variables: {
959
+ ...state.variables,
960
+ ...resumeVariablePatch,
961
+ },
962
+ lastActivityOutput,
963
+ lastUpdated: new Date(),
1033
964
  };
1034
- }
1035
- // Backend activity → execute it
1036
- console.log(`[BackendWorkflowEngineService] ⚙️ Executing backend activity '${currentActivity.name || currentActivity.id}' (${activityType})`);
1037
- const lastOutputBeforeBackendResume = lastActivityOutputResume;
1038
- const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutputResume);
1039
- lastActivityOutputResume = executionResult.output;
1040
- await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendResume, executionResult.output);
1041
- const isSetVariable = currentActivity.name === 'workflow-activity:set-variable';
1042
- const variableUpdatesResumeLoop = isSetVariable
1043
- ? executionResult.output
1044
- : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
1045
- const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
1046
- const sessionUserResume = this.getSessionUser();
1047
- const backendAuditResume = {
1048
- [`${currentActivity.id}_outcome`]: backendOutcome,
1049
- [`${currentActivity.id}_activityOutput`]: executionResult.output,
1050
- };
1051
- if (sessionUserResume?.id) {
1052
- backendAuditResume[`${currentActivity.id}_completedByUserId`] =
1053
- sessionUserResume.id;
1054
- backendAuditResume[`${currentActivity.id}_completedByLabel`] =
1055
- sessionUserResume.title ||
1056
- sessionUserResume.name ||
1057
- sessionUserResume.id;
1058
- }
1059
- else {
1060
- backendAuditResume[`${currentActivity.id}_completedByLabel`] = 'System';
1061
- }
1062
- state = {
1063
- ...state,
1064
- variables: {
1065
- ...state.variables,
1066
- ...(typeof variableUpdatesResumeLoop === 'object' &&
1067
- variableUpdatesResumeLoop !== null
1068
- ? variableUpdatesResumeLoop
1069
- : {}),
1070
- ...backendAuditResume,
1071
- },
1072
- lastActivityOutput: lastActivityOutputResume,
1073
- currentStepId: currentActivity.id,
1074
- lastUpdated: new Date(),
1075
- };
1076
- await this.updateInstanceFromState(request.instanceId, state);
1077
- // Find next activity based on outcome (backend execution result)
1078
- const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
1079
- if (!nextConn) {
1080
- // No next activity → workflow completed
965
+ // Find next activity based on outcome from frontend (use normalized outcome)
966
+ const nextConnection = this.findWorkflowConnection(connections, request.stepId, outcome);
967
+ if (!nextConnection) {
968
+ // No next activity → workflow completed
969
+ console.log('[AXCWorkflowExecutionService] ✅ No next activity found - workflow completed');
970
+ state = {
971
+ ...state,
972
+ status: 'completed',
973
+ currentStepId: undefined,
974
+ output: state.variables,
975
+ lastActivityOutput,
976
+ };
977
+ console.log('[AXCWorkflowExecutionService] 📝 Updating instance to completed state');
978
+ await this.updateInstanceFromState(request.instanceId, state);
979
+ return {
980
+ output: { outcome, userInput: request.userInput },
981
+ outcomes: { [outcome]: true },
982
+ nextTask: null,
983
+ state,
984
+ };
985
+ }
986
+ // 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
987
+ let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
988
+ let lastActivityOutputResume = lastActivityOutput;
989
+ let executionCount = 0;
990
+ const maxExecutions = 500;
991
+ if (!currentActivity) {
992
+ console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
993
+ state = {
994
+ ...state,
995
+ status: 'error',
996
+ currentStepId: undefined,
997
+ };
998
+ await this.updateInstanceFromState(request.instanceId, state);
999
+ throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
1000
+ }
1001
+ while (currentActivity && executionCount < maxExecutions) {
1002
+ executionCount++;
1003
+ // Map activity name (registry key) to activity type
1004
+ const activityType = currentActivity.name;
1005
+ // Determine execution mode (async)
1006
+ const executionMode = await this.determineExecutionMode(activityType, currentActivity);
1007
+ // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
1008
+ const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
1009
+ let taskType = currentActivity.metadata?.['taskType'] ??
1010
+ activityDef?.taskType ??
1011
+ 'ui-activity';
1012
+ // Defensive: human-task and cartable must suspend (same as run())
1013
+ if (this.isTaskBoardSuspendGraphActivity(activityType)) {
1014
+ if (activityDef?.taskType === 'human-task')
1015
+ taskType = 'human-task';
1016
+ }
1017
+ const effectiveExecutionModeResume = taskType === 'human-task' && executionMode === 'backend'
1018
+ ? 'frontend'
1019
+ : this.isTaskBoardSuspendGraphActivity(activityType) &&
1020
+ (activityDef?.taskType === 'human-task' ||
1021
+ taskType === 'human-task')
1022
+ ? executionMode === 'backend'
1023
+ ? 'frontend'
1024
+ : executionMode
1025
+ : executionMode;
1026
+ // If frontend activity → stop and return as pendingTask
1027
+ if (effectiveExecutionModeResume === 'frontend' ||
1028
+ effectiveExecutionModeResume === 'both') {
1029
+ const pendingTask = await this.createTaskFromActivity(currentActivity, state);
1030
+ // Extract assignment and task metadata from activity inputs (for human-task activities)
1031
+ const resolvedTaskType = taskType === 'human-task' || pendingTask.taskType === 'human-task'
1032
+ ? 'human-task'
1033
+ : pendingTask.taskType;
1034
+ let bookmarkPayload = {
1035
+ activityType,
1036
+ activityName: currentActivity.name,
1037
+ taskToken: pendingTask.taskToken, // Store taskToken for resume
1038
+ taskType: resolvedTaskType, // Add taskType to payload
1039
+ };
1040
+ // For human-task activities, extract assignment and task metadata from inputs
1041
+ if (resolvedTaskType === 'human-task' && pendingTask.input) {
1042
+ const inputs = pendingTask.input;
1043
+ // Assignment metadata (normalize to array; expression may return single id e.g. {{ variables.managerId }})
1044
+ bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
1045
+ bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
1046
+ bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
1047
+ // Task metadata
1048
+ bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
1049
+ bookmarkPayload['description'] = inputs['description'] || null;
1050
+ bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
1051
+ bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
1052
+ // UI configuration
1053
+ bookmarkPayload['layout'] = inputs['layout'] || null;
1054
+ bookmarkPayload['actions'] = inputs['actions'] || null;
1055
+ bookmarkPayload['context'] = inputs['context'] || {};
1056
+ }
1057
+ if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
1058
+ bookmarkPayload['assignedUserIds'] = [];
1059
+ }
1060
+ if (resolvedTaskType === 'human-task') {
1061
+ const htInputs = pendingTask.input;
1062
+ this.logHumanTaskBookmarkAssignmentTrace({
1063
+ phase: 'engine-resume',
1064
+ instanceId: request.instanceId,
1065
+ workflowDefinitionName: workflow.name,
1066
+ activityId: currentActivity.id,
1067
+ activityType,
1068
+ rawAssignedUserIds: htInputs?.['assignedUserIds'],
1069
+ normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
1070
+ variables: state.variables,
1071
+ });
1072
+ }
1073
+ // Create bookmark for frontend activity
1074
+ // Store all metadata in bookmark payload (replaces Work Item)
1075
+ await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
1076
+ // Note: Work Item creation removed - all metadata stored in bookmark payload
1077
+ // Set status: task-board activities → suspended, else running (form/popup)
1078
+ const isTaskBoardSuspendResume = this.isTaskBoardSuspendGraphActivity(activityType);
1079
+ state = {
1080
+ ...state,
1081
+ lastActivityOutput: lastActivityOutputResume,
1082
+ status: isTaskBoardSuspendResume ? 'suspended' : 'running',
1083
+ currentStepId: currentActivity.id,
1084
+ };
1085
+ await this.updateInstanceFromState(request.instanceId, state);
1086
+ console.log(`[BackendWorkflowEngineService] ✅ Workflow suspended at frontend activity: ${activityType} (taskType: ${taskType}, status: ${state.status})`);
1087
+ return {
1088
+ output: { outcome, userInput: request.userInput },
1089
+ outcomes: { [outcome]: true },
1090
+ nextTask: pendingTask,
1091
+ state,
1092
+ };
1093
+ }
1094
+ const parallelWrites = await this.runIndependentEntityWriteBatch({
1095
+ workflow,
1096
+ connections,
1097
+ graph: workflow.graph,
1098
+ instanceId: request.instanceId,
1099
+ activity: currentActivity,
1100
+ state,
1101
+ lastActivityOutput: lastActivityOutputResume,
1102
+ });
1103
+ if (parallelWrites) {
1104
+ state = parallelWrites.state;
1105
+ lastActivityOutputResume = parallelWrites.lastActivityOutput;
1106
+ executionCount += parallelWrites.executedCount - 1;
1107
+ if (!parallelWrites.nextActivity) {
1108
+ return {
1109
+ output: state.variables,
1110
+ outcomes: { Done: true },
1111
+ nextTask: null,
1112
+ state,
1113
+ };
1114
+ }
1115
+ currentActivity = parallelWrites.nextActivity;
1116
+ continue;
1117
+ }
1118
+ // Backend activity → execute it
1119
+ console.log(`[BackendWorkflowEngineService] ⚙️ Executing backend activity '${currentActivity.name || currentActivity.id}' (${activityType})`);
1120
+ const lastOutputBeforeBackendResume = lastActivityOutputResume;
1121
+ const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutputResume);
1122
+ lastActivityOutputResume = executionResult.output;
1123
+ await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendResume, executionResult.output);
1124
+ const isSetVariable = currentActivity.name === 'workflow-activity:set-variable';
1125
+ const variableUpdatesResumeLoop = isSetVariable
1126
+ ? executionResult.output
1127
+ : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
1128
+ const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
1129
+ const sessionUserResume = this.getSessionUser();
1130
+ const backendAuditResume = {
1131
+ [`${currentActivity.id}_outcome`]: backendOutcome,
1132
+ [`${currentActivity.id}_activityOutput`]: executionResult.output,
1133
+ };
1134
+ if (sessionUserResume?.id) {
1135
+ backendAuditResume[`${currentActivity.id}_completedByUserId`] =
1136
+ sessionUserResume.id;
1137
+ backendAuditResume[`${currentActivity.id}_completedByLabel`] =
1138
+ sessionUserResume.title ||
1139
+ sessionUserResume.name ||
1140
+ sessionUserResume.id;
1141
+ }
1142
+ else {
1143
+ backendAuditResume[`${currentActivity.id}_completedByLabel`] = 'System';
1144
+ }
1145
+ state = {
1146
+ ...state,
1147
+ variables: {
1148
+ ...state.variables,
1149
+ ...(typeof variableUpdatesResumeLoop === 'object' &&
1150
+ variableUpdatesResumeLoop !== null
1151
+ ? variableUpdatesResumeLoop
1152
+ : {}),
1153
+ ...backendAuditResume,
1154
+ },
1155
+ lastActivityOutput: lastActivityOutputResume,
1156
+ currentStepId: currentActivity.id,
1157
+ lastUpdated: new Date(),
1158
+ };
1159
+ await this.updateInstanceFromState(request.instanceId, state);
1160
+ // Find next activity based on outcome (backend execution result)
1161
+ const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
1162
+ if (!nextConn) {
1163
+ // No next activity → workflow completed
1164
+ state = {
1165
+ ...state,
1166
+ status: 'completed',
1167
+ currentStepId: undefined,
1168
+ output: state.variables,
1169
+ lastActivityOutput: lastActivityOutputResume,
1170
+ };
1171
+ await this.updateInstanceFromState(request.instanceId, state);
1172
+ return {
1173
+ output: state.variables,
1174
+ outcomes: { Done: true },
1175
+ nextTask: null,
1176
+ state,
1177
+ };
1178
+ }
1179
+ // Find next activity
1180
+ const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
1181
+ if (!nextActivity) {
1182
+ console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
1183
+ state = {
1184
+ ...state,
1185
+ status: 'error',
1186
+ currentStepId: undefined,
1187
+ };
1188
+ await this.updateInstanceFromState(request.instanceId, state);
1189
+ throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
1190
+ }
1191
+ currentActivity = nextActivity;
1192
+ }
1193
+ // If we exit loop due to max executions, mark as error
1194
+ if (executionCount >= maxExecutions) {
1195
+ console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
1196
+ state = {
1197
+ ...state,
1198
+ status: 'error',
1199
+ currentStepId: undefined,
1200
+ };
1201
+ await this.updateInstanceFromState(request.instanceId, state);
1202
+ throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
1203
+ }
1204
+ // If we exit loop, workflow is complete
1081
1205
  state = {
1082
1206
  ...state,
1083
1207
  status: 'completed',
@@ -1092,47 +1216,16 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
1092
1216
  nextTask: null,
1093
1217
  state,
1094
1218
  };
1095
- }
1096
- // Find next activity
1097
- const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
1098
- if (!nextActivity) {
1099
- console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
1100
- state = {
1101
- ...state,
1102
- status: 'error',
1103
- currentStepId: undefined,
1104
- };
1105
- await this.updateInstanceFromState(request.instanceId, state);
1106
- throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
1107
- }
1108
- currentActivity = nextActivity;
1219
+ }));
1109
1220
  }
1110
- // If we exit loop due to max executions, mark as error
1111
- if (executionCount >= maxExecutions) {
1112
- console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
1113
- state = {
1114
- ...state,
1115
- status: 'error',
1116
- currentStepId: undefined,
1117
- };
1118
- await this.updateInstanceFromState(request.instanceId, state);
1119
- throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
1221
+ finally {
1222
+ if (dirty) {
1223
+ const current = await this.store.getInstance(request.instanceId);
1224
+ if (current) {
1225
+ await this.writeInstanceProjection(current, workflowForFlush, false);
1226
+ }
1227
+ }
1120
1228
  }
1121
- // If we exit loop, workflow is complete
1122
- state = {
1123
- ...state,
1124
- status: 'completed',
1125
- currentStepId: undefined,
1126
- output: state.variables,
1127
- lastActivityOutput: lastActivityOutputResume,
1128
- };
1129
- await this.updateInstanceFromState(request.instanceId, state);
1130
- return {
1131
- output: state.variables,
1132
- outcomes: { Done: true },
1133
- nextTask: null,
1134
- state,
1135
- };
1136
1229
  }
1137
1230
  /**
1138
1231
  * Get current workflow execution state.
@@ -2110,10 +2203,21 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2110
2203
  .map((value) => value.replace(/([a-z])([A-Z])/g, '$1 $2').trim());
2111
2204
  return fallbackTokens.length > 0 ? fallbackTokens.join(' - ') : 'Unknown';
2112
2205
  }
2206
+ /**
2207
+ * One entity write for a finished start/resume/front-complete run.
2208
+ * New runs insert; later steps update. Skips a second store persist.
2209
+ */
2210
+ async writeInstanceProjection(instance, definition, insert) {
2211
+ if (insert) {
2212
+ await this.saveInstanceToEntityService(instance, definition);
2213
+ return;
2214
+ }
2215
+ await this.updateInstanceInEntityService(instance, definition);
2216
+ }
2113
2217
  /**
2114
2218
  * Save workflow instance to entity service for entity-based access.
2115
2219
  */
2116
- async saveInstanceToEntityService(instance) {
2220
+ async saveInstanceToEntityService(instance, predefined) {
2117
2221
  try {
2118
2222
  // Get active bookmarks for this instance to include in entity model
2119
2223
  const activeBookmarks = await this.getActiveBookmarks(instance.id);
@@ -2132,12 +2236,13 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2132
2236
  createdAt: b.createdAt,
2133
2237
  })),
2134
2238
  };
2135
- const definition = await this.getDefinition(instance.definitionId);
2239
+ const definition = predefined ?? (await this.getDefinition(instance.definitionId));
2136
2240
  const overview = this.computeOverviewDisplayFields(instance, definition);
2137
2241
  const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
2138
2242
  const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
2139
2243
  const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
2140
2244
  await this.syncLinkedTaskAssignee(instance, assignUserFields);
2245
+ const scopeInfo = this.getSessionScopeInfo();
2141
2246
  const entityModel = {
2142
2247
  id: instance.id,
2143
2248
  definitionId: instance.definitionId,
@@ -2163,11 +2268,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2163
2268
  totalSteps: overview.totalSteps,
2164
2269
  progressPercentage: overview.progressPercentage,
2165
2270
  workflowVariablesTable,
2271
+ ...(scopeInfo ? { scopeInfo } : {}),
2166
2272
  };
2167
2273
  const entityName = 'WorkflowManagement.WorkflowInstance';
2168
2274
  // Check if instance already exists
2169
2275
  try {
2170
- const existing = await this.entityStorageService.getOne(entityName, entityModel.id);
2276
+ const existing = await this.entityStorageService.getOneAsEngine(entityName, entityModel.id);
2171
2277
  if (existing) {
2172
2278
  // Already exists - don't insert again, will be updated later
2173
2279
  console.log('[AXCWorkflowExecutionService] ℹ️ Instance already exists in entity service, will be updated later', {
@@ -2180,7 +2286,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2180
2286
  // Instance doesn't exist - proceed with insert
2181
2287
  }
2182
2288
  // Insert new instance
2183
- await this.entityStorageService.insertOne(entityName, entityModel);
2289
+ await this.entityStorageService.insertOneAsEngine(entityName, entityModel);
2184
2290
  console.log('[AXCWorkflowExecutionService] ✅ Inserted new instance in entity service', {
2185
2291
  instanceId: instance.id,
2186
2292
  status: instance.status,
@@ -2196,7 +2302,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2196
2302
  * Update workflow instance in entity service.
2197
2303
  * Uses upsert pattern: try update first, if fails then insert.
2198
2304
  */
2199
- async updateInstanceInEntityService(instance) {
2305
+ async updateInstanceInEntityService(instance, predefined) {
2200
2306
  try {
2201
2307
  // Get active bookmarks for this instance to include in entity model
2202
2308
  const activeBookmarks = await this.getActiveBookmarks(instance.id);
@@ -2215,12 +2321,13 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2215
2321
  createdAt: b.createdAt,
2216
2322
  })),
2217
2323
  };
2218
- const definition = await this.getDefinition(instance.definitionId);
2324
+ const definition = predefined ?? (await this.getDefinition(instance.definitionId));
2219
2325
  const overview = this.computeOverviewDisplayFields(instance, definition);
2220
2326
  const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
2221
2327
  const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
2222
2328
  const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
2223
2329
  await this.syncLinkedTaskAssignee(instance, assignUserFields);
2330
+ const scopeInfo = this.getSessionScopeInfo();
2224
2331
  const entityModel = {
2225
2332
  id: instance.id,
2226
2333
  definitionId: instance.definitionId,
@@ -2246,11 +2353,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2246
2353
  totalSteps: overview.totalSteps,
2247
2354
  progressPercentage: overview.progressPercentage,
2248
2355
  workflowVariablesTable,
2356
+ ...(scopeInfo ? { scopeInfo } : {}),
2249
2357
  };
2250
2358
  const entityName = 'WorkflowManagement.WorkflowInstance';
2251
2359
  // Try update first (upsert pattern)
2252
2360
  try {
2253
- await this.entityStorageService.updateOne(entityName, entityModel.id, entityModel);
2361
+ await this.entityStorageService.updateOneAsEngine(entityName, entityModel.id, entityModel);
2254
2362
  console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service', {
2255
2363
  instanceId: instance.id,
2256
2364
  status: instance.status,
@@ -2267,7 +2375,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2267
2375
  }
2268
2376
  // Try insert
2269
2377
  try {
2270
- await this.entityStorageService.insertOne(entityName, entityModel);
2378
+ await this.entityStorageService.insertOneAsEngine(entityName, entityModel);
2271
2379
  console.log('[AXCWorkflowExecutionService] ✅ Inserted instance in entity service', {
2272
2380
  instanceId: instance.id,
2273
2381
  status: instance.status,
@@ -2278,7 +2386,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2278
2386
  // Insert might fail if instance was created between update and insert
2279
2387
  // Try update again as fallback
2280
2388
  try {
2281
- await this.entityStorageService.updateOne(entityName, entityModel.id, entityModel);
2389
+ await this.entityStorageService.updateOneAsEngine(entityName, entityModel.id, entityModel);
2282
2390
  console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service (after insert failed)', {
2283
2391
  instanceId: instance.id,
2284
2392
  status: instance.status,
@@ -2357,6 +2465,128 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2357
2465
  correlationId: correlationId != null ? String(correlationId) : null,
2358
2466
  });
2359
2467
  }
2468
+ /**
2469
+ * Runs a Done-chain of entity creates and updates that do not read each other's new ids.
2470
+ * Returns null when this step must stay on the one-activity path.
2471
+ */
2472
+ async runIndependentEntityWriteBatch(args) {
2473
+ const batch = this.collectIndependentEntityWriteBatch(args.connections, args.graph, args.activity);
2474
+ if (batch.length < 2) {
2475
+ return null;
2476
+ }
2477
+ const results = await Promise.all(batch.map((activity) => this.executeBackendActivityLocally(activity, args.state, args.lastActivityOutput)));
2478
+ let state = args.state;
2479
+ let lastActivityOutput = args.lastActivityOutput;
2480
+ let routeActivity = batch[batch.length - 1];
2481
+ let routeOutcome = 'Done';
2482
+ let executedCount = 0;
2483
+ for (let index = 0; index < batch.length; index++) {
2484
+ const activity = batch[index];
2485
+ const executionResult = results[index];
2486
+ executedCount++;
2487
+ lastActivityOutput = executionResult.output;
2488
+ await this.maybeLinkCreatedEntityRecord(args.instanceId, activity, state, args.lastActivityOutput, executionResult.output);
2489
+ const variableUpdates = activity.name === 'workflow-activity:set-variable'
2490
+ ? executionResult.output
2491
+ : this.getVariableUpdatesFromOutput(args.workflow, activity.id, executionResult.output);
2492
+ const outcome = Object.keys(executionResult.outcomes ?? {})[0] || 'Done';
2493
+ const sessionUser = this.getSessionUser();
2494
+ const audit = {
2495
+ [`${activity.id}_outcome`]: outcome,
2496
+ [`${activity.id}_activityOutput`]: executionResult.output,
2497
+ };
2498
+ if (sessionUser?.id) {
2499
+ audit[`${activity.id}_completedByUserId`] = sessionUser.id;
2500
+ audit[`${activity.id}_completedByLabel`] =
2501
+ sessionUser.title || sessionUser.name || sessionUser.id;
2502
+ }
2503
+ else {
2504
+ audit[`${activity.id}_completedByLabel`] = 'System';
2505
+ }
2506
+ state = {
2507
+ ...state,
2508
+ variables: {
2509
+ ...state.variables,
2510
+ ...(variableUpdates && typeof variableUpdates === 'object' ? variableUpdates : {}),
2511
+ ...audit,
2512
+ },
2513
+ lastActivityOutput,
2514
+ currentStepId: activity.id,
2515
+ lastUpdated: new Date(),
2516
+ };
2517
+ routeActivity = activity;
2518
+ routeOutcome = outcome;
2519
+ if (outcome.toLowerCase() !== 'done') {
2520
+ break;
2521
+ }
2522
+ }
2523
+ await this.updateInstanceFromState(args.instanceId, state);
2524
+ const nextConn = this.findWorkflowConnection(args.connections, routeActivity.id, routeOutcome);
2525
+ if (!nextConn) {
2526
+ state = {
2527
+ ...state,
2528
+ status: 'completed',
2529
+ currentStepId: undefined,
2530
+ output: state.variables,
2531
+ lastActivityOutput,
2532
+ };
2533
+ await this.updateInstanceFromState(args.instanceId, state);
2534
+ return { state, lastActivityOutput, nextActivity: null, executedCount };
2535
+ }
2536
+ const nextActivity = this.findActivityInGraph(args.graph, nextConn.target.activtyName);
2537
+ if (!nextActivity) {
2538
+ state = { ...state, status: 'error', currentStepId: undefined };
2539
+ await this.updateInstanceFromState(args.instanceId, state);
2540
+ throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
2541
+ }
2542
+ return { state, lastActivityOutput, nextActivity, executedCount };
2543
+ }
2544
+ collectIndependentEntityWriteBatch(connections, graph, start) {
2545
+ if (!BackendWorkflowEngineService_1.ENTITY_WRITE_ACTIVITY_NAMES.has(start.name)) {
2546
+ return [start];
2547
+ }
2548
+ const batch = [start];
2549
+ const produced = new Set(this.outputVariableNames(start));
2550
+ let cursor = start;
2551
+ while (batch.length < 8) {
2552
+ const conn = this.findWorkflowConnection(connections, cursor.id, 'Done');
2553
+ if (!conn) {
2554
+ break;
2555
+ }
2556
+ const next = this.findActivityInGraph(graph, conn.target.activtyName);
2557
+ if (!next || !BackendWorkflowEngineService_1.ENTITY_WRITE_ACTIVITY_NAMES.has(next.name)) {
2558
+ break;
2559
+ }
2560
+ if (this.activityReadsVariables(next, produced)) {
2561
+ break;
2562
+ }
2563
+ batch.push(next);
2564
+ for (const name of this.outputVariableNames(next)) {
2565
+ produced.add(name);
2566
+ }
2567
+ cursor = next;
2568
+ }
2569
+ return batch;
2570
+ }
2571
+ outputVariableNames(activity) {
2572
+ const mapping = activity.outputToVariables;
2573
+ if (!mapping || typeof mapping !== 'object') {
2574
+ return [];
2575
+ }
2576
+ return Object.keys(mapping);
2577
+ }
2578
+ activityReadsVariables(activity, names) {
2579
+ if (names.size === 0) {
2580
+ return false;
2581
+ }
2582
+ const blob = JSON.stringify(activity.inputs ?? {});
2583
+ for (const name of names) {
2584
+ if (new RegExp(`variables\\.${name}(?![A-Za-z0-9_])`).test(blob)) {
2585
+ return true;
2586
+ }
2587
+ }
2588
+ return false;
2589
+ }
2360
2590
  /**
2361
2591
  * Execute backend activities locally via {@link WorkflowActivityRegistryService}.
2362
2592
  *
@@ -2696,9 +2926,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2696
2926
  }
2697
2927
  });
2698
2928
  }
2699
- // Save to IndexedDB
2700
- await this.updateInstance(instance);
2701
- // 🎯 Also update in entity service
2929
+ // Save runtime state in memory. Entity row is written once per start/resume
2930
+ // unless persistence is not deferred (cancel and other direct updates).
2931
+ await this.store.updateInstance(instance, { persist: false });
2932
+ if (this.store.isPersistenceDeferred()) {
2933
+ return instance;
2934
+ }
2702
2935
  await this.updateInstanceInEntityService(instance);
2703
2936
  return instance;
2704
2937
  }
@@ -2732,332 +2965,370 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
2732
2965
  * @returns Response with nextTask (if frontend activity) or completion status
2733
2966
  */
2734
2967
  async frontActivtyComplete(request) {
2968
+ let dirty = false;
2969
+ let workflowForFlush = null;
2735
2970
  try {
2736
- // Get current workflow instance state
2737
- let state = await this.getState({ instanceId: request.instanceId });
2738
- if (!state) {
2739
- console.error('[BackendWorkflowEngineService] ❌ Workflow instance not found:', request.instanceId);
2740
- throw new Error(`Workflow instance not found: ${request.instanceId}`);
2741
- }
2742
- // Check if workflow is suspended
2743
- // If suspended, check if the activity being completed is a ui-activity (not human-task)
2744
- // If it's a ui-activity, we can continue execution (workflow manager is handling it)
2745
- // If it's a human-task, use resume() instead
2746
- if (state.status === 'suspended') {
2747
- // Find the activity being completed to check its taskType
2748
- const workflow = await this.getDefinition(state.workflowId);
2749
- if (!workflow) {
2750
- console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
2751
- throw new Error(`Workflow definition not found: ${state.workflowId}`);
2971
+ return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
2972
+ // Get current workflow instance state
2973
+ let state = await this.getState({ instanceId: request.instanceId });
2974
+ if (!state) {
2975
+ console.error('[BackendWorkflowEngineService] ❌ Workflow instance not found:', request.instanceId);
2976
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
2752
2977
  }
2753
- const activity = this.findActivityInGraph(workflow.graph, request.activityNode);
2754
- if (activity) {
2755
- const activityType = activity.name;
2756
- const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
2757
- const taskType = activityDef?.taskType || 'ui-activity';
2758
- // If it's a ui-activity, allow continuation (workflow manager is handling it)
2759
- if (taskType === 'ui-activity') {
2760
- // Change status to running to allow continuation
2978
+ // Check if workflow is suspended
2979
+ // If suspended, check if the activity being completed is a ui-activity (not human-task)
2980
+ // If it's a ui-activity, we can continue execution (workflow manager is handling it)
2981
+ // If it's a human-task, use resume() instead
2982
+ if (state.status === 'suspended') {
2983
+ // Find the activity being completed to check its taskType
2984
+ const workflow = await this.getDefinition(state.workflowId);
2985
+ if (!workflow) {
2986
+ console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
2987
+ throw new Error(`Workflow definition not found: ${state.workflowId}`);
2988
+ }
2989
+ const activity = this.findActivityInGraph(workflow.graph, request.activityNode);
2990
+ if (activity) {
2991
+ const activityType = activity.name;
2992
+ const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
2993
+ const taskType = activityDef?.taskType || 'ui-activity';
2994
+ // If it's a ui-activity, allow continuation (workflow manager is handling it)
2995
+ if (taskType === 'ui-activity') {
2996
+ // Change status to running to allow continuation
2997
+ state = {
2998
+ ...state,
2999
+ status: 'running',
3000
+ };
3001
+ }
3002
+ else {
3003
+ // It's a human-task, must use resume()
3004
+ console.warn('[BackendWorkflowEngineService] ⚠️ Workflow is suspended. Use resume() method instead.');
3005
+ throw new Error('Workflow is suspended. Use resume() method for suspended workflows.');
3006
+ }
3007
+ }
3008
+ else {
3009
+ // Activity not found, but allow continuation (might be edge case)
2761
3010
  state = {
2762
3011
  ...state,
2763
3012
  status: 'running',
2764
3013
  };
2765
3014
  }
2766
- else {
2767
- // It's a human-task, must use resume()
2768
- console.warn('[BackendWorkflowEngineService] ⚠️ Workflow is suspended. Use resume() method instead.');
2769
- throw new Error('Workflow is suspended. Use resume() method for suspended workflows.');
2770
- }
2771
- }
2772
- else {
2773
- // Activity not found, but allow continuation (might be edge case)
2774
- state = {
2775
- ...state,
2776
- status: 'running',
2777
- };
2778
- }
2779
- }
2780
- // Load workflow definition
2781
- const workflow = await this.getDefinition(state.workflowId);
2782
- if (!workflow) {
2783
- console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
2784
- throw new Error(`Workflow definition not found: ${state.workflowId}`);
2785
- }
2786
- const connections = workflow.graph.connections || [];
2787
- // Consume bookmark for the activity being completed so only the next step has an active bookmark
2788
- await this.consumeBookmark(request.instanceId, request.activityNode);
2789
- const output = (request.output || {});
2790
- const completedActivity = this.findActivityInGraph(workflow.graph, request.activityNode);
2791
- // Resolve allowed outcomes from workflow definition and normalize outcome
2792
- const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.activityNode);
2793
- const coercedOutcome = this.coerceCreateEntityFormOutcome(completedActivity, request.outcome ?? 'Done', output);
2794
- const outcome = this.normalizeOutcome(coercedOutcome, allowedOutcomes, request.activityNode);
2795
- // Apply frontend output: only last activity output is kept (overwritten each step)
2796
- let lastActivityOutput = output;
2797
- // Apply outputToVariables for the completed frontend activity (e.g. leaveRequestForm → leaveRequestId: 'id')
2798
- const variableUpdatesFromOutput = this.getVariableUpdatesFromOutput(workflow, request.activityNode, output);
2799
- const formResultPromotions = this.buildPersistedEntityFormVariablePromotions(output, completedActivity?.name, outcome);
2800
- const sessionUserFrontComplete = this.getSessionUser();
2801
- const frontCompleteAudit = {
2802
- [`${request.activityNode}_outcome`]: outcome,
2803
- [`${request.activityNode}_activityOutput`]: output,
2804
- };
2805
- if (sessionUserFrontComplete?.id) {
2806
- frontCompleteAudit[`${request.activityNode}_completedByUserId`] =
2807
- sessionUserFrontComplete.id;
2808
- frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
2809
- sessionUserFrontComplete.title ||
2810
- sessionUserFrontComplete.name ||
2811
- sessionUserFrontComplete.id;
2812
- }
2813
- else {
2814
- frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
2815
- 'System';
2816
- }
2817
- state = {
2818
- ...state,
2819
- variables: {
2820
- ...state.variables,
2821
- ...output, // Merge frontend output into variables
2822
- ...formResultPromotions, // Promote capture-form fields (e.g. effectiveDate) onto variables
2823
- ...variableUpdatesFromOutput, // Map output paths to variable names (e.g. id → leaveRequestId)
2824
- ...frontCompleteAudit,
2825
- },
2826
- lastActivityOutput,
2827
- lastUpdated: new Date(),
2828
- };
2829
- // Find next activity based on outcome from frontend (use normalized outcome)
2830
- const nextConnection = this.findWorkflowConnection(connections, request.activityNode, outcome);
2831
- if (!nextConnection) {
2832
- // No next activity → workflow completed
2833
- state = {
2834
- ...state,
2835
- status: 'completed',
2836
- currentStepId: undefined,
2837
- output: state.variables,
2838
- };
2839
- await this.updateInstanceFromState(request.instanceId, state);
2840
- return {
2841
- output: { outcome, userInput: request.output },
2842
- outcomes: { [outcome]: true },
2843
- nextTask: null,
2844
- state,
2845
- };
2846
- }
2847
- // 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
2848
- let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
2849
- let executionCount = 0;
2850
- const maxExecutions = 500;
2851
- if (!currentActivity) {
2852
- console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
2853
- state = {
2854
- ...state,
2855
- status: 'error',
2856
- currentStepId: undefined,
2857
- };
2858
- await this.updateInstanceFromState(request.instanceId, state);
2859
- throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
2860
- }
2861
- // Only lastActivityOutput is persisted; used as scope.outputs for expressions.
2862
- while (currentActivity && executionCount < maxExecutions) {
2863
- executionCount++;
2864
- // Map activity name (registry key) to activity type
2865
- const activityType = currentActivity.name;
2866
- // Determine execution mode (async)
2867
- const executionMode = await this.determineExecutionMode(activityType, currentActivity);
2868
- // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
2869
- const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
2870
- let taskTypeComplete = currentActivity.metadata?.['taskType'] ??
2871
- activityDef?.taskType ??
2872
- 'ui-activity';
2873
- // Defensive: human-task and cartable must suspend (same as run())
2874
- if (this.isTaskBoardSuspendGraphActivity(activityType)) {
2875
- if (activityDef?.taskType === 'human-task')
2876
- taskTypeComplete = 'human-task';
2877
3015
  }
2878
- const effectiveExecutionModeComplete = taskTypeComplete === 'human-task' && executionMode === 'backend'
2879
- ? 'frontend'
2880
- : this.isTaskBoardSuspendGraphActivity(activityType) &&
2881
- (activityDef?.taskType === 'human-task' ||
2882
- taskTypeComplete === 'human-task')
2883
- ? executionMode === 'backend'
2884
- ? 'frontend'
2885
- : executionMode
2886
- : executionMode;
2887
- // If frontend activity → stop and return as nextTask
2888
- if (effectiveExecutionModeComplete === 'frontend' ||
2889
- effectiveExecutionModeComplete === 'both') {
2890
- const pendingTask = await this.createTaskFromActivity(currentActivity, state);
2891
- const resolvedTaskTypeComplete = taskTypeComplete === 'human-task' ||
2892
- pendingTask.taskType === 'human-task'
2893
- ? 'human-task'
2894
- : pendingTask.taskType;
2895
- // Extract assignment and task metadata from activity inputs (for human-task activities)
2896
- let bookmarkPayload = {
2897
- activityType,
2898
- activityName: currentActivity.name,
2899
- taskToken: pendingTask.taskToken, // Store taskToken for resume
2900
- taskType: resolvedTaskTypeComplete,
2901
- };
2902
- // For human-task activities, extract assignment and task metadata from inputs
2903
- if (resolvedTaskTypeComplete === 'human-task' && pendingTask.input) {
2904
- const inputs = pendingTask.input;
2905
- // Assignment metadata (normalize to array; expression may return single id)
2906
- bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
2907
- bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
2908
- bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
2909
- // Task metadata
2910
- bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
2911
- bookmarkPayload['description'] = inputs['description'] || null;
2912
- bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
2913
- bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
2914
- // UI configuration
2915
- bookmarkPayload['layout'] = inputs['layout'] || null;
2916
- bookmarkPayload['actions'] = inputs['actions'] || null;
2917
- bookmarkPayload['context'] = inputs['context'] || {};
2918
- }
2919
- if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
2920
- bookmarkPayload['assignedUserIds'] = [];
2921
- }
2922
- if (resolvedTaskTypeComplete === 'human-task') {
2923
- const htInputs = pendingTask.input;
2924
- this.logHumanTaskBookmarkAssignmentTrace({
2925
- phase: 'engine-frontActivityComplete',
2926
- instanceId: request.instanceId,
2927
- workflowDefinitionName: workflow.name,
2928
- activityId: currentActivity.id,
2929
- activityType,
2930
- rawAssignedUserIds: htInputs?.['assignedUserIds'],
2931
- normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
2932
- variables: state.variables,
2933
- });
2934
- }
2935
- // Create bookmark for frontend activity
2936
- // Store all metadata in bookmark payload (replaces Work Item)
2937
- const bookmarkId = await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
2938
- // Update state: task-board activities → suspended, else running (form/popup)
2939
- const isHumanTaskComplete = this.isTaskBoardSuspendGraphActivity(activityType);
2940
- state = {
2941
- ...state,
2942
- lastActivityOutput,
2943
- status: isHumanTaskComplete ? 'suspended' : 'running',
2944
- currentStepId: currentActivity.id,
2945
- };
2946
- await this.updateInstanceFromState(request.instanceId, state);
2947
- return {
2948
- output: { outcome, userInput: request.output },
2949
- outcomes: { [outcome]: true },
2950
- nextTask: pendingTask,
2951
- state,
2952
- };
3016
+ // Load workflow definition
3017
+ const workflow = await this.getDefinition(state.workflowId);
3018
+ workflowForFlush = workflow;
3019
+ if (!workflow) {
3020
+ console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
3021
+ throw new Error(`Workflow definition not found: ${state.workflowId}`);
2953
3022
  }
2954
- // Backend activity → execute it
2955
- const lastOutputBeforeBackendComplete = lastActivityOutput;
2956
- const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
2957
- lastActivityOutput = executionResult.output;
2958
- await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendComplete, executionResult.output);
2959
- // set-variable activity writes variables directly in output; it has no outputToVariables
2960
- const isSetVariableComplete = currentActivity.name === 'workflow-activity:set-variable';
2961
- const variableUpdatesComplete = isSetVariableComplete
2962
- ? executionResult.output
2963
- : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
2964
- const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
2965
- const sessionUserComplete = this.getSessionUser();
2966
- const backendAuditComplete = {
2967
- [`${currentActivity.id}_outcome`]: backendOutcome,
2968
- [`${currentActivity.id}_activityOutput`]: executionResult.output,
3023
+ const connections = workflow.graph.connections || [];
3024
+ // Consume bookmark for the activity being completed so only the next step has an active bookmark
3025
+ dirty = true;
3026
+ await this.consumeBookmark(request.instanceId, request.activityNode);
3027
+ const output = (request.output || {});
3028
+ const completedActivity = this.findActivityInGraph(workflow.graph, request.activityNode);
3029
+ // Resolve allowed outcomes from workflow definition and normalize outcome
3030
+ const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.activityNode);
3031
+ const coercedOutcome = this.coerceCreateEntityFormOutcome(completedActivity, request.outcome ?? 'Done', output);
3032
+ const outcome = this.normalizeOutcome(coercedOutcome, allowedOutcomes, request.activityNode);
3033
+ // Apply frontend output: only last activity output is kept (overwritten each step)
3034
+ let lastActivityOutput = output;
3035
+ // Apply outputToVariables for the completed frontend activity (e.g. leaveRequestForm → leaveRequestId: 'id')
3036
+ const variableUpdatesFromOutput = this.getVariableUpdatesFromOutput(workflow, request.activityNode, output);
3037
+ const formResultPromotions = this.buildPersistedEntityFormVariablePromotions(output, completedActivity?.name, outcome);
3038
+ const sessionUserFrontComplete = this.getSessionUser();
3039
+ const frontCompleteAudit = {
3040
+ [`${request.activityNode}_outcome`]: outcome,
3041
+ [`${request.activityNode}_activityOutput`]: output,
2969
3042
  };
2970
- if (sessionUserComplete?.id) {
2971
- backendAuditComplete[`${currentActivity.id}_completedByUserId`] =
2972
- sessionUserComplete.id;
2973
- backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
2974
- sessionUserComplete.title ||
2975
- sessionUserComplete.name ||
2976
- sessionUserComplete.id;
3043
+ if (sessionUserFrontComplete?.id) {
3044
+ frontCompleteAudit[`${request.activityNode}_completedByUserId`] =
3045
+ sessionUserFrontComplete.id;
3046
+ frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
3047
+ sessionUserFrontComplete.title ||
3048
+ sessionUserFrontComplete.name ||
3049
+ sessionUserFrontComplete.id;
2977
3050
  }
2978
3051
  else {
2979
- backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
3052
+ frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
2980
3053
  'System';
2981
3054
  }
2982
3055
  state = {
2983
3056
  ...state,
2984
3057
  variables: {
2985
3058
  ...state.variables,
2986
- ...(typeof variableUpdatesComplete === 'object' &&
2987
- variableUpdatesComplete !== null
2988
- ? variableUpdatesComplete
2989
- : {}),
2990
- ...backendAuditComplete,
3059
+ ...output, // Merge frontend output into variables
3060
+ ...formResultPromotions, // Promote capture-form fields (e.g. effectiveDate) onto variables
3061
+ ...variableUpdatesFromOutput, // Map output paths to variable names (e.g. id → leaveRequestId)
3062
+ ...frontCompleteAudit,
2991
3063
  },
2992
3064
  lastActivityOutput,
2993
- currentStepId: currentActivity.id,
2994
3065
  lastUpdated: new Date(),
2995
3066
  };
2996
- await this.updateInstanceFromState(request.instanceId, state);
2997
- // Find next activity based on outcome (backend execution result)
2998
- const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
2999
- if (!nextConn) {
3067
+ // Find next activity based on outcome from frontend (use normalized outcome)
3068
+ const nextConnection = this.findWorkflowConnection(connections, request.activityNode, outcome);
3069
+ if (!nextConnection) {
3000
3070
  // No next activity → workflow completed
3001
3071
  state = {
3002
3072
  ...state,
3003
3073
  status: 'completed',
3004
3074
  currentStepId: undefined,
3005
3075
  output: state.variables,
3006
- lastActivityOutput,
3007
3076
  };
3008
3077
  await this.updateInstanceFromState(request.instanceId, state);
3009
3078
  return {
3010
- output: state.variables,
3011
- outcomes: { Done: true },
3079
+ output: { outcome, userInput: request.output },
3080
+ outcomes: { [outcome]: true },
3012
3081
  nextTask: null,
3013
3082
  state,
3014
3083
  };
3015
3084
  }
3016
- // Find next activity
3017
- const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
3018
- if (!nextActivity) {
3019
- console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
3085
+ // 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
3086
+ let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
3087
+ let executionCount = 0;
3088
+ const maxExecutions = 500;
3089
+ if (!currentActivity) {
3090
+ console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
3020
3091
  state = {
3021
3092
  ...state,
3022
3093
  status: 'error',
3023
3094
  currentStepId: undefined,
3024
3095
  };
3025
3096
  await this.updateInstanceFromState(request.instanceId, state);
3026
- throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
3097
+ throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
3027
3098
  }
3028
- currentActivity = nextActivity;
3029
- }
3030
- // If we exit loop due to max executions, mark as error
3031
- if (executionCount >= maxExecutions) {
3032
- console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
3099
+ // Only lastActivityOutput is persisted; used as scope.outputs for expressions.
3100
+ while (currentActivity && executionCount < maxExecutions) {
3101
+ executionCount++;
3102
+ // Map activity name (registry key) to activity type
3103
+ const activityType = currentActivity.name;
3104
+ // Determine execution mode (async)
3105
+ const executionMode = await this.determineExecutionMode(activityType, currentActivity);
3106
+ // Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
3107
+ const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
3108
+ let taskTypeComplete = currentActivity.metadata?.['taskType'] ??
3109
+ activityDef?.taskType ??
3110
+ 'ui-activity';
3111
+ // Defensive: human-task and cartable must suspend (same as run())
3112
+ if (this.isTaskBoardSuspendGraphActivity(activityType)) {
3113
+ if (activityDef?.taskType === 'human-task')
3114
+ taskTypeComplete = 'human-task';
3115
+ }
3116
+ const effectiveExecutionModeComplete = taskTypeComplete === 'human-task' && executionMode === 'backend'
3117
+ ? 'frontend'
3118
+ : this.isTaskBoardSuspendGraphActivity(activityType) &&
3119
+ (activityDef?.taskType === 'human-task' ||
3120
+ taskTypeComplete === 'human-task')
3121
+ ? executionMode === 'backend'
3122
+ ? 'frontend'
3123
+ : executionMode
3124
+ : executionMode;
3125
+ // If frontend activity → stop and return as nextTask
3126
+ if (effectiveExecutionModeComplete === 'frontend' ||
3127
+ effectiveExecutionModeComplete === 'both') {
3128
+ const pendingTask = await this.createTaskFromActivity(currentActivity, state);
3129
+ const resolvedTaskTypeComplete = taskTypeComplete === 'human-task' ||
3130
+ pendingTask.taskType === 'human-task'
3131
+ ? 'human-task'
3132
+ : pendingTask.taskType;
3133
+ // Extract assignment and task metadata from activity inputs (for human-task activities)
3134
+ let bookmarkPayload = {
3135
+ activityType,
3136
+ activityName: currentActivity.name,
3137
+ taskToken: pendingTask.taskToken, // Store taskToken for resume
3138
+ taskType: resolvedTaskTypeComplete,
3139
+ };
3140
+ // For human-task activities, extract assignment and task metadata from inputs
3141
+ if (resolvedTaskTypeComplete === 'human-task' && pendingTask.input) {
3142
+ const inputs = pendingTask.input;
3143
+ // Assignment metadata (normalize to array; expression may return single id)
3144
+ bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
3145
+ bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
3146
+ bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
3147
+ // Task metadata
3148
+ bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
3149
+ bookmarkPayload['description'] = inputs['description'] || null;
3150
+ bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
3151
+ bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
3152
+ // UI configuration
3153
+ bookmarkPayload['layout'] = inputs['layout'] || null;
3154
+ bookmarkPayload['actions'] = inputs['actions'] || null;
3155
+ bookmarkPayload['context'] = inputs['context'] || {};
3156
+ }
3157
+ if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
3158
+ bookmarkPayload['assignedUserIds'] = [];
3159
+ }
3160
+ if (resolvedTaskTypeComplete === 'human-task') {
3161
+ const htInputs = pendingTask.input;
3162
+ this.logHumanTaskBookmarkAssignmentTrace({
3163
+ phase: 'engine-frontActivityComplete',
3164
+ instanceId: request.instanceId,
3165
+ workflowDefinitionName: workflow.name,
3166
+ activityId: currentActivity.id,
3167
+ activityType,
3168
+ rawAssignedUserIds: htInputs?.['assignedUserIds'],
3169
+ normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
3170
+ variables: state.variables,
3171
+ });
3172
+ }
3173
+ // Create bookmark for frontend activity
3174
+ // Store all metadata in bookmark payload (replaces Work Item)
3175
+ const bookmarkId = await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
3176
+ // Update state: task-board activities → suspended, else running (form/popup)
3177
+ const isHumanTaskComplete = this.isTaskBoardSuspendGraphActivity(activityType);
3178
+ state = {
3179
+ ...state,
3180
+ lastActivityOutput,
3181
+ status: isHumanTaskComplete ? 'suspended' : 'running',
3182
+ currentStepId: currentActivity.id,
3183
+ };
3184
+ await this.updateInstanceFromState(request.instanceId, state);
3185
+ return {
3186
+ output: { outcome, userInput: request.output },
3187
+ outcomes: { [outcome]: true },
3188
+ nextTask: pendingTask,
3189
+ state,
3190
+ };
3191
+ }
3192
+ const parallelWrites = await this.runIndependentEntityWriteBatch({
3193
+ workflow,
3194
+ connections,
3195
+ graph: workflow.graph,
3196
+ instanceId: request.instanceId,
3197
+ activity: currentActivity,
3198
+ state,
3199
+ lastActivityOutput,
3200
+ });
3201
+ if (parallelWrites) {
3202
+ state = parallelWrites.state;
3203
+ lastActivityOutput = parallelWrites.lastActivityOutput;
3204
+ executionCount += parallelWrites.executedCount - 1;
3205
+ if (!parallelWrites.nextActivity) {
3206
+ return {
3207
+ output: state.variables,
3208
+ outcomes: { Done: true },
3209
+ nextTask: null,
3210
+ state,
3211
+ };
3212
+ }
3213
+ currentActivity = parallelWrites.nextActivity;
3214
+ continue;
3215
+ }
3216
+ // Backend activity → execute it
3217
+ const lastOutputBeforeBackendComplete = lastActivityOutput;
3218
+ const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
3219
+ lastActivityOutput = executionResult.output;
3220
+ await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendComplete, executionResult.output);
3221
+ // set-variable activity writes variables directly in output; it has no outputToVariables
3222
+ const isSetVariableComplete = currentActivity.name === 'workflow-activity:set-variable';
3223
+ const variableUpdatesComplete = isSetVariableComplete
3224
+ ? executionResult.output
3225
+ : this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
3226
+ const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
3227
+ const sessionUserComplete = this.getSessionUser();
3228
+ const backendAuditComplete = {
3229
+ [`${currentActivity.id}_outcome`]: backendOutcome,
3230
+ [`${currentActivity.id}_activityOutput`]: executionResult.output,
3231
+ };
3232
+ if (sessionUserComplete?.id) {
3233
+ backendAuditComplete[`${currentActivity.id}_completedByUserId`] =
3234
+ sessionUserComplete.id;
3235
+ backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
3236
+ sessionUserComplete.title ||
3237
+ sessionUserComplete.name ||
3238
+ sessionUserComplete.id;
3239
+ }
3240
+ else {
3241
+ backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
3242
+ 'System';
3243
+ }
3244
+ state = {
3245
+ ...state,
3246
+ variables: {
3247
+ ...state.variables,
3248
+ ...(typeof variableUpdatesComplete === 'object' &&
3249
+ variableUpdatesComplete !== null
3250
+ ? variableUpdatesComplete
3251
+ : {}),
3252
+ ...backendAuditComplete,
3253
+ },
3254
+ lastActivityOutput,
3255
+ currentStepId: currentActivity.id,
3256
+ lastUpdated: new Date(),
3257
+ };
3258
+ await this.updateInstanceFromState(request.instanceId, state);
3259
+ // Find next activity based on outcome (backend execution result)
3260
+ const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
3261
+ if (!nextConn) {
3262
+ // No next activity → workflow completed
3263
+ state = {
3264
+ ...state,
3265
+ status: 'completed',
3266
+ currentStepId: undefined,
3267
+ output: state.variables,
3268
+ lastActivityOutput,
3269
+ };
3270
+ await this.updateInstanceFromState(request.instanceId, state);
3271
+ return {
3272
+ output: state.variables,
3273
+ outcomes: { Done: true },
3274
+ nextTask: null,
3275
+ state,
3276
+ };
3277
+ }
3278
+ // Find next activity
3279
+ const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
3280
+ if (!nextActivity) {
3281
+ console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
3282
+ state = {
3283
+ ...state,
3284
+ status: 'error',
3285
+ currentStepId: undefined,
3286
+ };
3287
+ await this.updateInstanceFromState(request.instanceId, state);
3288
+ throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
3289
+ }
3290
+ currentActivity = nextActivity;
3291
+ }
3292
+ // If we exit loop due to max executions, mark as error
3293
+ if (executionCount >= maxExecutions) {
3294
+ console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
3295
+ state = {
3296
+ ...state,
3297
+ status: 'error',
3298
+ currentStepId: undefined,
3299
+ };
3300
+ await this.updateInstanceFromState(request.instanceId, state);
3301
+ throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
3302
+ }
3303
+ // If we exit loop, workflow is complete
3033
3304
  state = {
3034
3305
  ...state,
3035
- status: 'error',
3306
+ status: 'completed',
3036
3307
  currentStepId: undefined,
3308
+ output: state.variables,
3309
+ lastActivityOutput,
3037
3310
  };
3038
3311
  await this.updateInstanceFromState(request.instanceId, state);
3039
- throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
3040
- }
3041
- // If we exit loop, workflow is complete
3042
- state = {
3043
- ...state,
3044
- status: 'completed',
3045
- currentStepId: undefined,
3046
- output: state.variables,
3047
- lastActivityOutput,
3048
- };
3049
- await this.updateInstanceFromState(request.instanceId, state);
3050
- return {
3051
- output: state.variables,
3052
- outcomes: { Done: true },
3053
- nextTask: null,
3054
- state,
3055
- };
3312
+ return {
3313
+ output: state.variables,
3314
+ outcomes: { Done: true },
3315
+ nextTask: null,
3316
+ state,
3317
+ };
3318
+ }));
3056
3319
  }
3057
3320
  catch (error) {
3058
3321
  console.error('[BackendWorkflowEngineService] ❌ Error completing front activity:', error);
3059
3322
  throw error; // Re-throw to let caller handle
3060
3323
  }
3324
+ finally {
3325
+ if (dirty) {
3326
+ const current = await this.store.getInstance(request.instanceId);
3327
+ if (current) {
3328
+ await this.writeInstanceProjection(current, workflowForFlush, false);
3329
+ }
3330
+ }
3331
+ }
3061
3332
  }
3062
3333
  //#endregion
3063
3334
  //#region ---- Stale suspension cleanup (app startup) ----
@@ -3139,6 +3410,10 @@ exports.BackendWorkflowEngineService = BackendWorkflowEngineService;
3139
3410
  * Default outcome used when the requested outcome is not in the allowed list (standard scenario).
3140
3411
  */
3141
3412
  BackendWorkflowEngineService.DEFAULT_UNMATCHED_OUTCOME = 'Done';
3413
+ BackendWorkflowEngineService.ENTITY_WRITE_ACTIVITY_NAMES = new Set([
3414
+ 'workflow-activity:entity-create',
3415
+ 'workflow-activity:entity-update',
3416
+ ]);
3142
3417
  exports.BackendWorkflowEngineService = BackendWorkflowEngineService = BackendWorkflowEngineService_1 = tslib_1.__decorate([
3143
3418
  (0, common_1.Injectable)(),
3144
3419
  tslib_1.__metadata("design:paramtypes", [workflow_execution_store_service_1.WorkflowExecutionStoreService,