@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,90 @@
1
+ /**
2
+ * Generic channel install profiles (optional `profile` on the same POST).
3
+ *
4
+ * @fileoverview Catalog profiles from CHANNEL_TARGETS or mcp-client templates
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { CHANNEL_TARGETS } = require('./channel-targets');
12
+ const { listMcpClientProfileChoices } = require('./mcp-client/profiles');
13
+
14
+ /**
15
+ * @param {{ key?: string, displayLabel?: string, name?: string, value?: string }} row
16
+ * @returns {{ key: string, displayLabel: string }}
17
+ */
18
+ function toProfileRow(row) {
19
+ return {
20
+ key: row.key || row.value,
21
+ displayLabel: row.displayLabel || row.name || row.key || row.value
22
+ };
23
+ }
24
+
25
+ /**
26
+ * @returns {{ key: string, displayLabel: string }[]}
27
+ */
28
+ function mcpClientProfiles() {
29
+ try {
30
+ return listMcpClientProfileChoices().map(toProfileRow);
31
+ } catch {
32
+ return [
33
+ { key: 'cursor', displayLabel: 'Cursor' },
34
+ { key: 'vscode', displayLabel: 'VS Code' },
35
+ { key: 'github-copilot', displayLabel: 'GitHub Copilot' }
36
+ ];
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Profiles accepted on POST install.
42
+ * @param {string} channelKey
43
+ * @returns {{ key: string, displayLabel: string }[]}
44
+ */
45
+ function profilesForInstall(channelKey) {
46
+ const target = CHANNEL_TARGETS[channelKey];
47
+ if (target && Array.isArray(target.profiles) && target.profiles.length > 0) {
48
+ return target.profiles.map(toProfileRow);
49
+ }
50
+ if (channelKey === 'mcp-client') {
51
+ return mcpClientProfiles();
52
+ }
53
+ return [];
54
+ }
55
+
56
+ /**
57
+ * Profiles advertised on GET /channels when the target is live.
58
+ * @param {string} channelKey
59
+ * @returns {{ key: string, displayLabel: string }[]}
60
+ */
61
+ function profilesForCatalogRow(channelKey) {
62
+ if (!CHANNEL_TARGETS[channelKey]) {
63
+ return [];
64
+ }
65
+ return profilesForInstall(channelKey);
66
+ }
67
+
68
+ /**
69
+ * @param {string} channelKey
70
+ * @returns {boolean}
71
+ */
72
+ function installRequiresProfile(channelKey) {
73
+ return profilesForInstall(channelKey).length > 0;
74
+ }
75
+
76
+ /**
77
+ * @param {string} channelKey
78
+ * @param {string} profile
79
+ * @returns {boolean}
80
+ */
81
+ function isAllowedInstallProfile(channelKey, profile) {
82
+ return profilesForInstall(channelKey).some((row) => row.key === profile);
83
+ }
84
+
85
+ module.exports = {
86
+ profilesForCatalogRow,
87
+ profilesForInstall,
88
+ installRequiresProfile,
89
+ isAllowedInstallProfile
90
+ };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * HTTP install collision: instance key already on Dataplane → 409.
3
+ *
4
+ * @fileoverview Existing instance becomes continue/update; CLI skip stays on add-channel
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getExternalSystem } = require('../api/external-systems.api');
12
+ const { unwrapApiData } = require('../utils/external-system-readiness-core');
13
+ const { httpError } = require('../programmatic/channel-install-session');
14
+
15
+ /**
16
+ * @param {unknown} err
17
+ * @returns {boolean}
18
+ */
19
+ function isNotFoundError(err) {
20
+ const status = Number(
21
+ err && (err.statusCode || err.status || (err.errorData && err.errorData.status))
22
+ );
23
+ return status === 404;
24
+ }
25
+
26
+ /**
27
+ * True only when Dataplane returned that Connected System key.
28
+ * Envelopes, RFC 7807 bodies, leftover catalog keys, and empty objects are not collisions.
29
+ *
30
+ * @param {unknown} system
31
+ * @param {string} instanceKey
32
+ * @returns {boolean}
33
+ */
34
+ function isExistingChannelInstance(system, instanceKey) {
35
+ if (!system || typeof system !== 'object' || Array.isArray(system)) {
36
+ return false;
37
+ }
38
+ const status = Number(system.status || system.statusCode);
39
+ if (status === 404) {
40
+ return false;
41
+ }
42
+ const key =
43
+ (typeof system.key === 'string' && system.key) ||
44
+ (typeof system.systemKey === 'string' && system.systemKey) ||
45
+ null;
46
+ return key === instanceKey;
47
+ }
48
+
49
+ /**
50
+ * @param {Object} ctx
51
+ * @param {string} [instanceKey]
52
+ * @param {Object} [deps]
53
+ * @returns {Promise<void>}
54
+ */
55
+ async function assertChannelInstanceAvailable(ctx, instanceKey, deps = {}) {
56
+ if (!instanceKey || deps.continueInstall === true || deps.force === true) {
57
+ return;
58
+ }
59
+ const getSystem = deps.getExternalSystem || getExternalSystem;
60
+ let system = null;
61
+ try {
62
+ const res = await getSystem(ctx.dataplaneUrl, instanceKey, {
63
+ type: 'bearer',
64
+ token: ctx.bearerToken,
65
+ preserveBearer: true
66
+ });
67
+ system = unwrapApiData(res);
68
+ } catch (err) {
69
+ if (isNotFoundError(err)) {
70
+ return;
71
+ }
72
+ throw err;
73
+ }
74
+ if (!isExistingChannelInstance(system, instanceKey)) {
75
+ return;
76
+ }
77
+ const displayName = typeof system.displayName === 'string' ? system.displayName.trim() : '';
78
+ const named = displayName && displayName !== instanceKey ? ` ("${displayName}")` : '';
79
+ throw httpError(`Channel instance "${instanceKey}"${named} already exists.`, 409);
80
+ }
81
+
82
+ /**
83
+ * Same Connected System key is an in-place update, not a 409.
84
+ * @param {Object} ctx
85
+ * @param {Object} addOptions
86
+ * @param {Object} [body]
87
+ * @param {Object} [deps]
88
+ * @returns {Promise<void>}
89
+ */
90
+ async function adoptExistingInstanceAsContinue(ctx, addOptions, body, deps = {}) {
91
+ if (addOptions.continueInstall === true || addOptions.force === true) {
92
+ return;
93
+ }
94
+ if (body && (body.continueInstall === true || body.force === true)) {
95
+ addOptions.continueInstall = true;
96
+ return;
97
+ }
98
+ try {
99
+ await assertChannelInstanceAvailable(ctx, addOptions.systemKey, {
100
+ getExternalSystem: deps.getExternalSystem
101
+ });
102
+ } catch (err) {
103
+ if (Number(err.statusCode) !== 409) {
104
+ throw err;
105
+ }
106
+ addOptions.continueInstall = true;
107
+ if (body && typeof body === 'object') {
108
+ body.continueInstall = true;
109
+ }
110
+ }
111
+ }
112
+
113
+ module.exports = {
114
+ assertChannelInstanceAvailable,
115
+ adoptExistingInstanceAsContinue,
116
+ isExistingChannelInstance,
117
+ isNotFoundError
118
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Derive and validate Connected System instance keys for channel install.
3
+ *
4
+ * @fileoverview definition key ≠ instance key
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const INSTANCE_KEY_RE = /^[a-z0-9-]+$/;
12
+ const MAX_KEY_LENGTH = 40;
13
+ const INSTANCE_EQUALS_CATALOG =
14
+ 'Connected System key cannot equal the catalog key.';
15
+
16
+ /**
17
+ * @param {string} definitionKey
18
+ * @param {string} instanceKey
19
+ * @returns {void}
20
+ */
21
+ function assertInstanceKeyNotCatalogKey(definitionKey, instanceKey) {
22
+ if (String(instanceKey) === String(definitionKey)) {
23
+ throw new Error(
24
+ `${INSTANCE_EQUALS_CATALOG} Use ${definitionKey}-<name-slug>, not "${definitionKey}".`
25
+ );
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @param {string} name
31
+ * @returns {string}
32
+ */
33
+ function slugDisplayName(name) {
34
+ return String(name || '')
35
+ .trim()
36
+ .toLowerCase()
37
+ .replace(/[^a-z0-9]+/g, '-')
38
+ .replace(/^-+|-+$/g, '');
39
+ }
40
+
41
+ /**
42
+ * @param {string} key
43
+ * @returns {string}
44
+ */
45
+ function clipInstanceKey(key) {
46
+ if (key.length <= MAX_KEY_LENGTH) {
47
+ return key;
48
+ }
49
+ return key.slice(0, MAX_KEY_LENGTH).replace(/-+$/g, '');
50
+ }
51
+
52
+ /**
53
+ * @param {string} definitionKey
54
+ * @param {string} name
55
+ * @param {string} [explicitKey]
56
+ * @returns {string}
57
+ */
58
+ function deriveChannelInstanceKey(definitionKey, name, explicitKey) {
59
+ let instanceKey;
60
+ if (typeof explicitKey === 'string' && explicitKey.trim()) {
61
+ const trimmed = explicitKey.trim().toLowerCase();
62
+ if (!INSTANCE_KEY_RE.test(trimmed) || trimmed.length < 3 || trimmed.length > MAX_KEY_LENGTH) {
63
+ throw new Error(
64
+ 'systemKey must be 3–40 lowercase letters, digits, or hyphens.'
65
+ );
66
+ }
67
+ instanceKey = trimmed;
68
+ } else {
69
+ const slug = slugDisplayName(name);
70
+ if (!slug) {
71
+ throw new Error('name is required to derive a Connected System key.');
72
+ }
73
+ instanceKey = clipInstanceKey(`${definitionKey}-${slug}`);
74
+ }
75
+ assertInstanceKeyNotCatalogKey(definitionKey, instanceKey);
76
+ return instanceKey;
77
+ }
78
+
79
+ /**
80
+ * Derive the instance key and persist it on options.systemKey (never the catalog key).
81
+ * @param {string} definitionKey
82
+ * @param {Object} [options]
83
+ * @returns {string}
84
+ */
85
+ function resolveChannelInstanceKey(definitionKey, options = {}) {
86
+ const key = deriveChannelInstanceKey(definitionKey, options.name, options.systemKey);
87
+ options.systemKey = key;
88
+ return key;
89
+ }
90
+
91
+ module.exports = {
92
+ slugDisplayName,
93
+ clipInstanceKey,
94
+ deriveChannelInstanceKey,
95
+ resolveChannelInstanceKey,
96
+ assertInstanceKeyNotCatalogKey,
97
+ INSTANCE_KEY_RE,
98
+ MAX_KEY_LENGTH,
99
+ INSTANCE_EQUALS_CATALOG
100
+ };
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Materialize a channel instance package from the definition template.
3
+ *
4
+ * @fileoverview Swap instance key; keep channel.definitionKey
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { isCommentOrEmptyLine } = require('../utils/secrets-kv-refs');
14
+
15
+ const DEFERRED_CHANNEL_ENV_KV =
16
+ /kv:\/\/\S+\/(clientId|clientSecret|tokenUrl|authorizationUrl|botAppId|signingSecret)(\b|$)/i;
17
+
18
+ /**
19
+ * @param {string} integrationDir
20
+ * @param {string} definitionKey
21
+ * @param {string} instanceKey
22
+ * @returns {void}
23
+ */
24
+ function applyInstanceKeyToApplication(integrationDir, definitionKey, instanceKey) {
25
+ const appPath = path.join(integrationDir, 'application.json');
26
+ if (!fs.existsSync(appPath)) {
27
+ return;
28
+ }
29
+ const app = JSON.parse(fs.readFileSync(appPath, 'utf8'));
30
+ if (app.app && typeof app.app === 'object') {
31
+ app.app.key = instanceKey;
32
+ }
33
+ if (app.externalIntegration && Array.isArray(app.externalIntegration.systems)) {
34
+ app.externalIntegration.systems = app.externalIntegration.systems.map((name) =>
35
+ name === `${definitionKey}-system.json` ? `${instanceKey}-system.json` : name
36
+ );
37
+ }
38
+ fs.writeFileSync(appPath, `${JSON.stringify(app, null, 2)}\n`);
39
+ }
40
+
41
+ /**
42
+ * Stamp catalog identity on the instance ES (channel.definitionKey only).
43
+ * Package JSON must not include `config` (not in the External System schema).
44
+ * @param {Object} doc
45
+ * @param {string} definitionKey
46
+ * @returns {void}
47
+ */
48
+ function stampInstanceDefinitionKey(doc, definitionKey) {
49
+ const channel = doc.channel && typeof doc.channel === 'object' ? { ...doc.channel } : {};
50
+ channel.definitionKey = definitionKey;
51
+ doc.channel = channel;
52
+ delete doc.config;
53
+ }
54
+
55
+ /**
56
+ * Bind datasources to the instance key so vendor OpenAPI resolves to openapi/*.json.
57
+ * Catalog documentKey `{definition}-openapi` + instance systemKey looks for the wrong file.
58
+ * @param {Object} doc
59
+ * @param {string} definitionKey
60
+ * @param {string} instanceKey
61
+ * @returns {void}
62
+ */
63
+ function rewriteDatasourceInstanceIdentity(doc, definitionKey, instanceKey) {
64
+ if (!doc || typeof doc !== 'object') {
65
+ return;
66
+ }
67
+ doc.systemKey = instanceKey;
68
+ if (!doc.openapi || typeof doc.openapi !== 'object') {
69
+ return;
70
+ }
71
+ const catalogDoc = `${definitionKey}-openapi`;
72
+ if (doc.openapi.documentKey === catalogDoc) {
73
+ doc.openapi.documentKey = `${instanceKey}-openapi`;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @param {string} integrationDir
79
+ * @param {string} definitionKey
80
+ * @param {string} instanceKey
81
+ * @returns {void}
82
+ */
83
+ function applyInstanceKeyToDatasources(integrationDir, definitionKey, instanceKey) {
84
+ if (!fs.existsSync(integrationDir)) {
85
+ return;
86
+ }
87
+ for (const name of fs.readdirSync(integrationDir)) {
88
+ if (!name.includes('-datasource-') || !name.endsWith('.json')) {
89
+ continue;
90
+ }
91
+ const filePath = path.join(integrationDir, name);
92
+ const doc = JSON.parse(fs.readFileSync(filePath, 'utf8'));
93
+ rewriteDatasourceInstanceIdentity(doc, definitionKey, instanceKey);
94
+ fs.writeFileSync(filePath, `${JSON.stringify(doc, null, 2)}\n`);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Secrets written during channel add (Entra/OAuth, Teams botAppId) must not block upload.
100
+ * @param {string} integrationDir
101
+ * @returns {void}
102
+ */
103
+ function commentStaleOauthEnvTemplateLines(integrationDir) {
104
+ const envPath = path.join(integrationDir, 'env.template');
105
+ if (!fs.existsSync(envPath)) {
106
+ return;
107
+ }
108
+ const original = fs.readFileSync(envPath, 'utf8');
109
+ const next = original
110
+ .split('\n')
111
+ .map((line) => {
112
+ if (isCommentOrEmptyLine(line)) {
113
+ return line;
114
+ }
115
+ return DEFERRED_CHANNEL_ENV_KV.test(line) ? `# ${line.trim()}` : line;
116
+ })
117
+ .join('\n');
118
+ if (next !== original) {
119
+ fs.writeFileSync(envPath, next.endsWith('\n') ? next : `${next}\n`);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param {string} integrationDir
125
+ * @param {string} definitionKey
126
+ * @param {string} instanceKey
127
+ * @returns {string}
128
+ */
129
+ function resolveInstanceSystemSource(integrationDir, definitionKey, instanceKey) {
130
+ const src = path.join(integrationDir, `${definitionKey}-system.json`);
131
+ const dest = path.join(integrationDir, `${instanceKey}-system.json`);
132
+ if (fs.existsSync(src)) {
133
+ return src;
134
+ }
135
+ return fs.existsSync(dest) ? dest : '';
136
+ }
137
+
138
+ /**
139
+ * @param {string} integrationDir
140
+ * @param {string} definitionKey
141
+ * @param {string} instanceKey
142
+ * @returns {string}
143
+ */
144
+ function removeLeftoverCatalogDeployJson(integrationDir, definitionKey, instanceKey) {
145
+ if (definitionKey === instanceKey) {
146
+ return;
147
+ }
148
+ const leftover = path.join(integrationDir, `${definitionKey}-deploy.json`);
149
+ if (fs.existsSync(leftover)) {
150
+ fs.unlinkSync(leftover);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * @param {string} integrationDir
156
+ * @param {string} instanceKey
157
+ * @returns {string[]}
158
+ */
159
+ function collectDatasourceIdentityErrors(integrationDir, instanceKey) {
160
+ const errors = [];
161
+ for (const name of fs.readdirSync(integrationDir)) {
162
+ if (!name.includes('-datasource-') || !name.endsWith('.json')) {
163
+ continue;
164
+ }
165
+ const doc = JSON.parse(fs.readFileSync(path.join(integrationDir, name), 'utf8'));
166
+ if (doc.systemKey !== instanceKey) {
167
+ errors.push(`${name} systemKey '${doc.systemKey}'`);
168
+ }
169
+ }
170
+ return errors;
171
+ }
172
+
173
+ /**
174
+ * @param {string} integrationDir
175
+ * @param {string} instanceKey
176
+ * @returns {void}
177
+ */
178
+ function assertMaterializedInstanceIdentity(integrationDir, instanceKey) {
179
+ const app = JSON.parse(fs.readFileSync(path.join(integrationDir, 'application.json'), 'utf8'));
180
+ const system = JSON.parse(
181
+ fs.readFileSync(path.join(integrationDir, `${instanceKey}-system.json`), 'utf8')
182
+ );
183
+ const errors = [];
184
+ if (app.app && app.app.key !== instanceKey) {
185
+ errors.push(`application.app.key '${app.app.key}'`);
186
+ }
187
+ if (system.key !== instanceKey) {
188
+ errors.push(`system.key '${system.key}'`);
189
+ }
190
+ errors.push(...collectDatasourceIdentityErrors(integrationDir, instanceKey));
191
+ if (errors.length > 0) {
192
+ throw new Error(
193
+ `Channel instance identity must be '${instanceKey}': ${errors.join('; ')}`
194
+ );
195
+ }
196
+ }
197
+
198
+ function maybeAssertMaterializedInstanceIdentity(integrationDir, instanceKey) {
199
+ const appPath = path.join(integrationDir, 'application.json');
200
+ const systemPath = path.join(integrationDir, `${instanceKey}-system.json`);
201
+ if (!fs.existsSync(appPath) || !fs.existsSync(systemPath)) {
202
+ return;
203
+ }
204
+ assertMaterializedInstanceIdentity(integrationDir, instanceKey);
205
+ }
206
+
207
+ function writeInstanceSystemFile(from, dest, definitionKey, instanceKey) {
208
+ const doc = JSON.parse(fs.readFileSync(from, 'utf8'));
209
+ doc.key = instanceKey;
210
+ stampInstanceDefinitionKey(doc, definitionKey);
211
+ fs.writeFileSync(dest, `${JSON.stringify(doc, null, 2)}\n`);
212
+ if (from !== dest && fs.existsSync(from)) {
213
+ fs.unlinkSync(from);
214
+ }
215
+ }
216
+
217
+ function materializeChannelInstanceSystem(integrationDir, definitionKey, instanceKey) {
218
+ commentStaleOauthEnvTemplateLines(integrationDir);
219
+ applyInstanceKeyToDatasources(integrationDir, definitionKey, instanceKey);
220
+ const dest = path.join(integrationDir, `${instanceKey}-system.json`);
221
+ const from = resolveInstanceSystemSource(integrationDir, definitionKey, instanceKey);
222
+ if (from) {
223
+ writeInstanceSystemFile(from, dest, definitionKey, instanceKey);
224
+ }
225
+ applyInstanceKeyToApplication(integrationDir, definitionKey, instanceKey);
226
+ removeLeftoverCatalogDeployJson(integrationDir, definitionKey, instanceKey);
227
+ maybeAssertMaterializedInstanceIdentity(integrationDir, instanceKey);
228
+ return dest;
229
+ }
230
+
231
+ module.exports = {
232
+ materializeChannelInstanceSystem,
233
+ applyInstanceKeyToApplication,
234
+ applyInstanceKeyToDatasources,
235
+ commentStaleOauthEnvTemplateLines,
236
+ rewriteDatasourceInstanceIdentity,
237
+ stampInstanceDefinitionKey,
238
+ assertMaterializedInstanceIdentity,
239
+ removeLeftoverCatalogDeployJson
240
+ };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Scaffold integration/<channelTarget>/ from published templates (plan 152).
3
- * Manifest SSOT: dataplane integration/ (slack via make sync-json) → templates/channels/{target}/integration/.
3
+ * Manifest SSOT: templates/channels/{target}/integration/.
4
4
  * Builder generates only *-deploy.json at scaffold time.
5
5
  *
6
6
  * @fileoverview Channel integration scaffold — template copy + deploy JSON
@@ -10,11 +10,13 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const fs = require('fs');
14
13
  const path = require('path');
15
14
  const { getChannelTarget } = require('./channel-targets');
16
15
  const { getIntegrationPath } = require('../utils/paths');
17
- const { copyChannelIntegrationTemplate } = require('./channel-integration-template');
16
+ const {
17
+ copyChannelIntegrationTemplate,
18
+ hasChannelIntegrationFiles
19
+ } = require('./channel-integration-template');
18
20
  const { generateDeployJson } = require('../generator/index');
19
21
 
20
22
  /**
@@ -33,16 +35,20 @@ async function scaffoldChannelIntegration(channelTarget, options = {}) {
33
35
  );
34
36
  }
35
37
 
36
- const integrationDir = options.projectRoot
37
- ? path.join(path.resolve(options.projectRoot), 'integration', channelTarget)
38
- : getIntegrationPath(channelTarget);
39
- if (fs.existsSync(integrationDir) && !options.force) {
38
+ const integrationDir = options.integrationDir
39
+ ? path.resolve(options.integrationDir)
40
+ : options.projectRoot
41
+ ? path.join(path.resolve(options.projectRoot), 'integration', channelTarget)
42
+ : getIntegrationPath(channelTarget);
43
+ if (hasChannelIntegrationFiles(integrationDir, channelTarget) && !options.force) {
40
44
  throw new Error(
41
45
  `Integration folder already exists: ${integrationDir}. Use --force to overwrite generated files.`
42
46
  );
43
47
  }
44
48
 
45
- const copied = copyChannelIntegrationTemplate(channelTarget, integrationDir);
49
+ const copied = copyChannelIntegrationTemplate(channelTarget, integrationDir, {
50
+ overwrite: options.force === true
51
+ });
46
52
  const deployPath = await generateDeployJson(channelTarget, { appPath: integrationDir });
47
53
  const files = [...copied, deployPath];
48
54
 
@@ -53,5 +59,6 @@ async function scaffoldChannelIntegration(channelTarget, options = {}) {
53
59
  }
54
60
 
55
61
  module.exports = {
56
- scaffoldChannelIntegration
62
+ scaffoldChannelIntegration,
63
+ hasChannelIntegrationFiles
57
64
  };