@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -4,40 +4,73 @@
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
- const path = require('path');
8
7
  const { getIntegrationPath } = require('../utils/paths');
9
8
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
10
9
  const { loadConfigFile } = require('../utils/config-format');
10
+ const {
11
+ datasourceIdentifierMatchesFile,
12
+ resolveDatasourceFilePath
13
+ } = require('./datasource-identifier');
14
+
15
+ /**
16
+ * @param {string} appPath
17
+ * @param {string} schemaBasePath
18
+ * @param {string[]} datasourceFiles
19
+ * @param {string} identifier
20
+ * @returns {string|null}
21
+ */
22
+ function findDatasourceFileByIdentifier(appPath, schemaBasePath, datasourceFiles, identifier) {
23
+ const fsSync = require('fs');
24
+ for (const fileRef of datasourceFiles) {
25
+ if (!fileRef || typeof fileRef !== 'string') {
26
+ continue;
27
+ }
28
+ const fullPath = resolveDatasourceFilePath(appPath, schemaBasePath, fileRef);
29
+ if (!fsSync.existsSync(fullPath)) {
30
+ continue;
31
+ }
32
+ try {
33
+ const parsed = loadConfigFile(fullPath);
34
+ if (datasourceIdentifierMatchesFile(identifier, fileRef, parsed)) {
35
+ return fileRef;
36
+ }
37
+ } catch {
38
+ // skip unreadable or invalid files
39
+ }
40
+ }
41
+ return null;
42
+ }
11
43
 
12
44
  /**
13
45
  * @param {string} appKey
14
46
  * @param {string} datasourceKey
15
47
  * @param {Function} findDatasourceFileByKey - From integration-context
16
- * @returns {{ datasource: Object, datasourcePath: string }}
48
+ * @returns {{ datasource: Object, datasourcePath: string, datasourceKey: string }}
17
49
  */
18
50
  function loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey) {
19
51
  const appPath = getIntegrationPath(appKey);
20
52
  const config = loadConfigFile(resolveApplicationConfigPath(appPath));
21
53
  const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
22
54
  const datasourceFiles = config.externalIntegration?.dataSources || [];
23
- let datasourceFile = datasourceFiles.find(f => {
24
- const base = path.basename(f, path.extname(f));
25
- return base === datasourceKey || base.includes(datasourceKey);
26
- });
55
+ let datasourceFile = findDatasourceFileByIdentifier(
56
+ appPath,
57
+ schemaBasePath,
58
+ datasourceFiles,
59
+ datasourceKey
60
+ );
27
61
  if (!datasourceFile) {
28
62
  datasourceFile = findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey);
29
63
  }
30
64
  if (!datasourceFile) {
31
65
  throw new Error(`Datasource '${datasourceKey}' not found in application config`);
32
66
  }
33
- const datasourcePath = path.isAbsolute(schemaBasePath)
34
- ? path.join(schemaBasePath, datasourceFile)
35
- : path.join(appPath, schemaBasePath, datasourceFile);
67
+ const datasourcePath = resolveDatasourceFilePath(appPath, schemaBasePath, datasourceFile);
36
68
  const datasource = loadConfigFile(datasourcePath);
37
- if (datasource.key !== datasourceKey) {
38
- throw new Error(`Datasource key mismatch: file has '${datasource.key}', expected '${datasourceKey}'`);
69
+ const canonicalKey = datasource.key;
70
+ if (!canonicalKey || typeof canonicalKey !== 'string') {
71
+ throw new Error(`Datasource file is missing a valid key: ${datasourcePath}`);
39
72
  }
40
- return { datasource, datasourcePath };
73
+ return { datasource, datasourcePath, datasourceKey: canonicalKey };
41
74
  }
42
75
 
43
76
  module.exports = { loadDatasourceForApp };
@@ -71,6 +71,40 @@ function loadDatasourceForAppExport(appKey, datasourceKey) {
71
71
  return loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey);
72
72
  }
73
73
 
