@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
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @fileoverview Run Plan 407.1 nine-row audit evidence matrix against dataplane APIs
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { queryExecutions } = require('../api/audit.api');
10
+ const { pollExecutionsUntilReady } = require('./audit-evidence-poll');
11
+ const {
12
+ buildListMatrixRows,
13
+ buildPerExecutionAndTailRows
14
+ } = require('./audit-evidence-verify-matrix');
15
+ const { checkRow8CorrelationGroup, checkRow9Traces } = require('./audit-evidence-verify-helpers');
16
+
17
+ /**
18
+ * @param {Object} ctx
19
+ * @returns {number}
20
+ */
21
+ /**
22
+ * E2E runs tag executions with testRunId as correlationId; prefer that for list poll.
23
+ * @param {Object} ctx
24
+ * @returns {Object}
25
+ */
26
+ function buildExecutionsPollQuery(ctx) {
27
+ const base = { pageSize: 100, sort: '-startedAt' };
28
+ if (ctx.correlationId) {
29
+ return { ...base, correlationId: ctx.correlationId };
30
+ }
31
+ return { ...base, datasourceKey: ctx.datasourceKey };
32
+ }
33
+
34
+ function resolveMinExpected(ctx) {
35
+ const fromOps = ctx.expectedOperations && ctx.expectedOperations.length;
36
+ const fromIds = ctx.executionIds && ctx.executionIds.length;
37
+ if (fromIds && fromOps) {
38
+ return Math.max(1, Math.min(fromOps, fromIds));
39
+ }
40
+ return Math.max(1, fromOps || fromIds || 1);
41
+ }
42
+
43
+ /**
44
+ * @param {string} datasourceKey
45
+ * @param {Object} ctx
46
+ * @returns {import('../api/types/audit.types').AuditEvidenceVerification}
47
+ */
48
+ function failedMissingIds(datasourceKey, ctx) {
49
+ return {
50
+ status: 'failed',
51
+ datasourceKey,
52
+ correlationId: ctx.correlationId || null,
53
+ executionIds: [],
54
+ matrix: [
55
+ {
56
+ row: 0,
57
+ status: 'failed',
58
+ detail: 'missing audit.executionIds in envelope',
59
+ code: 'auditIdsMissing'
60
+ }
61
+ ]
62
+ };
63
+ }
64
+
65
+ /**
66
+ * @param {Object} ctx
67
+ * @returns {Promise<import('../api/types/audit.types').AuditEvidenceVerification>}
68
+ */
69
+ async function verifyAuditEvidenceMatrix(ctx) {
70
+ const datasourceKey = ctx.datasourceKey || '';
71
+ const executionIds = Array.isArray(ctx.executionIds) ? ctx.executionIds : [];
72
+ if (!executionIds.length && !ctx.correlationId) {
73
+ return failedMissingIds(datasourceKey, ctx);
74
+ }
75
+
76
+ const { dataplaneUrl, authConfig } = ctx;
77
+ const minExpected = resolveMinExpected(ctx);
78
+ const execQuery = buildExecutionsPollQuery(ctx);
79
+ const polled = await pollExecutionsUntilReady({
80
+ fetchExecutions: () => queryExecutions(dataplaneUrl, authConfig, execQuery),
81
+ minExpected,
82
+ executionIds,
83
+ maxWaitMs: ctx.maxWaitMs,
84
+ intervalMs: ctx.intervalMs
85
+ });
86
+
87
+ const executions = polled.data || [];
88
+ const matrix = await buildListMatrixRows(ctx, polled, minExpected);
89
+ const tail = await buildPerExecutionAndTailRows(ctx, executions, executionIds);
90
+ matrix.push(...tail.rows);
91
+
92
+ const passed = matrix.every(r => r.status === 'passed');
93
+ return {
94
+ status: passed ? 'passed' : 'failed',
95
+ datasourceKey,
96
+ correlationId: ctx.correlationId || null,
97
+ executionIds: tail.execIds,
98
+ matrix
99
+ };
100
+ }
101
+
102
+ module.exports = {
103
+ verifyAuditEvidenceMatrix,
104
+ buildExecutionsPollQuery,
105
+ resolveMinExpected,
106
+ checkRow8CorrelationGroup,
107
+ checkRow9Traces
108
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @fileoverview Batch upload loop for datasource load (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { bulkSyncRecords } = require('../api/records-bulk.api');
10
+ const { withTransientRetry, errorStatusCode } = require('./bulk-loader-service-retry');
11
+
12
+ /**
13
+ * @param {Object} state
14
+ * @param {number} index
15
+ * @param {Error} err
16
+ * @param {Object[]} batchRecords
17
+ */
18
+ function recordBatchFailure(state, index, err, batchRecords) {
19
+ state.batches.push({
20
+ index: index + 1,
21
+ total: state.chunkCount,
22
+ error: err.message || String(err),
23
+ statusCode: errorStatusCode(err),
24
+ recordCount: batchRecords.length,
25
+ failed: batchRecords.map(r => ({ key: r.key, message: err.message || String(err) }))
26
+ });
27
+ state.totals.failedCount += batchRecords.length;
28
+ }
29
+
30
+ /**
31
+ * @param {Object} state
32
+ * @param {number} index
33
+ * @param {Object} response
34
+ * @param {Object[]} batchRecords
35
+ */
36
+ function recordBatchSuccess(state, index, response, batchRecords) {
37
+ const inserted = response.insertedCount || 0;
38
+ const updated = response.updatedCount || 0;
39
+ const processed = response.totalProcessed || batchRecords.length;
40
+ const failedInBatch = Math.max(0, batchRecords.length - processed);
41
+
42
+ state.batches.push({
43
+ index: index + 1,
44
+ total: state.chunkCount,
45
+ insertedCount: inserted,
46
+ updatedCount: updated,
47
+ totalProcessed: processed,
48
+ failed: failedInBatch > 0 ? [{ message: 'Some records were not processed' }] : []
49
+ });
50
+ state.totals.insertedCount += inserted;
51
+ state.totals.updatedCount += updated;
52
+ state.totals.failedCount += failedInBatch;
53
+ }
54
+
55
+ /**
56
+ * @async
57
+ * @param {Object} params
58
+ * @returns {Promise<{ batches: Object[], totals: Object }>}
59
+ */
60
+ async function uploadRecordBatches(params) {
61
+ const { chunks, syncType, dataplaneUrl, datasourceKey, authConfig } = params;
62
+ const state = {
63
+ batches: [],
64
+ totals: { insertedCount: 0, updatedCount: 0, failedCount: 0 },
65
+ chunkCount: chunks.length
66
+ };
67
+
68
+ for (let i = 0; i < chunks.length; i += 1) {
69
+ const batchRecords = chunks[i];
70
+ const body = { syncType, sync: false, records: batchRecords };
71
+ try {
72
+ const response = await withTransientRetry(() =>
73
+ bulkSyncRecords(dataplaneUrl, datasourceKey, authConfig, body)
74
+ );
75
+ recordBatchSuccess(state, i, response, batchRecords);
76
+ } catch (err) {
77
+ recordBatchFailure(state, i, err, batchRecords);
78
+ }
79
+ }
80
+
81
+ return { batches: state.batches, totals: state.totals };
82
+ }
83
+
84
+ module.exports = {
85
+ uploadRecordBatches,
86
+ recordBatchFailure,
87
+ recordBatchSuccess
88
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Retry helpers for bulk loader (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const MAX_RETRIES = 3;
10
+ const RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
11
+
12
+ /**
13
+ * @param {Error} err
14
+ * @returns {number|null}
15
+ */
16
+ function errorStatusCode(err) {
17
+ if (!err || typeof err !== 'object') return null;
18
+ if (typeof err.statusCode === 'number') return err.statusCode;
19
+ if (err.response && typeof err.response.status === 'number') return err.response.status;
20
+ return null;
21
+ }
22
+
23
+ function sleep(ms) {
24
+ return new Promise(resolve => setTimeout(resolve, ms));
25
+ }
26
+
27
+ /**
28
+ * @async
29
+ * @param {Function} fn
30
+ * @returns {Promise<*>}
31
+ */
32
+ async function withTransientRetry(fn) {
33
+ let lastErr;
34
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {
35
+ try {
36
+ return await fn();
37
+ } catch (err) {
38
+ lastErr = err;
39
+ const code = errorStatusCode(err);
40
+ if (!code || !RETRY_STATUS.has(code) || attempt >= MAX_RETRIES - 1) {
41
+ throw err;
42
+ }
43
+ await sleep(500 * (attempt + 1));
44
+ }
45
+ }
46
+ throw lastErr;
47
+ }
48
+
49
+ module.exports = {
50
+ withTransientRetry,
51
+ errorStatusCode
52
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Load local fixtures via dataplane bulk record sync (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { resolveDataFilePath } = require('./local-data-paths');
10
+ const { parseRecordFile, estimatePayloadBytes } = require('./record-file-parser');
11
+ const { normalizeRecordsForBulk } = require('./record-mapper');
12
+ const { resolveLoadExportContext } = require('./datasource-load-export-context');
13
+ const { uploadRecordBatches } = require('./bulk-loader-run-batches');
14
+ const { runWithDatasourceLoadExportSpinner } = require('../utils/datasource-load-export-ui');
15
+
16
+ const DEFAULT_BATCH_SIZE = 100;
17
+
18
+ /**
19
+ * @param {Object[]} records
20
+ * @param {number} batchSize
21
+ * @returns {Object[][]}
22
+ */
23
+ function chunkRecords(records, batchSize) {
24
+ const size = Math.max(1, batchSize);
25
+ const chunks = [];
26
+ for (let i = 0; i < records.length; i += size) {
27
+ chunks.push(records.slice(i, i + size));
28
+ }
29
+ return chunks;
30
+ }
31
+
32
+ /**
33
+ * @param {Object} params
34
+ * @returns {Object}
35
+ */
36
+ function buildLoadResultBase(params) {
37
+ const { ctx, filePath, format, records, batchSize, syncType, dryRun } = params;
38
+ return {
39
+ datasourceKey: ctx.datasourceKey,
40
+ systemKey: ctx.systemKey,
41
+ appKey: ctx.appKey,
42
+ dryRun,
43
+ file: filePath,
44
+ format,
45
+ recordCount: records.length,
46
+ batchSize,
47
+ syncType,
48
+ estimatedPayloadBytes: estimatePayloadBytes(records),
49
+ batches: [],
50
+ totals: { insertedCount: 0, updatedCount: 0, failedCount: 0 }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * @async
56
+ * @param {Object} params
57
+ * @returns {Promise<Object>}
58
+ */
59
+ async function executeDatasourceLoadWork(params) {
60
+ const { ctx, options, batchSize, syncType, dryRun } = params;
61
+ const { filePath, format } = resolveDataFilePath({
62
+ systemKey: ctx.systemKey,
63
+ entitySuffix: ctx.entitySuffix,
64
+ file: options.file,
65
+ format: options.format
66
+ });
67
+
68
+ const raw = parseRecordFile(filePath, format);
69
+ const records = normalizeRecordsForBulk(raw, ctx.datasource);
70
+ const result = buildLoadResultBase({
71
+ ctx,
72
+ filePath,
73
+ format,
74
+ records,
75
+ batchSize,
76
+ syncType,
77
+ dryRun
78
+ });
79
+
80
+ if (dryRun) {
81
+ return { ...result, context: ctx };
82
+ }
83
+
84
+ const { batches, totals } = await uploadRecordBatches({
85
+ chunks: chunkRecords(records, batchSize),
86
+ syncType,
87
+ dataplaneUrl: ctx.dataplaneUrl,
88
+ datasourceKey: ctx.datasourceKey,
89
+ authConfig: ctx.authConfig
90
+ });
91
+
92
+ return { ...result, batches, totals, context: ctx };
93
+ }
94
+
95
+ /**
96
+ * @async
97
+ * @param {string} datasourceKey
98
+ * @param {Object} options
99
+ * @returns {Promise<Object>}
100
+ */
101
+ async function runDatasourceLoad(datasourceKey, options = {}) {
102
+ const ctx = await resolveLoadExportContext(datasourceKey, options);
103
+ const batchSize = options.batchSize
104
+ ? parseInt(String(options.batchSize), 10)
105
+ : DEFAULT_BATCH_SIZE;
106
+ const syncType = options.syncType || 'incremental';
107
+ const dryRun = options.dryRun === true;
108
+ const startedAt = Date.now();
109
+
110
+ const payload = await runWithDatasourceLoadExportSpinner(
111
+ () => executeDatasourceLoadWork({ ctx, options, batchSize, syncType, dryRun }),
112
+ {
113
+ operation: dryRun ? 'load-dry-run' : 'load',
114
+ datasourceKey: ctx.datasourceKey,
115
+ json: options.json === true
116
+ }
117
+ );
118
+
119
+ return { ...payload, durationMs: Date.now() - startedAt };
120
+ }
121
+
122
+ module.exports = {
123
+ runDatasourceLoad,
124
+ chunkRecords,
125
+ buildLoadResultBase,
126
+ DEFAULT_BATCH_SIZE
127
+ };
@@ -43,7 +43,8 @@ async function maybeLoadDimensionCatalogKeys() {
43
43
  page: 1,
44
44
  pageSize: 500
45
45
  });
46
- const items = res?.data?.items || res?.data?.data?.items || res?.items || [];
46
+ const raw = res?.data?.items ?? res?.data?.data ?? res?.items;
47
+ const items = Array.isArray(raw) ? raw : raw?.items || [];
47
48
  const keys = new Set(
48
49
  Array.isArray(items) ? items.map((d) => String(d?.key || '').trim()).filter(Boolean) : []
49
50
  );
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Export path and search body helpers (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { resolveDefaultDataOutputPath, inferFormatFromPath } = require('./local-data-paths');
11
+
12
+ /**
13
+ * @param {string|undefined} raw
14
+ * @returns {Object|undefined}
15
+ */
16
+ function parseFilterJson(raw) {
17
+ if (!raw || typeof raw !== 'string' || !raw.trim()) {
18
+ return undefined;
19
+ }
20
+ try {
21
+ const parsed = JSON.parse(raw);
22
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
23
+ return parsed;
24
+ }
25
+ throw new Error('filter must be a JSON object');
26
+ } catch (err) {
27
+ throw new Error(`Invalid --filter JSON: ${err.message}`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param {string|undefined} raw
33
+ * @returns {string[]|undefined}
34
+ */
35
+ function parseFieldsCsv(raw) {
36
+ if (!raw || typeof raw !== 'string' || !raw.trim()) {
37
+ return undefined;
38
+ }
39
+ return raw
40
+ .split(',')
41
+ .map(s => s.trim())
42
+ .filter(Boolean);
43
+ }
44
+
45
+ /**
46
+ * @param {Object} opts
47
+ * @returns {{ outputPath: string, format: 'json'|'ndjson', limit: number, intent: string, filters: Object|undefined, fields: string[]|undefined }}
48
+ */
49
+ function resolveExportOptions(opts) {
50
+ let limit = opts.limit ? parseInt(String(opts.limit), 10) : 1000;
51
+ if (Number.isNaN(limit) || limit < 1) {
52
+ limit = 1000;
53
+ }
54
+ limit = Math.min(limit, 10000);
55
+
56
+ const intent = opts.intent || 'validation';
57
+ const filters = parseFilterJson(opts.filter);
58
+ const fields = parseFieldsCsv(opts.fields);
59
+
60
+ let outputPath;
61
+ let format = opts.format;
62
+ if (opts.file && String(opts.file).trim()) {
63
+ outputPath = path.resolve(String(opts.file).trim());
64
+ format = format || inferFormatFromPath(outputPath);
65
+ } else {
66
+ const resolved = resolveDefaultDataOutputPath({
67
+ systemKey: opts.systemKey,
68
+ entitySuffix: opts.entitySuffix,
69
+ format: format || 'json'
70
+ });
71
+ outputPath = resolved.filePath;
72
+ format = opts.format || resolved.format;
73
+ }
74
+
75
+ return { outputPath, format, limit, intent, filters, fields };
76
+ }
77
+
78
+ module.exports = {
79
+ parseFilterJson,
80
+ parseFieldsCsv,
81
+ resolveExportOptions
82
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @fileoverview Export governed records via Records Search (plan 144)
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 { searchRecords } = require('../api/records-search.api');
12
+ const { resolveLoadExportContext } = require('./datasource-load-export-context');
13
+ const { resolveExportOptions } = require('./datasource-exporter-resolve');
14
+ const { runWithDatasourceLoadExportSpinner } = require('../utils/datasource-load-export-ui');
15
+
16
+ const DEFAULT_LIMIT = 1000;
17
+ const MAX_LIMIT = 10000;
18
+
19
+ /**
20
+ * @param {Object[]} data
21
+ * @param {string[]|undefined} fields
22
+ * @returns {Object[]}
23
+ */
24
+ function projectExportRows(data, fields) {
25
+ if (!fields || !fields.length) {
26
+ return data;
27
+ }
28
+ return data.map(row => {
29
+ if (!row || typeof row !== 'object') {
30
+ return row;
31
+ }
32
+ const meta =
33
+ row.metadata && typeof row.metadata === 'object' && !Array.isArray(row.metadata)
34
+ ? row.metadata
35
+ : row;
36
+ const out = {};
37
+ fields.forEach(field => {
38
+ if (Object.prototype.hasOwnProperty.call(meta, field)) {
39
+ out[field] = meta[field];
40
+ }
41
+ });
42
+ return out;
43
+ });
44
+ }
45
+
46
+ /**
47
+ * @param {string} filePath
48
+ * @param {'json'|'ndjson'} format
49
+ * @param {Object[]} rows
50
+ */
51
+ function writeExportFile(filePath, format, rows) {
52
+ const dir = path.dirname(filePath);
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ if (format === 'ndjson') {
55
+ const body = rows.map(row => JSON.stringify(row)).join('\n');
56
+ fs.writeFileSync(filePath, body.length ? `${body}\n` : '', 'utf8');
57
+ return;
58
+ }
59
+ fs.writeFileSync(filePath, `${JSON.stringify(rows, null, 2)}\n`, 'utf8');
60
+ }
61
+
62
+ /**
63
+ * @async
64
+ * @param {Object} ctx
65
+ * @param {Object} resolved
66
+ * @returns {Promise<Object>}
67
+ */
68
+ async function executeDatasourceExportWork(ctx, resolved) {
69
+ const searchBody = {
70
+ intent: resolved.intent,
71
+ datasourceKeys: [ctx.datasourceKey],
72
+ searchMode: 'full',
73
+ limit: resolved.limit
74
+ };
75
+ if (resolved.filters) {
76
+ searchBody.filters = resolved.filters;
77
+ }
78
+
79
+ const response = await searchRecords(ctx.dataplaneUrl, ctx.authConfig, searchBody);
80
+ if (!response || response.success === false) {
81
+ const msg = response?.formattedError || response?.error || 'Records search request failed';
82
+ throw new Error(msg);
83
+ }
84
+
85
+ const data = Array.isArray(response.data) ? response.data : [];
86
+ const projected = projectExportRows(data, resolved.fields);
87
+ writeExportFile(resolved.outputPath, resolved.format, projected);
88
+
89
+ const meta = response.meta && typeof response.meta === 'object' ? response.meta : {};
90
+ const excluded = meta.excluded && typeof meta.excluded === 'object' ? meta.excluded : {};
91
+
92
+ return {
93
+ datasourceKey: ctx.datasourceKey,
94
+ systemKey: ctx.systemKey,
95
+ appKey: ctx.appKey,
96
+ outputFile: resolved.outputPath,
97
+ format: resolved.format,
98
+ recordCount: projected.length,
99
+ limit: resolved.limit,
100
+ intent: resolved.intent,
101
+ filters: resolved.filters || null,
102
+ fields: resolved.fields || null,
103
+ meta: { excluded, auditRef: meta.auditRef },
104
+ excludedAbac: Number(excluded.abac) || 0,
105
+ excludedFilter: Number(excluded.filter) || 0,
106
+ hitLimitCap: projected.length >= resolved.limit,
107
+ context: ctx
108
+ };
109
+ }
110
+
111
+ /**
112
+ * @async
113
+ * @param {string} datasourceKey
114
+ * @param {Object} options
115
+ * @returns {Promise<Object>}
116
+ */
117
+ async function runDatasourceExport(datasourceKey, options = {}) {
118
+ const ctx = await resolveLoadExportContext(datasourceKey, options);
119
+ const resolved = resolveExportOptions({
120
+ ...options,
121
+ systemKey: ctx.systemKey,
122
+ entitySuffix: ctx.entitySuffix
123
+ });
124
+ const startedAt = Date.now();
125
+
126
+ const payload = await runWithDatasourceLoadExportSpinner(
127
+ () => executeDatasourceExportWork(ctx, resolved),
128
+ {
129
+ operation: 'export',
130
+ datasourceKey: ctx.datasourceKey,
131
+ json: options.json === true
132
+ }
133
+ );
134
+
135
+ return { ...payload, durationMs: Date.now() - startedAt };
136
+ }
137
+
138
+ module.exports = {
139
+ runDatasourceExport,
140
+ projectExportRows,
141
+ writeExportFile,
142
+ DEFAULT_LIMIT,
143
+ MAX_LIMIT
144
+ };