@servicenow/sdk-build-plugins 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/dist/acl-plugin.js +3 -4
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.js +0 -2
  4. package/dist/applicability-plugin.js.map +1 -1
  5. package/dist/application-menu-plugin.js +0 -2
  6. package/dist/application-menu-plugin.js.map +1 -1
  7. package/dist/arrow-function-plugin.js +0 -1
  8. package/dist/arrow-function-plugin.js.map +1 -1
  9. package/dist/atf/test-plugin.js +6 -10
  10. package/dist/atf/test-plugin.js.map +1 -1
  11. package/dist/basic-syntax-plugin.js +10 -4
  12. package/dist/basic-syntax-plugin.js.map +1 -1
  13. package/dist/business-rule-plugin.js +0 -1
  14. package/dist/business-rule-plugin.js.map +1 -1
  15. package/dist/call-expression-plugin.js +0 -1
  16. package/dist/call-expression-plugin.js.map +1 -1
  17. package/dist/claims-plugin.js +0 -1
  18. package/dist/claims-plugin.js.map +1 -1
  19. package/dist/client-script-plugin.js +0 -1
  20. package/dist/client-script-plugin.js.map +1 -1
  21. package/dist/column-plugin.js +120 -49
  22. package/dist/column-plugin.js.map +1 -1
  23. package/dist/cross-scope-privilege-plugin.js +0 -1
  24. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  25. package/dist/dashboard/dashboard-plugin.js +0 -2
  26. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  27. package/dist/data-plugin.js +0 -1
  28. package/dist/data-plugin.js.map +1 -1
  29. package/dist/data-policy-plugin.d.ts +2 -0
  30. package/dist/data-policy-plugin.js +276 -0
  31. package/dist/data-policy-plugin.js.map +1 -0
  32. package/dist/email-notification-plugin.js +2 -3
  33. package/dist/email-notification-plugin.js.map +1 -1
  34. package/dist/flow/flow-logic/flow-logic-constants.d.ts +2 -0
  35. package/dist/flow/flow-logic/flow-logic-constants.js +6 -1
  36. package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -1
  37. package/dist/flow/flow-logic/flow-logic-diagnostics.js +192 -56
  38. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  39. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +2 -1
  40. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +44 -5
  41. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -1
  42. package/dist/flow/flow-logic/flow-logic-plugin.js +279 -29
  43. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  44. package/dist/flow/flow-logic/flow-logic-shapes.d.ts +15 -0
  45. package/dist/flow/flow-logic/flow-logic-shapes.js +25 -1
  46. package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -1
  47. package/dist/flow/plugins/approval-rules-plugin.js +0 -1
  48. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -1
  49. package/dist/flow/plugins/flow-action-definition-plugin.js +804 -205
  50. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  51. package/dist/flow/plugins/flow-data-pill-plugin.js +3 -5
  52. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  53. package/dist/flow/plugins/flow-definition-plugin.js +84 -17
  54. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  55. package/dist/flow/plugins/flow-diagnostics-plugin.js +65 -3
  56. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -1
  57. package/dist/flow/plugins/flow-instance-plugin.js +13 -5
  58. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  59. package/dist/flow/plugins/flow-trigger-instance-plugin.js +0 -1
  60. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
  61. package/dist/flow/plugins/inline-script-plugin.js +0 -1
  62. package/dist/flow/plugins/inline-script-plugin.js.map +1 -1
  63. package/dist/flow/plugins/step-definition-plugin.js +0 -2
  64. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  65. package/dist/flow/plugins/step-instance-plugin.js +216 -77
  66. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  67. package/dist/flow/plugins/trigger-plugin.js +0 -2
  68. package/dist/flow/plugins/trigger-plugin.js.map +1 -1
  69. package/dist/flow/plugins/wfa-datapill-plugin.js +0 -1
  70. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  71. package/dist/flow/utils/datapill-transformer.js +9 -5
  72. package/dist/flow/utils/datapill-transformer.js.map +1 -1
  73. package/dist/flow/utils/flow-constants.d.ts +12 -0
  74. package/dist/flow/utils/flow-constants.js +17 -3
  75. package/dist/flow/utils/flow-constants.js.map +1 -1
  76. package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
  77. package/dist/flow/utils/flow-io-to-record.js +21 -13
  78. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  79. package/dist/flow/utils/flow-pill-utils.d.ts +26 -0
  80. package/dist/flow/utils/flow-pill-utils.js +50 -0
  81. package/dist/flow/utils/flow-pill-utils.js.map +1 -0
  82. package/dist/flow/utils/flow-stage-processor.d.ts +138 -0
  83. package/dist/flow/utils/flow-stage-processor.js +665 -0
  84. package/dist/flow/utils/flow-stage-processor.js.map +1 -0
  85. package/dist/flow/utils/pill-string-parser.js +28 -43
  86. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  87. package/dist/flow/utils/utils.d.ts +11 -6
  88. package/dist/flow/utils/utils.js +37 -28
  89. package/dist/flow/utils/utils.js.map +1 -1
  90. package/dist/form-plugin.js +4 -14
  91. package/dist/form-plugin.js.map +1 -1
  92. package/dist/html-import-plugin.js +0 -1
  93. package/dist/html-import-plugin.js.map +1 -1
  94. package/dist/import-sets-plugin.js +0 -2
  95. package/dist/import-sets-plugin.js.map +1 -1
  96. package/dist/inbound-email-action-plugin.js +0 -1
  97. package/dist/inbound-email-action-plugin.js.map +1 -1
  98. package/dist/index.d.ts +2 -1
  99. package/dist/index.js +5 -1
  100. package/dist/index.js.map +1 -1
  101. package/dist/instance-scan-plugin.js +0 -7
  102. package/dist/instance-scan-plugin.js.map +1 -1
  103. package/dist/json-plugin.js +0 -1
  104. package/dist/json-plugin.js.map +1 -1
  105. package/dist/list-plugin.js +4 -1
  106. package/dist/list-plugin.js.map +1 -1
  107. package/dist/now-attach-plugin.js +0 -1
  108. package/dist/now-attach-plugin.js.map +1 -1
  109. package/dist/now-config-plugin.js +1 -1
  110. package/dist/now-config-plugin.js.map +1 -1
  111. package/dist/now-id-plugin.js +0 -1
  112. package/dist/now-id-plugin.js.map +1 -1
  113. package/dist/now-include-plugin.js +0 -1
  114. package/dist/now-include-plugin.js.map +1 -1
  115. package/dist/now-ref-plugin.js +0 -1
  116. package/dist/now-ref-plugin.js.map +1 -1
  117. package/dist/now-unresolved-plugin.js +0 -1
  118. package/dist/now-unresolved-plugin.js.map +1 -1
  119. package/dist/package-json-plugin.js +3 -2
  120. package/dist/package-json-plugin.js.map +1 -1
  121. package/dist/property-plugin.js +0 -2
  122. package/dist/property-plugin.js.map +1 -1
  123. package/dist/record-plugin.d.ts +2 -0
  124. package/dist/record-plugin.js +5 -4
  125. package/dist/record-plugin.js.map +1 -1
  126. package/dist/repack/lint/Rules.d.ts +1 -2
  127. package/dist/rest-api-plugin.js +6 -5
  128. package/dist/rest-api-plugin.js.map +1 -1
  129. package/dist/role-plugin.js +0 -1
  130. package/dist/role-plugin.js.map +1 -1
  131. package/dist/schedule-script/scheduled-script-plugin.js +5 -4
  132. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
  133. package/dist/script-action-plugin.js +0 -2
  134. package/dist/script-action-plugin.js.map +1 -1
  135. package/dist/script-include-plugin.js +4 -4
  136. package/dist/script-include-plugin.js.map +1 -1
  137. package/dist/server-module-plugin/index.js +2 -3
  138. package/dist/server-module-plugin/index.js.map +1 -1
  139. package/dist/service-catalog/catalog-clientscript-plugin.js +2 -4
  140. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  141. package/dist/service-catalog/catalog-item-plugin.js +0 -2
  142. package/dist/service-catalog/catalog-item-plugin.js.map +1 -1
  143. package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -4
  144. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  145. package/dist/service-catalog/sc-record-producer-plugin.js +0 -2
  146. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
  147. package/dist/service-catalog/service-catalog-base.d.ts +2 -2
  148. package/dist/service-catalog/service-catalog-base.js +2 -2
  149. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  150. package/dist/service-catalog/utils.js +1 -1
  151. package/dist/service-catalog/utils.js.map +1 -1
  152. package/dist/service-catalog/variable-set-plugin.js +0 -2
  153. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  154. package/dist/service-portal/angular-provider-plugin.js +0 -2
  155. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  156. package/dist/service-portal/dependency-plugin.js +3 -5
  157. package/dist/service-portal/dependency-plugin.js.map +1 -1
  158. package/dist/service-portal/header-footer-plugin.js +3 -5
  159. package/dist/service-portal/header-footer-plugin.js.map +1 -1
  160. package/dist/service-portal/menu-plugin.js +0 -1
  161. package/dist/service-portal/menu-plugin.js.map +1 -1
  162. package/dist/service-portal/page-plugin.js +0 -1
  163. package/dist/service-portal/page-plugin.js.map +1 -1
  164. package/dist/service-portal/page-route-map-plugin.js +0 -1
  165. package/dist/service-portal/page-route-map-plugin.js.map +1 -1
  166. package/dist/service-portal/portal-plugin.js +0 -2
  167. package/dist/service-portal/portal-plugin.js.map +1 -1
  168. package/dist/service-portal/theme-plugin.js +0 -2
  169. package/dist/service-portal/theme-plugin.js.map +1 -1
  170. package/dist/service-portal/widget-plugin.js +3 -5
  171. package/dist/service-portal/widget-plugin.js.map +1 -1
  172. package/dist/sla-plugin.js +0 -2
  173. package/dist/sla-plugin.js.map +1 -1
  174. package/dist/static-content-plugin.js +32 -3
  175. package/dist/static-content-plugin.js.map +1 -1
  176. package/dist/table-plugin.js +303 -66
  177. package/dist/table-plugin.js.map +1 -1
  178. package/dist/ui-action-plugin.js +26 -17
  179. package/dist/ui-action-plugin.js.map +1 -1
  180. package/dist/ui-page-plugin.js +159 -17
  181. package/dist/ui-page-plugin.js.map +1 -1
  182. package/dist/ui-policy-plugin.js +28 -97
  183. package/dist/ui-policy-plugin.js.map +1 -1
  184. package/dist/user-preference-plugin.js +0 -2
  185. package/dist/user-preference-plugin.js.map +1 -1
  186. package/dist/utils.d.ts +5 -9
  187. package/dist/utils.js +38 -11
  188. package/dist/utils.js.map +1 -1
  189. package/dist/ux-list-menu-config-plugin.js +0 -2
  190. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  191. package/dist/view-plugin.js +0 -1
  192. package/dist/view-plugin.js.map +1 -1
  193. package/dist/workspace-plugin.js +0 -2
  194. package/dist/workspace-plugin.js.map +1 -1
  195. package/package.json +6 -6
  196. package/src/acl-plugin.ts +4 -5
  197. package/src/applicability-plugin.ts +0 -2
  198. package/src/application-menu-plugin.ts +0 -2
  199. package/src/arrow-function-plugin.ts +0 -1
  200. package/src/atf/test-plugin.ts +6 -11
  201. package/src/basic-syntax-plugin.ts +11 -4
  202. package/src/business-rule-plugin.ts +1 -2
  203. package/src/call-expression-plugin.ts +0 -1
  204. package/src/claims-plugin.ts +0 -1
  205. package/src/client-script-plugin.ts +1 -2
  206. package/src/column-plugin.ts +163 -76
  207. package/src/cross-scope-privilege-plugin.ts +1 -2
  208. package/src/dashboard/dashboard-plugin.ts +0 -2
  209. package/src/data-plugin.ts +0 -1
  210. package/src/data-policy-plugin.ts +333 -0
  211. package/src/email-notification-plugin.ts +8 -4
  212. package/src/flow/flow-logic/flow-logic-constants.ts +6 -0
  213. package/src/flow/flow-logic/flow-logic-diagnostics.ts +236 -58
  214. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +59 -6
  215. package/src/flow/flow-logic/flow-logic-plugin.ts +368 -38
  216. package/src/flow/flow-logic/flow-logic-shapes.ts +25 -0
  217. package/src/flow/plugins/approval-rules-plugin.ts +0 -1
  218. package/src/flow/plugins/flow-action-definition-plugin.ts +940 -208
  219. package/src/flow/plugins/flow-data-pill-plugin.ts +3 -5
  220. package/src/flow/plugins/flow-definition-plugin.ts +159 -26
  221. package/src/flow/plugins/flow-diagnostics-plugin.ts +89 -3
  222. package/src/flow/plugins/flow-instance-plugin.ts +26 -12
  223. package/src/flow/plugins/flow-trigger-instance-plugin.ts +0 -1
  224. package/src/flow/plugins/inline-script-plugin.ts +0 -1
  225. package/src/flow/plugins/step-definition-plugin.ts +0 -2
  226. package/src/flow/plugins/step-instance-plugin.ts +259 -65
  227. package/src/flow/plugins/trigger-plugin.ts +0 -2
  228. package/src/flow/plugins/wfa-datapill-plugin.ts +0 -1
  229. package/src/flow/utils/datapill-transformer.ts +13 -5
  230. package/src/flow/utils/flow-constants.ts +19 -1
  231. package/src/flow/utils/flow-io-to-record.ts +29 -19
  232. package/src/flow/utils/flow-pill-utils.ts +48 -0
  233. package/src/flow/utils/flow-stage-processor.ts +831 -0
  234. package/src/flow/utils/pill-string-parser.ts +29 -47
  235. package/src/flow/utils/utils.ts +39 -35
  236. package/src/form-plugin.ts +5 -15
  237. package/src/html-import-plugin.ts +0 -1
  238. package/src/import-sets-plugin.ts +0 -2
  239. package/src/inbound-email-action-plugin.ts +1 -2
  240. package/src/index.ts +7 -1
  241. package/src/instance-scan-plugin.ts +0 -7
  242. package/src/json-plugin.ts +0 -1
  243. package/src/list-plugin.ts +6 -2
  244. package/src/now-attach-plugin.ts +0 -1
  245. package/src/now-config-plugin.ts +1 -1
  246. package/src/now-id-plugin.ts +0 -1
  247. package/src/now-include-plugin.ts +0 -1
  248. package/src/now-ref-plugin.ts +0 -1
  249. package/src/now-unresolved-plugin.ts +0 -1
  250. package/src/package-json-plugin.ts +8 -3
  251. package/src/property-plugin.ts +0 -2
  252. package/src/record-plugin.ts +14 -6
  253. package/src/repack/lint/Rules.ts +1 -1
  254. package/src/rest-api-plugin.ts +7 -6
  255. package/src/role-plugin.ts +1 -2
  256. package/src/schedule-script/scheduled-script-plugin.ts +11 -5
  257. package/src/script-action-plugin.ts +0 -2
  258. package/src/script-include-plugin.ts +8 -4
  259. package/src/server-module-plugin/index.ts +2 -3
  260. package/src/service-catalog/catalog-clientscript-plugin.ts +2 -4
  261. package/src/service-catalog/catalog-item-plugin.ts +0 -2
  262. package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -4
  263. package/src/service-catalog/sc-record-producer-plugin.ts +0 -2
  264. package/src/service-catalog/service-catalog-base.ts +2 -2
  265. package/src/service-catalog/utils.ts +1 -1
  266. package/src/service-catalog/variable-set-plugin.ts +0 -2
  267. package/src/service-portal/angular-provider-plugin.ts +0 -2
  268. package/src/service-portal/dependency-plugin.ts +0 -2
  269. package/src/service-portal/header-footer-plugin.ts +0 -2
  270. package/src/service-portal/menu-plugin.ts +1 -2
  271. package/src/service-portal/page-plugin.ts +1 -2
  272. package/src/service-portal/page-route-map-plugin.ts +1 -2
  273. package/src/service-portal/portal-plugin.ts +0 -2
  274. package/src/service-portal/theme-plugin.ts +0 -2
  275. package/src/service-portal/widget-plugin.ts +0 -2
  276. package/src/sla-plugin.ts +0 -2
  277. package/src/static-content-plugin.ts +37 -4
  278. package/src/table-plugin.ts +371 -92
  279. package/src/ui-action-plugin.ts +30 -17
  280. package/src/ui-page-plugin.ts +188 -20
  281. package/src/ui-policy-plugin.ts +33 -130
  282. package/src/user-preference-plugin.ts +0 -2
  283. package/src/utils.ts +48 -11
  284. package/src/ux-list-menu-config-plugin.ts +0 -2
  285. package/src/view-plugin.ts +0 -1
  286. package/src/workspace-plugin.ts +0 -2
