@aifabrix/builder 2.45.5 → 2.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/.npmrc.token +1 -0
  5. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  8. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  9. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  10. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  14. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  15. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  16. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  17. package/.temp/validation/summary.txt +5 -0
  18. package/README.md +43 -17
  19. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  20. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  21. package/jest.config.default.js +2 -1
  22. package/jest.config.isolated.js +2 -1
  23. package/jest.global-live-fabrix-hooks.js +36 -0
  24. package/jest.isolated-projects.js +146 -0
  25. package/jest.projects.js +65 -136
  26. package/lib/api/api-response-errors.js +124 -0
  27. package/lib/api/dataplane-health.api.js +19 -7
  28. package/lib/api/governance-scenario-pack.api.js +159 -0
  29. package/lib/api/index.js +43 -3
  30. package/lib/api/lifecycle.api.js +98 -0
  31. package/lib/api/pipeline.api.js +3 -1
  32. package/lib/api/records-bulk.api.js +14 -1
  33. package/lib/api/resource-types.api.js +107 -0
  34. package/lib/api/runtime-configuration.api.js +72 -0
  35. package/lib/api/types/resource-types.types.js +45 -0
  36. package/lib/api/user-groups.api.js +9 -9
  37. package/lib/api/users.api.js +8 -8
  38. package/lib/api/validation-runner.js +3 -1
  39. package/lib/api/wizard.api.js +10 -2
  40. package/lib/app/deploy-config.js +4 -1
  41. package/lib/app/down.js +78 -27
  42. package/lib/app/index.js +2 -2
  43. package/lib/app/push.js +4 -5
  44. package/lib/app/register.js +7 -4
  45. package/lib/app/run-env-compose.js +84 -30
  46. package/lib/app/run-resolve-image.js +2 -1
  47. package/lib/app/run-stop-containers.js +113 -0
  48. package/lib/app/run.js +1 -31
  49. package/lib/app/show-display.js +35 -0
  50. package/lib/app/show.js +32 -1
  51. package/lib/build/index.js +22 -5
  52. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  53. package/lib/certification/sync-system-certification.js +13 -6
  54. package/lib/channels/add-channel.js +348 -0
  55. package/lib/channels/channel-artifact-generator.js +141 -0
  56. package/lib/channels/channel-audit.js +101 -0
  57. package/lib/channels/channel-auth-context.js +37 -0
  58. package/lib/channels/channel-credential.js +200 -0
  59. package/lib/channels/channel-display-name.js +64 -0
  60. package/lib/channels/channel-external-system.js +106 -0
  61. package/lib/channels/channel-integration-generator.js +274 -0
  62. package/lib/channels/channel-probe.js +89 -0
  63. package/lib/channels/channel-runtime-resolver.js +152 -0
  64. package/lib/channels/channel-status-display.js +141 -0
  65. package/lib/channels/channel-targets.js +115 -0
  66. package/lib/cli/index.js +20 -4
  67. package/lib/cli/infra-guided-auth.js +109 -0
  68. package/lib/cli/infra-guided.js +84 -81
  69. package/lib/cli/setup-app.help.js +0 -31
  70. package/lib/cli/setup-app.js +82 -5
  71. package/lib/cli/setup-app.test-commands.js +5 -57
  72. package/lib/cli/setup-auth.js +25 -0
  73. package/lib/cli/setup-certification-commands.js +147 -0
  74. package/lib/cli/setup-certification.help.js +141 -0
  75. package/lib/cli/setup-environment.js +19 -2
  76. package/lib/cli/setup-external-system-help.js +62 -0
  77. package/lib/cli/setup-external-system.js +25 -3
  78. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  79. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  80. package/lib/cli/setup-infra.js +9 -1
  81. package/lib/cli/setup-platform.js +6 -1
  82. package/lib/cli/setup-secrets.js +33 -1
  83. package/lib/cli/setup-utility-help-after.js +25 -2
  84. package/lib/cli/setup-utility-repair.js +198 -0
  85. package/lib/cli/setup-utility-resolve.js +44 -5
  86. package/lib/cli/setup-utility-validate.js +197 -0
  87. package/lib/cli/setup-utility.js +4 -239
  88. package/lib/commands/app-logs.js +54 -26
  89. package/lib/commands/app.js +41 -0
  90. package/lib/commands/auth-status-client-tokens.js +214 -0
  91. package/lib/commands/auth-status-dataplane-version.js +142 -68
  92. package/lib/commands/auth-status.js +43 -54
  93. package/lib/commands/channel.js +58 -0
  94. package/lib/commands/datasource-test-trust-cli.js +27 -23
  95. package/lib/commands/datasource-unified-test-cli.js +6 -3
  96. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  97. package/lib/commands/datasource.js +8 -8
  98. package/lib/commands/dev-show-display.js +8 -1
  99. package/lib/commands/governance-cli-shared.js +94 -0
  100. package/lib/commands/governance-command-action.js +132 -0
  101. package/lib/commands/governance-download-external.js +34 -0
  102. package/lib/commands/governance-generate-external.js +71 -0
  103. package/lib/commands/governance-upload-external.js +36 -0
  104. package/lib/commands/governance-verify-external.js +64 -0
  105. package/lib/commands/governance.js +144 -0
  106. package/lib/commands/identity-apply-sync.js +49 -8
  107. package/lib/commands/identity-group-cli.js +66 -26
  108. package/lib/commands/identity-membership-role-cache.js +58 -37
  109. package/lib/commands/identity-shared.js +22 -1
  110. package/lib/commands/identity-user-cli.js +80 -34
  111. package/lib/commands/lifecycle-command-action.js +123 -0
  112. package/lib/commands/lifecycle-external.js +268 -0
  113. package/lib/commands/login.js +11 -2
  114. package/lib/commands/repair-auth-apply.js +153 -0
  115. package/lib/commands/repair-auth-config.js +8 -13
  116. package/lib/commands/repair-changed-files.js +41 -0
  117. package/lib/commands/repair-datasource-com-v2.js +15 -0
  118. package/lib/commands/repair-datasource-keys.js +4 -1
  119. package/lib/commands/repair-datasource-run.js +59 -0
  120. package/lib/commands/repair-datasource-service-contract.js +206 -0
  121. package/lib/commands/repair-datasource-test-payload.js +259 -0
  122. package/lib/commands/repair-datasource.js +49 -64
  123. package/lib/commands/repair-env-template.js +76 -25
  124. package/lib/commands/repair-external-run.js +216 -0
  125. package/lib/commands/repair-openapi-sync.js +49 -4
  126. package/lib/commands/repair-persist.js +4 -0
  127. package/lib/commands/repair-rbac.js +52 -1
  128. package/lib/commands/repair-steps.js +160 -0
  129. package/lib/commands/repair-system-alignment.js +7 -4
  130. package/lib/commands/repair.js +29 -296
  131. package/lib/commands/resource-type.js +371 -0
  132. package/lib/commands/secrets-get.js +184 -0
  133. package/lib/commands/secrets-remove-all.js +3 -1
  134. package/lib/commands/setup-image-refresh.js +146 -0
  135. package/lib/commands/setup-modes.js +158 -162
  136. package/lib/commands/setup-platform-auth.js +76 -0
  137. package/lib/commands/setup-prompts-admin.js +244 -0
  138. package/lib/commands/setup-prompts-platform-mode.js +70 -0
  139. package/lib/commands/setup-prompts-validators.js +36 -0
  140. package/lib/commands/setup-prompts.js +24 -76
  141. package/lib/commands/setup-run-context.js +45 -0
  142. package/lib/commands/setup-spinners.js +39 -0
  143. package/lib/commands/setup.js +27 -5
  144. package/lib/commands/teardown.js +39 -0
  145. package/lib/commands/test-e2e-external.js +24 -8
  146. package/lib/commands/test-governance-external.js +3 -24
  147. package/lib/commands/test-trust-external.js +48 -100
  148. package/lib/commands/up-common.js +57 -13
  149. package/lib/commands/up-dataplane.js +56 -15
  150. package/lib/commands/upload-publish-context.js +75 -0
  151. package/lib/commands/upload-sidecar-followups.js +89 -0
  152. package/lib/commands/upload.js +113 -110
  153. package/lib/commands/verify-governance-combined-external.js +61 -0
  154. package/lib/commands/verify-governance-command-action.js +64 -0
  155. package/lib/commands/verify-governance-db.js +102 -0
  156. package/lib/commands/verify-governance-external.js +77 -0
  157. package/lib/commands/verify-governance-result-builders.js +71 -0
  158. package/lib/commands/verify-operations-command-action.js +68 -0
  159. package/lib/commands/verify-operations-external.js +268 -0
  160. package/lib/commands/verify-operations-persist.js +75 -0
  161. package/lib/commands/verify-operations-step-plan.js +68 -0
  162. package/lib/commands/verify-operations-steps.js +111 -0
  163. package/lib/commands/verify-trust-command-action.js +73 -0
  164. package/lib/commands/verify-trust-external.js +59 -0
  165. package/lib/commands/wizard-config-normalizer.js +43 -23
  166. package/lib/commands/wizard-core.js +2 -0
  167. package/lib/core/admin-password-generator.js +38 -0
  168. package/lib/core/admin-secrets-bundle.js +111 -0
  169. package/lib/core/admin-secrets-env-overlay.js +62 -0
  170. package/lib/core/admin-secrets.js +32 -4
  171. package/lib/core/config-attach-extensions.js +3 -0
  172. package/lib/core/config-platform-controller.js +44 -0
  173. package/lib/core/config-setup-installation-profile.js +42 -0
  174. package/lib/core/config-setup-platform-mode.js +46 -0
  175. package/lib/core/config.js +46 -36
  176. package/lib/core/secrets-admin-env.js +6 -2
  177. package/lib/core/secrets-ensure.js +4 -0
  178. package/lib/core/secrets-env-content.js +54 -48
  179. package/lib/core/secrets-env-write.js +10 -1
  180. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  181. package/lib/core/setup-installation-profile.js +43 -0
  182. package/lib/core/setup-platform-mode.js +90 -0
  183. package/lib/datasource/agent-trust-debug-log.js +1 -1
  184. package/lib/datasource/agent-trust-run.js +5 -3
  185. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  186. package/lib/datasource/audit-evidence-extract.js +7 -4
  187. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  188. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  189. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  190. package/lib/datasource/capability/capability-manifest.js +254 -0
  191. package/lib/datasource/capability/capability-resolve.js +3 -4
  192. package/lib/datasource/capability/copy-operations.js +5 -3
  193. package/lib/datasource/capability/remove-operations.js +2 -1
  194. package/lib/datasource/capability/run-capability-copy.js +32 -4
  195. package/lib/datasource/capability/run-capability-diff.js +3 -3
  196. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  197. package/lib/datasource/capability/run-capability-edit.js +4 -7
  198. package/lib/datasource/capability/run-capability-relate.js +2 -4
  199. package/lib/datasource/capability/run-capability-remove.js +2 -4
  200. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  201. package/lib/datasource/datasource-identifier.js +2 -1
  202. package/lib/datasource/datasource-load-export-context.js +3 -0
  203. package/lib/datasource/datasource-validate-display.js +1 -1
  204. package/lib/datasource/field-reference-validator.js +24 -3
  205. package/lib/datasource/log-cleaner.js +2 -2
  206. package/lib/datasource/log-viewer.js +8 -8
  207. package/lib/datasource/orphan-profiles-validator.js +98 -0
  208. package/lib/datasource/test-e2e.js +4 -1
  209. package/lib/datasource/unified-validation-run-body.js +3 -1
  210. package/lib/datasource/unified-validation-run.js +5 -2
  211. package/lib/datasource/validate.js +41 -23
  212. package/lib/deployment/environment-poll-status.js +138 -0
  213. package/lib/deployment/environment.js +42 -34
  214. package/lib/external-system/download-helpers.js +87 -0
  215. package/lib/external-system/download.js +78 -138
  216. package/lib/external-system/generator.js +1 -55
  217. package/lib/external-system/integration-auth-context.js +45 -0
  218. package/lib/external-system/test-auth.js +12 -5
  219. package/lib/external-system/test.js +16 -21
  220. package/lib/generator/builders.js +11 -1
  221. package/lib/generator/external-controller-manifest.js +18 -0
  222. package/lib/generator/split.js +3 -1
  223. package/lib/generator/wizard.js +1 -28
  224. package/lib/governance/governance-cli-errors.js +96 -0
  225. package/lib/governance/governance-download-display.js +38 -0
  226. package/lib/governance/governance-run-display.js +215 -25
  227. package/lib/identity/fk-reference.js +65 -0
  228. package/lib/identity/identity-apply-core.js +13 -58
  229. package/lib/identity/identity-apply-phases.js +5 -1
  230. package/lib/identity/identity-display.js +243 -0
  231. package/lib/identity/identity-response-helpers.js +52 -0
  232. package/lib/identity/identity-sync-errors.js +62 -0
  233. package/lib/identity/resolve-user-id.js +129 -0
  234. package/lib/identity/username-default.js +31 -0
  235. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  236. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  237. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  238. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  239. package/lib/infrastructure/helpers.js +23 -129
  240. package/lib/infrastructure/index.js +5 -6
  241. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  242. package/lib/lifecycle/certification-progress.js +231 -0
  243. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  244. package/lib/lifecycle/exit-codes.js +24 -0
  245. package/lib/lifecycle/json-envelope.js +25 -0
  246. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  247. package/lib/lifecycle/logger-quiet.js +29 -0
  248. package/lib/lifecycle/open-api-published.js +59 -0
  249. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  250. package/lib/lifecycle/product-model.js +196 -0
  251. package/lib/lifecycle/report-display-trust.js +70 -0
  252. package/lib/lifecycle/report-display.js +472 -0
  253. package/lib/lifecycle/scenario-upload.js +191 -0
  254. package/lib/lifecycle/verify-step-progress.js +87 -0
  255. package/lib/parameters/infra-kv-discovery.js +27 -2
  256. package/lib/protection/auth-context.js +10 -4
  257. package/lib/resolvers/datasource-resolver.js +2 -3
  258. package/lib/resolvers/resource-type-file.js +159 -0
  259. package/lib/schema/application-schema.json +17 -1
  260. package/lib/schema/datasource-test-run.schema.json +89 -6
  261. package/lib/schema/deployment-rules.yaml +2 -1
  262. package/lib/schema/external-datasource.schema.json +2947 -2619
  263. package/lib/schema/external-system.schema.json +1418 -761
  264. package/lib/schema/infra.parameter.yaml +45 -0
  265. package/lib/schema/type/credential-field-registry.json +103 -97
  266. package/lib/schema/type/document-storage.json +9 -5
  267. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  268. package/lib/utils/api.js +55 -31
  269. package/lib/utils/app-relative-path-env.js +112 -0
  270. package/lib/utils/app-run-containers.js +25 -2
  271. package/lib/utils/authentication-shape.js +224 -0
  272. package/lib/utils/build-copy.js +36 -5
  273. package/lib/utils/build-resolve-image.js +66 -12
  274. package/lib/utils/celery-companion-apps.js +53 -0
  275. package/lib/utils/cli-secrets-error-format.js +83 -21
  276. package/lib/utils/cli-utils.js +20 -4
  277. package/lib/utils/compose-celery-worker.js +23 -0
  278. package/lib/utils/compose-generate-docker-compose.js +13 -1
  279. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  280. package/lib/utils/config-secrets-path.js +46 -0
  281. package/lib/utils/configuration-env-resolver.js +36 -0
  282. package/lib/utils/controller-url.js +88 -1
  283. package/lib/utils/credential-secrets-env-kv.js +216 -0
  284. package/lib/utils/credential-secrets-env.js +175 -257
  285. package/lib/utils/dataplane-resolver.js +18 -2
  286. package/lib/utils/declarative-url-ports.js +27 -4
  287. package/lib/utils/deployment-auth-mode.js +41 -0
  288. package/lib/utils/developer-volume-id.js +33 -0
  289. package/lib/utils/docker-build.js +1 -4
  290. package/lib/utils/env-copy.js +14 -9
  291. package/lib/utils/env-kv-orphan-lines.js +43 -0
  292. package/lib/utils/error-formatters/network-errors.js +13 -4
  293. package/lib/utils/external-env-template.js +11 -3
  294. package/lib/utils/external-system-readiness-core.js +10 -0
  295. package/lib/utils/external-system-readiness-display.js +80 -23
  296. package/lib/utils/external-system-validators.js +8 -2
  297. package/lib/utils/health-check.js +4 -1
  298. package/lib/utils/help-builder.js +13 -8
  299. package/lib/utils/keycloak-realm-ready.js +101 -0
  300. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  301. package/lib/utils/manifest-source-emit.js +2 -0
  302. package/lib/utils/paths.js +25 -0
  303. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  304. package/lib/utils/platform-controller-url.js +157 -0
  305. package/lib/utils/platform-kv-ref.js +57 -0
  306. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  307. package/lib/utils/postgres-wipe.js +9 -2
  308. package/lib/utils/resolve-env-mode.js +47 -0
  309. package/lib/utils/runtime-configuration-download.js +189 -0
  310. package/lib/utils/runtime-configuration-values.js +119 -0
  311. package/lib/utils/schema-loader.js +19 -4
  312. package/lib/utils/secrets-generator.js +31 -25
  313. package/lib/utils/secrets-missing-error.js +164 -0
  314. package/lib/utils/service-url-label.js +81 -0
  315. package/lib/utils/setup-quiet-output.js +25 -0
  316. package/lib/utils/template-helpers.js +15 -0
  317. package/lib/utils/token-manager-client-credentials.js +201 -0
  318. package/lib/utils/token-manager-messages.js +8 -0
  319. package/lib/utils/token-manager-refresh.js +12 -2
  320. package/lib/utils/token-manager.js +86 -105
  321. package/lib/utils/upload-pipeline-ui.js +12 -5
  322. package/lib/utils/upload-sync-options.js +42 -0
  323. package/lib/utils/url-declarative-public-base.js +21 -3
  324. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  325. package/lib/utils/validation-poll-ui.js +8 -0
  326. package/lib/utils/validation-run-stage.js +45 -0
  327. package/lib/utils/worker-sidecars.js +142 -0
  328. package/lib/validation/external-manifest-validator.js +2 -0
  329. package/lib/validation/runtime-configuration-rules.js +98 -0
  330. package/lib/validation/validate-display-dimensions.js +154 -0
  331. package/lib/validation/validate-display-sections.js +276 -0
  332. package/lib/validation/validate-display-steps.js +67 -0
  333. package/lib/validation/validate-display.js +24 -402
  334. package/package.json +1 -1
  335. package/templates/applications/dataplane/application.yaml +7 -1
  336. package/templates/applications/dataplane/env.template +50 -14
  337. package/templates/applications/dataplane/rbac.yaml +108 -1
  338. package/templates/applications/miso-controller/application.yaml +2 -2
  339. package/templates/applications/miso-controller/env.template +53 -122
  340. package/templates/applications/miso-controller/rbac.yaml +28 -13
  341. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  342. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  343. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  344. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  345. package/templates/channels/slack/install.template.md.hbs +49 -0
  346. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  347. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  348. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  349. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  350. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  351. package/templates/external-system/README.md.hbs +74 -17
  352. package/templates/python/docker-compose.hbs +75 -18
  353. package/templates/typescript/docker-compose.hbs +70 -17
  354. package/lib/commands/test-governance-command-action.js +0 -55
  355. package/lib/commands/test-trust-command-action.js +0 -118
