@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
@@ -14,9 +14,28 @@ const fs = require('fs').promises;
14
14
  const fsSync = require('fs');
15
15
  const pathsUtil = require('../utils/paths');
16
16
  const adminSecrets = require('../core/admin-secrets');
17
+ const config = require('../core/config');
17
18
  const secretsEnsure = require('../core/secrets-ensure');
18
19
  const secretsEnvWrite = require('../core/secrets-env-write');
19
20
  const { getContainerPort } = require('../utils/port-resolver');
21
+ const { applyAdminSecretsPlatformOverlay } = require('../core/admin-secrets-env-overlay');
22
+
23
+ /**
24
+ * Merge app env with admin-secrets; overlay ensures platform keys come from admin file.
25
+ * @param {Object.<string, string>} merged
26
+ * @param {Object.<string, string>} adminObj
27
+ */
28
+ function mergeRunEnvWithAdminOverlay(merged, adminObj) {
29
+ applyAdminSecretsPlatformOverlay(merged, adminObj);
30
+ }
31
+
32
+ async function applyOnboardingPlatformModeIfNeeded(appName, merged) {
33
+ if (appName !== 'miso-controller') {
34
+ return;
35
+ }
36
+ const mode = await config.getSetupPlatformMode();
37
+ merged.ONBOARDING_FULL_PLATFORM = mode === 'full' ? 'true' : 'false';
38
+ }
20
39
 
21
40
  /**
22
41
  * Backfill only secrets needed for `aifabrix run <app>`: kv:// keys from that app's env.template
@@ -194,31 +213,72 @@ async function readPasswordFromPgpassFile(pgpassPath) {
194
213
  }
195
214
 
196
215
  /**
197
- * Read POSTGRES_PASSWORD from `pgpass` next to the active infra compose (same as `resolveInfraStatePaths()`).
198
- * Does not use `getAifabrixHome()` alone, so a stale pgpass under the wrong base directory (e.g. when
199
- * `aifabrix-home` points at $HOME but compose lives under the config dir) cannot override admin-secrets.
216
+ * Read POSTGRES_PASSWORD from pgpass-style files beside the active infra compose
217
+ * (`resolveInfraStatePaths()`). Tries legacy `pgpass`, then `.pgpass.bootstrap` written at `up-infra`
218
+ * so db-init matches the password Postgres was started with when admin-secrets later drifted.
200
219
  * @param {number|string} developerId - Developer ID
201
220
  * @returns {Promise<string|undefined>} Password or undefined
202
221
  */
