@aifabrix/builder 2.44.3 → 2.44.4

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 (56) hide show
  1. package/.npmrc.token +1 -1
  2. package/integration/roundtrip-test-local/README.md +1 -2
  3. package/integration/roundtrip-test-local2/README.md +1 -2
  4. package/jest.projects.js +12 -1
  5. package/lib/api/certificates.api.js +21 -3
  6. package/lib/certification/post-unified-cert-sync.js +13 -2
  7. package/lib/certification/sync-after-external-command.js +6 -3
  8. package/lib/certification/sync-system-certification.js +60 -14
  9. package/lib/cli/setup-app.test-commands.js +67 -35
  10. package/lib/cli/setup-utility.js +1 -1
  11. package/lib/commands/datasource-unified-test-cli.js +81 -46
  12. package/lib/commands/datasource-unified-test-cli.options.js +4 -2
  13. package/lib/commands/datasource.js +3 -31
  14. package/lib/commands/repair-datasource-keys.js +1 -1
  15. package/lib/commands/repair-datasource-openapi.js +57 -0
  16. package/lib/commands/repair-datasource.js +5 -0
  17. package/lib/commands/repair-internal.js +2 -4
  18. package/lib/commands/repair.js +1 -2
  19. package/lib/commands/test-e2e-external.js +5 -6
  20. package/lib/commands/upload.js +18 -4
  21. package/lib/commands/wizard-dataplane.js +14 -6
  22. package/lib/datasource/datasource-validate-display.js +162 -0
  23. package/lib/datasource/datasource-validate-summary.js +194 -0
  24. package/lib/datasource/test-e2e.js +65 -37
  25. package/lib/datasource/unified-validation-run-body.js +1 -2
  26. package/lib/datasource/validate.js +14 -6
  27. package/lib/external-system/test.js +12 -8
  28. package/lib/generator/external-controller-manifest.js +12 -2
  29. package/lib/schema/cip-capacity-display.fallback.json +7 -0
  30. package/lib/schema/datasource-test-run.schema.json +79 -1
  31. package/lib/schema/external-datasource.schema.json +94 -2
  32. package/lib/schema/flag-map-validation-run.json +1 -2
  33. package/lib/schema/type/document-storage.json +83 -3
  34. package/lib/utils/configuration-env-resolver.js +38 -0
  35. package/lib/utils/dataplane-resolver.js +3 -2
  36. package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
  37. package/lib/utils/datasource-test-run-debug-display.js +143 -1
  38. package/lib/utils/datasource-test-run-display.js +46 -33
  39. package/lib/utils/datasource-test-run-tty-log.js +6 -2
  40. package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
  41. package/lib/utils/error-formatter.js +32 -2
  42. package/lib/utils/external-system-readiness-core.js +39 -0
  43. package/lib/utils/external-system-readiness-deploy-display.js +2 -3
  44. package/lib/utils/external-system-readiness-display-internals.js +3 -2
  45. package/lib/utils/external-system-system-test-tty.js +33 -9
  46. package/lib/utils/external-system-validators.js +62 -5
  47. package/lib/utils/load-cip-capacity-display-config.js +130 -0
  48. package/lib/utils/paths.js +10 -3
  49. package/lib/utils/schema-resolver.js +98 -2
  50. package/lib/utils/validation-run-poll.js +15 -4
  51. package/lib/utils/validation-run-request.js +4 -6
  52. package/lib/validation/dimension-display-helpers.js +60 -0
  53. package/lib/validation/validate-display-log-helpers.js +39 -0
  54. package/lib/validation/validate-display.js +89 -83
  55. package/package.json +1 -1
  56. package/templates/external-system/README.md.hbs +1 -2
package/.npmrc.token CHANGED
@@ -1 +1 @@
1
- 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
1
+ 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
@@ -123,8 +123,7 @@ Options:
123
123
  -e, --env <env> Environment: dev, tst, or pro
124
124
  -v, --verbose Show detailed step output and poll progress
125
125
  --debug Include debug output and write log to integration/roundtrip-test-local/logs/
126
- --test-crud Enable CRUD lifecycle test (body testCrud: true)
127
- --record-id <id> Record ID for test (body recordId)
126
+ --no-run-scenarios Skip expanding testPayload.scenarios in capacity step
128
127
  --no-cleanup Disable cleanup after test (body cleanup: false)
129
128
  --primary-key-value <value|@path> Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue
130
129
  --no-async Use sync mode (no polling); single POST, no asyncRun
