@aifabrix/builder 2.45.5 → 2.47.0

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 (355) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/.npmrc.token +1 -0
  5. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  8. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  9. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  10. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  14. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  15. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  16. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  17. package/.temp/validation/summary.txt +5 -0
  18. package/README.md +43 -17
  19. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  20. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  21. package/jest.config.default.js +2 -1
  22. package/jest.config.isolated.js +2 -1
  23. package/jest.global-live-fabrix-hooks.js +36 -0
  24. package/jest.isolated-projects.js +146 -0
  25. package/jest.projects.js +65 -136
  26. package/lib/api/api-response-errors.js +124 -0
  27. package/lib/api/dataplane-health.api.js +19 -7
  28. package/lib/api/governance-scenario-pack.api.js +159 -0
  29. package/lib/api/index.js +43 -3
  30. package/lib/api/lifecycle.api.js +98 -0
  31. package/lib/api/pipeline.api.js +3 -1
  32. package/lib/api/records-bulk.api.js +14 -1
  33. package/lib/api/resource-types.api.js +107 -0
  34. package/lib/api/runtime-configuration.api.js +72 -0
  35. package/lib/api/types/resource-types.types.js +45 -0
  36. package/lib/api/user-groups.api.js +9 -9
  37. package/lib/api/users.api.js +8 -8
  38. package/lib/api/validation-runner.js +3 -1
  39. package/lib/api/wizard.api.js +10 -2
  40. package/lib/app/deploy-config.js +4 -1
  41. package/lib/app/down.js +78 -27
  42. package/lib/app/index.js +2 -2
  43. package/lib/app/push.js +4 -5
  44. package/lib/app/register.js +7 -4
  45. package/lib/app/run-env-compose.js +84 -30
  46. package/lib/app/run-resolve-image.js +2 -1
  47. package/lib/app/run-stop-containers.js +113 -0
  48. package/lib/app/run.js +1 -31
  49. package/lib/app/show-display.js +35 -0
  50. package/lib/app/show.js +32 -1
  51. package/lib/build/index.js +22 -5
  52. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  53. package/lib/certification/sync-system-certification.js +13 -6
  54. package/lib/channels/add-channel.js +348 -0
  55. package/lib/channels/channel-artifact-generator.js +141 -0
  56. package/lib/channels/channel-audit.js +101 -0
  57. package/lib/channels/channel-auth-context.js +37 -0
  58. package/lib/channels/channel-credential.js +200 -0
  59. package/lib/channels/channel-display-name.js +64 -0
  60. package/lib/channels/channel-external-system.js +106 -0
  61. package/lib/channels/channel-integration-generator.js +274 -0
  62. package/lib/channels/channel-probe.js +89 -0
  63. package/lib/channels/channel-runtime-resolver.js +152 -0
  64. package/lib/channels/channel-status-display.js +141 -0
  65. package/lib/channels/channel-targets.js +115 -0
  66. package/lib/cli/index.js +20 -4
  67. package/lib/cli/infra-guided-auth.js +109 -0
  68. package/lib/cli/infra-guided.js +84 -81
  69. package/lib/cli/setup-app.help.js +0 -31
  70. package/lib/cli/setup-app.js +82 -5
  71. package/lib/cli/setup-app.test-commands.js +5 -57
  72. package/lib/cli/setup-auth.js +25 -0
  73. package/lib/cli/setup-certification-commands.js +147 -0
  74. package/lib/cli/setup-certification.help.js +141 -0
  75. package/lib/cli/setup-environment.js +19 -2
  76. package/lib/cli/setup-external-system-help.js +62 -0
  77. package/lib/cli/setup-external-system.js +25 -3
  78. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  79. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  80. package/lib/cli/setup-infra.js +9 -1
  81. package/lib/cli/setup-platform.js +6 -1
  82. package/lib/cli/setup-secrets.js +33 -1
  83. package/lib/cli/setup-utility-help-after.js +25 -2
  84. package/lib/cli/setup-utility-repair.js +198 -0
  85. package/lib/cli/setup-utility-resolve.js +44 -5
  86. package/lib/cli/setup-utility-validate.js +197 -0
  87. package/lib/cli/setup-utility.js +4 -239
  88. package/lib/commands/app-logs.js +54 -26
  89. package/lib/commands/app.js +41 -0
  90. package/lib/commands/auth-status-client-tokens.js +214 -0
  91. package/lib/commands/auth-status-dataplane-version.js +142 -68
  92. package/lib/commands/auth-status.js +43 -54
  93. package/lib/commands/channel.js +58 -0
  94. package/lib/commands/datasource-test-trust-cli.js +27 -23
  95. package/lib/commands/datasource-unified-test-cli.js +6 -3
  96. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  97. package/lib/commands/datasource.js +8 -8
  98. package/lib/commands/dev-show-display.js +8 -1
  99. package/lib/commands/governance-cli-shared.js +94 -0
  100. package/lib/commands/governance-command-action.js +132 -0
  101. package/lib/commands/governance-download-external.js +34 -0
  102. package/lib/commands/governance-generate-external.js +71 -0
  103. package/lib/commands/governance-upload-external.js +36 -0
  104. package/lib/commands/governance-verify-external.js +64 -0
  105. package/lib/commands/governance.js +144 -0
  106. package/lib/commands/identity-apply-sync.js +49 -8
  107. package/lib/commands/identity-group-cli.js +66 -26
  108. package/lib/commands/identity-membership-role-cache.js +58 -37
  109. package/lib/commands/identity-shared.js +22 -1
  110. package/lib/commands/identity-user-cli.js +80 -34
  111. package/lib/commands/lifecycle-command-action.js +123 -0
  112. package/lib/commands/lifecycle-external.js +268 -0
  113. package/lib/commands/login.js +11 -2
  114. package/lib/commands/repair-auth-apply.js +153 -0
  115. package/lib/commands/repair-auth-config.js +8 -13
  116. package/lib/commands/repair-changed-files.js +41 -0
  117. package/lib/commands/repair-datasource-com-v2.js +15 -0
  118. package/lib/commands/repair-datasource-keys.js +4 -1
  119. package/lib/commands/repair-datasource-run.js +59 -0
  120. package/lib/commands/repair-datasource-service-contract.js +206 -0
  121. package/lib/commands/repair-datasource-test-payload.js +259 -0
  122. package/lib/commands/repair-datasource.js +49 -64
  123. package/lib/commands/repair-env-template.js +76 -25
  124. package/lib/commands/repair-external-run.js +216 -0
  125. package/lib/commands/repair-openapi-sync.js +49 -4
  126. package/lib/commands/repair-persist.js +4 -0
  127. package/lib/commands/repair-rbac.js +52 -1
  128. package/lib/commands/repair-steps.js +160 -0
  129. package/lib/commands/repair-system-alignment.js +7 -4
  130. package/lib/commands/repair.js +29 -296
  131. package/lib/commands/resource-type.js +371 -0
  132. package/lib/commands/secrets-get.js +184 -0
  133. package/lib/commands/secrets-remove-all.js +3 -1
  134. package/lib/commands/setup-image-refresh.js +146 -0
  135. package/lib/commands/setup-modes.js +158 -162
  136. package/lib/commands/setup-platform-auth.js +76 -0
  137. package/lib/commands/setup-prompts-admin.js +244 -0
  138. package/lib/commands/setup-prompts-platform-mode.js +70 -0
  139. package/lib/commands/setup-prompts-validators.js +36 -0
  140. package/lib/commands/setup-prompts.js +24 -76
  141. package/lib/commands/setup-run-context.js +45 -0
  142. package/lib/commands/setup-spinners.js +39 -0
  143. package/lib/commands/setup.js +27 -5
  144. package/lib/commands/teardown.js +39 -0
  145. package/lib/commands/test-e2e-external.js +24 -8
  146. package/lib/commands/test-governance-external.js +3 -24
  147. package/lib/commands/test-trust-external.js +48 -100
  148. package/lib/commands/up-common.js +57 -13
  149. package/lib/commands/up-dataplane.js +56 -15
  150. package/lib/commands/upload-publish-context.js +75 -0
  151. package/lib/commands/upload-sidecar-followups.js +89 -0
  152. package/lib/commands/upload.js +113 -110
  153. package/lib/commands/verify-governance-combined-external.js +61 -0
  154. package/lib/commands/verify-governance-command-action.js +64 -0
  155. package/lib/commands/verify-governance-db.js +102 -0
  156. package/lib/commands/verify-governance-external.js +77 -0
  157. package/lib/commands/verify-governance-result-builders.js +71 -0
  158. package/lib/commands/verify-operations-command-action.js +68 -0
  159. package/lib/commands/verify-operations-external.js +268 -0
  160. package/lib/commands/verify-operations-persist.js +75 -0
  161. package/lib/commands/verify-operations-step-plan.js +68 -0
  162. package/lib/commands/verify-operations-steps.js +111 -0
  163. package/lib/commands/verify-trust-command-action.js +73 -0
  164. package/lib/commands/verify-trust-external.js +59 -0
  165. package/lib/commands/wizard-config-normalizer.js +43 -23
  166. package/lib/commands/wizard-core.js +2 -0
  167. package/lib/core/admin-password-generator.js +38 -0
  168. package/lib/core/admin-secrets-bundle.js +111 -0
  169. package/lib/core/admin-secrets-env-overlay.js +62 -0
  170. package/lib/core/admin-secrets.js +32 -4
  171. package/lib/core/config-attach-extensions.js +3 -0
  172. package/lib/core/config-platform-controller.js +44 -0
  173. package/lib/core/config-setup-installation-profile.js +42 -0
  174. package/lib/core/config-setup-platform-mode.js +46 -0
  175. package/lib/core/config.js +46 -36
  176. package/lib/core/secrets-admin-env.js +6 -2
  177. package/lib/core/secrets-ensure.js +4 -0
  178. package/lib/core/secrets-env-content.js +54 -48
  179. package/lib/core/secrets-env-write.js +10 -1
  180. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  181. package/lib/core/setup-installation-profile.js +43 -0
  182. package/lib/core/setup-platform-mode.js +90 -0
  183. package/lib/datasource/agent-trust-debug-log.js +1 -1
  184. package/lib/datasource/agent-trust-run.js +5 -3
  185. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  186. package/lib/datasource/audit-evidence-extract.js +7 -4
  187. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  188. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  189. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  190. package/lib/datasource/capability/capability-manifest.js +254 -0
  191. package/lib/datasource/capability/capability-resolve.js +3 -4
  192. package/lib/datasource/capability/copy-operations.js +5 -3
  193. package/lib/datasource/capability/remove-operations.js +2 -1
  194. package/lib/datasource/capability/run-capability-copy.js +32 -4
  195. package/lib/datasource/capability/run-capability-diff.js +3 -3
  196. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  197. package/lib/datasource/capability/run-capability-edit.js +4 -7
  198. package/lib/datasource/capability/run-capability-relate.js +2 -4
  199. package/lib/datasource/capability/run-capability-remove.js +2 -4
  200. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  201. package/lib/datasource/datasource-identifier.js +2 -1
  202. package/lib/datasource/datasource-load-export-context.js +3 -0
  203. package/lib/datasource/datasource-validate-display.js +1 -1
  204. package/lib/datasource/field-reference-validator.js +24 -3
  205. package/lib/datasource/log-cleaner.js +2 -2
  206. package/lib/datasource/log-viewer.js +8 -8
  207. package/lib/datasource/orphan-profiles-validator.js +98 -0
  208. package/lib/datasource/test-e2e.js +4 -1
  209. package/lib/datasource/unified-validation-run-body.js +3 -1
  210. package/lib/datasource/unified-validation-run.js +5 -2
  211. package/lib/datasource/validate.js +41 -23
  212. package/lib/deployment/environment-poll-status.js +138 -0
  213. package/lib/deployment/environment.js +42 -34
  214. package/lib/external-system/download-helpers.js +87 -0
  215. package/lib/external-system/download.js +78 -138
  216. package/lib/external-system/generator.js +1 -55
  217. package/lib/external-system/integration-auth-context.js +45 -0
  218. package/lib/external-system/test-auth.js +12 -5
  219. package/lib/external-system/test.js +16 -21
  220. package/lib/generator/builders.js +11 -1
  221. package/lib/generator/external-controller-manifest.js +18 -0
  222. package/lib/generator/split.js +3 -1
  223. package/lib/generator/wizard.js +1 -28
  224. package/lib/governance/governance-cli-errors.js +96 -0
  225. package/lib/governance/governance-download-display.js +38 -0
  226. package/lib/governance/governance-run-display.js +215 -25
  227. package/lib/identity/fk-reference.js +65 -0
  228. package/lib/identity/identity-apply-core.js +13 -58
  229. package/lib/identity/identity-apply-phases.js +5 -1
  230. package/lib/identity/identity-display.js +243 -0
  231. package/lib/identity/identity-response-helpers.js +52 -0
  232. package/lib/identity/identity-sync-errors.js +62 -0
  233. package/lib/identity/resolve-user-id.js +129 -0
  234. package/lib/identity/username-default.js +31 -0
  235. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  236. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  237. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  238. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  239. package/lib/infrastructure/helpers.js +23 -129
  240. package/lib/infrastructure/index.js +5 -6
  241. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  242. package/lib/lifecycle/certification-progress.js +231 -0
  243. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  244. package/lib/lifecycle/exit-codes.js +24 -0
  245. package/lib/lifecycle/json-envelope.js +25 -0
  246. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  247. package/lib/lifecycle/logger-quiet.js +29 -0
  248. package/lib/lifecycle/open-api-published.js +59 -0
  249. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  250. package/lib/lifecycle/product-model.js +196 -0
  251. package/lib/lifecycle/report-display-trust.js +70 -0
  252. package/lib/lifecycle/report-display.js +472 -0
  253. package/lib/lifecycle/scenario-upload.js +191 -0
  254. package/lib/lifecycle/verify-step-progress.js +87 -0
  255. package/lib/parameters/infra-kv-discovery.js +27 -2
  256. package/lib/protection/auth-context.js +10 -4
  257. package/lib/resolvers/datasource-resolver.js +2 -3
  258. package/lib/resolvers/resource-type-file.js +159 -0
  259. package/lib/schema/application-schema.json +17 -1
  260. package/lib/schema/datasource-test-run.schema.json +89 -6
  261. package/lib/schema/deployment-rules.yaml +2 -1
  262. package/lib/schema/external-datasource.schema.json +2947 -2619
  263. package/lib/schema/external-system.schema.json +1418 -761
  264. package/lib/schema/infra.parameter.yaml +45 -0
  265. package/lib/schema/type/credential-field-registry.json +103 -97
  266. package/lib/schema/type/document-storage.json +9 -5
  267. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  268. package/lib/utils/api.js +55 -31
  269. package/lib/utils/app-relative-path-env.js +112 -0
  270. package/lib/utils/app-run-containers.js +25 -2
  271. package/lib/utils/authentication-shape.js +224 -0
  272. package/lib/utils/build-copy.js +36 -5
  273. package/lib/utils/build-resolve-image.js +66 -12
  274. package/lib/utils/celery-companion-apps.js +53 -0
  275. package/lib/utils/cli-secrets-error-format.js +83 -21
  276. package/lib/utils/cli-utils.js +20 -4
  277. package/lib/utils/compose-celery-worker.js +23 -0
  278. package/lib/utils/compose-generate-docker-compose.js +13 -1
  279. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  280. package/lib/utils/config-secrets-path.js +46 -0
  281. package/lib/utils/configuration-env-resolver.js +36 -0
  282. package/lib/utils/controller-url.js +88 -1
  283. package/lib/utils/credential-secrets-env-kv.js +216 -0
  284. package/lib/utils/credential-secrets-env.js +175 -257
  285. package/lib/utils/dataplane-resolver.js +18 -2
  286. package/lib/utils/declarative-url-ports.js +27 -4
  287. package/lib/utils/deployment-auth-mode.js +41 -0
  288. package/lib/utils/developer-volume-id.js +33 -0
  289. package/lib/utils/docker-build.js +1 -4
  290. package/lib/utils/env-copy.js +14 -9
  291. package/lib/utils/env-kv-orphan-lines.js +43 -0
  292. package/lib/utils/error-formatters/network-errors.js +13 -4
  293. package/lib/utils/external-env-template.js +11 -3
  294. package/lib/utils/external-system-readiness-core.js +10 -0
  295. package/lib/utils/external-system-readiness-display.js +80 -23
  296. package/lib/utils/external-system-validators.js +8 -2
  297. package/lib/utils/health-check.js +4 -1
  298. package/lib/utils/help-builder.js +13 -8
  299. package/lib/utils/keycloak-realm-ready.js +101 -0
  300. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  301. package/lib/utils/manifest-source-emit.js +2 -0
  302. package/lib/utils/paths.js +25 -0
  303. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  304. package/lib/utils/platform-controller-url.js +157 -0
  305. package/lib/utils/platform-kv-ref.js +57 -0
  306. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  307. package/lib/utils/postgres-wipe.js +9 -2
  308. package/lib/utils/resolve-env-mode.js +47 -0
  309. package/lib/utils/runtime-configuration-download.js +189 -0
  310. package/lib/utils/runtime-configuration-values.js +119 -0
  311. package/lib/utils/schema-loader.js +19 -4
  312. package/lib/utils/secrets-generator.js +31 -25
  313. package/lib/utils/secrets-missing-error.js +164 -0
  314. package/lib/utils/service-url-label.js +81 -0
  315. package/lib/utils/setup-quiet-output.js +25 -0
  316. package/lib/utils/template-helpers.js +15 -0
  317. package/lib/utils/token-manager-client-credentials.js +201 -0
  318. package/lib/utils/token-manager-messages.js +8 -0
  319. package/lib/utils/token-manager-refresh.js +12 -2
  320. package/lib/utils/token-manager.js +86 -105
  321. package/lib/utils/upload-pipeline-ui.js +12 -5
  322. package/lib/utils/upload-sync-options.js +42 -0
  323. package/lib/utils/url-declarative-public-base.js +21 -3
  324. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  325. package/lib/utils/validation-poll-ui.js +8 -0
  326. package/lib/utils/validation-run-stage.js +45 -0
  327. package/lib/utils/worker-sidecars.js +142 -0
  328. package/lib/validation/external-manifest-validator.js +2 -0
  329. package/lib/validation/runtime-configuration-rules.js +98 -0
  330. package/lib/validation/validate-display-dimensions.js +154 -0
  331. package/lib/validation/validate-display-sections.js +276 -0
  332. package/lib/validation/validate-display-steps.js +67 -0
  333. package/lib/validation/validate-display.js +24 -402
  334. package/package.json +1 -1
  335. package/templates/applications/dataplane/application.yaml +7 -1
  336. package/templates/applications/dataplane/env.template +50 -14
  337. package/templates/applications/dataplane/rbac.yaml +108 -1
  338. package/templates/applications/miso-controller/application.yaml +2 -2
  339. package/templates/applications/miso-controller/env.template +53 -122
  340. package/templates/applications/miso-controller/rbac.yaml +28 -13
  341. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  342. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  343. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  344. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  345. package/templates/channels/slack/install.template.md.hbs +49 -0
  346. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  347. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  348. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  349. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  350. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  351. package/templates/external-system/README.md.hbs +74 -17
  352. package/templates/python/docker-compose.hbs +75 -18
  353. package/templates/typescript/docker-compose.hbs +70 -17
  354. package/lib/commands/test-governance-command-action.js +0 -55
  355. package/lib/commands/test-trust-command-action.js +0 -118
