@aifabrix/builder 2.45.0 → 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 (180) hide show
  1. package/.cursor/rules/docs-rules.mdc +3 -1
  2. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  4. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  5. package/jest.projects.js +44 -1
  6. package/lib/api/agent-metadata-validation.api.js +93 -0
  7. package/lib/api/audit-api-helpers.js +68 -0
  8. package/lib/api/audit.api.js +119 -0
  9. package/lib/api/auth-cache.api.js +44 -0
  10. package/lib/api/certificates.api.js +4 -4
  11. package/lib/api/credential.api.js +2 -2
  12. package/lib/api/dataplane-health.api.js +137 -0
  13. package/lib/api/dataplane-sync.api.js +27 -0
  14. package/lib/api/datasources-core.api.js +21 -18
  15. package/lib/api/datasources-extended.api.js +22 -22
  16. package/lib/api/dimensions.api.js +3 -0
  17. package/lib/api/external-systems.api.js +13 -13
  18. package/lib/api/governance-scenarios.api.js +39 -0
  19. package/lib/api/groups.api.js +79 -0
  20. package/lib/api/index.js +45 -2
  21. package/lib/api/pipeline.api.js +4 -4
  22. package/lib/api/protection.api.js +157 -0
  23. package/lib/api/records-bulk.api.js +30 -0
  24. package/lib/api/records-search-parse.js +49 -0
  25. package/lib/api/records-search.api.js +29 -0
  26. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  27. package/lib/api/types/audit.types.js +72 -0
  28. package/lib/api/types/dataplane-health.types.js +27 -0
  29. package/lib/api/types/dimensions.types.js +2 -0
  30. package/lib/api/types/governance-scenarios.types.js +39 -0
  31. package/lib/api/types/protection.types.js +69 -0
  32. package/lib/api/types/records-bulk.types.js +35 -0
  33. package/lib/api/types/records-search.types.js +28 -0
  34. package/lib/api/user-groups.api.js +50 -0
  35. package/lib/api/users.api.js +95 -0
  36. package/lib/api/validation-run.api.js +3 -3
  37. package/lib/api/validation-runner.js +3 -3
  38. package/lib/api/wizard-platform.api.js +3 -3
  39. package/lib/api/wizard.api.js +21 -21
  40. package/lib/app/deploy.js +34 -19
  41. package/lib/cli/index.js +4 -2
  42. package/lib/cli/infra-guided.js +22 -3
  43. package/lib/cli/setup-app.help.js +50 -2
  44. package/lib/cli/setup-app.js +33 -24
  45. package/lib/cli/setup-app.test-commands.js +80 -21
  46. package/lib/cli/setup-auth.js +30 -3
  47. package/lib/cli/setup-external-system.js +37 -3
  48. package/lib/cli/setup-utility-help-after.js +45 -0
  49. package/lib/cli/setup-utility-resolve.js +2 -2
  50. package/lib/cli/setup-utility.js +99 -81
  51. package/lib/commands/auth-status-dataplane-version.js +231 -0
  52. package/lib/commands/auth-status-display.js +94 -0
  53. package/lib/commands/auth-status.js +70 -6
  54. package/lib/commands/credential-push.js +10 -5
  55. package/lib/commands/datasource-load-export-cli.js +178 -0
  56. package/lib/commands/datasource-test-trust-cli.js +114 -0
  57. package/lib/commands/datasource-unified-test-cli.js +25 -17
  58. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  59. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  60. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  61. package/lib/commands/datasource.js +68 -1
  62. package/lib/commands/dimension-value.js +43 -13
  63. package/lib/commands/dimension.js +38 -7
  64. package/lib/commands/identity-apply-sync.js +131 -0
  65. package/lib/commands/identity-group-cli.js +146 -0
  66. package/lib/commands/identity-membership-role-cache.js +175 -0
  67. package/lib/commands/identity-shared.js +94 -0
  68. package/lib/commands/identity-user-cli.js +158 -0
  69. package/lib/commands/identity.js +43 -0
  70. package/lib/commands/login.js +46 -16
  71. package/lib/commands/protection-cli-leaves.js +286 -0
  72. package/lib/commands/protection.js +50 -0
  73. package/lib/commands/repair.js +13 -3
  74. package/lib/commands/test-e2e-external.js +40 -9
  75. package/lib/commands/test-governance-cli-options.js +47 -0
  76. package/lib/commands/test-governance-command-action.js +55 -0
  77. package/lib/commands/test-governance-external.js +88 -0
  78. package/lib/commands/test-trust-cli-options.js +32 -0
  79. package/lib/commands/test-trust-command-action.js +118 -0
  80. package/lib/commands/test-trust-external.js +127 -0
  81. package/lib/commands/upload.js +34 -13
  82. package/lib/commands/wizard-core.js +3 -0
  83. package/lib/core/config-device-dataplane.js +185 -0
  84. package/lib/datasource/agent-trust-debug-log.js +45 -0
  85. package/lib/datasource/agent-trust-fetch.js +115 -0
  86. package/lib/datasource/agent-trust-map.js +101 -0
  87. package/lib/datasource/agent-trust-mock.js +44 -0
  88. package/lib/datasource/agent-trust-run.js +93 -0
  89. package/lib/datasource/agent-trust-timeout.js +25 -0
  90. package/lib/datasource/audit-evidence-display.js +80 -0
  91. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  92. package/lib/datasource/audit-evidence-extract.js +177 -0
  93. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  94. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  95. package/lib/datasource/audit-evidence-poll.js +109 -0
  96. package/lib/datasource/audit-evidence-run.js +177 -0
  97. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  98. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  99. package/lib/datasource/audit-evidence-verify.js +108 -0
  100. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  101. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  102. package/lib/datasource/bulk-loader-service.js +127 -0
  103. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  104. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  105. package/lib/datasource/datasource-exporter-service.js +144 -0
  106. package/lib/datasource/datasource-identifier.js +148 -0
  107. package/lib/datasource/datasource-load-export-context.js +92 -0
  108. package/lib/datasource/integration-context.js +6 -4
  109. package/lib/datasource/local-data-paths.js +145 -0
  110. package/lib/datasource/log-cleaner.js +241 -0
  111. package/lib/datasource/log-viewer.js +111 -6
  112. package/lib/datasource/record-file-parser.js +85 -0
  113. package/lib/datasource/record-mapper.js +129 -0
  114. package/lib/datasource/resolve-app.js +74 -55
  115. package/lib/datasource/test-e2e.js +26 -2
  116. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  117. package/lib/datasource/unified-validation-run.js +43 -17
  118. package/lib/datasource/validate.js +37 -20
  119. package/lib/external-system/test-system-level.js +25 -4
  120. package/lib/external-system/test.js +3 -1
  121. package/lib/governance/governance-pack-loader.js +70 -0
  122. package/lib/governance/governance-run-display.js +119 -0
  123. package/lib/identity/identity-apply-core.js +108 -0
  124. package/lib/identity/identity-apply-phases.js +244 -0
  125. package/lib/identity/identity-apply-service.js +77 -0
  126. package/lib/identity/identity-csv-parser.js +114 -0
  127. package/lib/internal/fs-real-sync.js +29 -1
  128. package/lib/protection/auth-context.js +45 -0
  129. package/lib/protection/convert-batch.js +90 -0
  130. package/lib/protection/load.js +41 -0
  131. package/lib/protection/paths.js +158 -0
  132. package/lib/protection/preflight-datasource-ready.js +46 -0
  133. package/lib/protection/protection-create-probe-lines.js +142 -0
  134. package/lib/protection/protection-create-probes.js +95 -0
  135. package/lib/protection/protection-create-scaffold.js +281 -0
  136. package/lib/protection/protection-display-helpers.js +445 -0
  137. package/lib/protection/protection-display.js +215 -0
  138. package/lib/protection/protection-preset-registry.js +97 -0
  139. package/lib/protection/report-exit.js +62 -0
  140. package/lib/protection/resolve.js +202 -0
  141. package/lib/protection/run-commands-validate.js +130 -0
  142. package/lib/protection/run-commands.js +216 -0
  143. package/lib/protection/run-protection-create-helpers.js +134 -0
  144. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  145. package/lib/protection/run-protection-create.js +191 -0
  146. package/lib/protection/scope.js +22 -0
  147. package/lib/protection/sync-after-upload.js +64 -0
  148. package/lib/protection/upload-batch.js +127 -0
  149. package/lib/protection/validate-batch.js +113 -0
  150. package/lib/protection/validate-local.js +65 -0
  151. package/lib/resolvers/dimension-file.js +29 -4
  152. package/lib/schema/external-datasource.schema.json +47 -13
  153. package/lib/schema/flag-map-validation-run.json +2 -2
  154. package/lib/schema/protection.schema.json +374 -0
  155. package/lib/schema/type/credential-field-registry.json +636 -0
  156. package/lib/schema/wizard-config.schema.json +1 -1
  157. package/lib/utils/agent-trust-run-display.js +231 -0
  158. package/lib/utils/agent-trust-run-exit.js +59 -0
  159. package/lib/utils/agent-trust-wait-ui.js +94 -0
  160. package/lib/utils/cli-exit-codes.js +30 -0
  161. package/lib/utils/cli-sync-options.js +33 -0
  162. package/lib/utils/credential-push-ui.js +90 -0
  163. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  164. package/lib/utils/dataplane-cli-version-help.js +74 -0
  165. package/lib/utils/datasource-export-display.js +137 -0
  166. package/lib/utils/datasource-load-display.js +174 -0
  167. package/lib/utils/datasource-load-export-ui.js +120 -0
  168. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  169. package/lib/utils/datasource-test-run-display.js +3 -3
  170. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  171. package/lib/utils/help-builder.js +30 -24
  172. package/lib/utils/manifest-source-emit.js +22 -0
  173. package/lib/utils/semver-compare.js +127 -0
  174. package/lib/utils/upload-pipeline-ui.js +91 -0
  175. package/lib/utils/validation-poll-ui.js +14 -7
  176. package/lib/validation/wizard-config-validator.js +58 -19
  177. package/package.json +1 -1
  178. package/templates/applications/dataplane/env.template +13 -1
  179. package/templates/applications/dataplane/rbac.yaml +4 -0
  180. package/templates/external-system/README.md.hbs +56 -8
