@aifabrix/builder 2.52.1 → 2.53.1

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 (225) hide show
  1. package/lib/app/deploy-config.js +3 -2
  2. package/lib/channels/add-channel.js +11 -41
  3. package/lib/channels/approval-guides/index.js +3 -0
  4. package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
  5. package/lib/channels/channel-add-existing.js +72 -0
  6. package/lib/channels/channel-runtime-resolver.js +5 -1
  7. package/lib/channels/channel-status-display.js +30 -3
  8. package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
  9. package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
  10. package/lib/channels/microsoft-copilot-limits.js +22 -3
  11. package/lib/channels/microsoft-copilot-package.js +59 -14
  12. package/lib/channels/microsoft-copilot-validate.js +40 -10
  13. package/lib/cli/attach-default-help-examples.js +102 -0
  14. package/lib/cli/index.js +2 -0
  15. package/lib/cli/setup-credential-deployment.js +7 -5
  16. package/lib/cli/setup-utility-validate.js +1 -0
  17. package/lib/commands/credential-push.js +37 -5
  18. package/lib/commands/package-score.js +72 -0
  19. package/lib/commands/role-assistant.js +46 -8
  20. package/lib/deployment/deployer-poll-run.js +2 -2
  21. package/lib/deployment/deployer.js +1 -1
  22. package/lib/deployment/environment.js +2 -2
  23. package/lib/external-system/deploy.js +11 -21
  24. package/lib/integration-definition/apply.js +12 -14
  25. package/lib/integration-definition/enrich-openapi-operations.js +9 -10
  26. package/lib/lab/package-parity-score.js +417 -0
  27. package/lib/programmatic/capability-registry.js +1 -0
  28. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  29. package/lib/programmatic/help-content/cip-overview.md +27 -0
  30. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  31. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  32. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  33. package/lib/programmatic/help-content/overview.md +25 -0
  34. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  35. package/lib/programmatic/help-content/workflow.md +50 -0
  36. package/lib/programmatic/openapi-component-schemas.js +1 -0
  37. package/lib/programmatic/openapi-descriptions.js +6 -0
  38. package/lib/programmatic/openapi-envelope-examples.js +11 -0
  39. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  40. package/lib/programmatic/openapi-request-schemas.js +39 -13
  41. package/lib/programmatic/openapi-result-schemas.js +30 -0
  42. package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
  43. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  44. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  45. package/lib/programmatic/route-handler-map.js +6 -0
  46. package/lib/programmatic/run-integration-definition.js +13 -5
  47. package/lib/role-assistant/evidence-sync.js +55 -7
  48. package/lib/role-assistant/test-runner-attachments.js +118 -0
  49. package/lib/role-assistant/test-runner-bid-apply.js +100 -21
  50. package/lib/role-assistant/test-runner-bid-document.js +1 -1
  51. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  52. package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
  53. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  54. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  55. package/lib/role-assistant/test-runner-workhub.js +68 -105
  56. package/lib/role-assistant/test-runner.js +5 -63
  57. package/lib/role-assistant/test-suite.js +33 -40
  58. package/lib/schema/external-datasource.schema.json +13 -4
  59. package/lib/utils/compose-generator.js +10 -1
  60. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  61. package/lib/utils/docker-build.js +2 -2
  62. package/lib/utils/help-builder.js +2 -1
  63. package/lib/utils/promote-dev-docker-image.js +15 -10
  64. package/package.json +14 -16
  65. package/templates/README.md +12 -1
  66. package/templates/applications/dataplane/application.yaml +8 -1
  67. package/templates/applications/dataplane/env.template +1 -1
  68. package/templates/applications/miso-controller/application.yaml +2 -1
  69. package/templates/applications/miso-controller/rbac.yaml +1 -0
  70. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
  71. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
  72. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  73. package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
  74. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
  75. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
  76. package/templates/python/docker-compose.hbs +36 -0
  77. package/templates/typescript/docker-compose.hbs +36 -0
  78. package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
  79. package/.cursor/process/README.md +0 -9
  80. package/.cursor/rules/anchor-docs.mdc +0 -15
  81. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  82. package/.cursor/rules/cli-layout.mdc +0 -81
  83. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  84. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  85. package/.cursor/rules/docs-rules.mdc +0 -32
  86. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  87. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  88. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  89. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  90. package/.cursor/rules/project-rules.mdc +0 -913
  91. package/.cursor/skills/README.md +0 -15
  92. package/.dockerignore +0 -52
  93. package/.eslintignore +0 -22
  94. package/.markdownlint.json +0 -14
  95. package/anchor-docs/README.md +0 -10
  96. package/anchor-docs/_TEMPLATE +0 -24
  97. package/babel.config.js +0 -6
  98. package/bin/builder-api-cleanup.js +0 -26
  99. package/bin/builder-api-nest.js +0 -10
  100. package/bin/builder-api-spike.js +0 -19
  101. package/enterprise-knowledge/README.md +0 -50
  102. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  103. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  104. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  105. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  106. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  107. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  108. package/integration/README.md +0 -81
  109. package/integration/github/README.md +0 -53
  110. package/integration/github/application.json +0 -21
  111. package/integration/github/deploy.js +0 -72
  112. package/integration/github/env.template +0 -11
  113. package/integration/github/github-datasource-issue-comments.json +0 -239
  114. package/integration/github/github-datasource-issues.json +0 -399
  115. package/integration/github/github-datasource-repository.json +0 -354
  116. package/integration/github/github-deploy.json +0 -1202
  117. package/integration/github/github-system.json +0 -82
  118. package/integration/hubspot-test/README.md +0 -159
  119. package/integration/hubspot-test/application.json +0 -54
  120. package/integration/hubspot-test/companies.json +0 -2048
  121. package/integration/hubspot-test/create-hubspot.js +0 -523
  122. package/integration/hubspot-test/env.template +0 -4
  123. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  124. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  125. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  126. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  127. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  128. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  129. package/integration/hubspot-test/rbac.json +0 -166
  130. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  131. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  132. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  133. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  134. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  135. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  136. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  137. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  138. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  139. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  140. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  141. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  142. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  143. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  144. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  145. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  146. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  147. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  148. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  149. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  150. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  151. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  152. package/integration/hubspot-test/test.js +0 -1588
  153. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  154. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  155. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  156. package/integration/roundtrip-test-local/README.md +0 -143
  157. package/integration/roundtrip-test-local/application.yaml +0 -13
  158. package/integration/roundtrip-test-local/env.template +0 -15
  159. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  160. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  161. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  162. package/integration/roundtrip-test-local2/README.md +0 -143
  163. package/integration/roundtrip-test-local2/application.yaml +0 -13
  164. package/integration/roundtrip-test-local2/env.template +0 -15
  165. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  166. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  167. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  168. package/integration/test/wizard.yaml +0 -8
  169. package/jest.config.coverage.js +0 -37
  170. package/jest.config.default.js +0 -11
  171. package/jest.config.integration.fixtures.js +0 -22
  172. package/jest.config.integration.js +0 -33
  173. package/jest.config.isolated.js +0 -11
  174. package/jest.config.manual.js +0 -30
  175. package/jest.global-live-fabrix-hooks.js +0 -36
  176. package/jest.isolated-projects.js +0 -283
  177. package/jest.projects.js +0 -79
  178. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
  179. package/packages/builder-api/README.md +0 -120
  180. package/packages/builder-api/package.json +0 -30
  181. package/packages/builder-api/src/app.module.ts +0 -7
  182. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  183. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  184. package/packages/builder-api/src/main.ts +0 -16
  185. package/packages/builder-api/tsconfig.json +0 -15
  186. package/scripts/check-builder-api-architecture-grep.js +0 -79
  187. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  188. package/scripts/ci-fix.sh +0 -19
  189. package/scripts/ci-simulate.sh +0 -19
  190. package/scripts/diagnose-cli.js +0 -150
  191. package/scripts/docker_image_content_gate.js +0 -47
  192. package/scripts/docker_image_content_gate.sh +0 -5
  193. package/scripts/export-builder-api-evidence.js +0 -262
  194. package/scripts/install-local.js +0 -490
  195. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  196. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  197. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  198. package/scripts/lib/plan-actor.js +0 -109
  199. package/scripts/lib/plan-config.js +0 -20
  200. package/scripts/lib/plan-gates.js +0 -154
  201. package/scripts/lib/plan-meta.js +0 -104
  202. package/scripts/lib/plan-parse.js +0 -162
  203. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  204. package/scripts/lib/plan-run-core.js +0 -125
  205. package/scripts/lib/plan-run-report.js +0 -119
  206. package/scripts/plan-approve.js +0 -24
  207. package/scripts/plan-archive.js +0 -49
  208. package/scripts/plan-run.js +0 -13
  209. package/scripts/plan-set-actor.js +0 -21
  210. package/scripts/plan-validate.js +0 -80
  211. package/scripts/pnpm/af.mjs +0 -125
  212. package/scripts/pnpm/check-quiet.mjs +0 -32
  213. package/scripts/pnpm/dev-install.mjs +0 -29
  214. package/scripts/pnpm/dev-reload.mjs +0 -18
  215. package/scripts/pnpm/help.mjs +0 -55
  216. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  217. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  218. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  219. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  220. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  221. package/scripts/pnpm/lib/spinner.mjs +0 -42
  222. package/scripts/pnpm-global-remove.js +0 -48
  223. package/scripts/stage_builder_api_for_docker.js +0 -65
  224. package/scripts/sync-builder-api-template.js +0 -44
  225. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Build work.start Intent payload for role-assistant cases.