203
222
  async function readPostgresPasswordFromPgpass(developerId) {
204
- const { resolveInfraStatePaths } = require('../infrastructure/helpers');
223
+ const { resolveInfraStatePaths, PGPASS_BOOTSTRAP_BASENAME } = require('../infrastructure/helpers');
205
224
  const { infraDir } = resolveInfraStatePaths(developerId);
206
- const pgpassPath = path.join(infraDir, 'pgpass');
207
- if (!fsSync.existsSync(pgpassPath)) {
208
- return undefined;
209
- }
210
- try {
211
- const pwd = await readPasswordFromPgpassFile(pgpassPath);
212
- return pwd;
213
- } catch {
214
- return undefined;
225
+ const candidates = ['pgpass', PGPASS_BOOTSTRAP_BASENAME];
226
+ for (const name of candidates) {
227
+ const pgpassPath = path.join(infraDir, name);
228
+ if (!fsSync.existsSync(pgpassPath)) {
229
+ continue;
230
+ }
231
+ try {
232
+ const pwd = await readPasswordFromPgpassFile(pgpassPath);
233
+ if (pwd !== undefined && pwd !== '') {
234
+ return pwd;
235
+ }
236
+ } catch {
237
+ // try next candidate
238
+ }
215
239
  }
240
+ return undefined;
241
+ }
242
+
243
+ /**
244
+ * @returns {Function}
245
+ */
246
+ function resolveEnsureAdminSecretsFn() {
247
+ const infra = require('../infrastructure');
248
+ return typeof infra.ensureAdminSecrets === 'function'
249
+ ? infra.ensureAdminSecrets
250
+ : require('../infrastructure/helpers').ensureAdminSecrets;
251
+ }
252
+
253
+ /**
254
+ * @param {Object} merged
255
+ * @param {number|string|undefined} developerId
256
+ * @returns {Promise<void>}
257
+ */
258
+ async function applyPostgresPasswordFromPgpass(merged, developerId) {
259
+ if (developerId === undefined) return;
260
+ const pgpassPwd = await readPostgresPasswordFromPgpass(developerId);
261
+ if (pgpassPwd !== undefined) merged.POSTGRES_PASSWORD = pgpassPwd;
262
+ }
263
+
264
+ /**
265
+ * @param {string} runEnvPath
266
+ * @param {string} runEnvAdminPath
267
+ * @param {Object} appOnly
268
+ * @param {Object} dbInitOnly
269
+ * @returns {Promise<void>}
270
+ */
271
+ async function writeRunEnvFiles(runEnvPath, runEnvAdminPath, appOnly, dbInitOnly) {
272
+ const toContent = adminSecrets.envObjectToContent;
273
+ await fs.writeFile(runEnvPath, toContent(appOnly), { mode: 0o600 });
274
+ await fs.writeFile(runEnvAdminPath, toContent(dbInitOnly), { mode: 0o600 });
216
275
  }
217
276
 
218
277
  /**
219
278
  * Build two run env files: .env.run (app-only, no admin secrets) and .env.run.admin (start-only, for db-init).
220
279
  * Admin password is never set in the app container; .env.run.admin is used only for start and then deleted.
221
- * When `pgpass` exists beside the active infra compose, POSTGRES_PASSWORD is taken from it so db-init matches Postgres.
280
+ * When `pgpass` or `.pgpass.bootstrap` exists beside the active infra compose, POSTGRES_PASSWORD is taken
281
+ * from it so db-init matches Postgres.
222
282
  * @async
223
283
  * @param {string} appName - Application name
224
284
  * @param {Object} appConfig - Application configuration
@@ -227,12 +287,8 @@ async function readPostgresPasswordFromPgpass(developerId) {
227
287
  * @returns {Promise<{ runEnvPath: string, runEnvAdminPath: string }>} Paths to .env.run and .env.run.admin
228
288
  */
229
289
  async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId, scopeOpts = null) {
230
- const infra = require('../infrastructure');
231
- const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
232
- ? infra.ensureAdminSecrets
233
- : require('../infrastructure/helpers').ensureAdminSecrets;
234
290
  await ensureRunSecretsForApp(appName);
235
- await ensureAdminSecretsFn();
291
+ await resolveEnsureAdminSecretsFn()();
236
292
  const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
237
293
  const runEnvKey = scopeOpts && scopeOpts.runEnvKey ? String(scopeOpts.runEnvKey).toLowerCase() : 'dev';
238
294
  const appObj = await secretsEnvWrite.resolveAndGetEnvMap(appName, {
@@ -242,22 +298,20 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId
242
298
  runEnvKey
243
299
  });
244
300
  const merged = { ...adminObj, ...appObj };
245
- if (developerId !== undefined) {
246
- const pgpassPwd = await readPostgresPasswordFromPgpass(developerId);
247
- if (pgpassPwd !== undefined) merged.POSTGRES_PASSWORD = pgpassPwd;
248
- }
301
+ mergeRunEnvWithAdminOverlay(merged, adminObj);
302
+ await applyOnboardingPlatformModeIfNeeded(appName, merged);
303
+ await applyPostgresPasswordFromPgpass(merged, developerId);
249
304
  injectDatabaseNamesAndUsers(merged, appConfig, scopeOpts);
250
305
  injectContainerPortForRun(merged, appConfig, appName);
251
306
 
252
307
  const runEnvPath = path.join(devDir, '.env.run');
253
308
  const runEnvAdminPath = path.join(devDir, '.env.run.admin');
254
-
255
- const appOnly = buildAppOnlyEnv(merged);
256
- const dbInitOnly = buildDbInitOnlyEnv(merged);
257
-
258
- await fs.writeFile(runEnvPath, adminSecrets.envObjectToContent(appOnly), { mode: 0o600 });
259
- await fs.writeFile(runEnvAdminPath, adminSecrets.envObjectToContent(dbInitOnly), { mode: 0o600 });
260
-
309
+ await writeRunEnvFiles(
310
+ runEnvPath,
311
+ runEnvAdminPath,
312
+ buildAppOnlyEnv(merged),
313
+ buildDbInitOnlyEnv(merged)
314
+ );
261
315
  return { runEnvPath, runEnvAdminPath };
262
316
  }