@@ -2,7 +2,7 @@
2
2
  * AI Fabrix Builder - Datasource Commands
3
3
  *
4
4
  * Handles datasource validation, listing, comparison, deployment, and online validation runs.
5
- * Subcommands `test`, `test-integration`, and `test-e2e` call the dataplane unified validation API; `log-test` / `log-integration` / `log-e2e` read saved debug JSON locally. Permissions are summarized in `docs/commands/permissions.md`.
5
+ * Subcommands `test`, `test-integration`, `test-e2e`, and `test-trust` call the dataplane; `log-*` read saved debug JSON locally; `clean-logs` removes those files. Permissions are summarized in `docs/commands/permissions.md`.
6
6
  *
7
7
  * @fileoverview Datasource management commands for AI Fabrix Builder
8
8
  * @author AI Fabrix Team
@@ -18,11 +18,15 @@ const { listDatasources } = require('../datasource/list');
18
18
  const { compareDatasources } = require('../datasource/diff');
19
19
  const { deployDatasource } = require('../datasource/deploy');
20
20
  const { runLogViewer } = require('../datasource/log-viewer');
21
+ const { runCleanLogs } = require('../datasource/log-cleaner');
21
22
  const {
22
23
  setupDatasourceTestCommand,
23
24
  setupDatasourceTestIntegrationCommand,
24
25
  setupDatasourceTestE2ECommand
25
26
  } = require('./datasource-unified-test-cli');