74
+ /**
75
+ * @async
76
+ * @param {string} appKey
77
+ * @param {Object} options
78
+ * @returns {Promise<{ authConfig: Object, dataplaneUrl: string }>}
79
+ */
80
+ async function resolveValidationAuth(appKey, options) {
81
+ if (options.authConfig && (options.dataplaneUrl || options.dataplane)) {
82
+ return {
83
+ authConfig: options.authConfig,
84
+ dataplaneUrl: options.dataplaneUrl || options.dataplane
85
+ };
86
+ }
87
+ const configObj = await getConfig();
88
+ const auth = await setupIntegrationTestAuth(appKey, options, configObj);
89
+ return { authConfig: auth.authConfig, dataplaneUrl: auth.dataplaneUrl };
90
+ }
91
+
92
+ /**
93
+ * @async
94
+ * @param {string} datasourceKey
95
+ * @param {Object} options
96
+ * @returns {Promise<Object>}
97
+ */
98
+ async function prepareValidationRunContext(datasourceKey, options) {
99
+ const runType = options.runType || 'test';
100
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
101
+ const systemKey = await getSystemKeyFromAppKey(appKey);
102
+ const { datasource } = loadDatasourceForAppExport(appKey, datasourceKey);
103
+ const { authConfig, dataplaneUrl } = await resolveValidationAuth(appKey, options);
104
+ await maybeSyncDatasourceToDataplane({ options, authConfig, systemKey });
105
+ return { runType, systemKey, datasource, authConfig, dataplaneUrl };
106
+ }
107
+
74
108
  /**
75
109
  * Run unified validation for one persisted datasource.
76
110
  * @async
@@ -83,41 +117,33 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
83
117
  if (!datasourceKey || typeof datasourceKey !== 'string') {
84
118
  throw new Error('Datasource key is required');
85
119
  }
86
- const runType = options.runType || 'test';
87
- const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
88
- const systemKey = await getSystemKeyFromAppKey(appKey);
89
- const { datasource } = loadDatasourceForAppExport(appKey, datasourceKey);
90
-
91
- const configObj = await getConfig();
92
- const { authConfig, dataplaneUrl } = await setupIntegrationTestAuth(appKey, options, configObj);
93
-
94
- await maybeSyncDatasourceToDataplane({ options, authConfig, systemKey });
95
120
 
121
+ const ctx = await prepareValidationRunContext(datasourceKey, options);
96
122
  const useAsync = options.noAsync ? false : options.async !== false;
97
123
  const timeoutMs = parseInt(String(options.timeout || '30000'), 10) || 30000;
98
-
99
124
  const body = await buildUnifiedValidationBody({
100
- systemKey,
125
+ systemKey: ctx.systemKey,
101
126
  datasourceKey,
102
- runType,
103
- datasource,
127
+ runType: ctx.runType,
128
+ datasource: ctx.datasource,
104
129
  payloadPath: options.payload,
105
130
  useAsync,
106
131
  options
107
132
  });
108
133
 
109
134
  const result = await postValidationRunAndOptionalPoll({
110
- dataplaneUrl,
111
- authConfig,
135
+ dataplaneUrl: ctx.dataplaneUrl,
136
+ authConfig: ctx.authConfig,
112
137
  body,
113
138
  timeoutMs,
114
139
  useAsync,
115
140
  noAsync: options.noAsync === true || options.async === false,
116
- verbosePoll: options.verbose === true
141
+ verbosePoll: options.verbose === true,
142
+ pollSubject: datasourceKey
117
143
  });
118
144
 
119
145
  if (result && result.envelope) {
120
- annotateMissingAutoRbacPermissions(result.envelope, datasourceKey, datasource);
146
+ annotateMissingAutoRbacPermissions(result.envelope, datasourceKey, ctx.datasource);
121
147
  }
122
148
  return result;
123
149
  }
@@ -11,6 +11,11 @@
11
11
  const path = require('path');
12
12
  const fs = require('fs');
13
13
  const pathsUtil = require('../utils/paths');
14
+ const {
15
+ datasourceIdentifierMatchesFile,
16
+ pickIntegrationAppForDatasourceIdentifier,
17
+ collectDatasourceFileRefsMatchingIdentifier
18
+ } = require('./datasource-identifier');
14
19
  const { loadExternalDataSourceSchema } = require('../utils/schema-loader');
15
20
  const { formatValidationErrors } = require('../utils/error-formatter');
16
21
  const { validateFieldReferences } = require('./field-reference-validator');
@@ -46,16 +51,13 @@ function tryResolveAsExistingFile(identifier) {
46
51
  }
47
52
 
48
53
  /**
49
- * Pick integration app folder for a datasource `key` (longest matching prefix wins).
54
+ * Pick integration app folder for a datasource identifier (longest matching prefix wins).
50
55
  * @param {string} datasourceKey
51
56
  * @param {string[]} appNames
52
57
  * @returns {string|null}
53
58
  */