@@ -8,8 +8,8 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const fs = require('fs');
12
11
  const path = require('path');
12
+ const { existsSync, readFileSync } = require('../internal/fs-real-sync');
13
13
 
14
14
  /**
15
15
  * @param {object|null} schema - Parsed external-datasource.schema.json
@@ -33,8 +33,8 @@ function extractStandardOperationOrderFromSchema(schema) {
33
33
  */
34
34
  function tryReadJsonFile(p) {
35
35
  try {
36
- if (!p || !fs.existsSync(p)) return null;
37
- return JSON.parse(fs.readFileSync(p, 'utf8'));
36
+ if (!p || !existsSync(p)) return null;
37
+ return JSON.parse(readFileSync(p, 'utf8'));
38
38
  } catch {
39
39
  return null;
40
40
  }
@@ -48,25 +48,74 @@ function tryReadJsonFile(p) {
48
48
  function resolveExternalDatasourceSchemaPath() {
49
49
  if (process.env.AIFABRIX_EXTERNAL_DATASOURCE_SCHEMA) {
50
50
  const envPath = String(process.env.AIFABRIX_EXTERNAL_DATASOURCE_SCHEMA).trim();
51
- if (envPath && fs.existsSync(envPath)) return envPath;
51
+ if (envPath && existsSync(envPath)) return envPath;
52
52
  }
53
53
  const candidates = [
54
+ path.join(__dirname, '../schema/external-datasource.schema.json'),
54
55
  path.join(process.cwd(), 'app/schemas/json/external-datasource.schema.json'),
55
56
  path.join(process.cwd(), 'aifabrix-dataplane/app/schemas/json/external-datasource.schema.json'),
56
57
  path.join(process.cwd(), '..', 'aifabrix-dataplane', 'app/schemas/json/external-datasource.schema.json'),
57
58
  path.join(__dirname, '../../../aifabrix-dataplane/app/schemas/json/external-datasource.schema.json')
58
59
  ];
59
60
  for (const c of candidates) {
60
- if (c && fs.existsSync(c)) return c;
61
+ if (c && existsSync(c)) return c;
61
62
  }
62
63
  return null;
63
64
  }
64
65
 
65
66
  const FALLBACK_PATH = path.join(__dirname, '../schema/cip-capacity-display.fallback.json');
66
67
 
68
+ /**
69
+ * When schema/fallback JSON cannot be read (missing file, parse error, or Jest fs mocks in a shared worker).
70
+ * Must stay aligned with `cip-capacity-display.fallback.json` and dataplane `cipDefinition.operations`.
71
+ */
72
+ const HARDCODED_STANDARD_OPERATION_ORDER = ['list', 'get', 'create', 'update', 'delete'];
73
+ const HARDCODED_DISPLAY_ALIASES = { create: 'insert (create)' };
74
+
67
75
  /** @type {{ standardOrder: string[], aliases: Record<string, string> }|null} */
68
76
  let _cache = null;
69
77
 
78
+ /**
79
+ * @returns {{ standardOperationOrder: string[], displayAliases: Record<string, string> }}
80
+ */
81
+ function readFallbackDisplayConfig() {
82
+ const fromFile = tryReadJsonFile(FALLBACK_PATH);
83
+ const order =
84
+ fromFile && Array.isArray(fromFile.standardOperationOrder) && fromFile.standardOperationOrder.length
85
+ ? fromFile.standardOperationOrder
86
+ : HARDCODED_STANDARD_OPERATION_ORDER;
87
+ const aliases =
88
+ fromFile && fromFile.displayAliases && typeof fromFile.displayAliases === 'object'
89
+ ? fromFile.displayAliases
90
+ : HARDCODED_DISPLAY_ALIASES;
91
+ return { standardOperationOrder: order, displayAliases: aliases };
92
+ }
93
+
94
+ /**
95
+ * Prefer schema key order, then append any fallback standard ops missing from the schema
96
+ * (guards partial/stale schema files and cross-test cache pollution in Jest).
97
+ * @param {string[]|null} fromSchema
98
+ * @param {string[]} fbOrder
99
+ * @returns {string[]}
100
+ */
101
+ function mergeStandardOperationOrders(fromSchema, fbOrder) {
102
+ const schemaOps = Array.isArray(fromSchema)
103
+ ? fromSchema.map(op => String(op).trim().toLowerCase()).filter(Boolean)
104
+ : [];
105
+ const fallbackOps = Array.isArray(fbOrder)
106
+ ? fbOrder.map(op => String(op).trim().toLowerCase()).filter(Boolean)
107
+ : [];
108
+ if (!schemaOps.length) return fallbackOps;
109
+ const seen = new Set(schemaOps);
110
+ const merged = [...schemaOps];
111
+ for (const op of fallbackOps) {
112
+ if (seen.has(op)) continue;
113
+ seen.add(op);
114
+ merged.push(op);
115
+ }
116
+ return merged;
117
+ }
118
+
70
119
  /**
71
120
  * @returns {{ standardOrder: string[], aliases: Record<string, string> }}
72
121
  */
@@ -75,16 +124,12 @@ function getCipCapacityDisplayConfig() {
75
124
  const schemaPath = resolveExternalDatasourceSchemaPath();
76
125
  const schema = schemaPath ? tryReadJsonFile(schemaPath) : null;
77
126
  const fromSchema = extractStandardOperationOrderFromSchema(schema);
78
- const fallback = tryReadJsonFile(FALLBACK_PATH) || {};
79
- const fbOrder = Array.isArray(fallback.standardOperationOrder)
80
- ? fallback.standardOperationOrder
81
- : [];
82
- const standardOrder = fromSchema && fromSchema.length ? fromSchema : fbOrder;
83
- const aliases =
84
- fallback.displayAliases && typeof fallback.displayAliases === 'object'
85
- ? fallback.displayAliases
86
- : { create: 'insert (create)' };
87
- _cache = { standardOrder, aliases };
127
+ const fallback = readFallbackDisplayConfig();
128
+ const standardOrder = mergeStandardOperationOrders(
129
+ fromSchema,
130
+ fallback.standardOperationOrder
131
+ );
132
+ _cache = { standardOrder, aliases: fallback.displayAliases };
88
133
  return _cache;
89
134
  }
90
135
 
@@ -122,6 +167,7 @@ function clearCipCapacityDisplayConfigCacheForTests() {
122
167
 
123
168
  module.exports = {
124
169
  extractStandardOperationOrderFromSchema,
170
+ mergeStandardOperationOrders,
125
171
  resolveExternalDatasourceSchemaPath,
126
172
  getCipCapacityDisplayConfig,
127
173
  standardOperationRank,
@@ -104,6 +104,8 @@ function isStdoutTty() {
104
104
  function emitManifestMetadataLineIfTTY(logger, opts) {
105
105
  if (!logger || typeof logger.log !== 'function') return;
106
106
  if (!opts || opts.json || opts.envOnly) return;
107
+ const { isSetupQuietOutput } = require('./setup-quiet-output');
108
+ if (isSetupQuietOutput()) return;
107
109
  if (!isStdoutTty()) return;
108
110
  const appKey = opts.appKey && typeof opts.appKey === 'string' ? opts.appKey.trim() : '';
109
111
  const appPath = opts.appPath && typeof opts.appPath === 'string' ? opts.appPath.trim() : '';
@@ -315,6 +315,30 @@ function getDevDirectory(appName, developerId) {
315
315
  return baseDir;
316
316
  }
317
317
 
318
+ /**
319
+ * Target directory for `aifabrix create` — always under {@link process.cwd} (`builder/` or
320
+ * `integration/`) even when those folders do not exist yet. Platform system apps still
321
+ * materialize under {@link getAppsMaterializationParent}.
322
+ *
323
+ * Post-create lookup uses {@link getAppPath} / {@link getBuilderPath} / {@link getIntegrationPath}.
324
+ *
325
+ * @param {string} appName - Application name
326
+ * @param {string} [appType] - Application type (`external` or other)
327
+ * @returns {string} Absolute path to the new application directory
328
+ */
329
+ function getAppCreatePath(appName, appType) {
330
+ if (!appName || typeof appName !== 'string') {
331
+ throw new Error('App name is required and must be a string');
332
+ }
333
+ if (appType === 'external') {
334
+ return path.join(path.resolve(process.cwd()), 'integration', appName);
335
+ }
336
+ if (isSystemBuilderAppName(appName)) {
337
+ return path.join(getAppsMaterializationParent(), 'builder', appName);
338
+ }
339
+ return path.join(path.resolve(process.cwd()), 'builder', appName);
340
+ }
341
+
318
342
  /**
319
343
  * Gets the application path (builder or integration folder).
320
344
  * Matches {@link getBuilderPath} / {@link getIntegrationPath} (cwd `integration/` / `builder/`, then
@@ -881,6 +905,7 @@ module.exports = {
881
905
  getApplicationsBaseDir,
882
906
  getDevDirectory,
883
907
  getAppPath,
908
+ getAppCreatePath,
884
909
  getAppsMaterializationParent,
885
910
  getCwdIntegrationRoot,
886
911
  getCwdBuilderRoot,
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Retry dataplane pipeline upload with application client token when device Bearer fails.
3
+ *
4
+ * @fileoverview Pipeline publish auth fallback when device Bearer is rejected
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+ const { uploadApplicationViaPipeline } = require('../api/pipeline.api');
14
+ const { getDeploymentAuth } = require('./token-manager');
15
+ const { resolveControllerUrl } = require('./controller-url');
16
+ const config = require('../core/config');
17
+
18
+ /**
19
+ * @param {Object} apiResponse
20
+ * @returns {boolean}
21
+ */
22
+ function isPipelineAuthFailure(apiResponse) {
23
+ if (!apiResponse || apiResponse.success !== false) {
24
+ return false;
25
+ }
26
+ const status = Number(apiResponse.status) || 0;
27
+ if (status === 401 || status === 403) {
28
+ return true;
29
+ }
30
+ const text = String(
31
+ apiResponse.formattedError || apiResponse.error || apiResponse.message || ''
32
+ ).toLowerCase();
33
+ return text.includes('invalid token') || text.includes('unauthorized') || text.includes('authentication');
34
+ }
35
+
36
+ /**
37
+ * @param {string} systemKey
38
+ * @returns {Promise<Object|null>}
39
+ */
40
+ async function resolveClientTokenAuthForPipelineRetry(systemKey) {
41
+ try {
42
+ const controllerUrl = await resolveControllerUrl();
43
+ const environment = await config.resolveEnvironment();
44
+ return await getDeploymentAuth(controllerUrl, environment, systemKey, {
45
+ deploymentAuth: 'client-credentials'
46
+ });
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * POST /api/v1/pipeline/upload with optional client-token retry after device Bearer 401.
54
+ *
55
+ * @async
56
+ * @param {string} dataplaneUrl
57
+ * @param {Object} authConfig
58
+ * @param {Object} payload
59
+ * @param {string} systemKey - External system key for credential lookup on retry
60
+ * @returns {Promise<Object>}
61
+ */
62
+ async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfig, payload, systemKey) {
63
+ const first = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
64
+ if (!isPipelineAuthFailure(first) || authConfig.type !== 'bearer') {
65
+ return first;
66
+ }
67
+ const clientAuth = await resolveClientTokenAuthForPipelineRetry(systemKey);
68
+ if (!clientAuth || clientAuth.type === 'bearer') {
69
+ return first;
70
+ }
71
+ logger.log(
72
+ chalk.yellow(
73
+ 'Device login token was rejected for pipeline publish; retrying with application client token…'
74
+ )
75
+ );
76
+ return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload);
77
+ }
78
+
79
+ module.exports = {
80
+ isPipelineAuthFailure,
81
+ uploadApplicationViaPipelineWithAuthRetry
82
+ };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Absolute public base URLs for platform apps (Keycloak, Miso Controller, Dataplane).
3
+ * Used by setup (`platform-controller`), guided footers, and declarative routing.
4
+ *
5
+ * @fileoverview Platform app public URL resolution (Traefik / localhost + dev ports)
6
+ * @author AI Fabrix Team
7
+ * @version 1.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const config = require('../core/config');
16
+ const pathsUtil = require('./paths');
17
+ const { loadConfigFile } = require('./config-format');
18
+ const { computePublicUrlBaseString } = require('./url-declarative-public-base');
19
+ const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('./health-check-url');
20
+ const { getDevPorts } = require('./dev-config');
21
+ const { flagsForSetupPlatformMode } = require('../core/setup-platform-mode');
22
+
23
+ const MISO_CONTROLLER_APP = 'miso-controller';
24
+
25
+ /**
26
+ * Bundled `templates/applications/<app>/application.yaml` (fresh install has no builder copy yet).
27
+ *
28
+ * @param {string} appName
29
+ * @returns {Object|null}
30
+ */
31
+ function loadBundledApplicationVariables(appName) {
32
+ const templateConfigPath = path.join(
33
+ pathsUtil.getProjectRoot(),
34
+ 'templates',
35
+ 'applications',
36
+ appName,
37
+ 'application.yaml'
38
+ );
39
+ if (!fs.existsSync(templateConfigPath)) {
40
+ return null;
41
+ }
42
+ const variables = loadConfigFile(templateConfigPath);
43
+ return variables && typeof variables === 'object' ? variables : null;
44
+ }
45
+
46
+ /**
47
+ * Load platform app variables from materialized builder config, or from bundled template
48
+ * when `aifabrix setup` runs before `up-platform` has copied templates.
49
+ *
50
+ * @param {string} appName
51
+ * @returns {Object}
52
+ */
53
+ function loadAppVariables(appName) {
54
+ const builderPath = pathsUtil.getBuilderPath(appName);
55
+ if (fs.existsSync(builderPath)) {
56
+ try {
57
+ const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
58
+ return loadConfigFile(configPath) || {};
59
+ } catch (err) {
60
+ const msg = err && err.message ? String(err.message) : '';
61
+ if (!msg.includes('Application config not found')) {
62
+ throw err;
63
+ }
64
+ }
65
+ }
66
+ const fromTemplate = loadBundledApplicationVariables(appName);
67
+ if (fromTemplate) {
68
+ return fromTemplate;
69
+ }
70
+ throw new Error(
71
+ `Application config not found for platform app "${appName}". ` +
72
+ 'Run `aifabrix setup` or `aifabrix up-platform` to materialize builder templates.'
73
+ );
74
+ }
75
+
76
+ function computeLocalFullPlatformFrontDoorUrl(params) {
77
+ const { setupPlatformMode, remoteServer, developerIdNum, frontDoorRouting } = params;
78
+ if (!flagsForSetupPlatformMode(setupPlatformMode).traefik) {
79
+ return null;
80
+ }
81
+ if (remoteServer && String(remoteServer).trim()) {
82
+ return null;
83
+ }
84
+ const ports = getDevPorts(Number.isFinite(developerIdNum) ? developerIdNum : 0);
85
+ const mount = normalizeFrontDoorPatternForHealth(frontDoorRouting.pattern);
86
+ // Full local platform uses a single front-door port (3000 + devId×100), not the infra Traefik port.
87
+ return joinUrlPath(`http://localhost:${ports.app}`, mount);
88
+ }
89
+
90
+ /**
91
+ * Compute the absolute base URL for a platform app (same rules as guided `up-platform` footer).
92
+ *
93
+ * @async
94
+ * @param {string} appName - e.g. `miso-controller`, `keycloak`, `dataplane`
95
+ * @returns {Promise<string>}
96
+ */
97
+ async function computeAppBaseUrl(appName) {
98
+ const variables = loadAppVariables(appName);
99
+ const basePort = Number(variables.port || 3000);
100
+ const developerIdRaw = await config.getDeveloperId();
101
+ const developerIdNum =
102
+ typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
103
+ const userCfg = await config.getConfig();
104
+ const remoteServer = await config.getRemoteServer();
105
+ const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
106
+
107
+ const fd = variables.frontDoorRouting || null;
108
+ const frontDoorEnabled = Boolean(fd && fd.enabled === true);
109
+ const traefikOn = Boolean(userCfg && userCfg.traefik);
110
+ const pathActive = Boolean(traefikOn && frontDoorEnabled);
111
+ const setupPlatformMode = await config.getSetupPlatformMode();
112
+
113
+ const publicBase = computePublicUrlBaseString({
114
+ traefik: traefikOn,
115
+ pathActive,
116
+ hostTemplate: fd ? fd.host : null,
117
+ tls: fd ? fd.tls : true,
118
+ developerIdRaw,
119
+ remoteServer,
120
+ profile: 'docker',
121
+ listenPort: basePort,
122
+ developerIdNum,
123
+ infraTlsEnabled
124
+ });
125
+
126
+ if (pathActive) {
127
+ const localFullUrl = computeLocalFullPlatformFrontDoorUrl({
128
+ setupPlatformMode,
129
+ remoteServer,
130
+ developerIdNum,
131
+ frontDoorRouting: fd
132
+ });
133
+ if (localFullUrl) return localFullUrl;
134
+ const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
135
+ return joinUrlPath(publicBase, mount);
136
+ }
137
+
138
+ return String(publicBase).replace(/\/+$/, '');
139
+ }
140
+
141
+ /**
142
+ * Absolute Miso Controller URL for local platform install / setup auth.
143
+ *
144
+ * @async
145
+ * @returns {Promise<string>}
146
+ */
147
+ async function resolvePlatformControllerUrl() {
148
+ return computeAppBaseUrl(MISO_CONTROLLER_APP);
149
+ }
150
+
151
+ module.exports = {
152
+ MISO_CONTROLLER_APP,
153
+ loadAppVariables,
154
+ loadBundledApplicationVariables,
155
+ computeAppBaseUrl,
156
+ resolvePlatformControllerUrl
157
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Platform installation kv:// refs from infra.parameter.yaml (flat keys, no path segments).
3
+ *
4
+ * @fileoverview Distinguish platform catalog secrets from per-integration path-style kv refs
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getInfraParameterCatalog } = require('../parameters/infra-parameter-catalog');
12
+
13
+ /**
14
+ * Returns the single-segment path after kv:// when the ref has no slashes.
15
+ *
16
+ * @param {string} val - kv:// reference
17
+ * @returns {string|null}
18
+ */
19
+ function kvRefSegment(val) {
20
+ if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return null;
21
+ const after = val.trim().slice(5);
22
+ if (!after || after.includes('/')) return null;
23
+ return after;
24
+ }
25
+
26
+ /**
27
+ * True when val is kv://<key> and key exists in shipped infra.parameter.yaml.
28
+ *
29
+ * @param {string} val - kv:// reference
30
+ * @returns {boolean}
31
+ */
32
+ function isPlatformCatalogKvRef(val) {
33
+ const segment = kvRefSegment(val);
34
+ if (!segment) return false;
35
+ try {
36
+ const catalog = getInfraParameterCatalog();
37
+ return Boolean(catalog.findEntryForKey(segment));
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * True when repair should normalize a kv value to path-style kv://systemKey/variable.
45
+ * Platform catalog refs (e.g. kv://secrets-openaiApiKeyVault) are never legacy.
46
+ *
47
+ * @param {string} val - Value from authentication.security or configuration
48
+ * @returns {boolean}
49
+ */
50
+ function isLegacyKvValue(val) {
51
+ if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
52
+ if (isPlatformCatalogKvRef(val)) return false;
53
+ const after = val.trim().slice(5);
54
+ return after.includes('KeyVault') || !after.includes('/');
55
+ }
56
+
57
+ module.exports = { kvRefSegment, isPlatformCatalogKvRef, isLegacyKvValue };
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Recreate platform Postgres databases and roles after setup "wipe data".
3
+ *
4
+ * Infra init scripts run only on first volume creation; dropping DBs in a
5
+ * running Postgres container requires explicit bootstrap before Keycloak/Miso start.
6
+ *
7
+ * @fileoverview Platform database bootstrap after postgres wipe
8
+ * @author AI Fabrix Team
9
+ * @version 1.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const config = require('../core/config');
15
+ const adminSecrets = require('../core/admin-secrets');
16
+ const logger = require('./logger');
17
+ const chalk = require('chalk');
18
+ const { successGlyph } = require('./cli-test-layout-chalk');
19
+ const { getPostgresContainerName } = require('./postgres-wipe');
20
+ const dockerExec = require('./docker-exec');
21
+ const { isSetupQuietOutput } = require('./setup-quiet-output');
22
+
23
+ const SUPERUSER_ROLE = 'pgadmin';
24
+
25
+ /** @type {{ dbName: string, secretKey: string }[]} */
26
+ const PLATFORM_DATABASE_SLOTS = [
27
+ { dbName: 'keycloak', secretKey: 'databases-keycloak-0-passwordKeyVault' },
28
+ { dbName: 'miso', secretKey: 'databases-miso-controller-0-passwordKeyVault' },
29
+ { dbName: 'miso-logs', secretKey: 'databases-miso-controller-1-passwordKeyVault' },
30
+ { dbName: 'dataplane', secretKey: 'databases-dataplane-0-passwordKeyVault' },
31
+ { dbName: 'dataplane-vector', secretKey: 'databases-dataplane-1-passwordKeyVault' },
32
+ { dbName: 'dataplane-logs', secretKey: 'databases-dataplane-2-passwordKeyVault' },
33
+ { dbName: 'dataplane-records', secretKey: 'databases-dataplane-3-passwordKeyVault' }
34
+ ];
35
+
36
+ /**
37
+ * @param {string} dbName
38
+ * @returns {string}
39
+ */
40
+ function pgRoleName(dbName) {
41
+ return `${String(dbName).replace(/-/g, '_')}_user`;
42
+ }
43
+
44
+ /**
45
+ * @param {string} s
46
+ * @returns {string}
47
+ */
48
+ function escapePgString(s) {
49
+ if (s === null || s === undefined || typeof s !== 'string') return '';
50
+ return s.replace(/'/g, '\'\'');
51
+ }
52
+
53
+ /**
54
+ * @param {string} urlOrPassword
55
+ * @returns {string|null}
56
+ */
57
+ function extractPasswordFromUrlOrValue(urlOrPassword) {
58
+ if (typeof urlOrPassword !== 'string' || urlOrPassword.length === 0) return null;
59
+ if (!urlOrPassword.includes('://')) return urlOrPassword;
60
+ try {
61
+ const u = new URL(
62
+ urlOrPassword.replace(/\$\{DB_HOST\}/g, 'postgres').replace(/\$\{DB_PORT\}/g, '5432')
63
+ );
64
+ return u.password || null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Escape SQL for embedding in a double-quoted shell argument (-c "...").
72
+ * Bash expands `$` and `$$` (PID) inside double quotes; backslash-escape every `$`.
73
+ *
74
+ * @param {string} s
75
+ * @returns {string}
76
+ */
77
+ function escapeShellDoubleQuoted(s) {
78
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
79
+ }
80
+
81
+ /**
82
+ * @async
83
+ * @param {string} container
84
+ * @param {string} sql
85
+ * @param {string} adminPassword
86
+ * @returns {Promise<void>}
87
+ */
88
+ async function runPsqlCommand(container, sql, adminPassword, database = 'postgres') {
89
+ const escapedSql = escapeShellDoubleQuoted(sql);
90
+ const cmd =
91
+ `docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d ${database} ` +
92
+ `-v ON_ERROR_STOP=1 -c "${escapedSql}"`;
93
+ await dockerExec.execWithDockerEnv(cmd, { env: { PGPASSWORD: adminPassword } });
94
+ }
95
+
96
+ /**
97
+ * @async
98
+ * @param {string} container
99
+ * @param {string} dbName
100
+ * @param {string} role
101
+ * @param {string} password
102
+ * @param {string} adminPassword
103
+ * @returns {Promise<void>}
104
+ */
105
+ async function ensureRoleAndDatabase(container, dbName, role, password, adminPassword) {
106
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(dbName) || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(role)) {
107
+ throw new Error(`Unsafe database or role name: ${dbName} / ${role}`);
108
+ }
109
+ const pwd = escapePgString(password);
110
+ await runPsqlCommand(
111
+ container,
112
+ `DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${role}') THEN CREATE USER "${role}" WITH PASSWORD '${pwd}'; ELSE ALTER USER "${role}" WITH PASSWORD '${pwd}'; END IF; END $$;`,
113
+ adminPassword
114
+ );
115
+ const exists = await dockerExec.execWithDockerEnv(
116
+ `docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'"`,
117
+ { env: { PGPASSWORD: adminPassword } }
118
+ );
119
+ const hasDb = String((exists && exists.stdout) || '').trim() === '1';
120
+ if (!hasDb) {
121
+ await runPsqlCommand(
122
+ container,
123
+ `CREATE DATABASE "${dbName}" OWNER "${role}";`,
124
+ adminPassword
125
+ );
126
+ }
127
+ await runPsqlCommand(
128
+ container,
129
+ `GRANT ALL ON SCHEMA public TO "${role}"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${role}";`,
130
+ adminPassword,
131
+ dbName
132
+ );
133
+ }
134
+
135
+ /**
136
+ * @async
137
+ * @param {string} container
138
+ * @param {{ dbName: string, secretKey: string }} slot
139
+ * @param {Object} secrets
140
+ * @param {string} adminPassword
141
+ * @returns {Promise<string>}
142
+ */
143
+ async function ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword) {
144
+ const raw = secrets[slot.secretKey];
145
+ const password = extractPasswordFromUrlOrValue(raw);
146
+ if (!password || !password.trim()) {
147
+ throw new Error(
148
+ `Secret ${slot.secretKey} is missing or empty. Run "aifabrix up-infra" to ensure infra secrets.`
149
+ );
150
+ }
151
+ const role = pgRoleName(slot.dbName);
152
+ await ensureRoleAndDatabase(container, slot.dbName, role, password.trim(), adminPassword);
153
+ if (!isSetupQuietOutput()) {
154
+ logger.log(chalk.gray(` ${successGlyph()} Ensured database ${slot.dbName} (${role})`));
155
+ }
156
+ return slot.dbName;
157
+ }
158
+
159
+ /**
160
+ * @async
161
+ * @returns {Promise<{ container: string, adminPassword: string, secrets: Object }>}
162
+ */
163
+ async function resolveBootstrapContext() {
164
+ const devId = await config.getDeveloperId();
165
+ const container = getPostgresContainerName(devId);
166
+ const admin = await adminSecrets.readAndDecryptAdminSecrets();
167
+ const adminPassword = admin && admin.POSTGRES_PASSWORD;
168
+ if (!adminPassword) {
169
+ throw new Error('POSTGRES_PASSWORD not found in admin-secrets.env. Run "aifabrix up-infra" first.');
170
+ }
171
+ const secretsEnsure = require('../core/secrets-ensure');
172
+ await secretsEnsure.ensureInfraSecrets();
173
+ const { loadSecrets } = require('../core/secrets');
174
+ const secrets = await loadSecrets(undefined);
175
+ return { container, adminPassword, secrets };
176
+ }
177
+
178
+ /**
179
+ * Recreate platform DB users and databases from resolved kv secrets.
180
+ *
181
+ * @async
182
+ * @returns {Promise<string[]>} database names ensured
183
+ */
184
+ async function bootstrapPlatformPostgresDatabases() {
185
+ const { container, adminPassword, secrets } = await resolveBootstrapContext();
186
+ const ensured = [];
187
+ for (const slot of PLATFORM_DATABASE_SLOTS) {
188
+ ensured.push(await ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword));
189
+ }
190
+ return ensured;
191
+ }
192
+
193
+ module.exports = {
194
+ PLATFORM_DATABASE_SLOTS,
195
+ bootstrapPlatformPostgresDatabases,
196
+ pgRoleName,
197
+ extractPasswordFromUrlOrValue,
198
+ escapeShellDoubleQuoted
199
+ };