27
+ const { setupDatasourceTestTrustCommand } = require('./datasource-test-trust-cli');
28
+ const { setupDatasourceVerifyAuditCommand } = require('./datasource-verify-audit-cli');
29
+ const { setupDatasourceLoadExportCommands } = require('./datasource-load-export-cli');
26
30
  const { setupDatasourceCapabilityCommands } = require('./datasource-capability');
27
31
 
28
32
  /**
@@ -38,10 +42,17 @@ Examples:
38
42
  $ af ds upload <datasourceKey> # same as: aifabrix datasource upload …
39
43
  $ af ds test <datasourceKey> --app <integrationFolder> --debug
40
44
  $ af ds test-e2e <datasourceKey> --app <integrationFolder> -v
45
+ $ af ds test-e2e <datasourceKey> --verify-audit
46
+ $ af ds verify-audit <datasourceKey>
47
+ $ af ds test-trust <datasourceKey> --app <integrationFolder> -v
48
+ $ af ds load <datasourceKey> --app <integrationFolder> --dry-run
49
+ $ af ds export <datasourceKey> --app <integrationFolder> --format ndjson -v
41
50
  $ aifabrix datasource capability copy <file-or-key> --from create --as createBasicTrial --dry-run
42
51
  $ aifabrix datasource capability remove <file-or-key> --capability createTrial --dry-run
43
52
  $ aifabrix datasource capability diff ./a.json ./b.json --capability list
44
53
  $ aifabrix datasource capability edit <file-or-key> --capability create --section openapi
54
+ $ aifabrix datasource clean-logs --app test-e2e-hubspot --dry-run
55
+ $ aifabrix datasource clean-logs --all --type e2e
45
56
 
46
57
  Per-command help: aifabrix datasource <command> --help
47
58
  Shorthand: af ds (alias for datasource)
@@ -220,6 +231,57 @@ function setupDatasourceLogTestCommand(datasource) {
220
231
  });
221
232
  }
222
233
 
234
+ function setupDatasourceCleanLogsCommand(datasource) {
235
+ datasource
236
+ .command('clean-logs')
237
+ .description('Remove saved test debug JSON under integration/<app>/logs/')
238
+ .option('-a, --app <app>', 'Integration folder name (system key)')
239
+ .option('--all', 'Clean logs under every integration/<app>/logs/ folder')
240
+ .option(
241
+ '-t, --type <type>',
242
+ 'Log kind: test, integration, e2e, trust, or all (default: all)',
243
+ 'all'
244
+ )
245
+ .option('--dry-run', 'List files that would be removed without deleting')
246
+ .option('--json', 'Machine-readable summary on stdout')
247
+ .action(async options => {
248
+ try {
249
+ await runCleanLogs({
250
+ app: options.app,
251
+ all: options.all,
252
+ type: options.type,
253
+ dryRun: options.dryRun,
254
+ json: options.json
255
+ });
256
+ } catch (error) {
257
+ logger.error(formatBlockingError('clean-logs failed:'), error.message);
258
+ process.exit(1);
259
+ }
260
+ });
261
+ }
262
+
263
+ function setupDatasourceLogTrustCommand(datasource) {
264
+ datasource.command('log-trust <datasourceKey>')
265
+ .description('Show agent trust log from datasource test-trust (latest test-trust-*.json or --file)')
266
+ .option(
267
+ '-a, --app <app>',
268
+ 'Integration folder name (optional: resolve from cwd or datasource key if single match)'
269
+ )
270
+ .option('-f, --file <path>', 'Path to log file (default: latest test-trust log in app logs folder)')
271
+ .action(async(datasourceKey, options) => {
272
+ try {
273
+ await runLogViewer(datasourceKey, {
274
+ app: options.app,
275
+ file: options.file,
276
+ logType: 'test-trust'
277
+ });
278
+ } catch (error) {
279
+ logger.error(formatBlockingError('log-trust failed:'), error.message);
280
+ process.exit(1);
281
+ }
282
+ });
283
+ }
284
+
223
285
  /**
224
286
  * Setup datasource management commands
225
287
  * @param {Command} program - Commander program instance
@@ -237,12 +299,17 @@ function setupDatasourceCommands(program) {
237
299
  setupDatasourceListCommand(datasource);
238
300
  setupDatasourceDiffCommand(datasource);
239
301
  setupDatasourceUploadCommand(datasource);
302
+ setupDatasourceLoadExportCommands(datasource);
240
303
  setupDatasourceTestCommand(datasource);
241
304
  setupDatasourceTestIntegrationCommand(datasource);
242
305
  setupDatasourceTestE2ECommand(datasource);
306
+ setupDatasourceVerifyAuditCommand(datasource);
307
+ setupDatasourceTestTrustCommand(datasource);
243
308
  setupDatasourceLogE2ECommand(datasource);
244
309
  setupDatasourceLogIntegrationCommand(datasource);
245
310
  setupDatasourceLogTestCommand(datasource);
311
+ setupDatasourceLogTrustCommand(datasource);
312
+ setupDatasourceCleanLogsCommand(datasource);
246
313
  }
247
314
 
248
315
  module.exports = { setupDatasourceCommands };
@@ -2,9 +2,9 @@
2
2
  * Dimension value commands (Controller).
3
3
  *
4
4
  * Commands:
5
- * - aifabrix dimension-value create
6
- * - aifabrix dimension-value list
7
- * - aifabrix dimension-value delete
5
+ * - aifabrix dimension value create
6
+ * - aifabrix dimension value list
7
+ * - aifabrix dimension value delete
8
8
  *
9
9
  * @fileoverview Dimension value CLI commands
10
10
  * @author AI Fabrix Team
@@ -25,29 +25,39 @@ const {
25
25
  deleteDimensionValue
26
26
  } = require('../api/dimension-values.api');
27
27
 
28
+ const DIMENSION_VALUE_HELP_AFTER = `
29
+
30
+ Examples:
31
+ $ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
32
+ $ aifabrix dimension value list customerRegion
33
+ $ aifabrix dimension value delete clx1234567890abcdef
34
+
35
+ Per-command help: aifabrix dimension value <command> --help
36
+ `;
37
+
28
38
  const DIMENSION_VALUE_CREATE_HELP_AFTER = `
29
39
 
30
40
  Examples:
31
- $ aifabrix dimension-value create customerRegion --value emea --display-name "EMEA"
32
- $ aifabrix dimension-value create customerRegion --value na --display-name "North America"
41
+ $ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
42
+ $ aifabrix dimension value create customerRegion --value na --display-name "North America"
33
43
 
34
44
  `;
35
45
 
36
46
  const DIMENSION_VALUE_LIST_HELP_AFTER = `
37
47
 
38
48
  Examples:
39
- $ aifabrix dimension-value list customerRegion
40
- $ aifabrix dimension-value list customerRegion --page 1 --page-size 50
41
- $ aifabrix dimension-value list customerRegion --search emea
49
+ $ aifabrix dimension value list customerRegion
50
+ $ aifabrix dimension value list customerRegion --page 1 --page-size 50
51
+ $ aifabrix dimension value list customerRegion --search emea
42
52
 
43
53
  `;
44
54
 
45
55
  const DIMENSION_VALUE_DELETE_HELP_AFTER = `
46
56
 
47
57
  Examples:
48
- $ aifabrix dimension-value delete clx1234567890abcdef
58
+ $ aifabrix dimension value delete clx1234567890abcdef
49
59
 
50
- Tip: Find ids via "aifabrix dimension get <key>" (it prints values) or "aifabrix dimension-value list <key>".
60
+ Tip: Find ids via "aifabrix dimension get <key>" (it prints values) or "aifabrix dimension value list <key>".
51
61
 
52
62
  `;
53
63
 
@@ -62,6 +72,17 @@ function requireNonEmpty(raw, label) {
62
72
  return s;
63
73
  }
64
74
 
75
+ /**
76
+ * @param {Object} response - Controller API response
77
+ * @throws {Error} When response.success === false
78
+ */
79
+ function throwIfApiFailed(response) {
80
+ if (!response || response.success !== false) {
81
+ return;
82
+ }
83
+ throw new Error(response.formattedError || response.error || 'Request failed');
84
+ }
85
+
65
86
  async function resolveControllerAndAuth() {
66
87
  const controllerUrl = await resolveControllerUrl();
67
88
  if (!controllerUrl) {
@@ -100,6 +121,7 @@ function setupDimensionValueCreateCommand(cmd) {
100
121
  displayName: options.displayName,
101
122
  description: options.description
102
123
  });
124
+ throwIfApiFailed(res);
103
125
  const row = unwrapControllerData(res);
104
126
  logger.log(formatSuccessLine(`Dimension value created: ${dimKey}.${row.value}`));
105
127
  } catch (e) {
@@ -126,6 +148,7 @@ function setupDimensionValueListCommand(cmd) {
126
148
  pageSize: options.pageSize,
127
149
  search: options.search
128
150
  });
