@aifabrix/builder 2.42.1 → 2.44.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 (392) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +2 -2
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +157 -0
  7. package/integration/{hubspot → hubspot-test}/application.json +6 -6
  8. package/integration/{hubspot → hubspot-test}/create-hubspot.js +10 -10
  9. package/integration/hubspot-test/env.template +4 -0
  10. package/integration/hubspot-test/hubspot-test-datasource-company.json +138 -0
  11. package/integration/hubspot-test/hubspot-test-datasource-contact.json +146 -0
  12. package/integration/hubspot-test/hubspot-test-datasource-deal.json +146 -0
  13. package/integration/hubspot-test/hubspot-test-datasource-users.json +76 -0
  14. package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +201 -24
  15. package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
  16. package/integration/hubspot-test/rbac.json +166 -0
  17. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
  18. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
  19. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
  20. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
  21. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
  22. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
  23. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  24. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
  25. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
  26. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
  27. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
  28. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
  29. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  30. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  31. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
  32. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
  33. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
  34. package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +1 -7
  35. package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +3 -3
  36. package/integration/{hubspot → hubspot-test}/test.js +137 -102
  37. package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
  38. package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
  39. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  40. package/integration/roundtrip-test-local/README.md +144 -0
  41. package/integration/roundtrip-test-local/application.yaml +13 -0
  42. package/integration/roundtrip-test-local/env.template +15 -0
  43. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  44. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  45. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  46. package/integration/roundtrip-test-local2/README.md +144 -0
  47. package/integration/roundtrip-test-local2/application.yaml +13 -0
  48. package/integration/roundtrip-test-local2/env.template +15 -0
  49. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  50. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  51. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  52. package/integration/test/wizard.yaml +8 -0
  53. package/jest.config.default.js +10 -0
  54. package/jest.config.integration.fixtures.js +22 -0
  55. package/jest.config.integration.js +21 -18
  56. package/jest.config.isolated.js +10 -0
  57. package/jest.projects.js +288 -0
  58. package/lib/api/datasources-core.api.js +3 -3
  59. package/lib/api/dev-mtls-request.js +110 -0
  60. package/lib/api/dev-server-https.js +145 -0
  61. package/lib/api/dev.api.js +133 -144
  62. package/lib/api/index.js +0 -1
  63. package/lib/api/pipeline.api.js +67 -20
  64. package/lib/api/service-users.api.js +111 -2
  65. package/lib/api/types/dev.types.js +4 -3
  66. package/lib/api/types/pipeline.types.js +8 -5
  67. package/lib/api/types/service-users.types.js +41 -0
  68. package/lib/api/types/validation-run.types.js +56 -0
  69. package/lib/api/validation-run.api.js +99 -0
  70. package/lib/api/validation-runner.js +99 -0
  71. package/lib/app/config.js +1 -1
  72. package/lib/app/deploy-status-display.js +2 -2
  73. package/lib/app/deploy.js +7 -6
  74. package/lib/app/display.js +2 -1
  75. package/lib/app/dockerfile.js +3 -2
  76. package/lib/app/down.js +2 -1
  77. package/lib/app/helpers.js +6 -5
  78. package/lib/app/index.js +27 -8
  79. package/lib/app/list.js +7 -6
  80. package/lib/app/push.js +4 -3
  81. package/lib/app/register.js +19 -8
  82. package/lib/app/rotate-secret.js +17 -13
  83. package/lib/app/run-container-start.js +184 -0
  84. package/lib/app/run-docker-fallback.js +108 -0
  85. package/lib/app/run-env-compose.js +30 -42
  86. package/lib/app/run-helpers.js +49 -126
  87. package/lib/app/run-infra-requirements.js +30 -0
  88. package/lib/app/run-resolve-image.js +21 -0
  89. package/lib/app/run.js +74 -21
  90. package/lib/app/show-display.js +1 -1
  91. package/lib/app/show.js +1 -1
  92. package/lib/build/index.js +13 -10
  93. package/lib/cli/index.js +2 -0
  94. package/lib/cli/setup-app.help.js +67 -0
  95. package/lib/cli/setup-app.js +59 -123
  96. package/lib/cli/setup-app.test-commands.js +179 -0
  97. package/lib/cli/setup-auth.js +36 -14
  98. package/lib/cli/setup-credential-deployment.js +22 -8
  99. package/lib/cli/setup-dev-path-commands.js +124 -0
  100. package/lib/cli/setup-dev.js +190 -103
  101. package/lib/cli/setup-environment.js +11 -20
  102. package/lib/cli/setup-external-system.js +62 -22
  103. package/lib/cli/setup-infra.js +139 -47
  104. package/lib/cli/setup-parameters.js +32 -0
  105. package/lib/cli/setup-secrets.js +147 -10
  106. package/lib/cli/setup-service-user.js +146 -20
  107. package/lib/cli/setup-utility.js +47 -19
  108. package/lib/commands/app-down.js +5 -7
  109. package/lib/commands/app-install.js +14 -7
  110. package/lib/commands/app-logs.js +13 -10
  111. package/lib/commands/app-shell.js +4 -1
  112. package/lib/commands/app-test.js +25 -19
  113. package/lib/commands/app.js +22 -10
  114. package/lib/commands/auth-config.js +10 -14
  115. package/lib/commands/auth-status.js +4 -3
  116. package/lib/commands/credential-env.js +4 -3
  117. package/lib/commands/credential-list.js +5 -4
  118. package/lib/commands/credential-push.js +4 -3
  119. package/lib/commands/datasource-unified-test-cli.js +495 -0
  120. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  121. package/lib/commands/datasource-validation-cli.js +129 -0
  122. package/lib/commands/datasource.js +123 -71
  123. package/lib/commands/deployment-list.js +6 -5
  124. package/lib/commands/dev-cli-handlers.js +122 -18
  125. package/lib/commands/dev-down.js +4 -3
  126. package/lib/commands/dev-init.js +231 -116
  127. package/lib/commands/dev-show-display.js +473 -0
  128. package/lib/commands/login-credentials.js +3 -2
  129. package/lib/commands/login-device.js +4 -3
  130. package/lib/commands/login.js +5 -4
  131. package/lib/commands/logout.js +8 -7
  132. package/lib/commands/parameters-validate.js +54 -0
  133. package/lib/commands/repair-datasource.js +314 -68
  134. package/lib/commands/repair-env-template.js +16 -10
  135. package/lib/commands/repair-rbac.js +25 -19
  136. package/lib/commands/repair.js +116 -32
  137. package/lib/commands/secrets-list.js +23 -12
  138. package/lib/commands/secrets-remove-all.js +220 -0
  139. package/lib/commands/secrets-remove.js +22 -13
  140. package/lib/commands/secrets-set.js +21 -12
  141. package/lib/commands/secrets-validate.js +20 -7
  142. package/lib/commands/secure.js +10 -9
  143. package/lib/commands/service-user.js +243 -13
  144. package/lib/commands/test-e2e-external.js +27 -1
  145. package/lib/commands/up-common.js +28 -2
  146. package/lib/commands/up-dataplane.js +31 -18
  147. package/lib/commands/up-miso.js +19 -29
  148. package/lib/commands/upload.js +138 -39
  149. package/lib/commands/wizard-core-helpers.js +1 -1
  150. package/lib/commands/wizard-dataplane.js +4 -3
  151. package/lib/commands/wizard-helpers.js +3 -3
  152. package/lib/commands/wizard.js +2 -2
  153. package/lib/core/admin-secrets.js +16 -5
  154. package/lib/core/audit-logger.js +12 -4
  155. package/lib/core/config-attach-extensions.js +46 -0
  156. package/lib/core/config-runtime-paths.js +29 -0
  157. package/lib/core/config.js +59 -58
  158. package/lib/core/diff.js +3 -2
  159. package/lib/core/ensure-encryption-key.js +2 -4
  160. package/lib/core/secrets-ensure-infra.js +77 -0
  161. package/lib/core/secrets-ensure.js +120 -64
  162. package/lib/core/secrets-env-write.js +35 -7
  163. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  164. package/lib/core/secrets.js +228 -42
  165. package/lib/core/templates-env.js +4 -3
  166. package/lib/core/templates.js +1 -1
  167. package/lib/datasource/abac-validator.js +148 -0
  168. package/lib/datasource/deploy.js +75 -53
  169. package/lib/datasource/field-reference-validator.js +77 -36
  170. package/lib/datasource/integration-context.js +63 -0
  171. package/lib/datasource/list.js +8 -7
  172. package/lib/datasource/log-viewer.js +252 -0
  173. package/lib/datasource/resolve-app.js +109 -0
  174. package/lib/datasource/test-e2e.js +95 -155
  175. package/lib/datasource/test-integration.js +121 -109
  176. package/lib/datasource/unified-validation-run-body.js +65 -0
  177. package/lib/datasource/unified-validation-run-post.js +23 -0
  178. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  179. package/lib/datasource/unified-validation-run.js +92 -0
  180. package/lib/datasource/validate.js +162 -15
  181. package/lib/deployment/deployer.js +4 -3
  182. package/lib/deployment/environment.js +7 -6
  183. package/lib/deployment/push.js +17 -8
  184. package/lib/external-system/delete.js +4 -3
  185. package/lib/external-system/deploy.js +131 -53
  186. package/lib/external-system/download-helpers.js +1 -1
  187. package/lib/external-system/download.js +7 -6
  188. package/lib/external-system/generator.js +104 -14
  189. package/lib/external-system/integration-test-dispatch.js +26 -0
  190. package/lib/external-system/test-execution.js +5 -1
  191. package/lib/external-system/test-helpers.js +0 -4
  192. package/lib/external-system/test-system-level-helpers.js +110 -0
  193. package/lib/external-system/test-system-level.js +83 -44
  194. package/lib/external-system/test.js +59 -8
  195. package/lib/generator/builders.js +23 -11
  196. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  197. package/lib/generator/external-controller-manifest.js +3 -3
  198. package/lib/generator/external.js +23 -11
  199. package/lib/generator/helpers.js +71 -12
  200. package/lib/generator/index.js +8 -4
  201. package/lib/generator/split-readme.js +12 -7
  202. package/lib/generator/split-variables.js +2 -1
  203. package/lib/generator/split.js +46 -11
  204. package/lib/generator/wizard-readme.js +3 -3
  205. package/lib/generator/wizard.js +16 -13
  206. package/lib/infrastructure/compose.js +60 -6
  207. package/lib/infrastructure/helpers.js +238 -51
  208. package/lib/infrastructure/index.js +64 -37
  209. package/lib/infrastructure/services.js +21 -15
  210. package/lib/internal/fs-real-sync.js +104 -0
  211. package/lib/internal/node-fs.js +98 -0
  212. package/lib/parameters/database-secret-values.js +173 -0
  213. package/lib/parameters/infra-kv-discovery.js +121 -0
  214. package/lib/parameters/infra-parameter-catalog.js +458 -0
  215. package/lib/parameters/infra-parameter-validate.js +64 -0
  216. package/lib/schema/application-schema.json +37 -17
  217. package/lib/schema/datasource-test-run.schema.json +493 -0
  218. package/lib/schema/deployment-rules.yaml +102 -63
  219. package/lib/schema/external-datasource.schema.json +1201 -433
  220. package/lib/schema/external-system.schema.json +181 -5
  221. package/lib/schema/flag-map-validation-run.json +31 -0
  222. package/lib/schema/infra-parameter.schema.json +106 -0
  223. package/lib/schema/infra.parameter.yaml +421 -0
  224. package/lib/schema/type/credential-auth-templates.json +40 -0
  225. package/lib/schema/type/document-storage.json +213 -0
  226. package/lib/schema/type/message-service.json +123 -0
  227. package/lib/schema/type/vector-store.json +88 -0
  228. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  229. package/lib/utils/api-error-handler.js +2 -2
  230. package/lib/utils/api.js +49 -14
  231. package/lib/utils/app-config-resolver.js +23 -1
  232. package/lib/utils/app-register-api.js +3 -2
  233. package/lib/utils/app-register-auth.js +1 -1
  234. package/lib/utils/app-register-config.js +4 -4
  235. package/lib/utils/app-register-display.js +3 -2
  236. package/lib/utils/app-register-validator.js +3 -2
  237. package/lib/utils/app-run-containers.js +26 -22
  238. package/lib/utils/app-scoped-config.js +31 -0
  239. package/lib/utils/app-service-env-from-builder.js +164 -0
  240. package/lib/utils/build-copy.js +1 -1
  241. package/lib/utils/build-helpers.js +20 -20
  242. package/lib/utils/build-resolve-image.js +165 -0
  243. package/lib/utils/cli-layout-chalk.js +8 -0
  244. package/lib/utils/cli-test-layout-chalk.js +267 -0
  245. package/lib/utils/cli-utils.js +88 -11
  246. package/lib/utils/compose-db-passwords.js +138 -0
  247. package/lib/utils/compose-generate-docker-compose.js +216 -0
  248. package/lib/utils/compose-generator.js +197 -291
  249. package/lib/utils/compose-miso-env.js +18 -0
  250. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  251. package/lib/utils/config-paths.js +209 -6
  252. package/lib/utils/config-scoped-resources-preference.js +41 -0
  253. package/lib/utils/controller-deployment-outcome.js +68 -0
  254. package/lib/utils/credential-display.js +2 -2
  255. package/lib/utils/credential-secrets-env.js +16 -1
  256. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  257. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  258. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  259. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  260. package/lib/utils/datasource-test-run-display.js +442 -0
  261. package/lib/utils/datasource-test-run-exit.js +58 -0
  262. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  263. package/lib/utils/datasource-test-run-report-version.js +51 -0
  264. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  265. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  266. package/lib/utils/datasource-validation-watch.js +266 -0
  267. package/lib/utils/declarative-url-ports.js +47 -0
  268. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  269. package/lib/utils/dev-ca-install.js +185 -23
  270. package/lib/utils/dev-cert-helper.js +266 -17
  271. package/lib/utils/dev-hosts-helper.js +307 -0
  272. package/lib/utils/dev-init-cert-hints.js +37 -0
  273. package/lib/utils/dev-init-health-messages.js +52 -0
  274. package/lib/utils/dev-init-resolve.js +86 -0
  275. package/lib/utils/dev-init-ssh-merge.js +65 -0
  276. package/lib/utils/dev-ssh-config-helper.js +196 -0
  277. package/lib/utils/dev-user-groups.js +93 -0
  278. package/lib/utils/docker-build.js +42 -17
  279. package/lib/utils/docker-exec.js +28 -0
  280. package/lib/utils/docker-manifest-public-port.js +116 -0
  281. package/lib/utils/docker-not-running-hint.js +52 -0
  282. package/lib/utils/docker.js +98 -11
  283. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  284. package/lib/utils/env-config-loader.js +10 -91
  285. package/lib/utils/env-copy.js +19 -10
  286. package/lib/utils/env-map.js +42 -11
  287. package/lib/utils/env-template.js +2 -2
  288. package/lib/utils/environment-scoped-resources.js +144 -0
  289. package/lib/utils/error-formatter.js +125 -9
  290. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  291. package/lib/utils/error-formatters/network-errors.js +2 -1
  292. package/lib/utils/error-formatters/permission-errors.js +2 -1
  293. package/lib/utils/error-formatters/validation-errors.js +2 -1
  294. package/lib/utils/external-env-template.js +180 -0
  295. package/lib/utils/external-readme.js +8 -1
  296. package/lib/utils/external-system-display.js +277 -136
  297. package/lib/utils/external-system-local-test-tty.js +389 -0
  298. package/lib/utils/external-system-readiness-core.js +377 -0
  299. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  300. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  301. package/lib/utils/external-system-readiness-display.js +186 -0
  302. package/lib/utils/external-system-test-helpers.js +24 -6
  303. package/lib/utils/external-system-validators.js +32 -14
  304. package/lib/utils/health-check-url.js +119 -0
  305. package/lib/utils/health-check.js +59 -25
  306. package/lib/utils/help-builder.js +14 -13
  307. package/lib/utils/image-version.js +4 -8
  308. package/lib/utils/infra-containers.js +4 -7
  309. package/lib/utils/infra-env-defaults.js +162 -0
  310. package/lib/utils/infra-status-display.js +167 -0
  311. package/lib/utils/infra-status.js +16 -8
  312. package/lib/utils/local-secrets.js +29 -7
  313. package/lib/utils/paths.js +136 -48
  314. package/lib/utils/port-resolver.js +10 -23
  315. package/lib/utils/redis-env-scope.js +62 -0
  316. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  317. package/lib/utils/remote-builder-validation.js +99 -0
  318. package/lib/utils/remote-dev-auth.js +117 -21
  319. package/lib/utils/remote-docker-env.js +67 -15
  320. package/lib/utils/remote-secrets-loader.js +13 -4
  321. package/lib/utils/resolve-docker-image-ref.js +124 -0
  322. package/lib/utils/schema-loader.js +22 -9
  323. package/lib/utils/secrets-bash-kv.js +25 -0
  324. package/lib/utils/secrets-generator.js +171 -51
  325. package/lib/utils/secrets-helpers.js +70 -59
  326. package/lib/utils/secrets-kv-scope.js +60 -0
  327. package/lib/utils/secrets-utils.js +35 -37
  328. package/lib/utils/secrets-validation.js +3 -1
  329. package/lib/utils/secrets-yaml-preserve.js +109 -0
  330. package/lib/utils/secure-file-permissions.js +91 -0
  331. package/lib/utils/ssh-key-helper.js +4 -2
  332. package/lib/utils/template-helpers.js +2 -2
  333. package/lib/utils/test-log-writer.js +3 -3
  334. package/lib/utils/token-manager.js +37 -5
  335. package/lib/utils/url-declarative-public-base.js +188 -0
  336. package/lib/utils/url-declarative-resolve-build.js +493 -0
  337. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  338. package/lib/utils/url-declarative-resolve.js +220 -0
  339. package/lib/utils/url-declarative-token-parse.js +74 -0
  340. package/lib/utils/url-declarative-url-flags.js +50 -0
  341. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  342. package/lib/utils/url-public-path-prefix.js +34 -0
  343. package/lib/utils/urls-local-registry.js +220 -0
  344. package/lib/utils/validation-report-tty-kit.js +77 -0
  345. package/lib/utils/validation-run-poll.js +89 -0
  346. package/lib/utils/validation-run-post-retry.js +73 -0
  347. package/lib/utils/validation-run-request.js +98 -0
  348. package/lib/utils/variable-transformer.js +21 -4
  349. package/lib/utils/yaml-preserve.js +78 -1
  350. package/lib/validation/datasource-warnings.js +56 -0
  351. package/lib/validation/env-template-auth.js +50 -2
  352. package/lib/validation/external-manifest-validator.js +35 -7
  353. package/lib/validation/validate-display.js +37 -31
  354. package/lib/validation/validate.js +9 -10
  355. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  356. package/lib/validation/validator.js +32 -78
  357. package/lib/validation/wizard-config-validator.js +2 -1
  358. package/package.json +11 -3
  359. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  360. package/scripts/diagnose-cli.js +150 -0
  361. package/scripts/install-local.js +304 -55
  362. package/templates/README.md +15 -2
  363. package/templates/applications/dataplane/application.yaml +52 -2
  364. package/templates/applications/dataplane/env.template +80 -18
  365. package/templates/applications/dataplane/rbac.yaml +8 -0
  366. package/templates/applications/keycloak/application.yaml +9 -1
  367. package/templates/applications/keycloak/env.template +15 -6
  368. package/templates/applications/miso-controller/application.yaml +10 -2
  369. package/templates/applications/miso-controller/env.template +55 -14
  370. package/templates/applications/miso-controller/rbac.yaml +5 -0
  371. package/templates/external-system/README.md.hbs +20 -7
  372. package/templates/external-system/deploy.js.hbs +5 -5
  373. package/templates/external-system/env.template.hbs +22 -0
  374. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  375. package/templates/infra/compose.yaml.hbs +20 -4
  376. package/templates/python/docker-compose.hbs +16 -0
  377. package/templates/typescript/docker-compose.hbs +16 -0
  378. package/integration/hubspot/README.md +0 -102
  379. package/integration/hubspot/env.template +0 -4
  380. package/integration/hubspot/hubspot-datasource-company.json +0 -541
  381. package/integration/hubspot/hubspot-datasource-contact.json +0 -639
  382. package/integration/hubspot/hubspot-datasource-deal.json +0 -588
  383. package/integration/hubspot/hubspot-datasource-users.json +0 -116
  384. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  385. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  386. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  387. package/lib/api/external-test.api.js +0 -111
  388. package/lib/schema/env-config.yaml +0 -43
  389. /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
  390. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
  391. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
  392. /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