@@ -17,27 +17,34 @@ import {
17
17
  type Shape,
18
18
  } from '@servicenow/sdk-build-core'
19
19
  import { buildVariableRecords, buildVariableShapes, complexObjectMatchesIoRecord } from '../utils/flow-io-to-record'
20
+ import { builtInComplexObjects } from '../utils/built-in-complex-objects'
20
21
  import { generateXML } from '../utils/flow-to-xml'
21
22
  import {
22
23
  slugifyString,
23
24
  BUILT_IN_STEP_DEFINITIONS,
24
25
  BUILT_IN_STEP_SYS_ID_NAME_MAP,
25
- CORE_ACTIONS_SYS_ID_NAME_MAP,
26
26
  ELEMENT_MAPPING_FIELD_ALIASES,
27
+ ASSIGN_ACTION_OUTPUTS_CALLEE,
28
+ ERROR_EVALUATION_CALLEE,
29
+ STEP_DEF_INPUT_TABLE_PREFIX,
30
+ EXT_INPUT_TABLE_PREFIX,
31
+ ACTION_OUTPUT_TABLE_PREFIX,
27
32
  getVarEntryName,
28
33
  getVarEntryType,
29
34
  } from '../utils/flow-constants'
35
+ import { STEP_PILL_TYPE_REGEX, stripPillType, collectPillTypes } from '../utils/flow-pill-utils'
30
36
  import { COLUMN_TYPE_TO_API } from '../../column/column-helper'
31
37
  import { getAttributeValue } from '../utils/schema-to-flow-object'
32
38
  import { normalizeInputValue } from './flow-instance-plugin'
39
+ import { ApprovalRulesShape, ApprovalDueDateShape } from '../utils/flow-shapes'
33
40
  import { ArrowFunctionShape } from '../../arrow-function-plugin'
34
- import { createSdkDocEntry } from '../../utils'
35
41
  import { FDInlineScriptCallShape } from './inline-script-plugin'
36
42
  import { StepInstanceShape, StepInstancePlugin } from './step-instance-plugin'
37
43
  import { NowIdShape } from '../../now-id-plugin'
38
44
  import { NowIncludeShape } from '../../now-include-plugin'
39
45
  import { getBuiltInStepIdentifier, getIdentifierFromRecord } from '../utils/utils'
40
46
  import { convertPillStringToShape, detectPillPattern } from '../utils/pill-string-parser'
47
+ import { PillShape } from '../utils/data-pill-shapes'
41
48
  import { createLableCacheNameToTypeMap } from '../utils/label-cache-parser'
42
49
  import { COLUMN_API_TO_TYPE } from '../../column/column-helper'
43
50
  import { wrapWithDataPillCall, extractDataPillNames } from '../utils/pill-shape-helpers'
@@ -187,27 +194,7 @@ function resolveAnyPillFromShape(
187
194
  return { pill: `{{step[${stepCid}].${pathParts.join('.')}${typeSuffix}}}`, isStep: true }
188
195
  }
189
196
 
190
- /**
191
- * Strips the |type suffix from a pill string.
192
- * e.g., "{{step[CID].record.number|string}}" → "{{step[CID].record.number}}"
193
- * Note: Mirrors stripPillType in step-instance-plugin.ts (kept separate to avoid exporting private functions).
194
- */
195
- function stripPillTypeSuffix(pill: string): string {
196
- return pill.replace(/\|[^}]+/, '')
197
- }
198
-
199
- /** Regex for extracting step pill type annotations — same pattern as STEP_PILL_TYPE_REGEX in step-instance-plugin.ts */
200
- const STEP_PILL_WITH_TYPE_REGEX = /\{\{step\[([^\]]+)\]\.([^|}]+)(?:\|([^}]+))?\}\}/g
201
-
202
- /** Extracts the step pill type annotation from a pill string and stores it in pillTypeMap for label_cache. */
203
- function collectStepPillType(pill: string, pillTypeMap: Map<string, string>): void {
204
- for (const match of pill.matchAll(STEP_PILL_WITH_TYPE_REGEX)) {
205
- const [, cid, pillPath, dataType] = match
206
- if (cid && pillPath && dataType) {
207
- pillTypeMap.set(`${cid}::${pillPath}`, dataType)
208
- }
209
- }
210
- }
197
+ // stripPillType, collectPillTypes, and STEP_PILL_TYPE_REGEX are imported from ../utils/flow-pill-utils
211
198
 
212
199
  /** Creates a sys_element_mapping record — shared by all cases in resolveUnresolvedStepPills. */