54
59
  function pickIntegrationAppForDatasourceKey(datasourceKey, appNames) {
55
- const matches = appNames.filter((app) => datasourceKey === app || datasourceKey.startsWith(`${app}-`));
56
- if (matches.length === 0) return null;
57
- matches.sort((a, b) => b.length - a.length);
58
- return matches[0];
60
+ return pickIntegrationAppForDatasourceIdentifier(datasourceKey, appNames);
59
61
  }
60
62
 
61
63
  /**
@@ -94,7 +96,7 @@ function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
94
96
  } catch {
95
97
  continue;
96
98
  }
97
- if (parsed && typeof parsed === 'object' && parsed.key === datasourceKey) {
99
+ if (datasourceIdentifierMatchesFile(datasourceKey, name, parsed)) {
98
100
  hits.push(full);
99
101
  }
100
102
  }
@@ -102,21 +104,12 @@ function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
102
104
  }
103
105
 
104
106
  /**
105
- * Find integration JSON whose parsed `key` equals datasourceKey.
107
+ * @param {string} appName
106
108
  * @param {string} datasourceKey
107
- * @returns {string} Absolute file path
109
+ * @returns {string}
108
110
  * @throws {Error}
109
111
  */
110
- function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
111
- const appNames = pathsUtil.listIntegrationAppNames();
112
- const appName = pickIntegrationAppForDatasourceKey(datasourceKey, appNames);
113
- if (!appName) {
114
- throw new Error(
115
- `No integration/<app>/ folder matches datasource key "${datasourceKey}". ` +
116
- 'Pass a path to the JSON file, or use a key whose prefix matches an app under integration/ ' +
117
- '(e.g. app test-e2e-hubspot for key test-e2e-hubspot-users).'
118
- );
119
- }
112
+ function resolvePathFromIntegrationFolderScan(appName, datasourceKey) {
120
113
  const dir = pathsUtil.getIntegrationPath(appName);
121
114
  if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
122
115
  throw new Error(`Integration folder not found: ${dir}`);
@@ -128,16 +121,40 @@ function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
128
121
  const hits = collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey);
129
122
  if (hits.length === 0) {
130
123
  throw new Error(
131
- `No datasource JSON with key "${datasourceKey}" under integration/${appName}/. ` +
124
+ `No datasource JSON matching "${datasourceKey}" under integration/${appName}/. ` +
132
125
  `Checked: ${scanOrder.length ? scanOrder.join(', ') : '(no JSON candidates)'}`
133
126
  );
134
127
  }
135
128
  if (hits.length > 1) {
136
- throw new Error(`Ambiguous: multiple files declare key "${datasourceKey}": ${hits.join(', ')}`);
129
+ throw new Error(`Ambiguous: multiple files match "${datasourceKey}": ${hits.join(', ')}`);
137
130
  }
138
131
  return hits[0];
139
132
  }
140
133
 