@@ -24,10 +24,14 @@ const {
24
24
  rewriteInfraEndpoints,
25
25
  readYamlAtPath,
26
26
  applyCanonicalSecretsOverride,
27
- ensureNonEmptySecrets,
28
27
  validateSecrets
29
28
  } = require('../utils/secrets-helpers');
30
29
  const { buildEnvVarMap } = require('../utils/env-map');
30
+ const {
31
+ mergeInfraParameterDefaultsForCli,
32
+ getInfraParameterCatalog,
33
+ readRelaxedCatalogDefaults
34
+ } = require('../parameters/infra-parameter-catalog');
31
35
  const { resolveServicePortsInEnvContent } = require('../utils/secrets-url');
32
36
  const {
33
37
  updatePortForDocker,
@@ -35,7 +39,7 @@ const {
35
39
  applyDockerEnvOverride,
36
40
  getContainerPortFromDockerEnv
37
41
  } = require('./secrets-docker-env');
38
- const { getContainerPortFromPath } = require('../utils/port-resolver');
42
+ const { getContainerPortFromPath, loadVariablesFromPath } = require('../utils/port-resolver');
39
43
  const {
40
44
  generateMissingSecrets,
41
45
  createDefaultSecrets
@@ -48,10 +52,21 @@ const {
48
52
  const {
49
53
  loadUserSecrets,
50
54
  loadPrimaryUserSecrets,
51
- loadDefaultSecrets
55
+ loadDefaultSecrets,
56
+ ensurePrimaryUserSecretsFileExists
52
57
  } = require('../utils/secrets-utils');
53
58
  const { decryptSecret, isEncrypted } = require('../utils/secrets-encryption');
54
59
  const pathsUtil = require('../utils/paths');
60
+ const { ensureSecureFilePermissions } = require('../utils/secure-file-permissions');
61
+ const { readAppEnvironmentScopedFlagForAppPath } = require('../utils/app-scoped-config');
62
+ const { computeEffectiveEnvironmentScopedResources, redisDbIndexForScopedRunEnv } = require('../utils/environment-scoped-resources');
63
+ const { applyRedisDbIndexToEnvContent } = require('../utils/redis-env-scope');
64
+ const { expandDeclarativeUrlsInEnvContent } = require('../utils/url-declarative-resolve');
65
+ const { rewriteInactiveDeclarativeVdirPublicContent } = require('../utils/url-declarative-vdir-inactive-env');
66
+ const {
67
+ mergeDockerManifestPublishedPort,
68
+ rewriteDockerManifestPublicPortEnvLine
69
+ } = require('../utils/docker-manifest-public-port');
55
70
 
56
71
  /**
57
72
  * Generates a canonical secret name from an environment variable key.
@@ -127,8 +142,8 @@ async function decryptSecretsObject(secrets) {
127
142
  * @function loadSecrets
128
143
  * @param {string} [secretsPath] - Path to secrets file (optional, for explicit override)
129
144
  * @param {string} [appName] - Application name (optional, for backward compatibility)
130
- * @returns {Promise<Object>} Loaded secrets object with decrypted values
131
- * @throws {Error} If no secrets file found and no fallback available
145
+ * @returns {Promise<Object>} Loaded secrets object with decrypted values (may be empty after bootstrap file create)
146
+ * @throws {Error} If explicit secretsPath is set but file is missing or invalid
132
147
  *
133
148
  * @example
134
149
  * const secrets = await loadSecrets('../../secrets.local.yaml');
@@ -150,6 +165,7 @@ function mergeUserWithConfigFile(userSecrets, resolvedConfigPath) {
150
165
  if (!fs.existsSync(resolvedConfigPath)) {
151
166
  return null;
152
167
  }
168
+ ensureSecureFilePermissions(resolvedConfigPath);
153
169
  let configSecrets;
154
170
  try {
155
171
  configSecrets = readYamlAtPath(resolvedConfigPath);
@@ -170,16 +186,17 @@ function mergeUserWithConfigFile(userSecrets, resolvedConfigPath) {
170
186
 
171
187
  /**
172
188
  * Loads config secrets path, merges with user secrets (user/master wins, public fills missing).
173
- * User/master = primary home (AIFABRIX_HOME or ~/.aifabrix) secrets.local.yaml.
189
+ * User/master = getPrimaryUserSecretsLocalPath() (config dir; same file as loadPrimaryUserSecrets).
174
190
  * Public = aifabrix-secrets path from config. Used by loadSecrets cascade.
175
- * When aifabrix-secrets is an http(s) URL, fetches shared secrets from API (never persisted to disk).
191
+ * When the effective shared-secrets target (after remote-dev resolution) is an http(s) URL,
192
+ * fetches shared secrets from API (never persisted to disk).
176
193
  *
177
194
  * @async
178
195
  * @returns {Promise<Object|null>} Merged secrets object or null
179
196
  */
180
197
  async function loadMergedConfigAndUserSecrets() {
181
198
  const { loadRemoteSharedSecrets, mergeUserWithRemoteSecrets } = require('../utils/remote-secrets-loader');
182
- const { isRemoteSecretsUrl } = require('../utils/remote-dev-auth');
199
+ const remoteDevAuth = require('../utils/remote-dev-auth');
183
200
  const userSecrets = loadPrimaryUserSecrets();
184
201
  const hasKeys = (obj) => obj && Object.keys(obj).length > 0;
185
202
  const userOrNull = () => (hasKeys(userSecrets) ? userSecrets : null);
@@ -188,7 +205,8 @@ async function loadMergedConfigAndUserSecrets() {
188
205
  if (!configSecretsPath) {
189
206
  return userOrNull();
190
207
  }
191
- if (isRemoteSecretsUrl(configSecretsPath)) {
208
+ const effectiveShared = await remoteDevAuth.resolveSharedSecretsEndpoint(configSecretsPath);
209
+ if (remoteDevAuth.isRemoteSecretsUrl(effectiveShared)) {
192
210
  const remoteSecrets = await loadRemoteSharedSecrets();
193
211
  const merged = mergeUserWithRemoteSecrets(userSecrets, remoteSecrets);
194
212
  return hasKeys(merged) ? merged : userOrNull();
@@ -225,6 +243,7 @@ async function loadSecretsWithFallbacks() {
225
243
  if (projectRoot) {
226
244
  const builderPath = path.join(projectRoot, 'builder', 'secrets.local.yaml');
227
245
  if (fs.existsSync(builderPath)) {
246
+ ensureSecureFilePermissions(builderPath);
228
247
  const builderSecrets = mergeUserWithConfigFile(merged || {}, builderPath);
229
248
  if (builderSecrets) merged = builderSecrets;
230
249
  }
@@ -244,15 +263,19 @@ async function loadSecrets(secretsPath, _appName) {
244
263
  if (!fs.existsSync(resolvedPath)) {
245
264
  throw new Error(`Secrets file not found: ${resolvedPath}`);
246
265
  }
266
+ ensureSecureFilePermissions(resolvedPath);
247
267
  const explicitSecrets = readYamlAtPath(resolvedPath);
248
268
  if (!explicitSecrets || typeof explicitSecrets !== 'object') {
249
269
  throw new Error(`Invalid secrets file format: ${resolvedPath}`);
250
270
  }
251
271
  return await decryptSecretsObject(explicitSecrets);
252
272
  }
253
- const mergedSecrets = await loadSecretsWithFallbacks();
254
- ensureNonEmptySecrets(mergedSecrets);
255
- return await decryptSecretsObject(mergedSecrets);
273
+ let mergedSecrets = await loadSecretsWithFallbacks();
274
+ if (!mergedSecrets || Object.keys(mergedSecrets).length === 0) {
275
+ ensurePrimaryUserSecretsFileExists();
276
+ mergedSecrets = await loadSecretsWithFallbacks();
277
+ }
278
+ return await decryptSecretsObject(mergedSecrets || {});
256
279
  }
257
280
 
258
281
  /**
@@ -275,7 +298,7 @@ async function loadSecrets(secretsPath, _appName) {
275
298
  * const resolved = await resolveKvReferences(template, secrets, 'local');
276
299
  * // Returns: 'DATABASE_URL=postgresql://user:pass@localhost:5432/db'
277
300
  */
278
- async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null) {
301
+ async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null, scopedKv = null) {
279
302
  const os = require('os');
280
303
 
281
304
  // Get developer-id for port variables (local and docker: *_PUBLIC_PORT = base + devId*100)
@@ -286,25 +309,56 @@ async function resolveKvReferences(envTemplate, secrets, environment = 'local',
286
309
  // ignore, buildEnvVarMap will use default
287
310
  }
288
311
 
289
- let envVars = await buildEnvVarMap(environment, os, developerId);
312
+ const envKey = String(environment || 'local').toLowerCase();
313
+ const mapContext = envKey === 'docker' || envKey === 'local' ? envKey : 'local';
314
+
315
+ let envVars = await buildEnvVarMap(mapContext, os, developerId);
290
316
  if (!envVars || Object.keys(envVars).length === 0) {
291
317
  // Fallback to local environment variables if requested environment does not exist
292
318
  envVars = await buildEnvVarMap('local', os, developerId);
293
319
  }
294
320
  const resolved = interpolateEnvVars(envTemplate, envVars);
295
- const missing = collectMissingSecrets(resolved, secrets);
321
+ const missing = collectMissingSecrets(resolved, secrets, scopedKv);
296
322
  if (missing.length > 0) {
297
323
  const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
298
324
  const resolveCommand = appName ? `aifabrix resolve ${appName}` : 'aifabrix resolve <app-name>';
299
325
  throw new Error(`Missing secrets: ${missing.join(', ')}${fileInfo}\n\nRun "${resolveCommand}" to generate missing secrets.`);
300
326
  }
301
- return replaceKvInContent(resolved, secrets, envVars);
327
+ return replaceKvInContent(resolved, secrets, envVars, scopedKv);
302
328
  }
303
329
 
304
- /** Docker env transformations: ports, infra endpoints, PORT. */
305
- async function applyDockerTransformations(resolved, variablesPath) {
306
- resolved = await resolveServicePortsInEnvContent(resolved, 'docker');
307
- resolved = await rewriteInfraEndpoints(resolved, 'docker');
330
+ /**
331
+ * Resolve run env key and effective env-scoped kv/redis behavior for generateEnvContent.
332
+ *
333
+ * @async
334
+ * @param {string} appPath - Builder application directory
335
+ * @param {Object} [options] - generateEnvContent options; may set runEnvKey
336
+ * @returns {Promise<{ runEnvKey: string, effective: boolean }>}
337
+ */
338
+ async function buildScopedKvContext(appPath, options = {}) {
339
+ let runEnvKey;
340
+ if (options.runEnvKey !== undefined && options.runEnvKey !== null) {
341
+ runEnvKey = String(options.runEnvKey).toLowerCase();
342
+ } else if (typeof config.getCurrentEnvironment === 'function') {
343
+ runEnvKey = String((await config.getCurrentEnvironment()) || 'dev').toLowerCase();
344
+ } else {
345
+ runEnvKey = 'dev';
346
+ }
347
+ const userCfg =
348
+ typeof config.getConfig === 'function'
349
+ ? await config.getConfig()
350
+ : { useEnvironmentScopedResources: false };
351
+ const useGate = Boolean(userCfg.useEnvironmentScopedResources);
352
+ const appFlag = readAppEnvironmentScopedFlagForAppPath(appPath);
353
+ const effective = computeEffectiveEnvironmentScopedResources(useGate, appFlag, runEnvKey);
354
+ return { runEnvKey, effective };
355
+ }
356
+
357
+ /**
358
+ * Redis/DB service endpoints for docker env interpolation.
359
+ * @returns {Promise<{ redisHost: string, redisPort: number, dbHost: string, dbPort: number }>}
360
+ */
361
+ async function getDockerRedisDbEndpoints() {
308
362
  const { getEnvHosts, getServiceHost, getServicePort, getLocalhostOverride } = require('../utils/env-endpoints');
309
363
  const hosts = await getEnvHosts('docker');
310
364
  const localhostOverride = getLocalhostOverride('docker');
@@ -312,16 +366,97 @@ async function applyDockerTransformations(resolved, variablesPath) {
312
366
  const redisPort = await getServicePort('REDIS_PORT', 'redis', hosts, 'docker', null);
313
367
  const dbHost = getServiceHost(hosts.DB_HOST, 'docker', 'postgres', localhostOverride);
314
368
  const dbPort = await getServicePort('DB_PORT', 'postgres', hosts, 'docker', null);
369
+ return { redisHost, redisPort, dbHost, dbPort };
370
+ }
371
+
372
+ /**
373
+ * Config inputs for declarative url:// expansion (keeps expandDeclarativeUrlsIfPresent small).
374
+ * @param {string} appPath
375
+ * @returns {Promise<Object>}
376
+ */
377
+ async function loadDeclarativeUrlExpandInputs(appPath) {
378
+ const userCfg = await config.getConfig();
379
+ let remoteServer = null;
380
+ try {
381
+ const rs = await config.getRemoteServer();
382
+ if (rs && String(rs).trim()) {
383
+ remoteServer = String(rs).trim();
384
+ }
385
+ } catch {
386
+ remoteServer = null;
387
+ }
388
+ let developerIdRaw = null;
389
+ try {
390
+ developerIdRaw = await config.getDeveloperId();
391
+ } catch {
392
+ developerIdRaw = null;
393
+ }
394
+ let infraTlsEnabled = false;
395
+ try {
396
+ infraTlsEnabled = await config.getTlsEnabled();
397
+ } catch {
398
+ infraTlsEnabled = false;
399
+ }
400
+ return {
401
+ userCfg,
402
+ remoteServer,
403
+ developerIdRaw,
404
+ infraTlsEnabled,
405
+ appScopedFlag: readAppEnvironmentScopedFlagForAppPath(appPath)
406
+ };
407
+ }
408
+
409
+ /**
410
+ * After kv:// resolution, expand url:// references when application config exists.
411
+ * @param {string} resolved
412
+ * @param {string} appName
413
+ * @param {string} appPath
414
+ * @param {string|null} variablesPath
415
+ * @param {string} environment
416
+ * @param {boolean} envOnly
417
+ * @returns {Promise<string>}
418
+ */
419
+ async function expandDeclarativeUrlsIfPresent(resolved, appName, appPath, variablesPath, environment, envOnly) {
420
+ if (envOnly || !variablesPath) {
421
+ return resolved;
422
+ }
423
+ const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } =
424
+ await loadDeclarativeUrlExpandInputs(appPath);
425
+ resolved = rewriteInactiveDeclarativeVdirPublicContent(resolved, variablesPath, userCfg);
426
+ if (!resolved.includes('url://')) {
427
+ return resolved;
428
+ }
429
+ return expandDeclarativeUrlsInEnvContent(resolved, {
430
+ profile: environment === 'docker' ? 'docker' : 'local',
431
+ currentAppKey: appName,
432
+ variablesPath,
433
+ useEnvironmentScopedResources: Boolean(userCfg.useEnvironmentScopedResources),
434
+ appEnvironmentScopedResources: appScopedFlag,
435
+ remoteServer,
436
+ developerIdRaw,
437
+ traefik: Boolean(userCfg.traefik),
438
+ infraTlsEnabled
439
+ });
440
+ }
441
+
442
+ /** Docker env transformations: ports, infra endpoints, PORT. */
443
+ async function applyDockerTransformations(resolved, variablesPath) {
444
+ resolved = await resolveServicePortsInEnvContent(resolved, 'docker');
445
+ resolved = await rewriteInfraEndpoints(resolved, 'docker');
446
+ const { redisHost, redisPort, dbHost, dbPort } = await getDockerRedisDbEndpoints();
315
447
  let dockerEnv = await getBaseDockerEnv();
316
448
  dockerEnv = applyDockerEnvOverride(dockerEnv);
317
449
  const containerPort = getContainerPortFromPath(variablesPath) ?? getContainerPortFromDockerEnv(dockerEnv) ?? 3000;
318
450
  const envVars = await buildEnvVarMap('docker', null, null, { appPort: containerPort });
451
+ const appDoc = loadVariablesFromPath(variablesPath);
452
+ await mergeDockerManifestPublishedPort(envVars, appDoc);
319
453
  envVars.REDIS_HOST = redisHost;
320
454
  envVars.REDIS_PORT = String(redisPort);
321
455
  envVars.DB_HOST = dbHost;
322
456
  envVars.DB_PORT = String(dbPort);
323
457
  envVars.PORT = String(containerPort);
324
458
  resolved = interpolateEnvVars(resolved, envVars);
459
+ resolved = rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc);
325
460
  return updatePortForDocker(resolved, variablesPath);
326
461
  }
327
462
  /** Environment-specific transformations to resolved content. */
@@ -353,8 +488,22 @@ async function generateEnvContent(appName, secretsPath, environment = 'local', f
353
488
  await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, { preferredFilePath: preferredPath });
354
489
  }
355
490
  const secrets = await loadSecrets(secretsPath, appName);
356
- let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName);
491
+ const { runEnvKey, effective } = await buildScopedKvContext(appPath, options);
492
+ const scopedKv = { envKey: runEnvKey, effective };
493
+ let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, scopedKv);
494
+ resolved = await expandDeclarativeUrlsIfPresent(
495
+ resolved,
496
+ appName,
497
+ appPath,
498
+ variablesPath,
499
+ environment,
500
+ Boolean(options.envOnly)
501
+ );
357
502
  resolved = await applyEnvironmentTransformations(resolved, environment, variablesPath);
503
+ if (effective) {
504
+ const idx = redisDbIndexForScopedRunEnv(runEnvKey);
505
+ resolved = applyRedisDbIndexToEnvContent(resolved, idx);
506
+ }
358
507
 
359
508
  return resolved;
360
509
  }
@@ -516,40 +665,76 @@ async function generateEnvFile(appName, secretsPath, environment = 'local', forc
516
665
  return envPath;
517
666
  }
518
667
 
519
- /** Generates admin secrets for infrastructure (~/.aifabrix/admin-secrets.env). Uses admin123 when no postgres password. */
520
- async function generateAdminSecretsEnv(secretsPath) {
521
- let secrets;
668
+ /**
669
+ * Writes admin env key-value pairs to content; encrypts values when encryption key is set.
670
+ * @async
671
+ * @param {Object.<string, string>} adminObj - Key-value object (e.g. POSTGRES_PASSWORD, ...)
672
+ * @returns {Promise<string>} .env-style content (plaintext or secure:// for secrets)
673
+ */
674
+ async function formatAdminSecretsContent(adminObj) {
675
+ const encryptionKey = await config.getSecretsEncryptionKey();
676
+ const { encryptSecret } = require('../utils/secrets-encryption');
677
+ const lines = ['# Infrastructure Admin Credentials'];
678
+ for (const [k, v] of Object.entries(adminObj)) {
679
+ const value = (v === null || v === undefined) ? '' : String(v).replace(/\n/g, ' ').trim();
680
+ const valueToWrite = encryptionKey ? encryptSecret(value, encryptionKey) : value;
681
+ lines.push(`${k}=${valueToWrite}`);
682
+ }
683
+ return lines.join('\n');
684
+ }
522
685
 
686
+ async function loadSecretsOrBootstrapForAdmin(secretsPath) {
523
687
  try {
524
- secrets = await loadSecrets(secretsPath);
688
+ return await loadSecrets(secretsPath);
525
689
  } catch (error) {
526
690
  const defaultSecretsPath = secretsPath || path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
527
691
  if (!fs.existsSync(defaultSecretsPath)) {
528
692
  logger.log('Creating default secrets file...');
529
693
  await createDefaultSecrets(defaultSecretsPath);
530
- secrets = await loadSecrets(secretsPath);
531
- } else {
532
- throw error;
694
+ return await loadSecrets(secretsPath);
533
695
  }
696
+ throw error;
534
697
  }
535
- const aifabrixDir = pathsUtil.getAifabrixHome();
536
- const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
537
- if (!fs.existsSync(aifabrixDir)) {
538
- fs.mkdirSync(aifabrixDir, { recursive: true, mode: 0o700 });
698
+ }
699
+
700
+ function getInfraDefaultsMergedForAdmin() {
701
+ try {
702
+ return mergeInfraParameterDefaultsForCli(getInfraParameterCatalog().data, {});
703
+ } catch {
704
+ return {};
539
705
  }
706
+ }
540
707
 
708
+ function buildLocalAdminSecretsObject(secrets, infraDefaults) {
541
709
  const raw = secrets['postgres-passwordKeyVault'];
542
- const postgresPassword = (raw && String(raw).trim()) || 'admin123';
543
-
544
- const adminSecrets = `# Infrastructure Admin Credentials
545
- POSTGRES_PASSWORD=${postgresPassword}
546
- PGADMIN_DEFAULT_EMAIL=admin@aifabrix.dev
547
- PGADMIN_DEFAULT_PASSWORD=${postgresPassword}
548
- REDIS_HOST=local:redis:6379:0:
549
- REDIS_COMMANDER_USER=admin
550
- REDIS_COMMANDER_PASSWORD=${postgresPassword}
551
- `;
710
+ const relaxed = readRelaxedCatalogDefaults();
711
+ const postgresPassword =
712
+ (raw && String(raw).trim()) ||
713
+ infraDefaults.adminPassword ||
714
+ relaxed.adminPassword ||
715
+ '';
716
+ const pgAdminEmail = infraDefaults.adminEmail || relaxed.adminEmail || '';
717
+ return {
718
+ POSTGRES_PASSWORD: postgresPassword,
719
+ PGADMIN_DEFAULT_EMAIL: pgAdminEmail,
720
+ PGADMIN_DEFAULT_PASSWORD: postgresPassword,
721
+ REDIS_HOST: 'local:redis:6379:0:',
722
+ REDIS_COMMANDER_USER: 'admin',
723
+ REDIS_COMMANDER_PASSWORD: postgresPassword
724
+ };
725
+ }
552
726
 
727
+ /** Generates admin secrets for infrastructure (beside config.yaml, typically ~/.aifabrix/admin-secrets.env). Defaults from infra.parameter.yaml `defaults`. */
728
+ async function generateAdminSecretsEnv(secretsPath) {
729
+ const secrets = await loadSecretsOrBootstrapForAdmin(secretsPath);
730
+ const infraDefaults = getInfraDefaultsMergedForAdmin();
731
+ const adminObj = buildLocalAdminSecretsObject(secrets, infraDefaults);
732
+ const aifabrixDir = pathsUtil.getAifabrixSystemDir();
733
+ const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
734
+ if (!fs.existsSync(aifabrixDir)) {
735
+ fs.mkdirSync(aifabrixDir, { recursive: true, mode: 0o700 });
736
+ }
737
+ const adminSecrets = await formatAdminSecretsContent(adminObj);
553
738
  fs.writeFileSync(adminEnvPath, adminSecrets, { mode: 0o600 });
554
739
  return adminEnvPath;
555
740
  }
@@ -560,6 +745,7 @@ module.exports = {
560
745
  generateEnvContent,
561
746
  generateMissingSecrets,
562
747
  generateAdminSecretsEnv,
748
+ formatAdminSecretsContent,
563
749
  validateSecrets,
564
750
  createDefaultSecrets,
565
751
  getCanonicalSecretName,
@@ -106,9 +106,10 @@ function buildMonitoringEnv(config) {
106
106
  return {
107
107
  'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.dev',
108
108
  'MISO_ENVIRONMENT': 'dev',
109
- 'MISO_CLIENTID': 'kv://miso-controller-client-idKeyVault',
110
- 'MISO_CLIENTSECRET': 'kv://miso-controller-client-secretKeyVault',
111
- 'MISO_WEB_SERVER_URL': 'kv://miso-controller-web-server-url'
109
+ // Filled after `aifabrix register <app>`; empty avoids bogus kv placeholders in new apps
110
+ 'MISO_CLIENTID': '',
111
+ 'MISO_CLIENTSECRET': '',
112
+ 'MISO_WEB_SERVER_URL': 'url://miso-controller-public'
112
113
  };
113
114
  }
114
115
 
@@ -268,7 +268,7 @@ function generateSecretsYaml(config, existingSecrets = {}) {
268
268
  Object.entries(existingSecrets).forEach(([key, value]) => {
269
269
  secrets.data[key] = Buffer.from(value).toString('base64');
270
270
  });
271
- return yaml.dump(secrets, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false });
271
+ return yaml.dump(secrets, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
272
272
  }
273
273
 
274
274
  module.exports = {
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ABAC (Attribute-Based Access Control) validator for external datasources.
3
+ *
4
+ * Validates config.abac.dimensions (dimension-to-attribute references),
5
+ * config.abac.crossSystemJson (allowed operators, one per path, value types),
6
+ * and errors on legacy config.abac.crossSystem.
7
+ *
8
+ * @fileoverview ABAC validation for AI Fabrix Builder
9
+ * @author AI Fabrix Team
10
+ * @version 2.0.0
11
+ */
12
+
13
+ const DIMENSION_KEY_PATTERN = /^[a-zA-Z0-9_]+$/;
14
+ const ATTRIBUTE_PATH_PATTERN = /^[a-zA-Z0-9_.]+$/;
15
+ const CROSS_SYSTEM_JSON_PATH_PATTERN = /^[a-zA-Z0-9_.]+$/;
16
+ const ALLOWED_CROSS_SYSTEM_OPERATORS = new Set([
17
+ 'eq', 'ne', 'gt', 'lt', 'gte', 'lte', 'in', 'nin', 'contains', 'like', 'isNull', 'isNotNull'
18
+ ]);
19
+
20
+ /**
21
+ * Validates dimension keys and attribute path values for a dimensions object.
22
+ *
23
+ * @param {Object} dimensions - Object mapping dimension keys to attribute paths
24
+ * @param {string} source - Label for error messages (e.g. "config.abac.dimensions")
25
+ * @param {Set<string>} validAttributeNames - Set of valid attribute names (from fieldMappings.attributes)
26
+ * @returns {string[]} Error messages
27
+ */
28
+ function validateDimensionsObject(dimensions, source, validAttributeNames) {
29
+ const errors = [];
30
+ if (!dimensions || typeof dimensions !== 'object' || Array.isArray(dimensions)) {
31
+ return errors;
32
+ }
33
+ for (const [dimKey, attrPath] of Object.entries(dimensions)) {
34
+ if (!DIMENSION_KEY_PATTERN.test(dimKey)) {
35
+ errors.push(
36
+ `${source}: dimension key '${dimKey}' must contain only letters, numbers, and underscores. Add '${dimKey}' to fieldMappings.attributes or fix the key.`
37
+ );
38
+ }
39
+ if (typeof attrPath !== 'string' || !ATTRIBUTE_PATH_PATTERN.test(attrPath)) {
40
+ errors.push(
41
+ `${source}: attribute path for dimension '${dimKey}' must be a string with letters, numbers, underscores, and dots only.`
42
+ );
43
+ } else if (validAttributeNames && validAttributeNames.size > 0) {
44
+ const normalizedName = attrPath.includes('.') ? attrPath.split('.').pop() : attrPath;
45
+ if (!validAttributeNames.has(attrPath) && !validAttributeNames.has(normalizedName)) {
46
+ errors.push(
47
+ `${source}: dimension '${dimKey}' maps to '${attrPath}' which is not in fieldMappings.attributes. Add the attribute or remove from dimensions.`
48
+ );
49
+ }
50
+ }
51
+ }
52
+ return errors;
53
+ }
54
+
55
+ /**
56
+ * Validates crossSystemJson: path format, exactly one operator per path, allowed operators and value types.
57
+ *
58
+ * @param {Object} crossSystemJson - Object mapping field paths to operator objects
59
+ * @returns {string[]} Error messages
60
+ */
61
+ function validateCrossSystemJson(crossSystemJson) {
62
+ const errors = [];
63
+ if (!crossSystemJson || typeof crossSystemJson !== 'object' || Array.isArray(crossSystemJson)) {
64
+ return errors;
65
+ }
66
+ for (const [path, opObj] of Object.entries(crossSystemJson)) {
67
+ if (!CROSS_SYSTEM_JSON_PATH_PATTERN.test(path)) {
68
+ errors.push(
69
+ `config.abac.crossSystemJson: path '${path}' must contain only letters, numbers, underscores, and dots.`
70
+ );
71
+ continue;
72
+ }
73
+ if (typeof opObj !== 'object' || opObj === null || Array.isArray(opObj)) {
74
+ errors.push(
75
+ `config.abac.crossSystemJson.${path}: value must be an object with exactly one operator (e.g. { "eq": "user.country" }).`
76
+ );
77
+ continue;
78
+ }
79
+ const keys = Object.keys(opObj);
80
+ if (keys.length === 0) {
81
+ errors.push(
82
+ `config.abac.crossSystemJson.${path}: object must have exactly one operator. Allowed: ${[...ALLOWED_CROSS_SYSTEM_OPERATORS].join(', ')}.`
83
+ );
84
+ } else if (keys.length > 1) {
85
+ errors.push(
86
+ `config.abac.crossSystemJson.${path}: must have exactly one operator per path, got ${keys.join(', ')}. Use one of: ${[...ALLOWED_CROSS_SYSTEM_OPERATORS].join(', ')}.`
87
+ );
88
+ } else {
89
+ const op = keys[0];
90
+ if (!ALLOWED_CROSS_SYSTEM_OPERATORS.has(op)) {
91
+ errors.push(
92
+ `config.abac.crossSystemJson.${path}: unknown operator '${op}'. Allowed: ${[...ALLOWED_CROSS_SYSTEM_OPERATORS].join(', ')}.`
93
+ );
94
+ }
95
+ }
96
+ }
97
+ return errors;
98
+ }
99
+
100
+ /**
101
+ * Validates ABAC configuration for a parsed datasource.
102
+ * Checks dimensions from config.abac, crossSystemJson, and rejects legacy crossSystem.
103
+ *
104
+ * @function validateAbac
105
+ * @param {Object} parsed - Parsed datasource object (after JSON parse)
106
+ * @returns {string[]} Array of error messages; empty if valid
107
+ *
108
+ * @example
109
+ * const errors = validateAbac(parsed);
110
+ * if (errors.length > 0) errors.forEach(e => console.error(e));
111
+ */
112
+ function validateAbac(parsed) {
113
+ const errors = [];
114
+ const abac = parsed?.config?.abac;
115
+ if (!abac || typeof abac !== 'object') {
116
+ return errors;
117
+ }
118
+
119
+ if ('crossSystem' in abac) {
120
+ errors.push(
121
+ 'config.abac.crossSystem is deprecated. Use config.abac.crossSystemJson or config.abac.crossSystemSql instead.'
122
+ );
123
+ }
124
+
125
+ const attributeNames = new Set(
126
+ Object.keys(parsed?.fieldMappings?.attributes ?? {})
127
+ );
128
+
129
+ if (abac.dimensions) {
130
+ errors.push(...validateDimensionsObject(
131
+ abac.dimensions,
132
+ 'config.abac.dimensions',
133
+ attributeNames
134
+ ));
135
+ }
136
+
137
+ if (abac.crossSystemJson) {
138
+ errors.push(...validateCrossSystemJson(abac.crossSystemJson));
139
+ }
140
+
141
+ return errors;
142
+ }
143
+
144
+ module.exports = {
145
+ validateAbac,
146
+ validateDimensionsObject,
147
+ validateCrossSystemJson
148
+ };