263
317
 
@@ -401,6 +401,11 @@ async function resolveDockerBuildArgsForApp(appName) {
401
401
  for (const [k, v] of Object.entries(bashOverlay)) {
402
402
  if (v && buildArgs[k] === undefined) buildArgs[k] = v;
403
403
  }
404
+ // Exported shell tokens win (local dev: export NPM_TOKEN=... before af build / docker build).
405
+ const shellNpm = typeof process.env.NPM_TOKEN === 'string' ? process.env.NPM_TOKEN.trim() : '';
406
+ const shellPypi = typeof process.env.PYPI_TOKEN === 'string' ? process.env.PYPI_TOKEN.trim() : '';
407
+ if (shellNpm) buildArgs.NPM_TOKEN = shellNpm;
408
+ if (shellPypi) buildArgs.PYPI_TOKEN = shellPypi;
404
409
  return buildArgs;
405
410
  }
406
411
 
@@ -211,7 +211,8 @@ async function maybeSyncSystemCertificationFromDataplane(params) {
211
211
  authConfig,
212
212
  datasourceKeys,
213
213
  issuanceFailureHint,
214
- verboseCertHints
214
+ verboseCertHints,
215
+ silent
215
216
  } = params;
216
217
  if (noCertSync === true) return;
217
218
  try {
@@ -224,14 +225,20 @@ async function maybeSyncSystemCertificationFromDataplane(params) {
224
225
  verboseCertHints
225
226
  });
226
227
  if (r.written) {
227
- logger.log(
228
- chalk.gray(`Updated certification block from dataplane${label ? ` (${label})` : ''} in system file.`)
229
- );
228
+ if (silent !== true) {
229
+ logger.log(
230
+ chalk.gray(`Updated certification block from dataplane${label ? ` (${label})` : ''} in system file.`)
231
+ );
232
+ }
230
233
  } else if (r.reason) {
231
- logCertificationSyncNotWritten(r, label, verboseCertHints === true);
234
+ if (silent !== true) {
235
+ logCertificationSyncNotWritten(r, label, verboseCertHints === true);
236
+ }
232
237
  }
233
238
  } catch (e) {
234
- logger.log(chalk.yellow(`⚠ Certification sync skipped: ${e.message}`));
239
+ if (silent !== true) {
240
+ logger.log(chalk.yellow(`⚠ Certification sync skipped: ${e.message}`));
241
+ }
235
242
  }
236
243
  }
237
244
 
package/lib/cli/index.js CHANGED
@@ -27,6 +27,8 @@ const { setupProtectionCommands } = require('../commands/protection');
27
27
  const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
28
28
  const { setupIntegrationClientCommands } = require('./setup-integration-client');
29
29
  const { setupPlatformCommands } = require('./setup-platform');
30
+ const { setupCertificationCommands } = require('./setup-certification-commands');
31
+ const { setupGovernanceCommands } = require('../commands/governance');
30
32
 