3
+ * @fileoverview Extracted from test-runner.js (file-size) + 517.1 attachments
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const crypto = require('crypto');
9
+ const { buildStartAttachments } = require('./test-runner-attachments');
10
+
11
+ function buildRuntimeId(prefix) {
12
+ if (typeof crypto.randomUUID === 'function') {
13
+ return `${prefix}-${crypto.randomUUID()}`;
14
+ }
15
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
16
+ }
17
+
18
+ function applyOptionalStringField(payload, value, key) {
19
+ if (typeof value === 'string' && value.trim()) {
20
+ payload[key] = value.trim();
21
+ }
22
+ }
23
+
24
+ function resolveInputObject(testCase) {
25
+ const input = testCase && typeof testCase.input === 'object' && testCase.input ? testCase.input : {};
26
+ return Array.isArray(input) ? {} : input;
27
+ }
28
+
29
+ function resolveInputMessage(testCase) {
30
+ const input = resolveInputObject(testCase);
31
+ const candidates = [input.message, input.text, input.prompt];
32
+ for (const row of candidates) {
33
+ if (typeof row === 'string' && row.trim()) {
34
+ return row.trim();
35
+ }
36
+ }
37
+ return '';
38
+ }
39
+
40
+ function applyStartPayloadOptionalFields(payload, input) {
41
+ const mappedFields = [
42
+ ['activationId', 'activationId'],
43
+ ['workerTaskId', 'workerTaskId'],
44
+ ['principalRef', 'principalRef'],
45
+ ['businessRoleRef', 'businessRoleRef']
46
+ ];
47
+ for (const [inputKey, payloadKey] of mappedFields) {
48
+ applyOptionalStringField(payload, input[inputKey], payloadKey);
49
+ }
50
+ }
51
+
52
+ function buildStartPayloadSourceMetadata(roleKey, testCase, input) {
53
+ return {
54
+ roleKey,
55
+ caseId: testCase.caseId,
56
+ runner: 'aifabrix-role-assistant-test',
57
+ ...(input.sourceMetadata && typeof input.sourceMetadata === 'object' ? input.sourceMetadata : {})
58
+ };
59
+ }
60
+
61
+ function buildStartIntentPayload(roleKey, testCase) {
62
+ const input = resolveInputObject(testCase);
63
+ const message = resolveInputMessage(testCase);
64
+ const hasContent = typeof input.content !== 'undefined';
65
+ if (!hasContent && !message) {
66
+ throw new Error(
67
+ `Role assistant test case "${testCase.caseId}" is missing input.message or input.content for runtime execution.`
68
+ );
69
+ }
70
+ const payload = {
71
+ submissionId: String(input.submissionId || buildRuntimeId(`ra-${roleKey}`)),
72
+ content: hasContent ? input.content : message,
73
+ comModelRef: String(input.comModelRef || 'aifabrix.com/intent/default'),
74
+ comModelVersion: String(input.comModelVersion || '1.0'),
75
+ comVocabulary:
76
+ Array.isArray(input.comVocabulary) && input.comVocabulary.length > 0
77
+ ? input.comVocabulary.map((row) => String(row))
78
+ : ['objective', 'entity', 'action'],
79
+ sourceType: String(input.sourceType || 'api'),
80
+ sourceLanguage: String(input.sourceLanguage || 'en'),
81
+ responseLanguage: String(input.responseLanguage || input.sourceLanguage || 'en'),
82
+ sourceMetadata: buildStartPayloadSourceMetadata(roleKey, testCase, input)
83
+ };
84
+ applyStartPayloadOptionalFields(payload, input);
85
+ const attachments = buildStartAttachments(testCase);
86
+ if (attachments && attachments.length > 0) {
87
+ payload.attachments = attachments;
88
+ }
89
+ return payload;
90
+ }
91
+
92
+ module.exports = {
93
+ buildStartIntentPayload,
94
+ resolveInputObject,
95
+ resolveInputMessage
96
+ };
@@ -322,15 +322,17 @@ function synthesizeBidApplyAnswer(wait, prompt, kind, testCase) {
322
322
  const wantsBody = kind === 'missingRequestBody' || /missing required request body/i.test(prompt);
323
323
  const wantsPath = kind === 'missingPathParams' || /missing required path params/i.test(prompt);
324
324
  const bid = loadCaseBidDocument(testCase);
325
+ // Always attach the full BID for apply waits (path or body). Path-only responds
326
+ // that send only systemKey let CIP POST an empty definition → Invalid BID.
325
327
  if (!bid || (!wantsBody && !wantsPath)) {
326
328
  return undefined;
327
329
  }
328
- const out = wantsBody ? { integrationDefinition: bid } : {};
330
+ const out = { integrationDefinition: bid };
329
331
  const targetSystemKey = resolveTargetSystemKey(testCase);
330
332
  if (targetSystemKey) {
331
333
  out.systemKey = targetSystemKey;
332
334
  }
333
- return Object.keys(out).length > 0 ? out : undefined;
335
+ return out;
334
336
  }
