@aifabrix/builder 2.45.5 → 2.46.0

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 (231) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/README.md +43 -17
  5. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  6. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  7. package/jest.config.default.js +2 -1
  8. package/jest.config.isolated.js +2 -1
  9. package/jest.global-live-fabrix-hooks.js +36 -0
  10. package/jest.isolated-projects.js +130 -0
  11. package/jest.projects.js +41 -136
  12. package/lib/api/api-response-errors.js +124 -0
  13. package/lib/api/governance-scenario-pack.api.js +159 -0
  14. package/lib/api/index.js +43 -3
  15. package/lib/api/lifecycle.api.js +77 -0
  16. package/lib/api/pipeline.api.js +3 -1
  17. package/lib/api/records-bulk.api.js +14 -1
  18. package/lib/api/validation-runner.js +3 -1
  19. package/lib/app/deploy-config.js +4 -1
  20. package/lib/app/down.js +20 -1
  21. package/lib/app/push.js +4 -5
  22. package/lib/app/register.js +7 -4
  23. package/lib/app/run-env-compose.js +84 -30
  24. package/lib/build/index.js +5 -0
  25. package/lib/certification/sync-system-certification.js +13 -6
  26. package/lib/cli/index.js +4 -0
  27. package/lib/cli/infra-guided-auth.js +109 -0
  28. package/lib/cli/infra-guided.js +84 -81
  29. package/lib/cli/setup-app.help.js +0 -31
  30. package/lib/cli/setup-app.js +28 -2
  31. package/lib/cli/setup-app.test-commands.js +5 -57
  32. package/lib/cli/setup-auth.js +25 -0
  33. package/lib/cli/setup-certification-commands.js +147 -0
  34. package/lib/cli/setup-certification.help.js +141 -0
  35. package/lib/cli/setup-environment.js +19 -2
  36. package/lib/cli/setup-external-system-help.js +62 -0
  37. package/lib/cli/setup-external-system.js +25 -3
  38. package/lib/cli/setup-infra.js +9 -1
  39. package/lib/cli/setup-platform.js +6 -1
  40. package/lib/cli/setup-secrets.js +33 -1
  41. package/lib/cli/setup-utility-help-after.js +22 -2
  42. package/lib/cli/setup-utility-repair.js +198 -0
  43. package/lib/cli/setup-utility-resolve.js +40 -4
  44. package/lib/cli/setup-utility-validate.js +197 -0
  45. package/lib/cli/setup-utility.js +4 -239
  46. package/lib/commands/auth-status-client-tokens.js +214 -0
  47. package/lib/commands/auth-status-dataplane-version.js +58 -20
  48. package/lib/commands/auth-status.js +41 -47
  49. package/lib/commands/datasource-test-trust-cli.js +27 -23
  50. package/lib/commands/datasource-unified-test-cli.js +6 -3
  51. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  52. package/lib/commands/datasource.js +7 -7
  53. package/lib/commands/dev-show-display.js +8 -1
  54. package/lib/commands/governance-cli-shared.js +94 -0
  55. package/lib/commands/governance-command-action.js +132 -0
  56. package/lib/commands/governance-download-external.js +34 -0
  57. package/lib/commands/governance-generate-external.js +71 -0
  58. package/lib/commands/governance-upload-external.js +36 -0
  59. package/lib/commands/governance-verify-external.js +64 -0
  60. package/lib/commands/governance.js +144 -0
  61. package/lib/commands/lifecycle-command-action.js +123 -0
  62. package/lib/commands/lifecycle-external.js +268 -0
  63. package/lib/commands/login.js +11 -2
  64. package/lib/commands/repair-auth-apply.js +127 -0
  65. package/lib/commands/repair-changed-files.js +41 -0
  66. package/lib/commands/repair-datasource-keys.js +4 -1
  67. package/lib/commands/repair-datasource-run.js +59 -0
  68. package/lib/commands/repair-datasource-test-payload.js +259 -0
  69. package/lib/commands/repair-datasource.js +1 -51
  70. package/lib/commands/repair-env-template.js +64 -13
  71. package/lib/commands/repair-external-run.js +216 -0
  72. package/lib/commands/repair-openapi-sync.js +23 -0
  73. package/lib/commands/repair-persist.js +4 -0
  74. package/lib/commands/repair-rbac.js +52 -1
  75. package/lib/commands/repair-steps.js +145 -0
  76. package/lib/commands/repair.js +29 -296
  77. package/lib/commands/secrets-get.js +184 -0
  78. package/lib/commands/secrets-remove-all.js +3 -1
  79. package/lib/commands/setup-image-refresh.js +146 -0
  80. package/lib/commands/setup-modes.js +158 -162
  81. package/lib/commands/setup-platform-auth.js +76 -0
  82. package/lib/commands/setup-prompts-admin.js +244 -0
  83. package/lib/commands/setup-prompts-platform-mode.js +67 -0
  84. package/lib/commands/setup-prompts-validators.js +36 -0
  85. package/lib/commands/setup-prompts.js +24 -76
  86. package/lib/commands/setup-run-context.js +45 -0
  87. package/lib/commands/setup-spinners.js +39 -0
  88. package/lib/commands/setup.js +27 -5
  89. package/lib/commands/teardown.js +39 -0
  90. package/lib/commands/test-e2e-external.js +24 -8
  91. package/lib/commands/test-governance-external.js +3 -24
  92. package/lib/commands/test-trust-external.js +20 -21
  93. package/lib/commands/up-common.js +25 -13
  94. package/lib/commands/up-dataplane.js +50 -13
  95. package/lib/commands/upload-publish-context.js +75 -0
  96. package/lib/commands/upload-sidecar-followups.js +89 -0
  97. package/lib/commands/upload.js +90 -108
  98. package/lib/commands/verify-governance-combined-external.js +61 -0
  99. package/lib/commands/verify-governance-command-action.js +64 -0
  100. package/lib/commands/verify-governance-db.js +102 -0
  101. package/lib/commands/verify-governance-external.js +77 -0
  102. package/lib/commands/verify-governance-result-builders.js +71 -0
  103. package/lib/commands/verify-operations-command-action.js +68 -0
  104. package/lib/commands/verify-operations-external.js +268 -0
  105. package/lib/commands/verify-operations-persist.js +75 -0
  106. package/lib/commands/verify-operations-step-plan.js +68 -0
  107. package/lib/commands/verify-operations-steps.js +111 -0
  108. package/lib/commands/verify-trust-command-action.js +70 -0
  109. package/lib/commands/verify-trust-external.js +67 -0
  110. package/lib/commands/wizard-config-normalizer.js +43 -23
  111. package/lib/commands/wizard-core.js +2 -0
  112. package/lib/core/admin-password-generator.js +38 -0
  113. package/lib/core/admin-secrets-bundle.js +111 -0
  114. package/lib/core/admin-secrets-env-overlay.js +62 -0
  115. package/lib/core/admin-secrets.js +32 -4
  116. package/lib/core/config-attach-extensions.js +3 -0
  117. package/lib/core/config-platform-controller.js +44 -0
  118. package/lib/core/config-setup-installation-profile.js +42 -0
  119. package/lib/core/config-setup-platform-mode.js +46 -0
  120. package/lib/core/config.js +46 -36
  121. package/lib/core/secrets-admin-env.js +6 -2
  122. package/lib/core/secrets-ensure.js +4 -0
  123. package/lib/core/secrets-env-content.js +54 -48
  124. package/lib/core/secrets-env-write.js +10 -1
  125. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  126. package/lib/core/setup-installation-profile.js +43 -0
  127. package/lib/core/setup-platform-mode.js +55 -0
  128. package/lib/datasource/agent-trust-debug-log.js +1 -1
  129. package/lib/datasource/agent-trust-run.js +5 -3
  130. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  131. package/lib/datasource/audit-evidence-extract.js +7 -4
  132. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  133. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  134. package/lib/datasource/datasource-load-export-context.js +3 -0
  135. package/lib/datasource/log-cleaner.js +2 -2
  136. package/lib/datasource/log-viewer.js +8 -8
  137. package/lib/datasource/test-e2e.js +4 -1
  138. package/lib/datasource/unified-validation-run-body.js +3 -1
  139. package/lib/datasource/unified-validation-run.js +5 -2
  140. package/lib/deployment/environment-poll-status.js +138 -0
  141. package/lib/deployment/environment.js +42 -34
  142. package/lib/external-system/download.js +21 -20
  143. package/lib/external-system/generator.js +5 -1
  144. package/lib/external-system/integration-auth-context.js +45 -0
  145. package/lib/external-system/test-auth.js +12 -5
  146. package/lib/external-system/test.js +6 -6
  147. package/lib/generator/external-controller-manifest.js +15 -0
  148. package/lib/governance/governance-cli-errors.js +96 -0
  149. package/lib/governance/governance-download-display.js +38 -0
  150. package/lib/governance/governance-run-display.js +215 -25
  151. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  152. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  153. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  154. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  155. package/lib/infrastructure/helpers.js +23 -129
  156. package/lib/infrastructure/index.js +5 -6
  157. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  158. package/lib/lifecycle/certification-progress.js +231 -0
  159. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  160. package/lib/lifecycle/exit-codes.js +24 -0
  161. package/lib/lifecycle/json-envelope.js +25 -0
  162. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  163. package/lib/lifecycle/logger-quiet.js +29 -0
  164. package/lib/lifecycle/open-api-published.js +59 -0
  165. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  166. package/lib/lifecycle/product-model.js +196 -0
  167. package/lib/lifecycle/report-display.js +471 -0
  168. package/lib/lifecycle/scenario-upload.js +191 -0
  169. package/lib/lifecycle/verify-step-progress.js +87 -0
  170. package/lib/parameters/infra-kv-discovery.js +27 -2
  171. package/lib/protection/auth-context.js +10 -4
  172. package/lib/schema/application-schema.json +1 -1
  173. package/lib/schema/datasource-test-run.schema.json +89 -6
  174. package/lib/schema/external-datasource.schema.json +2636 -2622
  175. package/lib/schema/external-system.schema.json +1089 -761
  176. package/lib/schema/infra.parameter.yaml +45 -0
  177. package/lib/schema/type/credential-field-registry.json +103 -97
  178. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  179. package/lib/utils/api.js +55 -31
  180. package/lib/utils/app-relative-path-env.js +112 -0
  181. package/lib/utils/cli-secrets-error-format.js +83 -21
  182. package/lib/utils/cli-utils.js +16 -4
  183. package/lib/utils/compose-generate-docker-compose.js +4 -0
  184. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  185. package/lib/utils/config-secrets-path.js +46 -0
  186. package/lib/utils/controller-url.js +88 -1
  187. package/lib/utils/credential-secrets-env.js +137 -9
  188. package/lib/utils/dataplane-resolver.js +129 -2
  189. package/lib/utils/declarative-url-ports.js +3 -3
  190. package/lib/utils/deployment-auth-mode.js +41 -0
  191. package/lib/utils/developer-volume-id.js +33 -0
  192. package/lib/utils/env-copy.js +14 -9
  193. package/lib/utils/env-kv-orphan-lines.js +43 -0
  194. package/lib/utils/error-formatters/network-errors.js +13 -4
  195. package/lib/utils/external-env-template.js +1 -0
  196. package/lib/utils/external-system-readiness-core.js +10 -0
  197. package/lib/utils/external-system-readiness-display.js +80 -23
  198. package/lib/utils/health-check.js +4 -1
  199. package/lib/utils/help-builder.js +11 -8
  200. package/lib/utils/keycloak-realm-ready.js +101 -0
  201. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  202. package/lib/utils/manifest-source-emit.js +2 -0
  203. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  204. package/lib/utils/platform-controller-url.js +156 -0
  205. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  206. package/lib/utils/postgres-wipe.js +9 -2
  207. package/lib/utils/resolve-env-mode.js +47 -0
  208. package/lib/utils/secrets-generator.js +31 -25
  209. package/lib/utils/secrets-missing-error.js +164 -0
  210. package/lib/utils/service-url-label.js +81 -0
  211. package/lib/utils/setup-quiet-output.js +25 -0
  212. package/lib/utils/template-helpers.js +15 -0
  213. package/lib/utils/token-manager-client-credentials.js +201 -0
  214. package/lib/utils/token-manager-messages.js +8 -0
  215. package/lib/utils/token-manager-refresh.js +12 -2
  216. package/lib/utils/token-manager.js +86 -105
  217. package/lib/utils/upload-pipeline-ui.js +12 -5
  218. package/lib/utils/upload-sync-options.js +42 -0
  219. package/lib/utils/url-declarative-public-base.js +21 -3
  220. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  221. package/lib/utils/validation-poll-ui.js +8 -0
  222. package/lib/utils/validation-run-stage.js +45 -0
  223. package/package.json +1 -1
  224. package/templates/applications/dataplane/application.yaml +2 -1
  225. package/templates/applications/dataplane/env.template +24 -1
  226. package/templates/applications/miso-controller/env.template +16 -0
  227. package/templates/external-system/README.md.hbs +74 -17
  228. package/templates/python/docker-compose.hbs +19 -18
  229. package/templates/typescript/docker-compose.hbs +18 -17
  230. package/lib/commands/test-governance-command-action.js +0 -55
  231. package/lib/commands/test-trust-command-action.js +0 -118
