@aifabrix/builder 2.45.5 → 2.46.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 (231) 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/README.md +43 -17
  5. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  6. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  7. package/jest.config.default.js +2 -1
  8. package/jest.config.isolated.js +2 -1
  9. package/jest.global-live-fabrix-hooks.js +36 -0
  10. package/jest.isolated-projects.js +130 -0
  11. package/jest.projects.js +41 -136
  12. package/lib/api/api-response-errors.js +124 -0
  13. package/lib/api/governance-scenario-pack.api.js +159 -0
  14. package/lib/api/index.js +43 -3
  15. package/lib/api/lifecycle.api.js +77 -0
  16. package/lib/api/pipeline.api.js +3 -1
  17. package/lib/api/records-bulk.api.js +14 -1
  18. package/lib/api/validation-runner.js +3 -1
  19. package/lib/app/deploy-config.js +4 -1
  20. package/lib/app/down.js +20 -1
  21. package/lib/app/push.js +4 -5
  22. package/lib/app/register.js +7 -4
  23. package/lib/app/run-env-compose.js +84 -30
  24. package/lib/build/index.js +5 -0
  25. package/lib/certification/sync-system-certification.js +13 -6
  26. package/lib/cli/index.js +4 -0
  27. package/lib/cli/infra-guided-auth.js +109 -0
  28. package/lib/cli/infra-guided.js +84 -81
  29. package/lib/cli/setup-app.help.js +0 -31
  30. package/lib/cli/setup-app.js +28 -2
  31. package/lib/cli/setup-app.test-commands.js +5 -57
  32. package/lib/cli/setup-auth.js +25 -0
  33. package/lib/cli/setup-certification-commands.js +147 -0
  34. package/lib/cli/setup-certification.help.js +141 -0
  35. package/lib/cli/setup-environment.js +19 -2
  36. package/lib/cli/setup-external-system-help.js +62 -0
  37. package/lib/cli/setup-external-system.js +25 -3
  38. package/lib/cli/setup-infra.js +9 -1
  39. package/lib/cli/setup-platform.js +6 -1
  40. package/lib/cli/setup-secrets.js +33 -1
  41. package/lib/cli/setup-utility-help-after.js +22 -2
  42. package/lib/cli/setup-utility-repair.js +198 -0
  43. package/lib/cli/setup-utility-resolve.js +40 -4
  44. package/lib/cli/setup-utility-validate.js +197 -0
  45. package/lib/cli/setup-utility.js +4 -239
  46. package/lib/commands/auth-status-client-tokens.js +214 -0
  47. package/lib/commands/auth-status-dataplane-version.js +58 -20
  48. package/lib/commands/auth-status.js +41 -47
  49. package/lib/commands/datasource-test-trust-cli.js +27 -23
  50. package/lib/commands/datasource-unified-test-cli.js +6 -3
  51. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  52. package/lib/commands/datasource.js +7 -7
  53. package/lib/commands/dev-show-display.js +8 -1
  54. package/lib/commands/governance-cli-shared.js +94 -0
  55. package/lib/commands/governance-command-action.js +132 -0
  56. package/lib/commands/governance-download-external.js +34 -0
  57. package/lib/commands/governance-generate-external.js +71 -0
  58. package/lib/commands/governance-upload-external.js +36 -0
  59. package/lib/commands/governance-verify-external.js +64 -0
  60. package/lib/commands/governance.js +144 -0
  61. package/lib/commands/lifecycle-command-action.js +123 -0
  62. package/lib/commands/lifecycle-external.js +268 -0
  63. package/lib/commands/login.js +11 -2
  64. package/lib/commands/repair-auth-apply.js +127 -0
  65. package/lib/commands/repair-changed-files.js +41 -0
  66. package/lib/commands/repair-datasource-keys.js +4 -1
  67. package/lib/commands/repair-datasource-run.js +59 -0
  68. package/lib/commands/repair-datasource-test-payload.js +259 -0
  69. package/lib/commands/repair-datasource.js +1 -51
  70. package/lib/commands/repair-env-template.js +64 -13
  71. package/lib/commands/repair-external-run.js +216 -0
  72. package/lib/commands/repair-openapi-sync.js +23 -0
  73. package/lib/commands/repair-persist.js +4 -0
  74. package/lib/commands/repair-rbac.js +52 -1
  75. package/lib/commands/repair-steps.js +145 -0
  76. package/lib/commands/repair.js +29 -296
  77. package/lib/commands/secrets-get.js +184 -0
  78. package/lib/commands/secrets-remove-all.js +3 -1
  79. package/lib/commands/setup-image-refresh.js +146 -0
  80. package/lib/commands/setup-modes.js +158 -162
  81. package/lib/commands/setup-platform-auth.js +76 -0
  82. package/lib/commands/setup-prompts-admin.js +244 -0
  83. package/lib/commands/setup-prompts-platform-mode.js +67 -0
  84. package/lib/commands/setup-prompts-validators.js +36 -0
  85. package/lib/commands/setup-prompts.js +24 -76
  86. package/lib/commands/setup-run-context.js +45 -0
  87. package/lib/commands/setup-spinners.js +39 -0
  88. package/lib/commands/setup.js +27 -5
  89. package/lib/commands/teardown.js +39 -0
  90. package/lib/commands/test-e2e-external.js +24 -8
  91. package/lib/commands/test-governance-external.js +3 -24
  92. package/lib/commands/test-trust-external.js +20 -21
  93. package/lib/commands/up-common.js +25 -13
  94. package/lib/commands/up-dataplane.js +50 -13
  95. package/lib/commands/upload-publish-context.js +75 -0
  96. package/lib/commands/upload-sidecar-followups.js +89 -0
  97. package/lib/commands/upload.js +90 -108
  98. package/lib/commands/verify-governance-combined-external.js +61 -0
  99. package/lib/commands/verify-governance-command-action.js +64 -0
  100. package/lib/commands/verify-governance-db.js +102 -0
  101. package/lib/commands/verify-governance-external.js +77 -0
  102. package/lib/commands/verify-governance-result-builders.js +71 -0
  103. package/lib/commands/verify-operations-command-action.js +68 -0
  104. package/lib/commands/verify-operations-external.js +268 -0
  105. package/lib/commands/verify-operations-persist.js +75 -0
  106. package/lib/commands/verify-operations-step-plan.js +68 -0
  107. package/lib/commands/verify-operations-steps.js +111 -0
  108. package/lib/commands/verify-trust-command-action.js +70 -0
  109. package/lib/commands/verify-trust-external.js +67 -0
  110. package/lib/commands/wizard-config-normalizer.js +43 -23
  111. package/lib/commands/wizard-core.js +2 -0
  112. package/lib/core/admin-password-generator.js +38 -0
  113. package/lib/core/admin-secrets-bundle.js +111 -0
  114. package/lib/core/admin-secrets-env-overlay.js +62 -0
  115. package/lib/core/admin-secrets.js +32 -4
  116. package/lib/core/config-attach-extensions.js +3 -0
  117. package/lib/core/config-platform-controller.js +44 -0
  118. package/lib/core/config-setup-installation-profile.js +42 -0
  119. package/lib/core/config-setup-platform-mode.js +46 -0
  120. package/lib/core/config.js +46 -36
  121. package/lib/core/secrets-admin-env.js +6 -2
  122. package/lib/core/secrets-ensure.js +4 -0
  123. package/lib/core/secrets-env-content.js +54 -48
  124. package/lib/core/secrets-env-write.js +10 -1
  125. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  126. package/lib/core/setup-installation-profile.js +43 -0
  127. package/lib/core/setup-platform-mode.js +55 -0
  128. package/lib/datasource/agent-trust-debug-log.js +1 -1
  129. package/lib/datasource/agent-trust-run.js +5 -3
  130. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  131. package/lib/datasource/audit-evidence-extract.js +7 -4
  132. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  133. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  134. package/lib/datasource/datasource-load-export-context.js +3 -0
  135. package/lib/datasource/log-cleaner.js +2 -2
  136. package/lib/datasource/log-viewer.js +8 -8
  137. package/lib/datasource/test-e2e.js +4 -1
  138. package/lib/datasource/unified-validation-run-body.js +3 -1
  139. package/lib/datasource/unified-validation-run.js +5 -2
  140. package/lib/deployment/environment-poll-status.js +138 -0
  141. package/lib/deployment/environment.js +42 -34
  142. package/lib/external-system/download.js +21 -20
  143. package/lib/external-system/generator.js +5 -1
  144. package/lib/external-system/integration-auth-context.js +45 -0
  145. package/lib/external-system/test-auth.js +12 -5
  146. package/lib/external-system/test.js +6 -6
  147. package/lib/generator/external-controller-manifest.js +15 -0
  148. package/lib/governance/governance-cli-errors.js +96 -0
  149. package/lib/governance/governance-download-display.js +38 -0
  150. package/lib/governance/governance-run-display.js +215 -25
  151. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  152. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  153. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  154. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  155. package/lib/infrastructure/helpers.js +23 -129
  156. package/lib/infrastructure/index.js +5 -6
  157. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  158. package/lib/lifecycle/certification-progress.js +231 -0
  159. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  160. package/lib/lifecycle/exit-codes.js +24 -0
  161. package/lib/lifecycle/json-envelope.js +25 -0
  162. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  163. package/lib/lifecycle/logger-quiet.js +29 -0
  164. package/lib/lifecycle/open-api-published.js +59 -0
  165. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  166. package/lib/lifecycle/product-model.js +196 -0
  167. package/lib/lifecycle/report-display.js +471 -0
  168. package/lib/lifecycle/scenario-upload.js +191 -0
  169. package/lib/lifecycle/verify-step-progress.js +87 -0
  170. package/lib/parameters/infra-kv-discovery.js +27 -2
  171. package/lib/protection/auth-context.js +10 -4
  172. package/lib/schema/application-schema.json +1 -1
  173. package/lib/schema/datasource-test-run.schema.json +89 -6
  174. package/lib/schema/external-datasource.schema.json +2636 -2622
  175. package/lib/schema/external-system.schema.json +1089 -761
  176. package/lib/schema/infra.parameter.yaml +45 -0
  177. package/lib/schema/type/credential-field-registry.json +103 -97
  178. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  179. package/lib/utils/api.js +55 -31
  180. package/lib/utils/app-relative-path-env.js +112 -0
  181. package/lib/utils/cli-secrets-error-format.js +83 -21
  182. package/lib/utils/cli-utils.js +16 -4
  183. package/lib/utils/compose-generate-docker-compose.js +4 -0
  184. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  185. package/lib/utils/config-secrets-path.js +46 -0
  186. package/lib/utils/controller-url.js +88 -1
  187. package/lib/utils/credential-secrets-env.js +137 -9
  188. package/lib/utils/dataplane-resolver.js +129 -2
  189. package/lib/utils/declarative-url-ports.js +3 -3
  190. package/lib/utils/deployment-auth-mode.js +41 -0
  191. package/lib/utils/developer-volume-id.js +33 -0
  192. package/lib/utils/env-copy.js +14 -9
  193. package/lib/utils/env-kv-orphan-lines.js +43 -0
  194. package/lib/utils/error-formatters/network-errors.js +13 -4
  195. package/lib/utils/external-env-template.js +1 -0
  196. package/lib/utils/external-system-readiness-core.js +10 -0
  197. package/lib/utils/external-system-readiness-display.js +80 -23
  198. package/lib/utils/health-check.js +4 -1
  199. package/lib/utils/help-builder.js +11 -8
  200. package/lib/utils/keycloak-realm-ready.js +101 -0
  201. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  202. package/lib/utils/manifest-source-emit.js +2 -0
  203. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  204. package/lib/utils/platform-controller-url.js +156 -0
  205. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  206. package/lib/utils/postgres-wipe.js +9 -2
  207. package/lib/utils/resolve-env-mode.js +47 -0
  208. package/lib/utils/secrets-generator.js +31 -25
  209. package/lib/utils/secrets-missing-error.js +164 -0
  210. package/lib/utils/service-url-label.js +81 -0
  211. package/lib/utils/setup-quiet-output.js +25 -0
  212. package/lib/utils/template-helpers.js +15 -0
  213. package/lib/utils/token-manager-client-credentials.js +201 -0
  214. package/lib/utils/token-manager-messages.js +8 -0
  215. package/lib/utils/token-manager-refresh.js +12 -2
  216. package/lib/utils/token-manager.js +86 -105
  217. package/lib/utils/upload-pipeline-ui.js +12 -5
  218. package/lib/utils/upload-sync-options.js +42 -0
  219. package/lib/utils/url-declarative-public-base.js +21 -3
  220. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  221. package/lib/utils/validation-poll-ui.js +8 -0
  222. package/lib/utils/validation-run-stage.js +45 -0
  223. package/package.json +1 -1
  224. package/templates/applications/dataplane/application.yaml +2 -1
  225. package/templates/applications/dataplane/env.template +24 -1
  226. package/templates/applications/miso-controller/env.template +16 -0
  227. package/templates/external-system/README.md.hbs +74 -17
  228. package/templates/python/docker-compose.hbs +19 -18
  229. package/templates/typescript/docker-compose.hbs +18 -17
  230. package/lib/commands/test-governance-command-action.js +0 -55
  231. package/lib/commands/test-trust-command-action.js +0 -118