134
+ /**
135
+ * Find integration JSON whose parsed `key` equals datasourceKey.
136
+ * @param {string} datasourceKey
137
+ * @returns {string} Absolute file path
138
+ * @throws {Error}
139
+ */
140
+ function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
141
+ const appNames = pathsUtil.listIntegrationAppNames();
142
+ const appName = pickIntegrationAppForDatasourceKey(datasourceKey, appNames);
143
+ if (!appName) {
144
+ throw new Error(
145
+ `No integration/<app>/ folder matches datasource identifier "${datasourceKey}". ` +
146
+ 'Pass a path to the JSON file, or use a datasource key or filename whose prefix matches an app under integration/ ' +
147
+ '(e.g. test-e2e-hubspot-companies or test-e2e-hubspot-datasource-companies for app test-e2e-hubspot).'
148
+ );
149
+ }
150
+ const manifestHits = collectDatasourceFileRefsMatchingIdentifier(appName, datasourceKey);
151
+ if (manifestHits.length === 1) {
152
+ const dir = pathsUtil.getIntegrationPath(appName);
153
+ return path.join(dir, manifestHits[0]);
154
+ }
155
+ return resolvePathFromIntegrationFolderScan(appName, datasourceKey);
156
+ }
157
+
141
158
  /**
142
159
  * @param {string} identifier - File path or datasource `key`
143
160
  * @returns {string} Absolute path to JSON file
@@ -169,7 +169,7 @@ function logImmediateControllerDeploymentOutcome(deploymentOutcome) {
169
169
  );
170
170
  return;
171
171
  }
172
- logger.log(chalk.gray(' See Deployment section below for details.'));
172
+ logger.log(chalk.gray(' Check the controller deployment job or logs for details.'));
173
173
  }
174
174
 
175
175
  async function executeDeployAndDisplay(manifest, controllerUrl, environment, authConfig, options) {
@@ -192,6 +192,10 @@ async function executeDeployAndDisplay(manifest, controllerUrl, environment, aut
192
192
  const deploymentOutcome = parseControllerDeploymentOutcome(result);
193
193
  logImmediateControllerDeploymentOutcome(deploymentOutcome);
194
194
 
195
+ if (!deploymentOutcome.ok) {
196
+ return result;
197
+ }
198
+
195
199
  const ctx = await fetchDataplaneDeployReadiness(
196
200
  controllerUrl,
197
201
  environment,
@@ -16,7 +16,7 @@ const { getIntegrationPath } = require('../utils/paths');
16
16
  const { runUnifiedDatasourceValidation } = require('../datasource/unified-validation-run');
17
17
 
18
18
  async function runOneDatasourceKey(appName, datasourceKey, opts) {
19
- const { environment, runType, debug, timeoutMs } = opts;
19
+ const { environment, runType, debug, timeoutMs, authConfig, dataplaneUrl } = opts;
20
20
  try {
21
21
  const uni = await runUnifiedDatasourceValidation(datasourceKey, {
22
22
  app: appName,
@@ -26,7 +26,9 @@ async function runOneDatasourceKey(appName, datasourceKey, opts) {
26
26
  verbose: false,
27
27
  timeout: timeoutMs,
28
28
  async: true,
29
- noAsync: false
29
+ noAsync: false,
30
+ authConfig,
31
+ dataplaneUrl
30
32
  });
31
33
 
32
34
  if (uni.apiError) {
@@ -89,7 +91,17 @@ async function maybeWriteSystemRunLog(appName, systemKey, datasourceKeys, runTyp
89
91
  * @param {number} [params.timeout] - Request timeout
90
92
  * @returns {Promise<{success: boolean, datasourceResults: Object[]}>}
91
93
  */
