@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,114 @@
1
+ /**
2
+ * @fileoverview Parse controller users.csv-style files for identity apply
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { readFileSync } = require('../internal/fs-real-sync');
10
+
11
+ /**
12
+ * Parse one CSV line respecting double-quoted fields.
13
+ * @param {string} line
14
+ * @returns {string[]}
15
+ */
16
+ function parseCsvLine(line) {
17
+ const out = [];
18
+ let cur = '';
19
+ let inQuotes = false;
20
+ for (let i = 0; i < line.length; i += 1) {
21
+ const ch = line[i];
22
+ if (ch === '"') {
23
+ if (inQuotes && line[i + 1] === '"') {
24
+ cur += '"';
25
+ i += 1;
26
+ } else {
27
+ inQuotes = !inQuotes;
28
+ }
29
+ } else if (ch === ',' && !inQuotes) {
30
+ out.push(cur.trim());
31
+ cur = '';
32
+ } else {
33
+ cur += ch;
34
+ }
35
+ }
36
+ out.push(cur.trim());
37
+ return out;
38
+ }
39
+
40
+ /**
41
+ * @param {string} filePath
42
+ * @param {string} [filterPrefix]
43
+ * @returns {{ headers: string[], rows: Object[] }}
44
+ */
45
+ function parseUsersCsvFile(filePath, filterPrefix = '') {
46
+ const raw = readFileSync(filePath, 'utf8');
47
+ const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
48
+ if (lines.length < 2) {
49
+ throw new Error('CSV must include a header row and at least one data row');
50
+ }
51
+ const headers = parseCsvLine(lines[0]);
52
+ const prefix = String(filterPrefix || '').trim();
53
+ const rows = [];
54
+ for (let i = 1; i < lines.length; i += 1) {
55
+ const cells = parseCsvLine(lines[i]);
56
+ const row = {};
57
+ headers.forEach((h, idx) => {
58
+ row[h] = cells[idx] !== undefined ? cells[idx] : '';
59
+ });
60
+ const id = String(row.Id || row.id || '').trim();
61
+ if (prefix && id && !id.startsWith(prefix)) {
62
+ continue;
63
+ }
64
+ rows.push(row);
65
+ }
66
+ return { headers, rows };
67
+ }
68
+
69
+ /**
70
+ * Build unique entities from CSV rows (users.csv shape).
71
+ * @param {Object[]} rows
72
+ * @returns {{ groups: Map<string, { name: string, displayName: string }>, users: Map<string, Object>, memberships: { userKey: string, groupName: string }[] }}
73
+ */
74
+ // eslint-disable-next-line complexity -- row shape varies; logic is linear per field
75
+ function buildApplyPlanFromRows(rows) {
76
+ const groups = new Map();
77
+ const users = new Map();
78
+ const memberships = [];
79
+
80
+ for (const row of rows) {
81
+ const csvId = String(row.Id || row.id || '').trim();
82
+ const email = String(row.Email || row.email || '').trim();
83
+ if (!email) {
84
+ continue;
85
+ }
86
+ const userKey = csvId || email;
87
+ const groupName = String(row.GroupId || row.groupId || '').trim();
88
+ const groupDisplay = String(row.GroupName || row.groupName || groupName).trim();
89
+ if (groupName) {
90
+ if (!groups.has(groupName)) {
91
+ groups.set(groupName, { name: groupName, displayName: groupDisplay || groupName });
92
+ }
93
+ memberships.push({ userKey, groupName });
94
+ }
95
+ if (!users.has(userKey)) {
96
+ users.set(userKey, {
97
+ csvId,
98
+ email,
99
+ firstName: String(row.FirstName || row.firstName || '').trim() || undefined,
100
+ lastName: String(row.LastName || row.lastName || '').trim() || undefined,
101
+ displayName: String(row.DisplayName || row.displayName || '').trim() || undefined,
102
+ username: String(row.UserPrincipalName || row.userPrincipalName || row.username || '').trim() || undefined
103
+ });
104
+ }
105
+ }
106
+
107
+ return { groups, users, memberships };
108
+ }
109
+
110
+ module.exports = {
111
+ parseCsvLine,
112
+ parseUsersCsvFile,
113
+ buildApplyPlanFromRows
114
+ };
@@ -93,6 +93,31 @@ function readdirSync(p, opts) {
93
93
  return delegate('readdirSync', opts !== undefined ? [p, opts] : [p]);
94
94
  }