31
33
  /**
32
34
  * Sets up all CLI commands on the Commander program instance
@@ -36,6 +38,8 @@ function setupCommands(program) {
36
38
  setupInfraCommands(program);
37
39
  setupAuthCommands(program);
38
40
  setupAppCommands(program);
41
+ setupCertificationCommands(program);
42
+ setupGovernanceCommands(program);
39
43
  setupEnvironmentCommands(program);
40
44
  setupAppManagementCommands(program);
41
45
  setupDatasourceCommands(program);
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Guided platform install authentication (device login URL + Dataplane recovery).
3
+ *
4
+ * @fileoverview Auth helpers for infra-guided up-platform / setup
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const config = require('../core/config');
14
+ const { resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
15
+ const { withMutedLogger } = require('../utils/with-muted-logger');
16
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
17
+ const { isAuthenticationError } = require('../utils/cli-utils');
18
+ const { resolveControllerUrl, isPlatformAuthValidForController } = require('../utils/controller-url');
19
+
20
+ /**
21
+ * @param {Object} authOpts
22
+ * @returns {Promise<string>}
23
+ */
24
+ async function resolveGuidedAuthControllerUrl(authOpts = {}) {
25
+ return (
26
+ authOpts.platformControllerUrl ||
27
+ (await config.getPlatformControllerUrl()) ||
28
+ (await resolvePlatformControllerUrl())
29
+ );
30
+ }
31
+
32
+ /**
33
+ * @async
34
+ * @param {Function} handleLoginFn
35
+ * @param {string} controllerUrl
36
+ * @param {{ skipDataplaneVersionRefresh?: boolean }} [opts]
37
+ * @returns {Promise<void>}
38
+ */
39
+ async function runGuidedDeviceLogin(handleLoginFn, controllerUrl, opts = {}) {
40
+ await handleLoginFn({
41
+ method: 'device',
42
+ controller: controllerUrl,
43
+ environment: 'dev',
44
+ compact: true,
45
+ skipDataplaneVersionRefresh: opts.skipDataplaneVersionRefresh === true,
46
+ exitOnFailure: false
47
+ });
48
+ }
49
+
50
+ /**
51
+ * @async
52
+ * @param {Function} handleLoginFn
53
+ * @param {Object} authOpts
54
+ * @param {{ startSpinner: Function, stopSpinnerSuccess: Function }} spinners
55
+ * @returns {Promise<void>}
56
+ */
57
+ async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
58
+ const spin = spinners.startSpinner('Authenticating...');
59
+ const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
60
+ const skipLogin =
61
+ authOpts.skipLoginIfAuthenticated === true ||
62
+ (await isPlatformAuthValidForController(controllerUrl));
63
+ try {
64
+ if (skipLogin) {
65
+ spinners.stopSpinnerSuccess(spin, 'Already authenticated');
66
+ return;
67
+ }
68
+ await runGuidedDeviceLogin(handleLoginFn, controllerUrl, { skipDataplaneVersionRefresh: true });
69
+ } catch (authErr) {
70
+ if (spin) spin.fail('Authentication required');
71
+ logger.error(formatBlockingError('Authentication required. Complete sign-in in your browser.'));
72
+ await runGuidedDeviceLogin(handleLoginFn, controllerUrl, { skipDataplaneVersionRefresh: true });
73
+ }
74
+ spinners.stopSpinnerSuccess(spin, 'Authenticated');
75
+ }
76
+
77
+ /**
78
+ * @async
79
+ * @param {Object} options
80
+ * @param {Function} handleUpDataplane
81
+ * @param {Function} handleLoginFn
82
+ * @param {string} controllerUrl
83
+ * @returns {Promise<void>}
84
+ */
85
+ async function runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, handleLoginFn, controllerUrl) {
86
+ try {
87
+ await withMutedLogger(() =>
88
+ handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
89
+ );
90
+ } catch (error) {
91
+ if (!isAuthenticationError(error)) {
92
+ throw error;
93
+ }
94
+ const loginUrl = error.controllerUrl || controllerUrl || (await resolveControllerUrl());
95
+ logger.log('');
96
+ logger.log(chalk.yellow('Device login required to register and deploy Dataplane.'));
97
+ await runGuidedDeviceLogin(handleLoginFn, loginUrl, { skipDataplaneVersionRefresh: false });
98
+ await withMutedLogger(() =>
99
+ handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
100
+ );
101
+ }
102
+ }
103
+
104
+ module.exports = {
105
+ resolveGuidedAuthControllerUrl,
106
+ runGuidedDeviceLogin,
107
+ runGuidedAuthStep,
108
+ runGuidedDataplaneWithAuthRecovery
109
+ };
@@ -10,16 +10,19 @@ const config = require('../core/config');
10
10
  const devConfig = require('../utils/dev-config');