151
+ throwIfApiFailed(res);
129
152
  const payload = unwrapControllerData(res);
130
153
  const items = payload?.data ?? payload ?? [];
131
154
  logger.log(chalk.bold('\n🏷 Dimension values:\n'));
@@ -157,7 +180,8 @@ function setupDimensionValueDeleteCommand(cmd) {
157
180
  try {
158
181
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
159
182
  const id = requireNonEmpty(dimensionValueId, 'dimensionValueId');
160
- await deleteDimensionValue(controllerUrl, authConfig, id);
183
+ const res = await deleteDimensionValue(controllerUrl, authConfig, id);
184
+ throwIfApiFailed(res);
161
185
  logger.log(formatSuccessLine(`Dimension value deleted: ${id}`));
162
186
  } catch (e) {
163
187
  logger.error(formatBlockingError(e.message));
@@ -166,8 +190,14 @@ function setupDimensionValueDeleteCommand(cmd) {
166
190
  });
167
191
  }
168
192
 
169
- function setupDimensionValueCommands(program) {
170
- const cmd = program.command('dimension-value').description('Manage dimension values (static dimensions)');
193
+ /**
194
+ * @param {import('commander').Command} dimensionCmd - Parent `dimension` command
195
+ */
196
+ function setupDimensionValueCommands(dimensionCmd) {
197
+ const cmd = dimensionCmd
198
+ .command('value')
199
+ .description('Manage dimension values (static dimensions)')
200
+ .addHelpText('after', DIMENSION_VALUE_HELP_AFTER);
171
201
  setupDimensionValueCreateCommand(cmd);
172
202
  setupDimensionValueListCommand(cmd);
173
203
  setupDimensionValueDeleteCommand(cmd);
@@ -5,6 +5,7 @@
5
5
  * - aifabrix dimension create
6
6
  * - aifabrix dimension get
7
7
  * - aifabrix dimension list
8
+ * - aifabrix dimension value create|list|delete
8
9
  *
9
10
  * @fileoverview Dimension CLI commands
10
11
  * @author AI Fabrix Team
@@ -18,7 +19,7 @@ const logger = require('../utils/logger');
18
19
  const { resolveControllerUrl } = require('../utils/controller-url');
19
20
  const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
20
21
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
21
- const { readDimensionCreateFile } = require('../resolvers/dimension-file');
22
+ const { readDimensionCreateFile, normalizeValueType } = require('../resolvers/dimension-file');
22
23
  const {
23
24
  formatBlockingError,
24
25
  formatSuccessLine,
@@ -31,8 +32,23 @@ const {
31
32
  createDimensionIdempotent
32
33
  } = require('../api/dimensions.api');
33
34
  const { createDimensionValue } = require('../api/dimension-values.api');
35
+ const { setupDimensionValueCommands } = require('./dimension-value');
34
36
 
35
37
  const DIMENSION_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
38
+
39
+ /** Parent help for `aifabrix dimension -h` (leaf commands have their own Examples blocks). */
40
+ const DIMENSION_HELP_AFTER = `
41
+
42
+ Examples:
43
+ $ aifabrix dimension create --key customerRegion --display-name "Customer Region" --data-type string
44
+ $ aifabrix dimension get customerRegion
45
+ $ aifabrix dimension list --search region
46
+ $ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
47
+ $ aifabrix dimension value list customerRegion
48
+
49
+ Per-command help: aifabrix dimension <command> --help
50
+ `;
51
+
36
52
  const DATA_TYPES = new Set(['string', 'number', 'boolean']);
37
53
 
38
54
  const DIMENSION_CREATE_HELP_AFTER = `
@@ -139,6 +155,11 @@ function buildCreatePayload(options) {
139
155
  if (options.displayName) payload.displayName = options.displayName;
140
156
  if (options.description !== undefined) payload.description = options.description;
141
157
  if (options.dataType) payload.dataType = options.dataType;
158
+ if (options.valueType) {
159
+ payload.valueType = normalizeValueType(options.valueType);
160
+ } else if (payload.valueType === undefined) {
161
+ payload.valueType = 'static';
162
+ }
142
163
  if (options.required !== undefined && options.required !== null) {
143
164
  payload.isRequired = Boolean(options.required);
144
165
  }
@@ -197,6 +218,7 @@ function setupDimensionCreateCommand(dim) {
197
218
  .option('--display-name <name>', 'Display name')
198
219
  .option('--description <text>', 'Description')
199
220
  .option('--data-type <type>', 'string | number | boolean')
221
+ .option('--value-type <mode>', 'static | dynamic | both (catalog assignment mode)')
200
222
  .option('--required', 'Mark dimension required (isRequired=true)')
201
223
  .option('--no-required', 'Mark dimension not required (isRequired=false)')
202
224
  .action(async(options) => {
@@ -206,11 +228,12 @@ function setupDimensionCreateCommand(dim) {
206
228
  const out = await createDimensionIdempotent(controllerUrl, authConfig, payload);
207
229
  const dimRow = unwrapControllerData(out.response);
208
230
  await maybeCreateValuesFromFile(controllerUrl, authConfig, payload);
231
+ const vt = dimRow.valueType || payload.valueType || 'static';
209
232
  if (out.created) {
210
- logger.log(formatSuccessLine(`Dimension created: ${dimRow.key}`));
233
+ logger.log(formatSuccessLine(`Dimension created: ${dimRow.key} (valueType: ${vt})`));
211
234
  return;
212
235
  }
213
- logger.log(formatSuccessLine(`Dimension exists: ${dimRow.key}`));
236
+ logger.log(formatSuccessLine(`Dimension exists: ${dimRow.key} (valueType: ${vt})`));
214
237
  logger.log(infoLine('No changes were needed.'));
215
238
  } catch (e) {
216
239
  logger.error(formatBlockingError(e.message));
@@ -236,6 +259,7 @@ function setupDimensionGetCommand(dim) {
236
259
  logger.log(headerKeyValue('Key:', row.key || '—'));
237
260
  logger.log(headerKeyValue('Display:', row.displayName || '—'));
238
261
  logger.log(headerKeyValue('Type:', row.dataType || '—'));
262
+ logger.log(headerKeyValue('Value type:', row.valueType || '—'));
239
263
  logger.log(headerKeyValue('Required:', String(row.isRequired)));
240
264
  if (row.description) {
241
265
  logger.log(headerKeyValue('Description:', row.description));
@@ -268,22 +292,25 @@ function setupDimensionListCommand(dim) {
268
292
  const KEY_WIDTH = 26;
269
293
  const DISPLAY_WIDTH = 34;
270
294
  const TYPE_WIDTH = 10;
295
+ const VTYPE_WIDTH = 8;
271
296
  const REQ_WIDTH = 10;
272
- const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + TYPE_WIDTH + REQ_WIDTH;
297
+ const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + TYPE_WIDTH + VTYPE_WIDTH + REQ_WIDTH;
273
298
 
274
299
  const keyCol = 'Key'.padEnd(KEY_WIDTH);
275
300
  const displayCol = 'Display'.padEnd(DISPLAY_WIDTH);
276
301
  const typeCol = 'Type'.padEnd(TYPE_WIDTH);
302
+ const vtypeCol = 'VType'.padEnd(VTYPE_WIDTH);
277
303
  const reqCol = 'Required'.padEnd(REQ_WIDTH);
278
- logger.log(chalk.gray(`${keyCol}${displayCol}${typeCol}${reqCol}`));
304
+ logger.log(chalk.gray(`${keyCol}${displayCol}${typeCol}${vtypeCol}${reqCol}`));
279
305
  logger.log(chalk.gray('-'.repeat(SEP_LEN)));
280
306
 
281
307
  items.forEach((d) => {
282
308
  const key = (d?.key ?? '—').toString().padEnd(KEY_WIDTH);
283
309
  const displayName = (d?.displayName ?? '—').toString().padEnd(DISPLAY_WIDTH);
284
310
  const dataType = (d?.dataType ?? '—').toString().padEnd(TYPE_WIDTH);
311
+ const valueType = (d?.valueType ?? '—').toString().padEnd(VTYPE_WIDTH);
285
312
  const required = (d?.isRequired ?? '—').toString().padEnd(REQ_WIDTH);
286
- logger.log(`${key}${displayName}${dataType}${required}`);
313
+ logger.log(`${key}${displayName}${dataType}${valueType}${required}`);
287
314
  });
288
315
  logger.log('');
289
316
  }
@@ -318,10 +345,14 @@ function setupDimensionListCommand(dim) {
318
345
  * @param {import('commander').Command} program
319
346
  */
320
347
  function setupDimensionCommands(program) {
321
- const dim = program.command('dimension').description('Manage the Controller Dimension Catalog');
348
+ const dim = program
349
+ .command('dimension')
350
+ .description('Manage the Controller Dimension Catalog')
351
+ .addHelpText('after', DIMENSION_HELP_AFTER);
322
352
  setupDimensionCreateCommand(dim);
323
353
  setupDimensionGetCommand(dim);
324
354
  setupDimensionListCommand(dim);
355
+ setupDimensionValueCommands(dim);
325
356
  }
326
357
 
327
358
  module.exports = {
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Identity apply and sync commands
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const logger = require('../utils/logger');
10
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
11
+ const {
12
+ resolveControllerAndAuth,
13
+ resolveEnvKey,
14
+ requireNonEmpty,
15
+ unwrapControllerData,
16
+ throwIfApiFailed,
17
+ assertSyncStatsOk
18
+ } = require('./identity-shared');
19
+ const { clearAuthCache } = require('../api/auth-cache.api');
20
+ const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
21
+ const { runIdentityApply } = require('../identity/identity-apply-service');
22
+
23
+ function printJson(data) {
24
+ logger.log(JSON.stringify(data, null, 2));
25
+ }
26
+
27
+ /**
28
+ * @param {import('commander').Command} identityCmd
29
+ */
30
+ function setupIdentityApplyCommand(identityCmd) {
31
+ identityCmd
32
+ .command('apply')
33
+ .description('Apply users/groups/memberships from a CSV file (users.csv shape)')
34
+ .requiredOption('--file <path>', 'CSV file path')
35
+ .option('-e, --env <key>', 'Environment for --sync')
36
+ .option('--filter-prefix <prefix>', 'Only rows whose Id starts with this prefix')
37
+ .option('--dry-run', 'Parse and plan only; no writes')
38
+ .option('--sync', 'Run dataplane full sync after apply')
39
+ .option('--purge-cache', 'Clear controller cache before sync')
40
+ .option('--allow-empty-sync', 'Do not fail when sync reports usersProcessed=0')
41
+ .option('--json', 'JSON summary')
42
+ .action(async(options) => {
43
+ try {
44
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
45
+ const envKey = options.sync ? await resolveEnvKey(options) : undefined;
46
+ const summary = await runIdentityApply(controllerUrl, authConfig, {
47
+ filePath: requireNonEmpty(options.file, 'file'),
48
+ filterPrefix: options.filterPrefix,
49
+ dryRun: Boolean(options.dryRun),
50
+ purgeCache: Boolean(options.purgeCache),
51
+ sync: Boolean(options.sync),
52
+ envKey,
53
+ allowEmptySync: Boolean(options.allowEmptySync)
54
+ });
55
+ if (options.json) {
56
+ printJson(summary);
57
+ return;
58
+ }
59
+ logger.log(formatSuccessLine('Identity apply finished'));
60
+ logger.log(
61
+ ` groups: ${summary.groupsProcessed} processed (${summary.groupsCreated} created)`
62
+ );
63
+ logger.log(` users: ${summary.usersProcessed} processed (${summary.usersCreated} created)`);
64
+ logger.log(
65
+ ` memberships: ${summary.membershipsProcessed} processed (${summary.membershipsCreated} created, ${summary.membershipsSkipped} skipped)`
66
+ );
67
+ if (summary.syncStats) {
68
+ logger.log(
69
+ ` sync: usersProcessed=${summary.syncStats.usersProcessed ?? 0} groupsProcessed=${summary.syncStats.groupsProcessed ?? 0}`
70
+ );
71
+ }
72
+ } catch (e) {
73
+ logger.error(formatBlockingError(e.message));
74
+ process.exit(1);
75
+ }
76
+ });
77
+ }
78
+
79
+ /**
80
+ * @param {import('commander').Command} identityCmd
81
+ */
82
+ function setupIdentitySyncCommand(identityCmd) {
83
+ identityCmd
84
+ .command('sync')
85
+ .description('Full identity sync from controller to dataplane')
86
+ .option('-e, --env <key>', 'Environment key')
87
+ .option('--purge-cache', 'Clear controller cache before sync')
88
+ .option('--allow-empty-sync', 'Do not fail when usersProcessed=0')
89
+ .option('--json', 'JSON stats')
90
+ .action(async(options) => runIdentitySyncAction(options));
91
+ }
92
+
93
+ /**
94
+ * @param {Object} options
95
+ * @returns {Promise<void>}
96
+ */
97
+ async function maybePurgeCache(controllerUrl, authConfig, purgeCache) {
98
+ if (!purgeCache) {
99
+ return;
100
+ }
101
+ const cacheRes = await clearAuthCache(controllerUrl, authConfig);
102
+ throwIfApiFailed(cacheRes);
103
+ logger.log(formatSuccessLine('Controller cache cleared'));
104
+ }
105
+
106
+ async function runIdentitySyncAction(options) {
107
+ try {
108
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
109
+ const envKey = await resolveEnvKey(options);
110
+ await maybePurgeCache(controllerUrl, authConfig, options.purgeCache);
111
+ const res = await fullSyncToDataplane(controllerUrl, authConfig, envKey);
112
+ throwIfApiFailed(res);
113
+ const stats = unwrapControllerData(res);
114
+ assertSyncStatsOk(stats, { allowEmptySync: options.allowEmptySync });
115
+ if (options.json) {
116
+ printJson(stats);
117
+ return;
118
+ }
119
+ logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
120
+ logger.log(` usersProcessed=${stats?.usersProcessed ?? 0}`);
121
+ logger.log(` groupsProcessed=${stats?.groupsProcessed ?? 0}`);
122
+ } catch (e) {
123
+ logger.error(formatBlockingError(e.message));
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ module.exports = {
129
+ setupIdentityApplyCommand,
130
+ setupIdentitySyncCommand
131
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @fileoverview Identity group subcommands
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const logger = require('../utils/logger');
10
+ const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
11
+ const {
12
+ resolveControllerAndAuth,
13
+ requireNonEmpty,
14
+ unwrapControllerData,
15
+ throwIfApiFailed
16
+ } = require('./identity-shared');
17
+ const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
18
+
19
+ function printJson(data) {
20
+ logger.log(JSON.stringify(data, null, 2));
21
+ }
22
+
23
+ function registerGroupCreate(groupCmd) {
24
+ groupCmd
25
+ .command('create')
26
+ .description('Create a group')
27
+ .requiredOption('--name <name>', 'Group name (unique key)')
28
+ .requiredOption('--display-name <name>', 'Display name')
29
+ .option('--description <text>', 'Description')
30
+ .action(async(options) => {
31
+ try {
32
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
33
+ const res = await createGroup(controllerUrl, authConfig, {
34
+ name: requireNonEmpty(options.name, 'name'),
35
+ displayName: requireNonEmpty(options.displayName, 'display-name'),
36
+ description: options.description
37
+ });
38
+ throwIfApiFailed(res);
39
+ const row = unwrapControllerData(res);
40
+ logger.log(formatSuccessLine(`Group created: ${row.id} (${row.name})`));
41
+ } catch (e) {
42
+ logger.error(formatBlockingError(e.message));
43
+ process.exit(1);
44
+ }
45
+ });
46
+ }
47
+
48
+ function registerGroupList(groupCmd) {
49
+ groupCmd
50
+ .command('list')
51
+ .description('List groups')
52
+ .option('--page <n>', 'Page', (v) => parseInt(v, 10))
53
+ .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
54
+ .option('--search <text>', 'Search groups')
55
+ .option('--json', 'JSON output')
56
+ .action(async(options) => {
57
+ try {
58
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
59
+ const res = await listGroups(controllerUrl, authConfig, {
60
+ page: options.page,
61
+ pageSize: options.pageSize,
62
+ search: options.search
63
+ });
64
+ throwIfApiFailed(res);
65
+ const data = unwrapControllerData(res);
66
+ if (options.json) {
67
+ printJson(data);
68
+ return;
69
+ }
70
+ const items = Array.isArray(data) ? data : data?.data || [];
71
+ items.forEach((g) => logger.log(` ${g.id} ${g.name}`));
72
+ logger.log('');
73
+ } catch (e) {
74
+ logger.error(formatBlockingError(e.message));
75
+ process.exit(1);
76
+ }
77
+ });
78
+ }
79
+
80
+ function registerGroupGet(groupCmd) {
81
+ groupCmd
82
+ .command('get <idOrName>')
83
+ .description('Get group by id or name')
84
+ .option('--json', 'JSON output')
85
+ .action(async(idOrName, options) => {
86
+ try {
87
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
88
+ const res = await getGroup(controllerUrl, authConfig, requireNonEmpty(idOrName, 'idOrName'));
89
+ throwIfApiFailed(res);
90
+ const row = unwrapControllerData(res);
91
+ if (options.json) {
92
+ printJson(row);
93
+ return;
94
+ }
95
+ logger.log(headerKeyValue('Id:', row.id));
96
+ logger.log(headerKeyValue('Name:', row.name));
97
+ logger.log('');
98
+ } catch (e) {
99
+ logger.error(formatBlockingError(e.message));
100
+ process.exit(1);
101
+ }
102
+ });
103
+ }
104
+
105
+ function registerGroupMembers(groupCmd) {
106
+ groupCmd
107
+ .command('members <idOrName>')
108
+ .description('List members of a group')
109
+ .option('--json', 'JSON output')
110
+ .action(async(idOrName, options) => {
111
+ try {
112
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
113
+ const res = await listGroupMembers(
114
+ controllerUrl,
115
+ authConfig,
116
+ requireNonEmpty(idOrName, 'idOrName')
117
+ );
118
+ throwIfApiFailed(res);
119
+ const data = unwrapControllerData(res);
120
+ if (options.json) {
121
+ printJson(data);
122
+ return;
123
+ }
124
+ const items = Array.isArray(data) ? data : data?.data || [];
125
+ items.forEach((m) => logger.log(` ${m.userId || m.id} ${m.email || ''}`));
126
+ logger.log('');
127
+ } catch (e) {
128
+ logger.error(formatBlockingError(e.message));
129
+ process.exit(1);
130
+ }
131
+ });
132
+ }
133
+
134
+ /**
135
+ * @param {import('commander').Command} groupCmd
136
+ */
137
+ function setupIdentityGroupCommands(groupCmd) {
138
+ registerGroupCreate(groupCmd);
139
+ registerGroupList(groupCmd);
140
+ registerGroupGet(groupCmd);
141
+ registerGroupMembers(groupCmd);
142
+ }
143
+
144
+ module.exports = {
145
+ setupIdentityGroupCommands
146
+ };