@@ -22,6 +22,7 @@ const dockerExec = require('./docker-exec');
22
22
  const logger = require('./logger');
23
23
  const chalk = require('chalk');
24
24
  const { successGlyph } = require('./cli-test-layout-chalk');
25
+ const { isSetupQuietOutput } = require('./setup-quiet-output');
25
26
 
26
27
  /** Roles that must never be dropped (Postgres-managed predefined roles). */
27
28
  const PROTECTED_ROLES = new Set([
@@ -190,15 +191,21 @@ async function wipePostgresData() {
190
191
  }
191
192
 
192
193
  const databases = await listUserDatabases(container, password);
194
+ const quiet = isSetupQuietOutput();
195
+
193
196
  for (const dbName of databases) {
194
197
  await dropDatabase(container, dbName, password);
195
- logger.log(chalk.gray(` ${successGlyph()} Dropped database ${dbName}`));
198
+ if (!quiet) {
199
+ logger.log(chalk.gray(` ${successGlyph()} Dropped database ${dbName}`));
200
+ }
196
201
  }
197
202
 
198
203
  const roles = await listDropableRoles(container, password);
199
204
  for (const role of roles) {
200
205
  await dropRole(container, role, password);
201
- logger.log(chalk.gray(` ${successGlyph()} Dropped role ${role}`));
206
+ if (!quiet) {
207
+ logger.log(chalk.gray(` ${successGlyph()} Dropped role ${role}`));
208
+ }
202
209
  }
203
210
 
204
211
  return { databases, roles };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Chooses `local` vs `docker` for `generateEnvFile` / `resolve` (integration → local, no Docker rewrite).
3
+ *
4
+ * @fileoverview Resolve environment mode for env generation
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+
13
+ /** Platform builder apps that run in Docker and need docker host/port transforms on resolve. */
14
+ const PLATFORM_DOCKER_APP_KEYS = new Set(['keycloak', 'miso-controller', 'dataplane']);
15
+
16
+ /**
17
+ * @param {string} appPath - Resolved app directory from getResolveAppPath
18
+ * @returns {boolean}
19
+ */
20
+ function isIntegrationAppPath(appPath) {
21
+ if (!appPath || typeof appPath !== 'string') return false;
22
+ const normalized = path.resolve(appPath);
23
+ const marker = `${path.sep}integration${path.sep}`;
24
+ return normalized.includes(marker);
25
+ }
26
+
27
+ /**
28
+ * @param {string} appPath - Resolved application directory
29
+ * @param {string} [appName] - App or system key
30
+ * @returns {'local'|'docker'}
31
+ */
32
+ function resolveGenerateEnvEnvironment(appPath, appName) {
33
+ if (isIntegrationAppPath(appPath)) {
34
+ return 'local';
35
+ }
36
+ const key = String(appName || '').trim().toLowerCase();
37
+ if (PLATFORM_DOCKER_APP_KEYS.has(key)) {
38
+ return 'docker';
39
+ }
40
+ return 'docker';
41
+ }
42
+
43
+ module.exports = {
44
+ PLATFORM_DOCKER_APP_KEYS,
45
+ isIntegrationAppPath,
46
+ resolveGenerateEnvEnvironment
47
+ };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Sync runtime configuration_values from dataplane on download (values → env.template; manifest → placeholders).
3
+ *
4
+ * @fileoverview Download-time runtime configuration sync
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const fsPromises = require('fs').promises;
11
+ const path = require('path');
12
+ const yaml = require('js-yaml');
13
+
14
+ const {
15
+ getExternalSystemConfigurationResolved,
16
+ getExternalDataSourceConfigurationResolved
17
+ } = require('../api/runtime-configuration.api');
18
+ const {
19
+ retemplateAllVariableDefinitions,
20
+ retemplateDatasourceConfigurationOverrides
21
+ } = require('./configuration-env-resolver');
22
+ const { mergeEnvTemplateWithExisting } = require('../generator/split');
23
+
24
+ /**
25
+ * @param {Object} response - Dataplane API client response
26
+ * @returns {Record<string, string>}
27
+ */
28
+ function extractResolvedVariableValues(response) {
29
+ if (!response || !response.success) {
30
+ return {};
31
+ }
32
+ const body = response.data && typeof response.data === 'object' ? response.data : {};
33
+ const variables = body.variables && typeof body.variables === 'object' ? body.variables : {};
34
+ const out = {};
35
+ for (const [name, entry] of Object.entries(variables)) {
36
+ if (!entry || typeof entry.value !== 'string') {
37
+ continue;
38
+ }
39
+ const value = entry.value.trim();
40
+ if (!value || value.startsWith('kv://')) {
41
+ continue;
42
+ }
43
+ out[name] = value;
44
+ }
45
+ return out;
46
+ }
47
+
48
+ /**
49
+ * @param {string} dataplaneUrl
50
+ * @param {Object} authConfig
51
+ * @param {string} systemKey
52
+ * @param {Array<{ key?: string }>} dataSources
53
+ * @returns {Promise<Record<string, string>>}
54
+ */
55
+ async function collectResolvedConfigurationEnvUpdates(
56
+ dataplaneUrl,
57
+ authConfig,
58
+ systemKey,
59
+ dataSources
60
+ ) {
61
+ const updates = {};
62
+ const systemResponse = await getExternalSystemConfigurationResolved(
63
+ dataplaneUrl,
64
+ systemKey,
65
+ authConfig
66
+ );
67
+ Object.assign(updates, extractResolvedVariableValues(systemResponse));
68
+
69
+ for (const ds of dataSources || []) {
70
+ if (!ds?.key) {
71
+ continue;
72
+ }
73
+ const dsResponse = await getExternalDataSourceConfigurationResolved(
74
+ dataplaneUrl,
75
+ ds.key,
76
+ authConfig
77
+ );
78
+ Object.assign(updates, extractResolvedVariableValues(dsResponse));
79
+ }
80
+ return updates;
81
+ }
82
+
83
+ /**
84
+ * @param {string} integrationPath
85
+ * @param {Record<string, string>} updates
86
+ * @returns {Promise<boolean>}
87
+ */
88
+ async function mergeConfigurationUpdatesIntoEnvTemplate(integrationPath, updates) {
89
+ const keys = Object.keys(updates || {});
90
+ if (!keys.length) {
91
+ return false;
92
+ }
93
+ const envTemplatePath = path.join(integrationPath, 'env.template');
94
+ const expectedByKey = new Map();
95
+ for (const key of keys) {
96
+ expectedByKey.set(key, `${key}=${updates[key]}`);
97
+ }
98
+ const existingContent = fs.existsSync(envTemplatePath)
99
+ ? await fsPromises.readFile(envTemplatePath, 'utf8')
100
+ : '';
101
+ const merged = mergeEnvTemplateWithExisting(existingContent, expectedByKey);
102
+ await fsPromises.writeFile(envTemplatePath, merged, { mode: 0o644, encoding: 'utf8' });
103
+ return true;
104
+ }
105
+
106
+ /**
107
+ * @param {string} filePath
108
+ * @returns {Promise<boolean>}
109
+ */
110
+ async function retemplateConfigurationFile(filePath, { system = false } = {}) {
111
+ if (!filePath || !fs.existsSync(filePath)) {
112
+ return false;
113
+ }
114
+ const raw = await fsPromises.readFile(filePath, 'utf8');
115
+ const isJson = filePath.endsWith('.json');
116
+ const parsed = isJson ? JSON.parse(raw) : yaml.load(raw);
117
+ if (!parsed || typeof parsed !== 'object') {
118
+ return false;
119
+ }
120
+ if (system) {
121
+ retemplateAllVariableDefinitions(parsed.configuration);
122
+ } else {
123
+ retemplateDatasourceConfigurationOverrides(parsed.configuration);
124
+ }
125
+ const next = isJson ? `${JSON.stringify(parsed, null, 2)}\n` : yaml.dump(parsed, { indent: 2, lineWidth: -1 });
126
+ await fsPromises.writeFile(filePath, next, { mode: 0o644, encoding: 'utf8' });
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * @param {string} integrationPath
132
+ * @param {string} systemKey
133
+ * @returns {Promise<void>}
134
+ */
135
+ async function retemplateDownloadedConfigurationFiles(integrationPath, systemKey) {
136
+ for (const ext of ['yaml', 'json']) {
137
+ const systemPath = path.join(integrationPath, `${systemKey}-system.${ext}`);
138
+ if (fs.existsSync(systemPath)) {
139
+ await retemplateConfigurationFile(systemPath, { system: true });
140
+ }
141
+ }
142
+ const entries = await fsPromises.readdir(integrationPath);
143
+ for (const name of entries) {
144
+ if (!name.includes('-datasource-') || (!name.endsWith('.yaml') && !name.endsWith('.json'))) {
145
+ continue;
146
+ }
147
+ await retemplateConfigurationFile(path.join(integrationPath, name), { system: false });
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Pulls configuration_values from dataplane, merges env.template, and re-templates manifests.
153
+ *
154
+ * @param {Object} params
155
+ * @param {string} params.dataplaneUrl
156
+ * @param {Object} params.authConfig
157
+ * @param {string} params.systemKey
158
+ * @param {string} params.integrationPath
159
+ * @param {Array<{ key?: string }>} params.dataSources
160
+ * @returns {Promise<{ merged: number, names: string[] }>}
161
+ */
162
+ async function syncRuntimeConfigurationFromDataplane({
163
+ dataplaneUrl,
164
+ authConfig,
165
+ systemKey,
166
+ integrationPath,
167
+ dataSources
168
+ }) {
169
+ const updates = await collectResolvedConfigurationEnvUpdates(
170
+ dataplaneUrl,
171
+ authConfig,
172
+ systemKey,
173
+ dataSources
174
+ );
175
+ const names = Object.keys(updates);
176
+ if (names.length) {
177
+ await mergeConfigurationUpdatesIntoEnvTemplate(integrationPath, updates);
178
+ }
179
+ await retemplateDownloadedConfigurationFiles(integrationPath, systemKey);
180
+ return { merged: names.length, names };
181
+ }
182
+
183
+ module.exports = {
184
+ extractResolvedVariableValues,
185
+ collectResolvedConfigurationEnvUpdates,
186
+ mergeConfigurationUpdatesIntoEnvTemplate,
187
+ retemplateDownloadedConfigurationFiles,
188
+ syncRuntimeConfigurationFromDataplane
189
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Push runtime configuration_values to dataplane after publish (never logs values).
3
+ *
4
+ * @fileoverview Post-publish configuration PUT helpers
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const {
10
+ putExternalSystemConfiguration,
11
+ putExternalDataSourceConfiguration
12
+ } = require('../api/runtime-configuration.api');
13
+ const { buildResolvedEnvMapForIntegration } = require('./configuration-env-resolver');
14
+
15
+ /**
16
+ * @param {Array<{ name?: string, location?: string }>|undefined} systemConfiguration
17
+ * @param {Object.<string, string>} envMap
18
+ * @returns {Array<{ name: string, value: string }>}
19
+ */
20
+ function collectSystemVariablePutRequests(systemConfiguration, envMap) {
21
+ if (!Array.isArray(systemConfiguration)) {
22
+ return [];
23
+ }
24
+ const requests = [];
25
+ for (const item of systemConfiguration) {
26
+ if (!item || !item.name) {
27
+ continue;
28
+ }
29
+ const location = String(item.location || 'variable').toLowerCase();
30
+ if (location !== 'variable') {
31
+ continue;
32
+ }
33
+ const name = String(item.name).trim();
34
+ const raw = envMap[name];
35
+ if (raw === undefined || raw === null) {
36
+ continue;
37
+ }
38
+ const value = String(raw).trim();
39
+ if (!value || value.startsWith('kv://') || value.includes('{{')) {
40
+ continue;
41
+ }
42
+ requests.push({ name, value });
43
+ }
44
+ return requests;
45
+ }
46
+
47
+ /**
48
+ * Datasource overrides are name+value only; values may already be resolved in upload payload.
49
+ *
50
+ * @param {Array<{ name?: string, value?: string }>|undefined} datasourceConfiguration
51
+ * @returns {Array<{ name: string, value: string }>}
52
+ */
53
+ function collectDatasourceOverridePutRequests(datasourceConfiguration) {
54
+ if (!Array.isArray(datasourceConfiguration)) {
55
+ return [];
56
+ }
57
+ const requests = [];
58
+ for (const item of datasourceConfiguration) {
59
+ if (!item || !item.name) {
60
+ continue;
61
+ }
62
+ const name = String(item.name).trim();
63
+ const raw = item.value;
64
+ if (typeof raw !== 'string') {
65
+ continue;
66
+ }
67
+ const value = raw.trim();
68
+ if (!value || value.startsWith('kv://') || value.includes('{{')) {
69
+ continue;
70
+ }
71
+ requests.push({ name, value });
72
+ }
73
+ return requests;
74
+ }
75
+
76
+ /**
77
+ * Upserts system-scoped and datasource-scoped configuration values after publish.
78
+ *
79
+ * @param {string} dataplaneUrl
80
+ * @param {Object} authConfig
81
+ * @param {string} systemKey
82
+ * @param {{ application?: { configuration?: unknown[] }, dataSources?: Array<{ key?: string, configuration?: unknown[] }> }} payload
83
+ * @returns {Promise<{ pushed: number, names: string[] }>}
84
+ */
85
+ async function pushRuntimeConfigurationValues(dataplaneUrl, authConfig, systemKey, payload) {
86
+ const { envMap } = await buildResolvedEnvMapForIntegration(systemKey);
87
+ const names = [];
88
+ let pushed = 0;
89
+
90
+ const systemRequests = collectSystemVariablePutRequests(
91
+ payload?.application?.configuration,
92
+ envMap
93
+ );
94
+ for (const body of systemRequests) {
95
+ await putExternalSystemConfiguration(dataplaneUrl, systemKey, authConfig, body);
96
+ names.push(body.name);
97
+ pushed += 1;
98
+ }
99
+
100
+ for (const ds of payload?.dataSources || []) {
101
+ if (!ds?.key) {
102
+ continue;
103
+ }
104
+ const dsRequests = collectDatasourceOverridePutRequests(ds.configuration);
105
+ for (const body of dsRequests) {
106
+ await putExternalDataSourceConfiguration(dataplaneUrl, ds.key, authConfig, body);
107
+ names.push(`${ds.key}:${body.name}`);
108
+ pushed += 1;
109
+ }
110
+ }
111
+
112
+ return { pushed, names };
113
+ }
114
+
115
+ module.exports = {
116
+ collectSystemVariablePutRequests,
117
+ collectDatasourceOverridePutRequests,
118
+ pushRuntimeConfigurationValues
119
+ };
@@ -28,6 +28,23 @@ function resetValidators() {
28
28
  externalDataSourceValidator = null;
29
29
  }
30
30
 
31
+ /**
32
+ * Register AJV schemas required to compile or validate external-datasource manifests.
33
+ * Includes type schemas and external-system (for $defs/subscriptionFilter cross-ref).
34
+ *
35
+ * @function registerExternalDatasourceAjvDependencies
36
+ * @param {import('ajv').default} ajv - AJV instance
37
+ */
38
+ function registerExternalDatasourceAjvDependencies(ajv) {
39
+ ajv.addSchema(require('../schema/type/document-storage.json'));
40
+ ajv.addSchema(require('../schema/type/message-service.json'));
41
+ ajv.addSchema(require('../schema/type/vector-store.json'));
42
+ const externalSystemSchema = require('../schema/external-system.schema.json');
43
+ if (!ajv.getSchema(externalSystemSchema.$id)) {
44
+ ajv.addSchema(externalSystemSchema, externalSystemSchema.$id);
45
+ }
46
+ }
47
+
31
48
  /**
32
49
  * Loads and compiles external-system schema
33
50
  * Caches the compiled validator for performance
@@ -115,10 +132,7 @@ function loadExternalDataSourceSchema() {
115
132
 
116
133
  const ajv = new Ajv({ allErrors: true, strict: false, strictSchema: false });
117
134
  addFormats(ajv);
118
- // external-datasource.schema.json references these by $id (aifabrix://schema/type/*)
119
- ajv.addSchema(require('../schema/type/document-storage.json'));
120
- ajv.addSchema(require('../schema/type/message-service.json'));
121
- ajv.addSchema(require('../schema/type/vector-store.json'));
135
+ registerExternalDatasourceAjvDependencies(ajv);
122
136
  externalDataSourceValidator = ajv.compile(schemaToCompile);
123
137
 
124
138
  return externalDataSourceValidator;
@@ -333,6 +347,7 @@ function detectSchemaType(filePath, content) {
333
347
  module.exports = {
334
348
  loadExternalSystemSchema,
335
349
  loadExternalDataSourceSchema,
350
+ registerExternalDatasourceAjvDependencies,
336
351
  detectSchemaType,
337
352
  detectSchemaTypeFromParsed,
338
353
  resetValidators
@@ -77,42 +77,44 @@ function loadYamlTolerantOfDuplicateKeys(content) {
77
77
  }
78
78
 
79
79
  /**
80
- * Skips commented or empty lines when scanning env.template
81
- * @param {string} line - Single line
80
+ * True when secrets map already has a non-empty value for a kv path (flat or nested).
81
+ * @param {Object} secrets
82
+ * @param {string} pathStr - Path after kv:// (e.g. hubspot-demo/token)
82
83
  * @returns {boolean}
83
84
  */
84
- function isCommentOrEmptyLine(line) {
85
- const t = line.trim();
86
- return t === '' || t.startsWith('#');
85
+ function secretPathExistsInStore(secrets, pathStr) {
86
+ if (!secrets || typeof secrets !== 'object' || !pathStr) {
87
+ return false;
88
+ }
89
+ const direct = secrets[pathStr];
90
+ if (direct !== undefined && direct !== null && String(direct).trim() !== '') {
91
+ return true;
92
+ }
93
+ if (!pathStr.includes('/')) {
94
+ return false;
95
+ }
96
+ let current = secrets;
97
+ for (const segment of pathStr.split('/')) {
98
+ if (current === null || current === undefined || typeof current !== 'object' || !(segment in current)) {
99
+ return false;
100
+ }
101
+ current = current[segment];
102
+ }
103
+ return current !== undefined && current !== null && String(current).trim() !== '';
87
104
  }
88
105
 
89
106
  /**
90
- * Finds missing secret keys from template (skips commented and empty lines)
107
+ * Finds missing secret keys from template (skips commented and empty lines).
108
+ * Uses full kv:// paths (e.g. hubspot-demo/token), aligned with collectMissingSecrets.
91
109
  * @function findMissingSecretKeys
92
110
  * @param {string} envTemplate - Environment template content
93
111
  * @param {Object} existingSecrets - Existing secrets object
94
112
  * @returns {string[]} Array of missing secret keys
95
113
  */
96
114
  function findMissingSecretKeys(envTemplate, existingSecrets) {
97
- const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
98
- const missingKeys = [];
99
- const seenKeys = new Set();
100
- const lines = envTemplate.split('\n');
101
-
102
- for (const line of lines) {
103
- if (isCommentOrEmptyLine(line)) continue;
104
- let match;
105
- kvPattern.lastIndex = 0;
106
- while ((match = kvPattern.exec(line)) !== null) {
107
- const secretKey = match[1];
108
- if (!seenKeys.has(secretKey) && !(secretKey in existingSecrets)) {
109
- missingKeys.push(secretKey);
110
- seenKeys.add(secretKey);
111
- }
112
- }
113
- }
114
-
115
- return missingKeys;
115
+ const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
116
+ const paths = collectUniqueKvPathStrings(envTemplate);
117
+ return paths.filter(pathStr => !secretPathExistsInStore(existingSecrets, pathStr));
116
118
  }
117
119
 
118
120
  /**
@@ -275,6 +277,8 @@ function loadExistingSecrets(resolvedPath) {
275
277
  * @throws {Error} If save fails
276
278
  */
277
279
  function saveSecretsFile(resolvedPath, secrets) {
280
+ const { assertWritableSecretsPathForTests } = require('./aifabrix-test-runtime-guard');
281
+ assertWritableSecretsPathForTests(resolvedPath);
278
282
  const dir = path.dirname(resolvedPath);
279
283
  if (!fs.existsSync(dir)) {
280
284
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
@@ -356,6 +360,8 @@ function appendSecretsToFile(resolvedPath, secrets) {
356
360
  if (!secrets || typeof secrets !== 'object' || Object.keys(secrets).length === 0) {
357
361
  return;
358
362
  }
363
+ const { assertWritableSecretsPathForTests } = require('./aifabrix-test-runtime-guard');
364
+ assertWritableSecretsPathForTests(resolvedPath);
359
365
  const dir = path.dirname(resolvedPath);
360
366
  if (!fs.existsSync(dir)) {
361
367
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Build missing-secrets errors with remediation hints (no secret values).
3
+ *
4
+ * @fileoverview Missing kv:// error message for resolve and env generation
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { formatMissingSecretsFileInfo } = require('./secrets-helpers');
12
+
13
+ /**
14
+ * @param {string} line
15
+ * @returns {boolean}
16
+ */
17
+ function isCommentOrEmptyLine(line) {
18
+ const t = String(line).trim();
19
+ return t === '' || t.startsWith('#');
20
+ }
21
+
22
+ /**
23
+ * @param {string} kvRef - e.g. kv://hubspot-demo/clientSecret
24
+ * @returns {string}
25
+ */
26
+ function kvRefToSecretSetKey(kvRef) {
27
+ const s = String(kvRef || '').trim();
28
+ return s.replace(/^kv:\/\//i, '');
29
+ }
30
+
31
+ /**
32
+ * @param {string} envTemplate
33
+ * @param {string} kvRef
34
+ * @returns {string|null}
35
+ */
36
+ function findActiveEnvLineForKvRef(envTemplate, kvRef) {
37
+ if (!envTemplate || !kvRef) return null;
38
+ const needle = String(kvRef).trim();
39
+ for (const line of envTemplate.split('\n')) {
40
+ if (isCommentOrEmptyLine(line)) continue;
41
+ if (line.includes(needle)) return line.trim();
42
+ }
43
+ return null;
44
+ }
45
+
46
+ const OAUTH_STALE_KV_PATH_RE = /\/(clientId|clientSecret|tokenUrl|authorizationUrl)$/i;
47
+
48
+ /**
49
+ * @param {string[]} missing
50
+ * @returns {boolean}
51
+ */
52
+ function hasStaleOAuthTemplatePaths(missing) {
53
+ return missing.some((ref) => OAUTH_STALE_KV_PATH_RE.test(String(ref)));
54
+ }
55
+
56
+ /**
57
+ * @param {string} key
58
+ * @param {Object} secrets
59
+ * @returns {boolean}
60
+ */
61
+ function isPopulatedSecretKey(key, secrets) {
62
+ const value = secrets[key];
63
+ return value !== undefined && value !== null && String(value).trim() !== '';
64
+ }
65
+
66
+ /**
67
+ * Other populated secret paths that share the same path segment after the namespace.
68
+ *
69
+ * @param {string} pathStr - Secret path without kv:// (e.g. my-app-e2e/clientId)
70
+ * @param {Object} secrets
71
+ * @returns {string[]}
72
+ */
73
+ function findPopulatedAlternateKvPaths(pathStr, secrets) {
74
+ if (!secrets || typeof secrets !== 'object' || !pathStr) {
75
+ return [];
76
+ }
77
+ const slash = pathStr.indexOf('/');
78
+ if (slash <= 0) {
79
+ return [];
80
+ }
81
+ const missingNamespace = pathStr.slice(0, slash);
82
+ const segment = pathStr.slice(slash + 1);
83
+ return Object.keys(secrets).filter((key) => {
84
+ if (typeof key !== 'string' || !key.includes('/')) {
85
+ return false;
86
+ }
87
+ const keySlash = key.indexOf('/');
88
+ const namespace = key.slice(0, keySlash);
89
+ const keySegment = key.slice(keySlash + 1);
90
+ return (
91
+ keySegment === segment &&
92
+ namespace !== missingNamespace &&
93
+ isPopulatedSecretKey(key, secrets)
94
+ );
95
+ });
96
+ }
97
+
98
+ /**
99
+ * @param {string[]} missing - kv:// refs
100
+ * @param {Object} [secrets]
101
+ * @param {string} [appName]
102
+ * @returns {string[]}
103
+ */
104
+ function buildKvNamespaceAlternateHints(missing, secrets, appName) {
105
+ const lines = [];
106
+ const seen = new Set();
107
+ for (const ref of missing) {
108
+ const pathStr = kvRefToSecretSetKey(ref);
109
+ for (const alt of findPopulatedAlternateKvPaths(pathStr, secrets)) {
110
+ const line = `Found kv://${alt} in secrets; env.template references kv://${pathStr}.`;
111
+ if (seen.has(line)) {
112
+ continue;
113
+ }
114
+ seen.add(line);
115
+ lines.push(line);
116
+ }
117
+ }
118
+ if (lines.length === 0) {
119
+ return [];
120
+ }
121
+ const repairTail = appName
122
+ ? `Use aifabrix secret set <key> for each missing path, or aifabrix repair ${appName} to align env.template.`
123
+ : 'Use aifabrix secret set <key> for each missing path, or repair to align env.template.';
124
+ lines.push(repairTail);
125
+ return lines;
126
+ }
127
+
128
+ /**
129
+ * @param {Object} opts
130
+ * @param {string[]} opts.missing
131
+ * @param {Object|string|null} [opts.secretsFilePaths]
132
+ * @param {string} [opts.appName]
133
+ * @param {string} [opts.envTemplatePath]
134
+ * @param {string} [opts.envTemplate]
135
+ * @param {Object} [opts.secrets]
136
+ * @returns {string}
137
+ */
138
+ function buildMissingSecretsErrorMessage(opts) {
139
+ const { missing, secretsFilePaths, appName, envTemplatePath, envTemplate, secrets } = opts;
140
+ const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
141
+ const lines = [`Missing secrets: ${missing.join(', ')}`];
142
+ if (fileInfo) lines.push(fileInfo.trim());
143
+ if (envTemplatePath) lines.push(`Env-template-path: ${envTemplatePath}`);
144
+ for (const ref of missing) {
145
+ const templateLine = findActiveEnvLineForKvRef(envTemplate, ref);
146
+ if (templateLine) lines.push(`Env-template-line: ${ref}|${templateLine}`);
147
+ }
148
+ if (appName) lines.push(`App-name: ${appName}`);
149
+ if (hasStaleOAuthTemplatePaths(missing) && appName) {
150
+ lines.push(`Stale-template-hint: oauth-leftover | repair ${appName}`);
151
+ }
152
+ for (const hint of buildKvNamespaceAlternateHints(missing, secrets, appName)) {
153
+ lines.push(`Alternate-kv-hint: ${hint}`);
154
+ }
155
+ lines.push('Remediation-hint: comment-or-delete-template-line | secret-set');
156
+ return `${lines.join('\n')}\n`;
157
+ }
158
+
159
+ module.exports = {
160
+ kvRefToSecretSetKey,
161
+ findActiveEnvLineForKvRef,
162
+ findPopulatedAlternateKvPaths,
163
+ buildMissingSecretsErrorMessage
164
+ };