11
11
  const pathsUtil = require('../utils/paths');
12
12
  const { loadConfigFile } = require('../utils/config-format');
13
- const { resolveControllerUrl } = require('../utils/controller-url');
13
+ const { computeAppBaseUrl, resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
14
14
  const { withMutedLogger } = require('../utils/with-muted-logger');
15
- const { formatBlockingError, formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
16
- const { computePublicUrlBaseString } = require('../utils/url-declarative-public-base');
17
- const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('../utils/health-check-url');
15
+ const { formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
18
16
  const ora = require('ora');
19
17
  const infra = require('../infrastructure');
20
18
  const { execWithDockerEnv } = require('../utils/docker-exec');
21
19
  const { getLogLevel, passesLevelFilter } = require('../commands/app-logs');
22
20
  const { handleLogin } = require('../commands/login');
21
+ const {
22
+ resolveGuidedAuthControllerUrl,
23
+ runGuidedAuthStep,
24
+ runGuidedDataplaneWithAuthRecovery
25
+ } = require('./infra-guided-auth');
23
26
  const healthCheck = require('../utils/health-check');
24
27
  const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
25
28
  const { assertDevInfraUp } = require('../commands/dev-infra-gate');
@@ -126,44 +129,17 @@ function resolveInfraHost(remoteServer) {
126
129
  }
127
130
  }
128
131
 
129
- async function computeAppBaseUrl(appName) {
130
- const builderPath = pathsUtil.getBuilderPath(appName);
131
- const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
132
- const variables = loadConfigFile(configPath) || {};
133
- const basePort = Number(variables.port || 3000);
134
- const developerIdRaw = await config.getDeveloperId();
135
- const developerIdNum = typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
136
- const userCfg = await config.getConfig();
137
- const remoteServer = await config.getRemoteServer();
138
- const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
139
-
140
- const fd = variables.frontDoorRouting || null;
141
- const frontDoorEnabled = Boolean(fd && fd.enabled === true);
142
- const traefikOn = Boolean(userCfg && userCfg.traefik);
143
- const pathActive = Boolean(traefikOn && frontDoorEnabled);
144
-
145
- const publicBase = computePublicUrlBaseString({
146
- traefik: traefikOn,
147
- pathActive,
148
- hostTemplate: fd ? fd.host : null,
149
- tls: fd ? fd.tls : true,
150
- developerIdRaw,
151
- remoteServer,
152
- // These guided bootstrap commands start services via Docker Compose, so we want the
153
- // docker-published host port (no workstation +10 offset).
154
- profile: 'docker',
155
- listenPort: basePort,
156
- developerIdNum,
157
- infraTlsEnabled
158
- });
159
-
160
- if (pathActive) {
161
- const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
162
- return joinUrlPath(publicBase, mount);
132
+ /**
133
+ * Prefer stored platform controller URL, then compute from routing rules.
134
+ * @async
135
+ * @returns {Promise<string>}
136
+ */
137
+ async function resolvePlatformControllerUrlForDisplay() {
138
+ const stored = await config.getPlatformControllerUrl();
139
+ if (stored) {
140
+ return stored;
163
141
  }
164
-
165
- // No Traefik front-door routing: publicBase is localhost + published port unless Traefik/proxy opts into remote.
166
- return String(publicBase).replace(/\/+$/, '');
142
+ return resolvePlatformControllerUrl();
167
143
  }
168
144
 
169
145
  function logSection(label, value) {
@@ -186,7 +162,7 @@ function logFooterEnd() {
186
162
 
187
163
  async function logPlatformReadyFooter() {
188
164
  const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
189
- resolveControllerUrl(),
165
+ resolvePlatformControllerUrlForDisplay(),
190
166
  computeAppBaseUrl('dataplane'),
191
167
  computeAppBaseUrl('keycloak')
192
168
  ]);
@@ -206,7 +182,7 @@ async function logPlatformReadyFooter() {
206
182
 
207
183
  async function logMisoReadyFooter() {
208
184
  const [controllerUrl, keycloakUrl] = await Promise.all([
209
- resolveControllerUrl(),
185
+ resolvePlatformControllerUrlForDisplay(),
210
186
  computeAppBaseUrl('keycloak')
211
187
  ]);
212
188
  logFooterStart('Miso Ready');
@@ -280,6 +256,8 @@ async function runGuidedUpMiso(options, handleUpMiso) {
280
256
  logger.log(title('AI Fabrix Platform Setup'));
281
257
  logger.log(SEPARATOR);
282
258
  logger.log('');
259
+ logPlatformSetupDurationHint('platform');
260
+ logger.log('');
283
261
  const kcSpin = startSpinner('Starting Keycloak...');
284
262
  await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
285
263
  stopSpinnerSuccess(kcSpin, 'Keycloak ready');
@@ -291,13 +269,11 @@ async function runGuidedUpMiso(options, handleUpMiso) {
291
269
  await validateAppErrorLogs(['miso-controller']);
292
270
  }
293
271
 
294
- async function runGuidedUpDataplane(options, handleUpDataplane) {
295
- logger.log('');
296
- logger.log(title('AI Fabrix Dataplane Setup'));
297
- logger.log(SEPARATOR);
298
- logger.log('');
299
- // Infra must be checked before auth: device login talks to the controller, which needs
300
- // Postgres/Redis when the controller runs locally — a down infra looks like "network error".
272
+ /**
273
+ * Infra must be checked before auth: device login talks to the controller, which needs
274
+ * Postgres/Redis when the controller runs locally — a down infra looks like "network error".
275
+ */
276
+ async function assertDevInfraForGuidedDataplane() {
301
277
  const infraSpin = startSpinner('Checking infrastructure...');
302
278
  try {
303
279
  await assertDevInfraUp({ quietSuccess: true });
@@ -307,11 +283,21 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
307
283
  if (infraSpin) infraSpin.stop();
308
284
  throw infraErr;
309
285
  }
286
+ }
287
+
288
+ async function runGuidedUpDataplane(options, handleUpDataplane) {
289
+ logger.log('');
290
+ logger.log(title('AI Fabrix Dataplane Setup'));
291
+ logger.log(SEPARATOR);
292
+ logger.log('');
293
+ logPlatformSetupDurationHint('dataplane');
294
+ logger.log('');
295
+ await assertDevInfraForGuidedDataplane();
310
296
  // Avoid an outer long-running spinner here: up-dataplane has multiple internal
311
297
  // phases and may print some output. An outer spinner causes "spinner over spinner"
312
298
  // rendering artifacts. Instead, do the same single auth step as up-platform, then
313
299
  // show one spinner for the dataplane install/run phase.
314
- await runGuidedAuthStep(handleLogin);
300
+ await runGuidedAuthStep(handleLogin, {}, { startSpinner, stopSpinnerSuccess });
315
301
 
316
302
  logger.log('');
317
303
  emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
@@ -331,6 +317,8 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
331
317
  * @param {string[]} cleanedApps
332
318
  */
333
319
  function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
320
+ const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
321
+ if (isSetupQuietOutput()) return;
334
322
  const check = successGlyph();
335
323
  logger.log(chalk.bold('Clean installation files'));
336
324
  logger.log(
@@ -350,40 +338,31 @@ function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
350
338
  logger.log('');
351
339
  }
352
340
 
341
+ /**
342
+ * Yellow notice before long-running platform/dataplane install steps (setup and guided up-*).
343
+ * @param {'platform'|'dataplane'} [scope]
344
+ */
345
+ function logPlatformSetupDurationHint(scope = 'platform') {
346
+ const line =
347
+ scope === 'dataplane'
348
+ ? 'Installation may take a few minutes. Authenticating and starting Dataplane — please wait.'
349
+ : 'Installation may take a few minutes. Keycloak, Miso Controller, and Dataplane are starting — please wait.';
350
+ logger.log(chalk.yellow(`⚠ ${line}`));
351
+ }
352
+
353
353
  function logGuidedPlatformSetupHeader() {
354
354
  logger.log('');
355
355
  logger.log(title('AI Fabrix Platform Setup'));
356
356
  logger.log(SEPARATOR);
357
- logger.log(chalk.gray('This may take a few minutes...'));
358
357
  logger.log('');
359
- }
360
-
361
- async function runGuidedAuthStep(handleLogin) {
362
- const spin = startSpinner('Authenticating...');
363
- try {
364
- const controllerUrl = await resolveControllerUrl();
365
- await handleLogin({
366
- method: 'device',
367
- controller: controllerUrl,
368
- environment: 'dev',
369
- compact: true,
370
- // Dataplane is not up yet during guided platform/dataplane install; refresh after it starts.
371
- skipDataplaneVersionRefresh: true
372
- });
373
- } catch (authErr) {
374
- if (spin) spin.fail('Authentication failed');
375
- logger.error(formatBlockingError('Authentication failed'));
376
- logger.log(chalk.gray('\nRun:\n af login\n\nThen retry:\n af up-platform\n'));
377
- throw authErr;
378
- }
379
- // Compact device-login prints "Authentication successful"; close the step spinner cleanly.
380
- stopSpinnerSuccess(spin, 'Authenticated');
358
+ logPlatformSetupDurationHint('platform');
359
+ logger.log('');
381
360
  }
382
361
 
383
362
  async function refreshDataplaneVersionAfterPlatformInstall() {
384
363
  try {
385
364
  const { tryRefreshDataplaneVersionAfterLogin } = require('../commands/auth-status-dataplane-version');
386
- const controllerUrl = await resolveControllerUrl();
365
+ const controllerUrl = await resolvePlatformControllerUrlForDisplay();
387
366
  await tryRefreshDataplaneVersionAfterLogin(controllerUrl, {});
388
367
  } catch {
389
368
  // best-effort only
@@ -403,13 +382,25 @@ async function runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso) {
403
382
  logger.log('');
404
383
  }
405
384
 
406
- async function runGuidedDataplaneInstallPhase(options, handleUpDataplane) {
385
+ async function runGuidedDataplaneInstallPhase(options, handleUpDataplane, handleLoginFn, controllerUrl) {
407
386
  logger.log('');
408
387
  emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
409
388
  const dpSpin = startSpinner('Starting Dataplane...');
410
- await withMutedLogger(() =>
411
- handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
412
- );
389
+ const platformMode = await config.getSetupPlatformMode();
390
+ if (platformMode === 'full') {
391
+ // Full platform: three dataplane containers + per-env registrations.
392
+ const envKeys = ['pro', 'dev', 'tst'];
393
+ for (const envKey of envKeys) {
394
+ await runGuidedDataplaneWithAuthRecovery(
395
+ { ...options, envKey, runEnvKey: envKey, allowNonDev: true },
396
+ handleUpDataplane,
397
+ handleLoginFn,
398
+ controllerUrl
399
+ );
400
+ }
401
+ } else {
402
+ await runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, handleLoginFn, controllerUrl);
403
+ }
413
404
  stopSpinnerSuccess(dpSpin, 'Dataplane ready');
414
405
  await refreshDataplaneVersionAfterPlatformInstall();
415
406
  }
@@ -420,10 +411,22 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
420
411
  logUpPlatformForceCleanSummary(forceCleanSummary.forceSummary, forceCleanSummary.cleanedApps);
421
412
  }
422
413
 
414
+ const authOpts = {
415
+ platformControllerUrl: options && options.platformControllerUrl,
416
+ skipLoginIfAuthenticated: options && options.skipLoginIfAuthenticated === true
417
+ };
418
+
423
419
  await withMutedLogger(() => prepareUrlsLocalRegistryForUpPlatform());
424
420
  await runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso);
425
- await runGuidedAuthStep(handleLogin);
426
- await runGuidedDataplaneInstallPhase(options, handleUpDataplane);
421
+ if (options && options.requireKeycloakRealm === true) {
422
+ const { waitForKeycloakRealmReady } = require('../utils/keycloak-realm-ready');
423
+ const realmSpin = startSpinner('Waiting for Keycloak realm (aifabrix)...');
424
+ await waitForKeycloakRealmReady({ timeoutSeconds: 180 });
425
+ stopSpinnerSuccess(realmSpin, 'Keycloak realm ready');
426
+ }
427
+ await runGuidedAuthStep(handleLogin, authOpts, { startSpinner, stopSpinnerSuccess });
428
+ const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
429
+ await runGuidedDataplaneInstallPhase(options, handleUpDataplane, handleLogin, controllerUrl);
427
430
  await validateAppErrorLogs(['miso-controller', 'dataplane']);
428
431
  await logPlatformReadyFooter();
429
432
  }
@@ -75,41 +75,10 @@ Notes:
75
75
  - Prefer identity apply over SQL in tests/fixtures/protection/optional-dev-identity-dataplane.sql.
76
76
  `;
77
77
 
78
- const TEST_GOVERNANCE_HELP_AFTER = `
79
- Examples:
80
- # External system — ABAC visibility acceptance (governance scenario pack)
81
- $ aifabrix test-governance test-protection
82
- $ aifabrix test-governance test-protection --pack scenarios/test-protection-v1.yaml
83
- $ aifabrix test-governance test-protection -v --no-sync
84
- $ aifabrix test-governance test-protection --scenario country-isolation-maria-fi --json
85
-
86
- Notes:
87
- - Separate from test-e2e (vendor connectivity). Does not call external vendor APIs.
88
- - Load baseline fixtures first (datasource load, protection upload) — not auto-run by this command.
89
- - Default pack: integration/<systemKey>/scenarios/default.yaml or <systemKey>-v1.yaml
90
- - Requires governance:evaluate on the operator token (see permissions.md).
91
- `;
92
-
93
- const TEST_TRUST_HELP_AFTER = `
94
- Examples:
95
- # External system — semantic trust for every datasource (dataplane 404.5)
96
- $ aifabrix test-trust hubspot
97
- $ aifabrix test-trust hubspot -v --revalidate
98
- $ aifabrix test-trust hubspot --no-sync
99
- $ aifabrix test-trust hubspot --summary --timeout 120000
100
-
101
- Notes:
102
- - For one datasource: aifabrix datasource test-trust <datasourceKey>
103
- - Does not call external APIs (use test-e2e for live proof).
104
- - Local integration files are published before run unless --no-sync.
105
- `;
106
-
107
78
  module.exports = {
108
79
  TEST_HELP_AFTER,
109
80
  TEST_INTEGRATION_HELP_AFTER,
110
81
  TEST_E2E_HELP_AFTER,
111
- TEST_TRUST_HELP_AFTER,
112
- TEST_GOVERNANCE_HELP_AFTER,
113
82
  IDENTITY_HELP_AFTER
114
83
  };
115
84