@@ -9,14 +9,24 @@
9
9
  */
10
10
 
11
11
  const fs = require('fs').promises;
12
- const path = require('path');
13
12
  const yaml = require('js-yaml');
14
- const os = require('os');
15
13
  const { encryptToken, decryptToken, isTokenEncrypted } = require('../utils/token-encryption');
16
14
  const { ensureSecureFilePermissions, ensureSecureDirPermissions } = require('../utils/secure-file-permissions');
17
15
  const { getRuntimeConfigDir, getRuntimeConfigFile } = require('./config-runtime-paths');
18
16
  const { getAdminEmailFromConfig, setAdminEmailInConfig } = require('./config-admin-email');
17
+ const {
18
+ getSetupInstallationProfileFromConfig,
19
+ setSetupInstallationProfileInConfig
20
+ } = require('./config-setup-installation-profile');
21
+ const {
22
+ getSetupPlatformModeFromConfig,
23
+ setSetupPlatformModeInConfig
24
+ } = require('./config-setup-platform-mode');
19
25
  const { getRegisteredControllerUrlsWithLoader } = require('./config-registered-controller-urls');
26
+ const {
27
+ getPlatformControllerUrlFromConfig,
28
+ setPlatformControllerUrlInConfig
29
+ } = require('./config-platform-controller');
20
30
  const { normalizeControllerUrl, validateAndNormalizeDeveloperId } = require('./config-normalize');
