@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,134 @@
1
+ /**
2
+ * @fileoverview Helpers for `runProtectionCreate` (auth, path checks, YAML).
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 { resolveControllerUrl } = require('../utils/controller-url');
12
+ const { getConfig } = require('../core/config');
13
+ const { writeConfigFile, jsonToYaml } = require('../utils/config-format');
14
+ const { normalizeControllerUrl } = require('../core/config');
15
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
16
+ const { getProtectionRoot } = require('./paths');
17
+ const { assertNoDuplicateDatasourceKeys, listProtectionManifestPaths } = require('./resolve');
18
+ const { loadProtectionManifest } = require('./load');
19
+
20
+ /**
21
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
22
+ */
23
+ async function resolveControllerAuthForDimensions() {
24
+ const controllerUrl = await resolveControllerUrl();
25
+ if (!controllerUrl) {
26
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
27
+ }
28
+ const normalized = normalizeControllerUrl(controllerUrl);
29
+ const deviceToken = await getOrRefreshDeviceToken(normalized);
30
+ if (!deviceToken || !deviceToken.token) {
31
+ throw new Error(
32
+ `Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`
33
+ );
34
+ }
35
+ return {
36
+ controllerUrl: deviceToken.controller || normalized,
37
+ authConfig: { type: 'bearer', token: deviceToken.token }
38
+ };
39
+ }
40
+
41
+ /**
42
+ * @param {string} root
43
+ * @param {string} datasourceKey
44
+ * @param {string} outputPath
45
+ */
46
+ function assertNoOtherFileClaimsDatasource(root, datasourceKey, outputPath) {
47
+ const key = String(datasourceKey || '').trim();
48
+ const out = path.resolve(outputPath);
49
+ for (const filePath of listProtectionManifestPaths(root)) {
50
+ if (path.resolve(filePath) === out) {
51
+ continue;
52
+ }
53
+ try {
54
+ const m = loadProtectionManifest(filePath);
55
+ if (String(m?.spec?.datasourceKey || '').trim() === key) {
56
+ throw new Error(
57
+ `Another manifest already claims datasource "${key}": ${filePath}. Remove it or use a different key.`
58
+ );
59
+ }
60
+ } catch (e) {
61
+ if (e.message && e.message.includes('Another manifest')) {
62
+ throw e;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * @param {Object} manifest
70
+ * @returns {string}
71
+ */
72
+ function protectionManifestToYaml(manifest) {
73
+ return protectionManifestToDisplayText(manifest, 'yaml');
74
+ }
75
+
76
+ /**
77
+ * @returns {Promise<'yaml'|'json'>}
78
+ */
79
+ async function resolveProtectionOutputFormat() {
80
+ const config = await getConfig();
81
+ const raw = config && typeof config.format === 'string' ? config.format.trim().toLowerCase() : '';
82
+ return raw === 'json' ? 'json' : 'yaml';
83
+ }
84
+
85
+ /**
86
+ * @param {string} dsKey
87
+ * @param {boolean} force
88
+ * @returns {Promise<{ outputPath: string, format: 'yaml'|'json' }>}
89
+ */
90
+ async function resolveOutputPathOrThrow(dsKey, force) {
91
+ const root = getProtectionRoot();
92
+ assertNoDuplicateDatasourceKeys(root);
93
+ const format = await resolveProtectionOutputFormat();
94
+ const ext = format === 'json' ? '.json' : '.yaml';
95
+ const outputPath = path.join(root, `${dsKey}${ext}`);
96
+ assertNoOtherFileClaimsDatasource(root, dsKey, outputPath);
97
+ if (fs.existsSync(outputPath) && !force) {
98
+ throw new Error(
99
+ `File already exists: ${outputPath}. Use --force to overwrite, or remove the file.`
100
+ );
101
+ }
102
+ return { outputPath, format };
103
+ }
104
+
105
+ /**
106
+ * @param {string} outputPath
107
+ * @param {Object} manifest
108
+ * @param {'yaml'|'json'} format
109
+ */
110
+ function writeProtectionManifest(outputPath, manifest, format) {
111
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
112
+ writeConfigFile(outputPath, manifest, format);
113
+ }
114
+
115
+ /**
116
+ * @param {Object} manifest
117
+ * @returns {string}
118
+ */
119
+ function protectionManifestToDisplayText(manifest, format = 'yaml') {
120
+ if (format === 'json') {
121
+ return `${JSON.stringify(manifest, null, 2)}\n`;
122
+ }
123
+ return `${jsonToYaml(manifest).trim()}\n`;
124
+ }
125
+
126
+ module.exports = {
127
+ resolveControllerAuthForDimensions,
128
+ assertNoOtherFileClaimsDatasource,
129
+ protectionManifestToYaml,
130
+ protectionManifestToDisplayText,
131
+ resolveProtectionOutputFormat,
132
+ resolveOutputPathOrThrow,
133
+ writeProtectionManifest
134
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview Collect probe TTY lines for protection create.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const {
10
+ probeDatasourceForProtectionCreate,
11
+ probeDimensionForProtectionCreate
12
+ } = require('./protection-create-probes');
13
+
14
+ /**
15
+ * @param {Object} dpCtx
16
+ * @param {Object} dimCtx
17
+ * @param {string} dsKey
18
+ * @param {string} dimensionKey
19
+ * @param {boolean} verbose
20
+ * @returns {Promise<{lines: string[], datasource: Object, dimension: Object}>}
21
+ */
22
+ async function collectProtectionCreateProbeContext(dpCtx, dimCtx, dsKey, dimensionKey, verbose) {
23
+ const lines = [];
24
+ const dsProbe = await probeDatasourceForProtectionCreate(
25
+ dpCtx.dataplaneUrl,
26
+ dpCtx.authConfig,
27
+ dsKey,
28
+ { verbose }
29
+ );
30
+ lines.push(...dsProbe.lines);
31
+ const dimProbe = await probeDimensionForProtectionCreate(
32
+ dimCtx.controllerUrl,
33
+ dimCtx.authConfig,
34
+ dimensionKey,
35
+ { verbose }
36
+ );
37
+ lines.push(...dimProbe.lines);
38
+ return { lines, datasource: dsProbe.row, dimension: dimProbe.row };
39
+ }
40
+
41
+ module.exports = {
42
+ collectProtectionCreateProbeContext
43
+ };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @fileoverview `aifabrix protection create` — online probes, scaffold, write YAML.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const { resolveProtectionDataplaneContext } = require('./auth-context');
11
+ const { validateProtectionManifestLocal } = require('./validate-local');
12
+ const { buildPresetProtectionManifest } = require('./protection-create-scaffold');
13
+ const { requireProtectionPreset } = require('./protection-preset-registry');
14
+ const {
15
+ sectionTitle,
16
+ formatSuccessLine,
17
+ formatNextActions,
18
+ formatBlockingError,
19
+ metadata
20
+ } = require('../utils/cli-test-layout-chalk');
21
+ const {
22
+ resolveControllerAuthForDimensions,
23
+ protectionManifestToDisplayText,
24
+ resolveProtectionOutputFormat,
25
+ resolveOutputPathOrThrow,
26
+ writeProtectionManifest
27
+ } = require('./run-protection-create-helpers');
28
+ const { collectProtectionCreateProbeContext } = require('./run-protection-create-probes-flow');
29
+
30
+ const SEP = chalk.gray('────────────────────────────────────────');
31
+
32
+ /**
33
+ * @param {Object} opts
34
+ * @param {string} dsKey
35
+ * @param {string} dimensionKey
36
+ * @param {Object} logger
37
+ */
38
+ function logCreateHeader(opts, dsKey, dimensionKey, logger) {
39
+ logger.log(sectionTitle('Protection create'));
40
+ logger.log(SEP);
41
+ logger.log(metadata(`Environment: ${opts.environment}`));
42
+ logger.log(metadata(`Datasource key: ${dsKey}`));
43
+ logger.log(metadata(`Dimension key: ${dimensionKey}`));
44
+ if (opts.type) {
45
+ logger.log(metadata(`Type preset: ${opts.type}`));
46
+ }
47
+ logger.log('');
48
+ }
49
+
50
+ /**
51
+ * @param {string} dsKey
52
+ * @param {string} dimensionKey
53
+ * @param {Object} opts
54
+ * @param {Object} probeContext
55
+ * @returns {Object}
56
+ */
57
+ function buildManifestForCreate(dsKey, dimensionKey, opts, probeContext) {
58
+ return buildPresetProtectionManifest({
59
+ datasourceKey: dsKey,
60
+ dimensionKey,
61
+ type: opts.type,
62
+ field: opts.field,
63
+ fkName: opts.fkName,
64
+ protectionKey: opts.protectionKey,
65
+ displayName: opts.displayName,
66
+ ruleKey: opts.ruleKey,
67
+ principalExpression: opts.principalExpression,
68
+ valueExpression: opts.valueExpression,
69
+ enabled: opts.disabled !== true,
70
+ datasource: probeContext.datasource,
71
+ dimension: probeContext.dimension
72
+ });
73
+ }
74
+
75
+ /**
76
+ * @param {Object} manifest
77
+ * @param {Object} logger
78
+ */
79
+ function assertLocalSchemaOrThrow(manifest, logger) {
80
+ const local = validateProtectionManifestLocal(manifest);
81
+ if (!local.valid) {
82
+ logger.error(formatBlockingError('Local schema validation failed after scaffold:'));
83
+ local.errors.forEach((e) => logger.error(chalk.gray(` ${e}`)));
84
+ throw new Error('Scaffolded manifest failed AJV');
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @param {Object} logger
90
+ * @param {string[]} lines
91
+ */
92
+ function logProbeLines(logger, lines) {
93
+ for (const line of lines) {
94
+ logger.log(line);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @param {Object} logger
100
+ */
101
+ function logManifestSection(logger) {
102
+ logger.log(sectionTitle('Protection manifest'));
103
+ logger.log(SEP);
104
+ logger.log(formatSuccessLine('Local schema check passed (AJV)'));
105
+ }
106
+
107
+ /**
108
+ * @param {Object} args
109
+ * @param {string} args.dsKey
110
+ * @param {boolean} args.dryRun
111
+ * @param {boolean} args.force
112
+ * @param {Object} args.manifest
113
+ * @param {Object} args.logger
114
+ * @returns {Promise<number>}
115
+ */
116
+ async function finalizeProtectionCreateWrite({ dsKey, dryRun, force, manifest, logger }) {
117
+ const format = await resolveProtectionOutputFormat();
118
+ const body = protectionManifestToDisplayText(manifest, format);
119
+ if (dryRun) {
120
+ logger.log(metadata('Dry run: no file written'));
121
+ process.stdout.write(body);
122
+ return 0;
123
+ }
124
+ const { outputPath } = await resolveOutputPathOrThrow(dsKey, force);
125
+ writeProtectionManifest(outputPath, manifest, format);
126
+ logger.log(formatSuccessLine(`Wrote ${outputPath}`));
127
+ logger.log('');
128
+ logger.log(
129
+ formatNextActions([
130
+ `aifabrix protection validate ${dsKey}`,
131
+ `aifabrix protection upload ${dsKey}`
132
+ ])
133
+ );
134
+ return 0;
135
+ }
136
+
137
+ /**
138
+ * @param {string} datasourceKey
139
+ * @param {Object} opts
140
+ * @returns {{ dsKey: string, dimensionKey: string, preset: Object|null }}
141
+ */
142
+ function resolveCreateKeys(datasourceKey, opts) {
143
+ const dsKey = String(datasourceKey || '').trim();
144
+ if (!dsKey) {
145
+ throw new Error('Datasource key is required');
146
+ }
147
+ const preset = requireProtectionPreset(opts.type);
148
+ const dimensionKey = String(opts.dimensionKey || preset?.dimensionKey || '').trim();
149
+ if (!dimensionKey) {
150
+ throw new Error('--dimension-key is required unless --type supplies a preset dimension');
151
+ }
152
+ return { dsKey, dimensionKey, preset };
153
+ }
154
+
155
+ /**
156
+ * @param {string} datasourceKey
157
+ * @param {Object} opts
158
+ * @param {Object} logger
159
+ * @returns {Promise<number>}
160
+ */
161
+ async function runProtectionCreate(datasourceKey, opts, logger) {
162
+ const { dsKey, dimensionKey, preset } = resolveCreateKeys(datasourceKey, opts);
163
+ const dryRun = opts.dryRun === true;
164
+ const force = opts.force === true;
165
+ const verbose = opts.verbose === true;
166
+
167
+ const dpCtx = await resolveProtectionDataplaneContext(opts);
168
+ const dimCtx = await resolveControllerAuthForDimensions();
169
+
170
+ logCreateHeader({ environment: dpCtx.environment, type: preset?.type }, dsKey, dimensionKey, logger);
171
+
172
+ const probeContext = await collectProtectionCreateProbeContext(
173
+ dpCtx,
174
+ dimCtx,
175
+ dsKey,
176
+ dimensionKey,
177
+ verbose
178
+ );
179
+ logProbeLines(logger, probeContext.lines);
180
+
181
+ const manifest = buildManifestForCreate(dsKey, dimensionKey, opts, probeContext);
182
+ assertLocalSchemaOrThrow(manifest, logger);
183
+ logManifestSection(logger);
184
+
185
+ return finalizeProtectionCreateWrite({ dsKey, dryRun, force, manifest, logger });
186
+ }
187
+
188
+ module.exports = {
189
+ runProtectionCreate,
190
+ resolveControllerAuthForDimensions: require('./run-protection-create-helpers').resolveControllerAuthForDimensions
191
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview `.protection` scope token for top-level CLI commands.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const PROTECTION_SCOPE = '.protection';
10
+
11
+ /**
12
+ * @param {string} arg
13
+ * @returns {boolean}
14
+ */
15
+ function isProtectionScope(arg) {
16
+ return String(arg || '').trim() === PROTECTION_SCOPE;
17
+ }
18
+
19
+ module.exports = {
20
+ PROTECTION_SCOPE,
21
+ isProtectionScope
22
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @fileoverview Trigger datasource sync after protection upload.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { createSyncJob, executeSyncJob } = require('../api/datasources-extended.api');
10
+ const { unwrapApiData } = require('../utils/external-system-readiness-core');
11
+
12
+ /**
13
+ * @param {string} dataplaneUrl
14
+ * @param {Object} authConfig
15
+ * @param {string} datasourceKey
16
+ * @returns {Promise<{ syncJobId: string|null, warning: string|null }>}
17
+ */
18
+ async function syncDatasourceAfterProtectionUpload(dataplaneUrl, authConfig, datasourceKey) {
19
+ const key = String(datasourceKey || '').trim();
20
+ if (!key) {
21
+ return { syncJobId: null, warning: 'Missing datasource key for sync' };
22
+ }
23
+ try {
24
+ const created = unwrapApiData(
25
+ await createSyncJob(dataplaneUrl, key, authConfig, {})
26
+ );
27
+ const syncJobId =
28
+ created?.id || created?.syncJobId || created?.data?.id || null;
29
+ if (syncJobId) {
30
+ await executeSyncJob(dataplaneUrl, key, syncJobId, authConfig);
31
+ }
32
+ return { syncJobId: syncJobId ? String(syncJobId) : null, warning: null };
33
+ } catch (err) {
34
+ return {
35
+ syncJobId: null,
36
+ warning: err?.message || String(err)
37
+ };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * @param {string} dataplaneUrl
43
+ * @param {Object} authConfig
44
+ * @param {string[]} datasourceKeys
45
+ * @returns {Promise<Array<{ datasourceKey: string, syncJobId: string|null, warning: string|null }>>}
46
+ */
47
+ async function syncUniqueDatasourcesAfterUpload(dataplaneUrl, authConfig, datasourceKeys) {
48
+ const unique = [...new Set(datasourceKeys.map((k) => String(k || '').trim()).filter(Boolean))];
49
+ const out = [];
50
+ for (const datasourceKey of unique) {
51
+ const result = await syncDatasourceAfterProtectionUpload(
52
+ dataplaneUrl,
53
+ authConfig,
54
+ datasourceKey
55
+ );
56
+ out.push({ datasourceKey, ...result });
57
+ }
58
+ return out;
59
+ }
60
+
61
+ module.exports = {
62
+ syncDatasourceAfterProtectionUpload,
63
+ syncUniqueDatasourcesAfterUpload
64
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Batch upload all manifests in `integration/.protection/` (yaml, yml, json).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { getProtectionRoot } = require('./paths');
11
+ const { loadProtectionManifest } = require('./load');
12
+ const { validateProtectionManifestLocal } = require('./validate-local');
13
+ const { resolveProtectionDataplaneContext } = require('./auth-context');
14
+ const { validateProtection, uploadProtection } = require('../api/protection.api');
15
+ const { exitCodeFromProtectionReport } = require('./report-exit');
16
+ const { preflightDatasourceReady } = require('./preflight-datasource-ready');
17
+ const { syncUniqueDatasourcesAfterUpload } = require('./sync-after-upload');
18
+ const { formatProtectionBatchUploadTTY } = require('./protection-display');
19
+
20
+ /**
21
+ * @param {string} manifestPath
22
+ * @param {Object} ctx
23
+ * @param {Object} opts
24
+ * @returns {Promise<{ stop: boolean, row: Object, uploadedKey?: string }>}
25
+ */
26
+ async function processProtectionUploadFile(manifestPath, ctx, opts) {
27
+ const manifest = loadProtectionManifest(manifestPath);
28
+ const datasourceKey = String(manifest?.spec?.datasourceKey || '').trim();
29
+ const local = validateProtectionManifestLocal(manifest);
30
+ if (!local.valid) {
31
+ return {
32
+ stop: true,
33
+ row: {
34
+ datasourceKey: datasourceKey || path.basename(manifestPath),
35
+ ok: false,
36
+ error: local.errors.join('; ')
37
+ }
38
+ };
39
+ }
40
+
41
+ try {
42
+ await preflightDatasourceReady(ctx.dataplaneUrl, ctx.authConfig, manifest);
43
+ } catch (err) {
44
+ return { stop: true, row: { datasourceKey, ok: false, error: err.message } };
45
+ }
46
+
47
+ const report = await validateProtection(ctx.dataplaneUrl, ctx.authConfig, manifest, {
48
+ strict: opts.warningsAsErrors === true
49
+ });
50
+ if (exitCodeFromProtectionReport(report, opts) !== 0) {
51
+ const firstFail = (report?.results || []).find((r) => String(r?.status).toUpperCase() === 'FAIL');
52
+ return {
53
+ stop: true,
54
+ row: {
55
+ datasourceKey,
56
+ ok: false,
57
+ error: firstFail?.message || 'Dataplane validation failed'
58
+ }
59
+ };
60
+ }
61
+
62
+ if (opts.dryRun) {
63
+ return { stop: false, row: { datasourceKey, ok: true, dryRun: true } };
64
+ }
65
+
66
+ await uploadProtection(ctx.dataplaneUrl, ctx.authConfig, manifest);
67
+ return { stop: false, row: { datasourceKey, ok: true }, uploadedKey: datasourceKey };
68
+ }
69
+
70
+ /**
71
+ * @param {Object} opts
72
+ * @returns {Promise<{ ok: boolean, results: Object[], exitCode: number, syncResults: Object[] }>}
73
+ */
74
+ async function runUploadProtectionBatch(opts = {}) {
75
+ const root = getProtectionRoot();
76
+ const { listProtectionManifestPaths } = require('./resolve');
77
+ const files = listProtectionManifestPaths(root);
78
+ const ctx = await resolveProtectionDataplaneContext(opts);
79
+ const results = [];
80
+ const uploadedDatasourceKeys = [];
81
+
82
+ for (const manifestPath of files) {
83
+ const { stop, row, uploadedKey } = await processProtectionUploadFile(manifestPath, ctx, opts);
84
+ results.push(row);
85
+ if (uploadedKey) uploadedDatasourceKeys.push(uploadedKey);
86
+ if (stop) break;
87
+ }
88
+
89
+ let syncResults = [];
90
+ if (!opts.dryRun && !opts.noSync && uploadedDatasourceKeys.length) {
91
+ syncResults = await syncUniqueDatasourcesAfterUpload(
92
+ ctx.dataplaneUrl,
93
+ ctx.authConfig,
94
+ uploadedDatasourceKeys
95
+ );
96
+ }
97
+
98
+ const ok = results.length > 0 && results.every((r) => r.ok);
99
+ return {
100
+ ok,
101
+ results,
102
+ exitCode: ok ? 0 : 1,
103
+ root,
104
+ ctx,
105
+ syncResults
106
+ };
107
+ }
108
+
109
+ /**
110
+ * @param {Object} opts
111
+ * @param {Object} logger
112
+ * @returns {Promise<number>}
113
+ */
114
+ async function uploadProtectionBatchCommand(opts, logger) {
115
+ const batch = await runUploadProtectionBatch(opts);
116
+ if (opts.json) {
117
+ process.stdout.write(JSON.stringify(batch, null, 2));
118
+ return batch.exitCode;
119
+ }
120
+ logger.log(formatProtectionBatchUploadTTY(batch.results, { folder: batch.root }));
121
+ return batch.exitCode;
122
+ }
123
+
124
+ module.exports = {
125
+ runUploadProtectionBatch,
126
+ uploadProtectionBatchCommand
127
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @fileoverview Batch validate all manifests in `integration/.protection/` (resolved protection root).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { getProtectionRoot } = require('./paths');
11
+ const { listProtectionManifestPaths } = require('./resolve');
12
+ const { loadProtectionManifest } = require('./load');
13
+ const { validateProtectionManifestLocal } = require('./validate-local');
14
+ const { resolveProtectionDataplaneContext } = require('./auth-context');
15
+ const { validateProtection, simulateProtection } = require('../api/protection.api');
16
+ const { exitCodeFromProtectionReport } = require('./report-exit');
17
+ const { formatProtectionBatchValidateTTY } = require('./protection-display');
18
+
19
+ /**
20
+ * @param {string} manifestPath
21
+ * @param {Object|null} ctx
22
+ * @param {Object} opts
23
+ * @returns {Promise<{ ctx: Object|null, row: Object, stop: boolean }>}
24
+ */
25
+ async function processProtectionValidateFile(manifestPath, ctx, opts) {
26
+ const manifest = loadProtectionManifest(manifestPath);
27
+ const datasourceKey = String(
28
+ manifest?.spec?.datasourceKey || path.basename(manifestPath, path.extname(manifestPath))
29
+ ).trim();
30
+ const local = validateProtectionManifestLocal(manifest);
31
+ if (!local.valid) {
32
+ return {
33
+ ctx,
34
+ stop: opts.stopOnFirstFailure !== false,
35
+ row: { datasourceKey, manifestPath, ok: false, error: local.errors.join('; ') }
36
+ };
37
+ }
38
+
39
+ if (opts.skipDataplane) {
40
+ return { ctx, stop: false, row: { datasourceKey, manifestPath, ok: true } };
41
+ }
42
+
43
+ let nextCtx = ctx;
44
+ if (!nextCtx) {
45
+ nextCtx = await resolveProtectionDataplaneContext(opts);
46
+ }
47
+ const report = await validateProtection(nextCtx.dataplaneUrl, nextCtx.authConfig, manifest, {
48
+ strict: opts.warningsAsErrors === true
49
+ });
50
+ if (opts.simulate && exitCodeFromProtectionReport(report, opts) === 0) {
51
+ await simulateProtection(nextCtx.dataplaneUrl, nextCtx.authConfig, manifest, {
52
+ strict: opts.warningsAsErrors === true
53
+ });
54
+ }
55
+ const code = exitCodeFromProtectionReport(report, opts);
56
+ if (code !== 0) {
57
+ const firstFail = (report?.results || []).find((r) => String(r?.status).toUpperCase() === 'FAIL');
58
+ return {
59
+ ctx: nextCtx,
60
+ stop: opts.stopOnFirstFailure !== false,
61
+ row: {
62
+ datasourceKey,
63
+ manifestPath,
64
+ ok: false,
65
+ error: firstFail?.message || 'Dataplane validation failed',
66
+ report
67
+ }
68
+ };
69
+ }
70
+ return { ctx: nextCtx, stop: false, row: { datasourceKey, manifestPath, ok: true, report } };
71
+ }
72
+
73
+ /**
74
+ * @param {Object} opts
75
+ * @returns {Promise<{ valid: boolean, results: Object[], exitCode: number }>}
76
+ */
77
+ async function runValidateProtectionBatch(opts = {}) {
78
+ const root = getProtectionRoot();
79
+ const files = listProtectionManifestPaths(root);
80
+ const results = [];
81
+ let ctx = null;
82
+
83
+ for (const manifestPath of files) {
84
+ const outcome = await processProtectionValidateFile(manifestPath, ctx, opts);
85
+ ctx = outcome.ctx;
86
+ results.push(outcome.row);
87
+ if (outcome.stop) break;
88
+ }
89
+
90
+ const valid = results.every((r) => r.ok);
91
+ return { valid, results, exitCode: valid ? 0 : 1, root, ctx };
92
+ }
93
+
94
+ /**
95
+ * @param {Object} opts
96
+ * @param {Object} logger
97
+ * @returns {Promise<number>}
98
+ */
99
+ async function validateProtectionBatchCommand(opts, logger) {
100
+ const batch = await runValidateProtectionBatch(opts);
101
+ if (opts.json) {
102
+ process.stdout.write(JSON.stringify(batch, null, 2));
103
+ return batch.exitCode;
104
+ }
105
+ logger.log(formatProtectionBatchValidateTTY(batch.results, { folder: batch.root }));
106
+ return batch.exitCode;
107
+ }
108
+
109
+ module.exports = {
110
+ processProtectionValidateFile,
111
+ runValidateProtectionBatch,
112
+ validateProtectionBatchCommand
113
+ };