@@ -113,22 +113,27 @@ async function buildListMatrixRows(ctx, polled, minExpected) {
113
113
 
114
114
  /**
115
115
  * @param {Object} ctx
116
- * @param {Array} executions
117
- * @param {string[]} executionIds
118
- * @returns {Promise<{ rows: import('../api/types/audit.types').AuditEvidenceMatrixRow[], execIds: string[] }>}
116
+ * @param {string[]} execIds
117
+ * @returns {Promise<import('../api/types/audit.types').AuditEvidenceMatrixRow[]>}
119
118
  */
120
- async function buildPerExecutionAndTailRows(ctx, executions, executionIds) {
119
+ async function buildPerExecutionSubRows(ctx, execIds) {
121
120
  const { dataplaneUrl, authConfig } = ctx;
122
- const execIds = resolveExecutionIdsForChecks(executions, executionIds);
123
- const sampleId = execIds[0];
124
- const base = { dataplaneUrl, authConfig, executionId: sampleId };
125
- const rows = await Promise.all([
121
+ const base = { dataplaneUrl, authConfig, executionId: execIds[0] };
122
+ return Promise.all([
126
123
  checkPerExecutionSub({ ...base, rowNum: 4, label: 'rbac', fetcher: getExecutionRbac, isValid: isRbacSubResourceValid }),
127
124
  checkPerExecutionSub({ ...base, rowNum: 5, label: 'abac', fetcher: getExecutionAbac, isValid: isAbacSubResourceValid }),
128
125
  checkPerExecutionSub({ ...base, rowNum: 6, label: 'errors', fetcher: getExecutionErrors, isValid: isErrorsValid }),
129
126
  checkPerExecutionSub({ ...base, rowNum: 7, label: 'steps', fetcher: getExecutionSteps, isValid: isStepsValid })
130
127
  ]);
128
+ }
131
129
 
130
+ /**
131
+ * @param {Object} ctx
132
+ * @param {Array} executions
133
+ * @returns {Promise<Array>}
134
+ */
135
+ async function resolveRow8PoolForMatrix(ctx, executions) {
136
+ const { dataplaneUrl, authConfig } = ctx;
132
137
  let corrQueryRows = [];
133
138
  if (ctx.correlationId) {
134
139
  const corr = await queryExecutions(dataplaneUrl, authConfig, {
@@ -137,28 +142,30 @@ async function buildPerExecutionAndTailRows(ctx, executions, executionIds) {
137
142
  });
138
143
  corrQueryRows = corr.data || [];
139
144
  }
140
- let row8Pool = corrQueryRows.length ? corrQueryRows : executions;
145
+ const row8Pool = corrQueryRows.length ? corrQueryRows : executions;
141
146
  const expected = ctx.expectedOperations || [];
142
- if (expected.length && ctx.correlationId) {
143
- const byOp = await fetchCorrelationExecutionsByOperation(
144
- dataplaneUrl,
145
- authConfig,
146
- ctx.correlationId,
147
- expected
148
- );
149
- if (byOp.length) row8Pool = byOp;
147
+ if (!expected.length || !ctx.correlationId) {
148
+ return row8Pool;
150
149
  }
151
- const row8Executions = resolveRow8Executions(ctx, executions, row8Pool);
152
- rows.push(
153
- checkRow8CorrelationGroup({
154
- correlationId: ctx.correlationId,
155
- executions: row8Executions,
156
- expectedOperations: ctx.expectedOperations
157
- })
150
+ const byOp = await fetchCorrelationExecutionsByOperation(
151
+ dataplaneUrl,
152
+ authConfig,
153
+ ctx.correlationId,
154
+ expected
158
155
  );
156
+ return byOp.length ? byOp : row8Pool;
157
+ }
159
158
 
159
+ /**
160
+ * @param {Object} ctx
161
+ * @param {string[]} execIds
162
+ * @returns {Promise<Set<string>>}
163
+ */
164
+ async function collectTraceIdsForRow9(ctx, execIds) {
165
+ const { dataplaneUrl, authConfig } = ctx;
160
166
  const traceIds = new Set();
161
- for (const id of execIds.slice(0, Math.min(execIds.length, 12))) {
167
+ const sample = execIds.slice(0, Math.min(execIds.length, 12));
168
+ for (const id of sample) {
162
169
  try {
163
170
  const tr = await getExecutionTrace(dataplaneUrl, authConfig, id);
164
171
  if (tr && tr.executionId) traceIds.add(String(tr.executionId));
@@ -167,7 +174,28 @@ async function buildPerExecutionAndTailRows(ctx, executions, executionIds) {
167
174
  /* row 9 may fail */
168
175
  }
169
176
  }
170
- rows.push(checkRow9Traces(traceIds, execIds));
177
+ return traceIds;
178
+ }
179
+
180
+ /**
181
+ * @param {Object} ctx
182
+ * @param {Array} executions
183
+ * @param {string[]} executionIds
184
+ * @returns {Promise<{ rows: import('../api/types/audit.types').AuditEvidenceMatrixRow[], execIds: string[] }>}
185
+ */
186
+ async function buildPerExecutionAndTailRows(ctx, executions, executionIds) {
187
+ const execIds = resolveExecutionIdsForChecks(executions, executionIds);
188
+ const rows = await buildPerExecutionSubRows(ctx, execIds);
189
+ const row8Pool = await resolveRow8PoolForMatrix(ctx, executions);
190
+ const row8Executions = resolveRow8Executions(ctx, executions, row8Pool);
191
+ rows.push(
192
+ checkRow8CorrelationGroup({
193
+ correlationId: ctx.correlationId,
194
+ executions: row8Executions,
195
+ expectedOperations: ctx.expectedOperations
196
+ })
197
+ );
198
+ rows.push(checkRow9Traces(await collectTraceIdsForRow9(ctx, execIds), execIds));
171
199
  return { rows, execIds };
172
200
  }
173
201
 
@@ -34,9 +34,21 @@ function recordBatchFailure(state, index, err, batchRecords) {
34
34
  * @param {Object[]} batchRecords
35
35
  */
36
36
  function recordBatchSuccess(state, index, response, batchRecords) {
37
- const inserted = response.insertedCount || 0;
38
- const updated = response.updatedCount || 0;
39
- const processed = response.totalProcessed || batchRecords.length;
37
+ if (!response || typeof response !== 'object') {
38
+ throw new Error('Bulk sync returned empty response');
39
+ }
40
+ if (response.success === false) {
41
+ throw new Error(
42
+ response.formattedError ||
43
+ response.error ||
44
+ `Bulk sync failed (HTTP ${response.status || 0})`
45
+ );
46
+ }
47
+ const payload =
48
+ response.data !== undefined && response.data !== null ? response.data : response;
49
+ const inserted = payload.insertedCount || 0;
50
+ const updated = payload.updatedCount || 0;
51
+ const processed = payload.totalProcessed || batchRecords.length;
40
52
  const failedInBatch = Math.max(0, batchRecords.length - processed);
41
53
 
42
54
  state.batches.push({
@@ -8,6 +8,7 @@
8
8
 
9
9
  const { getConfig } = require('../core/config');
10
10
  const { setupIntegrationTestAuth } = require('../external-system/test-auth');
11
+ const { applyLabDataplaneApiKeyBearer } = require('../utils/token-manager');
11
12
  const { resolveAppKeyForDatasource } = require('./resolve-app');
12
13
  const { getSystemKeyFromAppKey } = require('./integration-context');
13
14
  const { loadDatasourceForApp } = require('./unified-validation-run-resolve');
@@ -62,6 +63,8 @@ async function resolveLoadExportContext(datasourceKey, options = {}) {
62
63
  dataplaneUrl = auth.dataplaneUrl;
63
64
  }
64
65
 
66
+ authConfig = applyLabDataplaneApiKeyBearer(authConfig);
67
+
65
68
  return {
66
69
  appKey,
67
70
  systemKey,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Remove saved datasource test debug JSON under integration/<app>/logs/.
3
- * @fileoverview Local cleanup for test / test-integration / test-e2e / test-trust logs
3
+ * @fileoverview Local cleanup for test / test-integration / test-e2e / verify-trust logs
4
4
  * @author AI Fabrix Team
5
5
  * @version 1.0.0
6
6
  */
@@ -42,7 +42,7 @@ function matchesLogCleanType(fileName, type) {
42
42
  return fileName.startsWith('test-integration-');
43
43
  }
44
44
  if (type === 'e2e') return fileName.startsWith('test-e2e-');
45
- if (type === 'trust') return fileName.startsWith('test-trust-');
45
+ if (type === 'trust') return fileName.startsWith('verify-trust-');
46
46
  return false;
47
47
  }
48
48
 
@@ -59,7 +59,7 @@ function isStructuralTestLogFileName(name) {
59
59
  if (
60
60
  name.startsWith('test-e2e-') ||
61
61
  name.startsWith('test-integration-') ||
62
- name.startsWith('test-trust-')
62
+ name.startsWith('verify-trust-')
63
63
  ) {
64
64
  return false;
65
65
  }
@@ -276,7 +276,7 @@ function logTrustFindingsSection(run) {
276
276
  }
277
277
 
278
278
  /**
279
- * Format agent trust log (`datasource test-trust --debug` → `test-trust-*.json`).
279
+ * Format agent trust log (`datasource verify-trust --debug` → `verify-trust-*.json`).
280
280
  * @param {Object} data - Parsed JSON { request, response?, error? }
281
281
  * @param {string} [fileName] - Log file name for header
282
282
  */
@@ -356,7 +356,7 @@ function formatStructuralTestLog(data, fileName) {
356
356
  /**
357
357
  * Format log content by type
358
358
  * @param {Object} parsed - Parsed JSON log
359
- * @param {'test'|'test-e2e'|'test-integration'|'test-trust'} logType - Log type
359
+ * @param {'test'|'test-e2e'|'test-integration'|'verify-trust'} logType - Log type
360
360
  * @param {string} [fileName] - File name for header
361
361
  */
362
362
  function formatLogContent(parsed, logType, fileName) {
@@ -364,7 +364,7 @@ function formatLogContent(parsed, logType, fileName) {
364
364
  formatStructuralTestLog(parsed, fileName);
365
365
  return;
366
366
  }
367
- if (logType === 'test-trust') {
367
+ if (logType === 'verify-trust') {
368
368
  formatTrustLog(parsed, fileName);
369
369
  return;
370
370
  }
@@ -410,7 +410,7 @@ function parseDatasourceLogJson(content, logPath) {
410
410
  * @param {Object} options - Options
411
411
  * @param {string} [options.app] - App key (optional, resolved from key if omitted)
412
412
  * @param {string} [options.file] - Path to log file (overrides app resolution)
413
- * @param {'test'|'test-e2e'|'test-integration'|'test-trust'} options.logType - Log type
413
+ * @param {'test'|'test-e2e'|'test-integration'|'verify-trust'} options.logType - Log type
414
414
  * @throws {Error} When file not found or invalid JSON
415
415
  */
416
416
  /* eslint-disable-next-line max-statements -- Resolve path, read, parse, format */
@@ -433,11 +433,11 @@ async function runLogViewer(datasourceKey, options) {
433
433
  `No structural validation log found in ${logsDir}. Run: aifabrix datasource test <key> --debug`
434
434
  );
435
435
  }
436
- } else if (logType === 'test-trust') {
437
- logPath = await getLatestLogPath(logsDir, 'test-trust-');
436
+ } else if (logType === 'verify-trust') {
437
+ logPath = await getLatestLogPath(logsDir, 'verify-trust-');
438
438
  if (!logPath) {
439
439
  throw new Error(
440
- `No test-trust log found in ${logsDir}. Run: aifabrix datasource test-trust <key> --debug`
440
+ `No verify-trust log found in ${logsDir}. Run: aifabrix datasource verify-trust <key> --debug`
441
441
  );
442
442
  }
443
443
  } else {
@@ -42,8 +42,11 @@ function buildUnifiedE2eRunOptions(options, timeoutMs, pk) {
42
42
  capabilityKey: options.capabilityKey,
43
43
  timeout: timeoutMs,
44
44
  sync: options.sync === true,
45
+ force: options.force === true,
45
46
  authConfig: options.authConfig,
46
- dataplaneUrl: options.dataplaneUrl || options.dataplane
47
+ dataplaneUrl: options.dataplaneUrl || options.dataplane,
48
+ quietPollUi: options.quietPollUi === true,
49
+ onPollProgress: options.onPollProgress
47
50
  };
48
51
  }
49
52
 
@@ -10,6 +10,7 @@ const {
10
10
  includeDebugForRequest
11
11
  } = require('../utils/validation-run-request');
12
12
  const testHelpers = require('../utils/external-system-test-helpers');
13
+ const { withStageOnValidationRunBody } = require('../utils/validation-run-stage');
13
14
 
14
15
  /**
15
16
  * @param {Object} params
@@ -52,7 +53,7 @@ async function buildUnifiedValidationBody(params) {
52
53
  })
53
54
  : undefined;
54
55
 
55
- return buildExternalDataSourceValidationRequest({
56
+ const body = buildExternalDataSourceValidationRequest({
56
57
  systemKey,
57
58
  datasourceKey,
58
59
  runType,
@@ -62,6 +63,7 @@ async function buildUnifiedValidationBody(params) {
62
63
  explain: options.verbose === true,
63
64
  e2eOptions: e2eOptions && Object.keys(e2eOptions).length > 0 ? e2eOptions : undefined
64
65
  });
66
+ return withStageOnValidationRunBody(body, runType);
65
67
  }
66
68
 
67
69
  module.exports = { buildUnifiedValidationBody };
@@ -18,6 +18,7 @@ const { buildUnifiedValidationBody } = require('./unified-validation-run-body');
18
18
  const { postValidationRunAndOptionalPoll } = require('./unified-validation-run-post');
19
19
  const { requireBearerForDataplanePipeline } = require('../utils/token-manager');
20
20
  const logger = require('../utils/logger');
21
+ const { buildMinimalUploadSyncOptions } = require('../utils/upload-sync-options');
21
22
 
22
23
  function _resourceTypeFromDatasource(datasource) {
23
24
  return datasource && typeof datasource === 'object' && typeof datasource.resourceType === 'string'
@@ -59,7 +60,7 @@ async function maybeSyncDatasourceToDataplane({ options, authConfig, systemKey }
59
60
  // are updated before validation runs. Datasource-only publish can still fail DP-SEC-013 when the
60
61
  // dataplane system permissions[] are stale.
61
62
  const { uploadExternalSystem } = require('../commands/upload');
62
- await uploadExternalSystem(systemKey, { minimal: true });
63
+ await uploadExternalSystem(systemKey, buildMinimalUploadSyncOptions(options));
63
64
  logger.log(formatSuccessLine('Sync complete'));
64
65
  }
65
66
 
@@ -139,7 +140,9 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
139
140
  useAsync,
140
141
  noAsync: options.noAsync === true || options.async === false,
141
142
  verbosePoll: options.verbose === true,
142
- pollSubject: datasourceKey
143
+ quietPollUi: options.quietPollUi === true,
144
+ pollSubject: datasourceKey,
145
+ onPollProgress: options.onPollProgress
143
146
  });
144
147
 
145
148
  if (result && result.envelope) {
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Poll helpers for environment deploy status (deployment-by-ID).
3
+ *
4
+ * @fileoverview Parse pipeline/environments deployment poll responses and fail fast on terminal HTTP errors
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const TERMINAL_POLL_HTTP_STATUSES = new Set([400, 401, 403, 404, 410, 422]);
10
+
11
+ /**
12
+ * @param {Object|null|undefined} response - API client response
13
+ * @returns {boolean}
14
+ */
15
+ function isTerminalPollApiResponse(response) {
16
+ return Boolean(
17
+ response &&
18
+ response.success === false &&
19
+ typeof response.status === 'number' &&
20
+ TERMINAL_POLL_HTTP_STATUSES.has(response.status)
21
+ );
22
+ }
23
+
24
+ /**
25
+ * @param {Object|null|undefined} response - Successful API client response
26
+ * @returns {Object|null} Deployment record or null
27
+ */
28
+ function extractDeploymentRecord(response) {
29
+ if (!response || response.success === false) {
30
+ return null;
31
+ }
32
+ if (response?.data?.data) {
33
+ return response.data.data;
34
+ }
35
+ if (response?.data && typeof response.data === 'object' && response.data.status) {
36
+ return response.data;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * @param {Object|null|undefined} pipelineRes
43
+ * @param {Object|null|undefined} envRes
44
+ * @returns {Error}
45
+ */
46
+ /**
47
+ * @param {string} requestedEnvKey
48
+ * @param {Error} error
49
+ * @returns {Error}
50
+ */
51
+ function enrichPollTerminalError(requestedEnvKey, error) {
52
+ if (!error?.pollTerminal || error.status !== 404) {
53
+ return error;
54
+ }
55
+ const base = error.message || '';
56
+ if (!base.includes('Environment with key')) {
57
+ return error;
58
+ }
59
+ error.message =
60
+ `${base}\n\n` +
61
+ 'The deployment may have been created under a different environment key ' +
62
+ '(for example evaluation-mode fallback to miso). Check controller logs for ' +
63
+ `effectiveEnvironmentKey, register '${requestedEnvKey}' in the controller, or run ` +
64
+ 'aifabrix env deploy miso.';
65
+ return error;
66
+ }
67
+
68
+ function buildPollStatusError(pipelineRes, envRes) {
69
+ const terminal = [pipelineRes, envRes].filter(isTerminalPollApiResponse);
70
+ const source = terminal[0] || pipelineRes || envRes;
71
+ const message = source?.formattedError || source?.error || 'Unable to retrieve deployment status';
72
+ const err = new Error(message);
73
+ err.pollTerminal = true;
74
+ if (typeof source?.status === 'number') {
75
+ err.status = source.status;
76
+ }
77
+ return err;
78
+ }
79
+
80
+ /**
81
+ * Fetches deployment status by ID (pipeline endpoint first, then environments).
82
+ *
83
+ * @async
84
+ * @param {Function} getPipelineDeployment - pipeline.api getPipelineDeployment
85
+ * @param {Function} getDeployment - deployments.api getDeployment
86
+ * @param {string} controllerUrl
87
+ * @param {string} envKey
88
+ * @param {string} deploymentId
89
+ * @param {Object} apiAuthConfig
90
+ * @returns {Promise<Object|null>} Deployment record or null when status is not yet available (retry)
91
+ * @throws {Error} When both endpoints return a terminal HTTP error (e.g. environment not found)
92
+ */
93
+ async function fetchDeploymentStatusById(
94
+ getPipelineDeployment,
95
+ getDeployment,
96
+ controllerUrl,
97
+ envKey,
98
+ deploymentId,
99
+ apiAuthConfig
100
+ ) {
101
+ let pipelineRes;
102
+ let envRes;
103
+
104
+ try {
105
+ pipelineRes = await getPipelineDeployment(controllerUrl, envKey, deploymentId, apiAuthConfig);
106
+ const fromPipeline = extractDeploymentRecord(pipelineRes);
107
+ if (fromPipeline) {
108
+ return fromPipeline;
109
+ }
110
+ } catch (err) {
111
+ pipelineRes = { success: false, error: err?.message || String(err) };
112
+ }
113
+
114
+ try {
115
+ envRes = await getDeployment(controllerUrl, envKey, deploymentId, apiAuthConfig);
116
+ const fromEnv = extractDeploymentRecord(envRes);
117
+ if (fromEnv) {
118
+ return fromEnv;
119
+ }
120
+ } catch (err) {
121
+ envRes = { success: false, error: err?.message || String(err) };
122
+ }
123
+
124
+ if (isTerminalPollApiResponse(pipelineRes) || isTerminalPollApiResponse(envRes)) {
125
+ throw buildPollStatusError(pipelineRes, envRes);
126
+ }
127
+
128
+ return null;
129
+ }
130
+
131
+ module.exports = {
132
+ TERMINAL_POLL_HTTP_STATUSES,
133
+ isTerminalPollApiResponse,
134
+ extractDeploymentRecord,
135
+ enrichPollTerminalError,
136
+ buildPollStatusError,
137
+ fetchDeploymentStatusById
138
+ };
@@ -25,6 +25,7 @@ const {
25
25
  buildEnvironmentConfigFromPreset,
26
26
  loadAndValidateEnvironmentDeployConfig
27
27
  } = require('./environment-config');
28
+ const { fetchDeploymentStatusById, enrichPollTerminalError } = require('./environment-poll-status');
28
29
 
29
30
  /**
30
31
  * Validates environment deployment prerequisites
@@ -161,31 +162,24 @@ async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, opti
161
162
  }
162
163
 
163
164
  /**
164
- * Fetches deployment status by ID (pipeline endpoint first, then environments)
165
- * Mirrors miso-controller manual test: GET pipeline/deployments/:id then GET environments/deployments/:id
165
+ * Fetches deployment status by ID (pipeline endpoint first, then environments).
166
166
  * @async
167
167
  * @param {string} controllerUrl - Controller URL
168
168
  * @param {string} envKey - Environment key
169
169
  * @param {string} deploymentId - Deployment ID
170
170
  * @param {Object} apiAuthConfig - Auth config { type: 'bearer', token }
171
- * @returns {Promise<Object|null>} Deployment record (status, progress, etc.) or null
171
+ * @returns {Promise<Object|null>} Deployment record or null when still in progress
172
+ * @throws {Error} On terminal poll errors (e.g. environment or deployment not found)
172
173
  */
173
174
  async function getDeploymentStatusById(controllerUrl, envKey, deploymentId, apiAuthConfig) {
174
- try {
175
- const pipelineRes = await getPipelineDeployment(controllerUrl, envKey, deploymentId, apiAuthConfig);
176
- if (pipelineRes?.data?.data) return pipelineRes.data.data;
177
- if (pipelineRes?.data && typeof pipelineRes.data === 'object' && pipelineRes.data.status) return pipelineRes.data;
178
- } catch {
179
- // Fallback to environments endpoint
180
- }
181
- try {
182
- const envRes = await getDeployment(controllerUrl, envKey, deploymentId, apiAuthConfig);
183
- if (envRes?.data?.data) return envRes.data.data;
184
- if (envRes?.data && typeof envRes.data === 'object' && envRes.data.status) return envRes.data;
185
- } catch {
186
- // Ignore
187
- }
188
- return null;
175
+ return fetchDeploymentStatusById(
176
+ getPipelineDeployment,
177
+ getDeployment,
178
+ controllerUrl,
179
+ envKey,
180
+ deploymentId,
181
+ apiAuthConfig
182
+ );
189
183
  }
190
184
 
191
185
  /**
@@ -218,6 +212,33 @@ function processDeploymentStatusResponse(deployment, validatedEnvKey) {
218
212
  return null;
219
213
  }
220
214
 
215
+ /**
216
+ * @param {number} attempt
217
+ * @param {number} maxAttempts
218
+ * @param {Object|null} deployment
219
+ */
220
+ function logPollAttemptProgress(attempt, maxAttempts, deployment) {
221
+ const progress = deployment?.progress ?? 0;
222
+ const statusLabel = deployment?.status ?? 'pending';
223
+ logger.log(chalk.gray(` Attempt ${attempt}/${maxAttempts}... Status: ${statusLabel} (${progress}%)`));
224
+ }
225
+
226
+ /**
227
+ * @param {Error} error
228
+ * @param {string} validatedEnvKey
229
+ * @throws {Error}
230
+ */
231
+ function handlePollIterationError(error, validatedEnvKey) {
232
+ const msg = error?.message || '';
233
+ const isDeploymentFailed =
234
+ msg.includes('Environment deployment failed') ||
235
+ (msg.includes('failed') && !error.pollTerminal);
236
+ if (error.pollTerminal || isDeploymentFailed) {
237
+ throw enrichPollTerminalError(validatedEnvKey, error);
238
+ }
239
+ logger.log(chalk.yellow(` ⚠ Status check failed (will retry): ${msg}`));
240
+ }
241
+
221
242
  /**
222
243
  * Polls environment deployment status
223
244
  * @async
@@ -231,44 +252,31 @@ function processDeploymentStatusResponse(deployment, validatedEnvKey) {
231
252
  async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authConfig, options = {}) {
232
253
  const validatedUrl = validateControllerUrl(controllerUrl);
233
254
  const validatedEnvKey = validateEnvironmentKey(envKey);
234
-
235
255
  const pollInterval = options.pollInterval || 5000;
236
256
  const maxAttempts = options.maxAttempts || 60;
257
+ const apiAuthConfig = { type: 'bearer', token: authConfig.token };
237
258
 
238
259
  logger.log(chalk.blue(`⏳ Polling environment status (${pollInterval}ms intervals)...`));
239
260
 
240
- // Use centralized API client
241
- const apiAuthConfig = { type: 'bearer', token: authConfig.token };
242
-
243
261
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
244
262
  try {
245
263
  await new Promise(resolve => setTimeout(resolve, pollInterval));
246
-
247
264
  const deployment = await getDeploymentStatusById(
248
265
  validatedUrl,
249
266
  validatedEnvKey,
250
267
  deploymentId,
251
268
  apiAuthConfig
252
269
  );
253
- const progress = deployment?.progress ?? 0;
254
- const statusLabel = deployment?.status ?? 'pending';
255
- if (attempt <= maxAttempts) {
256
- logger.log(chalk.gray(` Attempt ${attempt}/${maxAttempts}... Status: ${statusLabel} (${progress}%)`));
257
- }
270
+ logPollAttemptProgress(attempt, maxAttempts, deployment);
258
271
  const statusResult = processDeploymentStatusResponse(deployment, validatedEnvKey);
259
272
  if (statusResult) {
260
273
  return statusResult;
261
274
  }
262
275
  } catch (error) {
263
- // If it's a terminal error (not a timeout), throw it
264
- if (error.message && error.message.includes('failed')) {
265
- throw error;
266
- }
267
- // Otherwise, continue polling
276
+ handlePollIterationError(error, validatedEnvKey);
268
277
  }
269
278
  }
270
279
 
271
- // Timeout
272
280
  throw new Error(`Environment deployment timeout after ${maxAttempts} attempts. Check controller logs for status.`);
273
281
  }
274
282
 
@@ -437,30 +437,24 @@ function logDownloadCommandHeader(systemKey) {
437
437
  }
438
438
 
439
439
  /**
440
- * Downloads external system from dataplane to local development structure
440
+ * Downloads external system from dataplane to local development structure.
441
441
  * @async
442
- * @function downloadExternalSystem
443
- * @param {string} systemKey - System key or ID
444
- * @param {Object} options - Download options
445
- * @param {string} [options.format] - Output format: 'yaml' (default) or 'json' (runs convert after split)
446
- * @param {string} [options.environment] - Environment (dev, tst, pro)
447
- * @param {string} [options.controller] - Controller URL
448
- * @param {boolean} [options.dryRun] - Show what would be downloaded without actually downloading
449
- * @param {boolean} [options.force] - Overwrite existing README.md without prompting
450
- * @returns {Promise<void>} Resolves when download completes
451
- * @throws {Error} If download fails
442
+ * @param {string} systemKey
443
+ * @param {Object} [options]
444
+ * @returns {Promise<void>}
452
445
  */
453
446
  async function downloadExternalSystem(systemKey, options = {}) {
454
447
  validateSystemKeyFormat(systemKey);
455
-
456
448
  const format = (options.format || 'yaml').toLowerCase();
457
449
 
458
450
  try {
459
451
  logDownloadCommandHeader(systemKey);
460
-
461
452
  const config = await getConfig();
462
- const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(systemKey, options, config);
463
-
453
+ const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(
454
+ systemKey,
455
+ options,
456
+ config
457
+ );
464
458
  if (options.dryRun) {
465
459
  handleDryRun(systemKey, dataplaneUrl);
466
460
  return;
@@ -468,15 +462,22 @@ async function downloadExternalSystem(systemKey, options = {}) {
468
462
 
469
463
  const manifest = await downloadFullManifest(dataplaneUrl, systemKey, authConfig);
470
464
  validateAndLogDownloadedData(manifest.application, manifest.dataSources);
471
-
472
465
  const finalPath = getIntegrationPath(systemKey);
473
- const splitOptions = await resolveDownloadSplitOptions(finalPath, options);
474
- await processDownloadedSystem(systemKey, manifest, splitOptions);
475
-
466
+ await processDownloadedSystem(
467
+ systemKey,
468
+ manifest,
469
+ await resolveDownloadSplitOptions(finalPath, options)
470
+ );
471
+ const { downloadGovernanceScenarioPacksFromManifest } = require('../lifecycle/scenario-upload');
472
+ await downloadGovernanceScenarioPacksFromManifest(
473
+ systemKey,
474
+ manifest,
475
+ dataplaneUrl,
476
+ authConfig
477
+ );
476
478
  if (format === 'json') {
477
479
  await runConvertToJsonIfRequested(systemKey);
478
480
  }
479
-
480
481
  displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
481
482
  } catch (error) {
482
483
  throw new Error(`Failed to download external system: ${error.message}`);
@@ -26,7 +26,7 @@ handlebars.registerHelper('json', (obj) => JSON.stringify(obj));
26
26
  * Build authentication object per schema authenticationVariablesByMethod.
27
27
  * Security values use canonical kv://<systemKey>/<segment> paths (segment from getKvPathSegmentForSecurityKey).
28
28
  * @param {string} systemKey - External system key
29
- * @param {string} authType - Auth method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none)
29
+ * @param {string} authType - Auth method (oauth2, aad, apikey, bearerToken, basic, queryParam, oidc, hmac, none)
30
30
  * @returns {{ method: string, variables: Object, security: Object }} Authentication object
31
31
  */
32
32
  function buildAuthenticationFromMethod(systemKey, authType) {
@@ -50,6 +50,10 @@ function buildAuthenticationFromMethod(systemKey, authType) {
50
50
  variables: { baseUrl: base, headerName: 'X-API-Key' },
51
51
  security: { apiKey: kvPath('apiKey') }
52
52
  },
53
+ bearerToken: {
54
+ variables: { baseUrl: base, headerName: 'Authorization', prefix: 'Bearer' },
55
+ security: { token: kvPath('token') }
56
+ },
53
57
  basic: {
54
58
  variables: { baseUrl: base },
55
59
  security: { username: kvPath('username'), password: kvPath('password') }