@@ -123,8 +123,7 @@ Options:
123
123
  -e, --env <env> Environment: dev, tst, or pro
124
124
  -v, --verbose Show detailed step output and poll progress
125
125
  --debug Include debug output and write log to integration/roundtrip-test-local2/logs/
126
- --test-crud Enable CRUD lifecycle test (body testCrud: true)
127
- --record-id <id> Record ID for test (body recordId)
126
+ --no-run-scenarios Skip expanding testPayload.scenarios in capacity step
128
127
  --no-cleanup Disable cleanup after test (body cleanup: false)
129
128
  --primary-key-value <value|@path> Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue
130
129
  --no-async Use sync mode (no polling); single POST, no asyncRun
package/jest.projects.js CHANGED
@@ -72,6 +72,9 @@ const defaultProject = {
72
72
  '/tests/lib/datasource/log-viewer.test.js',
73
73
  '\\\\tests\\\\lib\\\\datasource\\\\log-viewer.test.js',
74
74
  'lib/datasource/log-viewer.test.js',
75
+ '/tests/lib/datasource/log-viewer-structural.test.js',
76
+ '\\\\tests\\\\lib\\\\datasource\\\\log-viewer-structural.test.js',
77
+ 'lib/datasource/log-viewer-structural.test.js',
75
78
  '/tests/lib/commands/parameters-validate.test.js',
76
79
  '\\\\tests\\\\lib\\\\commands\\\\parameters-validate.test.js',
77
80
  'lib/commands/parameters-validate.test.js',
@@ -190,6 +193,10 @@ const defaultProject = {
190
193
  '\\\\tests\\\\lib\\\\validation\\\\schema-241-alignment.test.js',
191
194
  'lib/validation/schema-241-alignment.test.js',
192
195
  'schema-241-alignment\\.test\\.js',
196
+ '/tests/lib/utils/schema-resolver-order.test.js',
197
+ '\\\\tests\\\\lib\\\\utils\\\\schema-resolver-order.test.js',
198
+ 'lib/utils/schema-resolver-order.test.js',
199
+ 'schema-resolver-order\\.test\\.js',
193
200
  '/tests/lib/app/app.test.js',
194
201
  '\\\\tests\\\\lib\\\\app\\\\app.test.js',
195
202
  'lib/app/app.test.js',
@@ -222,7 +229,10 @@ const isolatedProjects = [
222
229
  makeIsolatedProject('datasource-validation-watch', [
223
230
  '**/tests/lib/utils/datasource-validation-watch.test.js'
224
231
  ]),
225
- makeIsolatedProject('log-viewer', ['**/tests/lib/datasource/log-viewer.test.js']),
232
+ makeIsolatedProject('log-viewer', [
233
+ '**/tests/lib/datasource/log-viewer.test.js',
234
+ '**/tests/lib/datasource/log-viewer-structural.test.js'
235
+ ]),
226
236
  makeIsolatedProject('datasource-test-run-schema-sync', [
227
237
  '**/tests/lib/utils/datasource-test-run-schema-sync.test.js'
228
238
  ]),
@@ -285,6 +295,7 @@ const isolatedProjects = [
285
295
  makeIsolatedProject('generator-validation', ['**/tests/lib/generator/generator-validation.test.js']),
286
296
  makeIsolatedProject('secrets-databaselog', ['**/tests/lib/core/secrets-databaselog.test.js']),
287
297
  makeIsolatedProject('schema-241-alignment', ['**/tests/lib/validation/schema-241-alignment.test.js']),
298
+ makeIsolatedProject('schema-resolver-order', ['**/tests/lib/utils/schema-resolver-order.test.js']),
288
299
  makeIsolatedProject('app-module', ['**/tests/lib/app/app.test.js']),
289
300
  makeIsolatedProject('admin-secrets', ['**/tests/lib/core/admin-secrets.test.js'])
290
301
  ];
@@ -5,6 +5,24 @@
5
5
  */
6
6
 
7
7
  const { ApiClient } = require('./index');
8
+ const { normalizeDataplaneAuth } = require('./validation-run.api');
9
+
10
+ /**
11
+ * @param {Object} authConfig
12
+ * @returns {Object}
13
+ */
14
+ function dataplaneAuthForBearerGet(authConfig) {
15
+ if (!authConfig || typeof authConfig !== 'object') {
16
+ return authConfig;
17
+ }
18
+ if (authConfig.token || authConfig.clientId) {
19
+ return authConfig;
20
+ }
21
+ if (authConfig.apiKey) {
22
+ return { ...authConfig, token: authConfig.apiKey, type: authConfig.type || 'bearer' };
23
+ }
24
+ return normalizeDataplaneAuth(authConfig);
25
+ }
8
26
 
9
27
  /**
10
28
  * Get active trusted integration certificate for a datasource.
@@ -18,7 +36,7 @@ const { ApiClient } = require('./index');
18
36
  * @returns {Promise<Object>} API envelope `{ success, data?, status, ... }`
19
37
  */
20
38
  async function getActiveIntegrationCertificate(dataplaneUrl, authConfig, systemKey, datasourceKey) {
21
- const client = new ApiClient(dataplaneUrl, authConfig);
39
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
22
40
  const path = `/api/v1/systems/${encodeURIComponent(systemKey)}/datasources/${encodeURIComponent(
23
41
  datasourceKey
24
42
  )}/certificates/active`;
@@ -36,7 +54,7 @@ async function getActiveIntegrationCertificate(dataplaneUrl, authConfig, systemK
36
54
  * @returns {Promise<Object>}
37
55
  */
38
56
  async function listIntegrationCertificates(dataplaneUrl, authConfig, params = {}) {
39
- const client = new ApiClient(dataplaneUrl, authConfig);
57
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
40
58
  return await client.get('/api/v1/certificates', { params });
41
59
  }
42
60
 
@@ -51,7 +69,7 @@ async function listIntegrationCertificates(dataplaneUrl, authConfig, params = {}
51
69
  * @returns {Promise<Object>}
52
70
  */
53
71
  async function verifyIntegrationCertificate(dataplaneUrl, authConfig, body) {
54
- const client = new ApiClient(dataplaneUrl, authConfig);
72
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
55
73
  return await client.post('/api/v1/certificates/verify', { body: body || {} });
56
74
  }
57
75
 
@@ -10,6 +10,7 @@ const chalk = require('chalk');
10
10
  const logger = require('../utils/logger');
11
11
  const { trySyncCertificationFromDataplaneForExternalApp } = require('./sync-after-external-command');
12
12
  const { cliOptsSkipCertSync } = require('./cli-cert-sync-skip');
13
+ const { resolveDebugDisplayMode } = require('../utils/datasource-test-run-debug-display');
13
14
 
14
15
  /**
15
16
  * @async
@@ -17,14 +18,24 @@ const { cliOptsSkipCertSync } = require('./cli-cert-sync-skip');
17
18
  * @param {string} datasourceKey
18
19
  * @param {Object} options - CLI flags (app, noCertSync)
19
20
  * @param {string} label - Log label
21
+ * @param {Object|null} [envelope] - Last DatasourceTestRun (optional); used for certificateIssuance failed hint
20
22
  * @returns {Promise<void>}
21
23
  */
22
- async function afterUnifiedValidationCertSync(exitCode, datasourceKey, options, label) {
24
+ async function afterUnifiedValidationCertSync(exitCode, datasourceKey, options, label, envelope = null) {
23
25
  if (exitCode !== 0 || cliOptsSkipCertSync(options)) return;
24
26
  try {
25
27
  const { resolveAppKeyForDatasource } = require('../datasource/resolve-app');
26
28
  const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
27
- await trySyncCertificationFromDataplaneForExternalApp(appKey, label);
29
+ let issuanceFailureHint = null;
30
+ if (envelope && envelope.certificateIssuance && envelope.certificateIssuance.status === 'failed') {
31
+ const ci = envelope.certificateIssuance;
32
+ issuanceFailureHint = [ci.reasonCode, ci.message].filter(Boolean).join(': ');
33
+ }
34
+ const verboseCertHints = resolveDebugDisplayMode(options.debug) !== null;
35
+ await trySyncCertificationFromDataplaneForExternalApp(appKey, label, {
36
+ issuanceFailureHint,
37
+ verboseCertHints
38
+ });
28
39
  } catch (e) {
29
40
  logger.log(chalk.yellow(`⚠ Certification sync (${label}) skipped: ${e.message}`));
30
41
  }
@@ -16,9 +16,10 @@ const logger = require('../utils/logger');
16
16
  * @async
17
17
  * @param {string} appKey - Integration / system key
18
18
  * @param {string} label - Short label for logs (e.g. "validate", "datasource test")
19
+ * @param {Object} [extra] - Optional `{ issuanceFailureHint }` from last DatasourceTestRun.certificateIssuance (failed)
19
20
  * @returns {Promise<void>}
20
21
  */
21
- async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
22
+ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label, extra = {}) {
22
23
  try {
23
24
  const { detectAppType } = require('../utils/paths');
24
25
  const t = await detectAppType(appKey).catch(() => null);
@@ -29,7 +30,7 @@ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
29
30
  const { maybeSyncSystemCertificationFromDataplane } = require('./sync-system-certification');
30
31
 
31
32
  validateSystemKeyFormat(appKey);
32
- const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(appKey);
33
+ const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(appKey, { silent: true });
33
34
  if (!authConfig.token) {
34
35
  logger.log(chalk.gray(`Certification sync (${label}) skipped: no Bearer token (run aifabrix login).`));
35
36
  return;
@@ -42,7 +43,9 @@ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
42
43
  systemKey: manifest.key,
43
44
  dataplaneUrl,
44
45
  authConfig,
45
- datasourceKeys: dsKeys
46
+ datasourceKeys: dsKeys,
47
+ issuanceFailureHint: extra && extra.issuanceFailureHint ? String(extra.issuanceFailureHint).trim() : null,
48
+ verboseCertHints: extra && extra.verboseCertHints === true
46
49
  });
47
50
  } catch (e) {
48
51
  logger.log(chalk.yellow(`⚠ Certification sync (${label}) skipped: ${e.message}`));
@@ -84,15 +84,43 @@ function tryWriteSystemCertification(systemFilePath, systemObj, nextCert) {
84
84
  /**
85
85
  * @param {Object|null} chosen
86
86
  * @param {'no_active'|'no_public_key'} detail
87
+ * @param {string|null} [issuanceFailureHint] - From last DatasourceTestRun.certificateIssuance when status failed
88
+ * @param {boolean} [verboseCertHints] - Extra gray lines and auto-issue detail (CLI `--debug`)
87
89
  */
88
- function logSkippedCertification(chosen, detail) {
90
+ function logSkippedCertification(chosen, detail, issuanceFailureHint, verboseCertHints = false) {
89
91
  if (detail === 'no_active' || !chosen) {
90
- logger.log(
91
- chalk.yellow(
92
- '⚠ Certification not written: dataplane has no active trusted certificate for this system/datasource scope yet. ' +
93
- 'Run a successful validation or E2E that issues a certificate, then upload or run cert sync again.'
94
- )
95
- );
92
+ const hint = issuanceFailureHint && String(issuanceFailureHint).trim();
93
+ const certNotPassed =
94
+ hint &&
95
+ (hint.includes('CERTIFICATION_NOT_PASSED') ||
96
+ hint.toLowerCase().includes('certification did not pass'));
97
+ if (certNotPassed) {
98
+ logger.log(
99
+ chalk.yellow(
100
+ '⚠ Certification not written: no active integration certificate because certification did not pass on the dataplane issuance validation pass.'
101
+ )
102
+ );
103
+ if (verboseCertHints) {
104
+ logger.log(
105
+ chalk.gray(
106
+ ' E2E can be green for every datasource while auto-issue still fails: issuance re-validates the whole system before signing.'
107
+ )
108
+ );
109
+ if (hint) {
110
+ logger.log(chalk.gray(` Auto-issue detail: ${hint}`));
111
+ }
112
+ }
113
+ } else {
114
+ logger.log(
115
+ chalk.yellow(
116
+ '⚠ Certification not written: dataplane has no active trusted certificate for this system/datasource scope yet. ' +
117
+ 'Fix auto-issue or signing (see hint), or run unified validation then upload or run cert sync again.'
118
+ )
119
+ );
120
+ if (hint && verboseCertHints) {
121
+ logger.log(chalk.gray(` Auto-issue detail: ${hint}`));
122
+ }
123
+ }
96
124
  return;
97
125
  }
98
126
  logger.log(
@@ -111,13 +139,19 @@ function logSkippedCertification(chosen, detail) {
111
139
  * @param {string} params.dataplaneUrl
112
140
  * @param {Object} params.authConfig
113
141
  * @param {string[]} params.datasourceKeys
142
+ * @param {boolean} [params.verboseCertHints]
114
143
  * @returns {Promise<{ written: boolean, reason?: string }>}
115
144
  */
116
145
  async function syncSystemCertificationFromDataplane(params) {
117
- const { systemKey, dataplaneUrl, authConfig, datasourceKeys } = params;
146
+ const { systemKey, dataplaneUrl, authConfig, datasourceKeys, issuanceFailureHint, verboseCertHints } = params;
118
147
  const resolved = resolvePrimarySystemFilePath(systemKey);
119
148
  if (!resolved) return { written: false, reason: 'no_system_file' };
120
- if (!dataplaneUrl || !authConfig || !authConfig.token) {
149
+ const hasBearerLike =
150
+ authConfig &&
151
+ typeof authConfig === 'object' &&
152
+ ((typeof authConfig.token === 'string' && authConfig.token.trim()) ||
153
+ (typeof authConfig.apiKey === 'string' && authConfig.apiKey.trim()));
154
+ if (!dataplaneUrl || !authConfig || !hasBearerLike) {
121
155
  return { written: false, reason: 'no_auth' };
122
156
  }
123
157
 
@@ -137,7 +171,7 @@ async function syncSystemCertificationFromDataplane(params) {
137
171
  const nextCert = buildCertificationFromArtifact(chosen, existing);
138
172
  if (!nextCert) {
139
173
  const detail = chosen ? 'no_public_key' : 'no_active';
140
- logSkippedCertification(chosen, detail);
174
+ logSkippedCertification(chosen, detail, issuanceFailureHint, verboseCertHints === true);
141
175
  return { written: false, reason: 'incomplete_certification', detail };
142
176
  }
143
177
 
@@ -154,7 +188,8 @@ async function syncSystemCertificationFromDataplane(params) {
154
188
  * @param {boolean} [params.noCertSync]
155
189
  * @returns {Promise<void>}
156
190
  */
157
- function logCertificationSyncNotWritten(r, label) {
191
+ function logCertificationSyncNotWritten(r, label, verboseCertHints = false) {
192
+ if (r && r.reason === 'incomplete_certification' && !verboseCertHints) return;
158
193
  const prefix = label ? ` (${label})` : '';
159
194
  const map = {
160
195
  no_system_file: `No *-system* file found under integration folder for this key${prefix}.`,
@@ -168,21 +203,32 @@ function logCertificationSyncNotWritten(r, label) {
168
203
  }
169
204
 
170
205
  async function maybeSyncSystemCertificationFromDataplane(params) {
171
- const { label, noCertSync, systemKey, dataplaneUrl, authConfig, datasourceKeys } = params;
206
+ const {
207
+ label,
208
+ noCertSync,
209
+ systemKey,
210
+ dataplaneUrl,
211
+ authConfig,
212
+ datasourceKeys,
213
+ issuanceFailureHint,
214
+ verboseCertHints
215
+ } = params;
172
216
  if (noCertSync === true) return;
173
217
  try {
174
218
  const r = await syncSystemCertificationFromDataplane({
175
219
  systemKey,
176
220
  dataplaneUrl,
177
221
  authConfig,
178
- datasourceKeys
222
+ datasourceKeys,
223
+ issuanceFailureHint,
224
+ verboseCertHints
179
225
  });
180
226
  if (r.written) {
181
227
  logger.log(
182
228
  chalk.gray(`Updated certification block from dataplane${label ? ` (${label})` : ''} in system file.`)
183
229
  );
184
230
  } else if (r.reason) {
185
- logCertificationSyncNotWritten(r, label);
231
+ logCertificationSyncNotWritten(r, label, verboseCertHints === true);
186
232
  }
187
233
  } catch (e) {
188
234
  logger.log(chalk.yellow(`⚠ Certification sync skipped: ${e.message}`));
@@ -54,6 +54,70 @@ function setupTestCommand(program) {
54
54
  });
55
55
  }
56
56
 
57
+ /**
58
+ * First failed `certificateIssuance` across E2E datasource rows (for cert-sync hint).
59
+ * @param {Array<{ key?: string, datasourceTestRun?: { certificateIssuance?: { status?: string, reasonCode?: string, message?: string } } }>} results
60
+ * @returns {string|null}
61
+ */
62
+ function firstIssuanceFailureHintFromE2eResults(results) {
63
+ const failed = [];
64
+ for (const row of results || []) {
65
+ const ci = row.datasourceTestRun && row.datasourceTestRun.certificateIssuance;
66
+ if (ci && ci.status === 'failed') {
67
+ failed.push({ key: row.key, ci });
68
+ }
69
+ }
70
+ if (failed.length === 0) return null;
71
+ const firstRc = failed[0].ci.reasonCode;
72
+ const sameAll = failed.every(f => f.ci.reasonCode === firstRc);
73
+ if (sameAll && failed.length > 1) {
74
+ const parts = [firstRc, failed[0].ci.message].filter(Boolean);
75
+ return `${parts.join(': ')} (${failed.length} datasource scopes; first: ${failed[0].key})`;
76
+ }
77
+ const parts = [failed[0].ci.reasonCode, failed[0].ci.message].filter(Boolean);
78
+ return `[${failed[0].key}] ${parts.join(': ')}`.trim();
79
+ }
80
+
81
+ /**
82
+ * External integration E2E: run, display, exit on aggregate, optional cert sync.
83
+ * @param {string} appName
84
+ * @param {Object} options
85
+ * @returns {Promise<void>}
86
+ */
87
+ async function runExternalIntegrationE2EAndCertSync(appName, options) {
88
+ const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
89
+ const { success, results } = await runTestE2EForExternalSystem(appName, {
90
+ env: options.env,
91
+ debug: options.debug,
92
+ verbose: options.verbose,
93
+ async: options.async !== false,
94
+ sync: options.sync === true
95
+ });
96
+ const { displayIntegrationTestResults } = require('../utils/external-system-display');
97
+ const datasourceResults = results.map(r => ({
98
+ key: r.key,
99
+ success: r.success,
100
+ error: r.error,
101
+ skipped: false,
102
+ datasourceTestRun: r.datasourceTestRun
103
+ }));
104
+ displayIntegrationTestResults(
105
+ { systemKey: appName, success, datasourceResults },
106
+ options.verbose,
107
+ { debug: options.debug, runType: 'e2e' }
108
+ );
109
+ const { computeSystemExitCodeFromDatasourceRows } = require('../utils/datasource-test-run-exit');
110
+ const exitCode = computeSystemExitCodeFromDatasourceRows(datasourceResults, {
111
+ warningsAsErrors: options.warningsAsErrors === true,
112
+ requireCert: options.requireCert === true
113
+ });
114
+ if (exitCode !== 0) process.exit(exitCode);
115
+ if (cliOptsSkipCertSync(options)) return;
116
+ const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
117
+ const issuanceFailureHint = firstIssuanceFailureHintFromE2eResults(results);
118
+ await trySyncCertificationFromDataplaneForExternalApp(appName, 'test-e2e', { issuanceFailureHint });
119
+ }
120
+
57
121
  async function runTestE2ECommand(appName, options) {
58
122
  const pathsUtil = require('../utils/paths');
59
123
  const appType = await pathsUtil.detectAppType(appName).catch(() => null);
@@ -64,40 +128,7 @@ async function runTestE2ECommand(appName, options) {
64
128
  );
65
129
  }
66
130
  if (appType && appType.baseDir === 'integration') {
67
- const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
68
- const { success, results } = await runTestE2EForExternalSystem(appName, {
69
- env: options.env,
70
- debug: options.debug,
71
- verbose: options.verbose,
72
- async: options.async !== false,
73
- sync: options.sync === true
74
- });
75
- const { displayIntegrationTestResults } = require('../utils/external-system-display');
76
- const datasourceResults = results.map(r => ({
77
- key: r.key,
78
- success: r.success,
79
- error: r.error,
80
- skipped: false,
81
- datasourceTestRun: r.datasourceTestRun
82
- }));
83
- displayIntegrationTestResults(
84
- {
85
- systemKey: appName,
86
- success,
87
- datasourceResults
88
- },
89
- options.verbose,
90
- { debug: options.debug, runType: 'e2e' }
91
- );
92
- const { computeSystemExitCodeFromDatasourceRows } = require('../utils/datasource-test-run-exit');
93
- const exitCode = computeSystemExitCodeFromDatasourceRows(datasourceResults, {
94
- warningsAsErrors: options.warningsAsErrors === true,
95
- requireCert: options.requireCert === true
96
- });
97
- if (exitCode !== 0) process.exit(exitCode);
98
- if (cliOptsSkipCertSync(options)) return;
99
- const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
100
- await trySyncCertificationFromDataplaneForExternalApp(appName, 'test-e2e');
131
+ await runExternalIntegrationE2EAndCertSync(appName, options);
101
132
  return;
102
133
  }
103
134
  const { runAppTestE2e } = require('../commands/app-test');
@@ -190,6 +221,7 @@ function setupInstallTestE2eLintCommands(program) {
190
221
  }
191
222
 
192
223
  module.exports = {
193
- setupInstallTestE2eLintCommands
224
+ setupInstallTestE2eLintCommands,
225
+ firstIssuanceFailureHintFromE2eResults
194
226
  };
195
227
 
@@ -118,7 +118,7 @@ function setupResolveCommand(program) {
118
118
  );
119
119
  logger.log(`✔ Generated .env file: ${envPath}`);
120
120
  if (envOnly) {
121
- logger.log(chalk.gray(' (env-only mode: validation skipped; no application.yaml)'));
121
+ logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
122
122
  } else if (!options.skipValidation) {
123
123
  const validate = require('../validation/validate');
124
124
  const result = await validate.validateAppOrFile(appName);
@@ -94,7 +94,7 @@ async function runDatasourceUnifiedTestOnceForWatch(datasourceKey, runOpts, disp
94
94
  try {
95
95
  const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
96
96
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
97
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test');
97
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test', result.envelope);
98
98
  return {
99
99
  exitCode,
100
100
  envelope: result.envelope
@@ -121,7 +121,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
121
121
  const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
122
122
  await writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options);
123
123
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
124
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)');
124
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)', result.envelope);
125
125
  return {
126
126
  exitCode,
127
127
  envelope: result.envelope
@@ -140,7 +140,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
140
140
  }
141
141
  }
142
142
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
143
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test');
143
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test', result.envelope);
144
144
  process.exit(exitCode);
145
145
  } catch (error) {
146
146
  logger.error(formatBlockingError('Datasource test failed:'), error.message);
@@ -195,7 +195,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
195
195
  if (unifiedModes) {
196
196
  const uni = unifiedCliResultFromIntegrationReturn(result);
197
197
  const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
198
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration (watch)');
198
+ await afterUnifiedValidationCertSync(
199
+ exitCode,
200
+ datasourceKey,
201
+ options,
202
+ 'datasource test-integration (watch)',
203
+ uni.envelope
204
+ );
199
205
  return { exitCode, envelope: uni.envelope };
200
206
  }
201
207
  displayIntegrationTestResults(
@@ -211,7 +217,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
211
217
  warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
212
218
  requireCert: unifiedDisplayOpts.requireCert === true
213
219
  });
214
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration (watch)');
220
+ await afterUnifiedValidationCertSync(
221
+ exitCode,
222
+ datasourceKey,
223
+ options,
224
+ 'datasource test-integration (watch)',
225
+ result.datasourceTestRun || null
226
+ );
215
227
  return { exitCode, envelope: result.datasourceTestRun || null };
216
228
  } catch (err) {
217
229
  logger.error(formatBlockingError('Integration test failed:'), err.message);
@@ -219,49 +231,67 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
219
231
  }
220
232
  }
221
233
 
234
+ async function integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts) {
235
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
236
+ await runDatasourceValidationWatchLoop({
237
+ appKey,
238
+ extraPaths: options.watchPath || [],
239
+ includeApplicationYaml: options.watchApplicationYaml === true,
240
+ watchCi: options.watchCi === true,
241
+ watchFullDiff: options.watchFullDiff === true,
242
+ runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
243
+ });
244
+ }
245
+
246
+ async function integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts) {
247
+ const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
248
+ const unifiedModes =
249
+ options.json || options.summary || options.warningsAsErrors || options.requireCert;
250
+ if (unifiedModes) {
251
+ const uni = unifiedCliResultFromIntegrationReturn(result);
252
+ const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
253
+ await afterUnifiedValidationCertSync(
254
+ exitCode,
255
+ datasourceKey,
256
+ options,
257
+ 'datasource test-integration',
258
+ uni.envelope
259
+ );
260
+ process.exit(exitCode);
261
+ return;
262
+ }
263
+ displayIntegrationTestResults(
264
+ {
265
+ systemKey: result.systemKey || 'unknown',
266
+ datasourceResults: [result],
267
+ success: result.success
268
+ },
269
+ options.verbose,
270
+ { debug: options.debug, runType: 'integration' }
271
+ );
272
+ const integExit = finalizeAfterIntegrationDisplay(result, {
273
+ warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
274
+ requireCert: unifiedDisplayOpts.requireCert === true
275
+ });
276
+ await afterUnifiedValidationCertSync(
277
+ integExit,
278
+ datasourceKey,
279
+ options,
280
+ 'datasource test-integration',
281
+ result.datasourceTestRun || null
282
+ );
283
+ process.exit(integExit);
284
+ }
285
+
222
286
  async function integrationTestCommandAction(datasourceKey, options) {
223
287
  const integOpts = buildIntegrationTestOpts(options);
224
288
  const unifiedDisplayOpts = buildIntegrationUnifiedDisplayOpts(options);
225
289
  try {
226
290
  if (options.watch) {
227
- const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
228
- await runDatasourceValidationWatchLoop({
229
- appKey,
230
- extraPaths: options.watchPath || [],
231
- includeApplicationYaml: options.watchApplicationYaml === true,
232
- watchCi: options.watchCi === true,
233
- watchFullDiff: options.watchFullDiff === true,
234
- runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
235
- });
236
- return;
237
- }
238
- const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
239
- const unifiedModes =
240
- options.json || options.summary || options.warningsAsErrors || options.requireCert;
241
- if (unifiedModes) {
242
- const exitCode = finalizeUnifiedValidationResult(
243
- unifiedCliResultFromIntegrationReturn(result),
244
- unifiedDisplayOpts
245
- );
246
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration');
247
- process.exit(exitCode);
291
+ await integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts);
248
292
  return;
249
293
  }
250
- displayIntegrationTestResults(
251
- {
252
- systemKey: result.systemKey || 'unknown',
253
- datasourceResults: [result],
254
- success: result.success
255
- },
256
- options.verbose,
257
- { debug: options.debug, runType: 'integration' }
258
- );
259
- const integExit = finalizeAfterIntegrationDisplay(result, {
260
- warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
261
- requireCert: unifiedDisplayOpts.requireCert === true
262
- });
263
- await afterUnifiedValidationCertSync(integExit, datasourceKey, options, 'datasource test-integration');
264
- process.exit(integExit);
294
+ await integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts);
265
295
  } catch (error) {
266
296
  logger.error(formatBlockingError('Integration test failed:'), error.message);
267
297
  process.exit(4);
@@ -296,9 +326,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
296
326
  debug: options.debug,
297
327
  verbose: options.verbose,
298
328
  async: options.async !== false,
299
- testCrud: options.testCrud,
300
- recordId: options.recordId,
301
329
  cleanup: options.cleanup,
330
+ runScenarios: options.runScenarios,
302
331
  primaryKeyValue: options.primaryKeyValue,
303
332
  minVectorHits: options.minVectorHits,
304
333
  minProcessed: options.minProcessed,
@@ -340,8 +369,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
340
369
  }
341
370
 
342
371
  async function runDatasourceTestE2ECliAction(datasourceKey, options) {
343
- const { exitCode } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
344
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e');
372
+ const { exitCode, envelope } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
373
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e', envelope);
345
374
  process.exit(exitCode);
346
375
  }
347
376
 
@@ -371,7 +400,13 @@ async function e2eTestCommandAction(datasourceKey, capabilityKey, options) {
371
400
  runOnce: async() => {
372
401
  try {
373
402
  const r = await runDatasourceTestE2ECliOnce(datasourceKey, mergedOptions);
374
- await afterUnifiedValidationCertSync(r.exitCode, datasourceKey, mergedOptions, 'datasource test-e2e (watch)');
403
+ await afterUnifiedValidationCertSync(
404
+ r.exitCode,
405
+ datasourceKey,
406
+ mergedOptions,
407
+ 'datasource test-e2e (watch)',
408
+ r.envelope
409
+ );
375
410
  return r;
376
411
  } catch (err) {
377
412
  logger.error(formatBlockingError('E2E test failed:'), err.message);
@@ -166,8 +166,10 @@ function attachDatasourceTestE2eExclusiveOptions(cmd) {
166
166
  'Minimum record count assertion (e2eOptions.minRecordCount)',
167
167
  (v) => parseInt(String(v), 10)
168
168
  )
169
- .option('--test-crud', 'Enable CRUD lifecycle test (e2eOptions.testCrud)')
170
- .option('--record-id <id>', 'Record ID for test (e2eOptions.recordId)')
169
+ .option(
170
+ '--no-run-scenarios',
171
+ 'Do not expand testPayload.scenarios in capacity step (e2eOptions.runScenarios: false)'
172
+ )
171
173
  .option('--no-cleanup', 'Disable cleanup after test (e2eOptions.cleanup: false)')
172
174
  .option(
173
175
  '--primary-key-value <value|@path>',