@aifabrix/builder 2.53.0 → 2.54.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 (321) hide show
  1. package/bin/aifabrix.js +2 -2
  2. package/lib/api/assistants.api.js +8 -5
  3. package/lib/api/channel-identity.api.js +41 -0
  4. package/lib/api/evidence-authoring.api.js +5 -1
  5. package/lib/api/evidence.api.js +2 -0
  6. package/lib/api/types/evidence.types.js +2 -0
  7. package/lib/api/work-search.api.js +136 -0
  8. package/lib/channels/add-channel.js +163 -126
  9. package/lib/channels/approval-guides/chatgpt.js +14 -18
  10. package/lib/channels/approval-guides/claude.js +29 -0
  11. package/lib/channels/approval-guides/index.js +88 -22
  12. package/lib/channels/approval-guides/mcp-client.js +27 -0
  13. package/lib/channels/approval-guides/microsoft-copilot.js +52 -3
  14. package/lib/channels/approval-guides/slack.js +10 -10
  15. package/lib/channels/approval-guides/teams-bot.js +8 -12
  16. package/lib/channels/channel-add-continuations.js +91 -0
  17. package/lib/channels/channel-add-ensure.js +124 -27
  18. package/lib/channels/channel-add-existing.js +160 -28
  19. package/lib/channels/channel-add-failure.js +60 -0
  20. package/lib/channels/channel-add-remaining.js +486 -0
  21. package/lib/channels/channel-add-tty.js +190 -0
  22. package/lib/channels/channel-add-wizard-config.js +138 -0
  23. package/lib/channels/channel-add-wizard.js +250 -0
  24. package/lib/channels/channel-artifact-generator.js +27 -13
  25. package/lib/channels/channel-cardinality.js +83 -0
  26. package/lib/channels/channel-catalog-icon.js +45 -0
  27. package/lib/channels/channel-cli-instructions.js +75 -0
  28. package/lib/channels/channel-configuration-map.js +213 -0
  29. package/lib/channels/channel-credential.js +11 -14
  30. package/lib/channels/channel-definition-project.js +101 -0
  31. package/lib/channels/channel-definition-registry.js +154 -0
  32. package/lib/channels/channel-description.js +59 -2
  33. package/lib/channels/channel-discovery.js +16 -2
  34. package/lib/channels/channel-external-system.js +86 -9
  35. package/lib/channels/channel-install-envelope-actions.js +88 -0
  36. package/lib/channels/channel-install-envelope-microsoft.js +155 -0
  37. package/lib/channels/channel-install-envelope.js +356 -0
  38. package/lib/channels/channel-install-help-md.js +172 -0
  39. package/lib/channels/channel-install-http-payload.js +62 -0
  40. package/lib/channels/channel-install-inventory.js +115 -0
  41. package/lib/channels/channel-install-profiles.js +90 -0
  42. package/lib/channels/channel-instance-collision.js +118 -0
  43. package/lib/channels/channel-instance-key.js +100 -0
  44. package/lib/channels/channel-instance-materialize.js +240 -0
  45. package/lib/channels/channel-integration-generator.js +16 -9
  46. package/lib/channels/channel-integration-template.js +101 -22
  47. package/lib/channels/channel-inventory-fetch.js +79 -0
  48. package/lib/channels/channel-oauth-preflight.js +104 -0
  49. package/lib/channels/channel-package-version.js +76 -0
  50. package/lib/channels/channel-predefined-client.js +154 -0
  51. package/lib/channels/channel-probe.js +7 -2
  52. package/lib/channels/channel-runtime-resolver.js +180 -72
  53. package/lib/channels/channel-status-display.js +86 -16
  54. package/lib/channels/channel-targets.js +106 -92
  55. package/lib/channels/chatgpt-callback.js +318 -0
  56. package/lib/channels/chatgpt-channel-add.js +229 -0
  57. package/lib/channels/chatgpt-preflight.js +396 -0
  58. package/lib/channels/chatgpt-runtime-contract.js +17 -0
  59. package/lib/channels/chatgpt-setup-values.js +124 -0
  60. package/lib/channels/claude-callback.js +67 -0
  61. package/lib/channels/claude-channel-add.js +254 -0
  62. package/lib/channels/claude-runtime-contract.js +40 -0
  63. package/lib/channels/claude-runtime.js +58 -0
  64. package/lib/channels/claude-setup-values.js +89 -0
  65. package/lib/channels/mcp-client/config-merge.js +148 -0
  66. package/lib/channels/mcp-client/ensure-client.js +231 -0
  67. package/lib/channels/mcp-client/paths.js +53 -0
  68. package/lib/channels/mcp-client/profiles.js +122 -0
  69. package/lib/channels/mcp-client/redirect-uris.js +99 -0
  70. package/lib/channels/mcp-client/runtime.js +59 -0
  71. package/lib/channels/mcp-client-channel-add.js +186 -0
  72. package/lib/channels/microsoft-copilot-channel-add.js +117 -7
  73. package/lib/channels/microsoft-copilot-entra-identifier-uris.js +187 -0
  74. package/lib/channels/microsoft-copilot-entra-sso-form.js +106 -18
  75. package/lib/channels/microsoft-copilot-entra-sso.js +31 -2
  76. package/lib/channels/microsoft-copilot-package-prior.js +107 -0
  77. package/lib/channels/microsoft-copilot-package.js +29 -28
  78. package/lib/channels/microsoft-provisioning/a365.js +80 -0
  79. package/lib/channels/microsoft-provisioning/auth-config.js +240 -0
  80. package/lib/channels/microsoft-provisioning/auth.js +179 -0
  81. package/lib/channels/microsoft-provisioning/cli.js +71 -0
  82. package/lib/channels/microsoft-provisioning/constants.js +75 -0
  83. package/lib/channels/microsoft-provisioning/errors.js +117 -0
  84. package/lib/channels/microsoft-provisioning/index.js +55 -0
  85. package/lib/channels/microsoft-provisioning/install-atk.js +158 -0
  86. package/lib/channels/microsoft-provisioning/install-teams.js +115 -0
  87. package/lib/channels/microsoft-provisioning/login-portal-device.js +236 -0
  88. package/lib/channels/microsoft-provisioning/login-portal-parse.js +71 -0
  89. package/lib/channels/microsoft-provisioning/login-portal-teams.js +193 -0
  90. package/lib/channels/microsoft-provisioning/login-portal.js +230 -0
  91. package/lib/channels/microsoft-provisioning/persist.js +110 -0
  92. package/lib/channels/microsoft-provisioning/teams-bot-args.js +119 -0
  93. package/lib/channels/microsoft-provisioning/teams-bot-controller.js +123 -0
  94. package/lib/channels/microsoft-provisioning/teams-bot-parse.js +134 -0
  95. package/lib/channels/microsoft-provisioning/teams-bot-persist.js +113 -0
  96. package/lib/channels/microsoft-provisioning/teams-bot-provision.js +234 -0
  97. package/lib/channels/microsoft-provisioning/teams-bot-secrets.js +45 -0
  98. package/lib/channels/microsoft-provisioning/teams-cli.js +82 -0
  99. package/lib/channels/runtime-tool-catalog.js +5 -2
  100. package/lib/channels/slack-channel-add-portal.js +130 -0
  101. package/lib/channels/slack-channel-add-tty-auth.js +95 -0
  102. package/lib/channels/slack-channel-add.js +258 -0
  103. package/lib/channels/slack-provisioning/apps.js +181 -0
  104. package/lib/channels/slack-provisioning/auth-store.js +110 -0
  105. package/lib/channels/slack-provisioning/auth-ticket.js +185 -0
  106. package/lib/channels/slack-provisioning/auth.js +115 -0
  107. package/lib/channels/slack-provisioning/callbacks.js +41 -0
  108. package/lib/channels/slack-provisioning/cli.js +226 -0
  109. package/lib/channels/slack-provisioning/constants.js +37 -0
  110. package/lib/channels/slack-provisioning/credentials.js +169 -0
  111. package/lib/channels/slack-provisioning/curl-shim.js +132 -0
  112. package/lib/channels/slack-provisioning/developer-install.js +118 -0
  113. package/lib/channels/slack-provisioning/env-file.js +120 -0
  114. package/lib/channels/slack-provisioning/errors.js +47 -0
  115. package/lib/channels/slack-provisioning/go-ca-bundle.js +137 -0
  116. package/lib/channels/slack-provisioning/index.js +250 -0
  117. package/lib/channels/slack-provisioning/install-cli.js +323 -0
  118. package/lib/channels/slack-provisioning/manifest.js +89 -0
  119. package/lib/channels/slack-provisioning/oauth-lock.js +33 -0
  120. package/lib/channels/slack-provisioning/offer-cli.js +113 -0
  121. package/lib/channels/slack-provisioning/onboard-members.js +122 -0
  122. package/lib/channels/slack-provisioning/onboard.js +89 -0
  123. package/lib/channels/slack-provisioning/persist.js +95 -0
  124. package/lib/channels/slack-provisioning/remaining-urls.js +158 -0
  125. package/lib/channels/slack-provisioning/with-curl-on-path.js +48 -0
  126. package/lib/channels/teams-bot-channel-add.js +94 -0
  127. package/lib/channels/vendor-dataplane-url.js +249 -0
  128. package/lib/cli/setup-certification.help.js +2 -0
  129. package/lib/cli/setup-external-system-help.js +2 -0
  130. package/lib/cli/setup-external-system.js +1 -1
  131. package/lib/commands/channel-add-help.js +366 -0
  132. package/lib/commands/channel-add-setup.js +162 -80
  133. package/lib/commands/channel-configure.js +118 -0
  134. package/lib/commands/channel.js +10 -1
  135. package/lib/commands/governance-upload-external.js +6 -3
  136. package/lib/commands/lifecycle-command-action.js +2 -5
  137. package/lib/commands/lifecycle-external.js +93 -21
  138. package/lib/commands/repair-datasource-test-payload.js +4 -2
  139. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +23 -2
  140. package/lib/commands/repair-datasource.js +3 -0
  141. package/lib/commands/repair-external-run.js +59 -25
  142. package/lib/commands/repair-openapi-rest-runtime.js +127 -0
  143. package/lib/commands/repair-orchestration-tag-merge.js +339 -0
  144. package/lib/commands/repair-rbac-catalog-defaults.js +96 -0
  145. package/lib/commands/repair-rbac-platform-preset.js +26 -15
  146. package/lib/commands/repair-rbac-reader-roles.js +8 -21
  147. package/lib/commands/repair-rbac.js +10 -34
  148. package/lib/commands/repair-system-permissions.js +24 -22
  149. package/lib/commands/role-assistant-test-output.js +14 -0
  150. package/lib/commands/role-assistant.js +59 -21
  151. package/lib/commands/test-e2e-external.js +148 -21
  152. package/lib/commands/upload-credential-push-log.js +2 -1
  153. package/lib/commands/upload-publish-context.js +1 -1
  154. package/lib/commands/upload-role-assistant-shell.js +124 -0
  155. package/lib/commands/upload-sidecar-followups.js +6 -3
  156. package/lib/commands/upload.js +18 -3
  157. package/lib/core/secrets-docker-env.js +4 -1
  158. package/lib/core/secrets-env-content.js +17 -3
  159. package/lib/datasource/capability/capability-manifest.js +0 -3
  160. package/lib/datasource/capability/copy-test-payload.js +3 -0
  161. package/lib/external-system/deploy-helpers.js +26 -1
  162. package/lib/external-system/deploy.js +10 -6
  163. package/lib/external-system/download-helpers.js +29 -2
  164. package/lib/external-system/download-role-assistant.js +83 -4
  165. package/lib/external-system/download.js +40 -30
  166. package/lib/external-system/sync-deploy-manifest.js +3 -2
  167. package/lib/external-system/upload-identity.js +42 -0
  168. package/lib/generator/index.js +19 -15
  169. package/lib/generator/split.js +13 -2
  170. package/lib/lifecycle/role-assistant-governance.js +146 -0
  171. package/lib/lifecycle/scenario-upload.js +181 -30
  172. package/lib/programmatic/builder-api-cors.js +143 -0
  173. package/lib/programmatic/builder-api-request.js +6 -0
  174. package/lib/programmatic/builder-api-route-dispatch.js +56 -42
  175. package/lib/programmatic/builder-api-server.js +8 -1
  176. package/lib/programmatic/capability-registry.js +54 -0
  177. package/lib/programmatic/channel-install-help-resolve.js +359 -0
  178. package/lib/programmatic/channel-install-session.js +218 -0
  179. package/lib/programmatic/help-content/channel-onboarding.md +2 -0
  180. package/lib/programmatic/help-content/cip-overview.md +3 -1
  181. package/lib/programmatic/http-raw-response.js +62 -0
  182. package/lib/programmatic/openapi-channel-install-bindings.js +179 -0
  183. package/lib/programmatic/openapi-channel-install-schemas.js +286 -0
  184. package/lib/programmatic/openapi-component-schemas.js +18 -1
  185. package/lib/programmatic/openapi-description-enricher.js +3 -1
  186. package/lib/programmatic/openapi-descriptions.js +74 -3
  187. package/lib/programmatic/openapi-envelope-examples.js +117 -0
  188. package/lib/programmatic/openapi-operation-bindings.js +4 -1
  189. package/lib/programmatic/openapi-schema-descriptions-channel.js +316 -0
  190. package/lib/programmatic/openapi-spec.js +12 -8
  191. package/lib/programmatic/platform-rbac-catalog.js +36 -18
  192. package/lib/programmatic/route-handler-map.js +51 -1
  193. package/lib/programmatic/route-handlers-context.js +2 -1
  194. package/lib/programmatic/run-auth-permissions.js +54 -0
  195. package/lib/programmatic/run-channel-catalog.js +196 -0
  196. package/lib/programmatic/run-channel-install-artifacts.js +137 -0
  197. package/lib/programmatic/run-channel-install-copilot.js +353 -0
  198. package/lib/programmatic/run-channel-install-options.js +232 -0
  199. package/lib/programmatic/run-channel-install-teams-bot.js +204 -0
  200. package/lib/programmatic/run-channel-install.js +346 -0
  201. package/lib/programmatic/run-channel-package.js +87 -7
  202. package/lib/programmatic/workspace-context.js +2 -0
  203. package/lib/role-assistant/evidence-sync.js +4 -2
  204. package/lib/role-assistant/evidence-validation-issues.js +51 -0
  205. package/lib/role-assistant/knowledge-sync.js +10 -4
  206. package/lib/role-assistant/settings.js +49 -2
  207. package/lib/role-assistant/test-cases-evaluate.js +30 -2
  208. package/lib/role-assistant/test-cases-evidence.js +175 -0
  209. package/lib/role-assistant/test-cases-search.js +229 -0
  210. package/lib/role-assistant/test-cases.js +37 -4
  211. package/lib/role-assistant/test-runner-search.js +149 -0
  212. package/lib/role-assistant/test-runner-workhub-helpers.js +40 -2
  213. package/lib/role-assistant/test-runner-workhub-soft.js +9 -3
  214. package/lib/role-assistant/test-runner-workhub.js +29 -11
  215. package/lib/role-assistant/test-runner.js +16 -1
  216. package/lib/schema/external-datasource.schema.json +119 -16
  217. package/lib/schema/external-system.schema.json +159 -2
  218. package/lib/schema/mcp-client-profile.schema.json +103 -0
  219. package/lib/utils/app-path-resolve.js +4 -0
  220. package/lib/utils/authentication-env-template.js +44 -6
  221. package/lib/utils/credential-secrets-env.js +6 -1
  222. package/lib/utils/env-copy.js +19 -7
  223. package/lib/utils/external-env-template.js +20 -2
  224. package/lib/utils/paths.js +18 -1
  225. package/lib/utils/platform-shell-package.js +69 -0
  226. package/lib/utils/port-resolver.js +50 -1
  227. package/lib/utils/rewrite-name-flag.js +20 -0
  228. package/lib/utils/role-assistant-paths.js +13 -10
  229. package/lib/utils/schema-resolver.js +11 -1
  230. package/lib/utils/secrets-helpers.js +12 -21
  231. package/lib/utils/token-manager.js +10 -5
  232. package/lib/validation/validate.js +40 -0
  233. package/package.json +1 -1
  234. package/templates/README.md +1 -1
  235. package/templates/applications/builder-api/Dockerfile +1 -1
  236. package/templates/applications/builder-api/env.template +7 -0
  237. package/templates/applications/dataplane/application.yaml +6 -5
  238. package/templates/applications/dataplane/env.template +2 -2
  239. package/templates/applications/dataplane/rbac.yaml +7 -2
  240. package/templates/applications/keycloak/Dockerfile +30 -4
  241. package/templates/applications/keycloak/application.yaml +2 -2
  242. package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +896 -0
  243. package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +19 -0
  244. package/templates/applications/keycloak/env.template +3 -0
  245. package/templates/applications/miso-controller/application.yaml +1 -2
  246. package/templates/applications/miso-controller/env.template +2 -0
  247. package/templates/channels/README.md +5 -5
  248. package/templates/channels/chatgpt/TROUBLESHOOTING.md +51 -0
  249. package/templates/channels/chatgpt/channel.json +59 -0
  250. package/templates/channels/chatgpt/icon.svg +1 -0
  251. package/templates/channels/chatgpt/install.template.md.hbs +20 -95
  252. package/templates/channels/chatgpt/integration/README.md +1 -1
  253. package/templates/channels/chatgpt/integration/application.json +2 -2
  254. package/templates/channels/chatgpt/integration/chatgpt-system.json +49 -4
  255. package/templates/channels/chatgpt/integration/env.template +2 -4
  256. package/templates/channels/chatgpt/integration/rbac.json +13 -0
  257. package/templates/channels/claude/TROUBLESHOOTING.md +41 -0
  258. package/templates/channels/claude/channel.json +59 -0
  259. package/templates/channels/claude/icon.svg +1 -0
  260. package/templates/channels/claude/install.template.md.hbs +21 -0
  261. package/templates/channels/claude/integration/README.md +14 -0
  262. package/templates/channels/claude/integration/application.json +15 -0
  263. package/templates/channels/claude/integration/claude-system.json +59 -0
  264. package/templates/channels/claude/integration/env.template +2 -0
  265. package/templates/channels/claude/integration/rbac.json +13 -0
  266. package/templates/channels/claude/manifest.template.json.hbs +40 -0
  267. package/templates/channels/mcp-client/channel.json +72 -0
  268. package/templates/channels/mcp-client/icon.svg +1 -0
  269. package/templates/channels/mcp-client/install.template.md.hbs +24 -0
  270. package/templates/channels/mcp-client/integration/README.md +13 -0
  271. package/templates/channels/mcp-client/integration/application.json +15 -0
  272. package/templates/channels/mcp-client/integration/env.template +2 -0
  273. package/templates/channels/mcp-client/integration/mcp-client-system.json +72 -0
  274. package/templates/channels/mcp-client/integration/rbac.json +13 -0
  275. package/templates/channels/mcp-client/manifest.template.json.hbs +41 -0
  276. package/templates/channels/mcp-client/profiles/cursor.json +26 -0
  277. package/templates/channels/mcp-client/profiles/github-copilot.json +29 -0
  278. package/templates/channels/mcp-client/profiles/vscode.json +29 -0
  279. package/templates/channels/microsoft-copilot/channel.json +73 -0
  280. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +8 -4
  281. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +65 -5
  282. package/templates/channels/microsoft-copilot/icon.svg +1 -0
  283. package/templates/channels/microsoft-copilot/install.template.md.hbs +9 -9
  284. package/templates/channels/microsoft-copilot/integration/README.md +1 -1
  285. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  286. package/templates/channels/microsoft-copilot/integration/env.template +3 -6
  287. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +63 -4
  288. package/templates/channels/microsoft-copilot/integration/rbac.json +13 -0
  289. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +5 -5
  290. package/templates/channels/slack/channel.json +223 -0
  291. package/templates/channels/slack/icon.svg +1 -0
  292. package/templates/channels/slack/install.template.md.hbs +36 -28
  293. package/templates/channels/slack/integration/README.md +245 -22
  294. package/templates/channels/slack/integration/application.json +44 -7
  295. package/templates/channels/slack/integration/env.template +15 -6
  296. package/templates/channels/slack/integration/openapi/openapi.json +126 -0
  297. package/templates/channels/slack/integration/rbac.json +84 -0
  298. package/templates/channels/slack/integration/slack-datasource-commands.json +154 -0
  299. package/templates/channels/slack/integration/slack-datasource-conversations.json +28 -3
  300. package/templates/channels/slack/integration/slack-datasource-events.json +154 -0
  301. package/templates/channels/slack/integration/slack-datasource-interactive.json +154 -0
  302. package/templates/channels/slack/integration/slack-datasource-messages.json +40 -2
  303. package/templates/channels/slack/integration/slack-system.json +155 -77
  304. package/templates/channels/slack/manifest.template.json.hbs +5 -2
  305. package/templates/channels/teams-bot/channel.json +161 -0
  306. package/templates/channels/teams-bot/icon.svg +1 -0
  307. package/templates/channels/teams-bot/install.template.md.hbs +12 -13
  308. package/templates/channels/teams-bot/integration/README.md +5 -4
  309. package/templates/channels/teams-bot/integration/application.json +6 -4
  310. package/templates/channels/teams-bot/integration/env.template +5 -4
  311. package/templates/channels/teams-bot/integration/openapi/openapi.json +68 -0
  312. package/templates/channels/teams-bot/integration/rbac.json +21 -0
  313. package/templates/channels/teams-bot/integration/teams-bot-datasource-activities.json +154 -0
  314. package/templates/channels/teams-bot/integration/teams-bot-system.json +128 -23
  315. package/templates/channels/web-embed/channel.json +60 -0
  316. package/templates/channels/web-embed/install.template.md.hbs +1 -1
  317. package/templates/channels/web-embed/integration/README.md +2 -2
  318. package/templates/channels/web-embed/integration/application.json +2 -2
  319. package/templates/channels/web-embed/integration/env.template +1 -1
  320. package/templates/channels/web-embed/integration/rbac.json +13 -0
  321. package/templates/channels/web-embed/integration/web-embed-system.json +50 -4
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Assertions for work.skills / work.search role-assistant cases.
3
+ *
4
+ * @fileoverview Evaluate expected.search and expected.skills against Layer B results
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { operationKeys } = require('./test-runner-search');
12
+
13
+ function asObject(value) {
14
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
15
+ }
16
+
17
+ function searchRows(payload) {
18
+ const body = asObject(payload);
19
+ if (Array.isArray(body.data)) {
20
+ return body.data;
21
+ }
22
+ return [];
23
+ }
24
+
25
+ function searchMeta(payload) {
26
+ const body = asObject(payload);
27
+ return asObject(body.meta);
28
+ }
29
+
30
+ function errorHaystack(searchObs) {
31
+ const payload = asObject(searchObs.payload);
32
+ return [
33
+ searchObs.errorText,
34
+ payload.error,
35
+ payload.detail,
36
+ payload.title,
37
+ payload.message
38
+ ]
39
+ .filter((row) => typeof row === 'string')
40
+ .join(' ');
41
+ }
42
+
43
+ function evaluateStatusCode(expectedCode, actualCode) {
44
+ if (!Number.isFinite(Number(expectedCode))) {
45
+ return null;
46
+ }
47
+ const expected = Number(expectedCode);
48
+ const actual = Number(actualCode);
49
+ const passed = actual === expected;
50
+ return {
51
+ id: 'expected.search.statusCode',
52
+ passed,
53
+ message: passed
54
+ ? `Search HTTP status matched ${expected}.`
55
+ : `Search HTTP status expected ${expected} but got ${actual}.`
56
+ };
57
+ }
58
+
59
+ function evaluateErrorContains(needle, haystack) {
60
+ if (typeof needle !== 'string' || !needle.trim()) {
61
+ return null;
62
+ }
63
+ const passed = String(haystack || '').toLowerCase().includes(needle.trim().toLowerCase());
64
+ return {
65
+ id: 'expected.search.errorContains',
66
+ passed,
67
+ message: passed
68
+ ? `Search error contained "${needle.trim()}".`
69
+ : `Search error did not contain "${needle.trim()}".`
70
+ };
71
+ }
72
+
73
+ function evaluateSearchShape(expectedSearch, searchObs) {
74
+ const assertions = [];
75
+ const meta = searchMeta(searchObs.payload);
76
+ const rows = searchRows(searchObs.payload);
77
+ if (expectedSearch.resultMode) {
78
+ const actual = String(meta.resultMode || '');
79
+ const passed = actual === String(expectedSearch.resultMode);
80
+ assertions.push({
81
+ id: 'expected.search.resultMode',
82
+ passed,
83
+ message: passed
84
+ ? `Search resultMode matched "${expectedSearch.resultMode}".`
85
+ : `Search resultMode expected "${expectedSearch.resultMode}" but got "${actual}".`
86
+ });
87
+ }
88
+ if (expectedSearch.operation) {
89
+ const actual = String(meta.operation || searchObs.arguments?.operation || '');
90
+ const passed = actual === String(expectedSearch.operation);
91
+ assertions.push({
92
+ id: 'expected.search.operation',
93
+ passed,
94
+ message: passed
95
+ ? `Search operation matched "${expectedSearch.operation}".`
96
+ : `Search operation expected "${expectedSearch.operation}" but got "${actual}".`
97
+ });
98
+ }
99
+ if (Number.isFinite(Number(expectedSearch.minRows))) {
100
+ const minRows = Number(expectedSearch.minRows);
101
+ const passed = rows.length >= minRows;
102
+ assertions.push({
103
+ id: 'expected.search.minRows',
104
+ passed,
105
+ message: passed
106
+ ? `Search returned at least ${minRows} row(s).`
107
+ : `Search returned ${rows.length} row(s); expected at least ${minRows}.`
108
+ });
109
+ }
110
+ return assertions;
111
+ }
112
+
113
+ function evaluateSearchExpectations(expectedSearch, observation) {
114
+ if (!expectedSearch || typeof expectedSearch !== 'object' || Array.isArray(expectedSearch)) {
115
+ return [];
116
+ }
117
+ const searchObs = asObject(observation.search);
118
+ const assertions = [];
119
+ const statusAssertion = evaluateStatusCode(expectedSearch.statusCode, searchObs.status);
120
+ if (statusAssertion) {
121
+ assertions.push(statusAssertion);
122
+ }
123
+ const errorAssertion = evaluateErrorContains(
124
+ expectedSearch.errorContains,
125
+ errorHaystack(searchObs)
126
+ );
127
+ if (errorAssertion) {
128
+ assertions.push(errorAssertion);
129
+ }
130
+ assertions.push(...evaluateSearchShape(expectedSearch, searchObs));
131
+ return assertions;
132
+ }
133
+
134
+ function skillsResourceEntry(skillsPayload, resourceType) {
135
+ const rts = asObject(asObject(skillsPayload).search).resourceTypes;
136
+ return asObject(asObject(rts)[resourceType]);
137
+ }
138
+
139
+ function evaluateSkillsOperations(expectedOps, spec) {
140
+ if (!Array.isArray(expectedOps) || expectedOps.length === 0) {
141
+ return [];
142
+ }
143
+ const observed = new Set(operationKeys(spec));
144
+ return expectedOps.map((operation) => {
145
+ const key = String(operation || '').trim();
146
+ const passed = observed.has(key);
147
+ return {
148
+ id: `expected.skills.operations:${key}`,
149
+ passed,
150
+ message: passed
151
+ ? `Search grammar published operation "${key}".`
152
+ : `Search grammar did not publish operation "${key}".`
153
+ };
154
+ });
155
+ }
156
+
157
+ function publishedFilterNames(spec) {
158
+ const names = new Set(Object.keys(asObject(spec.fields)));
159
+ const operations = Array.isArray(spec.operations) ? spec.operations : [];
160
+ for (const item of operations) {
161
+ const filters = item && typeof item === 'object' ? item.filters : null;
162
+ if (!Array.isArray(filters)) {
163
+ continue;
164
+ }
165
+ for (const name of filters) {
166
+ if (typeof name === 'string' && name.trim()) {
167
+ names.add(name.trim());
168
+ }
169
+ }
170
+ }
171
+ return names;
172
+ }
173
+
174
+ function evaluateSkillsFilterFields(expectedFields, spec) {
175
+ if (!Array.isArray(expectedFields) || expectedFields.length === 0) {
176
+ return [];
177
+ }
178
+ const published = publishedFilterNames(spec);
179
+ return expectedFields.map((name) => {
180
+ const key = String(name || '').trim();
181
+ const passed = Boolean(key && published.has(key));
182
+ return {
183
+ id: `expected.skills.filterFields:${key}`,
184
+ passed,
185
+ message: passed
186
+ ? `Search grammar published filter field "${key}".`
187
+ : `Search grammar did not publish filter field "${key}".`
188
+ };
189
+ });
190
+ }
191
+
192
+ function evaluateSkillsExpectations(expectedSkills, observation) {
193
+ if (!expectedSkills || typeof expectedSkills !== 'object' || Array.isArray(expectedSkills)) {
194
+ return [];
195
+ }
196
+ const skillsObs = asObject(observation.skills);
197
+ const payload = skillsObs.payload;
198
+ const resourceType = String(expectedSkills.resourceType || '').trim();
199
+ const spec = resourceType ? skillsResourceEntry(payload, resourceType) : {};
200
+ const assertions = [];
201
+ if (resourceType) {
202
+ const passed = Object.keys(spec).length > 0;
203
+ assertions.push({
204
+ id: 'expected.skills.resourceType',
205
+ passed,
206
+ message: passed
207
+ ? `work.skills published resourceType "${resourceType}".`
208
+ : `work.skills did not publish resourceType "${resourceType}".`
209
+ });
210
+ }
211
+ if (expectedSkills.operationRequired === true) {
212
+ const passed = spec.operationRequired === true;
213
+ assertions.push({
214
+ id: 'expected.skills.operationRequired',
215
+ passed,
216
+ message: passed
217
+ ? 'work.skills marked operationRequired for this resourceType.'
218
+ : 'work.skills did not mark operationRequired for this resourceType.'
219
+ });
220
+ }
221
+ assertions.push(...evaluateSkillsOperations(expectedSkills.operations, spec));
222
+ assertions.push(...evaluateSkillsFilterFields(expectedSkills.filterFields, spec));
223
+ return assertions;
224
+ }
225
+
226
+ module.exports = {
227
+ evaluateSearchExpectations,
228
+ evaluateSkillsExpectations
229
+ };
@@ -98,21 +98,54 @@ function normalizeCaseFromFile(filePath, parsed) {
98
98
  }
