@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
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Reuse dataplane auth resolved during sync/upload across verify and test commands.
3
+ *
4
+ * @fileoverview Integration auth context helpers
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { setupIntegrationTestAuth } = require('./test-auth');
12
+ const { getConfig } = require('../core/config');
13
+
14
+ /**
15
+ * @param {Object} options
16
+ * @returns {Object}
17
+ */
18
+ function integrationAuthOptions(options) {
19
+ const base = {
20
+ environment: options.env,
21
+ silentResolve: options.silentResolve === true
22
+ };
23
+ if (options.authConfig && options.dataplaneUrl) {
24
+ return { ...base, authConfig: options.authConfig, dataplaneUrl: options.dataplaneUrl };
25
+ }
26
+ return base;
27
+ }
28
+
29
+ /**
30
+ * @param {string} systemKey
31
+ * @param {Object} options
32
+ * @returns {Promise<{ authConfig: Object, dataplaneUrl: string }>}
33
+ */
34
+ async function resolveIntegrationAuth(systemKey, options) {
35
+ if (options.authConfig && options.dataplaneUrl) {
36
+ return { authConfig: options.authConfig, dataplaneUrl: options.dataplaneUrl };
37
+ }
38
+ const configObj = await getConfig();
39
+ return setupIntegrationTestAuth(systemKey, integrationAuthOptions(options), configObj);
40
+ }
41
+
42
+ module.exports = {
43
+ integrationAuthOptions,
44
+ resolveIntegrationAuth
45
+ };
@@ -26,9 +26,14 @@ const { resolveControllerUrl } = require('../utils/controller-url');
26
26
  * @throws {Error} If authentication fails
27
27
  */
28
28
  async function setupIntegrationTestAuth(appName, options, _config) {
29
+ const opts = options && typeof options === 'object' ? options : {};
30
+ if (opts.authConfig && opts.dataplaneUrl) {
31
+ return { authConfig: opts.authConfig, dataplaneUrl: opts.dataplaneUrl };
32
+ }
33
+
29
34
  const { resolveEnvironment } = require('../core/config');
30
- const environment = (options && options.environment && typeof options.environment === 'string' && options.environment.trim())
31
- ? options.environment.trim()
35
+ const environment = (opts.environment && typeof opts.environment === 'string' && opts.environment.trim())
36
+ ? opts.environment.trim()
32
37
  : await resolveEnvironment();
33
38
  const controllerUrl = await resolveControllerUrl();
34
39
  const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
@@ -38,10 +43,12 @@ async function setupIntegrationTestAuth(appName, options, _config) {
38
43
  }
39
44
 
40
45
  let dataplaneUrl;
41
- if (options && options.dataplane && typeof options.dataplane === 'string' && options.dataplane.trim()) {
42
- dataplaneUrl = options.dataplane.trim();
46
+ if (opts.dataplane && typeof opts.dataplane === 'string' && opts.dataplane.trim()) {
47
+ dataplaneUrl = opts.dataplane.trim();
43
48
  } else {
44
- dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
49
+ dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
50
+ silent: opts.silentResolve === true
51
+ });
45
52
  }
46
53
 
47
54
  return { authConfig, dataplaneUrl };
@@ -38,6 +38,7 @@ const {
38
38
  } = require('./test-execution');
39
39
  const { runSystemLevelTest } = require('./test-system-level');
40
40
  const { shouldUseSystemLevelIntegrationCall } = require('./integration-test-dispatch');
41
+ const { cliOptsSkipSync } = require('../utils/cli-sync-options');
41
42
 
42
43
  /**
43
44
  * Loads and parses application config file
@@ -412,7 +413,8 @@ function determineDatasourcesToTest(datasourceFiles, datasourceFilter) {
412
413
  * @param {string} [options.controller] - Controller URL
413
414
  * @param {boolean} [options.verbose] - Show detailed test output
414
415
  * @param {number} [options.timeout] - Request timeout in milliseconds
415
- * @param {boolean} [options.sync] - When true, run full `aifabrix upload`-equivalent publish before tests
416
+ * @param {boolean} [options.noSync] - When true, skip publish (exercise dataplane config already deployed)
417
+ * @param {boolean} [options.sync] - Legacy explicit sync; ignored when `noSync` / `--no-sync` is set
416
418
  * @returns {Promise<Object>} Integration test results
417
419
  * @throws {Error} If testing fails
418
420
  */
