@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,146 @@
1
+ /**
2
+ * Role Assistant lifecycle is not Connected System governance.
3
+ *
4
+ * Generated digital-worker datasources do not own scenario packs. Those packs
5
+ * belong on the Connected System. Missing-pack FAILED must not fail RA prove.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { VERDICT } = require('./product-model');
11
+
12
+ const GOVERNANCE_SCENARIOS_MISSING = 'GOVERNANCE_SCENARIOS_MISSING';
13
+ const GOVERNANCE_NOT_VERIFIED = 'GOVERNANCE_NOT_VERIFIED';
14
+ const ROLE_ASSISTANT_GOVERNANCE_WARNING = 'ROLE_ASSISTANT_GOVERNANCE_PACKS';
15
+
16
+ const VERDICT_RANK = Object.freeze({
17
+ [VERDICT.FAILED]: 4,
18
+ [VERDICT.NOT_VERIFIED]: 3,
19
+ [VERDICT.VERIFIED]: 2,
20
+ [VERDICT.NOT_APPLICABLE]: 1
21
+ });
22
+
23
+ function readGovernanceCode(block) {
24
+ if (!block || typeof block !== 'object') {
25
+ return '';
26
+ }
27
+ return String(block.errorCode || block.message || '').trim();
28
+ }
29
+
30
+ function governanceIsMissingPack(block) {
31
+ if (!block || typeof block !== 'object') {
32
+ return false;
33
+ }
34
+ if (String(block.verdict || '').trim() !== VERDICT.FAILED) {
35
+ return false;
36
+ }
37
+ return readGovernanceCode(block) === GOVERNANCE_SCENARIOS_MISSING;
38
+ }
39
+
40
+ function notApplicableMissingPack(block) {
41
+ const base = block && typeof block === 'object' ? { ...block } : {};
42
+ base.verdict = VERDICT.NOT_APPLICABLE;
43
+ base.errorCode = GOVERNANCE_SCENARIOS_MISSING;
44
+ return base;
45
+ }
46
+
47
+ function remapDatasourceRow(row) {
48
+ if (!row || typeof row !== 'object' || !governanceIsMissingPack(row.governance)) {
49
+ return { row, remapped: false };
50
+ }
51
+ return {
52
+ row: { ...row, governance: notApplicableMissingPack(row.governance) },
53
+ remapped: true
54
+ };
55
+ }
56
+
57
+ function worseGovernance(left, right) {
58
+ const leftRank = VERDICT_RANK[left && left.verdict] || 0;
59
+ const rightRank = VERDICT_RANK[right && right.verdict] || 0;
60
+ return rightRank > leftRank ? right : left;
61
+ }
62
+
63
+ function rollupGovernanceFromDatasources(datasources) {
64
+ let worst = null;
65
+ for (const row of datasources) {
66
+ if (row && row.governance) {
67
+ worst = worseGovernance(worst, row.governance);
68
+ }
69
+ }
70
+ return worst;
71
+ }
72
+
73
+ function stripGovernanceNotVerified(recommendations) {
74
+ if (!Array.isArray(recommendations)) {
75
+ return [];
76
+ }
77
+ return recommendations.filter((rec) => rec && rec.code !== GOVERNANCE_NOT_VERIFIED);
78
+ }
79
+
80
+ function appendRoleAssistantGovernanceWarning(warnings) {
81
+ const next = Array.isArray(warnings) ? [...warnings] : [];
82
+ const exists = next.some((row) => row && row.code === ROLE_ASSISTANT_GOVERNANCE_WARNING);
83
+ if (!exists) {
84
+ next.push({
85
+ code: ROLE_ASSISTANT_GOVERNANCE_WARNING,
86
+ message:
87
+ 'Role Assistant datasources do not use Connected System governance packs. ' +
88
+ 'Isolation is Work Hub cases and role-assistant.yaml.'
89
+ });
90
+ }
91
+ return next;
92
+ }
93
+
94
+ /**
95
+ * Treat CS-only missing packs as not applicable on a Role Assistant report.
96
+ * @param {Object} report
97
+ * @returns {Object}
98
+ */
99
+ function remapRoleAssistantGovernanceReport(report) {
100
+ if (!report || typeof report !== 'object') {
101
+ return report;
102
+ }
103
+ const next = { ...report };
104
+ let remapped = false;
105
+ if (Array.isArray(report.datasources)) {
106
+ next.datasources = report.datasources.map((row) => {
107
+ const mapped = remapDatasourceRow(row);
108
+ if (mapped.remapped) {
109
+ remapped = true;
110
+ }
111
+ return mapped.row;
112
+ });
113
+ if (remapped) {
114
+ next.governance =
115
+ rollupGovernanceFromDatasources(next.datasources) ||
116
+ notApplicableMissingPack(report.governance);
117
+ }
118
+ }
119
+ if (governanceIsMissingPack(next.governance)) {
120
+ next.governance = notApplicableMissingPack(next.governance);
121
+ remapped = true;
122
+ }
123
+ if (!remapped) {
124
+ return report;
125
+ }
126
+ next.recommendations = stripGovernanceNotVerified(report.recommendations);
127
+ next.warnings = appendRoleAssistantGovernanceWarning(report.warnings);
128
+ return next;
129
+ }
130
+
131
+ /**
132
+ * Pillar is satisfied for overall lifecycle (verified or not applicable).
133
+ * @param {string|undefined} verdict
134
+ * @returns {boolean}
135
+ */
136
+ function lifecyclePillarSatisfied(verdict) {
137
+ return verdict === VERDICT.VERIFIED || verdict === VERDICT.NOT_APPLICABLE;
138
+ }
139
+
140
+ module.exports = {
141
+ GOVERNANCE_SCENARIOS_MISSING,
142
+ ROLE_ASSISTANT_GOVERNANCE_WARNING,
143
+ governanceIsMissingPack,
144
+ remapRoleAssistantGovernanceReport,
145
+ lifecyclePillarSatisfied
146
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Upload local governance scenario YAML packs to dataplane (419.0).
3
3
  * @author AI Fabrix Team
4
- * @version 1.0.0
4
+ * @version 2.0.0
5
5
  */
6
6
 
7
7
  'use strict';
@@ -17,6 +17,9 @@ const {
17
17
  const yaml = require('js-yaml');
18
18
  const logger = require('../utils/logger');
19
19
  const { getIntegrationPath } = require('../utils/paths');
20
+ const { loadConfigFile } = require('../utils/config-format');
21
+ const { discoverIntegrationFiles } = require('../commands/repair-internal');
22
+ const { parseAccessControlAbac } = require('./access-control-abac');
20
23
  const { upsertDatasourceGovernancePack } = require('../api/governance-scenario-pack.api');
21
24
  const { formatSuccessLine, formatWarningLine } = require('../utils/cli-test-layout-chalk');
22
25
  const { shortGovernanceApiError } = require('../governance/governance-cli-errors');
@@ -63,6 +66,158 @@ function loadPackFromFile(filePath) {
63
66
  return doc;
64
67
  }
65
68
 
69
+ /**
70
+ * @param {string} filePath
71
+ * @returns {string}
72
+ */
73
+ function scenarioFileStem(filePath) {
74
+ return path.basename(filePath).replace(/\.ya?ml$/i, '').trim();
75
+ }
76
+
77
+ /**
78
+ * Sidecar and CLI callers historically received a number. Keep both shapes.
79
+ * @param {unknown} result
80
+ * @returns {{ uploaded: number, warnings: string[] }}
81
+ */
82
+ function normalizeGovernanceUploadResult(result) {
83
+ if (typeof result === 'number') {
84
+ return { uploaded: result, warnings: [] };
85
+ }
86
+ const uploaded = Number(result && result.uploaded);
87
+ const warnings = result && Array.isArray(result.warnings) ? result.warnings : [];
88
+ return {
89
+ uploaded: Number.isFinite(uploaded) ? uploaded : 0,
90
+ warnings
91
+ };
92
+ }
93
+
94
+ /**
95
+ * @param {string} appPath
96
+ * @returns {Map<string, Object>}
97
+ */
98
+ function loadPackageDatasourceMap(appPath) {
99
+ const map = new Map();
100
+ if (!existsSync(appPath)) {
101
+ return map;
102
+ }
103
+ const { datasourceFiles } = discoverIntegrationFiles(appPath);
104
+ for (const name of datasourceFiles) {
105
+ try {
106
+ const doc = loadConfigFile(path.join(appPath, name));
107
+ const key = doc && typeof doc.key === 'string' ? doc.key.trim() : '';
108
+ if (key) {
109
+ map.set(key, doc);
110
+ }
111
+ } catch {
112
+ // skip unreadable datasource files
113
+ }
114
+ }
115
+ return map;
116
+ }
117
+
118
+ /**
119
+ * @param {string} message
120
+ * @param {{ silent?: boolean }} opts
121
+ * @param {string[]} collected
122
+ */
123
+ function emitWarning(message, opts, collected) {
124
+ collected.push(message);
125
+ if (!opts.silent) {
126
+ logger.log(formatWarningLine(message));
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Owner = YAML stem when it matches a package datasource key.
132
+ * Otherwise fall back to search.datasourceKeys (legacy filenames).
133
+ * @param {string} stem
134
+ * @param {string[]} searchKeys
135
+ * @param {Set<string>} packageKeys
136
+ * @returns {{ ownerKeys: string[], warnings: string[], skipped: boolean }}
137
+ */
138
+ function resolveOwnerTargets(stem, searchKeys, packageKeys) {
139
+ if (packageKeys.has(stem)) {
140
+ const warnings = [];
141
+ if (searchKeys.length > 0 && !searchKeys.includes(stem)) {
142
+ warnings.push(
143
+ `${stem}.yaml searches ${searchKeys.join(', ')} — uploading onto ${stem} (carrier search)`
144
+ );
145
+ }
146
+ return { ownerKeys: [stem], warnings, skipped: false };
147
+ }
148
+ if (searchKeys.length === 0) {
149
+ return {
150
+ ownerKeys: [],
151
+ warnings: [
152
+ `Skipping ${stem}.yaml — filename is not a package datasource key and scenarios have no datasourceKeys`
153
+ ],
154
+ skipped: true
155
+ };
156
+ }
157
+ return {
158
+ ownerKeys: searchKeys,
159
+ warnings: [
160
+ `${stem}.yaml is not a package datasource key — uploading onto search keys (${searchKeys.join(', ')}). Rename to <datasourceKey>.yaml`
161
+ ],
162
+ skipped: false
163
+ };
164
+ }
165
+
166
+ /**
167
+ * @param {Object} ctx
168
+ * @returns {Promise<number>}
169
+ */
170
+ async function uploadOneScenarioFile(ctx) {
171
+ const { filePath, packageKeys, dataplaneUrl, authConfig, opts, warnings, ownerStems } = ctx;
172
+ const pack = loadPackFromFile(filePath);
173
+ const searchKeys = datasourceKeysFromPack(pack);
174
+ const stem = scenarioFileStem(filePath);
175
+ const resolved = resolveOwnerTargets(stem, searchKeys, packageKeys);
176
+ for (const warning of resolved.warnings) {
177
+ emitWarning(warning, opts, warnings);
178
+ }
179
+ if (resolved.skipped || resolved.ownerKeys.length === 0) {
180
+ return 0;
181
+ }
182
+ if (packageKeys.has(stem)) {
183
+ ownerStems.push(stem);
184
+ }
185
+ let uploaded = 0;
186
+ for (const dsKey of resolved.ownerKeys) {
187
+ await upsertDatasourceGovernancePack(dataplaneUrl, authConfig, dsKey, pack);
188
+ uploaded += 1;
189
+ if (!opts.silent) {
190
+ logger.log(
191
+ formatSuccessLine(
192
+ `Governance scenario pack uploaded for ${dsKey} (${path.basename(filePath)})`
193
+ )
194
+ );
195
+ }
196
+ }
197
+ return uploaded;
198
+ }
199
+
200
+ /**
201
+ * @param {Map<string, Object>} packageMap
202
+ * @param {string[]} ownerStems
203
+ * @param {{ silent?: boolean }} opts
204
+ * @param {string[]} warnings
205
+ */
206
+ function warnMissingAbacOwnerFiles(packageMap, ownerStems, opts, warnings) {
207
+ const present = new Set(ownerStems);
208
+ for (const [key, doc] of packageMap) {
209
+ const abac = parseAccessControlAbac(doc);
210
+ if (abac.applicability === 'notApplicable' || present.has(key)) {
211
+ continue;
212
+ }
213
+ emitWarning(
214
+ `No scenarios/${key}.yaml for ABAC-required datasource ${key}`,
215
+ opts,
216
+ warnings
217
+ );
218
+ }
219
+ }
220
+
66
221
  /**
67
222
  * Upload scenario packs under integration/<systemKey>/scenarios/ to dataplane.
68
223
  * @async
@@ -70,40 +225,33 @@ function loadPackFromFile(filePath) {
70
225
  * @param {string} dataplaneUrl
71
226
  * @param {Object} authConfig
72
227
  * @param {{ silent?: boolean }} [opts]
73
- * @returns {Promise<number>} count of packs uploaded
228
+ * @returns {Promise<{ uploaded: number, warnings: string[] }>}
74
229
  */
75
230
  async function uploadGovernanceScenarioPacks(systemKey, dataplaneUrl, authConfig, opts = {}) {
76
- const scenariosDir = path.join(getIntegrationPath(systemKey), 'scenarios');
77
- const files = listScenarioYamlFiles(scenariosDir);
78
- if (files.length === 0) return 0;
79
-
231
+ const appPath = getIntegrationPath(systemKey);
232
+ const files = listScenarioYamlFiles(path.join(appPath, 'scenarios'));
233
+ const warnings = [];
234
+ const packageMap = loadPackageDatasourceMap(appPath);
235
+ if (files.length === 0) {
236
+ warnMissingAbacOwnerFiles(packageMap, [], opts, warnings);
237
+ return { uploaded: 0, warnings };
238
+ }
239
+ const packageKeys = new Set(packageMap.keys());
240
+ const ownerStems = [];
80
241
  let uploaded = 0;
81
242
  for (const filePath of files) {
82
- const pack = loadPackFromFile(filePath);
83
- const dsKeys = datasourceKeysFromPack(pack);
84
- if (dsKeys.length === 0) {
85
- if (!opts.silent) {
86
- logger.log(
87
- formatWarningLine(
88
- `Skipping ${path.basename(filePath)} — no datasourceKeys in scenarios`
89
- )
90
- );
91
- }
92
- continue;
93
- }
94
- for (const dsKey of dsKeys) {
95
- await upsertDatasourceGovernancePack(dataplaneUrl, authConfig, dsKey, pack);
96
- uploaded += 1;
97
- if (!opts.silent) {
98
- logger.log(
99
- formatSuccessLine(
100
- `Governance scenario pack uploaded for ${dsKey} (${path.basename(filePath)})`
101
- )
102
- );
103
- }
104
- }
243
+ uploaded += await uploadOneScenarioFile({
244
+ filePath,
245
+ packageKeys,
246
+ dataplaneUrl,
247
+ authConfig,
248
+ opts,
249
+ warnings,
250
+ ownerStems
251
+ });
105
252
  }
106
- return uploaded;
253
+ warnMissingAbacOwnerFiles(packageMap, ownerStems, opts, warnings);
254
+ return { uploaded, warnings };
107
255
  }
108
256
 
109
257
  /**
@@ -185,6 +333,9 @@ module.exports = {
185
333
  listScenarioYamlFiles,
186
334
  datasourceKeysFromPack,
187
335
  loadPackFromFile,
336
+ scenarioFileStem,
337
+ resolveOwnerTargets,
338
+ normalizeGovernanceUploadResult,
188
339
  uploadGovernanceScenarioPacks,
189
340
  downloadGovernanceScenarioPacks,
190
341
  downloadGovernanceScenarioPacksFromManifest
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Browser CORS for builder-api from configured public URL allowlists.
3
+ *
4
+ * @fileoverview Reflect Origin only when it matches ALLOWED_ORIGINS
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const CORS_ALLOW_HEADERS = 'Authorization, Content-Type, X-Request-Id, X-Correlation-Id, Idempotency-Key';
12
+ const CORS_ALLOW_METHODS = 'GET, POST, PATCH, OPTIONS';
13
+
14
+ /**
15
+ * @param {NodeJS.ProcessEnv} [env]
16
+ * @returns {string[]}
17
+ */
18
+ function parseOriginAllowlist(env = process.env) {
19
+ const raw = [env.ALLOWED_ORIGINS, env.MISO_ALLOWED_ORIGINS]
20
+ .filter((value) => typeof value === 'string' && value.trim())
21
+ .join(',');
22
+ return raw
23
+ .split(',')
24
+ .map((part) => part.trim())
25
+ .filter(Boolean);
26
+ }
27
+
28
+ /**
29
+ * @param {string} origin
30
+ * @param {string} pattern
31
+ * @returns {boolean}
32
+ */
33
+ function originMatchesPattern(origin, pattern) {
34
+ if (!origin || !pattern) {
35
+ return false;
36
+ }
37
+ if (origin === pattern) {
38
+ return true;
39
+ }
40
+ if (!pattern.endsWith(':*')) {
41
+ return false;
42
+ }
43
+ try {
44
+ const url = new URL(origin);
45
+ return `${url.protocol}//${url.hostname}` === pattern.slice(0, -2) && Boolean(url.port);
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * @param {import('http').IncomingMessage} req
53
+ * @param {NodeJS.ProcessEnv} [env]
54
+ * @returns {string|null}
55
+ */
56
+ function allowedOriginForRequest(req, env = process.env) {
57
+ const origin = req && req.headers && req.headers.origin;
58
+ if (!origin || typeof origin !== 'string') {
59
+ return null;
60
+ }
61
+ const matched = parseOriginAllowlist(env).some((pattern) => originMatchesPattern(origin, pattern));
62
+ return matched ? origin : null;
63
+ }
64
+
65
+ /**
66
+ * @param {import('http').IncomingMessage} req
67
+ * @param {NodeJS.ProcessEnv} [env]
68
+ * @returns {Record<string, string>}
69
+ */
70
+ function corsHeadersForRequest(req, env = process.env) {
71
+ const allowed = allowedOriginForRequest(req, env);
72
+ if (!allowed) {
73
+ return {};
74
+ }
75
+ return {
76
+ 'Access-Control-Allow-Origin': allowed,
77
+ Vary: 'Origin',
78
+ 'Access-Control-Allow-Headers': CORS_ALLOW_HEADERS,
79
+ 'Access-Control-Allow-Methods': CORS_ALLOW_METHODS
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Merge CORS onto every writeHead so JSON, problems, and raw bytes stay consistent.
85
+ * @param {import('http').IncomingMessage} req
86
+ * @param {import('http').ServerResponse} res
87
+ * @param {NodeJS.ProcessEnv} [env]
88
+ */
89
+ function applyCorsToResponse(req, res, env = process.env) {
90
+ const originalWriteHead = res.writeHead.bind(res);
91
+ res.writeHead = function writeHeadWithCors(statusCode, reasonOrHeaders, maybeHeaders) {
92
+ const hasMessage = typeof reasonOrHeaders === 'string';
93
+ const headers = hasMessage ? maybeHeaders : reasonOrHeaders;
94
+ const merged = { ...corsHeadersForRequest(req, env), ...(headers || {}) };
95
+ if (hasMessage) {
96
+ return originalWriteHead(statusCode, reasonOrHeaders, merged);
97
+ }
98
+ return originalWriteHead(statusCode, merged);
99
+ };
100
+ }
101
+
102
+ /**
103
+ * @param {import('http').IncomingMessage} req
104
+ * @returns {boolean}
105
+ */
106
+ function isCorsPreflight(req) {
107
+ return String((req && req.method) || '').toUpperCase() === 'OPTIONS';
108
+ }
109
+
110
+ /**
111
+ * @param {import('http').IncomingMessage} req
112
+ * @param {import('http').ServerResponse} res
113
+ * @param {string} requestId
114
+ * @param {string} pathname
115
+ * @param {Function} logRequestOutcome
116
+ * @returns {boolean} true when the request was fully handled
117
+ */
118
+ function handleCorsPreflight(req, res, requestId, pathname, logRequestOutcome) {
119
+ applyCorsToResponse(req, res);
120
+ if (!isCorsPreflight(req)) {
121
+ return false;
122
+ }
123
+ res.writeHead(204);
124
+ res.end();
125
+ logRequestOutcome({
126
+ requestId,
127
+ route: pathname,
128
+ method: req.method,
129
+ status: 204,
130
+ userId: 'anonymous'
131
+ });
132
+ return true;
133
+ }
134
+
135
+ module.exports = {
136
+ parseOriginAllowlist,
137
+ originMatchesPattern,
138
+ allowedOriginForRequest,
139
+ corsHeadersForRequest,
140
+ applyCorsToResponse,
141
+ isCorsPreflight,
142
+ handleCorsPreflight
143
+ };
@@ -13,6 +13,8 @@ const { dispatchHandler } = require('./route-handlers');
13
13
  const { withWorkspaceLock } = require('./workspace-lock');
14
14
  const { getIdempotentResult, setIdempotentResult } = require('./idempotency-cache');
15
15
  const { withRequestTimeout } = require('./request-timeout');
16
+ const { isHttpRawResult } = require('./http-raw-response');
17
+ const { publicOriginFromRequest } = require('../channels/vendor-dataplane-url');
16
18
 
17
19
  /**
18
20
  * @param {Object} capability
@@ -140,6 +142,7 @@ async function executeCapabilityRequest(options) {
140
142
  const auth = requiresAuth(capability)
141
143
  ? await authenticateRequest(req)
142
144
  : { userId: 'anonymous', bearerToken: '' };
145
+ auth.publicOrigin = publicOriginFromRequest(req);
143
146
 
144
147
  const body = req.method === 'GET' || req.method === 'DELETE' ? {} : await readJsonBody(req);
145
148
 
@@ -157,6 +160,9 @@ async function executeCapabilityRequest(options) {
157
160
  }
158
161
 
159
162
  const result = await runHandler(auth, capability, params, body, query);
163
+ if (isHttpRawResult(result)) {
164
+ return { httpStatus: 200, raw: result, requestId, userId: auth.userId };
165
+ }
160
166
  return buildSuccessResponse({
161
167
  auth,
162
168
  idempotencyKey: req.headers['idempotency-key'],
@@ -16,6 +16,7 @@ const { executeCapabilityRequest } = require('./builder-api-request');
16
16
  * @property {import('http').ServerResponse} res
17
17
  * @property {Function} sendJson
18
18
  * @property {Function} sendProblem
19
+ * @property {Function} [sendRaw]
19
20
  * @property {Function} readJsonBody
20
21
  * @property {Object} resolved
21
22
  * @property {Object} query
@@ -26,54 +27,67 @@ const { executeCapabilityRequest } = require('./builder-api-request');
26
27
 
27
28
  /**
28
29
  * @param {DispatchCapabilityRouteOptions} options
30
+ * @param {Object} outcome
31
+ * @returns {void}
29
32
  */
30
- async function dispatchCapabilityRoute(options) {
31
- const {
32
- req,
33
- res,
34
- sendJson,
35
- sendProblem,
36
- readJsonBody,
37
- resolved,
38
- query,
33
+ function deliverCapabilityOutcome(options, outcome) {
34
+ const { req, res, sendJson, sendRaw, resolved, logRequestOutcome } = options;
35
+ if (outcome.raw && typeof sendRaw === 'function') {
36
+ sendRaw(res, outcome.raw, outcome.requestId);
37
+ } else {
38
+ sendJson(res, outcome.httpStatus, outcome.body, outcome.requestId);
39
+ }
40
+ logRequestOutcome({
41
+ requestId: outcome.requestId,
42
+ correlationId: (outcome.body && outcome.body.correlationId) || outcome.requestId,
43
+ route: resolved.routeKey,
44
+ method: req.method,
45
+ status: outcome.httpStatus,
46
+ systemKey: resolved.params.systemKey,
47
+ userId: outcome.userId
48
+ });
49
+ }
50
+
51
+ /**
52
+ * @param {DispatchCapabilityRouteOptions} options
53
+ * @param {Error & { statusCode?: number, retryAfter?: number }} err
54
+ * @returns {void}
55
+ */
56
+ function handleDispatchError(options, err) {
57
+ const { req, res, sendProblem, resolved, requestId, pathname, logRequestOutcome } = options;
58
+ const status = err.statusCode || 500;
59
+ if (status === 409 && err.retryAfter) {
60
+ res.setHeader('Retry-After', String(err.retryAfter));
61
+ }
62
+ sendProblem(req, res, err, status, requestId, pathname);
63
+ logRequestOutcome({
39
64
  requestId,
40
- pathname,
41
- logRequestOutcome
42
- } = options;
65
+ route: resolved.routeKey,
66
+ method: req.method,
67
+ status,
68
+ systemKey: resolved.params.systemKey,
69
+ error: err.message
70
+ });
71
+ }
72
+
73
+ /**
74
+ * @param {DispatchCapabilityRouteOptions} options
75
+ * @returns {Promise<void>}
76
+ */
77
+ async function dispatchCapabilityRoute(options) {
43
78
  try {
44
79
  const outcome = await executeCapabilityRequest({
45
- req,
46
- capability: resolved.capability,
47
- params: resolved.params,
48
- routeKey: resolved.routeKey,
49
- requestId,
50
- readJsonBody,
51
- query
52
- });
53
- sendJson(res, outcome.httpStatus, outcome.body, outcome.requestId);
54
- logRequestOutcome({
55
- requestId: outcome.requestId,
56
- correlationId: outcome.body.correlationId,
57
- route: resolved.routeKey,
58
- method: req.method,
59
- status: outcome.httpStatus,
60
- systemKey: resolved.params.systemKey,
61
- userId: outcome.userId
80
+ req: options.req,
81
+ capability: options.resolved.capability,
82
+ params: options.resolved.params,
83
+ routeKey: options.resolved.routeKey,
84
+ requestId: options.requestId,
85
+ readJsonBody: options.readJsonBody,
86
+ query: options.query
62
87
  });
88
+ deliverCapabilityOutcome(options, outcome);
63
89
  } catch (err) {
64
- const status = err.statusCode || 500;
65
- if (status === 409 && err.retryAfter) {
66
- res.setHeader('Retry-After', String(err.retryAfter));
67
- }
68
- sendProblem(req, res, err, status, requestId, pathname);
69
- logRequestOutcome({
70
- requestId,
71
- route: resolved.routeKey,
72
- method: req.method,
73
- status,
74
- systemKey: resolved.params.systemKey,
75
- error: err.message
76
- });
90
+ handleDispatchError(options, err);
77
91
  }
78
92
  }
79
93