@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
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Builder CLI ↔ dataplane version gate (plan 142.0).
3
+ *
4
+ * Resolves the dataplane-advertised `minBuilderCliVersion` (from cache or a
5
+ * fresh health probe) and compares it against the installed Builder CLI
6
+ * version. Used at the top of dataplane API entrypoints so an outdated CLI
7
+ * cannot mutate dataplane state silently.
8
+ *
9
+ * Local-only commands (validate, run, up-infra, file generation) do **not**
10
+ * call into this module; the gate is intentionally per-call, not global.
11
+ *
12
+ * @fileoverview Compatibility gate for dataplane API entrypoints
13
+ * @author AI Fabrix Team
14
+ * @version 2.0.0
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
20
+ const {
21
+ getDeviceDataplaneVersions,
22
+ updateDeviceDataplaneVersions,
23
+ isCacheStale
24
+ } = require('../core/config-device-dataplane');
25
+ const { compareSemver, isValidSemver } = require('./semver-compare');
26
+ const { formatCliVersionGateError } = require('./dataplane-cli-version-help');
27
+
28
+ /**
29
+ * Get the installed Builder CLI version from `package.json`.
30
+ * Centralized so tests can stub it via Jest module mocks.
31
+ * @returns {string}
32
+ */
33
+ function getInstalledCliVersion() {
34
+ return require('../../package.json').version;
35
+ }
36
+
37
+ /**
38
+ * Internal sentinel error code so callers can distinguish gate failures from
39
+ * generic dataplane errors when needed.
40
+ * @type {string}
41
+ */
42
+ const ERROR_CODE = 'CLI_VERSION_INCOMPATIBLE';
43
+
44
+ /**
45
+ * True when `err` was thrown by this gate.
46
+ * @param {*} err
47
+ * @returns {boolean}
48
+ */
49
+ function isCliVersionIncompatibleError(err) {
50
+ return Boolean(err && err.code === ERROR_CODE);
51
+ }
52
+
53
+ /**
54
+ * Resolve the dataplane minimum Builder CLI version, preferring cached value
55
+ * when fresh; otherwise probe health and persist a new snapshot under
56
+ * `device.<controllerUrl>`.
57
+ *
58
+ * @private
59
+ * @async
60
+ * @param {string} controllerUrl
61
+ * @param {string} dataplaneUrl
62
+ * @param {{ forceRefresh?: boolean }} [options]
63
+ * @returns {Promise<{ minBuilderCliVersion?: string, dataplaneVersion?: string }>}
64
+ */
65
+ async function resolveMinBuilderCliVersion(controllerUrl, dataplaneUrl, options = {}) {
66
+ const forceRefresh = Boolean(options.forceRefresh);
67
+ if (!forceRefresh) {
68
+ const cached = await safeReadCache(controllerUrl);
69
+ if (cached && !isCacheStale(cached)) {
70
+ return {
71
+ minBuilderCliVersion: cached.minBuilderCliVersion,
72
+ dataplaneVersion: cached.version
73
+ };
74
+ }
75
+ }
76
+
77
+ const snapshot = await safeFetch(dataplaneUrl);
78
+ if (!snapshot) {
79
+ // Cannot reach dataplane → do not gate (offline / pre-rollout). Reuse
80
+ // any cached value we may have.
81
+ const cached = await safeReadCache(controllerUrl);
82
+ return {
83
+ minBuilderCliVersion: cached ? cached.minBuilderCliVersion : undefined,
84
+ dataplaneVersion: cached ? cached.version : undefined
85
+ };
86
+ }
87
+
88
+ await safeWriteCache(controllerUrl, {
89
+ version: snapshot.version,
90
+ minBuilderCliVersion: snapshot.minBuilderCliVersion || null
91
+ });
92
+ return {
93
+ minBuilderCliVersion: snapshot.minBuilderCliVersion,
94
+ dataplaneVersion: snapshot.version
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Read cached versions without throwing — gate failures should not mask real
100
+ * dataplane errors caused by config corruption.
101
+ * @private
102
+ * @async
103
+ * @param {string} controllerUrl
104
+ * @returns {Promise<Object|null>}
105
+ */
106
+ async function safeReadCache(controllerUrl) {
107
+ if (!controllerUrl) return null;
108
+ try {
109
+ return await getDeviceDataplaneVersions(controllerUrl);
110
+ } catch (_error) {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Fetch health swallowing transport errors so callers can decide what to do
117
+ * when the dataplane is unreachable.
118
+ * @private
119
+ * @async
120
+ * @param {string} dataplaneUrl
121
+ * @returns {Promise<Object|null>}
122
+ */
123
+ async function safeFetch(dataplaneUrl) {
124
+ try {
125
+ return await fetchDataplaneGeneralHealth(dataplaneUrl);
126
+ } catch (_error) {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Write the cache without throwing — best-effort persistence.
133
+ * @private
134
+ * @async
135
+ * @param {string} controllerUrl
136
+ * @param {Object} versions
137
+ * @returns {Promise<void>}
138
+ */
139
+ async function safeWriteCache(controllerUrl, versions) {
140
+ if (!controllerUrl) return;
141
+ try {
142
+ await updateDeviceDataplaneVersions(controllerUrl, versions);
143
+ } catch (_error) {
144
+ // intentionally swallowed — cache write failure must not block the call
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Throw when the installed CLI is older than the dataplane minimum.
150
+ *
151
+ * No-op when:
152
+ * - `dataplaneUrl` is empty (no dataplane in play)
153
+ * - Dataplane omits `minBuilderCliVersion` (no enforcement)
154
+ * - Dataplane is unreachable and no cached minimum is known
155
+ * - Cached/advertised minimum is not valid semver (warn-only contract per plan)
156
+ *
157
+ * @async
158
+ * @param {string} dataplaneUrl - Dataplane base URL (e.g. `http://localhost:3201`)
159
+ * @param {Object} [options]
160
+ * @param {string} [options.controllerUrl] - Controller URL for cache lookup/write
161
+ * @param {boolean} [options.forceRefresh] - Skip cache and probe health now
162
+ * @returns {Promise<void>}
163
+ * @throws {Error & { code: 'CLI_VERSION_INCOMPATIBLE', required: string, installed: string, formatted: string }}
164
+ */
165
+ async function assertDataplaneCliVersionCompatible(dataplaneUrl, options = {}) {
166
+ if (!dataplaneUrl || typeof dataplaneUrl !== 'string') {
167
+ return;
168
+ }
169
+ const { minBuilderCliVersion } = await resolveMinBuilderCliVersion(
170
+ options.controllerUrl || null,
171
+ dataplaneUrl,
172
+ { forceRefresh: Boolean(options.forceRefresh) }
173
+ );
174
+ if (!minBuilderCliVersion) return;
175
+ if (!isValidSemver(minBuilderCliVersion)) return;
176
+
177
+ // Go through module.exports so tests can stub `getInstalledCliVersion`.
178
+ const installed = module.exports.getInstalledCliVersion();
179
+ if (!isValidSemver(installed)) return;
180
+
181
+ if (compareSemver(installed, minBuilderCliVersion) >= 0) {
182
+ return;
183
+ }
184
+
185
+ const formatted = formatCliVersionGateError(minBuilderCliVersion, installed);
186
+ const error = new Error(
187
+ `Builder CLI ${installed} is below the dataplane minimum (${minBuilderCliVersion}). ` +
188
+ 'Upgrade with: npm install -g @aifabrix/builder@latest'
189
+ );
190
+ error.code = ERROR_CODE;
191
+ error.required = minBuilderCliVersion;
192
+ error.installed = installed;
193
+ error.formatted = formatted;
194
+ throw error;
195
+ }
196
+
197
+ module.exports = {
198
+ assertDataplaneCliVersionCompatible,
199
+ isCliVersionIncompatibleError,
200
+ getInstalledCliVersion,
201
+ ERROR_CODE
202
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared "Next actions" + blocking error formatter for the Builder CLI ↔
3
+ * dataplane version gate (plan 142.0). Used by `auth status` display and by
4
+ * the gate thrown from dataplane API entrypoints so users see the same bullets
5
+ * wherever the incompatibility surfaces.
6
+ *
7
+ * @fileoverview Shared CLI version-gate UX helpers (next actions + blocking)
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const {
15
+ formatBlockingError,
16
+ formatNextActions
17
+ } = require('./cli-test-layout-chalk');
18
+
19
+ /**
20
+ * Build the canonical Next actions bullets for a CLI ↔ dataplane mismatch.
21
+ *
22
+ * @param {string} required - Minimum Builder CLI semver advertised by dataplane
23
+ * @param {string} installed - Current Builder CLI semver (from package.json)
24
+ * @returns {string[]} Bullet lines (no prefix)
25
+ */
26
+ function buildCliVersionUpgradeNextActions(required, installed) {
27
+ const req = (required || '').trim() || 'the dataplane minimum';
28
+ const inst = (installed || '').trim() || 'this CLI';
29
+ return [
30
+ `Upgrade Builder CLI: npm install -g @aifabrix/builder@${req} (or @latest)`,
31
+ 'Confirm after upgrade: aifabrix auth status',
32
+ 'CI / scripts: aifabrix auth status --validate (exit 3 until upgraded)',
33
+ `Local work (validate, run, up-infra) still works on ${inst}`
34
+ ];
35
+ }
36
+
37
+ /**
38
+ * Build the blocking line shown before the Next actions block when the CLI is
39
+ * older than the dataplane minimum.
40
+ *
41
+ * @param {string} required - Required minimum semver
42
+ * @param {string} installed - Currently installed semver
43
+ * @returns {string}
44
+ */
45
+ function buildCliVersionBlockingLine(required, installed) {
46
+ const req = (required || '').trim() || '(unknown)';
47
+ const inst = (installed || '').trim() || '(unknown)';
48
+ return formatBlockingError(
49
+ `Builder CLI ${inst} is below the dataplane minimum (${req}). Upgrade before running dataplane commands.`
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Format the full version-gate error block (blocking line + Next actions).
55
+ * Suitable for `handleCommandError` output and the `auth status` incompatible
56
+ * branch.
57
+ *
58
+ * @param {string} required - Required minimum semver
59
+ * @param {string} installed - Currently installed semver
60
+ * @returns {string} Multi-line, chalk-colored block
61
+ */
62
+ function formatCliVersionGateError(required, installed) {
63
+ return [
64
+ buildCliVersionBlockingLine(required, installed),
65
+ '',
66
+ formatNextActions(buildCliVersionUpgradeNextActions(required, installed))
67
+ ].join('\n');
68
+ }
69
+
70
+ module.exports = {
71
+ buildCliVersionUpgradeNextActions,
72
+ buildCliVersionBlockingLine,
73
+ formatCliVersionGateError
74
+ };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @fileoverview TTY display for `datasource export` (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 logger = require('./logger');
11
+ const {
12
+ sectionTitle,
13
+ headerKeyValue,
14
+ metadata: metaGray,
15
+ formatSuccessLine,
16
+ formatWarningLine
17
+ } = require('./cli-test-layout-chalk');
18
+ const { emitManifestSourceMetadata } = require('./manifest-source-emit');
19
+ const { integrationManifestLabel } = require('../datasource/datasource-load-export-context');
20
+ const { formatElapsedMs } = require('./datasource-load-export-ui');
21
+
22
+ const SEP = '────────────────────────────────────────────────────────';
23
+
24
+ function logExportHeader(ctx, envLabel) {
25
+ logger.log(SEP);
26
+ logger.log(sectionTitle(`Export records — ${ctx.datasourceKey}`));
27
+ logger.log(headerKeyValue('Environment:', envLabel || '—'));
28
+ logger.log(headerKeyValue('System:', ctx.systemKey));
29
+ logger.log(SEP);
30
+ logger.log(metaGray(' Layer: governed records search (ABAC applied)'));
31
+ logger.log(metaGray(' Not used: direct database access'));
32
+ logger.log('');
33
+ }
34
+
35
+ function logExportVerboseStats(result) {
36
+ const excluded = result.meta?.excluded || {};
37
+ const abac = excluded.abac !== undefined && excluded.abac !== null ? excluded.abac : 0;
38
+ const filterEx =
39
+ excluded.filter !== undefined && excluded.filter !== null ? excluded.filter : 0;
40
+ logger.log(
41
+ metaGray(
42
+ ` Fetched: ${result.recordCount} records · excluded abac ${abac} · filter ${filterEx}`
43
+ )
44
+ );
45
+ const stat = fs.statSync(result.outputFile);
46
+ const mb = (stat.size / (1024 * 1024)).toFixed(2);
47
+ logger.log(
48
+ metaGray(` Wrote: ${result.recordCount} line(s) (${result.format}) · ${mb} MB`)
49
+ );
50
+ logger.log('');
51
+ }
52
+
53
+ /**
54
+ * @param {Object} result
55
+ */
56
+ function displayExportAbacZeroRowsWarning(result) {
57
+ const excludedAbac = Number(result.excludedAbac ?? result.meta?.excluded?.abac) || 0;
58
+ if (result.recordCount !== 0 || excludedAbac <= 0) {
59
+ return;
60
+ }
61
+ logger.log(
62
+ formatWarningLine(
63
+ `Governed search returned 0 rows; ABAC excluded ${excludedAbac} record(s) for your principal.`
64
+ )
65
+ );
66
+ logger.log(
67
+ metaGray(
68
+ ' The physical table may still hold data — export only includes rows your token may read.'
69
+ )
70
+ );
71
+ logger.log(
72
+ metaGray(
73
+ ' Fix: upload a protection manifest (aifabrix protection upload <datasourceKey>) with dimension grants for your user, then retry.'
74
+ )
75
+ );
76
+ logger.log('');
77
+ }
78
+
79
+ /**
80
+ * @param {Object} result
81
+ */
82
+ function displayExportLimitCapWarning(result) {
83
+ if (!result.hitLimitCap) {
84
+ return;
85
+ }
86
+ logger.log(
87
+ formatWarningLine(`Fetched ${result.recordCount} records (limit cap). More rows may exist.`)
88
+ );
89
+ logger.log(metaGray(' Re-run with --filter or wait for dataplane search pagination.'));
90
+ logger.log('');
91
+ }
92
+
93
+ /**
94
+ * @param {Object} result
95
+ */
96
+ function logExportDurationFooter(result) {
97
+ if (result.durationMs === undefined || result.durationMs === null) {
98
+ return;
99
+ }
100
+ logger.log(metaGray(` Completed in ${formatElapsedMs(result.durationMs)}`));
101
+ logger.log('');
102
+ }
103
+
104
+ /**
105
+ * @param {Object} result
106
+ * @param {Object} opts
107
+ */
108
+ function displayDatasourceExportTTY(result, opts = {}) {
109
+ const ctx = result.context || {};
110
+ logExportHeader(ctx, opts.environment);
111
+
112
+ const filterLabel = result.filters ? 'filters (set)' : 'filters (none)';
113
+ logger.log(
114
+ metaGray(` Search: intent ${result.intent} · limit ${result.limit} · ${filterLabel}`)
115
+ );
116
+ emitManifestSourceMetadata({
117
+ tier: 'int',
118
+ configPath: ctx.manifestPath,
119
+ labelPrefix: integrationManifestLabel(ctx.appKey)
120
+ });
121
+ logger.log(metaGray(` Output: ${result.outputFile}`));
122
+ logger.log('');
123
+
124
+ if (opts.verbose) {
125
+ logExportVerboseStats(result);
126
+ }
127
+
128
+ displayExportAbacZeroRowsWarning(result);
129
+ displayExportLimitCapWarning(result);
130
+ logger.log(formatSuccessLine('Export complete'));
131
+ logExportDurationFooter(result);
132
+ logger.log(SEP);
133
+ }
134
+
135
+ module.exports = {
136
+ displayDatasourceExportTTY
137
+ };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @fileoverview TTY display for `datasource load` (plan 144)
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('./logger');
11
+ const {
12
+ sectionTitle,
13
+ headerKeyValue,
14
+ metadata: metaGray,
15
+ formatSuccessLine,
16
+ formatBlockingError,
17
+ successGlyph,
18
+ failureGlyph
19
+ } = require('./cli-test-layout-chalk');
20
+ const { emitManifestSourceMetadata } = require('./manifest-source-emit');
21
+ const { integrationManifestLabel } = require('../datasource/datasource-load-export-context');
22
+ const { formatElapsedMs } = require('./datasource-load-export-ui');
23
+
24
+ const SEP = '────────────────────────────────────────────────────────';
25
+
26
+ function logLoadHeader(ctx, envLabel) {
27
+ logger.log(SEP);
28
+ logger.log(sectionTitle(`Load records — ${ctx.datasourceKey}`));
29
+ logger.log(headerKeyValue('Environment:', envLabel || '—'));
30
+ logger.log(headerKeyValue('System:', ctx.systemKey));
31
+ logger.log(SEP);
32
+ logger.log(metaGray(' Layer: dataplane bulk sync'));
33
+ emitManifestSourceMetadata({
34
+ tier: 'int',
35
+ configPath: ctx.manifestPath,
36
+ labelPrefix: integrationManifestLabel(ctx.appKey)
37
+ });
38
+ }
39
+
40
+ function displayLoadDryRun(result) {
41
+ logger.log(chalk.cyan(' Mode: dry-run (no upload)'));
42
+ logger.log('');
43
+ const mb = (result.estimatedPayloadBytes / (1024 * 1024)).toFixed(2);
44
+ const batchCount = Math.ceil(result.recordCount / result.batchSize) || 0;
45
+ logger.log(
46
+ metaGray(
47
+ ` Parsed: ${result.recordCount} records · format ${result.format} · est. payload ~${mb} MB`
48
+ )
49
+ );
50
+ logger.log(
51
+ metaGray(` Would upload: ${batchCount} batch(es) · syncType ${result.syncType}`)
52
+ );
53
+ logger.log('');
54
+ logger.log(formatSuccessLine('Dry-run passed'));
55
+ }
56
+
57
+ function displayLoadBatchesVerbose(batches) {
58
+ batches.forEach(batch => {
59
+ if (batch.error) {
60
+ logger.log(
61
+ ` Batch ${batch.index}/${batch.total} ${failureGlyph()} failed (${batch.error})`
62
+ );
63
+ return;
64
+ }
65
+ const failed = Array.isArray(batch.failed) ? batch.failed.length : 0;
66
+ if (failed > 0) {
67
+ logger.log(
68
+ ` Batch ${batch.index}/${batch.total} ${failureGlyph()} failed ${failed} of ${batch.recordCount || '?'}`
69
+ );
70
+ return;
71
+ }
72
+ logger.log(
73
+ ` Batch ${batch.index}/${batch.total} ${successGlyph()} inserted ${batch.insertedCount || 0} · updated ${batch.updatedCount || 0} · failed 0`
74
+ );
75
+ });
76
+ }
77
+
78
+ const MAX_FAILURE_LINES = 5;
79
+
80
+ /**
81
+ * @param {Object[]} batches
82
+ */
83
+ function displayLoadFailureSamples(batches) {
84
+ const samples = [];
85
+ (batches || []).forEach(batch => {
86
+ if (!Array.isArray(batch.failed)) return;
87
+ batch.failed.forEach(item => {
88
+ if (samples.length >= MAX_FAILURE_LINES) return;
89
+ const key = item.key ? String(item.key) : '—';
90
+ const message = item.message || batch.error || 'failed';
91
+ samples.push({ key, message });
92
+ });
93
+ });
94
+ if (samples.length === 0) return;
95
+ logger.log('');
96
+ logger.log(chalk.white(` Failures (first ${MAX_FAILURE_LINES})`));
97
+ samples.forEach(({ key, message }) => {
98
+ logger.log(` • ${key} — ${message}`);
99
+ });
100
+ }
101
+
102
+ function displayLoadVerdict(result) {
103
+ const failed = result.totals.failedCount || 0;
104
+ const ok = result.recordCount - failed;
105
+ if (failed > 0) {
106
+ if (result.batches && result.batches.length > 0) {
107
+ displayLoadFailureSamples(result.batches);
108
+ logger.log('');
109
+ }
110
+ logger.log(formatBlockingError(`${ok} ok · ${failed} failed`));
111
+ } else {
112
+ logger.log(
113
+ formatSuccessLine(
114
+ `${result.recordCount} processed · ${result.totals.insertedCount} inserted · ${result.totals.updatedCount} updated · 0 failed`
115
+ )
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @param {Object} result
122
+ */
123
+ function logLoadDurationFooter(result) {
124
+ if (result.durationMs === undefined || result.durationMs === null) {
125
+ return;
126
+ }
127
+ logger.log(metaGray(` Completed in ${formatElapsedMs(result.durationMs)}`));
128
+ logger.log('');
129
+ }
130
+
131
+ /**
132
+ * @param {Object} result
133
+ * @param {Object} opts
134
+ */
135
+ function displayLoadUploadSummary(result, opts) {
136
+ const batchCount = Math.ceil(result.recordCount / result.batchSize) || 0;
137
+ logger.log(
138
+ metaGray(
139
+ ` Parsed: ${result.recordCount} records · format ${result.format} · est. ${batchCount} batch(es) @ ${result.batchSize}`
140
+ )
141
+ );
142
+ logger.log('');
143
+ if (opts.verbose) {
144
+ displayLoadBatchesVerbose(result.batches);
145
+ logger.log('');
146
+ }
147
+ displayLoadVerdict(result);
148
+ }
149
+
150
+ /**
151
+ * @param {Object} result
152
+ * @param {Object} opts
153
+ */
154
+ function displayDatasourceLoadTTY(result, opts = {}) {
155
+ const ctx = result.context || {};
156
+ logLoadHeader(ctx, opts.environment);
157
+ logger.log(metaGray(` Data file: ${result.file}`));
158
+ logger.log('');
159
+
160
+ if (result.dryRun) {
161
+ displayLoadDryRun(result);
162
+ logLoadDurationFooter(result);
163
+ logger.log(SEP);
164
+ return;
165
+ }
166
+
167
+ displayLoadUploadSummary(result, opts);
168
+ logLoadDurationFooter(result);
169
+ logger.log(SEP);
170
+ }
171
+
172
+ module.exports = {
173
+ displayDatasourceLoadTTY
174
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @fileoverview Spinner + elapsed time for datasource load/export (plan 144)
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('./logger');
11
+ const { formatProgress } = require('./cli-test-layout-chalk');
12
+
13
+ /**
14
+ * @param {number} ms
15
+ * @returns {string}
16
+ */
17
+ function formatElapsedMs(ms) {
18
+ const n = Math.max(0, Math.round(Number(ms) || 0));
19
+ if (n < 1000) {
20
+ return `${n} ms`;
21
+ }
22
+ if (n < 60000) {
23
+ const sec = n / 1000;
24
+ return sec >= 10 ? `${Math.round(sec)} s` : `${sec.toFixed(1)} s`;
25
+ }
26
+ const min = Math.floor(n / 60000);
27
+ const sec = Math.round((n % 60000) / 1000);
28
+ return sec > 0 ? `${min} m ${sec} s` : `${min} m`;
29
+ }
30
+
31
+ /**
32
+ * @param {Object} [options]
33
+ * @param {boolean} [options.json]
34
+ * @returns {boolean}
35
+ */
36
+ function shouldShowDatasourceLoadExportProgress(options = {}) {
37
+ return options.json !== true;
38
+ }
39
+
40
+ /**
41
+ * @param {Object} [options]
42
+ * @returns {boolean}
43
+ */
44
+ function shouldUseDatasourceLoadExportSpinner(options = {}) {
45
+ if (!shouldShowDatasourceLoadExportProgress(options)) {
46
+ return false;
47
+ }
48
+ return Boolean(process && process.stdout && process.stdout.isTTY);
49
+ }
50
+
51
+ /**
52
+ * @param {string} operation
53
+ * @param {string} datasourceKey
54
+ * @returns {string}
55
+ */
56
+ function buildDatasourceLoadExportSpinnerLabel(operation, datasourceKey) {
57
+ const key =
58
+ datasourceKey && String(datasourceKey).trim() ? String(datasourceKey).trim() : 'datasource';
59
+ if (operation === 'export') {
60
+ return `Exporting records for ${key}…`;
61
+ }
62
+ if (operation === 'load-dry-run') {
63
+ return `Parsing records for ${key}…`;
64
+ }
65
+ return `Loading records for ${key} into dataplane…`;
66
+ }
67
+
68
+ /**
69
+ * @param {string} operation
70
+ * @param {string} datasourceKey
71
+ * @returns {string}
72
+ */
73
+ function buildDatasourceLoadExportProgressText(operation, datasourceKey) {
74
+ return formatProgress(buildDatasourceLoadExportSpinnerLabel(operation, datasourceKey));
75
+ }
76
+
77
+ /**
78
+ * @async
79
+ * @param {Function} work
80
+ * @param {Object} [opts]
81
+ * @param {'load'|'load-dry-run'|'export'} [opts.operation]
82
+ * @param {string} [opts.datasourceKey]
83
+ * @param {boolean} [opts.json]
84
+ * @returns {Promise<*>}
85
+ */
86
+ async function runWithDatasourceLoadExportSpinner(work, opts = {}) {
87
+ if (!shouldShowDatasourceLoadExportProgress(opts)) {
88
+ return work();
89
+ }
90
+
91
+ const operation = opts.operation || 'load';
92
+ const datasourceKey = opts.datasourceKey || '';
93
+
94
+ if (!shouldUseDatasourceLoadExportSpinner(opts)) {
95
+ logger.log(buildDatasourceLoadExportProgressText(operation, datasourceKey));
96
+ return work();
97
+ }
98
+
99
+ const ora = require('ora');
100
+ const label = buildDatasourceLoadExportSpinnerLabel(operation, datasourceKey);
101
+ const spinner = ora({
102
+ text: chalk.white(label),
103
+ spinner: 'dots'
104
+ }).start();
105
+
106
+ try {
107
+ return await work();
108
+ } finally {
109
+ spinner.stop();
110
+ }
111
+ }
112
+
113
+ module.exports = {
114
+ formatElapsedMs,
115
+ shouldShowDatasourceLoadExportProgress,
116
+ shouldUseDatasourceLoadExportSpinner,
117
+ buildDatasourceLoadExportSpinnerLabel,
118
+ buildDatasourceLoadExportProgressText,
119
+ runWithDatasourceLoadExportSpinner
120
+ };