@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,97 @@
1
+ /**
2
+ * @fileoverview Deterministic preset registry for `aifabrix protection create --type`.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const ProtectionPresetRegistry = Object.freeze({
10
+ 'country-sales': Object.freeze({
11
+ type: 'country-sales',
12
+ dimensionKey: 'country',
13
+ principalTemplate: 'Sales {{value}} Users',
14
+ valueTemplate: '{{value}}'
15
+ }),
16
+ 'department-manager': Object.freeze({
17
+ type: 'department-manager',
18
+ dimensionKey: 'department',
19
+ principalType: 'user',
20
+ principalField: 'metadata.managerEmail',
21
+ valueField: 'department',
22
+ valueTemplate: '{{metadata.department}}',
23
+ requiresField: 'department',
24
+ when: Object.freeze({ groups: Object.freeze({ requireAny: Object.freeze(['Manager']) }) })
25
+ }),
26
+ 'customer-team': Object.freeze({
27
+ type: 'customer-team',
28
+ dimensionKey: 'customer',
29
+ principalTemplate: 'Customer {{metadata.customer}} Team',
30
+ valueField: 'customer',
31
+ valueTemplate: '{{metadata.customer}}',
32
+ requiresField: 'customer'
33
+ }),
34
+ 'project-team': Object.freeze({
35
+ type: 'project-team',
36
+ dimensionKey: 'project',
37
+ principalTemplate: 'Project {{metadata.project}} Team',
38
+ valueField: 'project',
39
+ valueTemplate: '{{metadata.project}}',
40
+ requiresField: 'project'
41
+ }),
42
+ 'static-region': Object.freeze({
43
+ type: 'static-region',
44
+ dimensionKey: 'region',
45
+ principalTemplate: '{{metadata.regionDisplayName}} Users',
46
+ valueField: 'region',
47
+ valueTemplate: '{{metadata.region}}',
48
+ requiresField: 'region',
49
+ staticOnly: true
50
+ }),
51
+ 'owner-direct': Object.freeze({
52
+ type: 'owner-direct',
53
+ dimensionKey: 'owner',
54
+ principalType: 'user',
55
+ principalField: 'metadata.ownerEmail',
56
+ valueField: 'owner',
57
+ valueTemplate: '{{metadata.owner}}',
58
+ requiresField: 'owner'
59
+ })
60
+ });
61
+
62
+ /**
63
+ * @returns {string[]}
64
+ */
65
+ function listProtectionPresetTypes() {
66
+ return Object.keys(ProtectionPresetRegistry);
67
+ }
68
+
69
+ /**
70
+ * @param {string|undefined} type
71
+ * @returns {Object|null}
72
+ */
73
+ function getProtectionPreset(type) {
74
+ const key = String(type || '').trim();
75
+ return key ? ProtectionPresetRegistry[key] || null : null;
76
+ }
77
+
78
+ /**
79
+ * @param {string|undefined} type
80
+ * @returns {Object|null}
81
+ */
82
+ function requireProtectionPreset(type) {
83
+ const preset = getProtectionPreset(type);
84
+ if (preset || !String(type || '').trim()) {
85
+ return preset;
86
+ }
87
+ throw new Error(
88
+ `Unknown protection type "${type}". Supported types: ${listProtectionPresetTypes().join(', ')}`
89
+ );
90
+ }
91
+
92
+ module.exports = {
93
+ ProtectionPresetRegistry,
94
+ getProtectionPreset,
95
+ listProtectionPresetTypes,
96
+ requireProtectionPreset
97
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Exit codes for protection validate reports.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {Object} report
11
+ * @returns {Object[]}
12
+ */
13
+ function reportTaskRows(report) {
14
+ if (!report || typeof report !== 'object') {
15
+ return [];
16
+ }
17
+ if (Array.isArray(report.results)) {
18
+ return report.results;
19
+ }
20
+ if (Array.isArray(report.tasks)) {
21
+ return report.tasks;
22
+ }
23
+ return [];
24
+ }
25
+
26
+ /**
27
+ * @param {Object} report
28
+ * @param {Object} [opts]
29
+ * @param {boolean} [opts.warningsAsErrors]
30
+ * @returns {number} 0 ok, 1 fail/warn-as-error, 3 invalid input
31
+ */
32
+ function exitCodeFromProtectionReport(report, opts = {}) {
33
+ if (!report || typeof report !== 'object') {
34
+ return 3;
35
+ }
36
+ const summary = report.summary || {};
37
+ const failCount = Number(summary.fail || 0);
38
+ const warnCount = Number(summary.warn || 0);
39
+ if (failCount > 0) {
40
+ return 1;
41
+ }
42
+ if (opts.warningsAsErrors === true && warnCount > 0) {
43
+ return 1;
44
+ }
45
+ const rows = reportTaskRows(report);
46
+ const hasFail = rows.some((r) => String(r?.status || '').toUpperCase() === 'FAIL');
47
+ if (hasFail) {
48
+ return 1;
49
+ }
50
+ if (opts.warningsAsErrors === true) {
51
+ const hasWarn = rows.some((r) => String(r?.status || '').toUpperCase() === 'WARN');
52
+ if (hasWarn) {
53
+ return 1;
54
+ }
55
+ }
56
+ return 0;
57
+ }
58
+
59
+ module.exports = {
60
+ reportTaskRows,
61
+ exitCodeFromProtectionReport
62
+ };
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @fileoverview Resolve protection manifests by datasource key under `integration/.protection/`.
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 { getProtectionRoot } = require('./paths');
12
+ const { loadProtectionManifest } = require('./load');
13
+
14
+ const PROTECTION_FILENAME_RE = /^([a-z0-9][a-z0-9-]*)-protection-([a-z0-9][a-z0-9-]*)$/i;
15
+
16
+ /**
17
+ * @param {string} root
18
+ * @param {string} candidate
19
+ * @returns {boolean}
20
+ */
21
+ function isPathInsideRoot(root, candidate) {
22
+ const resolvedRoot = path.resolve(root);
23
+ const resolved = path.resolve(candidate);
24
+ return resolved === resolvedRoot || resolved.startsWith(`${resolvedRoot}${path.sep}`);
25
+ }
26
+
27
+ /**
28
+ * @param {string} stem
29
+ * @returns {string|null}
30
+ */
31
+ function datasourceKeyFromProtectionFilename(stem) {
32
+ const m = PROTECTION_FILENAME_RE.exec(stem);
33
+ if (!m) {
34
+ return null;
35
+ }
36
+ return `${m[1]}-${m[2]}`;
37
+ }
38
+
39
+ /**
40
+ * @param {string} root
41
+ * @returns {string[]}
42
+ */
43
+ function listProtectionManifestPaths(root) {
44
+ if (!fs.existsSync(root)) {
45
+ return [];
46
+ }
47
+ return fs
48
+ .readdirSync(root)
49
+ .filter((name) => /\.(yaml|yml|json)$/i.test(name))
50
+ .map((name) => path.join(root, name))
51
+ .sort((a, b) => a.localeCompare(b));
52
+ }
53
+
54
+ /**
55
+ * @param {string} root
56
+ * @throws {Error}
57
+ */
58
+ function assertNoDuplicateDatasourceKeys(root) {
59
+ const seen = new Map();
60
+ for (const filePath of listProtectionManifestPaths(root)) {
61
+ let manifest;
62
+ try {
63
+ manifest = loadProtectionManifest(filePath);
64
+ } catch {
65
+ continue;
66
+ }
67
+ const ds = String(manifest?.spec?.datasourceKey || '').trim();
68
+ if (!ds) {
69
+ continue;
70
+ }
71
+ if (seen.has(ds)) {
72
+ throw new Error(
73
+ `Duplicate spec.datasourceKey "${ds}" in .protection/ (${seen.get(ds)} and ${filePath})`
74
+ );
75
+ }
76
+ seen.set(ds, filePath);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {Object} manifest
82
+ * @param {string} key
83
+ * @param {string} location
84
+ */
85
+ function assertManifestDatasourceKey(manifest, key, location) {
86
+ const specKey = String(manifest?.spec?.datasourceKey || '').trim();
87
+ if (specKey && specKey !== key) {
88
+ throw new Error(`spec.datasourceKey "${specKey}" does not match CLI argument "${key}"${location}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @param {string} key
94
+ * @param {string} manifestPath
95
+ * @param {Object} manifest
96
+ */
97
+ function protectionManifestResult(key, manifestPath, manifest) {
98
+ assertManifestDatasourceKey(manifest, key, ` in ${manifestPath}`);
99
+ return { datasourceKey: key, manifestPath, manifest };
100
+ }
101
+
102
+ /**
103
+ * @param {string} key
104
+ * @param {string} root
105
+ * @returns {string[]}
106
+ */
107
+ function collectProtectionManifestMatches(key, root) {
108
+ const matches = [];
109
+ for (const filePath of listProtectionManifestPaths(root)) {
110
+ const stem = path.basename(filePath, path.extname(filePath));
111
+ if (stem === key || datasourceKeyFromProtectionFilename(stem) === key) {
112
+ matches.push(filePath);
113
+ continue;
114
+ }
115
+ try {
116
+ const manifest = loadProtectionManifest(filePath);
117
+ if (String(manifest?.spec?.datasourceKey || '').trim() === key) {
118
+ matches.push(filePath);
119
+ }
120
+ } catch {
121
+ /* skip unreadable */
122
+ }
123
+ }
124
+ return matches;
125
+ }
126
+
127
+ /**
128
+ * @param {string} datasourceKey
129
+ * @param {string} [explicitPath]
130
+ * @returns {{ datasourceKey: string, manifestPath: string, manifest: Object }}
131
+ */
132
+ function resolveProtectionManifest(datasourceKey, explicitPath) {
133
+ const key = String(datasourceKey || '').trim();
134
+ if (!key) throw new Error('Datasource key is required');
135
+
136
+ const root = getProtectionRoot();
137
+ assertNoDuplicateDatasourceKeys(root);
138
+
139
+ if (explicitPath) {
140
+ const manifestPath = path.resolve(explicitPath);
141
+ if (!isPathInsideRoot(root, manifestPath)) {
142
+ throw new Error(`Protection path must be under ${root}`);
143
+ }
144
+ return protectionManifestResult(key, manifestPath, loadProtectionManifest(manifestPath));
145
+ }
146
+
147
+ for (const ext of ['.yaml', '.yml', '.json']) {
148
+ const preferred = path.join(root, `${key}${ext}`);
149
+ if (fs.existsSync(preferred)) {
150
+ return protectionManifestResult(key, preferred, loadProtectionManifest(preferred));
151
+ }
152
+ }
153
+
154
+ const matches = collectProtectionManifestMatches(key, root);
155
+ if (matches.length === 0) {
156
+ throw new Error(
157
+ `No protection manifest for datasource "${key}" under ${root}. Expected ${key}.yaml or ${key}.json`
158
+ );
159
+ }
160
+ if (matches.length > 1) {
161
+ throw new Error(
162
+ `Ambiguous protection manifest for "${key}": ${matches.map((p) => path.basename(p)).join(', ')}`
163
+ );
164
+ }
165
+
166
+ const manifestPath = matches[0];
167
+ return protectionManifestResult(key, manifestPath, loadProtectionManifest(manifestPath));
168
+ }
169
+
170
+ /**
171
+ * @param {string} arg - CLI positional (datasource key or file path)
172
+ * @returns {{ datasourceKey: string, manifestPath: string, manifest: Object }}
173
+ */
174
+ function resolveProtectionArgument(arg) {
175
+ const raw = String(arg || '').trim();
176
+ if (!raw) {
177
+ throw new Error('Datasource key is required');
178
+ }
179
+ const root = getProtectionRoot();
180
+ const asPath = path.resolve(raw);
181
+ if (fs.existsSync(asPath) && fs.statSync(asPath).isFile()) {
182
+ if (!isPathInsideRoot(root, asPath)) {
183
+ throw new Error(`Protection file must be under ${root}`);
184
+ }
185
+ const manifest = loadProtectionManifest(asPath);
186
+ const ds = String(manifest?.spec?.datasourceKey || '').trim();
187
+ if (!ds) {
188
+ throw new Error('Manifest spec.datasourceKey is required');
189
+ }
190
+ return resolveProtectionManifest(ds, asPath);
191
+ }
192
+ return resolveProtectionManifest(raw);
193
+ }
194
+
195
+ module.exports = {
196
+ resolveProtectionManifest,
197
+ resolveProtectionArgument,
198
+ listProtectionManifestPaths,
199
+ datasourceKeyFromProtectionFilename,
200
+ isPathInsideRoot,
201
+ assertNoDuplicateDatasourceKeys
202
+ };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @fileoverview Dataplane validation path for protection validate command.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const { validateProtectionManifestLocal } = require('./validate-local');
11
+ const { resolveProtectionDataplaneContext } = require('./auth-context');
12
+ const { validateProtection, simulateProtection } = require('../api/protection.api');
13
+ const { exitCodeFromProtectionReport } = require('./report-exit');
14
+ const {
15
+ printProtectionValidateReport,
16
+ formatProgress,
17
+ formatSuccessLine,
18
+ formatBlockingError,
19
+ metadata,
20
+ headerKeyValue,
21
+ sectionTitle,
22
+ SEP
23
+ } = require('./protection-display');
24
+
25
+ /**
26
+ * @param {Object} logger
27
+ * @param {Object} resolved
28
+ * @param {Object} ctx
29
+ */
30
+ function printProtectionTargetHeader(logger, resolved, ctx) {
31
+ logger.log(sectionTitle('Target'));
32
+ logger.log(SEP);
33
+ logger.log(headerKeyValue('Environment:', ctx.environment));
34
+ logger.log(headerKeyValue('Dataplane:', ctx.dataplaneUrl));
35
+ logger.log('');
36
+ logger.log(metadata(`Protection: work — ${resolved.manifestPath}`));
37
+ logger.log(headerKeyValue('Datasource:', resolved.datasourceKey));
38
+ logger.log('');
39
+ }
40
+
41
+ /**
42
+ * @param {Object} manifest
43
+ * @param {Object} opts
44
+ * @param {Object} logger
45
+ * @returns {number}
46
+ */
47
+ function runLocalSchemaGate(manifest, opts, logger) {
48
+ const local = validateProtectionManifestLocal(manifest);
49
+ if (!local.valid) {
50
+ if (opts.json) {
51
+ process.stdout.write(JSON.stringify({ valid: false, errors: local.errors }, null, 2));
52
+ return 1;
53
+ }
54
+ logger.error(formatBlockingError('Local schema check failed:'));
55
+ local.errors.forEach((e) => logger.error(chalk.gray(` ${e}`)));
56
+ return 1;
57
+ }
58
+ if (opts.json) {
59
+ logger.log(formatProgress('Local schema valid'));
60
+ } else {
61
+ logger.log(formatProgress('Local schema check...'));
62
+ logger.log(formatSuccessLine('Local schema valid'));
63
+ }
64
+ return 0;
65
+ }
66
+
67
+ /**
68
+ * @param {Object} ctx
69
+ * @param {Object} manifest
70
+ * @param {Object} opts
71
+ * @param {Object} logger
72
+ * @param {string} datasourceKey
73
+ * @param {string} manifestPath
74
+ * @returns {Promise<number>}
75
+ */
76
+ async function runDataplaneProtectionValidate(ctx, manifest, opts, logger, datasourceKey, manifestPath) {
77
+ printProtectionTargetHeader(logger, { datasourceKey, manifestPath }, ctx);
78
+ logger.log(formatProgress('Dataplane validation...'));
79
+ const report = await validateProtection(ctx.dataplaneUrl, ctx.authConfig, manifest, {
80
+ strict: opts.warningsAsErrors === true
81
+ });
82
+ let simulationBlock = null;
83
+ if (opts.simulate && exitCodeFromProtectionReport(report, opts) === 0) {
84
+ const sim = await simulateProtection(ctx.dataplaneUrl, ctx.authConfig, manifest, {
85
+ strict: opts.warningsAsErrors === true
86
+ });
87
+ const meta = sim?.metadata || {};
88
+ simulationBlock = {
89
+ recordsSampled: meta.recordsSampled,
90
+ grantsProjected: meta.grantsProjected,
91
+ unresolvedPrincipals: meta.unresolvedPrincipals
92
+ };
93
+ }
94
+ printProtectionValidateReport(report, {
95
+ json: opts.json,
96
+ verbose: opts.verbose,
97
+ warningsAsErrors: opts.warningsAsErrors,
98
+ simulationBlock,
99
+ logger
100
+ });
101
+ return exitCodeFromProtectionReport(report, opts);
102
+ }
103
+
104
+ /**
105
+ * @param {string} datasourceKey
106
+ * @param {Object} manifest
107
+ * @param {Object} opts
108
+ * @param {Object} logger
109
+ * @returns {Promise<number>}
110
+ */
111
+ async function runProtectionValidate(datasourceKey, manifest, opts, logger) {
112
+ const localCode = runLocalSchemaGate(manifest, opts, logger);
113
+ if (localCode !== 0) {
114
+ return localCode;
115
+ }
116
+ const ctx = await resolveProtectionDataplaneContext(opts);
117
+ return runDataplaneProtectionValidate(
118
+ ctx,
119
+ manifest,
120
+ opts,
121
+ logger,
122
+ datasourceKey,
123
+ opts.manifestPath
124
+ );
125
+ }
126
+
127
+ module.exports = {
128
+ printProtectionTargetHeader,
129
+ runProtectionValidate
130
+ };
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @fileoverview Single-manifest protection command implementations.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const inquirer = require('inquirer');
11
+ const { resolveProtectionArgument } = require('./resolve');
12
+ const { runProtectionValidate } = require('./run-commands-validate');
13
+ const { resolveProtectionDataplaneContext } = require('./auth-context');
14
+ const {
15
+ uploadProtection,
16
+ getProtection,
17
+ getProtectionStatus,
18
+ deleteProtection,
19
+ findProtectionKeyByDatasource
20
+ } = require('../api/protection.api');
21
+ const { preflightDatasourceReady } = require('./preflight-datasource-ready');
22
+ const { syncDatasourceAfterProtectionUpload } = require('./sync-after-upload');
23
+ const {
24
+ formatProtectionListTTY,
25
+ formatProtectionShowTTY,
26
+ formatProtectionDeleteSummaryTTY,
27
+ formatProgress,
28
+ formatSuccessLine,
29
+ formatWarningLine
30
+ } = require('./protection-display');
31
+ const { listProtectionManifests } = require('../api/protection.api');
32
+ const { getProtectionRoot } = require('./paths');
33
+
34
+ /**
35
+ * @param {string} datasourceKey
36
+ * @param {Object} manifest
37
+ * @param {Object} opts
38
+ * @param {Object} logger
39
+ * @returns {Promise<number>}
40
+ */
41
+ async function runProtectionUpload(datasourceKey, manifest, opts, logger) {
42
+ const code = await runProtectionValidate(datasourceKey, manifest, {
43
+ ...opts,
44
+ simulate: false,
45
+ json: false
46
+ }, logger);
47
+ if (code !== 0) {
48
+ return code;
49
+ }
50
+
51
+ const ctx = await resolveProtectionDataplaneContext(opts);
52
+ await preflightDatasourceReady(ctx.dataplaneUrl, ctx.authConfig, manifest);
53
+
54
+ if (opts.dryRun) {
55
+ logger.log(formatWarningLine('Dry run: would upload protection (no mutation).'));
56
+ logger.log(
57
+ chalk.gray(
58
+ ` key=${manifest?.metadata?.key || '—'} datasource=${datasourceKey} rules=${(manifest?.spec?.rules || []).length}`
59
+ )
60
+ );
61
+ return 0;
62
+ }
63
+
64
+ logger.log(formatProgress('Uploading protection manifest...'));
65
+ const uploaded = await uploadProtection(ctx.dataplaneUrl, ctx.authConfig, manifest);
66
+ logger.log(
67
+ formatSuccessLine(
68
+ `Protection for '${datasourceKey}' uploaded (deploymentId ${uploaded?.deploymentId || '—'}, revision ${uploaded?.revision ?? '—'})`
69
+ )
70
+ );
71
+
72
+ if (!opts.noSync) {
73
+ logger.log(formatProgress(`Syncing datasource ${datasourceKey}...`));
74
+ const sync = await syncDatasourceAfterProtectionUpload(
75
+ ctx.dataplaneUrl,
76
+ ctx.authConfig,
77
+ datasourceKey
78
+ );
79
+ if (sync.syncJobId) {
80
+ logger.log(formatSuccessLine(`Sync started (syncJobId: ${sync.syncJobId})`));
81
+ } else if (sync.warning) {
82
+ logger.log(formatWarningLine(`Sync warning: ${sync.warning}`));
83
+ }
84
+ } else {
85
+ logger.log(
86
+ chalk.gray(
87
+ 'Sync skipped (--no-sync). Protection active on dataplane; projections update on next datasource sync.'
88
+ )
89
+ );
90
+ }
91
+ return 0;
92
+ }
93
+
94
+ /**
95
+ * @param {string} datasourceKey
96
+ * @param {Object} opts
97
+ * @param {Object} logger
98
+ * @returns {Promise<string|null>}
99
+ */
100
+ async function resolveProtectionKeyForDatasource(datasourceKey, ctx) {
101
+ try {
102
+ const resolved = resolveProtectionArgument(datasourceKey);
103
+ return String(resolved.manifest?.metadata?.key || '').trim() || null;
104
+ } catch {
105
+ return findProtectionKeyByDatasource(ctx.dataplaneUrl, ctx.authConfig, datasourceKey);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * @param {Object} opts
111
+ * @param {Object} logger
112
+ * @returns {Promise<number>}
113
+ */
114
+ async function runProtectionList(opts, logger) {
115
+ const ctx = await resolveProtectionDataplaneContext(opts);
116
+ const params = {};
117
+ if (opts.page !== undefined && opts.page !== null && !Number.isNaN(Number(opts.page))) {
118
+ params.page = Number(opts.page);
119
+ }
120
+ if (
121
+ opts.pageSize !== undefined &&
122
+ opts.pageSize !== null &&
123
+ !Number.isNaN(Number(opts.pageSize))
124
+ ) {
125
+ params.pageSize = Number(opts.pageSize);
126
+ }
127
+ if (opts.filter && String(opts.filter).trim()) {
128
+ params.filter = String(opts.filter).trim();
129
+ }
130
+
131
+ const listed = await listProtectionManifests(ctx.dataplaneUrl, ctx.authConfig, params);
132
+ if (opts.json) {
133
+ process.stdout.write(
134
+ `${JSON.stringify(listed.raw ?? { data: listed.items, meta: listed.meta }, null, 2)}\n`
135
+ );
136
+ return 0;
137
+ }
138
+
139
+ logger.log(
140
+ formatProtectionListTTY(
141
+ {
142
+ items: listed.items,
143
+ meta: listed.meta,
144
+ environment: ctx.environment,
145
+ dataplaneUrl: ctx.dataplaneUrl
146
+ },
147
+ { verbose: opts.verbose === true }
148
+ )
149
+ );
150
+ return 0;
151
+ }
152
+
153
+ /**
154
+ * @param {string} datasourceKey
155
+ * @param {Object} opts
156
+ * @param {Object} logger
157
+ * @returns {Promise<number>}
158
+ */
159
+ async function runProtectionShow(datasourceKey, opts, logger) {
160
+ const ctx = await resolveProtectionDataplaneContext(opts);
161
+ const protectionKey = await resolveProtectionKeyForDatasource(datasourceKey, ctx);
162
+ if (!protectionKey) {
163
+ throw new Error(`No deployed protection found for datasource "${datasourceKey}"`);
164
+ }
165
+ const manifest = await getProtection(ctx.dataplaneUrl, ctx.authConfig, protectionKey);
166
+ const status = await getProtectionStatus(ctx.dataplaneUrl, ctx.authConfig, protectionKey);
167
+ const payload = { manifest, status, environment: ctx.environment, dataplaneUrl: ctx.dataplaneUrl };
168
+ if (opts.json) {
169
+ process.stdout.write(JSON.stringify(payload, null, 2));
170
+ return 0;
171
+ }
172
+ logger.log(formatProtectionShowTTY(payload, { verbose: opts.verbose }));
173
+ return 0;
174
+ }
175
+
176
+ /**
177
+ * @param {string} datasourceKey
178
+ * @param {Object} opts
179
+ * @param {Object} logger
180
+ * @returns {Promise<number>}
181
+ */
182
+ async function runProtectionDelete(datasourceKey, opts, logger) {
183
+ const ctx = await resolveProtectionDataplaneContext(opts);
184
+ const protectionKey = await resolveProtectionKeyForDatasource(datasourceKey, ctx);
185
+ if (!protectionKey) {
186
+ throw new Error(`No deployed protection found for datasource "${datasourceKey}"`);
187
+ }
188
+
189
+ if (!opts.yes) {
190
+ const { confirmed } = await inquirer.prompt([
191
+ {
192
+ type: 'confirm',
193
+ name: 'confirmed',
194
+ message: `Delete protection for '${datasourceKey}' (${protectionKey})?`,
195
+ default: false
196
+ }
197
+ ]);
198
+ if (!confirmed) {
199
+ logger.log(chalk.gray('Delete cancelled.'));
200
+ return 0;
201
+ }
202
+ }
203
+
204
+ const res = await deleteProtection(ctx.dataplaneUrl, ctx.authConfig, protectionKey);
205
+ logger.log(formatProtectionDeleteSummaryTTY(res?.data || res, datasourceKey));
206
+ return 0;
207
+ }
208
+
209
+ module.exports = {
210
+ getProtectionRoot,
211
+ runProtectionValidate,
212
+ runProtectionUpload,
213
+ runProtectionList,
214
+ runProtectionShow,
215
+ runProtectionDelete
216
+ };