95
95
 
96
+ /**
97
+ * @param {string} prefix
98
+ * @returns {string}
99
+ */
100
+ function mkdtempSync(prefix) {
101
+ return delegate('mkdtempSync', [prefix]);
102
+ }
103
+
104
+ /**
105
+ * @param {string} p
106
+ * @param {import('node:fs').RmOptions} [opts]
107
+ * @returns {void}
108
+ */
109
+ function rmSync(p, opts) {
110
+ return delegate('rmSync', opts !== undefined ? [p, opts] : [p]);
111
+ }
112
+
113
+ /**
114
+ * @param {string} p
115
+ * @returns {void}
116
+ */
117
+ function unlinkSync(p) {
118
+ return delegate('unlinkSync', [p]);
119
+ }
120
+
96
121
  module.exports = {
97
122
  existsSync,
98
123
  readFileSync,
@@ -100,5 +125,8 @@ module.exports = {
100
125
  writeFileSync,
101
126
  mkdirSync,
102
127
  statSync,
103
- readdirSync
128
+ readdirSync,
129
+ mkdtempSync,
130
+ rmSync,
131
+ unlinkSync
104
132
  };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Dataplane auth/url resolution for protection commands.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { resolveControllerUrl } = require('../utils/controller-url');
10
+ const { resolveEnvironment } = require('../core/config');
11
+ const { getDeviceOnlyAuth, getDeploymentAuth } = require('../utils/token-manager');
12
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
13
+
14
+ /**
15
+ * @param {Object} [options]
16
+ * @param {string} [options.env]
17
+ * @returns {Promise<{ environment: string, controllerUrl: string, dataplaneUrl: string, authConfig: Object }>}
18
+ */
19
+ async function resolveProtectionDataplaneContext(options = {}) {
20
+ const environment = options.env || (await resolveEnvironment());
21
+ const controllerUrl = await resolveControllerUrl();
22
+ if (!controllerUrl) {
23
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
24
+ }
25
+
26
+ let authConfig;
27
+ try {
28
+ authConfig = await getDeviceOnlyAuth(controllerUrl);
29
+ } catch {
30
+ authConfig = await getDeploymentAuth(controllerUrl, environment, 'dataplane');
31
+ }
32
+
33
+ if (!authConfig?.token) {
34
+ throw new Error(
35
+ 'Authentication required. Run "aifabrix login" and try again.'
36
+ );
37
+ }
38
+
39
+ const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
40
+ return { environment, controllerUrl, dataplaneUrl, authConfig };
41
+ }
42
+
43
+ module.exports = {
44
+ resolveProtectionDataplaneContext
45
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Convert all protection manifests in `.protection/` between JSON and YAML.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { existsSync, unlinkSync } = require('../internal/fs-real-sync');
11
+ const { getProtectionRoot } = require('./paths');
12
+ const { listProtectionManifestPaths } = require('./resolve');
13
+ const { promptConfirm, targetFileName, convertOneFile } = require('../commands/convert');
14
+
15
+ /**
16
+ * @param {string} root
17
+ * @param {string} format
18
+ * @returns {string[]}
19
+ */
20
+ function listFilesNeedingConvert(root, format) {
21
+ const targetExt = format === 'json' ? '.json' : '.yaml';
22
+ return listProtectionManifestPaths(root).filter((p) => {
23
+ return path.extname(p).toLowerCase() !== targetExt;
24
+ });
25
+ }
26
+
27
+ /**
28
+ * @param {string[]} toConvert
29
+ * @param {string} format
30
+ * @param {boolean} force
31
+ * @returns {Promise<void>}
32
+ */
33
+ async function confirmProtectionConvert(toConvert, format, force) {
34
+ if (force) {
35
+ return;
36
+ }
37
+ const lines = toConvert.map((p) => ` • ${path.basename(p)} → ${targetFileName(p, format)}`);
38
+ const confirmed = await promptConfirm(
39
+ `Convert all protection manifests to ${format}?\n${lines.join('\n')}\nAre you sure? (y/N) `
40
+ );
41
+ if (!confirmed) {
42
+ throw new Error('Convert cancelled.');
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @param {string} root
48
+ * @param {string[]} toConvert
49
+ * @param {string} format
50
+ * @returns {{ converted: string[], deleted: string[] }}
51
+ */
52
+ function convertProtectionFiles(root, toConvert, format) {
53
+ const converted = [];
54
+ const deleted = [];
55
+ for (const sourcePath of toConvert) {
56
+ const targetPath = path.join(root, targetFileName(sourcePath, format));
57
+ convertOneFile(sourcePath, targetPath, format);
58
+ converted.push(targetPath);
59
+ if (path.normalize(sourcePath) !== path.normalize(targetPath)) {
60
+ unlinkSync(sourcePath);
61
+ deleted.push(sourcePath);
62
+ }
63
+ }
64
+ return { converted, deleted };
65
+ }
66
+
67
+ /**
68
+ * @param {string} format - `yaml` | `json`
69
+ * @param {Object} opts
70
+ * @returns {Promise<{ converted: string[], deleted: string[] }>}
71
+ */
72
+ async function runConvertProtectionBatch(format, opts = {}) {
73
+ const root = opts.root || getProtectionRoot();
74
+ if (!existsSync(root)) {
75
+ throw new Error(`Protection folder not found: ${root}`);
76
+ }
77
+ const toConvert = listFilesNeedingConvert(root, format);
78
+ if (!toConvert.length) {
79
+ return { converted: [], deleted: [] };
80
+ }
81
+ await confirmProtectionConvert(toConvert, format, opts.force === true);
82
+ return convertProtectionFiles(root, toConvert, format);
83
+ }
84
+
85
+ module.exports = {
86
+ runConvertProtectionBatch,
87
+ listFilesNeedingConvert,
88
+ confirmProtectionConvert,
89
+ convertProtectionFiles
90
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @fileoverview Load protection manifest YAML files (v1).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { existsSync, readFileSync } = require('../internal/fs-real-sync');
11
+ const { yamlToJson } = require('../utils/config-format');
12
+
13
+ /**
14
+ * @param {string} manifestPath - Absolute path to `.yaml` manifest
15
+ * @returns {Object} Parsed manifest object
16
+ */
17
+ function loadProtectionManifest(manifestPath) {
18
+ const p = path.resolve(String(manifestPath || '').trim());
19
+ if (!p) {
20
+ throw new Error('Protection manifest path is required');
21
+ }
22
+ if (!existsSync(p)) {
23
+ throw new Error(`Protection manifest not found: ${p}`);
24
+ }
25
+ const ext = path.extname(p).toLowerCase();
26
+ if (ext !== '.yaml' && ext !== '.yml' && ext !== '.json') {
27
+ throw new Error(
28
+ `Protection manifest must be .yaml, .yml, or .json (got ${ext || '(none)'}).`
29
+ );
30
+ }
31
+ const content = readFileSync(p, 'utf8');
32
+ if (ext === '.json') {
33
+ const parsed = JSON.parse(content);
34
+ return parsed === undefined || parsed === null ? {} : parsed;
35
+ }
36
+ return yamlToJson(content);
37
+ }
38
+
39
+ module.exports = {
40
+ loadProtectionManifest
41
+ };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview Protection manifest directory under repo `integration/.protection/`.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const {
12
+ getAppsMaterializationParent,
13
+ getCwdIntegrationRoot
14
+ } = require('../utils/paths');
15
+ const { nodeFs } = require('../internal/node-fs');
16
+
17
+ /**
18
+ * Walk upward from cwd for the nearest directory containing `integration/`.
19
+ *
20
+ * @returns {string|null} Absolute path to `integration/` or null
21
+ */
22
+ function findNearestIntegrationRootFromCwd() {
23
+ const disk = nodeFs();
24
+ let walk = path.resolve(process.cwd());
25
+ for (let i = 0; i < 64; i += 1) {
26
+ const integrationDir = path.join(walk, 'integration');
27
+ try {
28
+ if (disk.existsSync(integrationDir)) {
29
+ const st = disk.statSync(integrationDir);
30
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
31
+ return integrationDir;
32
+ }
33
+ }
34
+ } catch {
35
+ // ignore
36
+ }
37
+ const parent = path.dirname(walk);
38
+ if (parent === walk) {
39
+ break;
40
+ }
41
+ walk = parent;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * Canonical repo-local protection root: `integration/.protection/`.
48
+ *
49
+ * @returns {string}
50
+ */
51
+ function getRepoProtectionRoot() {
52
+ const cwdInt = getCwdIntegrationRoot();
53
+ if (cwdInt) {
54
+ return path.join(cwdInt, '.protection');
55
+ }
56
+ const materializationParent = path.resolve(getAppsMaterializationParent());
57
+ const nearest = findNearestIntegrationRootFromCwd();
58
+ if (nearest) {
59
+ const resolvedNearest = path.resolve(nearest);
60
+ if (
61
+ resolvedNearest === materializationParent ||
62
+ resolvedNearest.startsWith(`${materializationParent}${path.sep}`)
63
+ ) {
64
+ return path.join(resolvedNearest, '.protection');
65
+ }
66
+ }
67
+ return path.join(materializationParent, 'integration', '.protection');
68
+ }
69
+
70
+ /**
71
+ * @returns {string}
72
+ */
73
+ function getLegacyProtectionRoot() {
74
+ return path.join(getAppsMaterializationParent(), '.protection');
75
+ }
76
+
77
+ /**
78
+ * @param {string} root
79
+ * @returns {boolean}
80
+ */
81
+ function protectionRootHasManifestFiles(root) {
82
+ if (!root || !fs.existsSync(root)) {
83
+ return false;
84
+ }
85
+ try {
86
+ return fs.readdirSync(root).some((name) => /\.(yaml|yml|json)$/i.test(name));
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @returns {string} Absolute path to protection manifests directory
94
+ */
95
+ function getProtectionRoot() {
96
+ const explicit = process.env.AIFABRIX_PROTECTION_ROOT;
97
+ if (explicit && typeof explicit === 'string' && explicit.trim()) {
98
+ return path.resolve(explicit.trim());
99
+ }
100
+
101
+ const legacyFlag =
102
+ process.env.AIFABRIX_PROTECTION_LEGACY === '1' ||
103
+ String(process.env.AIFABRIX_PROTECTION_LEGACY || '').toLowerCase() === 'true';
104
+
105
+ const repoRoot = getRepoProtectionRoot();
106
+ const legacyRoot = getLegacyProtectionRoot();
107
+
108
+ if (legacyFlag) {
109
+ return legacyRoot;
110
+ }
111
+
112
+ const repoHas = protectionRootHasManifestFiles(repoRoot);
113
+ const legacyHas = protectionRootHasManifestFiles(legacyRoot);
114
+
115
+ if (!repoHas && legacyHas && legacyRoot !== repoRoot) {
116
+ return legacyRoot;
117
+ }
118
+
119
+ return repoRoot;
120
+ }
121
+
122
+ /**
123
+ * Human-readable folder label for batch protection TTY output.
124
+ *
125
+ * @returns {{ root: string, label: string, usingLegacy: boolean, migrationHint: string|null }}
126
+ */
127
+ function describeProtectionRoot() {
128
+ const root = getProtectionRoot();
129
+ const legacyRoot = getLegacyProtectionRoot();
130
+ const repoRoot = getRepoProtectionRoot();
131
+ const usingLegacy = path.resolve(root) === path.resolve(legacyRoot);
132
+ const legacyHas = protectionRootHasManifestFiles(legacyRoot);
133
+ const repoHas = protectionRootHasManifestFiles(repoRoot);
134
+
135
+ let label = 'integration/.protection';
136
+ if (usingLegacy) {
137
+ label = 'work/.protection (legacy)';
138
+ } else if (process.env.AIFABRIX_PROTECTION_ROOT) {
139
+ label = 'custom';
140
+ }
141
+
142
+ let migrationHint = null;
143
+ if (usingLegacy && legacyHas) {
144
+ migrationHint = `Move manifests to ${repoRoot} (git-friendly integration/.protection/).`;
145
+ } else if (!usingLegacy && legacyHas && !repoHas) {
146
+ migrationHint = `Legacy manifests remain under ${legacyRoot}; migrate to ${repoRoot}.`;
147
+ }
148
+
149
+ return { root, label, usingLegacy, migrationHint };
150
+ }
151
+
152
+ module.exports = {
153
+ getProtectionRoot,
154
+ describeProtectionRoot,
155
+ getRepoProtectionRoot,
156
+ getLegacyProtectionRoot,
157
+ findNearestIntegrationRootFromCwd
158
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @fileoverview Preflight: datasource must exist on dataplane before protection upload.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { getDatasource } = require('../api/datasources-core.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<void>}
17
+ */
18
+ async function assertDatasourceExistsOnDataplane(dataplaneUrl, authConfig, datasourceKey) {
19
+ const key = String(datasourceKey || '').trim();
20
+ if (!key) {
21
+ throw new Error('spec.datasourceKey is required');
22
+ }
23
+ const res = await getDatasource(dataplaneUrl, key, authConfig);
24
+ const row = unwrapApiData(res);
25
+ if (!row || (res && res.success === false)) {
26
+ throw new Error(
27
+ `Datasource "${key}" is not deployed on the dataplane. Run aifabrix deploy <app> first.`
28
+ );
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param {string} dataplaneUrl
34
+ * @param {Object} authConfig
35
+ * @param {Object} manifest
36
+ * @returns {Promise<void>}
37
+ */
38
+ async function preflightDatasourceReady(dataplaneUrl, authConfig, manifest) {
39
+ const ds = String(manifest?.spec?.datasourceKey || '').trim();
40
+ await assertDatasourceExistsOnDataplane(dataplaneUrl, authConfig, ds);
41
+ }
42
+
43
+ module.exports = {
44
+ assertDatasourceExistsOnDataplane,
45
+ preflightDatasourceReady
46
+ };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @fileoverview TTY line builders for protection create probes (keeps probe async functions small).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const {
11
+ sectionTitle,
12
+ headerKeyValue,
13
+ metadata,
14
+ formatProgress,
15
+ formatSuccessLine
16
+ } = require('../utils/cli-test-layout-chalk');
17
+
18
+ const SEP = chalk.gray('────────────────────────────────────────');
19
+
20
+ /**
21
+ * @param {string} key
22
+ * @returns {string[]}
23
+ */
24
+ function startDatasourceProbeLines(key) {
25
+ return [sectionTitle('Datasource (dataplane)'), SEP, formatProgress(`Fetching datasource "${key}"…`)];
26
+ }
27
+
28
+ /**
29
+ * @param {string[]} lines
30
+ * @param {Object} row
31
+ * @param {string} key
32
+ * @param {string} dataplaneUrl
33
+ */
34
+ function appendDatasourceCoreFields(lines, row, key, dataplaneUrl) {
35
+ lines.push(formatSuccessLine(`Datasource "${key}" found`));
36
+ lines.push(metadata(`Dataplane: ${dataplaneUrl}`));
37
+ lines.push(headerKeyValue('Key:', row.key ?? key));
38
+ lines.push(headerKeyValue('Display name:', row.displayName ?? '—'));
39
+ lines.push(headerKeyValue('System key:', row.systemKey ?? '—'));
40
+ const ver = row.version;
41
+ lines.push(headerKeyValue('Version:', ver !== undefined && ver !== null ? String(ver) : '—'));
42
+ lines.push(headerKeyValue('Enabled:', String(row.enabled !== false)));
43
+ }
44
+
45
+ /**
46
+ * @param {string[]} lines
47
+ * @param {Object} row
48
+ */
49
+ function appendDatasourceOptionalTypes(lines, row) {
50
+ if (row.entityType) {
51
+ lines.push(headerKeyValue('Entity type:', String(row.entityType)));
52
+ }
53
+ if (row.resourceType) {
54
+ lines.push(headerKeyValue('Resource type:', String(row.resourceType)));
55
+ }
56
+ }
57
+
58
+ /**
59
+ * @param {string[]} lines
60
+ * @param {Object} row
61
+ */
62
+ function appendDatasourceVerboseBlock(lines, row) {
63
+ const pick = ['id', 'key', 'displayName', 'systemKey', 'version', 'enabled', 'entityType', 'resourceType'];
64
+ const extra = {};
65
+ for (const p of pick) {
66
+ if (row[p] !== undefined) {
67
+ extra[p] = row[p];
68
+ }
69
+ }
70
+ lines.push('');
71
+ lines.push(chalk.gray('Verbose (subset):'));
72
+ lines.push(chalk.gray(JSON.stringify(extra, null, 2)));
73
+ }
74
+
75
+ /**
76
+ * @param {string[]} lines
77
+ * @param {Object} row
78
+ * @param {string} key
79
+ * @param {string} dataplaneUrl
80
+ * @param {Object} opts
81
+ */
82
+ function appendDatasourceSuccessDetail(lines, row, key, dataplaneUrl, opts) {
83
+ appendDatasourceCoreFields(lines, row, key, dataplaneUrl);
84
+ appendDatasourceOptionalTypes(lines, row);
85
+ if (opts.verbose && row) {
86
+ appendDatasourceVerboseBlock(lines, row);
87
+ }
88
+ lines.push('');
89
+ }
90
+
91
+ /**
92
+ * @param {string} key
93
+ * @returns {string[]}
94
+ */
95
+ function startDimensionProbeLines(key) {
96
+ return [
97
+ sectionTitle('Dimension (Controller catalog)'),
98
+ SEP,
99
+ formatProgress(`Fetching dimension "${key}"…`)
100
+ ];
101
+ }
102
+
103
+ /**
104
+ * @param {string[]} lines
105
+ * @param {Object} row
106
+ * @param {string} controllerUrl
107
+ * @param {Object} opts
108
+ */
109
+ function appendDimensionSuccessDetail(lines, row, controllerUrl, opts) {
110
+ lines.push(formatSuccessLine(`Dimension "${row.key}" found`));
111
+ lines.push(metadata(`Controller: ${controllerUrl}`));
112
+ lines.push(headerKeyValue('Key:', row.key));
113
+ lines.push(headerKeyValue('Display:', row.displayName ?? '—'));
114
+ lines.push(headerKeyValue('Data type:', row.dataType ?? '—'));
115
+ lines.push(headerKeyValue('Value type:', row.valueType ?? 'static'));
116
+ lines.push(headerKeyValue('Required:', String(row.isRequired === true)));
117
+ if (Array.isArray(row.dimensionValues) && row.dimensionValues.length > 0) {
118
+ lines.push(
119
+ headerKeyValue('Catalog values:', `${row.dimensionValues.length} (static/baseline)`)
120
+ );
121
+ } else if (row.valueType === 'dynamic' || row.valueType === 'both') {
122
+ lines.push(headerKeyValue('Catalog values:', '— (dynamic / projection)'));
123
+ }
124
+ if (opts.verbose && Array.isArray(row.dimensionValues) && row.dimensionValues.length) {
125
+ lines.push('');
126
+ lines.push(chalk.gray('Sample values (first 5):'));
127
+ row.dimensionValues.slice(0, 5).forEach((v) => {
128
+ const val = v && v.value !== undefined && v.value !== null ? String(v.value) : '—';
129
+ const dn = v?.displayName ? ` (${v.displayName})` : '';
130
+ lines.push(chalk.gray(` ${val}${dn}`));
131
+ });
132
+ }
133
+ lines.push('');
134
+ }
135
+
136
+ module.exports = {
137
+ SEP,
138
+ startDatasourceProbeLines,
139
+ appendDatasourceSuccessDetail,
140
+ startDimensionProbeLines,
141
+ appendDimensionSuccessDetail
142
+ };