335
337
 
336
338
  function synthesizeTargetSystemKeyAnswer(wait, prompt, kind, testCase) {
@@ -353,11 +355,17 @@ function synthesizeTargetSystemKeyAnswer(wait, prompt, kind, testCase) {
353
355
 
354
356
  function synthesizeWaitAnswer(wait, statusRow, objective, testCase) {
355
357
  const prompt = String(wait.prompt || statusRow?.message || '').trim();
358
+ const kind = String(wait.objectRef?.kind || '').trim();
359
+ // BID apply before field/path synthesis — "(fields: systemKey)" must not
360
+ // drop integrationDefinition and let CIP POST an empty BID.
361
+ const bidAnswer = synthesizeBidApplyAnswer(wait, prompt, kind, testCase);
362
+ if (bidAnswer !== undefined) {
363
+ return bidAnswer;
364
+ }
356
365
  const fieldAnswer = synthesizeFieldAnswer(wait, statusRow, objective, testCase);
357
366
  if (fieldAnswer !== undefined) {
358
367
  return fieldAnswer;
359
368
  }
360
- const kind = String(wait.objectRef?.kind || '').trim();
361
369
  const isMissingInput =
362
370
  kind === 'missingRequestBody' ||
363
371
  kind === 'missingPathParams' ||
@@ -369,10 +377,6 @@ function synthesizeWaitAnswer(wait, statusRow, objective, testCase) {
369
377
  if (pathAnswer !== undefined) {
370
378
  return pathAnswer;
371
379
  }
372
- const bidAnswer = synthesizeBidApplyAnswer(wait, prompt, kind, testCase);
373
- if (bidAnswer !== undefined) {
374
- return bidAnswer;
375
- }
376
380
  const targetAnswer = synthesizeTargetSystemKeyAnswer(wait, prompt, kind, testCase);
377
381
  return targetAnswer || synthesizeFromCapability(resolveWaitCapabilityText(wait), extractObjectiveHints(objective));
378
382
  }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Shared Work Hub test-runner helpers (ids, delays, path detection).
3
+ * @fileoverview Pure helpers for role-assistant Work Hub harness
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const TERMINAL_TASK = new Set(['completed', 'failed', 'cancelled', 'canceled']);
9
+ const PLAN_LOST_RE =
10
+ /work plan not found|plan not found|404|ephemeral|reload|timed out|timeout|network error|econnreset|socket|fetch failed/i;
11
+ const APPROVAL_NOT_PENDING_RE = /approval state is not pending|not pending/i;
12
+
13
+ function delayMs(ms) {
14
+ const wait = Number(ms);
15
+ if (!Number.isFinite(wait) || wait <= 0) {
16
+ return Promise.resolve();
17
+ }
18
+ return new Promise((resolve) => setTimeout(resolve, wait));
19
+ }
20
+
21
+ function resolveInput(testCase) {
22
+ const input = testCase && typeof testCase.input === 'object' && testCase.input ? testCase.input : {};
23
+ return Array.isArray(input) ? {} : input;
24
+ }
25
+
26
+ function resolveObjective(testCase) {
27
+ const input = resolveInput(testCase);
28
+ const candidates = [input.message, input.text, input.prompt, input.objective];
29
+ for (const row of candidates) {
30
+ if (typeof row === 'string' && row.trim()) {
31
+ return row.trim();
32
+ }
33
+ }
34
+ return '';
35
+ }
36
+
37
+ function wantsWorkHubPath(testCase) {
38
+ const input = resolveInput(testCase);
39
+ const pathToken = String(input.executionPath || input.mode || '').trim().toLowerCase();
40
+ if (pathToken === 'workhub' || pathToken === 'work-hub' || pathToken === 'plan') {
41
+ return true;
42
+ }
43
+ if (pathToken === 'work.start' || pathToken === 'start' || pathToken === 'intent') {
44
+ return false;
45
+ }
46
+ return Boolean(resolveObjective(testCase));
47
+ }
48
+
49
+ function unwrapId(value) {
50
+ if (typeof value === 'string' && value.trim()) {
51
+ return value.trim();
52
+ }
53
+ if (value && typeof value === 'object') {
54
+ const nested = value.id || value.value || value.ref;
55
+ if (typeof nested === 'string' && nested.trim()) {
56
+ return nested.trim();
57
+ }
58
+ }
59
+ return '';
60
+ }
61
+
62
+ function extractPeFromTask(task) {
63
+ const candidates = [task?.processExecutionId, task?.processExecutionRef];
64
+ for (const row of candidates) {
65
+ const id = unwrapId(row);
66
+ if (id) {
67
+ return id;
68
+ }
69
+ }
70
+ return '';
71
+ }
72
+
73
+ function planAlreadyActivated(plan) {
74
+ const assistant = plan?.recommendedAssistant;
75
+ if (!assistant || typeof assistant !== 'object' || assistant.isActivated !== true) {
76
+ return false;
77
+ }
78
+ return Boolean(unwrapId(assistant.activationId));
79
+ }
80
+
81
+ function isPlanLostError(error) {
82
+ return PLAN_LOST_RE.test(String((error && error.message) || error || ''));
83
+ }
84
+
85
+ function isApprovalNotPendingError(error) {
86
+ return APPROVAL_NOT_PENDING_RE.test(String((error && error.message) || error || ''));
87
+ }
88
+
89
+ module.exports = {
90
+ TERMINAL_TASK,
91
+ delayMs,
92
+ resolveInput,
93
+ resolveObjective,
94
+ wantsWorkHubPath,
95
+ unwrapId,
96
+ extractPeFromTask,
97
+ planAlreadyActivated,
98
+ isPlanLostError,
99
+ isApprovalNotPendingError
100
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Soft Work Hub outcomes for insufficient-info / clarify-first Intent.
3
+ * Soft cases may stop before accept when Intent is not Act-ready — that is a
4
+ * governed fail-closed outcome, not a harness failure.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ function isIntentNotActReadyError(error) {
10
+ const msg = String((error && error.message) || error || '');
11
+ return /not Act-ready|intentNotActReady/i.test(msg);
12
+ }
13
+
14
+ function attachPlanIdToError(error, planId) {
15
+ if (error && typeof error === 'object' && planId) {
16
+ error.planId = planId;
17
+ }
18
+ return error;
19
+ }
20
+
21
+ function buildSoftClarifyLifecycleResult(roleKey, testCase, planId, error) {
22
+ const message = String((error && error.message) || error || 'intentNotActReady');
23
+ return {
24
+ roleKey,
25
+ caseId: testCase.caseId,
26
+ processExecutionId: null,
27
+ startResponse: {
28
+ planId: planId || null,
29
+ path: 'workHub',
30
+ softClarifyStop: true
31
+ },
32
+ finalStatus: {
33
+ status: 'stopped',
34
+ wait: null,
35
+ reason: 'intentNotActReady'
36
+ },
37
+ timeline: [],
38
+ interactions: [{ type: 'softClarifyStop', message }],
39
+ workResult: null,
40
+ workResultError: null
41
+ };
42
+ }
43
+
44
+ /**
45
+ * @param {Function} createActivateAcceptPlan
46
+ * @param {Object} startArgs
47
+ * @param {string} startArgs.dataplaneUrl
48
+ * @param {Object} startArgs.authConfig
49
+ * @param {Object} startArgs.planBody
50
+ * @param {string} startArgs.assistantName
51
+ * @param {Object} [startArgs.options]
52
+ * @param {Object} softCtx
53
+ * @param {string} softCtx.roleKey
54
+ * @param {Object} softCtx.testCase
55
+ */
56
+ async function startWorkHubOrSoftClarify(createActivateAcceptPlan, startArgs, softCtx) {
57
+ const { dataplaneUrl, authConfig, planBody, assistantName, options } = startArgs;
58
+ const { roleKey, testCase } = softCtx;
59
+ try {
60
+ return {
61
+ soft: false,
62
+ started: await createActivateAcceptPlan(
63
+ dataplaneUrl,
64
+ authConfig,
65
+ planBody,
66
+ assistantName,
67
+ options
68
+ )
69
+ };
70
+ } catch (error) {
71
+ if (testCase?.expected?.soft === true && isIntentNotActReadyError(error)) {
72
+ return {
73
+ soft: true,
74
+ result: buildSoftClarifyLifecycleResult(roleKey, testCase, error.planId, error)
75
+ };
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ module.exports = {
82
+ isIntentNotActReadyError,
83
+ attachPlanIdToError,
84
+ buildSoftClarifyLifecycleResult,
85
+ startWorkHubOrSoftClarify
86
+ };
@@ -25,89 +25,23 @@ const {
25
25
  } = require('./test-runner-workhub-answers');
26
26
  const {
27
27
  shouldApplyBidAfterWait,
28
- applyCaseBidViaBuilderApi
28
+ shouldSoftHttpBidApply,
29
+ applyCaseBidViaBuilderApi,
30
+ persistCaseOpenApiViaBuilderApi
29
31
  } = require('./test-runner-bid-apply');
30
-
31
- const TERMINAL_TASK = new Set(['completed', 'failed', 'cancelled', 'canceled']);
32
- const PLAN_LOST_RE =
33
- /work plan not found|plan not found|404|ephemeral|reload|timed out|timeout|network error|econnreset|socket|fetch failed/i;
34
- const APPROVAL_NOT_PENDING_RE = /approval state is not pending|not pending/i;
35
-
36
- function delayMs(ms) {
37
- const wait = Number(ms);
38
- if (!Number.isFinite(wait) || wait <= 0) {
39
- return Promise.resolve();
40
- }
41
- return new Promise((resolve) => setTimeout(resolve, wait));
42
- }
43
-
44
- function resolveInput(testCase) {
45
- const input = testCase && typeof testCase.input === 'object' && testCase.input ? testCase.input : {};
46
- return Array.isArray(input) ? {} : input;
47
- }
48
-
49
- function resolveObjective(testCase) {
50
- const input = resolveInput(testCase);
51
- const candidates = [input.message, input.text, input.prompt, input.objective];
52
- for (const row of candidates) {
53
- if (typeof row === 'string' && row.trim()) {
54
- return row.trim();
55
- }
56
- }
57
- return '';
58
- }
59
-
60
- function wantsWorkHubPath(testCase) {
61
- const input = resolveInput(testCase);
62
- const pathToken = String(input.executionPath || input.mode || '').trim().toLowerCase();
63
- if (pathToken === 'workhub' || pathToken === 'work-hub' || pathToken === 'plan') {
64
- return true;
65
- }
66
- if (pathToken === 'work.start' || pathToken === 'start' || pathToken === 'intent') {
67
- return false;
68
- }
69
- return Boolean(resolveObjective(testCase));
70
- }
71
-
72
- function unwrapId(value) {
73
- if (typeof value === 'string' && value.trim()) {
74
- return value.trim();
75
- }
76
- if (value && typeof value === 'object') {
77
- const nested = value.id || value.value || value.ref;
78
- if (typeof nested === 'string' && nested.trim()) {
79
- return nested.trim();
80
- }
81
- }
82
- return '';
83
- }
84
-
85
- function extractPeFromTask(task) {
86
- const candidates = [task?.processExecutionId, task?.processExecutionRef];
87
- for (const row of candidates) {
88
- const id = unwrapId(row);
89
- if (id) {
90
- return id;
91
- }
92
- }
93
- return '';
94
- }
95
-
96
- function planAlreadyActivated(plan) {
97
- const assistant = plan?.recommendedAssistant;
98
- if (!assistant || typeof assistant !== 'object' || assistant.isActivated !== true) {
99
- return false;
100
- }
101
- return Boolean(unwrapId(assistant.activationId));
102
- }
103
-
104
- function isPlanLostError(error) {
105
- return PLAN_LOST_RE.test(String((error && error.message) || error || ''));
106
- }
107
-
108
- function isApprovalNotPendingError(error) {
109
- return APPROVAL_NOT_PENDING_RE.test(String((error && error.message) || error || ''));
110
- }
32
+ const { resolveTargetSystemKey } = require('./test-runner-bid-document');
33
+ const {
34
+ TERMINAL_TASK,
35
+ delayMs,
36
+ resolveInput,
37
+ resolveObjective,
38
+ wantsWorkHubPath,
39
+ unwrapId,
40
+ extractPeFromTask,
41
+ planAlreadyActivated,
42
+ isPlanLostError,
43
+ isApprovalNotPendingError
44
+ } = require('./test-runner-workhub-helpers');
111
45
 
112
46
  async function waitDataplaneHealthy(dataplaneUrl, attempts = 12) {
113
47
  const base = String(dataplaneUrl || '')
@@ -153,11 +87,18 @@ async function acceptPlanForTask(dataplaneUrl, authConfig, planId) {
153
87
  }
154
88
 
155
89
  async function createActivateAcceptOnce(dataplaneUrl, authConfig, planBody, assistantName) {
90
+ const {
91
+ attachPlanIdToError
92
+ } = require('./test-runner-workhub-soft');
156
93
  await waitDataplaneHealthy(dataplaneUrl);
157
94
  const plan = await createWorkTaskPlan(dataplaneUrl, authConfig, planBody);
158
95
  const planId = await activateIfNeeded(dataplaneUrl, authConfig, plan, assistantName);
159
- const taskId = await acceptPlanForTask(dataplaneUrl, authConfig, planId);
160
- return { planId, taskId };
96
+ try {
97
+ const taskId = await acceptPlanForTask(dataplaneUrl, authConfig, planId);
98
+ return { planId, taskId };
99
+ } catch (error) {
100
+ throw attachPlanIdToError(error, planId);
101
+ }
161
102
  }
162
103
 
163
104
  async function createActivateAcceptPlan(dataplaneUrl, authConfig, planBody, assistantName, options) {
@@ -249,19 +190,23 @@ function recordSkippedResponse(interactions, peId, fingerprint, wait) {
249
190
  });
250
191
  }
251
192
 
252
- async function applyBidAfterWait(authConfig, testCase, interactions, peId) {
193
+ function recordBidEmit(testCase, interactions, peId) {
194
+ const { loadCaseBidDocument } = require('./test-runner-workhub-answers');
195
+ const emittedBid = loadCaseBidDocument(testCase);
196
+ if (!(emittedBid && emittedBid.meta && emittedBid.meta.suiteRunId)) {
197
+ return;
198
+ }
199
+ interactions.push({
200
+ type: 'bid-emit',
201
+ processExecutionId: peId,
202
+ suiteRunId: emittedBid.meta.suiteRunId,
203
+ definitionId: emittedBid.definitionId,
204
+ definitionHash: emittedBid.definitionHash
205
+ });
206
+ }
207
+
208
+ async function softHttpBidApplyAfterWait(authConfig, testCase, interactions, peId) {
253
209
  try {
254
- const { loadCaseBidDocument } = require('./test-runner-workhub-answers');
255
- const emittedBid = loadCaseBidDocument(testCase);
256
- if (emittedBid && emittedBid.meta && emittedBid.meta.suiteRunId) {
257
- interactions.push({
258
- type: 'bid-emit',
259
- processExecutionId: peId,
260
- suiteRunId: emittedBid.meta.suiteRunId,
261
- definitionId: emittedBid.definitionId,
262
- definitionHash: emittedBid.definitionHash
263
- });
264
- }
265
210
  const applied = await applyCaseBidViaBuilderApi(authConfig, testCase);
266
211
  interactions.push({
267
212
  type: 'bid-apply',
@@ -269,13 +214,15 @@ async function applyBidAfterWait(authConfig, testCase, interactions, peId) {
269
214
  systemKey: applied && applied.systemKey,
270
215
  definitionHash: applied && applied.definitionHash,
271
216
  suiteRunId: applied && applied.suiteRunId,
272
- status: applied && applied.status
217
+ status: applied && applied.status,
218
+ softHttp: true
273
219
  });
274
220
  } catch (applyErr) {
275
221
  interactions.push({
276
222
  type: 'bid-apply-error',
277
223
  processExecutionId: peId,
278
- error: applyErr.message || String(applyErr)
224
+ error: applyErr.message || String(applyErr),
225
+ softHttp: true
279
226
  });
280
227
  }
281
228
  }
@@ -305,11 +252,24 @@ async function respondToWaitingExecution(
305
252
  recordSkippedResponse(interactions, peId, fingerprint, wait);
306
253
  return;
307
254
  }
255
+ // Stage suite OpenAPI into workspace before CIP apply (517.2 layout).
256
+ // Product path: DE admits persistsuppliedopenapi; this is lab debt until then.
257
+ if (shouldApplyBidAfterWait(testCase, wait)) {
258
+ const systemKey = resolveTargetSystemKey(testCase);
259
+ if (systemKey) {
260
+ await persistCaseOpenApiViaBuilderApi(authConfig, testCase, systemKey);
261
+ }
262
+ }
308
263
  await respondWorkExecution(dataplaneUrl, authConfig, peId, payload);
309
264
  await continueWorkExecution(dataplaneUrl, authConfig, peId, {});
310
265
  interactions.push({ type: 'respond', processExecutionId: peId, fingerprint });
311
266
  if (shouldApplyBidAfterWait(testCase, wait)) {
312
- await applyBidAfterWait(authConfig, testCase, interactions, peId);
267
+ recordBidEmit(testCase, interactions, peId);
268
+ }
269
+ // Soft HTTP apply is lab debt only. Hard cases (expected.soft !== true) rely on
270
+ // DE → capability.execute(template.applyintegrationdefinition) after continue.
271
+ if (shouldSoftHttpBidApply(testCase, wait)) {
272
+ await softHttpBidApplyAfterWait(authConfig, testCase, interactions, peId);
313
273
  }
314
274
  }
315
275
 
@@ -429,6 +389,7 @@ function buildWorkHubLifecycleResult(roleKey, testCase, started, pollState, peBu
429
389
  }
430
390
 
431
391
  async function runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, options = {}) {
392
+ const { startWorkHubOrSoftClarify } = require('./test-runner-workhub-soft');
432
393
  const dataplaneUrl = runtimeContext?.dataplaneUrl;
433
394
  const authConfig = runtimeContext?.authConfig;
434
395
  const objective = resolveObjective(testCase);
@@ -445,13 +406,15 @@ async function runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, option
445
406
  objective,
446
407
  digitalWorkerKey: String(input.digitalWorkerKey || roleKey).trim()
447
408
  };
448
- const started = await createActivateAcceptPlan(
449
- dataplaneUrl,
450
- authConfig,
451
- planBody,
452
- assistantName,
453
- options
409
+ const gate = await startWorkHubOrSoftClarify(
410
+ createActivateAcceptPlan,
411
+ { dataplaneUrl, authConfig, planBody, assistantName, options },
412
+ { roleKey, testCase }
454
413
  );
414
+ if (gate.soft) {
415
+ return gate.result;
416
+ }
417
+ const started = gate.started;
455
418
  const pollState = await pollWorkHubTask(
456
419
  dataplaneUrl,
457
420
  authConfig,
@@ -18,6 +18,11 @@ const {
18
18
  getWorkExecutionResult
19
19
  } = require('../api/work.api');
20
20
  const { wantsWorkHubPath, runWorkHubCaseLifecycle } = require('./test-runner-workhub');
21
+ const {
22
+ buildStartIntentPayload,
23
+ resolveInputObject,
24
+ resolveInputMessage
25
+ } = require('./test-runner-start-payload');
21
26
  const TERMINAL_STATUSES = new Set(['completed', 'stopped']);
22
27
  const WAITING_STATUS = 'waiting';
23
28
 
@@ -52,22 +57,6 @@ function applyOptionalStringField(payload, value, key) {
52
57
  }
53
58
  }
54
59
 
55
- function resolveInputObject(testCase) {
56
- const input = testCase && typeof testCase.input === 'object' && testCase.input ? testCase.input : {};
57
- return Array.isArray(input) ? {} : input;
58
- }
59
-
60
- function resolveInputMessage(testCase) {
61
- const input = resolveInputObject(testCase);
62
- const candidates = [input.message, input.text, input.prompt];
63
- for (const row of candidates) {
64
- if (typeof row === 'string' && row.trim()) {
65
- return row.trim();
66
- }
67
- }
68
- return '';
69
- }
70
-
71
60
  function isExecutableRoleAssistantCase(testCase) {
72
61
  const input = resolveInputObject(testCase);
73
62
  if (typeof input.content !== 'undefined') {
@@ -79,53 +68,6 @@ function isExecutableRoleAssistantCase(testCase) {
79
68
  return false;
80
69
  }
81
70
 
82
- function buildStartPayloadSourceMetadata(roleKey, testCase, input) {
83
- return {
84
- roleKey,
85
- caseId: testCase.caseId,
86
- runner: 'aifabrix-role-assistant-test',
87
- ...(input.sourceMetadata && typeof input.sourceMetadata === 'object' ? input.sourceMetadata : {})
88
- };
89
- }
90
-
91
- function applyStartPayloadOptionalFields(payload, input) {
92
- const mappedFields = [
93
- ['activationId', 'activationId'],
94
- ['workerTaskId', 'workerTaskId'],
95
- ['principalRef', 'principalRef'],
96
- ['businessRoleRef', 'businessRoleRef']
97
- ];
98
- for (const [inputKey, payloadKey] of mappedFields) {
99
- applyOptionalStringField(payload, input[inputKey], payloadKey);
100
- }
101
- }
102
-
103
- function buildStartIntentPayload(roleKey, testCase) {
104
- const input = resolveInputObject(testCase);
105
- const message = resolveInputMessage(testCase);
106
- const hasContent = typeof input.content !== 'undefined';
107
- if (!hasContent && !message) {
108
- throw new Error(
109
- `Role assistant test case "${testCase.caseId}" is missing input.message or input.content for runtime execution.`
110
- );
111
- }
112
- const payload = {
113
- submissionId: String(input.submissionId || buildRuntimeId(`ra-${roleKey}`)),
114
- content: hasContent ? input.content : message,
115
- comModelRef: String(input.comModelRef || 'aifabrix.com/intent/default'),
116
- comModelVersion: String(input.comModelVersion || '1.0'),
117
- comVocabulary: Array.isArray(input.comVocabulary) && input.comVocabulary.length > 0
118
- ? input.comVocabulary.map((row) => String(row))
119
- : ['objective', 'entity', 'action'],
120
- sourceType: String(input.sourceType || 'api'),
121
- sourceLanguage: String(input.sourceLanguage || 'en'),
122
- responseLanguage: String(input.responseLanguage || input.sourceLanguage || 'en'),
123
- sourceMetadata: buildStartPayloadSourceMetadata(roleKey, testCase, input)
124
- };
125
- applyStartPayloadOptionalFields(payload, input);
126
- return payload;
127
- }
128
-
129
71
  function extractProcessExecutionId(startResponse) {
130
72
  const candidates = [
131
73
  startResponse?.intent?.processExecutionId,