@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,339 @@
1
+ /**
2
+ * Merge new tagged OpenAPI operations onto existing split orchestration datasources.
3
+ *
4
+ * @fileoverview --api follow-up for builder-api after OpenAPI export
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 { CAPABILITIES } = require('../programmatic/capability-registry');
14
+ const { buildOpenApiSpec } = require('../programmatic/openapi-spec');
15
+ const { buildOperationXAfabrix } = require('../programmatic/orchestration-openapi-meta');
16
+ const { getOperationDescription } = require('../programmatic/openapi-descriptions');
17
+ const { isProgrammaticOrchestrationSystem } = require('./repair-openapi-export');
18
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
19
+ const { backupIntegrationFile } = require('../utils/integration-file-backup');
20
+ const { trackRepairWrite } = require('./repair-changed-files');
21
+ const { withInferredRestRuntime } = require('./repair-openapi-rest-runtime');
22
+
23
+ const DEFAULT_SECURITY = Object.freeze([
24
+ { oauth2: ['business-transformation'] },
25
+ { oauth2: ['aifabrix-developer'] },
26
+ { oauth2: ['aifabrix-platform-admin'] }
27
+ ]);
28
+
29
+ /**
30
+ * @param {string} handlerId
31
+ * @returns {string}
32
+ */
33
+ function manifestKeyForHandler(handlerId) {
34
+ return String(handlerId || '').trim().toLowerCase();
35
+ }
36
+
37
+ /**
38
+ * @param {Object} parsed
39
+ * @returns {Set<string>}
40
+ */
41
+ function existingOperationIds(parsed) {
42
+ const ids = new Set();
43
+ const ops = parsed && parsed.openapi && parsed.openapi.operations;
44
+ if (!ops || typeof ops !== 'object') {
45
+ return ids;
46
+ }
47
+ for (const entry of Object.values(ops)) {
48
+ if (entry && typeof entry.operationId === 'string' && entry.operationId.trim()) {
49
+ ids.add(entry.operationId.trim());
50
+ }
51
+ }
52
+ return ids;
53
+ }
54
+
55
+ /**
56
+ * Copy oauth2 security from a sibling operation on the same datasource.
57
+ * @param {Object} parsed
58
+ * @returns {Array<{ oauth2: string[] }>}
59
+ */
60
+ function securityFromParsed(parsed) {
61
+ const ops = parsed && parsed.openapi && parsed.openapi.operations;
62
+ if (ops && typeof ops === 'object') {
63
+ for (const entry of Object.values(ops)) {
64
+ if (entry && Array.isArray(entry.security) && entry.security.length > 0) {
65
+ return entry.security.map((row) => ({
66
+ oauth2: Array.isArray(row.oauth2) ? [...row.oauth2] : []
67
+ }));
68
+ }
69
+ }
70
+ }
71
+ return DEFAULT_SECURITY.map((row) => ({ oauth2: [...row.oauth2] }));
72
+ }
73
+
74
+ /**
75
+ * @param {Object} spec
76
+ * @param {string} handlerId
77
+ * @returns {{ path: string, method: string, summary: string }|null}
78
+ */
79
+ function findSpecOperation(spec, handlerId) {
80
+ const paths = spec && spec.paths && typeof spec.paths === 'object' ? spec.paths : {};
81
+ for (const [route, methods] of Object.entries(paths)) {
82
+ if (!methods || typeof methods !== 'object') {
83
+ continue;
84
+ }
85
+ for (const [method, operation] of Object.entries(methods)) {
86
+ if (operation && operation.operationId === handlerId) {
87
+ return {
88
+ path: route,
89
+ method: String(method).toUpperCase(),
90
+ summary:
91
+ (typeof operation.summary === 'string' && operation.summary.trim()) ||
92
+ getOperationDescription(handlerId).summary
93
+ };
94
+ }
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * @param {Object} specOp
102
+ * @param {string} handlerId
103
+ * @param {Object} parsed
104
+ * @returns {Object}
105
+ */
106
+ function buildOpenapiOperation(specOp, handlerId, parsed) {
107
+ return withInferredRestRuntime({
108
+ operationId: handlerId,
109
+ path: specOp.path,
110
+ method: specOp.method,
111
+ security: securityFromParsed(parsed)
112
+ });
113
+ }
114
+
115
+ /**
116
+ * @param {Object} parsed
117
+ * @param {string} key
118
+ * @param {Object} openapiOp
119
+ * @returns {boolean}
120
+ */
121
+ function fillMissingRestRuntime(parsed, key, openapiOp) {
122
+ const existing = parsed.openapi && parsed.openapi.operations && parsed.openapi.operations[key];
123
+ if (!existing || existing.restRuntime || !openapiOp || !openapiOp.restRuntime) {
124
+ return false;
125
+ }
126
+ existing.restRuntime = openapiOp.restRuntime;
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * @param {string} key
132
+ * @param {string} description
133
+ * @returns {Object}
134
+ */
135
+ function buildCipPassthrough(key, description) {
136
+ return {
137
+ enabled: true,
138
+ description,
139
+ steps: [
140
+ { fetch: { source: 'openapi', openapiRef: key, query: {} } },
141
+ { map: { useFieldMappings: true, inputPath: '$' } },
142
+ { output: { mode: 'records' } }
143
+ ]
144
+ };
145
+ }
146
+
147
+ /**
148
+ * @param {Object} parsed
149
+ * @param {string} key
150
+ * @param {string} description
151
+ * @returns {boolean}
152
+ */
153
+ function ensureCapability(parsed, key, description) {
154
+ if (!Array.isArray(parsed.capabilities)) {
155
+ parsed.capabilities = [];
156
+ }
157
+ if (parsed.capabilities.some((row) => row && row.key === key)) {
158
+ return false;
159
+ }
160
+ parsed.capabilities.push({
161
+ key,
162
+ description,
163
+ riskLevel: 'medium',
164
+ approvalRequired: false
165
+ });
166
+ return true;
167
+ }
168
+
169
+ /**
170
+ * @param {Object} parsed
171
+ * @param {string} key
172
+ * @param {Object} openapiOp
173
+ * @param {string} description
174
+ * @returns {string[]}
175
+ */
176
+ function ensureCipScaffold(parsed) {
177
+ if (!parsed.openapi || typeof parsed.openapi !== 'object') {
178
+ parsed.openapi = {};
179
+ }
180
+ if (!parsed.openapi.operations || typeof parsed.openapi.operations !== 'object') {
181
+ parsed.openapi.operations = {};
182
+ }
183
+ if (!parsed.execution) {
184
+ parsed.execution = { engine: 'cip', cip: { version: '1.0', operations: {} } };
185
+ }
186
+ if (!parsed.execution.cip) {
187
+ parsed.execution.cip = { version: '1.0', operations: {} };
188
+ }
189
+ if (!parsed.execution.cip.operations) {
190
+ parsed.execution.cip.operations = {};
191
+ }
192
+ }
193
+
194
+ /**
195
+ * @param {Object} parsed
196
+ * @param {string} key
197
+ * @param {Object} openapiOp
198
+ * @param {string} description
199
+ * @returns {string[]}
200
+ */
201
+ function mergeOneOperation(parsed, key, openapiOp, description) {
202
+ const added = [];
203
+ ensureCipScaffold(parsed);
204
+ if (ensureCapability(parsed, key, description)) {
205
+ added.push(`capabilities.${key}`);
206
+ }
207
+ if (!parsed.openapi.operations[key]) {
208
+ parsed.openapi.operations[key] = withInferredRestRuntime(openapiOp);
209
+ added.push(`openapi.operations.${key}`);
210
+ } else if (fillMissingRestRuntime(parsed, key, withInferredRestRuntime(openapiOp))) {
211
+ added.push(`openapi.operations.${key}.restRuntime`);
212
+ }
213
+ if (!parsed.execution.cip.operations[key]) {
214
+ parsed.execution.cip.operations[key] = buildCipPassthrough(key, description);
215
+ added.push(`execution.cip.operations.${key}`);
216
+ }
217
+ return added;
218
+ }
219
+
220
+ /**
221
+ * @param {string} appPath
222
+ * @param {string} systemKey
223
+ * @param {string} tag
224
+ * @returns {string}
225
+ */
226
+ function tagDatasourcePath(appPath, systemKey, tag) {
227
+ return path.join(appPath, `${systemKey}-datasource-${tag}.json`);
228
+ }
229
+
230
+ /**
231
+ * @param {Map<string, Object|null>} cache
232
+ * @param {string} appPath
233
+ * @param {string} systemKey
234
+ * @param {string} tag
235
+ * @returns {{ filePath: string, parsed: Object, dirty: boolean }|null}
236
+ */
237
+ function loadTagEntry(cache, appPath, systemKey, tag) {
238
+ const filePath = tagDatasourcePath(appPath, systemKey, tag);
239
+ if (cache.has(filePath)) {
240
+ return cache.get(filePath);
241
+ }
242
+ if (!fs.existsSync(filePath)) {
243
+ cache.set(filePath, null);
244
+ return null;
245
+ }
246
+ const entry = { filePath, parsed: loadConfigFile(filePath), dirty: false };
247
+ cache.set(filePath, entry);
248
+ return entry;
249
+ }
250
+
251
+ /**
252
+ * @param {Map<string, Object|null>} cache
253
+ * @param {boolean} dryRun
254
+ * @param {Object} backupCtx
255
+ * @returns {void}
256
+ */
257
+ function persistDirtyEntries(cache, dryRun, backupCtx) {
258
+ for (const entry of cache.values()) {
259
+ if (!entry || !entry.dirty) {
260
+ continue;
261
+ }
262
+ if (!dryRun) {
263
+ backupIntegrationFile(entry.filePath, backupCtx);
264
+ writeConfigFile(entry.filePath, entry.parsed);
265
+ }
266
+ trackRepairWrite(entry.filePath, { ...(backupCtx || {}), dryRun: Boolean(dryRun) });
267
+ }
268
+ }
269
+
270
+ /**
271
+ * @param {Map<string, Object|null>} cache
272
+ * @param {Object} spec
273
+ * @param {Object} capability
274
+ * @param {string} appPath
275
+ * @param {string} systemKey
276
+ * @returns {string[]}
277
+ */
278
+ function mergeCapabilityIntoCache(cache, spec, capability, appPath, systemKey) {
279
+ const tag = buildOperationXAfabrix(capability).datasourceTag;
280
+ if (tag !== 'infra') {
281
+ return [];
282
+ }
283
+ const entry = loadTagEntry(cache, appPath, systemKey, tag);
284
+ if (!entry) {
285
+ return [];
286
+ }
287
+ const specOp = findSpecOperation(spec, capability.handlerId);
288
+ if (!specOp) {
289
+ return [];
290
+ }
291
+ const key = manifestKeyForHandler(capability.handlerId);
292
+ const openapiOp = buildOpenapiOperation(specOp, capability.handlerId, entry.parsed);
293
+ if (existingOperationIds(entry.parsed).has(capability.handlerId)) {
294
+ if (!fillMissingRestRuntime(entry.parsed, key, openapiOp)) {
295
+ return [];
296
+ }
297
+ entry.dirty = true;
298
+ return [`${path.basename(entry.filePath)}: openapi.operations.${key}.restRuntime`];
299
+ }
300
+ const added = mergeOneOperation(entry.parsed, key, openapiOp, specOp.summary);
301
+ if (added.length > 0) {
302
+ entry.dirty = true;
303
+ }
304
+ return added.map((part) => `${path.basename(entry.filePath)}: ${part}`);
305
+ }
306
+
307
+ /**
308
+ * Merge missing infra-tagged OpenAPI operations onto the existing infra datasource.
309
+ * Does not create new split files (package/branding stay on the channel tag).
310
+ * @param {Object} params
311
+ * @returns {string[]}
312
+ */
313
+ function mergeOrchestrationTagOperations(params) {
314
+ const { appPath, systemKey, dryRun, changes, backupCtx, options } = params;
315
+ if (!isProgrammaticOrchestrationSystem(systemKey)) {
316
+ return [];
317
+ }
318
+ if (!options || !options.api) {
319
+ return [];
320
+ }
321
+ const spec = buildOpenApiSpec();
322
+ const cache = new Map();
323
+ const added = [];
324
+ for (const capability of CAPABILITIES) {
325
+ added.push(...mergeCapabilityIntoCache(cache, spec, capability, appPath, systemKey));
326
+ }
327
+ persistDirtyEntries(cache, dryRun, backupCtx);
328
+ if (Array.isArray(changes)) {
329
+ added.forEach((line) => changes.push(line));
330
+ }
331
+ return added;
332
+ }
333
+
334
+ module.exports = {
335
+ manifestKeyForHandler,
336
+ existingOperationIds,
337
+ mergeOneOperation,
338
+ mergeOrchestrationTagOperations
339
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Catalog Admin/Observer defaults for Connected System RBAC.
3
+ *
4
+ * @fileoverview Upload requires COM catalog keys — never invent {systemKey}-admin
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const {
10
+ listPlatformRoles,
11
+ ROLE_PLATFORM_ADMIN,
12
+ ROLE_OBSERVER
13
+ } = require('../programmatic/platform-rbac-catalog');
14
+
15
+ const CATALOG_ADMIN_ROLE = ROLE_PLATFORM_ADMIN;
16
+ const CATALOG_READER_ROLE = ROLE_OBSERVER;
17
+
18
+ /**
19
+ * @returns {Array<{ name: string, value: string, description: string, groups: string[] }>}
20
+ */
21
+ function catalogAdminReaderRoles() {
22
+ const all = listPlatformRoles();
23
+ const admin = all.find((r) => r.value === CATALOG_ADMIN_ROLE);
24
+ const reader = all.find((r) => r.value === CATALOG_READER_ROLE);
25
+ return [admin, reader].filter(Boolean);
26
+ }
27
+
28
+ /**
29
+ * @param {string[]} roleValuesList
30
+ * @param {string} [systemKey]
31
+ * @param {string|null} [fallback]
32
+ * @returns {string|null}
33
+ */
34
+ function pickCatalogAdminRoleValue(roleValuesList, systemKey, fallback) {
35
+ const values = Array.isArray(roleValuesList) ? roleValuesList : [];
36
+ return (
37
+ values.find((v) => v === CATALOG_ADMIN_ROLE) ||
38
+ (systemKey ? values.find((v) => v === `${systemKey}-admin`) : undefined) ||
39
+ values.find((v) => typeof v === 'string' && v.endsWith('-admin')) ||
40
+ fallback ||
41
+ values[0] ||
42
+ null
43
+ );
44
+ }
45
+
46
+ /**
47
+ * @param {string[]} roleValuesList
48
+ * @param {string} [systemKey]
49
+ * @param {string|null} [fallback]
50
+ * @returns {string|null}
51
+ */
52
+ function pickCatalogReaderRoleValue(roleValuesList, systemKey, fallback) {
53
+ const values = Array.isArray(roleValuesList) ? roleValuesList : [];
54
+ return (
55
+ values.find((v) => v === CATALOG_READER_ROLE) ||
56
+ (systemKey ? values.find((v) => v === `${systemKey}-reader`) : undefined) ||
57
+ values.find((v) => typeof v === 'string' && v.endsWith('-reader')) ||
58
+ fallback ||
59
+ null
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Ensures catalog Admin/Reader roles if rbac.roles is empty. Mutates rbac.
65
+ * @param {Object} rbac
66
+ * @param {string[]} changes
67
+ * @returns {boolean}
68
+ */
69
+ function ensureDefaultRoles(rbac, changes) {
70
+ const hasRoles = Array.isArray(rbac.roles) && rbac.roles.length > 0;
71
+ if (hasRoles) return false;
72
+ const seeded = catalogAdminReaderRoles();
73
+ if (seeded.length < 2) return false;
74
+ rbac.roles = seeded;
75
+ const listGetPerms = (rbac.permissions || [])
76
+ .filter((p) => p?.name && (p.name.split(':')[1] === 'list' || p.name.split(':')[1] === 'get'))
77
+ .map((p) => p.name);
78
+ for (const p of rbac.permissions || []) {
79
+ if (!p.roles) p.roles = [];
80
+ if (p.name && listGetPerms.includes(p.name) && !p.roles.includes(CATALOG_READER_ROLE)) {
81
+ p.roles.push(CATALOG_READER_ROLE);
82
+ }
83
+ if (!p.roles.includes(CATALOG_ADMIN_ROLE)) p.roles.push(CATALOG_ADMIN_ROLE);
84
+ }
85
+ changes.push('Added catalog Admin and Reader roles to rbac file');
86
+ return true;
87
+ }
88
+
89
+ module.exports = {
90
+ CATALOG_ADMIN_ROLE,
91
+ CATALOG_READER_ROLE,
92
+ catalogAdminReaderRoles,
93
+ pickCatalogAdminRoleValue,
94
+ pickCatalogReaderRoleValue,
95
+ ensureDefaultRoles
96
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Platform RBAC preset for orchestration facade external systems (plan 154.7).
2
+ * Platform RBAC preset for orchestration facade external systems.
3
3
  *
4
4
  * @fileoverview Replace per-system admin/reader with dataplane platform roles
5
5
  * @author AI Fabrix Team
@@ -13,29 +13,40 @@ const fs = require('fs');
13
13
  const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
14
14
  const { backupIntegrationFile } = require('../utils/integration-file-backup');
15
15
  const { CAPABILITIES } = require('../programmatic/capability-registry');
16
- const { listPlatformRoles, PLATFORM_ROLE_VALUES } = require('../programmatic/platform-rbac-catalog');
16
+ const {
17
+ listPlatformRoles,
18
+ PLATFORM_ROLE_VALUES,
19
+ ROLE_OBSERVER,
20
+ ROLE_EXTERNAL_SYSTEM_DISCOVERER,
21
+ ROLE_DEVELOPER,
22
+ ROLE_DEPLOYMENT_ADMIN,
23
+ ROLE_PLATFORM_ADMIN,
24
+ ROLE_SECURITY_ADMIN,
25
+ ROLE_COMPLIANCE_ADMIN,
26
+ ROLE_AI_TRUST_OPERATOR
27
+ } = require('../programmatic/platform-rbac-catalog');
17
28
 
18
29
  const READ_ROLES = Object.freeze([
19
- 'aifabrix-observer',
20
- 'aifabrix-external-system-discoverer',
21
- 'aifabrix-developer',
22
- 'aifabrix-deployment-admin',
23
- 'aifabrix-platform-admin'
30
+ ROLE_OBSERVER,
31
+ ROLE_EXTERNAL_SYSTEM_DISCOVERER,
32
+ ROLE_DEVELOPER,
33
+ ROLE_DEPLOYMENT_ADMIN,
34
+ ROLE_PLATFORM_ADMIN
24
35
  ]);
25
36
 
26
37
  const WRITE_ROLES = Object.freeze([
27
- 'aifabrix-developer',
28
- 'aifabrix-deployment-admin',
29
- 'aifabrix-platform-admin'
38
+ ROLE_DEVELOPER,
39
+ ROLE_DEPLOYMENT_ADMIN,
40
+ ROLE_PLATFORM_ADMIN
30
41
  ]);
31
42
 
32
- const APPLY_ROLES = Object.freeze(['aifabrix-deployment-admin', 'aifabrix-platform-admin']);
43
+ const APPLY_ROLES = Object.freeze([ROLE_DEPLOYMENT_ADMIN, ROLE_PLATFORM_ADMIN]);
33
44
 
34
45
  const CERTIFY_ROLES = Object.freeze([
35
- 'aifabrix-security-admin',
36
- 'aifabrix-compliance-admin',
37
- 'aifabrix-ai-trust-operator',
38
- 'aifabrix-platform-admin'
46
+ ROLE_SECURITY_ADMIN,
47
+ ROLE_COMPLIANCE_ADMIN,
48
+ ROLE_AI_TRUST_OPERATOR,
49
+ ROLE_PLATFORM_ADMIN
39
50
  ]);
40
51
 
41
52
  /** @type {ReadonlyMap<string, string>} */
@@ -11,6 +11,10 @@
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { loadConfigFile } = require('../utils/config-format');
14
+ const {
15
+ pickCatalogAdminRoleValue,
16
+ pickCatalogReaderRoleValue
17
+ } = require('./repair-rbac-catalog-defaults');
14
18
 
15
19
  const READ_ONLY_HTTP_METHODS = new Set(['GET', 'HEAD']);
16
20
 
@@ -19,23 +23,6 @@ function roleValues(rbac) {
19
23
  return roles.map((r) => r?.value).filter((v) => typeof v === 'string' && v.trim());
20
24
  }
21
25
 
22
- function pickAdminRoleValue(roleValuesList, systemKey) {
23
- return (
24
- roleValuesList.find((v) => v === `${systemKey}-admin`) ||
25
- roleValuesList.find((v) => /-admin$/.test(v)) ||
26
- roleValuesList[0] ||
27
- null
28
- );
29
- }
30
-
31
- function pickReaderRoleValue(roleValuesList, systemKey) {
32
- return (
33
- roleValuesList.find((v) => v === `${systemKey}-reader`) ||
34
- roleValuesList.find((v) => /-reader$/.test(v)) ||
35
- null
36
- );
37
- }
38
-
39
26
  function capabilityFromPermissionName(name) {
40
27
  if (typeof name !== 'string' || !name.includes(':')) return null;
41
28
  return name.split(':')[1] || null;
@@ -71,8 +58,8 @@ function ensureNonEmptyPermissionRoles(rbac, systemKey, changes) {
71
58
  const values = roleValues(rbac);
72
59
  if (!Array.isArray(rbac.permissions) || values.length === 0) return false;
73
60
 
74
- const adminValue = pickAdminRoleValue(values, systemKey);
75
- const readerValue = pickReaderRoleValue(values, systemKey);
61
+ const adminValue = pickCatalogAdminRoleValue(values, systemKey);
62
+ const readerValue = pickCatalogReaderRoleValue(values, systemKey);
76
63
  let updated = false;
77
64
 
78
65
  for (const perm of rbac.permissions) {
@@ -103,8 +90,8 @@ function ensureNonEmptyPermissionRoles(rbac, systemKey, changes) {
103
90
  */
104
91
  function ensureReaderRoleOnReadPermissions(rbac, appPath, datasourceFiles, systemKey, changes) {
105
92
  const values = roleValues(rbac);
106
- const readerValue = pickReaderRoleValue(values, systemKey);
107
- const adminValue = pickAdminRoleValue(values, systemKey);
93
+ const readerValue = pickCatalogReaderRoleValue(values, systemKey);
94
+ const adminValue = pickCatalogAdminRoleValue(values, systemKey);
108
95
  if (!readerValue || !adminValue) return false;
109
96
  if (!Array.isArray(rbac.permissions) || rbac.permissions.length === 0) return false;
110
97
 
@@ -29,6 +29,7 @@ const {
29
29
  ensureOrchestrationResourceType
30
30
  } = require('./repair-rbac-platform-preset');
31
31
  const { trackRepairWrite } = require('./repair-changed-files');
32
+ const { ensureDefaultRoles } = require('./repair-rbac-catalog-defaults');
32
33
 
33
34
  const DEFAULT_CAPABILITIES = ['list', 'get', 'create', 'update', 'delete'];
34
35
 
@@ -269,48 +270,23 @@ function addMissingPermissions(rbac, permissionNames, changes) {
269
270
  return updated;
270
271
  }
271
272
 
272
- /**
273
- * Ensures default Admin and Reader roles if rbac.roles is empty. Mutates rbac.
274
- * @param {Object} rbac - RBAC object (mutated)
275
- * @param {string} systemKey - System key
276
- * @param {string} displayName - Display name
277
- * @param {string[]} changes - Array to append to
278
- * @returns {boolean}
279
- */
280
- function ensureDefaultRoles(rbac, systemKey, displayName, changes) {
281
- const hasRoles = Array.isArray(rbac.roles) && rbac.roles.length > 0;
282
- if (hasRoles) return false;
283
- const adminValue = `${systemKey}-admin`;
284
- const readerValue = `${systemKey}-reader`;
285
- rbac.roles = [
286
- { name: `${displayName} Admin`, value: adminValue, description: `Full access to all ${displayName} operations`, groups: [] },
287
- { name: `${displayName} Reader`, value: readerValue, description: `Read-only access to all ${displayName} data`, groups: [] }
288
- ];
289
- const listGetPerms = (rbac.permissions || [])
290
- .filter(p => p?.name && (p.name.split(':')[1] === 'list' || p.name.split(':')[1] === 'get'))
291
- .map(p => p.name);
292
- for (const p of rbac.permissions || []) {
293
- if (!p.roles) p.roles = [];
294
- if (p.name && listGetPerms.includes(p.name) && !p.roles.includes(readerValue)) p.roles.push(readerValue);
295
- if (!p.roles.includes(adminValue)) p.roles.push(adminValue);
296
- }
297
- changes.push('Added default Admin and Reader roles to rbac file');
298
- return true;
299
- }
300
-
301
273
  /**
302
274
  * @param {Object} opts
303
275
  * @returns {boolean}
304
276
  */
305
277
  function applyRbacPresetRoles(opts) {
306
- const { rbac, rbacPreset, systemKey, displayName, appPath, datasourceFiles, changes } = opts;
278
+ const { rbac, rbacPreset, appPath, datasourceFiles, changes } = opts;
307
279
  if (rbacPreset === 'platform') {
308
- return (
309
- ensurePlatformRolesOnRbac(rbac, changes) ||
310
- applyPlatformRolesToPermissions(rbac, appPath, datasourceFiles, changes)
280
+ const rolesUpdated = ensurePlatformRolesOnRbac(rbac, changes);
281
+ const permsUpdated = applyPlatformRolesToPermissions(
282
+ rbac,
283
+ appPath,
284
+ datasourceFiles,
285
+ changes
311
286
  );
287
+ return rolesUpdated || permsUpdated;
312
288
  }
313
- return ensureDefaultRoles(rbac, systemKey, displayName, changes);
289
+ return ensureDefaultRoles(rbac, changes);
314
290
  }
315
291
 
316
292
  /**
@@ -9,6 +9,11 @@
9
9
  const path = require('path');
10
10
  const fs = require('fs');
11
11
  const { loadConfigFile } = require('../utils/config-format');
12
+ const {
13
+ catalogAdminReaderRoles,
14
+ pickCatalogAdminRoleValue,
15
+ pickCatalogReaderRoleValue
16
+ } = require('./repair-rbac-catalog-defaults');
12
17
 
13
18
  function _safeString(v) {
14
19
  return typeof v === 'string' && v.trim() ? v.trim() : '';
@@ -23,32 +28,29 @@ function _normalizeOperationKey(opKey) {
23
28
  return cleaned.replace(/-+/g, '-').replace(/^-+|-+$/g, '');
24
29
  }
25
30
 
26
- function _ensureDefaultRoles(systemParsed, systemKey) {
31
+ function _roleValuesFromSystem(systemParsed) {
32
+ const roles = Array.isArray(systemParsed.roles) ? systemParsed.roles : [];
33
+ return roles
34
+ .map((r) => (r && typeof r === 'object' ? _safeString(r.value) : ''))
35
+ .filter(Boolean);
36
+ }
37
+
38
+ function _ensureDefaultRoles(systemParsed) {
27
39
  if (Array.isArray(systemParsed.roles) && systemParsed.roles.length > 0) return false;
28
- const key = _safeString(systemKey) || _safeString(systemParsed?.key) || 'system';
29
- const display = _safeString(systemParsed?.displayName) || key;
30
- systemParsed.roles = [
31
- {
32
- name: `${display} Admin`,
33
- value: `${key}-admin`,
34
- description: `Full access to all ${display} operations`,
35
- groups: []
36
- },
37
- {
38
- name: `${display} Reader`,
39
- value: `${key}-reader`,
40
- description: `Read-only access to all ${display} data`,
41
- groups: []
42
- }
43
- ];
40
+ const seeded = catalogAdminReaderRoles();
41
+ if (seeded.length < 2) return false;
42
+ systemParsed.roles = seeded;
44
43
  return true;
45
44
  }
46
45
 
47
46
  function _pickRoleValue(systemParsed, suffix, fallback) {
48
- const roles = Array.isArray(systemParsed.roles) ? systemParsed.roles : [];
49
- const vals = roles
50
- .map((r) => r && typeof r === 'object' ? _safeString(r.value) : '')
51
- .filter(Boolean);
47
+ const vals = _roleValuesFromSystem(systemParsed);
48
+ if (suffix === '-admin') {
49
+ return pickCatalogAdminRoleValue(vals, undefined, fallback);
50
+ }
51
+ if (suffix === '-reader') {
52
+ return pickCatalogReaderRoleValue(vals, undefined, vals[0] || fallback);
53
+ }
52
54
  return vals.find((v) => v.endsWith(suffix)) || vals[0] || fallback;
53
55
  }
54
56
 
@@ -145,7 +147,7 @@ function ensureSystemPermissionsForAutoRbac(appPath, systemParsed, datasourceFil
145
147
  const desired = _collectAutoRbacOperationPermissions(appPath, datasourceFiles);
146
148
  if (desired.size === 0) return false;
147
149
 
148
- const rolesAdded = _ensureDefaultRoles(systemParsed, systemParsed?.key);
150
+ const rolesAdded = _ensureDefaultRoles(systemParsed);
149
151
  if (rolesAdded) {
150
152
  changes.push('Added default Admin/Reader roles (required for RBAC permissions)');
151
153
  }