21
31
  // Avoid importing paths.js here to prevent circular dependency; use shared runtime config dir helper.
22
32
  // Config location: AIFABRIX_CONFIG dirname → AIFABRIX_HOME (with ~/.aifabrix fallback when config lives there) → ~/.aifabrix
@@ -34,6 +44,9 @@ function applyConfigDefaults(config) {
34
44
  if (typeof config.environment === 'undefined') {
35
45
  config.environment = 'dev';
36
46
  }
47
+ if (typeof config.setupPlatformMode === 'undefined') {
48
+ config.setupPlatformMode = 'single';
49
+ }
37
50
  // Ensure environments object exists
38
51
  if (typeof config.environments !== 'object' || config.environments === null) {
39
52
  config.environments = {};
@@ -59,6 +72,7 @@ function getDefaultConfig() {
59
72
  return {
60
73
  'developer-id': '0',
61
74
  environment: 'dev',
75
+ setupPlatformMode: 'single',
62
76
  controller: undefined,
63
77
  environments: {},
64
78
  device: {},
@@ -230,6 +244,12 @@ async function setAdminEmail(email) {
230
244
  return setAdminEmailInConfig(getConfig, saveConfig, email);
231
245
  }
232
246
 
247
+ /** @returns {Promise<'dev'|'pro'>} */
248
+ const getSetupInstallationProfile = () => getSetupInstallationProfileFromConfig(getConfig);
249
+ /** @param {unknown} profile @returns {Promise<'dev'|'pro'>} */
250
+ const setSetupInstallationProfile = (profile) =>
251
+ setSetupInstallationProfileInConfig(getConfig, saveConfig, profile);
252
+
233
253
  /**
234
254
  * Whether Traefik is enabled (`traefik: true` in config; infra compose includes the proxy).
235
255
  * @returns {Promise<boolean>}
@@ -259,6 +279,21 @@ async function setCurrentEnvironment(environment) {
259
279
  await saveConfig(config);
260
280
  }
261
281
 
282
+ /**
283
+ * @returns {Promise<'single'|'full'>}
284
+ */
285
+ async function getSetupPlatformMode() {
286
+ return getSetupPlatformModeFromConfig(getConfig);
287
+ }
288
+
289
+ /**
290
+ * @param {unknown} mode
291
+ * @returns {Promise<'single'|'full'>}
292
+ */
293
+ async function setSetupPlatformMode(mode) {
294
+ return setSetupPlatformModeInConfig(getConfig, saveConfig, mode);
295
+ }
296
+
262
297
  /**
263
298
  * Set controller URL in configuration
264
299
  * @async
@@ -392,38 +427,11 @@ async function setSecretsEncryptionKey(key) {
392
427
  */
393
428
  async function ensureSecretsEncryptionKey() {
394
429
  const { ensureSecretsEncryptionKey: run } = require('./ensure-encryption-key');
430
+ const { createSecretsPathFunctions } = require('../utils/config-secrets-path');
431
+ const { getSecretsPath } = createSecretsPathFunctions(getConfig, saveConfig);
395
432
  await run({ getSecretsEncryptionKey, setSecretsEncryptionKey, getSecretsPath });
396
433
  }
397
434
 
398
- /**
399
- * Expand leading ~ to home directory so config paths like ~/.aifabrix/secrets.local.yaml resolve correctly.
400
- * @param {string} filePath - Path that may start with ~ or ~/
401
- * @returns {string} Path with ~ expanded, or unchanged if no leading ~
402
- */
403
- function expandTilde(filePath) {
404
- if (!filePath || typeof filePath !== 'string') return filePath;
405
- if (filePath === '~') return os.homedir();
406
- if (filePath.startsWith('~/') || filePath.startsWith('~' + path.sep)) {
407
- return path.join(os.homedir(), filePath.slice(2));
408
- }
409
- return filePath;
410
- }
411
-
412
- async function getSecretsPath() {
413
- const config = await getConfig();
414
- const raw = config['aifabrix-secrets'] || config['secrets-path'] || null;
415
- return raw ? expandTilde(raw) : null;
416
- }
417
-
418
- async function setSecretsPath(secretsPath) {
419
- if (typeof secretsPath !== 'string') {
420
- throw new Error('Secrets path is required and must be a string');
421
- }
422
- const config = await getConfig();
423
- config['aifabrix-secrets'] = secretsPath.trim() || undefined;
424
- await saveConfig(config);
425
- }
426
-
427
435
  // Create exports object
428
436
  const exportsObj = {
429
437
  getConfig,
@@ -436,6 +444,10 @@ const exportsObj = {
436
444
  getTlsEnabled,
437
445
  getAdminEmail,
438
446
  setAdminEmail,
447
+ getSetupInstallationProfile,
448
+ setSetupInstallationProfile,
449
+ getSetupPlatformMode,
450
+ setSetupPlatformMode,
439
451
  getTraefikEnabled,
440
452
  setCurrentEnvironment,
441
453
  resolveEnvironment,
@@ -446,11 +458,11 @@ const exportsObj = {
446
458
  getSecretsEncryptionKey,
447
459
  setSecretsEncryptionKey,
448
460
  ensureSecretsEncryptionKey,
449
- getSecretsPath,
450
- setSecretsPath,
451
461
  normalizeControllerUrl,
452
462
  setControllerUrl,
453
463
  getControllerUrl,
464
+ getPlatformControllerUrl: () => getPlatformControllerUrlFromConfig(getConfig),
465
+ setPlatformControllerUrl: (url) => setPlatformControllerUrlInConfig(getConfig, saveConfig, normalizeControllerUrl, url),
454
466
  getRegisteredControllerUrls,
455
467
  get CONFIG_DIR() {
456
468
  return getRuntimeConfigDir();
@@ -460,9 +472,7 @@ const exportsObj = {
460
472
  }
461
473
  };
462
474
 
463
- // Add developerId as a property getter for direct access
464
- // After getConfig() or getDeveloperId() is called, config.developerId will be available
465
- // Developer ID: 0 = default infra, > 0 = developer-specific
475
+ // Developer ID: 0 = default infra, > 0 = developer-specific (getter after getConfig loads cache)
466
476
  Object.defineProperty(exportsObj, 'developerId', {
467
477
  get() {
468
478
  return cachedDeveloperId !== null ? cachedDeveloperId : '0'; // Default to "0" if not loaded yet
@@ -20,6 +20,7 @@ const {
20
20
  const { createDefaultSecrets } = require('../utils/secrets-generator');
21
21
  const pathsUtil = require('../utils/paths');
22
22
  const { loadSecrets } = require('./secrets-load');
23
+ const { formatAdminEnvLine } = require('./admin-secrets');
23
24
 
24
25
  /**
25
26
  * Writes admin env key-value pairs to content; encrypts values when encryption key is set.
@@ -34,7 +35,7 @@ async function formatAdminSecretsContent(adminObj) {
34
35
  for (const [k, v] of Object.entries(adminObj)) {
35
36
  const value = (v === null || v === undefined) ? '' : String(v).replace(/\n/g, ' ').trim();
36
37
  const valueToWrite = encryptionKey ? encryptSecret(value, encryptionKey) : value;
37
- lines.push(`${k}=${valueToWrite}`);
38
+ lines.push(formatAdminEnvLine(k, valueToWrite));
38
39
  }
39
40
  return lines.join('\n');
40
41
  }
@@ -76,7 +77,10 @@ function buildLocalAdminSecretsObject(secrets, infraDefaults) {
76
77
  PGADMIN_DEFAULT_PASSWORD: postgresPassword,
77
78
  REDIS_HOST: 'local:redis:6379:0:',
78
79
  REDIS_COMMANDER_USER: 'admin',
79
- REDIS_COMMANDER_PASSWORD: postgresPassword
80
+ REDIS_COMMANDER_PASSWORD: postgresPassword,
81
+ KEYCLOAK_ADMIN_USERNAME: 'admin',
82
+ KEYCLOAK_ADMIN_PASSWORD: postgresPassword,
83
+ PLATFORM_ADMIN_PASSWORD: postgresPassword
80
84
  };
81
85
  }
82
86
 
@@ -204,6 +204,8 @@ function summarizeSecretsStoreForLog(target) {
204
204
  */
205
205
  function logNewSecretValuesWritten(added, target) {
206
206
  if (!Array.isArray(added) || added.length === 0) return;
207
+ const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
208
+ if (isSetupQuietOutput()) return;
207
209
  const where = summarizeSecretsStoreForLog(target);
208
210
  logger.log(
209
211
  formatSuccessLine(
@@ -352,6 +354,8 @@ async function ensureSecretsFromEnvTemplate(envTemplatePathOrContent, options =
352
354
  * @returns {Promise<void>}
353
355
  */
354
356
  async function writeSecretToStoreFile(filePath, key, strValue) {
357
+ const { assertWritableSecretsPathForTests } = require('../utils/aifabrix-test-runtime-guard');
358
+ assertWritableSecretsPathForTests(filePath);
355
359
  const existing = loadExistingSecrets(filePath);
356
360
  const encryptionKey = await config.getSecretsEncryptionKey();
357
361
  let valueToWrite = strValue;
@@ -15,7 +15,6 @@ const config = require('./config');
15
15
  const {
16
16
  interpolateEnvVars,
17
17
  collectMissingSecrets,
18
- formatMissingSecretsFileInfo,
19
18
  replaceKvInContent,
20
19
  loadEnvTemplate,
21
20
  adjustLocalEnvPortsInContent,
@@ -43,6 +42,8 @@ const {
43
42
  rewriteDockerManifestPublicPortEnvLine
44
43
  } = require('../utils/docker-manifest-public-port');
45
44
  const { loadSecrets } = require('./secrets-load');
45
+ const { buildMissingSecretsErrorMessage } = require('../utils/secrets-missing-error');
46
+ const { commentOrphanKvEnvLines } = require('../utils/env-kv-orphan-lines');
46
47
 
47
48
  /**
48
49
  * Resolves kv:// references in environment template
@@ -54,10 +55,24 @@ const { loadSecrets } = require('./secrets-load');
54
55
  * @param {string} [environment='local'] - Environment context (docker/local)
55
56
  * @param {Object|string|null} [secretsFilePaths] - Paths object with userPath and buildPath, or string path (for backward compatibility)
56
57
  * @param {string} [appName] - Application name (optional, for error messages)
58
+ * @param {Object|null} [resolveOpts] - Scoped kv (`envKey`, `effective`) and/or `envTemplatePath` for error hints
57
59
  * @returns {Promise<string>} Resolved environment content
58
60
  * @throws {Error} If kv:// reference cannot be resolved
59
61
  */
60
- async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null, scopedKv = null) {
62
+ async function resolveKvReferences(
63
+ envTemplate,
64
+ secrets,
65
+ environment = 'local',
66
+ secretsFilePaths = null,
67
+ appName = null,
68
+ resolveOpts = null
69
+ ) {
70
+ const envTemplatePath =
71
+ resolveOpts && typeof resolveOpts === 'object' ? resolveOpts.envTemplatePath || null : null;
72
+ const scopedKv =
73
+ resolveOpts && typeof resolveOpts === 'object' && (resolveOpts.envKey !== undefined || resolveOpts.effective !== undefined)
74
+ ? { envKey: resolveOpts.envKey, effective: resolveOpts.effective }
75
+ : null;
61
76
  const os = require('os');
62
77
 
63
78
  let developerId = null;
@@ -77,9 +92,16 @@ async function resolveKvReferences(envTemplate, secrets, environment = 'local',
77
92
  const resolved = interpolateEnvVars(envTemplate, envVars);
78
93
  const missing = collectMissingSecrets(resolved, secrets, scopedKv);
79
94
  if (missing.length > 0) {
80
- const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
81
- const resolveCommand = appName ? `aifabrix resolve ${appName}` : 'aifabrix resolve <app-name>';
82
- throw new Error(`Missing secrets: ${missing.join(', ')}${fileInfo}\n\nRun "${resolveCommand}" to generate missing secrets.`);
95
+ throw new Error(
96
+ buildMissingSecretsErrorMessage({
97
+ missing,
98
+ secrets,
99
+ secretsFilePaths,
100
+ appName,
101
+ envTemplatePath,
102
+ envTemplate
103
+ })
104
+ );
83
105
  }
84
106
  return replaceKvInContent(resolved, secrets, envVars, scopedKv);
85
107
  }
@@ -178,7 +200,11 @@ async function generateEnvContent(appName, secretsPath, environment = 'local', f
178
200
  const secrets = await loadSecrets(secretsPath, appName);
179
201
  const { runEnvKey, effective } = await buildScopedKvContext(appPath, options);
180
202
  const scopedKv = { envKey: runEnvKey, effective };
181
- let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, scopedKv);
203
+ let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, {
204
+ envKey: runEnvKey,
205
+ effective,
206
+ envTemplatePath: templatePath
207
+ });
182
208
  if (!secretsPath) {
183
209
  await materializeResolvedKvSecretsToUserLocal(template, secrets, scopedKv, options);
184
210
  }
@@ -227,42 +253,6 @@ function parseEnvContentToMap(content) {
227
253
  return map;
228
254
  }
229
255
 
230
- /**
231
- * Merges new .env content with existing .env: newly resolved content wins for keys it
232
- * defines (so project secrets take effect when re-running). Keys only in existing .env
233
- * are appended so manual additions are kept.
234
- *
235
- * @param {string} newContent - Newly generated .env content (from template + loadSecrets)
236
- * @param {Object.<string, string>} existingMap - Existing key-to-value map from current .env
237
- * @returns {string} Merged content: new values for keys in newContent, plus extra existing keys
238
- */
239
- function mergeEnvContentPreservingExisting(newContent, existingMap) {
240
- const lines = newContent.split(/\r?\n/);
241
- const newKeys = new Set();
242
- const out = [];
243
- for (const line of lines) {
244
- const trimmed = line.trim();
245
- if (!trimmed || trimmed.startsWith('#')) {
246
- out.push(line);
247
- continue;
248
- }
249
- const eq = trimmed.indexOf('=');
250
- if (eq > 0) {
251
- const key = trimmed.substring(0, eq).trim();
252
- newKeys.add(key);
253
- }
254
- out.push(line);
255
- }
256
- if (existingMap && Object.keys(existingMap).length > 0) {
257
- for (const key of Object.keys(existingMap)) {
258
- if (!newKeys.has(key)) {
259
- out.push(`${key}=${existingMap[key]}`);
260
- }
261
- }
262
- }
263
- return out.join('\n');
264
- }
265
-
266
256
  /**
267
257
  * Merges a key-value map into existing .env file content, preserving comments and blank lines.
268
258
  * For each KEY=value line in existing content, replaces value with newMap[KEY] when the key exists
@@ -320,15 +310,27 @@ function mergeEnvMapIntoContent(existingContent, newMap, options = {}) {
320
310
 
321
311
  /**
322
312
  * Resolves content to write for .env: merges with existing file when present.
313
+ * Uses {@link mergeEnvMapIntoContent} so new template keys are appended and comments in the
314
+ * existing file are preserved (unlike a full replace).
315
+ *
323
316
  * @param {string} resolved - Newly generated content
324
317
  * @param {string} pathToPreserve - Path to existing .env to merge from (or null)
318
+ * @param {Object} [options]
319
+ * @param {boolean} [options.freshEnv=false] - When true, write full resolved content (no merge)
325
320
  * @returns {string} Content to write
326
321
  */
327
- function resolveEnvContentToWrite(resolved, pathToPreserve) {
328
- if (!pathToPreserve || !fs.existsSync(pathToPreserve)) return resolved;
322
+ function resolveEnvContentToWrite(resolved, pathToPreserve, options = {}) {
323
+ if (options.freshEnv === true || !pathToPreserve || !fs.existsSync(pathToPreserve)) {
324
+ return resolved;
325
+ }
329
326
  const existingContent = fs.readFileSync(pathToPreserve, 'utf8');
330
- const existingMap = parseEnvContentToMap(existingContent);
331
- return mergeEnvContentPreservingExisting(resolved, existingMap);
327
+ const resolvedMap = parseEnvContentToMap(resolved);
328
+ let merged = mergeEnvMapIntoContent(existingContent, resolvedMap, { appendMissingFromNewMap: true });
329
+ if (options.pruneOrphanKv === true) {
330
+ const { content: pruned } = commentOrphanKvEnvLines(merged, new Set(Object.keys(resolvedMap)));
331
+ merged = pruned;
332
+ }
333
+ return merged;
332
334
  }
333
335
 
334
336
  /**
@@ -381,14 +383,18 @@ function resolveEnvContentToWrite(resolved, pathToPreserve) {
381
383
  async function writeResolvedEnv({ appName, envPath, resolved, variablesPath, secretsPath, opts }) {
382
384
  const preservePath = opts.preserveFromPath !== undefined && opts.preserveFromPath !== null ? opts.preserveFromPath : null;
383
385
  const pathToPreserve = preservePath !== null ? preservePath : envPath;
384
- const toWrite = resolveEnvContentToWrite(resolved, pathToPreserve);
386
+ const toWrite = resolveEnvContentToWrite(resolved, pathToPreserve, {
387
+ freshEnv: opts.freshEnv === true,
388
+ pruneOrphanKv: opts.envOnly === true && opts.freshEnv !== true
389
+ });
385
390
  fs.writeFileSync(envPath, toWrite, { mode: 0o600 });
386
391
 
387
392
  if (!opts.skipOutputPath) {
388
393
  const { processEnvVariables } = require('../utils/env-copy');
389
394
  await processEnvVariables(envPath, variablesPath, appName, secretsPath, {
390
395
  preferLocalEnvOutputPath: opts.preferLocalEnvOutputPath === true,
391
- appPath: opts.appPath || null
396
+ appPath: opts.appPath || null,
397
+ freshEnv: opts.freshEnv === true
392
398
  });
393
399
  }
394
400
  return envPath;
@@ -207,7 +207,16 @@ async function resolveAndGetEnvMap(appName, options = {}) {
207
207
  let content = await secrets.generateEnvContent(appName, secretsPath, environment, force);
208
208
  content = await injectRegistryTokens(content, secretsPath, appName);
209
209
  content = await injectBashPrefixedExportLines(content, secretsPath, appName);
210
- return parseEnvContentToMap(content);
210
+ const map = parseEnvContentToMap(content);
211
+ try {
212
+ const adminSecrets = require('./admin-secrets');
213
+ const { applyAdminSecretsPlatformOverlay } = require('./admin-secrets-env-overlay');
214
+ const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
215
+ applyAdminSecretsPlatformOverlay(map, adminObj);
216
+ } catch {
217
+ // admin-secrets.env optional until up-infra/setup
218
+ }
219
+ return map;
211
220
  }
212
221
 
213
222
  module.exports = { resolveAndWriteEnvFile, resolveAndGetEnvMap };
@@ -43,6 +43,8 @@ async function syncLiteralKvSecretsFromCliOverrides(
43
43
  const val = ipc.generateValueFromCatalogEntry(key, entry, crypto, placeholderContext);
44
44
  await setSecretInStore(key, val);
45
45
  }
46
+ const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
47
+ if (isSetupQuietOutput()) return;
46
48
  logger.log(
47
49
  `Updated ${keys.length} secret(s) in store (catalog literals using {{${placeholder}}}).`
48
50
  );
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Setup installation profile identifiers (`dev` / `pro`).
3
+ *
4
+ * @fileoverview Normalization for setup wizard profile (local install, not login environment)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** @enum {string} */
12
+ const INSTALLATION_PROFILE = Object.freeze({
13
+ DEV: 'dev',
14
+ PRO: 'pro'
15
+ });
16
+
17
+ /**
18
+ * @param {unknown} value
19
+ * @returns {'dev'|'pro'}
20
+ */
21
+ function normalizeInstallationProfile(value) {
22
+ const raw = String(value ?? '')
23
+ .trim()
24
+ .toLowerCase();
25
+ if (raw === INSTALLATION_PROFILE.PRO || raw === 'production') {
26
+ return INSTALLATION_PROFILE.PRO;
27
+ }
28
+ return INSTALLATION_PROFILE.DEV;
29
+ }
30
+
31
+ /**
32
+ * @param {unknown} value
33
+ * @returns {boolean}
34
+ */
35
+ function isProInstallationProfile(value) {
36
+ return normalizeInstallationProfile(value) === INSTALLATION_PROFILE.PRO;
37
+ }
38
+
39
+ module.exports = {
40
+ INSTALLATION_PROFILE,
41
+ normalizeInstallationProfile,
42
+ isProInstallationProfile
43
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Setup platform topology mode (`single` | `full`).
3
+ *
4
+ * This is distinct from `setupInstallationProfile` (dev/pro password bundle) and from
5
+ * the login/run environment (`dev` | `tst` | `pro`).
6
+ *
7
+ * @fileoverview Setup platform mode normalization and defaults
8
+ * @author AI Fabrix Team
9
+ * @version 1.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ /** @enum {string} */
15
+ const SETUP_PLATFORM_MODE = Object.freeze({
16
+ SINGLE: 'single',
17
+ FULL: 'full'
18
+ });
19
+
20
+ /**
21
+ * @param {unknown} value
22
+ * @returns {'single'|'full'}
23
+ */
24
+ function normalizeSetupPlatformMode(value) {
25
+ const raw = String(value ?? '')
26
+ .trim()
27
+ .toLowerCase();
28
+ if (raw === SETUP_PLATFORM_MODE.FULL) return SETUP_PLATFORM_MODE.FULL;
29
+ if (raw === 'all' || raw === 'full-platform' || raw === 'platform-full') return SETUP_PLATFORM_MODE.FULL;
30
+ return SETUP_PLATFORM_MODE.SINGLE;
31
+ }
32
+
33
+ /**
34
+ * Derived config flags for a chosen platform mode.
35
+ *
36
+ * - single: direct ports, no front door ingress
37
+ * - full: enable Traefik ingress + env-scoped resources
38
+ *
39
+ * @param {'single'|'full'} mode
40
+ * @returns {{ traefik: boolean, useEnvironmentScopedResources: boolean }}
41
+ */
42
+ function flagsForSetupPlatformMode(mode) {
43
+ const m = normalizeSetupPlatformMode(mode);
44
+ return {
45
+ traefik: m === SETUP_PLATFORM_MODE.FULL,
46
+ useEnvironmentScopedResources: m === SETUP_PLATFORM_MODE.FULL
47
+ };
48
+ }
49
+
50
+ module.exports = {
51
+ SETUP_PLATFORM_MODE,
52
+ normalizeSetupPlatformMode,
53
+ flagsForSetupPlatformMode
54
+ };
55
+
@@ -21,7 +21,7 @@ async function writeTrustDebugLog(appKey, datasourceKey, payload) {
21
21
  const logPath = await writeTestLog(
22
22
  appKey,
23
23
  payload,
24
- `test-trust-${datasourceKey}`,
24
+ `verify-trust-${datasourceKey}`,
25
25
  path.dirname(getIntegrationPath(appKey))
26
26
  );
27
27
  return logPath;
@@ -14,14 +14,15 @@ const { setupIntegrationTestAuth } = require('../external-system/test-auth');
14
14
  const { resolveAppKeyForDatasource } = require('./resolve-app');
15
15
  const { getSystemKeyFromAppKey } = require('./integration-context');
16
16
  const { requireBearerForDataplanePipeline } = require('../utils/token-manager');
17
+ const { buildMinimalUploadSyncOptions } = require('../utils/upload-sync-options');
17
18
  const { fetchTrustRunFromDataplane } = require('./agent-trust-fetch');
18
19
 
19
- async function maybeSyncBeforeTrust({ noSync, authConfig, systemKey }) {
20
+ async function maybeSyncBeforeTrust({ noSync, authConfig, systemKey, options = {} }) {
20
21
  if (noSync === true) return;
21
22
  requireBearerForDataplanePipeline(authConfig);
22
23
  logger.log(chalk.cyan('Syncing local config to dataplane…'));
23
24
  const { uploadExternalSystem } = require('../commands/upload');
24
- await uploadExternalSystem(systemKey, { minimal: true });
25
+ await uploadExternalSystem(systemKey, buildMinimalUploadSyncOptions(options));
25
26
  logger.log(formatSuccessLine('Sync complete'));
26
27
  }
27
28
 
@@ -67,7 +68,8 @@ async function runDatasourceAgentTrust(datasourceKey, options = {}) {
67
68
  await maybeSyncBeforeTrust({
68
69
  noSync: options.noSync === true,
69
70
  authConfig,
70
- systemKey
71
+ systemKey,
72
+ options
71
73
  });
72
74
 
73
75
  try {
@@ -6,6 +6,8 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ const { parseCapacityDetailKey } = require('../utils/load-cip-capacity-display-config');
10
+
9
11
  /**
10
12
  * @param {Array<*>} ids
11
13
  * @returns {string[]}
@@ -67,32 +69,42 @@ function executionIdsFromDebugEnvelope(envelope) {
67
69
  }
68
70
 
69
71
  /**
70
- * CIP operation names from capacity step (insert, update, list, …).
71
72
  * @param {Object|null|undefined} envelope
72
- * @returns {string[]}
73
+ * @returns {Object|null}
73
74
  */
74
- function cipOperationsFromDebugEnvelope(envelope) {
75
- const dbg = envelope && envelope.debug;
76
- const asyncDbg = dbg && dbg.e2eAsyncDebug;
77
- if (!asyncDbg || !Array.isArray(asyncDbg.stepDebug)) return [];
78
- const capStep = asyncDbg.stepDebug.find(s => s && s.name === 'capacity');
79
- if (!capStep || !capStep.evidence) return [];
75
+ function getCapacityStepFromEnvelope(envelope) {
76
+ const asyncDbg = envelope && envelope.debug && envelope.debug.e2eAsyncDebug;
77
+ if (!asyncDbg || !Array.isArray(asyncDbg.stepDebug)) return null;
78
+ return asyncDbg.stepDebug.find(s => s && s.name === 'capacity') || null;
79
+ }
80
+
81
+ /**
82
+ * Operation names from CIP capacity evidence keys (`capacity:<op>#<index>`), without product aliases.
83
+ * @param {*} details
84
+ * @returns {Set<string>}
85
+ */
86
+ function collectCipOpsFromCapabilityDetails(details) {
80
87
  const ops = new Set();
81
- const details = capStep.evidence.datasources;
82
- if (!Array.isArray(details)) return [];
88
+ if (!Array.isArray(details)) return ops;
83
89
  for (const ds of details) {
84
90
  if (!ds || !Array.isArray(ds.capabilityDetails)) continue;
85
91
  for (const item of ds.capabilityDetails) {
86
- const key = item && item.key ? String(item.key) : '';
87
- const m = key.match(/^capacity:([a-zA-Z]+)/);
88
- if (m && m[1]) {
89
- let op = m[1] === 'updateBasic' ? 'updatebasic' : m[1].toLowerCase();
90
- if (op === 'create') op = 'insert';
91
- ops.add(op);
92
- }
92
+ const parsed = parseCapacityDetailKey(item && item.key);
93
+ if (parsed && parsed.op) ops.add(parsed.op);
93
94
  }
94
95
  }
95
- return [...ops];
96
+ return ops;
97
+ }
98
+
99
+ /**
100
+ * CIP operation names from capacity step (insert, update, list, …).
101
+ * @param {Object|null|undefined} envelope
102
+ * @returns {string[]}
103
+ */
104
+ function cipOperationsFromDebugEnvelope(envelope) {
105
+ const capStep = getCapacityStepFromEnvelope(envelope);
106
+ if (!capStep || !capStep.evidence) return [];
107
+ return [...collectCipOpsFromCapabilityDetails(capStep.evidence.datasources)];
96
108
  }
97
109
 
98
110
  module.exports = {
@@ -16,6 +16,7 @@ const {
16
16
  cipOperationsFromDebugEnvelope,
17
17
  dedupeIds: dedupeIdsFromDebug
18
18
  } = require('./audit-evidence-extract-debug');
19
+ const loadCipCapacityDisplayConfig = require('../utils/load-cip-capacity-display-config');
19
20
 
20
21
  /** @param {Array<*>} ids @returns {string[]} */
21
22
  function dedupeIds(ids) {
@@ -88,14 +89,16 @@ function correlationIdFromEnvelope(envelope) {
88
89
  function expectedOperationsFromEnvelope(envelope) {
89
90
  const fromCapacity = cipOperationsFromDebugEnvelope(envelope);
90
91
  if (fromCapacity.length) return fromCapacity;
92
+ const { standardOrder } = loadCipCapacityDisplayConfig.getCipCapacityDisplayConfig();
93
+ const allowed = new Set(
94
+ (standardOrder || []).map(op => String(op).trim().toLowerCase()).filter(Boolean)
95
+ );
91
96
  const ops = new Set();
92
97
  const caps = envelope && Array.isArray(envelope.capabilities) ? envelope.capabilities : [];
93
98
  for (const cap of caps) {
94
99
  if (!cap || cap.status === 'skipped' || !cap.key) continue;
95
- const key = String(cap.key).trim();
96
- if (['insert', 'update', 'get', 'delete', 'list', 'create'].includes(key)) {
97
- ops.add(key === 'create' ? 'insert' : key);
98
- }
100
+ const key = String(cap.key).trim().toLowerCase();
101
+ if (allowed.has(key)) ops.add(key);
99
102
  }
100
103
  const integration = envelope && envelope.integration;
101
104
  if (integration && integration.sync && integration.sync.status === 'ok') {