99
99
  return {
100
100
  caseId,
101
+ executionPath: parsed.executionPath,
102
+ roleRef: parsed.roleRef,
103
+ businessRoleRef: parsed.businessRoleRef,
101
104
  input: parsed.input || {},
102
105
  answers: Array.isArray(parsed.answers) ? parsed.answers : [],
103
106
  approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
104
107
  expected,
108
+ preflight: parsed.preflight && typeof parsed.preflight === 'object' ? parsed.preflight : {},
105
109
  sourceFile: filePath
106
110
  };
107
111
  }
108
112
 
109
- function listTestCaseFiles(testsDir) {
110
- if (!safeExistsSync(testsDir)) {
113
+ function listYamlFileNames(dirPath) {
114
+ if (!safeExistsSync(dirPath)) {
111
115
  return [];
112
116
  }
113
- return safeReaddirSync(testsDir)
117
+ return safeReaddirSync(dirPath)
114
118
  .filter((entry) => /\.(ya?ml)$/i.test(entry))
115
- .map((entry) => path.join(testsDir, entry));
119
+ .map((entry) => path.join(dirPath, entry));
120
+ }
121
+
122
+ function safeIsDirectory(dirPath) {
123
+ try {
124
+ if (nodeFs.existsSync(dirPath)) {
125
+ return nodeFs.statSync(dirPath).isDirectory();
126
+ }
127
+ return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ function listTestCaseFiles(testsDir) {
134
+ const files = listYamlFileNames(testsDir);
135
+ if (!safeExistsSync(testsDir)) {
136
+ return files;
137
+ }
138
+ for (const entry of safeReaddirSync(testsDir)) {
139
+ if (String(entry).startsWith('.')) {
140
+ continue;
141
+ }
142
+ const child = path.join(testsDir, entry);
143
+ if (!safeIsDirectory(child)) {
144
+ continue;
145
+ }
146
+ files.push(...listYamlFileNames(child));
147
+ }
148
+ return files;
116
149
  }
117
150
 
118
151
  function looksLikePathSelector(selector) {
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Role-assistant typed search cases (work.skills / work.search).
3
+ *
4
+ * @fileoverview Execute viewpoint and table search cases without Work Hub
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getWorkSkills, searchWork } = require('../api/work-search.api');
12
+ const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
13
+ const {
14
+ resolveInput,
15
+ wantsSearchPath,
16
+ wantsSkillsOnlyPath
17
+ } = require('./test-runner-workhub-helpers');
18
+
19
+ function operationKeys(spec) {
20
+ const raw = Array.isArray(spec && spec.operations) ? spec.operations : [];
21
+ const keys = [];
22
+ for (const item of raw) {
23
+ if (typeof item === 'string' && item.trim()) {
24
+ keys.push(item.trim());
25
+ continue;
26
+ }
27
+ const key = item && typeof item === 'object' ? String(item.key || '').trim() : '';
28
+ if (key) {
29
+ keys.push(key);
30
+ }
31
+ }
32
+ return keys;
33
+ }
34
+
35
+ function resourceTypesMap(skillsPayload) {
36
+ const rts = skillsPayload && skillsPayload.search && skillsPayload.search.resourceTypes;
37
+ return rts && typeof rts === 'object' && !Array.isArray(rts) ? rts : {};
38
+ }
39
+
40
+ function pickResourceType(skillsPayload, operation) {
41
+ const rts = resourceTypesMap(skillsPayload);
42
+ const keys = Object.keys(rts);
43
+ const wanted = String(operation || '').trim();
44
+ if (wanted) {
45
+ const hits = keys.filter((resourceType) => operationKeys(rts[resourceType]).includes(wanted));
46
+ if (hits.length === 1) {
47
+ return hits[0];
48
+ }
49
+ }
50
+ return keys.length === 1 ? keys[0] : '';
51
+ }
52
+
53
+ function resolveRoleRef(roleKey, testCase) {
54
+ const input = resolveInput(testCase);
55
+ const candidates = [
56
+ input.roleRef,
57
+ input.businessRoleRef,
58
+ testCase && testCase.roleRef,
59
+ testCase && testCase.businessRoleRef,
60
+ roleAssistantSystemKey(roleKey)
61
+ ];
62
+ for (const row of candidates) {
63
+ if (typeof row === 'string' && row.trim()) {
64
+ return row.trim();
65
+ }
66
+ }
67
+ return roleAssistantSystemKey(roleKey);
68
+ }
69
+
70
+ function assignOptionalNumber(args, input, key) {
71
+ if (Number.isFinite(input[key])) {
72
+ args[key] = input[key];
73
+ }
74
+ }
75
+
76
+ function buildSearchArguments(roleRef, input, skillsPayload) {
77
+ const args = { roleRef };
78
+ const resourceType =
79
+ String(input.resourceType || '').trim() || pickResourceType(skillsPayload, input.operation);
80
+ if (resourceType) {
81
+ args.resourceType = resourceType;
82
+ }
83
+ if (typeof input.operation === 'string' && input.operation.trim()) {
84
+ args.operation = input.operation.trim();
85
+ }
86
+ if (input.filters && typeof input.filters === 'object' && !Array.isArray(input.filters)) {
87
+ args.filters = input.filters;
88
+ }
89
+ assignOptionalNumber(args, input, 'page');
90
+ assignOptionalNumber(args, input, 'pageSize');
91
+ if (Array.isArray(input.fields) && input.fields.length) {
92
+ args.fields = input.fields;
93
+ }
94
+ return args;
95
+ }
96
+
97
+ function statusToken(statusCode) {
98
+ return Number(statusCode) === 200 ? 'completed' : 'stopped';
99
+ }
100
+
101
+ function searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, searchRes, args) {
102
+ return {
103
+ kind: searchRes ? 'search' : 'skills',
104
+ roleKey,
105
+ caseId: testCase.caseId,
106
+ roleRef,
107
+ processExecutionId: null,
108
+ skills: skillsRes,
109
+ search: searchRes ? { ...searchRes, arguments: args } : null,
110
+ finalStatus: {
111
+ status: statusToken((searchRes || skillsRes).status)
112
+ },
113
+ timeline: [],
114
+ interactions: [],
115
+ workResult: null,
116
+ workResultError: null
117
+ };
118
+ }
119
+
120
+ async function runSearchCaseLifecycle(roleKey, testCase, runtimeContext) {
121
+ const dataplaneUrl = runtimeContext && runtimeContext.dataplaneUrl;
122
+ const authConfig = runtimeContext && runtimeContext.authConfig;
123
+ if (!dataplaneUrl || !authConfig) {
124
+ throw new Error('Role assistant search case requires dataplane auth context.');
125
+ }
126
+ const roleRef = resolveRoleRef(roleKey, testCase);
127
+ const skillsRes = await getWorkSkills(dataplaneUrl, authConfig, roleRef);
128
+ if (wantsSkillsOnlyPath(testCase)) {
129
+ return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, null, null);
130
+ }
131
+ const args = buildSearchArguments(roleRef, resolveInput(testCase), skillsRes.payload);
132
+ if (!args.resourceType) {
133
+ throw new Error(
134
+ 'Search case requires input.resourceType (or exactly one matching type from work.skills).'
135
+ );
136
+ }
137
+ const searchRes = await searchWork(dataplaneUrl, authConfig, args);
138
+ return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, searchRes, args);
139
+ }
140
+
141
+ module.exports = {
142
+ wantsSearchPath,
143
+ wantsSkillsOnlyPath,
144
+ operationKeys,
145
+ pickResourceType,
146
+ resolveRoleRef,
147
+ buildSearchArguments,
148
+ runSearchCaseLifecycle
149
+ };
@@ -34,9 +34,45 @@ function resolveObjective(testCase) {
34
34
  return '';
35
35
  }
36
36
 
37
- function wantsWorkHubPath(testCase) {
37
+ function firstNonEmptyString(values) {
38
+ for (const value of values) {
39
+ if (typeof value === 'string' && value.trim()) {
40
+ return value.trim();
41
+ }
42
+ }
43
+ return '';
44
+ }
45
+
46
+ function searchPathToken(testCase) {
38
47
  const input = resolveInput(testCase);
39
- const pathToken = String(input.executionPath || input.mode || '').trim().toLowerCase();
48
+ return firstNonEmptyString([
49
+ input.executionPath,
50
+ input.execution_path,
51
+ testCase && testCase.executionPath,
52
+ input.mode
53
+ ]).toLowerCase();
54
+ }
55
+
56
+ function wantsSearchPath(testCase) {
57
+ const pathToken = searchPathToken(testCase);
58
+ return (
59
+ pathToken === 'search' ||
60
+ pathToken === 'work.search' ||
61
+ pathToken === 'skills' ||
62
+ pathToken === 'work.skills'
63
+ );
64
+ }
65
+
66
+ function wantsSkillsOnlyPath(testCase) {
67
+ const pathToken = searchPathToken(testCase);
68
+ return pathToken === 'skills' || pathToken === 'work.skills';
69
+ }
70
+
71
+ function wantsWorkHubPath(testCase) {
72
+ if (wantsSearchPath(testCase)) {
73
+ return false;
74
+ }
75
+ const pathToken = searchPathToken(testCase);
40
76
  if (pathToken === 'workhub' || pathToken === 'work-hub' || pathToken === 'plan') {
41
77
  return true;
42
78
  }
@@ -91,6 +127,8 @@ module.exports = {
91
127
  delayMs,
92
128
  resolveInput,
93
129
  resolveObjective,
130
+ wantsSearchPath,
131
+ wantsSkillsOnlyPath,
94
132
  wantsWorkHubPath,
95
133
  unwrapId,
96
134
  extractPeFromTask,
@@ -1,14 +1,20 @@
1
1
  /**
2
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.
3
+ * Soft cases may stop before accept when Intent is not Act-ready or cannot be
4
+ * classified (unavailable without a next question) — that is a governed
5
+ * fail-closed outcome, not a harness failure.
5
6
  */
6
7
 
7
8
  'use strict';
8
9
 
9
10
  function isIntentNotActReadyError(error) {
10
11
  const msg = String((error && error.message) || error || '');
11
- return /not Act-ready|intentNotActReady/i.test(msg);
12
+ const type = String((error && (error.errorType || error.type)) || '');
13
+ return (
14
+ /not Act-ready|intentNotActReady/i.test(msg) ||
15
+ /Intent is not available|runtimeIntentUnavailable/i.test(msg) ||
16
+ /runtimeIntentUnavailable/i.test(type)
17
+ );
12
18
  }
13
19
 
14
20
  function attachPlanIdToError(error, planId) {
@@ -302,21 +302,36 @@ function assertTerminalWorkHubTask(last, taskId, timeoutMs, options = {}) {
302
302
  throw new Error(`Work Hub task ${taskId} produced no status rows.`);
303
303
  }
304
304
  const finalStatusToken = String(last?.status || last?.taskStatus || '').trim().toLowerCase();
305
- if (!TERMINAL_TASK.has(finalStatusToken)) {
306
- if (options.soft === true) {
307
- return last;
308
- }
309
- throw new Error(
310
- `Work Hub task ${taskId} did not reach a terminal status within ${timeoutMs}ms (last=${finalStatusToken || 'unknown'}).`
311
- );
305
+ if (!TERMINAL_TASK.has(finalStatusToken) && options.recordTimeout === true) {
306
+ last = {
307
+ ...last,
308
+ pollTimedOut: true,
309
+ lastStatus: finalStatusToken || 'unknown',
310
+ pollTimeoutMs: timeoutMs
311
+ };
312
312
  }
313
313
  return last;
314
314
  }
315
315
 
316
+ function shouldStopPendingApprovalPoll(status, ctx) {
317
+ if (status !== 'pendingapproval' && status !== 'pending_approval') {
318
+ return false;
319
+ }
320
+ return ctx.approveStopped === true && (Number(ctx.pendingApprovalStreak) || 0) >= 3;
321
+ }
322
+
323
+ function applyWorkHubPollStatus(ctx, last, status) {
324
+ ctx.status = status;
325
+ ctx.taskRow = last;
326
+ ctx.pendingApprovalStreak =
327
+ status === 'pendingapproval' || status === 'pending_approval'
328
+ ? (Number(ctx.pendingApprovalStreak) || 0) + 1
329
+ : 0;
330
+ }
331
+
316
332
  async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, options) {
317
333
  const timeoutMs = Number(options.pollTimeoutMs) || 180000;
318
334
  const intervalMs = Number(options.pollIntervalMs) || 3000;
319
- const soft = testCase?.expected?.soft === true;
320
335
  const started = Date.now();
321
336
  let last = null;
322
337
  const timeline = [];
@@ -324,6 +339,7 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
324
339
  status: '',
325
340
  approveAttempts: 0,
326
341
  approveStopped: false,
342
+ pendingApprovalStreak: 0,
327
343
  interactions: [],
328
344
  taskRow: null
329
345
  };
@@ -335,15 +351,17 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
335
351
  if (TERMINAL_TASK.has(status)) {
336
352
  break;
337
353
  }
338
- ctx.status = status;
339
- ctx.taskRow = last;
354
+ applyWorkHubPollStatus(ctx, last, status);
340
355
  await maybeApproveTask(dataplaneUrl, authConfig, taskId, ctx);
341
356
  await maybeRespondWaiting(dataplaneUrl, authConfig, last, testCase, ctx.interactions);
357
+ if (shouldStopPendingApprovalPoll(status, ctx)) {
358
+ break;
359
+ }
342
360
  await delayMs(intervalMs);
343
361
  }
344
362
 
345
363
  return {
346
- finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, { soft }),
364
+ finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, { recordTimeout: true }),
347
365
  timeline,
348
366
  interactions: ctx.interactions
349
367
  };
@@ -18,6 +18,7 @@ const {
18
18
  getWorkExecutionResult
19
19
  } = require('../api/work.api');
20
20
  const { wantsWorkHubPath, runWorkHubCaseLifecycle } = require('./test-runner-workhub');
21
+ const { wantsSearchPath, runSearchCaseLifecycle } = require('./test-runner-search');
21
22
  const {
22
23
  buildStartIntentPayload,
23
24
  resolveInputObject,
@@ -58,6 +59,9 @@ function applyOptionalStringField(payload, value, key) {
58
59
  }
59
60
 
60
61
  function isExecutableRoleAssistantCase(testCase) {
62
+ if (wantsSearchPath(testCase)) {
63
+ return true;
64
+ }
61
65
  const input = resolveInputObject(testCase);
62
66
  if (typeof input.content !== 'undefined') {
63
67
  return true;
@@ -370,6 +374,9 @@ async function runRoleAssistantCaseLifecycle(roleKey, testCase, runtimeContext,
370
374
  throw new Error('Role assistant case runtime requires dataplane auth context.');
371
375
  }
372
376
  const runnerOptions = buildRunnerOptions(options);
377
+ if (wantsSearchPath(testCase)) {
378
+ return runSearchCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
379
+ }
373
380
  if (wantsWorkHubPath(testCase)) {
374
381
  return runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
375
382
  }
@@ -418,11 +425,19 @@ function mergeCaseExecutionIntoLifecycleResult(lifecycleResult, executionResult)
418
425
  task: executionResult?.workResult?.task || null,
419
426
  steps: Array.isArray(executionResult?.workResult?.steps) ? executionResult.workResult.steps : [],
420
427
  workTimeline: executionResult?.timeline || [],
421
- workInteractions: executionResult?.interactions || []
428
+ workInteractions: executionResult?.interactions || [],
429
+ search: executionResult?.search || null,
430
+ skills: executionResult?.skills || null,
431
+ roleRef: executionResult?.roleRef || null
422
432
  };
423
433
  }
424
434
 
425
435
  function lifecycleVerdictFromCaseExecution(executionResult, fallbackVerdict) {
436
+ if (executionResult?.kind === 'search' || executionResult?.kind === 'skills') {
437
+ return Number.isFinite(Number(executionResult.search?.status || executionResult.skills?.status))
438
+ ? VERDICT.VERIFIED
439
+ : VERDICT.FAILED;
440
+ }
426
441
  const statusToken = normalizeToken(executionResult?.finalStatus?.status);
427
442
  if (statusToken === 'completed') {
428
443
  return VERDICT.VERIFIED;