92
- async function runSystemLevelTest({ appName, systemKey, datasourceKeys, environment, runType, debug, timeout }) {
94
+ async function runSystemLevelTest({
95
+ appName,
96
+ systemKey,
97
+ datasourceKeys,
98
+ environment,
99
+ runType,
100
+ debug,
101
+ timeout,
102
+ authConfig,
103
+ dataplaneUrl
104
+ }) {
93
105
  if (!Array.isArray(datasourceKeys) || datasourceKeys.length === 0) {
94
106
  return { success: true, datasourceResults: [] };
95
107
  }
@@ -100,7 +112,16 @@ async function runSystemLevelTest({ appName, systemKey, datasourceKeys, environm
100
112
  // Keep sequential to avoid spiking external APIs / dataplane concurrency in CLI runs.
101
113
  // (Plan allows parallel later, but UX needs stable logs first.)
102
114
  // eslint-disable-next-line no-await-in-loop -- intentional sequential execution
103
- results.push(await runOneDatasourceKey(appName, key, { environment, runType, debug, timeoutMs }));
115
+ results.push(
116
+ await runOneDatasourceKey(appName, key, {
117
+ environment,
118
+ runType,
119
+ debug,
120
+ timeoutMs,
121
+ authConfig,
122
+ dataplaneUrl
123
+ })
124
+ );
104
125
  }
105
126
 
106
127
  const success = results.every(r => r.success);
@@ -486,7 +486,9 @@ async function testExternalSystemIntegration(appName, options = {}) {
486
486
  environment: options.environment,
487
487
  runType: 'integration',
488
488
  debug: options.debug,
489
- timeout
489
+ timeout,
490
+ authConfig,
491
+ dataplaneUrl
490
492
  });
491
493
  results.success = systemResult.success;
492
494
  results.datasourceResults = systemResult.datasourceResults;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @fileoverview Load governance scenario pack YAML from integration folder
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const yaml = require('js-yaml');
12
+ const { getIntegrationPath } = require('../utils/paths');
13
+
14
+ /**
15
+ * Resolve pack file path for test-governance.
16
+ * @param {string} systemKey
17
+ * @param {Object} options
18
+ * @param {string} [options.pack] - Explicit path (absolute or relative to integration root)
19
+ * @param {string} [options.app] - Integration folder override
20
+ * @returns {string}
21
+ */
22
+ function resolveGovernancePackPath(systemKey, options = {}) {
23
+ const integrationPath = options.app
24
+ ? path.isAbsolute(options.app)
25
+ ? options.app
26
+ : path.resolve(process.cwd(), options.app)
27
+ : getIntegrationPath(systemKey);
28
+
29
+ if (options.pack) {
30
+ const packPath = path.isAbsolute(options.pack)
31
+ ? options.pack
32
+ : path.join(integrationPath, options.pack);
33
+ if (!fs.existsSync(packPath)) {
34
+ throw new Error(`Scenario pack not found: ${packPath}`);
35
+ }
36
+ return packPath;
37
+ }
38
+
39
+ const candidates = [
40
+ path.join(integrationPath, 'scenarios', 'default.yaml'),
41
+ path.join(integrationPath, 'scenarios', `${systemKey}-v1.yaml`)
42
+ ];
43
+ for (const candidate of candidates) {
44
+ if (fs.existsSync(candidate)) {
45
+ return candidate;
46
+ }
47
+ }
48
+ throw new Error(
49
+ `No scenario pack found under ${path.join(integrationPath, 'scenarios')}. ` +
50
+ 'Use --pack <path> (e.g. scenarios/test-protection-v1.yaml).'
51
+ );
52
+ }
53
+
54
+ /**
55
+ * @param {string} packPath
56
+ * @returns {Object}
57
+ */
58
+ function loadGovernancePackYaml(packPath) {
59
+ const raw = fs.readFileSync(packPath, 'utf8');
60
+ const doc = yaml.load(raw);
61
+ if (!doc || typeof doc !== 'object') {
62
+ throw new Error(`Invalid scenario pack YAML: ${packPath}`);
63
+ }
64
+ return doc;
65
+ }
66
+
67
+ module.exports = {
68
+ resolveGovernancePackPath,
69
+ loadGovernancePackYaml
70
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview TTY display for governance scenario acceptance runs
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const {
12
+ sectionTitle,
13
+ headerKeyValue,
14
+ metadata: metaGray,
15
+ formatSuccessParagraph,
16
+ formatBlockingError
17
+ } = require('../utils/cli-test-layout-chalk');
18
+
19
+ const SEP = '────────────────────────────────';
20
+
21
+ /**
22
+ * @param {'pass'|'fail'} status
23
+ * @returns {string}
24
+ */
25
+ function scenarioGlyph(status) {
26
+ return status === 'pass' ? chalk.green('✔') : chalk.red('✖');
27
+ }
28
+
29
+ /**
30
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
31
+ * @param {boolean} verbose
32
+ */
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(', ')}`));
43
+ }
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) {
51
+ logger.log(
52
+ metaGray(` Unexpected: ${scenario.unexpectedVisibleKeys.join(', ')}`)
53
+ );
54
+ }
55
+ if (scenario.missingRequiredKeys && scenario.missingRequiredKeys.length > 0) {
56
+ logger.log(metaGray(` Missing: ${scenario.missingRequiredKeys.join(', ')}`));
57
+ }
58
+ if (scenario.fixHint) {
59
+ logger.log(metaGray(` Fix: ${scenario.fixHint}`));
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param {string} systemKey
65
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenariosRunResponse} result
66
+ * @param {{ verbose?: boolean, packPath?: string }} opts
67
+ */
68
+ function displayGovernanceRunTTY(systemKey, result, opts = {}) {
69
+ const agg = result.summary.failed > 0 ? 'fail' : 'ok';
70
+ logger.log(SEP);
71
+ logger.log(sectionTitle('Governed search acceptance (key sets only — no record payload)'));
72
+ logger.log(headerKeyValue('System:', systemKey));
73
+ logger.log(headerKeyValue('Pack:', result.packKey));
74
+ if (opts.packPath) {
75
+ logger.log(metaGray(` File: ${opts.packPath}`));
76
+ }
77
+ logger.log(
78
+ headerKeyValue(
79
+ 'Summary:',
80
+ `${result.summary.passed}/${result.summary.total} passed`
81
+ )
82
+ );
83
+ logger.log(metaGray(' Does not call vendor APIs (use test-e2e for connectivity).'));
84
+ logger.log('');
85
+ for (const scenario of result.scenarios || []) {
86
+ logScenarioVerdict(scenario, opts.verbose === true);
87
+ }
88
+ logger.log('');
89
+ if (agg === 'ok') {
90
+ logger.log(formatSuccessParagraph('All governance scenarios passed'));
91
+ } else {
92
+ logger.log(formatBlockingError(`${result.summary.failed} scenario(s) failed`));
93
+ }
94
+ logger.log(SEP);
95
+ }
96
+
97
+ /**
98
+ * @param {import('../api/types/governance-scenarios.types').GovernanceScenariosRunResponse} result
99
+ */
100
+ function printGovernanceRunJson(result) {
101
+ logger.log(JSON.stringify(result, null, 2));
102
+ }
103
+
104
+ /**
105
+ * @param {{ total: number, passed: number, failed: number }} summary
106
+ * @returns {number}
107
+ */
108
+ function exitCodeFromGovernanceSummary(summary) {
109
+ if (!summary || summary.failed > 0) {
110
+ return 1;
111
+ }
112
+ return 0;
113
+ }
114
+
115
+ module.exports = {
116
+ displayGovernanceRunTTY,
117
+ printGovernanceRunJson,
118
+ exitCodeFromGovernanceSummary
119
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @fileoverview Shared helpers for identity apply
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { getUser, listUsers } = require('../api/users.api');
10
+
11
+ /**
12
+ * @param {Object} response
13
+ * @returns {any}
14
+ */
15
+ function unwrapControllerData(response) {
16
+ if (!response || response.success === false) {
17
+ return null;
18
+ }
19
+ return response?.data?.data ?? response?.data ?? response;
20
+ }
21
+
22
+ /**
23
+ * @param {Object} response
24
+ * @returns {string}
25
+ */
26
+ function apiErrorMessage(response) {
27
+ if (!response) {
28
+ return 'Request failed';
29
+ }
30
+ const err = response.error;
31
+ if (typeof err === 'string') {
32
+ return err;
33
+ }
34
+ if (err && typeof err === 'object') {
35
+ return err.formattedError || err.message || err.error || 'Request failed';
36
+ }
37
+ return response.formattedError || response.message || 'Request failed';
38
+ }
39
+
40
+ /**
41
+ * @param {Object} response
42
+ * @throws {Error}
43
+ */
44
+ function throwIfApiFailed(response) {
45
+ if (!response || response.success === false) {
46
+ throw new Error(apiErrorMessage(response));
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {Object|null|undefined} stats
52
+ * @param {{ allowEmptySync?: boolean }} [opts]
53
+ * @throws {Error}
54
+ */
55
+ function assertSyncStatsOk(stats, opts = {}) {
56
+ const syncErrors = Number(stats?.errors ?? 0);
57
+ if (syncErrors > 0) {
58
+ throw new Error(
59
+ `Dataplane full sync reported ${syncErrors} error(s). ` +
60
+ `usersProcessed=${stats?.usersProcessed ?? 0} groupsProcessed=${stats?.groupsProcessed ?? 0}. ` +
61
+ 'Check miso-controller logs for the sync request.'
62
+ );
63
+ }
64
+ const processed = Number(stats?.usersProcessed ?? 0);
65
+ if (processed === 0 && !opts.allowEmptySync) {
66
+ throw new Error(
67
+ 'Dataplane full sync completed with usersProcessed=0. Use --allow-empty-sync to accept, or fix identity on the controller.'
68
+ );
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @async
74
+ * @param {string} controllerUrl
75
+ * @param {Object} authConfig
76
+ * @param {{ csvId?: string, email: string }} userSpec
77
+ * @returns {Promise<Object|null>}
78
+ */
79
+ async function findExistingUser(controllerUrl, authConfig, userSpec) {
80
+ const csvId = String(userSpec.csvId || '').trim();
81
+ if (csvId) {
82
+ const byId = await getUser(controllerUrl, authConfig, csvId);
83
+ const row = unwrapControllerData(byId);
84
+ if (row && row.id) {
85
+ return row;
86
+ }
87
+ }
88
+ const email = String(userSpec.email || '').trim();
89
+ const listed = await listUsers(controllerUrl, authConfig, {
90
+ search: email,
91
+ pageSize: 10,
92
+ page: 1
93
+ });
94
+ const items = unwrapControllerData(listed);
95
+ const list = Array.isArray(items) ? items : items?.data;
96
+ if (!Array.isArray(list)) {
97
+ return null;
98
+ }
99
+ return list.find((u) => String(u.email || '').toLowerCase() === email.toLowerCase()) || null;
100
+ }
101
+
102
+ module.exports = {
103
+ unwrapControllerData,
104
+ apiErrorMessage,
105
+ throwIfApiFailed,
106
+ assertSyncStatsOk,
107
+ findExistingUser
108
+ };