@@ -457,13 +459,11 @@ async function testExternalSystemIntegration(appName, options = {}) {
457
459
 
458
460
  const { systemKey, authConfig, dataplaneUrl, datasourcesToTest, customPayload } = await prepareIntegrationTestEnvironment(appName, options);
459
461
 
460
- if (options.sync === true) {
462
+ if (!cliOptsSkipSync(options)) {
461
463
  logger.log(chalk.cyan('Syncing local config to dataplane…'));
462
464
  const { uploadExternalSystem } = require('../commands/upload');
463
- await uploadExternalSystem(systemKey, {
464
- verbose: !!options.verbose,
465
- minimal: true
466
- });
465
+ const { buildMinimalUploadSyncOptions } = require('../utils/upload-sync-options');
466
+ await uploadExternalSystem(systemKey, buildMinimalUploadSyncOptions(options));
467
467
  logger.log(formatSuccessLine('Sync complete'));
468
468
  }
469
469
 
@@ -22,6 +22,19 @@ const { loadVariables, loadRbac } = require('./helpers');
22
22
  * @param {Object} systemJson - System JSON object
23
23
  * @param {Object|null} rbac - RBAC configuration
24
24
  */
25
+ /**
26
+ * Maps legacy integration auth method names to schema 1.3.0 values before upload/deploy.
27
+ * @param {Object} systemJson - System JSON object (mutated in place)
28
+ */
29
+ function normalizeLegacyAuthMethod(systemJson) {
30
+ const auth = systemJson && systemJson.authentication;
31
+ if (!auth || typeof auth !== 'object') return;
32
+ const method = String(auth.method || '').trim().toLowerCase();
33
+ if (method === 'bearerkey') {
34
+ auth.method = 'bearerToken';
35
+ }
36
+ }
37
+
25
38
  function mergeRbacIntoSystemJson(systemJson, rbac) {
26
39
  if (!rbac) {
27
40
  return;
@@ -78,6 +91,7 @@ function extractAppMetadata(variables, appName) {
78
91
  */
79
92
  async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
80
93
  const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFile);
94
+ normalizeLegacyAuthMethod(systemJson);
81
95
  const rbacPath = resolveRbacPath(appPath);
82
96
  const rbac = rbacPath ? loadRbac(rbacPath) : null;
83
97
  mergeRbacIntoSystemJson(systemJson, rbac);
@@ -187,6 +201,7 @@ function toDeployJsonShape(manifest) {
187
201
  }
188
202
 
189
203
  module.exports = {
204
+ normalizeLegacyAuthMethod,
190
205
  generateControllerManifest,
191
206
  toDeployJsonShape
192
207
  };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview Governance CLI error messages (424.0).
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ /**
8
+ * @param {Error|string} err
9
+ * @returns {boolean}
10
+ */
11
+ function isGovernancePackNotFoundError(err) {
12
+ const raw = (err && err.message) || String(err);
13
+ return /not found/i.test(raw) || /\b404\b/.test(raw);
14
+ }
15
+
16
+ /**
17
+ * @param {Error|string} err
18
+ * @returns {boolean}
19
+ */
20
+ function isGovernanceCoverageSchemaError(err) {
21
+ const raw = (err && err.message) || String(err);
22
+ return (
23
+ raw.includes('\'coverage\' is a required property') || /spec\.coverage/i.test(raw)
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Short, non-duplicative CLI message from a dataplane governance API error.
29
+ * @param {Error|string} err
30
+ * @returns {string}
31
+ */
32
+ function shortGovernanceApiError(err) {
33
+ const raw = ((err && err.message) || String(err)).trim();
34
+ if (isGovernancePackNotFoundError(err)) {
35
+ return 'No active pack on dataplane';
36
+ }
37
+ if (isGovernanceCoverageSchemaError(err)) {
38
+ return 'Pack missing spec.coverage (424.0 schema)';
39
+ }
40
+ const firstLine = raw.split('\n').find(line => line.trim()) || raw;
41
+ if (/^Governance scenario pack not found/i.test(firstLine)) {
42
+ return 'No active pack on dataplane';
43
+ }
44
+ return firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
45
+ }
46
+
47
+ /**
48
+ * User-facing hint after a failed governance pack upload (validation).
49
+ * @param {string} systemKey
50
+ * @returns {string}
51
+ */
52
+ function governanceUploadSchemaHint(systemKey) {
53
+ return (
54
+ 'Scenario pack does not meet the 424.0 schema. Use dataplane-first generation:\n' +
55
+ ` aifabrix governance generate ${systemKey} --subject-email <email>\n` +
56
+ 'Or: aifabrix verify-governance ' +
57
+ `${systemKey} --subject-email <email>`
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Clarify subject-resolution 400s (dataplane reachable; User row missing).
63
+ * @param {Error} err
64
+ * @param {{ subjectEmails?: string[] }} [context]
65
+ * @returns {Error}
66
+ */
67
+ function enrichUnresolvableGovernanceSubjectsError(err, context = {}) {
68
+ const hay = `${err.message || ''} ${err.formatted || ''}`;
69
+ if (!hay.includes('No resolvable subjects')) {
70
+ return err;
71
+ }
72
+ const emails = Array.isArray(context.subjectEmails)
73
+ ? context.subjectEmails.filter(Boolean)
74
+ : [];
75
+ const lines = [
76
+ '',
77
+ 'The dataplane is reachable. This is not a network or controller error.',
78
+ 'Pack generation needs --subject-email values that match existing dataplane User rows.',
79
+ emails.length > 0 ? `Subject email(s) you passed: ${emails.join(', ')}` : null,
80
+ 'Check:',
81
+ ' aifabrix identity user list --search <email>',
82
+ 'If the user is missing, apply your identity CSV first:',
83
+ ' aifabrix identity apply --file integration/<systemKey>/fixtures/<users>.csv --filter-prefix <systemKey>',
84
+ ' aifabrix identity sync -e dev --purge-cache'
85
+ ].filter(line => line !== null);
86
+ err.formatted = `${err.formatted || err.message}${lines.join('\n')}`;
87
+ return err;
88
+ }
89
+
90
+ module.exports = {
91
+ isGovernancePackNotFoundError,
92
+ isGovernanceCoverageSchemaError,
93
+ shortGovernanceApiError,
94
+ governanceUploadSchemaHint,
95
+ enrichUnresolvableGovernanceSubjectsError
96
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview TTY summary when governance download finds no active packs.
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const chalk = require('chalk');
8
+ const { failureGlyph } = require('../utils/cli-test-layout-chalk');
9
+ const logger = require('../utils/logger');
10
+
11
+ /**
12
+ * @param {Array<{ datasourceKey: string, reason: string }>} missing
13
+ */
14
+ function printGovernanceDownloadMisses(missing) {
15
+ if (!missing || missing.length === 0) {
16
+ return;
17
+ }
18
+ for (const row of missing) {
19
+ logger.log(
20
+ chalk.white(`${row.datasourceKey}: ${failureGlyph()} ${row.reason}`)
21
+ );
22
+ }
23
+ logger.log('');
24
+ logger.log(chalk.gray('Next steps:'));
25
+ logger.log(
26
+ chalk.gray(
27
+ ' • Run: aifabrix governance generate <systemKey> --subject-email <email>'
28
+ )
29
+ );
30
+ logger.log(
31
+ chalk.gray(' • Or use: aifabrix verify-governance <systemKey> --subject-email <email>')
32
+ );
33
+ logger.log(chalk.gray(' • Ensure protection is published and identity sync has run'));
34
+ }
35
+
36
+ module.exports = {
37
+ printGovernanceDownloadMisses
38
+ };
@@ -16,7 +16,45 @@ const {
16
16
  formatBlockingError
17
17
  } = require('../utils/cli-test-layout-chalk');
18
18
 
19
- const SEP = '────────────────────────────────';
19
+ const SEP = '────────────────────────';
20
+ const MAX_SAMPLE_KEYS = 5;
21
+
22
+ /**
23
+ * @param {Object|null|undefined} packDocument
24
+ * @returns {Map<string, { mustIncludeKeys: string[], mustExcludeKeys: string[] }>}
25
+ */
26
+ function buildScenarioExpectMap(packDocument) {
27
+ const map = new Map();
28
+ const scenarios = packDocument?.spec?.scenarios;
29
+ if (!Array.isArray(scenarios)) {
30
+ return map;
31
+ }
32
+ for (const scenario of scenarios) {
33
+ if (!scenario || typeof scenario.id !== 'string') {
34
+ continue;
35
+ }
36
+ const expect = scenario.expect || {};
37
+ map.set(scenario.id, {
38
+ mustIncludeKeys: Array.isArray(expect.mustIncludeKeys) ? expect.mustIncludeKeys : [],
39
+ mustExcludeKeys: Array.isArray(expect.mustExcludeKeys) ? expect.mustExcludeKeys : []
40
+ });
41
+ }
42
+ return map;
43
+ }
44
+
45
+ /**
46
+ * @param {string[]} keys
47
+ * @param {number} [maxKeys]
48
+ * @returns {string}
49
+ */
50
+ function formatKeySample(keys, maxKeys = MAX_SAMPLE_KEYS) {
51
+ if (!Array.isArray(keys) || keys.length === 0) {
52
+ return '—';
53
+ }
54
+ const slice = keys.slice(0, maxKeys);
55
+ const suffix = keys.length > maxKeys ? ` (+${keys.length - maxKeys} more)` : '';
56
+ return `${slice.join(', ')}${suffix}`;
57
+ }
20
58
 
21
59
  /**
22
60
  * @param {'pass'|'fail'} status
@@ -28,38 +66,162 @@ function scenarioGlyph(status) {
28
66
 
29
67
  /**
30
68
  * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
31
- * @param {boolean} verbose
32
69
  */
33
- function logScenarioVerdict(scenario, verbose) {
34
- logger.log(` ${scenarioGlyph(scenario.status)} ${scenario.verdict}`);
35
- if (!verbose) return;
36
- logger.log(
37
- metaGray(
38
- ` Subject: ${scenario.subjectDisplayName} (${scenario.subjectUserId})`
39
- )
40
- );
41
- if (scenario.subjectGroups && scenario.subjectGroups.length > 0) {
42
- logger.log(metaGray(` Groups: ${scenario.subjectGroups.join(', ')}`));
70
+ function logGovernanceScenarioSubjectLines(scenario) {
71
+ logger.log(` ${scenarioGlyph(scenario.status)} ${scenario.id}`);
72
+ logger.log(metaGray(` Subject email: ${scenario.subjectEmail || '—'}`));
73
+ if (scenario.subjectDisplayName) {
74
+ logger.log(
75
+ metaGray(
76
+ ` Subject: ${scenario.subjectDisplayName} (${scenario.subjectUserId || '—'})`
77
+ )
78
+ );
43
79
  }
44
- logger.log(
45
- metaGray(
46
- ` Visible keys: ${scenario.visibleKeyCount} · excluded ABAC: ${scenario.excludedAbac}` +
47
- (scenario.auditRef ? ` · audit: ${scenario.auditRef}` : '')
48
- )
49
- );
50
- if (scenario.unexpectedVisibleKeys && scenario.unexpectedVisibleKeys.length > 0) {
80
+ logger.log(metaGray(` Verdict: ${scenario.verdict || '—'}`));
81
+ }
82
+
83
+ /**
84
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
85
+ */
86
+ function logGovernanceScenarioCounts(scenario) {
87
+ const visible = typeof scenario.visibleKeyCount === 'number' ? scenario.visibleKeyCount : 0;
88
+ const abac = typeof scenario.excludedAbac === 'number' ? scenario.excludedAbac : 0;
89
+ const filter = typeof scenario.excludedFilter === 'number' ? scenario.excludedFilter : 0;
90
+ logger.log(metaGray(` Visible keys: ${visible} allowed`));
91
+ logger.log(metaGray(` Declined: ${abac} by ABAC · ${filter} by filter`));
92
+ }
93
+
94
+ /**
95
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
96
+ * @param {{ mustIncludeKeys: string[], mustExcludeKeys: string[] }} expect
97
+ */
98
+ function logGovernanceScenarioPassSamples(expect) {
99
+ const allowed =
100
+ expect.mustIncludeKeys.length > 0 ? formatKeySample(expect.mustIncludeKeys) : '—';
101
+ const blocked =
102
+ expect.mustExcludeKeys.length > 0 ? formatKeySample(expect.mustExcludeKeys) : '—';
103
+ logger.log(metaGray(` Sample allowed: ${allowed}`));
104
+ logger.log(metaGray(` Sample blocked: ${blocked}`));
105
+ }
106
+
107
+ /**
108
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
109
+ */
110
+ function logGovernanceScenarioFailSamples(scenario) {
111
+ if (scenario.missingRequiredKeys?.length) {
51
112
  logger.log(
52
- metaGray(` Unexpected: ${scenario.unexpectedVisibleKeys.join(', ')}`)
113
+ metaGray(` Missing keys: ${formatKeySample(scenario.missingRequiredKeys)}`)
53
114
  );
54
115
  }
55
- if (scenario.missingRequiredKeys && scenario.missingRequiredKeys.length > 0) {
56
- logger.log(metaGray(` Missing: ${scenario.missingRequiredKeys.join(', ')}`));
116
+ if (scenario.unexpectedVisibleKeys?.length) {
117
+ logger.log(
118
+ metaGray(` Unexpected: ${formatKeySample(scenario.unexpectedVisibleKeys)}`)
119
+ );
57
120
  }
58
121
  if (scenario.fixHint) {
59
- logger.log(metaGray(` Fix: ${scenario.fixHint}`));
122
+ logger.log(metaGray(` Fix: ${scenario.fixHint}`));
60
123
  }
61
124
  }
62
125
 
126
+ /**
127
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
128
+ * @param {Map<string, { mustIncludeKeys: string[], mustExcludeKeys: string[] }>} expectMap
129
+ */
130
+ function logGovernanceScenarioDetail(scenario, expectMap) {
131
+ const expect = expectMap.get(scenario.id) || {
132
+ mustIncludeKeys: [],
133
+ mustExcludeKeys: []
134
+ };
135
+ logGovernanceScenarioSubjectLines(scenario);
136
+ logGovernanceScenarioCounts(scenario);
137
+ if (scenario.status === 'pass') {
138
+ logGovernanceScenarioPassSamples(expect);
139
+ } else {
140
+ logGovernanceScenarioFailSamples(scenario);
141
+ }
142
+ if (scenario.subjectGroups?.length) {
143
+ logger.log(metaGray(` Groups: ${scenario.subjectGroups.join(', ')}`));
144
+ }
145
+ if (scenario.auditRef) {
146
+ logger.log(metaGray(` Audit: ${scenario.auditRef}`));
147
+ }
148
+ }
149
+
150
+ /**
151
+ * One datasource block for verify-governance -v.
152
+ * @param {Object} row
153
+ * @param {string} row.datasourceKey
154
+ * @param {Object} [row.result]
155
+ * @param {Object} [row.packDocument]
156
+ * @param {string} [row.packPath]
157
+ * @param {string} [row.error]
158
+ */
159
+ function displayGovernanceDatasourceVerbose(row) {
160
+ logger.log('');
161
+ logger.log(chalk.white(`Datasource: ${row.datasourceKey}`));
162
+ if (row.error) {
163
+ logger.log(formatBlockingError(row.error));
164
+ return;
165
+ }
166
+ const run = row.result;
167
+ if (!run || !run.summary) {
168
+ logger.log(metaGray(' No scenario run result (pack missing or run skipped).'));
169
+ return;
170
+ }
171
+ logger.log(headerKeyValue('Pack:', run.packKey || '—'));
172
+ if (row.packPath) {
173
+ logger.log(metaGray(` File: ${row.packPath}`));
174
+ }
175
+ const summary = run.summary;
176
+ logger.log(
177
+ headerKeyValue('Scenarios:', `${summary.passed || 0}/${summary.total || 0} passed`)
178
+ );
179
+ const expectMap = buildScenarioExpectMap(row.packDocument);
180
+ for (const scenario of run.scenarios || []) {
181
+ logGovernanceScenarioDetail(scenario, expectMap);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Verbose (-v) scenario breakdown for verify-governance / governance verify.
187
+ * @param {Object} result
188
+ */
189
+ function displayGovernanceVerboseDetails(result) {
190
+ if (!result) {
191
+ return;
192
+ }
193
+ logger.log('');
194
+ logger.log(chalk.white('Scenario results'));
195
+ logger.log(metaGray(SEP));
196
+ logger.log(metaGray('Key sets only — no record payloads.'));
197
+
198
+ if (result.legacyResult && result.legacyResult.summary) {
199
+ const dsKey =
200
+ result.packDatasourceKey ||
201
+ primaryDatasourceFromPack(result.packDocument) ||
202
+ 'scenario pack';
203
+ displayGovernanceDatasourceVerbose({
204
+ datasourceKey: dsKey,
205
+ result: result.legacyResult,
206
+ packDocument: result.packDocument,
207
+ packPath: result.packPath
208
+ });
209
+ logger.log('');
210
+ return;
211
+ }
212
+
213
+ const rows = result.datasourceRows || [];
214
+ if (rows.length === 0) {
215
+ logger.log(metaGray(' No datasource scenario packs were run.'));
216
+ logger.log('');
217
+ return;
218
+ }
219
+ for (const row of rows) {
220
+ displayGovernanceDatasourceVerbose(row);
221
+ }
222
+ logger.log('');
223
+ }
224
+
63
225
  /**
64
226
  * @param {string} systemKey
65
227
  * @param {import('../api/types/governance-scenarios.types').GovernanceScenariosRunResponse} result
@@ -82,8 +244,13 @@ function displayGovernanceRunTTY(systemKey, result, opts = {}) {
82
244
  );
83
245
  logger.log(metaGray(' Does not call vendor APIs (use test-e2e for connectivity).'));
84
246
  logger.log('');
247
+ const expectMap = buildScenarioExpectMap(opts.packDocument);
85
248
  for (const scenario of result.scenarios || []) {
86
- logScenarioVerdict(scenario, opts.verbose === true);
249
+ if (opts.verbose === true) {
250
+ logGovernanceScenarioDetail(scenario, expectMap);
251
+ } else {
252
+ logger.log(` ${scenarioGlyph(scenario.status)} ${scenario.verdict}`);
253
+ }
87
254
  }
88
255
  logger.log('');
89
256
  if (agg === 'ok') {
@@ -112,8 +279,31 @@ function exitCodeFromGovernanceSummary(summary) {
112
279
  return 0;
113
280
  }
114
281
 
282
+ /**
283
+ * @param {Object|null|undefined} packDocument
284
+ * @returns {string|null}
285
+ */
286
+ function primaryDatasourceFromPack(packDocument) {
287
+ const scenarios = packDocument?.spec?.scenarios;
288
+ if (!Array.isArray(scenarios)) {
289
+ return null;
290
+ }
291
+ for (const scenario of scenarios) {
292
+ const keys = scenario?.search?.datasourceKeys;
293
+ if (Array.isArray(keys) && keys.length > 0 && typeof keys[0] === 'string') {
294
+ return keys[0];
295
+ }
296
+ }
297
+ return null;
298
+ }
299
+
115
300
  module.exports = {
301
+ buildScenarioExpectMap,
302
+ formatKeySample,
303
+ displayGovernanceDatasourceVerbose,
304
+ displayGovernanceVerboseDetails,
116
305
  displayGovernanceRunTTY,
117
306
  printGovernanceRunJson,
118
- exitCodeFromGovernanceSummary
307
+ exitCodeFromGovernanceSummary,
308
+ primaryDatasourceFromPack
119
309
  };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Write admin-secrets.env from setup password bundles.
3
+ *
4
+ * @fileoverview Apply dev/pro bundles to admin-secrets.env
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fsRealSync = require('../internal/fs-real-sync');
12
+ const logger = require('../utils/logger');
13
+ const { applyPasswordBundleToAdminObj } = require('../core/admin-secrets-bundle');
14
+
15
+ const DEFAULT_ADMIN_OBJ = {
16
+ REDIS_HOST: 'local:redis:6379:0:',
17
+ REDIS_COMMANDER_USER: 'admin'
18
+ };
19
+
20
+ /**
21
+ * @param {() => object} getCoreSecrets
22
+ * @param {string} adminSecretsPath
23
+ * @param {Object.<string, string>} merged
24
+ * @param {string|null} logMessage
25
+ */
26
+ async function writeAdminSecretsFile(getCoreSecrets, adminSecretsPath, merged, logMessage) {
27
+ const { assertWritableSecretsPathForTests } = require('../utils/aifabrix-test-runtime-guard');
28
+ assertWritableSecretsPathForTests(adminSecretsPath);
29
+ const content = await getCoreSecrets().formatAdminSecretsContent(merged);
30
+ fsRealSync.writeFileSync(adminSecretsPath, content, { mode: 0o600 });
31
+ if (logMessage) {
32
+ logger.log(logMessage);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {Object} ctx
38
+ * @param {() => object} ctx.getCoreSecrets
39
+ * @param {(bundle: object) => Promise<void>} [ctx.syncAdminKvFromPasswordBundle]
40
+ * @param {(bundle: object) => Promise<void>} [ctx.syncRequiredKvFromPasswordBundle]
41
+ * @param {boolean} [ctx.syncAdminKv]
42
+ * @param {(dir: string) => void} ctx.logVolumeResetHint
43
+ * @param {string} ctx.infraDirForHint
44
+ * @param {string} ctx.adminSecretsPath
45
+ * @param {Object} ctx.adminObj
46
+ * @param {Object} ctx.bundle
47
+ * @param {string} [ctx.email]
48
+ */
49
+ async function applyAdminSecretsFromBundle(ctx) {
50
+ const {
51
+ getCoreSecrets,
52
+ syncAdminKvFromPasswordBundle,
53
+ syncRequiredKvFromPasswordBundle,
54
+ syncAdminKv,
55
+ logVolumeResetHint,
56
+ infraDirForHint,
57
+ adminSecretsPath,
58
+ adminObj,
59
+ bundle,
60
+ email
61
+ } = ctx;
62
+ let merged = applyPasswordBundleToAdminObj({ ...DEFAULT_ADMIN_OBJ, ...adminObj }, bundle);
63
+ if (email && String(email).trim()) {
64
+ merged = { ...merged, PGADMIN_DEFAULT_EMAIL: String(email).trim() };
65
+ }
66
+ await writeAdminSecretsFile(
67
+ getCoreSecrets,
68
+ adminSecretsPath,
69
+ merged,
70
+ 'Updated admin credentials in admin-secrets.env.'
71
+ );
72
+ if (syncAdminKv && typeof syncAdminKvFromPasswordBundle === 'function') {
73
+ await syncAdminKvFromPasswordBundle(bundle);
74
+ } else if (typeof syncRequiredKvFromPasswordBundle === 'function') {
75
+ await syncRequiredKvFromPasswordBundle(bundle);
76
+ }
77
+ logVolumeResetHint(infraDirForHint);
78
+ }
79
+
80
+ module.exports = {
81
+ DEFAULT_ADMIN_OBJ,
82
+ writeAdminSecretsFile,
83
+ applyAdminSecretsFromBundle
84
+ };