213
200
  function createElementMapping(
@@ -221,7 +208,7 @@ function createElementMapping(
221
208
  source: Shape,
222
209
  field: string,
223
210
  id: string,
224
- stepDefinitionSysId: string,
211
+ mappingTable: string,
225
212
  value: string
226
213
  ): Promise<Record> {
227
214
  return factory.createRecord({
@@ -230,47 +217,46 @@ function createElementMapping(
230
217
  properties: {
231
218
  field,
232
219
  id,
233
- table: `var__m_sys_flow_step_definition_input_${stepDefinitionSysId}`,
220
+ table: mappingTable,
234
221
  value,
235
222
  },
236
223
  })
237
224
  }
238
225
 
239
226
  /**
240
- * Resolves a TemplateExpressionShape, replacing step pill spans with resolved pill strings.
241
- * Action pills and plain text spans are preserved from the original template.
242
- * Returns the full resolved string and whether any step pills were resolved.
227
+ * Resolves ALL pills in a TemplateExpressionShape both action pills (PillShape) and
228
+ * step pills (wfa.dataPill CallExpressionShape). Returns the full resolved pill string
229
+ * with type suffixes stripped. Plain text spans are preserved verbatim.
243
230
  */
244
- function resolveStepPillsInTemplate(
231
+ function resolveAllPillsInTemplate(
245
232
  templateShape: TemplateExpressionShape,
246
233
  cidMap: Map<string, string>,
247
234
  pillTypeMap?: Map<string, string>
248
- ): { result: string; hasStepPills: boolean } | undefined {
235
+ ): string {
249
236
  let result = templateShape.getLiteralText()
250
- let hasStepPills = false
251
237
  for (const span of templateShape.getSpans()) {
252
238
  const expr = span.getExpression()
253
- const resolved = resolveAnyPillFromShape(expr as Shape, cidMap)
254
- if (resolved) {
255
- if (resolved.isStep) {
256
- if (pillTypeMap) {
257
- collectStepPillType(resolved.pill, pillTypeMap)
258
- }
259
- hasStepPills = true
239
+ if (expr instanceof PillShape) {
240
+ const raw = expr.getValue()
241
+ if (pillTypeMap) {
242
+ collectPillTypes(raw, pillTypeMap)
260
243
  }
261
- result += stripPillTypeSuffix(resolved.pill)
244
+ result += stripPillType(raw)
262
245
  } else {
263
- // Expression may be an already-resolved PillShape (from SDK auto-processing).
264
- // Strip type suffix so action pills match the expected format.
265
- const val = String(expr.getValue?.() ?? '')
266
- result += val.startsWith('{{') ? stripPillTypeSuffix(val) : val
246
+ const resolved = resolveAnyPillFromShape(expr as Shape, cidMap)
247
+ if (resolved) {
248
+ if (resolved.isStep && pillTypeMap) {
249
+ collectPillTypes(resolved.pill, pillTypeMap)
250
+ }
251
+ result += stripPillType(resolved.pill)
252
+ } else {
253
+ const val = String(expr.getValue?.() ?? '')
254
+ result += val.startsWith('{{') ? stripPillType(val) : val
255
+ }
267
256
  }
268
257
  result += span.getLiteralText()
269
258
  }
270
- if (!hasStepPills) {
271
- return undefined
272
- }
273
- return { result, hasStepPills }
259
+ return result
274
260
  }
275
261
 
276
262
  /**
@@ -311,6 +297,58 @@ async function resolveUnresolvedStepPills(
311
297
 
312
298
  const valuesProperties = inputs.properties({ resolve: false })
313
299
 
300
+ // Handle inputVariables — nested object where each entry's `value` may contain step pills.
301
+ // Ext inputs use a different element_mapping table than regular step definition inputs.
302
+ const inputVariablesShape = valuesProperties['inputVariables']
303
+ if (inputVariablesShape) {
304
+ const inputVarObj = inputVariablesShape.asObject()
305
+ const inputVarProps = inputVarObj.properties({ resolve: false })
306
+ const extInputTableName = `${EXT_INPUT_TABLE_PREFIX}${stepInstanceSysId}`
307
+
308
+ for (const [varName, configShapeRaw] of Object.entries(inputVarProps)) {
309
+ const configObj = configShapeRaw.asObject()
310
+ const varValueShape = configObj.properties({ resolve: false })['value']
311
+ if (!varValueShape) {
312
+ continue
313
+ }
314
+
315
+ const resolved = resolveAnyPillFromShape(varValueShape, cidMap)
316
+ if (resolved?.isStep) {
317
+ collectPillTypes(resolved.pill, pillTypeMap)
318
+ newRecords.push(
319
+ await createElementMapping(
320
+ factory,
321
+ stepShape,
322
+ varName,
323
+ stepInstanceSysId,
324
+ extInputTableName,
325
+ stripPillType(resolved.pill)
326
+ )
327
+ )
328
+ } else if (varValueShape.is(TemplateExpressionShape)) {
329
+ const resolvedText = resolveAllPillsInTemplate(
330
+ varValueShape as TemplateExpressionShape,
331
+ cidMap,
332
+ pillTypeMap
333
+ )
334
+ if (resolvedText.includes('{{step[')) {
335
+ newRecords.push(
336
+ await createElementMapping(
337
+ factory,
338
+ stepShape,
339
+ varName,
340
+ stepInstanceSysId,
341
+ extInputTableName,
342
+ resolvedText
343
+ )
344
+ )
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ const stepDefTableName = `${STEP_DEF_INPUT_TABLE_PREFIX}${stepDefinitionSysId}`
351
+
314
352
  for (const [key, valueShape] of Object.entries(valuesProperties)) {
315
353
  if (key === 'inputVariables' || key === 'outputVariables' || key === 'errorHandlingType') {
316
354
  continue
@@ -319,15 +357,15 @@ async function resolveUnresolvedStepPills(
319
357
  // Case 1: Simple step pill — wfa.dataPill(stepVar.prop, 'type')
320
358
  const resolved = resolveAnyPillFromShape(valueShape, cidMap)
321
359
  if (resolved?.isStep) {
322
- collectStepPillType(resolved.pill, pillTypeMap)
360
+ collectPillTypes(resolved.pill, pillTypeMap)
323
361
  newRecords.push(
324
362
  await createElementMapping(
325
363
  factory,
326
364
  stepShape,
327
365
  key,
328
366
  stepInstanceSysId,
329
- stepDefinitionSysId,
330
- stripPillTypeSuffix(resolved.pill)
367
+ stepDefTableName,
368
+ stripPillType(resolved.pill)
331
369
  )
332
370
  )
333
371
  continue
@@ -335,20 +373,22 @@ async function resolveUnresolvedStepPills(
335
373
 
336
374
  // Case 2: TemplateExpressionShape with step pills
337
375
  if (valueShape.is(TemplateExpressionShape)) {
338
- const templateResolved = resolveStepPillsInTemplate(
376
+ const resolvedText = resolveAllPillsInTemplate(
339
377
  valueShape as TemplateExpressionShape,
340
378
  cidMap,
341
379
  pillTypeMap
342
380
  )
343
- if (templateResolved) {
381
+ // Only emit if step pills were resolved — action-only pills are already
382
+ // handled by SDK auto-processing.
383
+ if (resolvedText.includes('{{step[')) {
344
384
  newRecords.push(
345
385
  await createElementMapping(
346
386
  factory,
347
387
  stepShape,
348
388
  key,
349
389
  stepInstanceSysId,
350
- stepDefinitionSysId,
351
- templateResolved.result
390
+ stepDefTableName,
391
+ resolvedText
352
392
  )
353
393
  )
354
394
  }
@@ -364,25 +404,22 @@ async function resolveUnresolvedStepPills(
364
404
  for (const [field, fieldShape] of templateObj.entries({ resolve: false })) {
365
405
  const fieldResolved = resolveAnyPillFromShape(fieldShape, cidMap)
366
406
  if (fieldResolved?.isStep) {
367
- collectStepPillType(fieldResolved.pill, pillTypeMap)
368
- entries.push(`${field}=${stripPillTypeSuffix(fieldResolved.pill)}`)
407
+ collectPillTypes(fieldResolved.pill, pillTypeMap)
408
+ entries.push(`${field}=${stripPillType(fieldResolved.pill)}`)
369
409
  hasStepPills = true
370
410
  } else if (fieldShape.is(TemplateExpressionShape)) {
371
- const resolved = resolveStepPillsInTemplate(
411
+ const resolvedText = resolveAllPillsInTemplate(
372
412
  fieldShape as TemplateExpressionShape,
373
413
  cidMap,
374
414
  pillTypeMap
375
415
  )
376
- if (resolved) {
377
- entries.push(`${field}=${resolved.result}`)
416
+ entries.push(`${field}=${resolvedText}`)
417
+ if (resolvedText.includes('{{step[')) {
378
418
  hasStepPills = true
379
- } else {
380
- const val = fieldShape.getValue?.()
381
- entries.push(`${field}=${String(val ?? '')}`)
382
419
  }
383
420
  } else {
384
421
  const val = String(fieldShape.getValue?.() ?? '')
385
- entries.push(`${field}=${val.startsWith('{{') ? stripPillTypeSuffix(val) : val}`)
422
+ entries.push(`${field}=${val.startsWith('{{') ? stripPillType(val) : val}`)
386
423
  }
387
424
  }
388
425
 
@@ -393,11 +430,120 @@ async function resolveUnresolvedStepPills(
393
430
  stepShape,
394
431
  key,
395
432
  stepInstanceSysId,
396
- stepDefinitionSysId,
433
+ stepDefTableName,
397
434
  entries.join('^')
398
435
  )
399
436
  )
400
437
  }
438
+ continue
439
+ }
440
+
441
+ // Case 4: ApprovalDueDateShape — resolve step pill in 'date' field
442
+ // During initial auto-processing, cidMap was empty so the step pill in 'date'
443
+ // couldn't be resolved. Now cidMap is populated, so resolve and create element_mapping.
444
+ if (valueShape.is(ApprovalDueDateShape)) {
445
+ const dueDateShape = valueShape as ApprovalDueDateShape
446
+ const dateFieldShape = dueDateShape.get('date', false)
447
+ const dateResolved = resolveAnyPillFromShape(dateFieldShape, cidMap)
448
+ if (dateResolved?.isStep) {
449
+ collectPillTypes(dateResolved.pill, pillTypeMap)
450
+ // Build the full JSON with the resolved step pill in the date field.
451
+ // getApprovalDueDate() resolves all properties (turning the unresolvable
452
+ // CallExpressionShape into a Symbol that JSON.stringify drops).
453
+ // Override the date field with the resolved pill string.
454
+ const dueDate = dueDateShape.getApprovalDueDate()
455
+ const correctedShape = new ApprovalDueDateShape({
456
+ source: dueDateShape.getSource(),
457
+ value: { ...dueDate, date: stripPillType(dateResolved.pill) },
458
+ })
459
+ const jsonValue = correctedShape.toString().getValue()
460
+ newRecords.push(
461
+ await createElementMapping(
462
+ factory,
463
+ stepShape,
464
+ key,
465
+ stepInstanceSysId,
466
+ stepDefTableName,
467
+ jsonValue
468
+ )
469
+ )
470
+ }
471
+ continue
472
+ }
473
+
474
+ // Case 5: ApprovalRulesShape — resolve step pills in users/groups arrays.
475
+ // Uses the same structural walk as resolveApprovalRulesPills in step-instance-plugin
476
+ // but with resolveAnyPillFromShape (which has access to cidMap).
477
+ if (valueShape.is(ApprovalRulesShape)) {
478
+ const rulesShape = valueShape as ApprovalRulesShape
479
+ const rules = rulesShape.getApprovalRules()
480
+ let hasStepPills = false
481
+
482
+ const ruleSetsShape = rulesShape.get('ruleSets', false).ifArray()
483
+ ruleSetsShape?.getElements(false).forEach((ruleSetShape, rsIdx) => {
484
+ const rulesArrayShape = ruleSetShape.ifObject()?.get('rules', false).ifArray()
485
+ rulesArrayShape?.getElements(false).forEach((ruleShape, rIdx) => {
486
+ ruleShape
487
+ .ifArray()
488
+ ?.getElements(false)
489
+ .forEach((conditionShape, cIdx) => {
490
+ const conditionObj = conditionShape.ifObject()
491
+ if (!conditionObj) {
492
+ return
493
+ }
494
+ const condition = rules.ruleSets?.[rsIdx]?.rules?.[rIdx]?.[cIdx]
495
+ if (!condition) {
496
+ return
497
+ }
498
+
499
+ // Resolve step pills in users array
500
+ conditionObj
501
+ .get('users', false)
502
+ .ifArray()
503
+ ?.getElements(false)
504
+ .forEach((userShape, uIdx) => {
505
+ const resolved = resolveAnyPillFromShape(userShape, cidMap)
506
+ if (resolved?.isStep && condition.users) {
507
+ collectPillTypes(resolved.pill, pillTypeMap)
508
+ condition.users[uIdx] = stripPillType(resolved.pill)
509
+ hasStepPills = true
510
+ }
511
+ })
512
+
513
+ // Resolve step pills in groups array
514
+ conditionObj
515
+ .get('groups', false)
516
+ .ifArray()
517
+ ?.getElements(false)
518
+ .forEach((groupShape, gIdx) => {
519
+ const resolved = resolveAnyPillFromShape(groupShape, cidMap)
520
+ if (resolved?.isStep && condition.groups) {
521
+ collectPillTypes(resolved.pill, pillTypeMap)
522
+ condition.groups[gIdx] = stripPillType(resolved.pill)
523
+ hasStepPills = true
524
+ }
525
+ })
526
+ })
527
+ })
528
+ })
529
+
530
+ if (hasStepPills) {
531
+ const correctedShape = new ApprovalRulesShape({
532
+ source: rulesShape.getSource(),
533
+ value: rules,
534
+ })
535
+ const jsonValue = correctedShape.toString().getValue()
536
+ newRecords.push(
537
+ await createElementMapping(
538
+ factory,
539
+ stepShape,
540
+ key,
541
+ stepInstanceSysId,
542
+ stepDefTableName,
543
+ jsonValue
544
+ )
545
+ )
546
+ }
401
547
  }
402
548
  }
403
549
  }
@@ -405,13 +551,34 @@ async function resolveUnresolvedStepPills(
405
551
  }
406
552
 
407
553
  /**
408
- * Extracts step pill info from sys_element_mapping records created during step processing.
409
- * Scans the `value` field of each sys_element_mapping record for step pill patterns
410
- * and deduplicates by (cid, pillPath).
411
- *
412
- * This follows the same post-processing pattern as flow-definition-plugin which extracts
413
- * pills from already-created records rather than during shape transformation.
554
+ * Collects all pill-bearing text strings from records.
555
+ * Scans sys_element_mapping.value, sys_hub_status_condition.condition and .status fields.
414
556
  */
557
+ function collectPillTextsFromRecords(records: Record[]): string[] {
558
+ const texts: string[] = []
559
+ for (const topRec of records) {
560
+ for (const rec of topRec.flat()) {
561
+ const table = rec.getTable()
562
+ if (table === 'sys_element_mapping') {
563
+ const value = rec.get('value')?.asString()?.getValue() ?? ''
564
+ if (value) {
565
+ texts.push(value)
566
+ }
567
+ } else if (table === 'sys_hub_status_condition') {
568
+ const condition = rec.get('condition')?.asString()?.getValue() ?? ''
569
+ if (condition) {
570
+ texts.push(condition)
571
+ }
572
+ const status = rec.get('status')?.asString()?.getValue() ?? ''
573
+ if (status) {
574
+ texts.push(status)
575
+ }
576
+ }
577
+ }
578
+ }
579
+ return texts
580
+ }
581
+
415
582
  function extractStepPillsFromRecords(
416
583
  records: Record[],
417
584
  cidToLabelMap: Map<string, string>,
@@ -420,42 +587,38 @@ function extractStepPillsFromRecords(
420
587
  const pills: StepPillInfo[] = []
421
588
  const seen = new Set<string>()
422
589
 
423
- for (const topRec of records) {
424
- // sys_element_mapping records are nested inside step instance records;
425
- // flat() expands them so we can scan pill values.
426
- for (const rec of topRec.flat()) {
427
- if (rec.getTable() !== 'sys_element_mapping') {
590
+ for (const text of collectPillTextsFromRecords(records)) {
591
+ const matches = text.matchAll(STEP_PILL_TYPE_REGEX)
592
+ for (const match of matches) {
593
+ const [, cid, pillPath, dataType] = match
594
+ if (!cid || !pillPath) {
428
595
  continue
429
596
  }
430
- const value = rec.get('value')?.asString()?.getValue() ?? ''
431
- const matches = value.matchAll(STEP_PILL_WITH_TYPE_REGEX)
432
- for (const match of matches) {
433
- const [, cid, pillPath, dataType] = match
434
- if (!cid || !pillPath) {
435
- continue
436
- }
437
- const key = `${cid}::${pillPath}`
438
- if (seen.has(key)) {
439
- continue
440
- }
441
- seen.add(key)
442
- // Prefer type from pillTypeMap (collected from wfa.dataPill() args before stripping),
443
- // fall back to regex capture from element_mapping, then default to 'string'
444
- const resolvedType = pillTypeMap?.get(key) ?? dataType ?? 'string'
445
- pills.push({
446
- cid,
447
- stepLabel: cidToLabelMap.get(cid) ?? '',
448
- pillPath,
449
- dataType: resolvedType,
450
- })
597
+ // Skip base __step_status__ already handled in buildActionLabelCache via cidToLabelMap
598
+ if (pillPath === '__step_status__') {
599
+ continue
600
+ }
601
+ const key = `${cid}::${pillPath}`
602
+ if (seen.has(key)) {
603
+ continue
451
604
  }
605
+ seen.add(key)
606
+ // Prefer type from pillTypeMap (collected from wfa.dataPill() args before stripping),
607
+ // fall back to regex capture from element_mapping, then default to 'string'
608
+ const resolvedType = pillTypeMap?.get(key) ?? dataType ?? 'string'
609
+ pills.push({
610
+ cid,
611
+ stepLabel: cidToLabelMap.get(cid) ?? '',
612
+ pillPath,
613
+ dataType: resolvedType,
614
+ })
452
615
  }
453
616
  }
454
617
  return pills
455
618
  }
456
619
 
457
620
  /**
458
- * Extracts action dot-walk pill info from sys_element_mapping records.
621
+ * Extracts action dot-walk pill info from records.
459
622
  * Scans for {{action.X.Y...}} patterns where the path has 2+ segments (i.e., deeper than top-level input).
460
623
  * Deduplicates by full path. Uses pillTypeMap for type resolution.
461
624
  */
@@ -464,34 +627,28 @@ function extractActionDotWalkPills(records: Record[], pillTypeMap?: Map<string,
464
627
  const seen = new Set<string>()
465
628
  const regex = /\{\{action\.([^|}]+)(?:\|([^}]+))?\}\}/g
466
629
 
467
- for (const topRec of records) {
468
- for (const rec of topRec.flat()) {
469
- if (rec.getTable() !== 'sys_element_mapping') {
630
+ for (const text of collectPillTextsFromRecords(records)) {
631
+ for (const match of text.matchAll(regex)) {
632
+ const [, path, dataType] = match
633
+ if (!path) {
470
634
  continue
471
635
  }
472
- const value = rec.get('value')?.asString()?.getValue() ?? ''
473
- for (const match of value.matchAll(regex)) {
474
- const [, path, dataType] = match
475
- if (!path) {
476
- continue
477
- }
478
- const segments = path.split('.')
479
- // Only dot-walk pills (2+ segments) — top-level ones are already in inputsConfig
480
- if (segments.length < 2) {
481
- continue
482
- }
483
- if (seen.has(path)) {
484
- continue
485
- }
486
- seen.add(path)
487
- const resolvedType = pillTypeMap?.get(`action::${path}`) ?? dataType ?? 'string'
488
- pills.push({
489
- fullPath: path,
490
- parentField: segments[0] ?? '',
491
- columnName: segments[segments.length - 1] ?? '',
492
- dataType: resolvedType,
493
- })
636
+ const segments = path.split('.')
637
+ // Only dot-walk pills (2+ segments) — top-level ones are already in inputsConfig
638
+ if (segments.length < 2) {
639
+ continue
640
+ }
641
+ if (seen.has(path)) {
642
+ continue
494
643
  }
644
+ seen.add(path)
645
+ const resolvedType = pillTypeMap?.get(`action::${path}`) ?? dataType ?? 'string'
646
+ pills.push({
647
+ fullPath: path,
648
+ parentField: segments[0] ?? '',
649
+ columnName: segments[segments.length - 1] ?? '',
650
+ dataType: resolvedType,
651
+ })
495
652
  }
496
653
  }
497
654
  return pills
@@ -778,6 +935,73 @@ function convertActionPillsInInputs(
778
935
  }
779
936
  }
780
937
 
938
+ /**
939
+ * Converts pill strings buried inside ApprovalRulesShape and ApprovalDueDateShape values
940
+ * to wfa.dataPill() shapes. These structured shapes are created by normalizeInputValue()
941
+ * before convertActionPillsInInputs runs, so their nested pill strings are unreachable
942
+ * by the normal string-based pill conversion.
943
+ */
944
+ function convertPillsInStructuredShapes(
945
+ inputsObj: Props,
946
+ source: Source,
947
+ diagnostics: Diagnostics,
948
+ labelCacheMap?: Map<string, string>
949
+ ): void {
950
+ for (const [key, val] of Object.entries(inputsObj)) {
951
+ if (val instanceof ApprovalRulesShape) {
952
+ const rules = val.getApprovalRules()
953
+ let changed = false
954
+ const newRuleSets = rules.ruleSets?.map((ruleSet) => ({
955
+ ...ruleSet,
956
+ rules: ruleSet.rules?.map((rule) =>
957
+ rule.map((condition) => ({
958
+ ...condition,
959
+ users: condition.users?.map((user) => {
960
+ const s = String(user)
961
+ if (detectPillPattern(s) !== 'none') {
962
+ const shape = convertActionPillToShape(s, source, diagnostics, labelCacheMap)
963
+ if (shape) {
964
+ changed = true
965
+ return shape
966
+ }
967
+ }
968
+ return user
969
+ }),
970
+ groups: condition.groups?.map((group) => {
971
+ const s = String(group)
972
+ if (detectPillPattern(s) !== 'none') {
973
+ const shape = convertActionPillToShape(s, source, diagnostics, labelCacheMap)
974
+ if (shape) {
975
+ changed = true
976
+ return shape
977
+ }
978
+ }
979
+ return group
980
+ }),
981
+ }))
982
+ ),
983
+ }))
984
+ if (changed && newRuleSets) {
985
+ inputsObj[key] = new ApprovalRulesShape({
986
+ source,
987
+ value: { ...rules, ruleSets: newRuleSets },
988
+ })
989
+ }
990
+ } else if (val instanceof ApprovalDueDateShape) {
991
+ const dueDate = val.getApprovalDueDate()
992
+ if (dueDate.date && detectPillPattern(dueDate.date) !== 'none') {
993
+ const shape = convertActionPillToShape(dueDate.date, source, diagnostics, labelCacheMap)
994
+ if (shape) {
995
+ inputsObj[key] = new ApprovalDueDateShape({
996
+ source,
997
+ value: { ...dueDate, date: shape as unknown as string },
998
+ })
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+
781
1005
  /** Regex to find step[UUID] pills */
782
1006
  const STEP_PILL_REGEX =
783
1007
  /\{\{step\[([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\]\.([^|}]+)(?:\|[^}]*)?\}\}/g
@@ -832,6 +1056,89 @@ function convertStepPillsInInputs(
832
1056
  if (hasChanges) {
833
1057
  inputsObj[key] = new TemplateValueShape({ source, value: newTemplateValue })
834
1058
  }
1059
+ } else if (val instanceof ApprovalDueDateShape) {
1060
+ const dueDate = val.getApprovalDueDate()
1061
+ if (dueDate.date && typeof dueDate.date === 'string' && detectPillPattern(dueDate.date) !== 'none') {
1062
+ const shape = convertStepPillString(dueDate.date, source, cidToIdentifierMap, labelCacheMap)
1063
+ if (shape) {
1064
+ inputsObj[key] = new ApprovalDueDateShape({
1065
+ source,
1066
+ value: { ...dueDate, date: shape as unknown as string },
1067
+ })
1068
+ }
1069
+ }
1070
+ } else if (val instanceof ApprovalRulesShape) {
1071
+ const rules = val.getApprovalRules()
1072
+ let changed = false
1073
+ const newRuleSets = rules.ruleSets?.map((ruleSet) => ({
1074
+ ...ruleSet,
1075
+ rules: ruleSet.rules?.map((rule) =>
1076
+ rule.map((condition) => ({
1077
+ ...condition,
1078
+ users: condition.users?.map((user) => {
1079
+ const s = String(user)
1080
+ if (detectPillPattern(s) !== 'none') {
1081
+ const shape = convertStepPillString(s, source, cidToIdentifierMap, labelCacheMap)
1082
+ if (shape) {
1083
+ changed = true
1084
+ return shape
1085
+ }
1086
+ }
1087
+ return user
1088
+ }),
1089
+ groups: condition.groups?.map((group) => {
1090
+ const s = String(group)
1091
+ if (detectPillPattern(s) !== 'none') {
1092
+ const shape = convertStepPillString(s, source, cidToIdentifierMap, labelCacheMap)
1093
+ if (shape) {
1094
+ changed = true
1095
+ return shape
1096
+ }
1097
+ }
1098
+ return group
1099
+ }),
1100
+ }))
1101
+ ),
1102
+ }))
1103
+ if (changed && newRuleSets) {
1104
+ inputsObj[key] = new ApprovalRulesShape({
1105
+ source,
1106
+ value: { ...rules, ruleSets: newRuleSets },
1107
+ })
1108
+ }
1109
+ } else if (val && typeof val === 'object' && !Array.isArray(val) && !isShapeInstance(val)) {
1110
+ // Recursively process nested plain objects (e.g., inputVariables)
1111
+ // This handles script step input variables which have 'value' fields with step pills
1112
+ const nestedObj = val as Props
1113
+ for (const nestedVal of Object.values(nestedObj)) {
1114
+ if (
1115
+ nestedVal &&
1116
+ typeof nestedVal === 'object' &&
1117
+ !Array.isArray(nestedVal) &&
1118
+ !isShapeInstance(nestedVal)
1119
+ ) {
1120
+ const innerObj = nestedVal as Props
1121
+ const innerValue = innerObj['value']
1122
+ if (typeof innerValue === 'string') {
1123
+ const converted = convertStepPillString(innerValue, source, cidToIdentifierMap, labelCacheMap)
1124
+ if (converted) {
1125
+ innerObj['value'] = converted
1126
+ }
1127
+ } else if (innerValue instanceof TemplateExpressionShape) {
1128
+ // Handle case where convertActionPillsInInputs already converted action pills,
1129
+ // leaving step pills in the template literal text segments
1130
+ const resolved = resolveStepPillsInTemplateExpression(
1131
+ innerValue,
1132
+ source,
1133
+ cidToIdentifierMap,
1134
+ labelCacheMap
1135
+ )
1136
+ if (resolved) {
1137
+ innerObj['value'] = resolved
1138
+ }
1139
+ }
1140
+ }
1141
+ }
835
1142
  }
836
1143
  }
837
1144
  }
@@ -851,9 +1158,12 @@ function resolveStepPillMatch(
851
1158
  return undefined
852
1159
  }
853
1160
 
1161
+ // Split dot-walk property path into individual segments (e.g., "record.manager.email" → ["record", "manager", "email"])
1162
+ // PropertyAccessShape needs each path segment as a separate element for correct code generation
1163
+ const pathParts = property.split('.')
854
1164
  const expression = new PropertyAccessShape({
855
1165
  source,
856
- elements: [identifier, property],
1166
+ elements: [identifier, ...pathParts] as unknown as readonly [unknown, unknown, ...unknown[]],
857
1167
  })
858
1168
 
859
1169
  const pillName = `step[${uuid}].${property}`
@@ -1151,9 +1461,131 @@ const actionDefRelationships = {
1151
1461
  },
1152
1462
  }
1153
1463
 
1464
+ /**
1465
+ * Resolves any shape (string literal, number, template expression, dataPill call)
1466
+ * to its final string representation with pill references resolved.
1467
+ */
1468
+ function resolveToString(shape: Shape, cidMap: Map<string, string>, pillTypeMap: Map<string, string>): string {
1469
+ if (shape instanceof TemplateExpressionShape) {
1470
+ return resolveAllPillsInTemplate(shape, cidMap, pillTypeMap)
1471
+ }
1472
+ if (shape instanceof PillShape) {
1473
+ const raw = shape.getValue()
1474
+ collectPillTypes(raw, pillTypeMap)
1475
+ return stripPillType(raw)
1476
+ }
1477
+ const resolved = resolveAnyPillFromShape(shape, cidMap)
1478
+ if (resolved) {
1479
+ if (resolved.isStep) {
1480
+ collectPillTypes(resolved.pill, pillTypeMap)
1481
+ }
1482
+ return stripPillType(resolved.pill)
1483
+ }
1484
+ return String(shape.getValue?.() ?? '')
1485
+ }
1486
+
1487
+ /**
1488
+ * Builds the complex JSON `status` field for a sys_hub_status_condition record.
1489
+ * The status contains complexObject (runtime values), complexObjectSchema
1490
+ * (FlowDesigner:FDACTIONSTATUS with SimpleMapFacet field descriptors), and type facets.
1491
+ *
1492
+ * When a field value contains datapill references ({{...}}), it goes into the
1493
+ * `mapped` field of the SimpleMapFacet; otherwise static values go into
1494
+ * complexObject.$cv.$v and mapped is "{}".
1495
+ */
1496
+ function buildStatusConditionJson(codeValue: string, messageValue: string): string {
1497
+ const hasPillInCode = codeValue.includes('{{')
1498
+ const hasPillInMessage = messageValue.includes('{{')
1499
+
1500
+ const codeMapped = hasPillInCode ? JSON.stringify({ code: codeValue }) : '{}'
1501
+ const messageMapped = hasPillInMessage ? JSON.stringify({ message: messageValue }) : '{}'
1502
+
1503
+ const codeCV = hasPillInCode ? '' : codeValue
1504
+ const messageCV = hasPillInMessage ? '' : messageValue
1505
+
1506
+ // Derive the complexObjectSchema from the built-in FDACTIONSTATUS definition
1507
+ // rather than hardcoding it, so it stays in sync with the canonical schema.
1508
+ const fdActionStatus = builtInComplexObjects['FDACTIONSTATUS']
1509
+ if (!fdActionStatus) {
1510
+ throw new Error('FDACTIONSTATUS not found in builtInComplexObjects')
1511
+ }
1512
+ const baseSchema = JSON.parse(fdActionStatus.data.serialized_content)
1513
+ const fdSchema = baseSchema['FlowDesigner:FDACTIONSTATUS']
1514
+
1515
+ // Inject `mapped` into the field facets for code and message
1516
+ for (const [fieldName, mapped] of [
1517
+ ['code', codeMapped],
1518
+ ['message', messageMapped],
1519
+ ] as const) {
1520
+ const facetKey = `${fieldName}.$field_facets`
1521
+ const facets = JSON.parse(fdSchema[facetKey].SimpleMapFacet)
1522
+ facets.mapped = mapped
1523
+ fdSchema[facetKey].SimpleMapFacet = JSON.stringify(facets)
1524
+ }
1525
+
1526
+ return JSON.stringify({
1527
+ version: '1.0',
1528
+ complexObject: {
1529
+ code: { $cv: { $c: 'java.lang.String', $v: codeCV } },
1530
+ message: { $cv: { $c: 'java.lang.String', $v: messageCV } },
1531
+ },
1532
+ complexObjectSchema: baseSchema,
1533
+ serializationFormat: 'JSON',
1534
+ })
1535
+ }
1536
+
1537
+ /**
1538
+ * Parses a status JSON blob from a sys_hub_status_condition record and extracts
1539
+ * the code and message values. Datapill references live in the `mapped` field
1540
+ * of the SimpleMapFacet; static values live in complexObject.$cv.$v.
1541
+ */
1542
+ function parseStatusConditionJson(
1543
+ statusJson: string,
1544
+ logger?: { warn: (msg: string) => void }
1545
+ ): { code: string; message: string } {
1546
+ try {
1547
+ const parsed = JSON.parse(statusJson)
1548
+ const co = parsed?.complexObject ?? {}
1549
+ const schema = parsed?.complexObjectSchema ?? {}
1550
+ const fdSchema = schema['FlowDesigner:FDACTIONSTATUS'] ?? {}
1551
+
1552
+ // Extract code
1553
+ let code = co?.code?.$cv?.$v ?? ''
1554
+ const codeFacetsRaw = fdSchema['code.$field_facets']?.SimpleMapFacet
1555
+ if (codeFacetsRaw) {
1556
+ const codeFacets = typeof codeFacetsRaw === 'string' ? JSON.parse(codeFacetsRaw) : codeFacetsRaw
1557
+ const codeMapped = codeFacets?.mapped
1558
+ if (codeMapped && codeMapped !== '{}') {
1559
+ const mappedObj = typeof codeMapped === 'string' ? JSON.parse(codeMapped) : codeMapped
1560
+ if (mappedObj?.code) {
1561
+ code = mappedObj.code
1562
+ }
1563
+ }
1564
+ }
1565
+
1566
+ // Extract message
1567
+ let message = co?.message?.$cv?.$v ?? ''
1568
+ const msgFacetsRaw = fdSchema['message.$field_facets']?.SimpleMapFacet
1569
+ if (msgFacetsRaw) {
1570
+ const msgFacets = typeof msgFacetsRaw === 'string' ? JSON.parse(msgFacetsRaw) : msgFacetsRaw
1571
+ const msgMapped = msgFacets?.mapped
1572
+ if (msgMapped && msgMapped !== '{}') {
1573
+ const mappedObj = typeof msgMapped === 'string' ? JSON.parse(msgMapped) : msgMapped
1574
+ if (mappedObj?.message) {
1575
+ message = mappedObj.message
1576
+ }
1577
+ }
1578
+ }
1579
+
1580
+ return { code, message }
1581
+ } catch (e) {
1582
+ logger?.warn(`parseStatusConditionJson: failed to parse status JSON — ${e}`)
1583
+ return { code: '', message: '' }
1584
+ }
1585
+ }
1586
+
1154
1587
  export const ActionDefinitionPlugin = Plugin.create({
1155
1588
  name: 'ActionDefinitionPlugin',
1156
- docs: [createSdkDocEntry('Action', ['sys_hub_action_type_definition'])],
1157
1589
  records: {
1158
1590
  sys_hub_action_type_definition: {
1159
1591
  relationships: {
@@ -1163,6 +1595,10 @@ export const ActionDefinitionPlugin = Plugin.create({
1163
1595
  descendant: true,
1164
1596
  relationships: {
1165
1597
  ...actionDefRelationships,
1598
+ sys_variable_value: {
1599
+ via: 'document_key',
1600
+ descendant: true,
1601
+ },
1166
1602
  sys_element_mapping: {
1167
1603
  via: 'id',
1168
1604
  descendant: true,
@@ -1197,95 +1633,38 @@ export const ActionDefinitionPlugin = Plugin.create({
1197
1633
  async toShape(record, { descendants, diagnostics, logger }) {
1198
1634
  const actionSysId = record.getId().getValue()
1199
1635
 
1200
- // Fall back to Record API for actions with error evaluation conditions (not yet fully supported).
1201
- // We check for sys_hub_status_condition records (the actual conditions) rather than
1202
- // sys_hub_action_status_metadata (which can exist as empty shells without conditions).
1203
- const actionStatusMetadataIds = descendants
1636
+ // Query error evaluation descendants for this action.
1637
+ // sys_hub_action_status_metadata is the parent container;
1638
+ // sys_hub_status_condition holds the individual conditions.
1639
+ const actionStatusMetadata = descendants
1204
1640
  .query('sys_hub_action_status_metadata')
1205
1641
  .filter((r) => r.get('action_type_id')?.asString()?.getValue() === actionSysId)
1206
- .map((r) => r.getId().getValue())
1207
- const hasErrorEvaluation = descendants
1642
+ const actionStatusMetadataIds = actionStatusMetadata.map((r) => r.getId().getValue())
1643
+ const statusConditions = descendants
1208
1644
  .query('sys_hub_status_condition')
1209
- .some((r) =>
1645
+ .filter((r) =>
1210
1646
  actionStatusMetadataIds.includes(
1211
1647
  r.get('action_status_metadata_id')?.asString()?.getValue() ?? ''
1212
1648
  )
1213
1649
  )
1214
- if (hasErrorEvaluation) {
1215
- const actionName = record.get('name')?.getValue() ?? actionSysId
1216
- logger.warn(`Action '${actionName}' has error evaluation — falling back to Record API`)
1217
- return { success: false }
1218
- }
1219
1650
 
1220
1651
  // Filter inputs/outputs to only those belonging to this action definition,
1221
- // excluding snapshot-parented records
1652
+ // excluding snapshot-parented records, and sort by <order> to preserve
1653
+ // the order defined in the XML (instance document order is not guaranteed)
1654
+ const sortByOrder = (a: Record, b: Record) =>
1655
+ Number(a.get('order')?.asString()?.getValue() ?? 0) -
1656
+ Number(b.get('order')?.asString()?.getValue() ?? 0)
1222
1657
  const actionInputs = descendants
1223
1658
  .query('sys_hub_action_input')
1224
1659
  .filter((r) => r.get('model')?.asString()?.getValue() === actionSysId)
1660
+ .sort(sortByOrder)
1225
1661
  const actionOutputs = descendants
1226
1662
  .query('sys_hub_action_output')
1227
1663
  .filter((r) => r.get('model')?.asString()?.getValue() === actionSysId)
1228
-
1229
- // Fall back to Record API for custom actions with user-defined outputs (not yet fully supported).
1230
- // Core actions (in CORE_ACTIONS_SYS_ID_NAME_MAP) are fully supported and should not fall back.
1231
- // System-generated outputs (__action_status__, __dont_treat_as_error__) are excluded —
1232
- // every action has these automatically, they are not custom outputs.
1233
- const isCoreAction = actionSysId in CORE_ACTIONS_SYS_ID_NAME_MAP
1234
- const customOutputs = actionOutputs.filter((r) => {
1235
- const element = r.get('element')?.asString()?.getValue() ?? ''
1236
- return element !== '__action_status__' && element !== '__dont_treat_as_error__'
1237
- })
1238
- if (!isCoreAction && customOutputs.length > 0) {
1239
- const actionName = record.get('name')?.getValue() ?? actionSysId
1240
- logger.warn(`Custom action '${actionName}' has outputs — falling back to Record API`)
1241
- return { success: false }
1242
- }
1243
-
1244
- // Build snapshot output value map: element name → assigned value
1245
- // Output values are stored in two places on the snapshot:
1246
- // 1. sys_variable_value records (plain text values)
1247
- // 2. sys_element_mapping records (datapill/template values)
1248
- const snapshotOutputValues = new Map<string, string>()
1249
- const snapshots = descendants.query('sys_hub_action_type_snapshot')
1250
- if (snapshots.length > 0) {
1251
- const snapshotId = snapshots[0]?.getId()?.getValue()
1252
-
1253
- // Check sys_element_mapping for datapill values (field=element name, id=snapshot id)
1254
- const elementMappings = descendants
1255
- .query('sys_element_mapping')
1256
- .filter((r) => r.get('id')?.asString()?.getValue() === snapshotId)
1257
- for (const mapping of elementMappings) {
1258
- const fieldName = mapping.get('field')?.asString()?.getValue()
1259
- const value = mapping.get('value')?.ifString()?.getValue()
1260
- if (fieldName && value !== undefined && value !== '') {
1261
- snapshotOutputValues.set(fieldName, value)
1262
- }
1263
- }
1264
-
1265
- // Check sys_variable_value for plain text values (on snapshot outputs)
1266
- const snapshotOutputs = descendants
1267
- .query('sys_hub_action_output')
1268
- .filter((r) => r.get('model')?.asString()?.getValue() === snapshotId)
1269
- for (const snapshotOutput of snapshotOutputs) {
1270
- const elementName = snapshotOutput.get('element')?.asString()?.getValue()
1271
- if (!elementName || snapshotOutputValues.has(elementName)) {
1272
- continue
1273
- }
1274
- const snapshotOutputSysId = snapshotOutput.getId().getValue()
1275
- const varValues = descendants
1276
- .query('sys_variable_value')
1277
- .filter((v) => v.get('variable')?.asString()?.getValue() === snapshotOutputSysId)
1278
- if (varValues.length > 0) {
1279
- const value = varValues[0]?.get('value')?.ifString()?.getValue()
1280
- if (value !== undefined && value !== '') {
1281
- snapshotOutputValues.set(elementName, value)
1282
- }
1283
- }
1284
- }
1285
- }
1664
+ .sort(sortByOrder)
1286
1665
 
1287
1666
  const inputs = buildVariableShapes(actionInputs, descendants)
1288
- const outputs = buildVariableShapes(actionOutputs, descendants, undefined, snapshotOutputValues)
1667
+ const outputs = buildVariableShapes(actionOutputs, descendants)
1289
1668
 
1290
1669
  // Extract label_cache from action definition for datapill type info
1291
1670
  let labelCacheMap: Map<string, string> | undefined
@@ -1384,7 +1763,7 @@ export const ActionDefinitionPlugin = Plugin.create({
1384
1763
  }
1385
1764
  // Ext input element_mappings have table var__m_sys_hub_step_ext_input_*
1386
1765
  // Collect their values but don't add to inputsObj (they go under inputVariables)
1387
- if (table.startsWith('var__m_sys_hub_step_ext_input_')) {
1766
+ if (table.startsWith(EXT_INPUT_TABLE_PREFIX)) {
1388
1767
  extInputMappingValues[rawFieldName] = value
1389
1768
  continue
1390
1769
  }
@@ -1461,8 +1840,8 @@ export const ActionDefinitionPlugin = Plugin.create({
1461
1840
  (v) => v.get('variable')?.asString()?.getValue() === extInputSysId
1462
1841
  )
1463
1842
  const value =
1464
- extInputMappingValues[elementName] ||
1465
- varValueRecord?.get('value')?.asString()?.getValue() ||
1843
+ extInputMappingValues[elementName] ??
1844
+ varValueRecord?.get('value')?.asString()?.getValue() ??
1466
1845
  ''
1467
1846
  const inputEntry: Props = { label }
1468
1847
  if (value) {
@@ -1489,17 +1868,17 @@ export const ActionDefinitionPlugin = Plugin.create({
1489
1868
  continue
1490
1869
  }
1491
1870
  const label = extOutput.get('label')?.asString()?.getValue() || elementName
1492
- const mandatory = extOutput.get('mandatory')?.asString()?.getValue() === 'true'
1493
- const internalType = extOutput.get('internal_type')?.asString()?.getValue() ?? 'string'
1871
+ const mandatory = extOutput.get('mandatory')?.ifString()?.getValue() === 'true'
1872
+ const internalType = extOutput.get('internal_type')?.ifString()?.getValue() ?? 'string'
1494
1873
  // Check attributes for uiType to handle cases where internal_type is generic
1495
- const attributes = extOutput.get('attributes')?.asString()?.getValue() ?? ''
1874
+ const attributes = extOutput.get('attributes')?.ifString()?.getValue() ?? ''
1496
1875
  const uiType = getAttributeValue(attributes, 'uiType') ?? internalType
1497
1876
  const columnApiName = COLUMN_TYPE_TO_API[uiType] ?? 'StringColumn'
1498
1877
  const columnProps: Props = { label }
1499
1878
  if (mandatory) {
1500
1879
  columnProps['mandatory'] = true
1501
1880
  }
1502
- const maxLength = extOutput.get('max_length')?.asString()?.getValue()
1881
+ const maxLength = extOutput.get('max_length')?.ifString()?.getValue()
1503
1882
  if (maxLength) {
1504
1883
  columnProps['maxLength'] = Number(maxLength)
1505
1884
  }
@@ -1514,14 +1893,14 @@ export const ActionDefinitionPlugin = Plugin.create({
1514
1893
  if (hint) {
1515
1894
  columnProps['hint'] = hint
1516
1895
  }
1517
- const referenceTable = extOutput.get('reference')?.asString()?.getValue()
1896
+ const referenceTable = extOutput.get('reference')?.ifString()?.getValue()
1518
1897
  if (
1519
1898
  referenceTable &&
1520
1899
  (columnApiName === 'ReferenceColumn' || columnApiName === 'ListColumn')
1521
1900
  ) {
1522
1901
  columnProps['referenceTable'] = referenceTable
1523
1902
  }
1524
- const defaultValue = extOutput.get('default_value')?.asString()?.getValue()
1903
+ const defaultValue = extOutput.get('default_value')?.ifString()?.getValue()
1525
1904
  if (defaultValue) {
1526
1905
  columnProps['default'] = defaultValue
1527
1906
  }
@@ -1542,6 +1921,10 @@ export const ActionDefinitionPlugin = Plugin.create({
1542
1921
  // Convert action input pills ({{action.xxx}}) to wfa.dataPill(params.inputs.xxx, 'type')
1543
1922
  convertActionPillsInInputs(inputsObj, stepInstance, diagnostics, labelCacheMap)
1544
1923
 
1924
+ // Convert pills buried inside ApprovalRulesShape/ApprovalDueDateShape
1925
+ // (these were already parsed by normalizeInputValue before pill conversion)
1926
+ convertPillsInStructuredShapes(inputsObj, stepInstance, diagnostics, labelCacheMap)
1927
+
1545
1928
  // Convert step output pills ({{step[UUID].xxx}}) to wfa.dataPill(varName.xxx, 'type')
1546
1929
  // Earlier steps are already in cidToIdentifierMap since steps are processed in order
1547
1930
  convertStepPillsInInputs(inputsObj, stepInstance, cidToIdentifierMap, labelCacheMap)
@@ -1595,6 +1978,174 @@ export const ActionDefinitionPlugin = Plugin.create({
1595
1978
  }
1596
1979
  }
1597
1980
 
1981
+ // Reconstruct wfa.errorEvaluation([...]) from sys_hub_status_condition records.
1982
+ // This must appear before wfa.assignActionOutputs in the generated Fluent file.
1983
+ if (statusConditions.length > 0) {
1984
+ const sortedConditions = [...statusConditions].sort(sortByOrder)
1985
+ const conditionShapes: Shape[] = []
1986
+
1987
+ for (const cond of sortedConditions) {
1988
+ const label = cond.get('label')?.asString()?.getValue() ?? ''
1989
+ const condition = cond.get('condition')?.asString()?.getValue() ?? ''
1990
+ const dontTreatAsError = cond.get('dont_treat_as_error')?.asString()?.getValue() === 'true'
1991
+ const statusJson = cond.get('status')?.asString()?.getValue() ?? '{}'
1992
+ const { code, message } = parseStatusConditionJson(statusJson, logger)
1993
+
1994
+ // Build status props as plain object, resolve pills, then wrap
1995
+ const statusProps: Props = {}
1996
+ statusProps['code'] = /^\d+$/.test(code) ? Number(code) : code
1997
+ statusProps['message'] = message
1998
+ convertActionPillsInInputs(statusProps, cond, diagnostics, labelCacheMap)
1999
+ convertStepPillsInInputs(statusProps, cond, cidToIdentifierMap, labelCacheMap)
2000
+
2001
+ // Build condition props as plain object, resolve pills on condition only.
2002
+ // label is a display name and must NOT be pill-converted;
2003
+ // status pills are already resolved above via statusProps.
2004
+ const conditionProps: Props = { condition }
2005
+ convertActionPillsInInputs(conditionProps, cond, diagnostics, labelCacheMap)
2006
+ convertStepPillsInInputs(conditionProps, cond, cidToIdentifierMap, labelCacheMap)
2007
+
2008
+ const condProps: Props = {
2009
+ label,
2010
+ condition: conditionProps['condition'],
2011
+ status: new ObjectShape({ source: cond, properties: statusProps }),
2012
+ }
2013
+ if (dontTreatAsError) {
2014
+ condProps['dontTreatAsError'] = true
2015
+ }
2016
+
2017
+ conditionShapes.push(new ObjectShape({ source: cond, properties: condProps }))
2018
+ }
2019
+
2020
+ const statusSource = actionStatusMetadata[0] ?? record
2021
+ stepShapes.push(
2022
+ new CallExpressionShape({
2023
+ source: statusSource,
2024
+ callee: ERROR_EVALUATION_CALLEE,
2025
+ args: [
2026
+ new ArrayShape({
2027
+ source: statusSource,
2028
+ elements: conditionShapes,
2029
+ }),
2030
+ ],
2031
+ })
2032
+ )
2033
+ }
2034
+
2035
+ // Reconstruct wfa.assignActionOutputs() from output element_mappings.
2036
+ // Element mappings for output assignments can be keyed to the action def sys_id
2037
+ // (draft actions) or a snapshot sys_id (published actions). Check both.
2038
+ {
2039
+ const candidateIds = new Set<string>([actionSysId])
2040
+ const snapshots = descendants
2041
+ .query('sys_hub_action_type_snapshot')
2042
+ .filter((s) => s.get('parent_action')?.asString()?.getValue() === actionSysId)
2043
+ for (const snap of snapshots) {
2044
+ candidateIds.add(snap.getId().getValue())
2045
+ }
2046
+
2047
+ // Find element_mappings for action outputs keyed to any candidate id
2048
+ const outputElementMappings = descendants
2049
+ .query('sys_element_mapping')
2050
+ .filter(
2051
+ (m) =>
2052
+ candidateIds.has(m.get('id')?.asString()?.getValue() ?? '') &&
2053
+ (m.get('table')?.asString()?.getValue() ?? '').startsWith(ACTION_OUTPUT_TABLE_PREFIX)
2054
+ )
2055
+
2056
+ // Build output element name lookup from all output records
2057
+ // (action-parented and snapshot-parented) for sys_variable_value resolution
2058
+ const varSysIdToElement = new Map<string, string>()
2059
+ const snapshotOutputs = descendants
2060
+ .query('sys_hub_action_output')
2061
+ .filter((r) => candidateIds.has(r.get('model')?.asString()?.getValue() ?? ''))
2062
+ for (const outputRec of [...snapshotOutputs, ...actionOutputs]) {
2063
+ const element = outputRec.get('element')?.asString()?.getValue()
2064
+ if (element) {
2065
+ varSysIdToElement.set(outputRec.getId().getValue(), element)
2066
+ }
2067
+ }
2068
+
2069
+ // Gather sys_variable_value records for static output values
2070
+ const outputVarValues = descendants.query('sys_variable_value').filter((v) => {
2071
+ const docKey = v.get('document_key')?.asString()?.getValue() ?? ''
2072
+ return candidateIds.has(docKey)
2073
+ })
2074
+
2075
+ if (outputElementMappings.length > 0 || outputVarValues.length > 0) {
2076
+ const valuesObj: Props = {}
2077
+ const sourceRecord = snapshots[0] ?? record
2078
+
2079
+ // Static values from sys_variable_value
2080
+ for (const varValue of outputVarValues) {
2081
+ const variableSysId = varValue.get('variable')?.asString()?.getValue()
2082
+ const value = varValue.get('value')?.asString()?.getValue()
2083
+ const outputName = variableSysId ? varSysIdToElement.get(variableSysId) : undefined
2084
+ // Skip internal platform outputs and missing values (null/undefined)
2085
+ if (!outputName || outputName.startsWith('__') || value == null) {
2086
+ continue
2087
+ }
2088
+ // Skip FlowObject/FlowArray complex object schema serializations.
2089
+ // These sys_variable_value records store the output's type structure
2090
+ // (already declared via FlowObject/FlowArray in Fluent), not user-assigned values.
2091
+ if (value.includes('"complexObjectSchema"') || value.includes('"$COCollectionField"')) {
2092
+ continue
2093
+ }
2094
+ // sys_variable_value can contain pill strings (e.g. {{action.variable|string}})
2095
+ // Strip type suffix (|string, |reference) before pill parsing
2096
+ const pillStripped = value.replace(/\|[a-z_]+\}\}/g, '}}')
2097
+ valuesObj[outputName] = pillStripped
2098
+ }
2099
+
2100
+ // Datapill values from sys_element_mapping (overrides var values when both exist)
2101
+ for (const mapping of outputElementMappings) {
2102
+ const outputName = mapping.get('field')?.asString()?.getValue()
2103
+ const pillValue = mapping.get('value')?.asString()?.getValue()
2104
+ // Skip internal outputs, missing values, and empty pill strings.
2105
+ // An empty pill value means "no datapill assignment" — the static value
2106
+ // from sys_variable_value (if any) should be preserved.
2107
+ if (!outputName || !pillValue || outputName.startsWith('__')) {
2108
+ continue
2109
+ }
2110
+ valuesObj[outputName] = pillValue
2111
+ }
2112
+
2113
+ // Two-pass pill resolution (same approach as step inputs):
2114
+ // Pass 1: Convert action pills ({{action.xxx}}) → dataPill shapes
2115
+ convertActionPillsInInputs(valuesObj, sourceRecord, diagnostics, labelCacheMap)
2116
+
2117
+ // Pass 2: Convert step pills ({{step[UUID].xxx}}) → dataPill shapes
2118
+ convertStepPillsInInputs(valuesObj, sourceRecord, cidToIdentifierMap, labelCacheMap)
2119
+
2120
+ if (Object.keys(valuesObj).length > 0) {
2121
+ // Use params.outputs reference instead of duplicating the full schema
2122
+ const outputsRef = new PropertyAccessShape({
2123
+ source: sourceRecord,
2124
+ elements: [
2125
+ new IdentifierShape({
2126
+ source: sourceRecord,
2127
+ name: ACTION_PILL_PARAM_NAME,
2128
+ }),
2129
+ 'outputs',
2130
+ ],
2131
+ })
2132
+ stepShapes.push(
2133
+ new CallExpressionShape({
2134
+ source: sourceRecord,
2135
+ callee: ASSIGN_ACTION_OUTPUTS_CALLEE,
2136
+ args: [
2137
+ outputsRef,
2138
+ new ObjectShape({
2139
+ source: sourceRecord,
2140
+ properties: valuesObj,
2141
+ }),
2142
+ ],
2143
+ })
2144
+ )
2145
+ }
2146
+ }
2147
+ }
2148
+
1598
2149
  // Build args: config + optional body with step instances
1599
2150
  const actionConfig = record.transform(({ $ }) => ({
1600
2151
  $id: $.val(NowIdShape.from(record)),
@@ -1603,7 +2154,7 @@ export const ActionDefinitionPlugin = Plugin.create({
1603
2154
  description: $.def(''),
1604
2155
  access: $.def('public'),
1605
2156
  category: $.def(''),
1606
- protection: $.from('sys_policy').def(''),
2157
+ protectionPolicy: $.from('sys_policy').def(''),
1607
2158
  inputs: $.val(inputs),
1608
2159
  outputs: $.val(outputs),
1609
2160
  }))
@@ -1665,6 +2216,9 @@ export const ActionDefinitionPlugin = Plugin.create({
1665
2216
  sys_hub_status_condition: {
1666
2217
  coalesce: ['action_status_metadata_id', 'order'],
1667
2218
  },
2219
+ sys_element_mapping: {
2220
+ coalesce: ['id', 'table', 'field'],
2221
+ },
1668
2222
  },
1669
2223
  shapes: [
1670
2224
  {
@@ -1686,12 +2240,11 @@ export const ActionDefinitionPlugin = Plugin.create({
1686
2240
  explicitId: actionConfiguration.get('$id'),
1687
2241
  properties: actionConfiguration.transform(({ $ }) => ({
1688
2242
  name: $,
1689
- internal_name: $.from('name').map((n) => slugifyString(n.getValue())),
1690
2243
  annotation: $.def(''),
1691
2244
  description: $.def(''),
1692
2245
  access: $.def('public'),
1693
2246
  category: $.def(''),
1694
- sys_policy: $.from('protection').def(''),
2247
+ sys_policy: $.from('protectionPolicy').def(''),
1695
2248
  active: $.val(true),
1696
2249
  state: $.val('draft'),
1697
2250
  system_level: $.val(false),
@@ -1738,11 +2291,34 @@ export const ActionDefinitionPlugin = Plugin.create({
1738
2291
  const pillTypeMap = new Map<string, string>()
1739
2292
 
1740
2293
  const stepInfos: StepInfo[] = []
2294
+ let assignActionOutputsShape: CallExpressionShape | undefined
2295
+ let errorEvaluationShape: CallExpressionShape | undefined
1741
2296
  let order = 1
1742
2297
  for (const [, v] of (allInstances ?? []).entries()) {
1743
2298
  const isVarStatement = v instanceof VariableStatementShape
1744
2299
  const innerShape = isVarStatement ? v.getInitializer() : v
1745
2300
 
2301
+ if (
2302
+ innerShape instanceof CallExpressionShape &&
2303
+ innerShape.getCallee() === ASSIGN_ACTION_OUTPUTS_CALLEE
2304
+ ) {
2305
+ assignActionOutputsShape = innerShape
2306
+ continue
2307
+ }
2308
+
2309
+ if (
2310
+ innerShape instanceof CallExpressionShape &&
2311
+ innerShape.getCallee() === ERROR_EVALUATION_CALLEE
2312
+ ) {
2313
+ errorEvaluationShape = innerShape
2314
+ if (assignActionOutputsShape) {
2315
+ diagnostics.error(
2316
+ innerShape,
2317
+ `wfa.errorEvaluation() must appear before wfa.assignActionOutputs() in the action body.`
2318
+ )
2319
+ }
2320
+ continue
2321
+ }
1746
2322
  if (innerShape.getSource() instanceof StepInstanceShape) {
1747
2323
  const stepShape = innerShape.getSource() as StepInstanceShape
1748
2324
  stepShape.setCidMap(cidMap)
@@ -1793,6 +2369,162 @@ export const ActionDefinitionPlugin = Plugin.create({
1793
2369
  )
1794
2370
  relatedRecords.push(...resolvedStepPillRecords)
1795
2371
 
2372
+ // Create sys_element_mapping records for assignActionOutputs values.
2373
+ // For draft actions, element_mappings reference the action definition sys_id directly.
2374
+ if (assignActionOutputsShape) {
2375
+ const actionDefId = actionDefinitionRecord.getId().getValue()
2376
+ const valuesArg = assignActionOutputsShape.getArgument(1)?.asObject()
2377
+
2378
+ // Validate that assigned output names match declared outputs
2379
+ if (valuesArg && !outputsConfig) {
2380
+ diagnostics.error(
2381
+ assignActionOutputsShape,
2382
+ 'assignActionOutputs is used but no outputs are declared in the action config'
2383
+ )
2384
+ }
2385
+ if (valuesArg && outputsConfig) {
2386
+ const declaredOutputNames = new Set(outputsConfig.keys())
2387
+ const assignedOutputNames = new Set<string>()
2388
+ for (const [outputName] of valuesArg.entries({ resolve: false })) {
2389
+ assignedOutputNames.add(outputName)
2390
+ if (!declaredOutputNames.has(outputName)) {
2391
+ diagnostics.error(
2392
+ valuesArg.get(outputName),
2393
+ `Unknown output '${outputName}'. Available outputs: ${[...declaredOutputNames].join(', ') || '(none)'}`
2394
+ )
2395
+ }
2396
+ }
2397
+ for (const declaredName of declaredOutputNames) {
2398
+ if (!assignedOutputNames.has(declaredName)) {
2399
+ diagnostics.error(
2400
+ assignActionOutputsShape,
2401
+ `Output '${declaredName}' is declared but not assigned in assignActionOutputs. All declared outputs must be assigned a value.`
2402
+ )
2403
+ }
2404
+ }
2405
+ }
2406
+
2407
+ if (valuesArg) {
2408
+ const entries = valuesArg.entries({ resolve: false })
2409
+ for (const [outputName, valueShape] of entries) {
2410
+ const shape = valueShape as Shape
2411
+
2412
+ // Validate that the output value is not an empty string
2413
+ if (shape.isString() && shape.getValue() === '') {
2414
+ diagnostics.error(
2415
+ valuesArg.get(outputName),
2416
+ `Output '${outputName}' is assigned an empty string. All action outputs must be assigned a non-empty value.`
2417
+ )
2418
+ }
2419
+
2420
+ const pillValue = resolveToString(shape, cidMap, pillTypeMap)
2421
+
2422
+ const elementMapping = await factory.createRecord({
2423
+ source: assignActionOutputsShape,
2424
+ table: 'sys_element_mapping',
2425
+ properties: {
2426
+ field: outputName,
2427
+ id: actionDefId,
2428
+ table: `${ACTION_OUTPUT_TABLE_PREFIX}${actionDefId}`,
2429
+ value: pillValue,
2430
+ },
2431
+ })
2432
+ relatedRecords.push(elementMapping)
2433
+ }
2434
+ }
2435
+ }
2436
+
2437
+ // ── Error Evaluation ─────────────────────────────────────────────
2438
+ // wfa.errorEvaluation([...]) → sys_hub_action_status_metadata + sys_hub_status_condition records
2439
+ if (errorEvaluationShape) {
2440
+ const actionDefId = actionDefinitionRecord.getId().getValue()
2441
+
2442
+ // Create the single sys_hub_action_status_metadata record for this action
2443
+ const statusMetadataRecord = await factory.createRecord({
2444
+ source: errorEvaluationShape,
2445
+ table: 'sys_hub_action_status_metadata',
2446
+ properties: {
2447
+ action_type_id: actionDefId,
2448
+ },
2449
+ })
2450
+ relatedRecords.push(statusMetadataRecord)
2451
+ const metadataId = statusMetadataRecord.getId().getValue()
2452
+
2453
+ // Parse the array of conditions (arg 0)
2454
+ const conditionsArg = errorEvaluationShape.getArgument(0)
2455
+ if (conditionsArg?.isArray()) {
2456
+ const elements = conditionsArg.asArray().getElements(false)
2457
+ let condOrder = 1
2458
+
2459
+ for (const condElement of elements) {
2460
+ if (!condElement.isObject()) {
2461
+ continue
2462
+ }
2463
+ const condObj = condElement.asObject()
2464
+
2465
+ // Extract fields from { label, condition, status, dontTreatAsError }
2466
+ const labelShape = condObj.get('label')
2467
+ const conditionShape = condObj.get('condition')
2468
+ const statusShape = condObj.get('status')?.ifObject()?.asObject()
2469
+ const dontTreatShape = condObj.get('dontTreatAsError')
2470
+
2471
+ const label = labelShape?.ifString()?.getValue() ?? ''
2472
+ const condition = conditionShape ? resolveToString(conditionShape, cidMap, pillTypeMap) : ''
2473
+ const dontTreatRaw = dontTreatShape?.getValue?.()
2474
+ const dontTreatAsError = dontTreatRaw === true || dontTreatRaw === 'true'
2475
+
2476
+ // Resolve status code and message (may contain datapills).
2477
+ // Use entries({ resolve: false }) to get raw shapes — without this,
2478
+ // PillShape values get auto-resolved and lose their pill reference.
2479
+ let codeValue = ''
2480
+ let messageValue = ''
2481
+ if (statusShape) {
2482
+ for (const [field, fieldShape] of statusShape.entries({ resolve: false })) {
2483
+ const shape = fieldShape as Shape
2484
+ if (field === 'code') {
2485
+ codeValue = resolveToString(shape, cidMap, pillTypeMap)
2486
+ } else if (field === 'message') {
2487
+ messageValue = resolveToString(shape, cidMap, pillTypeMap)
2488
+ }
2489
+ }
2490
+ }
2491
+
2492
+ const statusJson = buildStatusConditionJson(codeValue, messageValue)
2493
+
2494
+ const conditionRecord = await factory.createRecord({
2495
+ source: errorEvaluationShape,
2496
+ table: 'sys_hub_status_condition',
2497
+ properties: {
2498
+ action_status_metadata_id: metadataId,
2499
+ condition,
2500
+ dont_treat_as_error: dontTreatAsError,
2501
+ label,
2502
+ order: condOrder,
2503
+ status: statusJson,
2504
+ },
2505
+ })
2506
+ relatedRecords.push(conditionRecord)
2507
+ condOrder++
2508
+ }
2509
+ }
2510
+
2511
+ // Create the __action_status__ element_mapping record.
2512
+ // The platform requires this to connect error evaluation status
2513
+ // conditions to the action's output. Its value is the base
2514
+ // FDACTIONSTATUS JSON with empty code/message mapped fields.
2515
+ const actionStatusMapping = await factory.createRecord({
2516
+ source: errorEvaluationShape,
2517
+ table: 'sys_element_mapping',
2518
+ properties: {
2519
+ field: '__action_status__',
2520
+ id: actionDefId,
2521
+ table: `${ACTION_OUTPUT_TABLE_PREFIX}${actionDefId}`,
2522
+ value: buildStatusConditionJson('', ''),
2523
+ },
2524
+ })
2525
+ relatedRecords.push(actionStatusMapping)
2526
+ }
2527
+
1796
2528
  const collectedStepPills = extractStepPillsFromRecords(relatedRecords, cidToLabelMap, pillTypeMap)
1797
2529
  const dotWalkPills = extractActionDotWalkPills(relatedRecords, pillTypeMap)
1798
2530