@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
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @fileoverview Single-capability CLI contract when --capability is set.
2
+ * @fileoverview Single-capability CLI contract when positional [capabilityKey] is set.
3
3
  * @author AI Fabrix Team
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
7
  /**
8
8
  * @param {Object|null|undefined} envelope - DatasourceTestRun-like
9
- * @param {string|undefined|null} requestedCapabilityKey - From CLI --capability
9
+ * @param {string|undefined|null} requestedCapabilityKey - From positional [capabilityKey]
10
10
  * @returns {{ violated: boolean, message?: string, count?: number }}
11
11
  */
12
12
  function analyzeCapabilityScope(envelope, requestedCapabilityKey) {
@@ -34,7 +34,7 @@ function analyzeCapabilityScope(envelope, requestedCapabilityKey) {
34
34
  return {
35
35
  violated: true,
36
36
  count: caps.length,
37
- message: `Capabilities scope: with --capability "${key}", expected a single row in capabilities[]; server returned ${caps.length} (${preview}${suffix}).`
37
+ message: `Capabilities scope: with capabilityKey "${key}", expected a single row in capabilities[]; server returned ${caps.length} (${preview}${suffix}).`
38
38
  };
39
39
  }
40
40
 
@@ -98,7 +98,7 @@ function colorStepLine(ok, body) {
98
98
  }
99
99
 
100
100
  /**
101
- * Normalize CLI --capability value for matching envelope.capabilities[].key
101
+ * Normalize positional [capabilityKey] for matching envelope.capabilities[].key
102
102
  * @param {*} focusCapabilityKey
103
103
  * @returns {string} trimmed or ''
104
104
  */
@@ -354,7 +354,7 @@ function pickExecutiveVerdictLine(envelope) {
354
354
  }
355
355
 
356
356
  /**
357
- * Plan §3.9: explicit line when E2E has no capability rows (skipped when --capability drill-down is active).
357
+ * Plan §3.9: explicit line when E2E has no capability rows (skipped when capability drill-down is active).
358
358
  * @param {string[]} lines
359
359
  * @param {Object} envelope
360
360
  * @param {{ focusCapabilityKey?: string }} [options]
@@ -428,7 +428,7 @@ function formatDatasourceTestRunSummary(envelope, options = {}) {
428
428
  * focusCapabilityKey?: string,
429
429
  * includeRefs?: boolean,
430
430
  * includeDebugExecutionSummary?: boolean
431
- * }} [options] - When focusCapabilityKey set (e.g. --capability), append single-cap block (plan §2.3).
431
+ * }} [options] - When focusCapabilityKey set (positional [capabilityKey]), append single-cap block (plan §2.3).
432
432
  * includeDebugExecutionSummary: show dataplane `debug.executionSummary` in header (TTY debug appendix uses CLI `--debug`).
433
433
  * @returns {string}
434
434
  */
@@ -49,7 +49,11 @@ function printDatasourceTestRunForTTY(envelope, options = {}) {
49
49
  includeDebugExecutionSummary: Boolean(mode)
50
50
  };
51
51
  if (options.json) {
52
- logger.log(JSON.stringify(envelope));
52
+ const payload =
53
+ options.auditEvidenceVerification && typeof options.auditEvidenceVerification === 'object'
54
+ ? { ...envelope, auditEvidenceVerification: options.auditEvidenceVerification }
55
+ : envelope;
56
+ logger.log(JSON.stringify(payload));
53
57
  return;
54
58
  }
55
59
  if (options.summary) {
@@ -26,10 +26,10 @@ const CATEGORIES = [
26
26
  { name: 'up-platform' },
27
27
  { name: 'up-miso' },
28
28
  { name: 'up-dataplane' },
29
- { name: 'down-infra', term: 'down-infra [service|app]' },
29
+ { name: 'down-infra', term: 'down-infra [service|appKey]' },
30
30
  { name: 'doctor' },
31
31
  { name: 'status' },
32
- { name: 'restart', term: 'restart <service|app>' }
32
+ { name: 'restart', term: 'restart <service|appKey>' }
33
33
  ]
34
34
  },
35
35
  {
@@ -43,23 +43,22 @@ const CATEGORIES = [
43
43
  {
44
44
  name: 'Applications (Create & Develop)',
45
45
  commands: [
46
- { name: 'create', term: 'create <app>' },
47
- { name: 'wizard' },
48
- { name: 'build', term: 'build <app>' },
49
- { name: 'run', term: 'run <app>' },
50
- { name: 'shell', term: 'shell <app>' },
51
- { name: 'install', term: 'install <app>' },
52
- { name: 'lint', term: 'lint <app>' },
53
- { name: 'logs', term: 'logs <app>' },
54
- { name: 'stop', term: 'stop <app>' },
55
- { name: 'dockerfile', term: 'dockerfile <app>' }
46
+ { name: 'create', term: 'create <appKey|systemKey>' },
47
+ { name: 'build', term: 'build <appKey>' },
48
+ { name: 'run', term: 'run <appKey>' },
49
+ { name: 'shell', term: 'shell <appKey>' },
50
+ { name: 'install', term: 'install <appKey>' },
51
+ { name: 'lint', term: 'lint <appKey>' },
52
+ { name: 'logs', term: 'logs <appKey>' },
53
+ { name: 'stop', term: 'stop <appKey>' },
54
+ { name: 'dockerfile', term: 'dockerfile <appKey>' }
56
55
  ]
57
56
  },
58
57
  {
59
58
  name: 'Deployment',
60
59
  commands: [
61
- { name: 'push', term: 'push <app>' },
62
- { name: 'deploy', term: 'deploy <app>' }
60
+ { name: 'push', term: 'push <appKey>' },
61
+ { name: 'deploy', term: 'deploy <appKey|systemKey>' }
63
62
  ]
64
63
  },
65
64
  {
@@ -80,12 +79,12 @@ const CATEGORIES = [
80
79
  {
81
80
  name: 'Configuration & Validation',
82
81
  commands: [
83
- { name: 'resolve', term: 'resolve <app>' },
84
- { name: 'json', term: 'json <app>' },
85
- { name: 'split-json', term: 'split-json <app>' },
86
- { name: 'convert', term: 'convert <app>' },
87
- { name: 'show', term: 'show <app>' },
88
- { name: 'validate', term: 'validate <appOrFile>' },
82
+ { name: 'resolve', term: 'resolve <appKey|systemKey>' },
83
+ { name: 'json', term: 'json <appKey|systemKey>' },
84
+ { name: 'split-json', term: 'split-json <appKey|systemKey>' },
85
+ { name: 'convert', term: 'convert <appKey|systemKey>' },
86
+ { name: 'show', term: 'show <appKey|systemKey>' },
87
+ { name: 'validate', term: 'validate [appKey|systemKey|file]' },
89
88
  { name: 'parameters', term: 'parameters validate' },
90
89
  { name: 'diff', term: 'diff <file1> <file2>' }
91
90
  ]
@@ -93,16 +92,23 @@ const CATEGORIES = [
93
92
  {
94
93
  name: 'External Systems',
95
94
  commands: [
95
+ { name: 'wizard', term: 'wizard [systemKey]' },
96
96
  { name: 'download', term: 'download <systemKey>' },
97
97
  { name: 'upload', term: 'upload <systemKey>' },
98
98
  { name: 'delete', term: 'delete <systemKey>' },
99
99
  { name: 'repair', term: 'repair <systemKey>' },
100
100
  { name: 'datasource' },
101
+ { name: 'datasource load', term: 'datasource load <datasourceKey>' },
102
+ { name: 'datasource export', term: 'datasource export <datasourceKey>' },
101
103
  { name: 'dimension' },
102
- { name: 'dimension-value' },
103
- { name: 'test', term: 'test <app>' },
104
- { name: 'test-e2e', term: 'test-e2e <app>' },
105
- { name: 'test-integration', term: 'test-integration <app>' }
104
+ { name: 'dimension value', term: 'dimension value <command>' },
105
+ { name: 'identity' },
106
+ { name: 'protection' },
107
+ { name: 'test', term: 'test <appKey|systemKey>' },
108
+ { name: 'test-e2e', term: 'test-e2e <appKey|systemKey>' },
109
+ { name: 'test-governance', term: 'test-governance <appKey|systemKey>' },
110
+ { name: 'test-trust', term: 'test-trust <appKey|systemKey>' },
111
+ { name: 'test-integration', term: 'test-integration <appKey|systemKey>' }
106
112
  ]
107
113
  },
108
114
  {
@@ -151,8 +151,30 @@ function emitAllPlatformSystemManifestLinesIfTTY(logger, opts = {}) {
151
151
  }
152
152
  }
153
153
 
154
+ /**
155
+ * Integration load/export manifest line (plan 144).
156
+ *
157
+ * @param {{ tier?: string, configPath?: string, labelPrefix?: string }} opts
158
+ * @returns {void}
159
+ */
160
+ function emitManifestSourceMetadata(opts = {}) {
161
+ if (!isStdoutTty()) return;
162
+ const log = require('./logger');
163
+ const tier = opts.tier && typeof opts.tier === 'string' ? opts.tier : 'resolved';
164
+ const configPath =
165
+ opts.configPath && typeof opts.configPath === 'string' ? opts.configPath : '';
166
+ const labelPrefix =
167
+ opts.labelPrefix && typeof opts.labelPrefix === 'string' ? opts.labelPrefix : '';
168
+ const tierLabel = tier === 'int' ? 'integration' : humanTierLabel(tier);
169
+ const line = labelPrefix
170
+ ? metadata(`${labelPrefix} — ${tierLabel} — ${configPath}`)
171
+ : formatManifestSourceMetadataLine({ tier: tierLabel, configPath });
172
+ log.log(line);
173
+ }
174
+
154
175
  module.exports = {
155
176
  emitManifestMetadataLineIfTTY,
177
+ emitManifestSourceMetadata,
156
178
  emitSystemBuilderAppManifestLineIfTTY,
157
179
  emitAllPlatformSystemManifestLinesIfTTY,
158
180
  formatManifestSourceMetadataLine,
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Internal semver comparator (no `semver` npm dependency).
3
+ *
4
+ * Supports `MAJOR.MINOR.PATCH` and optional prerelease suffix
5
+ * `-<prerelease>` (e.g. `2.45.0-beta.1`). Optional leading `v` is tolerated.
6
+ * Prerelease versions always rank **before** the same MAJOR.MINOR.PATCH
7
+ * without a prerelease (semver §11.3).
8
+ *
9
+ * @fileoverview Internal semver compare for Builder CLI compatibility gate
10
+ * @author AI Fabrix Team
11
+ * @version 2.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const SEMVER_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z][-0-9A-Za-z.]*))?$/;
17
+
18
+ /**
19
+ * Parse a semver-like string into a comparable structure.
20
+ * @param {string} input - Version string (e.g. "2.45.0" or "v2.45.0-beta.1")
21
+ * @returns {{ major:number, minor:number, patch:number, prerelease:string[] }|null}
22
+ * Parsed parts or null when input does not match the supported grammar
23
+ */
24
+ function parseSemver(input) {
25
+ if (typeof input !== 'string') {
26
+ return null;
27
+ }
28
+ const match = SEMVER_PATTERN.exec(input.trim());
29
+ if (!match) {
30
+ return null;
31
+ }
32
+ const [, major, minor, patch, prerelease] = match;
33
+ return {
34
+ major: Number(major),
35
+ minor: Number(minor),
36
+ patch: Number(patch),
37
+ prerelease: prerelease ? prerelease.split('.') : []
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Check whether a string is a supported semver (incl. optional prerelease).
43
+ * @param {string} input
44
+ * @returns {boolean}
45
+ */
46
+ function isValidSemver(input) {
47
+ return parseSemver(input) !== null;
48
+ }
49
+
50
+ /**
51
+ * Compare two prerelease identifier arrays per semver §11.4.
52
+ * @private
53
+ * @param {string[]} a
54
+ * @param {string[]} b
55
+ * @returns {-1|0|1}
56
+ */
57
+ function comparePrereleaseIds(a, b) {
58
+ const len = Math.max(a.length, b.length);
59
+ for (let i = 0; i < len; i += 1) {
60
+ if (a[i] === undefined) return -1;
61
+ if (b[i] === undefined) return 1;
62
+ const aNum = /^\d+$/.test(a[i]);
63
+ const bNum = /^\d+$/.test(b[i]);
64
+ if (aNum && bNum) {
65
+ const diff = Number(a[i]) - Number(b[i]);
66
+ if (diff !== 0) return diff > 0 ? 1 : -1;
67
+ } else if (aNum) {
68
+ return -1;
69
+ } else if (bNum) {
70
+ return 1;
71
+ } else if (a[i] !== b[i]) {
72
+ return a[i] > b[i] ? 1 : -1;
73
+ }
74
+ }
75
+ return 0;
76
+ }
77
+
78
+ /**
79
+ * Compare two semver strings.
80
+ *
81
+ * @param {string} a
82
+ * @param {string} b
83
+ * @returns {-1|0|1} `-1` when `a < b`, `0` when equal, `1` when `a > b`
84
+ * @throws {Error} When either argument is not a valid semver
85
+ *
86
+ * @example
87
+ * compareSemver('2.44.0', '2.45.0') === -1
88
+ * compareSemver('2.45.0', '2.45.0') === 0
89
+ * compareSemver('2.45.0-beta.1', '2.45.0') === -1
90
+ */
91
+ function compareSemver(a, b) {
92
+ const parsedA = parseSemver(a);
93
+ const parsedB = parseSemver(b);
94
+ if (!parsedA) throw new Error(`Invalid semver: "${a}"`);
95
+ if (!parsedB) throw new Error(`Invalid semver: "${b}"`);
96
+
97
+ if (parsedA.major !== parsedB.major) return parsedA.major > parsedB.major ? 1 : -1;
98
+ if (parsedA.minor !== parsedB.minor) return parsedA.minor > parsedB.minor ? 1 : -1;
99
+ if (parsedA.patch !== parsedB.patch) return parsedA.patch > parsedB.patch ? 1 : -1;
100
+
101
+ // §11.3 — Prerelease has lower precedence than the same MAJOR.MINOR.PATCH release.
102
+ if (parsedA.prerelease.length === 0 && parsedB.prerelease.length === 0) return 0;
103
+ if (parsedA.prerelease.length === 0) return 1;
104
+ if (parsedB.prerelease.length === 0) return -1;
105
+
106
+ return comparePrereleaseIds(parsedA.prerelease, parsedB.prerelease);
107
+ }
108
+
109
+ /**
110
+ * Convenience: true when `current >= required`.
111
+ * Returns `true` when `required` is empty/null/undefined (no enforcement).
112
+ * @param {string} current
113
+ * @param {string} [required]
114
+ * @returns {boolean}
115
+ * @throws {Error} If either provided value is not valid semver
116
+ */
117
+ function isAtLeast(current, required) {
118
+ if (required === undefined || required === null || required === '') return true;
119
+ return compareSemver(current, required) >= 0;
120
+ }
121
+
122
+ module.exports = {
123
+ parseSemver,
124
+ isValidSemver,
125
+ compareSemver,
126
+ isAtLeast
127
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @fileoverview Progress line + optional ora spinner for dataplane pipeline upload/publish.
3
+ * @author AI Fabrix Team
4
+ * @version 1.1.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('./logger');
11
+ const { formatProgress } = require('./cli-test-layout-chalk');
12
+
13
+ /**
14
+ * @param {Object} [options]
15
+ * @param {boolean} [options.json] - When true, skip progress line and spinner
16
+ * @returns {boolean}
17
+ */
18
+ function shouldShowUploadPipelineProgress(options = {}) {
19
+ return options.json !== true;
20
+ }
21
+
22
+ /**
23
+ * @param {Object} [options]
24
+ * @returns {boolean}
25
+ */
26
+ function shouldUseUploadPipelineSpinner(options = {}) {
27
+ if (!shouldShowUploadPipelineProgress(options)) {
28
+ return false;
29
+ }
30
+ return Boolean(process && process.stdout && process.stdout.isTTY);
31
+ }
32
+
33
+ /**
34
+ * Plain label for spinner / progress (no glyph).
35
+ * @param {string} systemKey
36
+ * @returns {string}
37
+ */
38
+ function buildUploadPipelineSpinnerLabel(systemKey) {
39
+ const key = systemKey && String(systemKey).trim() ? String(systemKey).trim() : 'integration';
40
+ return `Publishing ${key} to dataplane…`;
41
+ }
42
+
43
+ /**
44
+ * @param {string} systemKey
45
+ * @returns {string}
46
+ */
47
+ function buildUploadPipelineProgressText(systemKey) {
48
+ return formatProgress(buildUploadPipelineSpinnerLabel(systemKey));
49
+ }
50
+
51
+ /**
52
+ * Run pipeline upload + OpenAPI sync with visible progress (⏳ line or ora with label on TTY).
53
+ *
54
+ * @async
55
+ * @param {Function} work - Zero-arg async function (publish + follow-up I/O)
56
+ * @param {Object} [opts]
57
+ * @param {string} [opts.systemKey]
58
+ * @param {boolean} [opts.json]
59
+ * @returns {Promise<*>}
60
+ */
61
+ async function runWithUploadPipelineSpinner(work, opts = {}) {
62
+ if (!shouldShowUploadPipelineProgress(opts)) {
63
+ return work();
64
+ }
65
+
66
+ if (!shouldUseUploadPipelineSpinner(opts)) {
67
+ logger.log(buildUploadPipelineProgressText(opts.systemKey));
68
+ return work();
69
+ }
70
+
71
+ const ora = require('ora');
72
+ const label = buildUploadPipelineSpinnerLabel(opts.systemKey);
73
+ const spinner = ora({
74
+ text: chalk.white(label),
75
+ spinner: 'dots'
76
+ }).start();
77
+
78
+ try {
79
+ return await work();
80
+ } finally {
81
+ spinner.stop();
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ shouldShowUploadPipelineProgress,
87
+ shouldUseUploadPipelineSpinner,
88
+ buildUploadPipelineSpinnerLabel,
89
+ buildUploadPipelineProgressText,
90
+ runWithUploadPipelineSpinner
91
+ };
@@ -23,10 +23,12 @@ function shouldUseValidationPollSpinner() {
23
23
  * @param {number} deadlineMs - Wall-clock deadline (Date.now() + remaining budget)
24
24
  * @returns {string}
25
25
  */
26
- function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs) {
26
+ function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject) {
27
27
  const remainingSec = Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1000));
28
+ const label =
29
+ subject && String(subject).trim() ? String(subject).trim() : 'validation run';
28
30
  if (!envelope || typeof envelope !== 'object') {
29
- return `Waiting for validation run… starting (budget ~${remainingSec}s)`;
31
+ return `Waiting for ${label}… starting (budget ~${remainingSec}s)`;
30
32
  }
31
33
  const c =
32
34
  envelope.reportCompleteness !== undefined && envelope.reportCompleteness !== null
@@ -34,26 +36,29 @@ function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs) {
34
36
  : '?';
35
37
  const st =
36
38
  envelope.status !== undefined && envelope.status !== null ? String(envelope.status) : '?';
37
- return `Waiting for validation run… completeness=${c} status=${st} (poll ${attemptIndex + 1} · ~${remainingSec}s left)`;
39
+ return `Waiting for ${label}… completeness=${c} status=${st} (poll ${attemptIndex + 1} · ~${remainingSec}s left)`;
38
40
  }
39
41
 
40
42
  const NON_TTY_THROTTLE_MS = 5000;
41
43
 
42
44
  /**
43
45
  * @param {number} deadlineMs - Absolute deadline for poll budget display
46
+ * @param {Object} [opts]
47
+ * @param {string} [opts.subject] - Datasource key or label shown in poll text
44
48
  * @returns {{ usesSpinner: boolean, onPollProgress: Function, finish: () => void }}
45
49
  */
46
- function createValidationPollHandlers(deadlineMs) {
50
+ function createValidationPollHandlers(deadlineMs, opts = {}) {
51
+ const subject = opts.subject;
47
52
  if (shouldUseValidationPollSpinner()) {
48
53
  const ora = require('ora');
49
54
  const spinner = ora({
50
- text: buildValidationPollSpinnerText(null, 0, deadlineMs),
55
+ text: buildValidationPollSpinnerText(null, 0, deadlineMs, subject),
51
56
  spinner: 'dots'
52
57
  }).start();
53
58
  return {
54
59
  usesSpinner: true,
55
60
  onPollProgress: (envelope, attemptIndex, _meta) => {
56
- spinner.text = buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs);
61
+ spinner.text = buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject);
57
62
  },
58
63
  finish: () => {
59
64
  spinner.stop();
@@ -68,7 +73,9 @@ function createValidationPollHandlers(deadlineMs) {
68
73
  const now = Date.now();
69
74
  if (now - lastNonTtyLogAt < NON_TTY_THROTTLE_MS) return;
70
75
  lastNonTtyLogAt = now;
71
- logger.log(chalk.gray(buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs)));
76
+ logger.log(
77
+ chalk.gray(buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject))
78
+ );
72
79
  },
73
80
  finish: () => {}
74
81
  };
@@ -164,36 +164,73 @@ function validateWizardConfigSchema(config) {
164
164
  };
165
165
  }
166
166
 
167
+ /**
168
+ * Candidate bases for relative openapi-file paths (first existing wins).
169
+ * @param {string} configPath - Path to wizard.yaml
170
+ * @returns {string[]}
171
+ */
172
+ function wizardOpenApiFilePathCandidates(filePath, configPath) {
173
+ const raw = String(filePath || '').trim();
174
+ if (!raw) {
175
+ return [];
176
+ }
177
+ if (path.isAbsolute(raw)) {
178
+ return [path.resolve(raw)];
179
+ }
180
+ const configDir = path.dirname(path.resolve(configPath));
181
+ return [...new Set([
182
+ path.resolve(process.cwd(), raw),
183
+ path.resolve(configDir, raw)
184
+ ])];
185
+ }
186
+
187
+ /**
188
+ * Resolve openapi-file path for wizard config (absolute path).
189
+ * Relative paths: process.cwd() first, then wizard.yaml directory.
190
+ * @param {string} filePath
191
+ * @param {string} configPath
192
+ * @returns {Promise<string>}
193
+ */
194
+ async function resolveWizardOpenApiFilePath(filePath, configPath) {
195
+ const candidates = wizardOpenApiFilePathCandidates(filePath, configPath);
196
+ if (candidates.length === 0) {
197
+ return '';
198
+ }
199
+ for (const candidate of candidates) {
200
+ try {
201
+ await fs.access(candidate);
202
+ return candidate;
203
+ } catch (error) {
204
+ if (error.code !== 'ENOENT') {
205
+ throw error;
206
+ }
207
+ }
208
+ }
209
+ return candidates[0];
210
+ }
211
+
167
212
  /**
168
213
  * Validate file path exists (for openapi-file source type)
169
214
  * @function validateFilePath
170
215
  * @param {string} filePath - Path to validate
171
- * @param {string} basePath - Base path for relative paths
172
- * @returns {Promise<Object>} Validation result with valid flag and errors
216
+ * @param {string} configPath - Path to wizard.yaml (relative paths resolve from cwd, then config dir)
217
+ * @returns {Promise<Object>} Validation result with valid flag, errors, and resolvedPath
173
218
  */
174
- async function validateFilePath(filePath, basePath) {
175
- const baseDir = path.resolve(basePath);
176
- const resolvedPath = path.isAbsolute(filePath)
177
- ? path.resolve(filePath)
178
- : path.resolve(baseDir, filePath);
179
- if (!path.isAbsolute(filePath)) {
180
- const baseWithSep = baseDir.endsWith(path.sep) ? baseDir : `${baseDir}${path.sep}`;
181
- if (!resolvedPath.startsWith(baseWithSep)) {
182
- return {
183
- valid: false,
184
- errors: [`OpenAPI file path must be within: ${baseDir}`]
185
- };
186
- }
219
+ async function validateFilePath(filePath, configPath) {
220
+ const resolvedPath = await resolveWizardOpenApiFilePath(filePath, configPath);
221
+ if (!resolvedPath) {
222
+ return { valid: false, errors: ['OpenAPI file path is required'], resolvedPath: '' };
187
223
  }
188
224
  try {
189
225
  await fs.access(resolvedPath);
190
226
  } catch (error) {
191
227
  return {
192
228
  valid: false,
193
- errors: [`OpenAPI file not found: ${resolvedPath}`]
229
+ errors: [`OpenAPI file not found: ${resolvedPath}`],
230
+ resolvedPath
194
231
  };
195
232
  }
196
- return { valid: true, errors: [] };
233
+ return { valid: true, errors: [], resolvedPath };
197
234
  }
198
235
 
199
236
  /**
@@ -229,10 +266,11 @@ function validateSchemaAndResolveEnvVars(config, shouldResolveEnvVars) {
229
266
  async function performSemanticValidations(config, configPath, validateFilePaths) {
230
267
  const errors = [];
231
268
  if (validateFilePaths && config.source?.type === 'openapi-file' && config.source?.filePath) {
232
- const basePath = path.dirname(path.resolve(configPath));
233
- const fileResult = await validateFilePath(config.source.filePath, basePath);
269
+ const fileResult = await validateFilePath(config.source.filePath, configPath);
234
270
  if (!fileResult.valid) {
235
271
  errors.push(...fileResult.errors);
272
+ } else if (fileResult.resolvedPath) {
273
+ config.source.filePath = fileResult.resolvedPath;
236
274
  }
237
275
  }
238
276
  if (config.mode === 'add-datasource' && !config.systemIdOrKey) {
@@ -296,6 +334,7 @@ module.exports = {
296
334
  wizardConfigExists,
297
335
  validateWizardConfig,
298
336
  validateWizardConfigSchema,
337
+ resolveWizardOpenApiFilePath,
299
338
  resolveEnvVar,
300
339
  resolveEnvVarsInObject,
301
340
  formatValidationErrors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.45.0",
3
+ "version": "2.45.5",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@ LOG_FORMAT=json
21
21
  # Production with persistent logs: use /mnt/data/logs/app.log and mount a volume at /mnt/data.
22
22
  LOG_FILE_PATH=/mnt/data/logs/app.log
23
23
  # If true, run without Redis/Celery (single process)
24
- LOCAL_MODE=false
24
+ LOCAL_MODE=true
25
25
 
26
26
  # When API_KEY is set, a matching Bearer token bypasses OAuth2 validation
27
27
  # Same kv:// as miso-controller API_KEY so pipeline Bearer bypass uses one secrets.local entry
@@ -30,6 +30,9 @@ API_KEY=kv://miso-controller-secrets-apiKeyVault
30
30
  # API Configuration
31
31
  API_V1_STR=/api/v1
32
32
  VERSION=1.9.5
33
+ # Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
34
+ # MIN_BUILDER_CLI_VERSION=2.45.0
35
+ MIN_BUILDER_CLI_VERSION=2.45.0
33
36
  # Base URL for the dataplane web server (used for default OAuth2 callback URL when redirectUri is omitted)
34
37
  # url:// resolves after kv://; includes front-door path from application.yaml (e.g. /data)
35
38
  DATAPLANE_WEB_SERVER_URL=url://public
@@ -165,6 +168,15 @@ RBAC_EXPLAIN_MODE_ENABLED=false
165
168
  # validates only (no second DB write). Default false.
166
169
  AUDIT_ASYNC_SHADOW_ENQUEUE=false
167
170
 
171
+ # Policy Library management HTTP API gate. When false (default), management
172
+ # routes are not registered (404): /api/v1/policies/*, /api/v1/external/{key}/policies,
173
+ # /api/v1/users/{id}/policies, /api/v1/groups/{id}/policies, IDE /validate/policy and
174
+ # /simulate/policy-impact/{datasourceKey}. ABAC runtime evaluation is unaffected.
175
+ # Set to true for local dev when iterating on policy CRUD APIs.
176
+ # Policy Library management HTTP APIs (default false → routes not registered, 404).
177
+ # Set true for local dev policy CRUD; pair with VITE_POLICY_MANAGEMENT_ENABLED=true in app-ui.
178
+ POLICY_MANAGEMENT_API_ENABLED=false
179
+
168
180
  # =============================================================================
169
181
  # OBSERVABILITY CONFIGURATION
170
182
  # =============================================================================
@@ -280,6 +280,10 @@ permissions:
280
280
  - name: "record:search"
281
281
  roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-deployment-admin", "aifabrix-compliance-admin", "aifabrix-developer", "aifabrix-observer"]
282
282
  description: "Search records"
283
+
284
+ - name: "governance:evaluate"
285
+ roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-developer"]
286
+ description: "Run governance scenario acceptance (subject impersonation for ABAC proof)"
283
287
 
284
288
  # IDE simulation operations
285
289
  - name: "dataplane:abac-simulate"