@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
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview Reusable TTY helpers for validation/test reports (verdict, readiness, data-quality lines).
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const { SEP, statusGlyph } = require('./datasource-test-run-display');
8
+
9
+ const TRUST_LINE_LABELS = Object.freeze({
10
+ schema: 'Schema coverage',
11
+ consistency: 'Data consistency',
12
+ reliability: 'Data reliability'
13
+ });
14
+
15
+ function rollupGlyph(r) {
16
+ return statusGlyph(r === 'fail' ? 'fail' : r === 'warn' ? 'warn' : 'ok');
17
+ }
18
+
19
+ function verdictLineFromEnvelope(rootStatus, certStatus, runType) {
20
+ if (rootStatus === 'skipped') return '⏭ Skipped';
21
+ if (rootStatus === 'warn') return '⚠ Limited production use';
22
+ if (rootStatus === 'fail') {
23
+ if (runType === 'integration') return '✖ Pipeline not working';
24
+ if (runType === 'e2e') return '✖ Not usable';
25
+ return '✖ Configuration invalid';
26
+ }
27
+ if (certStatus === 'not_passed') return '✔ Functional with certification gaps';
28
+ return '✔ Suitable for production use';
29
+ }
30
+
31
+ function verdictLineLocalExternalTest(status) {
32
+ if (status === 'ok') return '✔ Suitable for continued setup (local manifest check passed).';
33
+ if (status === 'warn') return '⚠ Limited production use';
34
+ return '✖ Configuration invalid';
35
+ }
36
+
37
+ function readinessLineFromAggregateStatus(status) {
38
+ if (status === 'fail') return `Readiness: ${statusGlyph('fail')} Not ready`;
39
+ if (status === 'warn') return `Readiness: ${statusGlyph('warn')} Partial`;
40
+ return `Readiness: ${statusGlyph('ok')} Ready`;
41
+ }
42
+
43
+ function readinessLineFromDataReadiness(dataReadiness) {
44
+ if (!dataReadiness) return null;
45
+ if (dataReadiness === 'not_ready') return `Readiness: ${statusGlyph('fail')} Not ready`;
46
+ if (dataReadiness === 'partial') return `Readiness: ${statusGlyph('warn')} Partial`;
47
+ if (dataReadiness === 'ready') return `Readiness: ${statusGlyph('ok')} Ready`;
48
+ return null;
49
+ }
50
+
51
+ function formatDataQualityLines(rollups, descriptions) {
52
+ const d = descriptions || {};
53
+ return [
54
+ `${rollupGlyph(rollups.schema)} ${TRUST_LINE_LABELS.schema}${d.schema ? ` — ${d.schema}` : ''}`,
55
+ `${rollupGlyph(rollups.consistency)} ${TRUST_LINE_LABELS.consistency}${d.consistency ? ` — ${d.consistency}` : ''}`,
56
+ `${rollupGlyph(rollups.reliability)} ${TRUST_LINE_LABELS.reliability}${d.reliability ? ` — ${d.reliability}` : ''}`
57
+ ];
58
+ }
59
+
60
+ function pushSeparatorBlock(lines) {
61
+ lines.push('');
62
+ lines.push(SEP);
63
+ lines.push('');
64
+ }
65
+
66
+ module.exports = {
67
+ SEP,
68
+ statusGlyph,
69
+ TRUST_LINE_LABELS,
70
+ rollupGlyph,
71
+ verdictLineFromEnvelope,
72
+ verdictLineLocalExternalTest,
73
+ readinessLineFromAggregateStatus,
74
+ readinessLineFromDataReadiness,
75
+ formatDataQualityLines,
76
+ pushSeparatorBlock
77
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @fileoverview Poll GET /api/v1/validation/run/{testRunId} until reportCompleteness is full (plan §3.5–3.6).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { getValidationRunWithTransportRetry } = require('./validation-run-post-retry');
8
+
9
+ const INITIAL_INTERVAL_MS = 2000;
10
+ const MAX_INTERVAL_MS = 15000;
11
+
12
+ /**
13
+ * Delay between polls after attempt `n` (0-based): 2s, 4s, 8s, … cap 15s.
14
+ * @param {number} attemptIndex - Zero-based poll index after initial POST
15
+ * @returns {number}
16
+ */
17
+ function nextPollDelayMs(attemptIndex) {
18
+ const raw = INITIAL_INTERVAL_MS * 2 ** Math.max(0, attemptIndex);
19
+ return Math.min(raw, MAX_INTERVAL_MS);
20
+ }
21
+
22
+ function sleep(ms) {
23
+ return new Promise(resolve => setTimeout(resolve, ms));
24
+ }
25
+
26
+ /**
27
+ * Whether polling should stop on this envelope.
28
+ * @param {Object} envelope - DatasourceTestRun-like
29
+ * @returns {boolean}
30
+ */
31
+ function isTerminalReportCompleteness(envelope) {
32
+ if (!envelope || typeof envelope !== 'object') return false;
33
+ return envelope.reportCompleteness === 'full';
34
+ }
35
+
36
+ /**
37
+ * Poll until reportCompleteness === 'full' or budget exhausted.
38
+ * @async
39
+ * @param {Object} opts
40
+ * @param {string} opts.dataplaneUrl
41
+ * @param {Object} opts.authConfig
42
+ * @param {string} opts.testRunId
43
+ * @param {number} opts.budgetMs - Remaining wall-clock budget for polls only (POST excluded)
44
+ * @param {typeof getValidationRunWithTransportRetry} [opts.fetchRun] - Inject for tests (default: GET with transport retry)
45
+ * @returns {Promise<{ envelope: Object|null, timedOut: boolean, lastApiResult: Object|null }>}
46
+ */
47
+ async function pollValidationRunUntilComplete(opts) {
48
+ const {
49
+ dataplaneUrl,
50
+ authConfig,
51
+ testRunId,
52
+ budgetMs,
53
+ fetchRun = getValidationRunWithTransportRetry
54
+ } = opts;
55
+ const deadline = Date.now() + Math.max(0, budgetMs);
56
+ let attempt = 0;
57
+ let lastApiResult = null;
58
+ let envelope = null;
59
+
60
+ while (Date.now() < deadline) {
61
+ const remaining = deadline - Date.now();
62
+ if (remaining <= 0) break;
63
+
64
+ lastApiResult = await fetchRun(dataplaneUrl, authConfig, testRunId);
65
+ if (!lastApiResult.success) {
66
+ return { envelope: null, timedOut: false, lastApiResult };
67
+ }
68
+ envelope = lastApiResult.data;
69
+ if (isTerminalReportCompleteness(envelope)) {
70
+ return { envelope, timedOut: false, lastApiResult };
71
+ }
72
+
73
+ const delay = Math.min(nextPollDelayMs(attempt), Math.max(0, deadline - Date.now()));
74
+ attempt += 1;
75
+ if (delay > 0) {
76
+ await sleep(delay);
77
+ }
78
+ }
79
+
80
+ return { envelope, timedOut: true, lastApiResult };
81
+ }
82
+
83
+ module.exports = {
84
+ INITIAL_INTERVAL_MS,
85
+ MAX_INTERVAL_MS,
86
+ nextPollDelayMs,
87
+ pollValidationRunUntilComplete,
88
+ isTerminalReportCompleteness
89
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @fileoverview Transient transport retries for POST validation/run and GET poll.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { postValidationRun, getValidationRun } = require('../api/validation-run.api');
8
+
9
+ const RETRYABLE_CODES = new Set(['ECONNRESET', 'ETIMEDOUT', 'ECONNABORTED']);
10
+
11
+ /**
12
+ * @param {Object} res - ApiClient-style result
13
+ * @returns {boolean}
14
+ */
15
+ function isRetryablePostFailure(res) {
16
+ if (!res || res.success) return false;
17
+ if (!res.network) return false;
18
+ const err = res.originalError;
19
+ if (!err) return false;
20
+ const code = err.code || (err.cause && err.cause.code);
21
+ if (code && RETRYABLE_CODES.has(code)) return true;
22
+ if (err.name === 'AbortError') return true;
23
+ return false;
24
+ }
25
+
26
+ function sleep(ms) {
27
+ return new Promise(resolve => setTimeout(resolve, ms));
28
+ }
29
+
30
+ /**
31
+ * POST validation run with up to 2 retries on transient socket/timeout errors (1s / 2s backoff).
32
+ * Does not retry HTTP 4xx/5xx.
33
+ * @param {string} dataplaneUrl
34
+ * @param {Object} authConfig
35
+ * @param {Object} body
36
+ * @returns {Promise<Object>}
37
+ */
38
+ async function postValidationRunWithTransportRetry(dataplaneUrl, authConfig, body) {
39
+ let last = await postValidationRun(dataplaneUrl, authConfig, body);
40
+ if (last.success || !isRetryablePostFailure(last)) return last;
41
+
42
+ await sleep(1000);
43
+ last = await postValidationRun(dataplaneUrl, authConfig, body);
44
+ if (last.success || !isRetryablePostFailure(last)) return last;
45
+
46
+ await sleep(2000);
47
+ return postValidationRun(dataplaneUrl, authConfig, body);
48
+ }
49
+
50
+ /**
51
+ * GET validation run poll with up to 2 retries on transient socket/timeout errors (1s / 2s backoff).
52
+ * @param {string} dataplaneUrl
53
+ * @param {Object} authConfig
54
+ * @param {string} testRunId
55
+ * @returns {Promise<Object>}
56
+ */
57
+ async function getValidationRunWithTransportRetry(dataplaneUrl, authConfig, testRunId) {
58
+ let last = await getValidationRun(dataplaneUrl, authConfig, testRunId);
59
+ if (last.success || !isRetryablePostFailure(last)) return last;
60
+
61
+ await sleep(1000);
62
+ last = await getValidationRun(dataplaneUrl, authConfig, testRunId);
63
+ if (last.success || !isRetryablePostFailure(last)) return last;
64
+
65
+ await sleep(2000);
66
+ return getValidationRun(dataplaneUrl, authConfig, testRunId);
67
+ }
68
+
69
+ module.exports = {
70
+ isRetryablePostFailure,
71
+ postValidationRunWithTransportRetry,
72
+ getValidationRunWithTransportRetry
73
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @fileoverview Build ValidationRunRequest bodies for datasource-scoped CLI commands.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Whether the unified validation request should set includeDebug (any `--debug` or `--debug <level>`).
9
+ * @param {*} debugOpt - Commander `options.debug`
10
+ * @returns {boolean}
11
+ */
12
+ function includeDebugForRequest(debugOpt) {
13
+ if (debugOpt === undefined || debugOpt === false || debugOpt === null || debugOpt === '') {
14
+ return false;
15
+ }
16
+ return true;
17
+ }
18
+
19
+ /**
20
+ * Merge E2E options from CLI flags into e2eOptions for ExternalDataSourceE2ETestRequest (dataplane).
21
+ * @param {Object} options - CLI-derived options
22
+ * @param {boolean} [options.debug]
23
+ * @param {boolean} [options.verbose]
24
+ * @param {boolean} [options.testCrud]
25
+ * @param {string} [options.recordId]
26
+ * @param {boolean} [options.cleanup]
27
+ * @param {string|Object} [options.primaryKeyValue]
28
+ * @param {Object} [options.e2eOptionsExtra] - Shallow-merged last (e.g. server-specific drill-down fields)
29
+ * @returns {Object}
30
+ */
31
+ function buildE2eOptionsFromCli(options = {}) {
32
+ const e2e = {};
33
+ if (options.debug) e2e.includeDebug = true;
34
+ if (options.verbose) e2e.audit = true;
35
+ if (options.testCrud === true) e2e.testCrud = true;
36
+ if (options.recordId !== undefined && options.recordId !== null && options.recordId !== '') {
37
+ e2e.recordId = String(options.recordId);
38
+ }
39
+ if (options.cleanup === false) e2e.cleanup = false;
40
+ else if (options.cleanup === true) e2e.cleanup = true;
41
+ if (options.primaryKeyValue !== undefined && options.primaryKeyValue !== null) {
42
+ e2e.primaryKeyValue = options.primaryKeyValue;
43
+ }
44
+ if (options.e2eOptionsExtra && typeof options.e2eOptionsExtra === 'object') {
45
+ Object.assign(e2e, options.e2eOptionsExtra);
46
+ }
47
+ return e2e;
48
+ }
49
+
50
+ /**
51
+ * Build request body for validationScope=externalDataSource (single datasource in DB).
52
+ * @param {Object} params
53
+ * @param {string} params.systemKey - External system key
54
+ * @param {string} params.datasourceKey - Datasource key
55
+ * @param {'test'|'integration'|'e2e'} params.runType
56
+ * @param {Object} [params.payloadTemplate] - Required for integration-style payload tests when runType=test
57
+ * @param {boolean} [params.asyncRun]
58
+ * @param {boolean} [params.includeDebug]
59
+ * @param {boolean} [params.explain]
60
+ * @param {Object} [params.e2eOptions] - Merged with buildE2eOptionsFromCli when both used by caller
61
+ * @returns {import('../api/types/validation-run.types').ValidationRunRequestBody}
62
+ */
63
+ function buildExternalDataSourceValidationRequest(params) {
64
+ const {
65
+ systemKey,
66
+ datasourceKey,
67
+ runType,
68
+ payloadTemplate,
69
+ asyncRun,
70
+ includeDebug,
71
+ explain,
72
+ e2eOptions
73
+ } = params;
74
+ if (!systemKey || !datasourceKey || !runType) {
75
+ throw new Error('systemKey, datasourceKey, and runType are required');
76
+ }
77
+ /** @type {import('../api/types/validation-run.types').ValidationRunRequestBody} */
78
+ const body = {
79
+ validationScope: 'externalDataSource',
80
+ systemIdOrKey: systemKey,
81
+ datasourceKey,
82
+ runType
83
+ };
84
+ if (payloadTemplate !== undefined) body.payloadTemplate = payloadTemplate;
85
+ if (asyncRun === true) body.asyncRun = true;
86
+ if (includeDebug === true) body.includeDebug = true;
87
+ if (explain === true) body.explain = true;
88
+ if (e2eOptions && typeof e2eOptions === 'object' && Object.keys(e2eOptions).length > 0) {
89
+ body.e2eOptions = e2eOptions;
90
+ }
91
+ return body;
92
+ }
93
+
94
+ module.exports = {
95
+ includeDebugForRequest,
96
+ buildE2eOptionsFromCli,
97
+ buildExternalDataSourceValidationRequest
98
+ };
@@ -105,6 +105,9 @@ function handlePartialAuthentication(authentication) {
105
105
  */
106
106
  function transformFlatStructure(variables, appName) {
107
107
  const result = buildBaseResult(variables, appName);
108
+ if (result.frontDoorRouting) {
109
+ result.frontDoorRouting = normalizeFrontDoorRoutingForValidation(result.frontDoorRouting);
110
+ }
108
111
 
109
112
  // Sanitize authentication if present
110
113
  if (result.authentication) {
@@ -190,9 +193,6 @@ function validateBuildConfig(build) {
190
193
  if (build.dockerfile && build.dockerfile.trim() !== '') {
191
194
  buildConfig.dockerfile = build.dockerfile;
192
195
  }
193
- if (build.localPort !== undefined && build.localPort !== null) {
194
- buildConfig.localPort = build.localPort;
195
- }
196
196
 
197
197
  return Object.keys(buildConfig).length > 0 ? buildConfig : null;
198
198
  }
@@ -256,6 +256,23 @@ function transformConfigSections(variables, transformed) {
256
256
  }
257
257
  }
258
258
 
259
+ /**
260
+ * Clone frontDoorRouting for schema validation: JSON Schema expects `tls` as a string
261
+ * (e.g. "${TLS_ENABLED}", "true") but YAML often uses booleans (`tls: false`).
262
+ * @param {Object} fd - frontDoorRouting block from application.yaml
263
+ * @returns {Object}
264
+ */
265
+ function normalizeFrontDoorRoutingForValidation(fd) {
266
+ if (!fd || typeof fd !== 'object') {
267
+ return fd;
268
+ }
269
+ const out = { ...fd };
270
+ if (typeof out.tls === 'boolean') {
271
+ out.tls = out.tls ? 'true' : 'false';
272
+ }
273
+ return out;
274
+ }
275
+
259
276
  /**
260
277
  * Transforms simple optional fields
261
278
  * @function transformSimpleOptionalFields
@@ -273,7 +290,7 @@ function transformSimpleOptionalFields(variables, transformed) {
273
290
  transformed.scaling = variables.scaling;
274
291
  }
275
292
  if (variables.frontDoorRouting) {
276
- transformed.frontDoorRouting = variables.frontDoorRouting;
293
+ transformed.frontDoorRouting = normalizeFrontDoorRoutingForValidation(variables.frontDoorRouting);
277
294
  }
278
295
  if (variables.roles) {
279
296
  transformed.roles = variables.roles;
@@ -185,6 +185,9 @@ function formatValue(value, quoted, quoteChar) {
185
185
  * const result = encryptYamlValues(yamlContent, encryptionKey);
186
186
  * // Returns: { content: '...', encrypted: 5, total: 10 }
187
187
  */
188
+ /** Pattern for YAML block scalar indicator (e.g. key: >- or key: |) */
189
+ const BLOCK_SCALAR_PATTERN = /^(\s*)([^#:\n]+?):\s*(\|[-+]?|>[-+]?)(\s*)(#.*)?$/;
190
+
188
191
  /**
189
192
  * Processes a single line for encryption
190
193
  * @function processLineForEncryption
@@ -221,13 +224,87 @@ function processLineForEncryption(line, encryptionKey, stats) {
221
224
  return line;
222
225
  }
223
226
 
227
+ /**
228
+ * Collects continuation lines for a YAML block scalar (value on next lines).
229
+ * @param {string[]} lines - All lines
230
+ * @param {number} startIndex - Index of line after the key: >- line
231
+ * @param {number} keyIndentLen - Number of leading spaces on the key line
232
+ * @returns {{ value: string, endIndex: number }} Combined value and index after last continuation line
233
+ */
234
+ function collectBlockScalarLines(lines, startIndex, keyIndentLen) {
235
+ const parts = [];
236
+ let i = startIndex;
237
+ while (i < lines.length) {
238
+ const line = lines[i];
239
+ const contentTrimmed = line.trim();
240
+ if (contentTrimmed === '' || contentTrimmed.startsWith('#')) {
241
+ i++;
242
+ continue;
243
+ }
244
+ const indentLen = line.length - line.trimStart().length;
245
+ if (indentLen <= keyIndentLen) {
246
+ break;
247
+ }
248
+ parts.push(contentTrimmed);
249
+ i++;
250
+ }
251
+ return { value: parts.join(' '), endIndex: i };
252
+ }
253
+
254
+ /**
255
+ * Processes a single block-scalar key line and its continuation lines.
256
+ * @param {string} line - Line matching BLOCK_SCALAR_PATTERN
257
+ * @param {string[]} lines - All lines
258
+ * @param {number} lineIndex - Index of current line
259
+ * @param {string} encryptionKey - Encryption key
260
+ * @param {Object} stats - Mutable stats object
261
+ * @returns {{ resultLine: string, nextIndex: number }|null} Result or null if not a block scalar
262
+ */
263
+ function processBlockScalarLine(line, lines, lineIndex, encryptionKey, stats) {
264
+ const blockMatch = line.match(BLOCK_SCALAR_PATTERN);
265
+ if (!blockMatch) {
266
+ return null;
267
+ }
268
+ const [, indent, key, blockIndicator, trailingWhitespace, comment] = blockMatch;
269
+ const keyIndentLen = indent.length;
270
+ const folded = blockIndicator.startsWith('>');
271
+ const { value: rawValue, endIndex } = collectBlockScalarLines(lines, lineIndex + 1, keyIndentLen);
272
+ const value = folded ? rawValue.replace(/\s+/g, ' ').trim() : rawValue;
273
+ stats.total++;
274
+ const needEncrypt = shouldEncryptValue(value);
275
+ const outValue = needEncrypt ? encryptSecret(value, encryptionKey) : value;
276
+ if (needEncrypt) {
277
+ stats.encrypted++;
278
+ }
279
+ const resultLine = `${indent}${key}: ${outValue}${trailingWhitespace || ''}${comment || ''}`;
280
+ return { resultLine, nextIndex: endIndex };
281
+ }
282
+
224
283
  function encryptYamlValues(content, encryptionKey) {
225
284
  const lines = content.split(/\r?\n/);
226
285
  const encryptedLines = [];
227
286
  const stats = { encrypted: 0, total: 0 };
287
+ let i = 0;
288
+
289
+ while (i < lines.length) {
290
+ const line = lines[i];
291
+ const trimmed = line.trim();
292
+
293
+ if (trimmed === '' || trimmed.startsWith('#')) {
294
+ encryptedLines.push(line);
295
+ i++;
296
+ continue;
297
+ }
298
+
299
+ const blockResult = processBlockScalarLine(line, lines, i, encryptionKey, stats);
300
+ if (blockResult) {
301
+ encryptedLines.push(blockResult.resultLine);
302
+ i = blockResult.nextIndex;
303
+ continue;
304
+ }
228
305
 
229
- for (const line of lines) {
230
306
  encryptedLines.push(processLineForEncryption(line, encryptionKey, stats));
307
+ i++;
231
308
  }
232
309
 
233
310
  return {
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Optional v2.4.x datasource validation warnings (beyond JSON Schema).
3
+ *
4
+ * @fileoverview Post-schema warnings for external datasource configs
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const STORAGE_ENTITIES = new Set(['recordStorage', 'documentStorage']);
10
+
11
+ function warnMissingDimensions(parsed, warnings) {
12
+ const entityType = parsed.entityType;
13
+ if (!STORAGE_ENTITIES.has(entityType)) {
14
+ return;
15
+ }
16
+ const dims = parsed.dimensions;
17
+ if (!dims || typeof dims !== 'object' || Array.isArray(dims) || Object.keys(dims).length === 0) {
18
+ warnings.push(
19
+ `Datasource "${parsed.key || '(unknown)'}": dimensions missing or empty for entityType=${entityType} (recommended for ABAC; see schema 2.4.x notes).`
20
+ );
21
+ }
22
+ }
23
+
24
+ function warnFkWithoutActor(parsed, warnings) {
25
+ const dimensions = parsed.dimensions;
26
+ if (!dimensions || typeof dimensions !== 'object' || Array.isArray(dimensions)) {
27
+ return;
28
+ }
29
+ for (const [dimKey, binding] of Object.entries(dimensions)) {
30
+ if (!binding || typeof binding !== 'object' || binding.type !== 'fk') {
31
+ continue;
32
+ }
33
+ if (!Object.prototype.hasOwnProperty.call(binding, 'actor')) {
34
+ warnings.push(
35
+ `Datasource "${parsed.key || '(unknown)'}": dimension "${dimKey}" uses type=fk without actor; set actor (displayName, email, userId, groups, roles) for predictable ABAC binding.`
36
+ );
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Collects non-fatal warnings for an external datasource document (already parsed).
43
+ * @param {Object} parsed - Parsed datasource JSON
44
+ * @returns {string[]} Warning messages (empty if none)
45
+ */
46
+ function collectExternalDatasourceWarnings(parsed) {
47
+ const warnings = [];
48
+ if (!parsed || typeof parsed !== 'object') {
49
+ return warnings;
50
+ }
51
+ warnMissingDimensions(parsed, warnings);
52
+ warnFkWithoutActor(parsed, warnings);
53
+ return warnings;
54
+ }
55
+
56
+ module.exports = { collectExternalDatasourceWarnings };
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  const { loadExternalIntegrationConfig, loadSystemFile } = require('../generator/external');
13
+ const { getKvPathSegmentForSecurityKey } = require('../utils/credential-secrets-env');
13
14
 
14
15
  /**
15
16
  * Extracts all kv:// paths from env.template content (RHS of VAR=value lines).
@@ -93,7 +94,7 @@ function setHasPathIgnoreCase(pathSet, requiredPath) {
93
94
  * @returns {Promise<{ requiredPaths: Set<string>, warning?: string }>} Required kv paths and optional warning
94
95
  *
95
96
  * @example
96
- * const { requiredPaths } = await collectRequiredAuthKvPaths('/path/to/integration/hubspot');
97
+ * const { requiredPaths } = await collectRequiredAuthKvPaths('/path/to/integration/hubspot-test');
97
98
  * // requiredPaths has kv:// paths from authentication.security
98
99
  */
99
100
  async function collectRequiredAuthKvPaths(appPath, _options = {}) {
@@ -148,10 +149,57 @@ async function validateAuthKvCoverage(appPath, content, errors, warnings, option
148
149
  }
149
150
  }
150
151
 
152
+ /**
153
+ * Derives system key from system file name (e.g. hubspot-system.yaml -> hubspot).
154
+ * @param {string} systemFileName - System file name
155
+ * @returns {string}
156
+ */
157
+ function systemKeyFromFileName(systemFileName) {
158
+ if (!systemFileName || typeof systemFileName !== 'string') return '';
159
+ return systemFileName.replace(/-system\.(yaml|yml|json)$/i, '');
160
+ }
161
+
162
+ /**
163
+ * Validates that authentication.security paths in system files match the canonical path
164
+ * (kv://<systemKey>/<getKvPathSegmentForSecurityKey(securityKey)>). Pushes errors when they differ.
165
+ *
166
+ * @async
167
+ * @function validateAuthSecurityPathConsistency
168
+ * @param {string} appPath - Application path (integration or builder dir)
169
+ * @param {string[]} errors - Errors array to push to
170
+ * @param {string[]} warnings - Warnings array to push to (unused; for API consistency)
171
+ */
172
+ async function validateAuthSecurityPathConsistency(appPath, errors, _warnings) {
173
+ try {
174
+ const { schemaBasePath, systemFiles } = await loadExternalIntegrationConfig(appPath);
175
+ for (const systemFileName of systemFiles) {
176
+ const systemKey = systemKeyFromFileName(systemFileName);
177
+ if (!systemKey) continue;
178
+ const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFileName);
179
+ const security = systemJson.authentication?.security;
180
+ if (!security || typeof security !== 'object') continue;
181
+ for (const [key, value] of Object.entries(security)) {
182
+ if (typeof value !== 'string' || !/^kv:\/\/.+/.test(value)) continue;
183
+ const canonicalSegment = getKvPathSegmentForSecurityKey(key);
184
+ const canonicalPath = canonicalSegment ? `kv://${systemKey}/${canonicalSegment}` : null;
185
+ if (canonicalPath && value !== canonicalPath) {
186
+ errors.push(
187
+ `authentication.security.${key} has path ${value}; canonical path is ${canonicalPath}. Run \`aifabrix repair <systemKey>\` to normalize.`
188
+ );
189
+ }
190
+ }
191
+ }
192
+ } catch (error) {
193
+ _warnings.push(`Could not validate auth path consistency: ${error.message}`);
194
+ }
195
+ }
196
+
151
197
  module.exports = {
152
198
  extractKvPathsFromEnvTemplate,
153
199
  extractKvPathsFromCommentedLines,
154
200
  setHasPathIgnoreCase,
155
201
  collectRequiredAuthKvPaths,
156
- validateAuthKvCoverage
202
+ validateAuthKvCoverage,
203
+ validateAuthSecurityPathConsistency,
204
+ systemKeyFromFileName
157
205
  };