@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.
- package/.npmrc.token +1 -1
- package/integration/roundtrip-test-local/README.md +1 -2
- package/integration/roundtrip-test-local2/README.md +1 -2
- package/jest.projects.js +12 -1
- package/lib/api/certificates.api.js +21 -3
- package/lib/certification/post-unified-cert-sync.js +13 -2
- package/lib/certification/sync-after-external-command.js +6 -3
- package/lib/certification/sync-system-certification.js +60 -14
- package/lib/cli/setup-app.test-commands.js +67 -35
- package/lib/cli/setup-utility.js +1 -1
- package/lib/commands/datasource-unified-test-cli.js +81 -46
- package/lib/commands/datasource-unified-test-cli.options.js +4 -2
- package/lib/commands/datasource.js +3 -31
- package/lib/commands/repair-datasource-keys.js +1 -1
- package/lib/commands/repair-datasource-openapi.js +57 -0
- package/lib/commands/repair-datasource.js +5 -0
- package/lib/commands/repair-internal.js +2 -4
- package/lib/commands/repair.js +1 -2
- package/lib/commands/test-e2e-external.js +5 -6
- package/lib/commands/upload.js +18 -4
- package/lib/commands/wizard-dataplane.js +14 -6
- package/lib/datasource/datasource-validate-display.js +162 -0
- package/lib/datasource/datasource-validate-summary.js +194 -0
- package/lib/datasource/test-e2e.js +65 -37
- package/lib/datasource/unified-validation-run-body.js +1 -2
- package/lib/datasource/validate.js +14 -6
- package/lib/external-system/test.js +12 -8
- package/lib/generator/external-controller-manifest.js +12 -2
- package/lib/schema/cip-capacity-display.fallback.json +7 -0
- package/lib/schema/datasource-test-run.schema.json +79 -1
- package/lib/schema/external-datasource.schema.json +94 -2
- package/lib/schema/flag-map-validation-run.json +1 -2
- package/lib/schema/type/document-storage.json +83 -3
- package/lib/utils/configuration-env-resolver.js +38 -0
- package/lib/utils/dataplane-resolver.js +3 -2
- package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
- package/lib/utils/datasource-test-run-debug-display.js +143 -1
- package/lib/utils/datasource-test-run-display.js +46 -33
- package/lib/utils/datasource-test-run-tty-log.js +6 -2
- package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
- package/lib/utils/error-formatter.js +32 -2
- package/lib/utils/external-system-readiness-core.js +39 -0
- package/lib/utils/external-system-readiness-deploy-display.js +2 -3
- package/lib/utils/external-system-readiness-display-internals.js +3 -2
- package/lib/utils/external-system-system-test-tty.js +33 -9
- package/lib/utils/external-system-validators.js +62 -5
- package/lib/utils/load-cip-capacity-display-config.js +130 -0
- package/lib/utils/paths.js +10 -3
- package/lib/utils/schema-resolver.js +98 -2
- package/lib/utils/validation-run-poll.js +15 -4
- package/lib/utils/validation-run-request.js +4 -6
- package/lib/validation/dimension-display-helpers.js +60 -0
- package/lib/validation/validate-display-log-helpers.js +39 -0
- package/lib/validation/validate-display.js +89 -83
- package/package.json +1 -1
- package/templates/external-system/README.md.hbs +1 -2
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"key": "document-storage-schema",
|
|
8
8
|
"name": "Document Storage Configuration Schema",
|
|
9
9
|
"description": "JSON schema for validating document storage configurations",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.7.0",
|
|
11
11
|
"type": "schema",
|
|
12
12
|
"category": "document-storage",
|
|
13
13
|
"author": "AI Fabrix Team",
|
|
14
14
|
"createdAt": "2026-01-02T00:00:00Z",
|
|
15
|
-
"updatedAt": "2026-
|
|
15
|
+
"updatedAt": "2026-05-01T00:00:00Z",
|
|
16
16
|
"compatibility": {
|
|
17
17
|
"minVersion": "1.0.0",
|
|
18
18
|
"maxVersion": "2.0.0",
|
|
@@ -71,6 +71,38 @@
|
|
|
71
71
|
"Added optional parameterLookupCoalesceNestedItemScope (boolean, default true) for manifest-controlled binary parameter lookup enrichment"
|
|
72
72
|
],
|
|
73
73
|
"breaking": false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"version": "1.4.0",
|
|
77
|
+
"date": "2026-04-26T00:00:00Z",
|
|
78
|
+
"changes": [
|
|
79
|
+
"Added optional maxBinaryFetchesPerSync (integer >= 1) to cap binary fetch+store operations per sync run"
|
|
80
|
+
],
|
|
81
|
+
"breaking": false
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"version": "1.5.0",
|
|
85
|
+
"date": "2026-04-27T00:00:00Z",
|
|
86
|
+
"changes": [
|
|
87
|
+
"ingestAfterSync: post-sync chunk+embed no longer waits on trust (eligibility/schema gates unchanged)"
|
|
88
|
+
],
|
|
89
|
+
"breaking": false
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"version": "1.6.0",
|
|
93
|
+
"date": "2026-04-28T00:00:00Z",
|
|
94
|
+
"changes": [
|
|
95
|
+
"Added documentStorage.changeDetection.ignoreMetadataKeys / ignoreMetadataPaths for stable contentHash when volatile metadata changes"
|
|
96
|
+
],
|
|
97
|
+
"breaking": false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"version": "1.7.0",
|
|
101
|
+
"date": "2026-05-01T00:00:00Z",
|
|
102
|
+
"changes": [
|
|
103
|
+
"Added optional maxBinaryFetchConcurrency (integer >= 1, default 1) to bound parallel binary fetches in two-phase sync phase 3"
|
|
104
|
+
],
|
|
105
|
+
"breaking": false
|
|
74
106
|
}
|
|
75
107
|
]
|
|
76
108
|
},
|
|
@@ -95,7 +127,18 @@
|
|
|
95
127
|
"ingestAfterSync": {
|
|
96
128
|
"type": "boolean",
|
|
97
129
|
"default": false,
|
|
98
|
-
"description": "When true, chunk and embed each document after store during sync
|
|
130
|
+
"description": "When true, chunk and embed each document after store during sync without requiring trust promotion first; eligibility (validation/schema/dimensions) still enforced. When false, ingestion runs later."
|
|
131
|
+
},
|
|
132
|
+
"maxBinaryFetchesPerSync": {
|
|
133
|
+
"type": "integer",
|
|
134
|
+
"minimum": 1,
|
|
135
|
+
"description": "When set, caps binary fetch + store operations per sync run (metadata list may still return more rows). Optional for fast integration/E2E; omit in production for full sync."
|
|
136
|
+
},
|
|
137
|
+
"maxBinaryFetchConcurrency": {
|
|
138
|
+
"type": "integer",
|
|
139
|
+
"minimum": 1,
|
|
140
|
+
"default": 1,
|
|
141
|
+
"description": "Maximum concurrent binary CIP/HTTP fetches during two-phase sync phase 3. Default 1 (sequential). Increase cautiously; external APIs may throttle."
|
|
99
142
|
},
|
|
100
143
|
"binaryOperationRef": {
|
|
101
144
|
"type": "string",
|
|
@@ -133,6 +176,22 @@
|
|
|
133
176
|
"default": true,
|
|
134
177
|
"description": "When true, binary parameterMapping and HTTP path templates use a lookup view that merges metadata and coalesces storage-scope ids from a nested item parentReference when the row's parentReference omits them. Set false for strict manifest-only paths."
|
|
135
178
|
},
|
|
179
|
+
"jsonNestedDownloadUrlKeys": {
|
|
180
|
+
"type": "array",
|
|
181
|
+
"items": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"minLength": 1
|
|
184
|
+
},
|
|
185
|
+
"description": "Optional JSON object key names (exact match) searched depth-first when a binary GET returns JSON without bytes; requires preAuthenticatedDownloadUrlHostSuffixAllowlist. Merged into binaryOperation when using binaryOperationRef."
|
|
186
|
+
},
|
|
187
|
+
"preAuthenticatedDownloadUrlHostSuffixAllowlist": {
|
|
188
|
+
"type": "array",
|
|
189
|
+
"items": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"minLength": 1
|
|
192
|
+
},
|
|
193
|
+
"description": "Substrings the discovered follow-up URL must contain (case-insensitive) before an unauthenticated GET. Required when jsonNestedDownloadUrlKeys is set."
|
|
194
|
+
},
|
|
136
195
|
"processing": {
|
|
137
196
|
"type": "object",
|
|
138
197
|
"properties": {
|
|
@@ -219,6 +278,27 @@
|
|
|
219
278
|
}
|
|
220
279
|
},
|
|
221
280
|
"additionalProperties": false
|
|
281
|
+
},
|
|
282
|
+
"changeDetection": {
|
|
283
|
+
"type": "object",
|
|
284
|
+
"description": "Optional configuration to ignore volatile metadata keys/paths during contentHash / change detection only.",
|
|
285
|
+
"properties": {
|
|
286
|
+
"ignoreMetadataKeys": {
|
|
287
|
+
"type": "array",
|
|
288
|
+
"description": "Top-level metadata keys to ignore when computing contentHash / change detection.",
|
|
289
|
+
"items": { "type": "string", "minLength": 1 },
|
|
290
|
+
"uniqueItems": true,
|
|
291
|
+
"default": []
|
|
292
|
+
},
|
|
293
|
+
"ignoreMetadataPaths": {
|
|
294
|
+
"type": "array",
|
|
295
|
+
"description": "Optional dot-path patterns to ignore (e.g. 'analytics.*', 'versions').",
|
|
296
|
+
"items": { "type": "string", "minLength": 1 },
|
|
297
|
+
"uniqueItems": true,
|
|
298
|
+
"default": []
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"additionalProperties": false
|
|
222
302
|
}
|
|
223
303
|
},
|
|
224
304
|
"additionalProperties": false
|
|
@@ -113,6 +113,43 @@ function resolveConfigurationValues(configArray, envMap, secrets, systemKey) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Collects `configuration[]` entry names with `location: "variable"` whose values look
|
|
118
|
+
* resolved for publish (literal strings: no `{{`, not `kv://`, non-empty). Used after
|
|
119
|
+
* `resolveConfigurationValues` mutates the upload payload.
|
|
120
|
+
*
|
|
121
|
+
* @param {{ application?: { configuration?: unknown[] }, dataSources?: unknown[] }} payload - Pipeline upload payload
|
|
122
|
+
* @returns {string[]} Unique names in encounter order (application first, then dataSources)
|
|
123
|
+
*/
|
|
124
|
+
function collectResolvedVariableConfigurationNames(payload) {
|
|
125
|
+
const out = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
|
|
128
|
+
function consider(arr) {
|
|
129
|
+
if (!Array.isArray(arr)) return;
|
|
130
|
+
for (const item of arr) {
|
|
131
|
+
if (!item || typeof item !== 'object') continue;
|
|
132
|
+
if (String(item.location || '').toLowerCase() !== 'variable') continue;
|
|
133
|
+
const name = item.name && String(item.name).trim();
|
|
134
|
+
if (!name || seen.has(name)) continue;
|
|
135
|
+
const val = item.value;
|
|
136
|
+
if (typeof val !== 'string') continue;
|
|
137
|
+
const trimmed = val.trim();
|
|
138
|
+
if (!trimmed) continue;
|
|
139
|
+
if (trimmed.startsWith('kv://')) continue;
|
|
140
|
+
if (trimmed.includes('{{')) continue;
|
|
141
|
+
seen.add(name);
|
|
142
|
+
out.push(name);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
consider(payload?.application?.configuration);
|
|
147
|
+
for (const ds of payload?.dataSources || []) {
|
|
148
|
+
consider(ds?.configuration);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
116
153
|
/**
|
|
117
154
|
* Returns the set of variable names (keys) defined in env.template content.
|
|
118
155
|
*
|
|
@@ -175,6 +212,7 @@ async function retemplateConfigurationForDownload(systemKey, configArray) {
|
|
|
175
212
|
module.exports = {
|
|
176
213
|
buildResolvedEnvMapForIntegration,
|
|
177
214
|
resolveConfigurationValues,
|
|
215
|
+
collectResolvedVariableConfigurationNames,
|
|
178
216
|
getEnvTemplateVariableNames,
|
|
179
217
|
retemplateConfigurationFromEnvTemplate,
|
|
180
218
|
retemplateConfigurationForDownload,
|
|
@@ -17,11 +17,12 @@ const { discoverDataplaneUrl } = require('../commands/wizard-dataplane');
|
|
|
17
17
|
* @param {string} controllerUrl - Controller URL
|
|
18
18
|
* @param {string} environment - Environment key
|
|
19
19
|
* @param {Object} authConfig - Authentication configuration
|
|
20
|
+
* @param {{ silent?: boolean }} [opts] - Passed to discoverDataplaneUrl
|
|
20
21
|
* @returns {Promise<string>} Resolved dataplane URL
|
|
21
22
|
* @throws {Error} If dataplane URL cannot be resolved
|
|
22
23
|
*/
|
|
23
|
-
async function resolveDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
24
|
-
return await discoverDataplaneUrl(controllerUrl, environment, authConfig);
|
|
24
|
+
async function resolveDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
|
|
25
|
+
return await discoverDataplaneUrl(controllerUrl, environment, authConfig, opts);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
module.exports = {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Capacity scenario outcome lines for DatasourceTestRun TTY (shared by header + debug appendix).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { successGlyph, failureGlyph } = require('./cli-test-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
getCipCapacityDisplayConfig,
|
|
13
|
+
standardOperationRank,
|
|
14
|
+
parseCapacityDetailKey
|
|
15
|
+
} = require('./load-cip-capacity-display-config');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} op
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function formatCapacityOperationLabel(op) {
|
|
22
|
+
const { aliases } = getCipCapacityDisplayConfig();
|
|
23
|
+
return (aliases && aliases[op]) || op;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} envelope
|
|
28
|
+
* @returns {object|null}
|
|
29
|
+
*/
|
|
30
|
+
function findCapacityStep(envelope) {
|
|
31
|
+
const dbg = envelope && envelope.debug;
|
|
32
|
+
const e2e = dbg && dbg.e2eAsyncDebug;
|
|
33
|
+
const stepDebug = e2e && Array.isArray(e2e.stepDebug) ? e2e.stepDebug : [];
|
|
34
|
+
return stepDebug.find(s => s && String(s.name) === 'capacity') || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object[]} rows
|
|
39
|
+
* @param {string} datasourceKey
|
|
40
|
+
* @returns {object[]}
|
|
41
|
+
*/
|
|
42
|
+
function filterCapacityDatasourceRows(rows, datasourceKey) {
|
|
43
|
+
if (!datasourceKey) return rows;
|
|
44
|
+
const matched = rows.filter(r => r && String(r.key) === datasourceKey);
|
|
45
|
+
return matched.length ? matched : rows;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {object[]} dsRows
|
|
50
|
+
* @returns {Map<string, { ok: boolean, error: string, minIndex: number }>}
|
|
51
|
+
*/
|
|
52
|
+
function mergeCapacityDetailsByOp(dsRows) {
|
|
53
|
+
/** @type {Map<string, { ok: boolean, error: string, minIndex: number }>} */
|
|
54
|
+
const byOp = new Map();
|
|
55
|
+
for (const ds of dsRows) {
|
|
56
|
+
const details = ds && Array.isArray(ds.capabilityDetails) ? ds.capabilityDetails : [];
|
|
57
|
+
for (const row of details) {
|
|
58
|
+
if (!row || row.key === undefined || row.key === null) continue;
|
|
59
|
+
const parsed = parseCapacityDetailKey(String(row.key));
|
|
60
|
+
if (!parsed) continue;
|
|
61
|
+
const { op, index } = parsed;
|
|
62
|
+
const ok = row.success !== false && !row.error;
|
|
63
|
+
const err = row.error ? String(row.error) : '';
|
|
64
|
+
const prev = byOp.get(op);
|
|
65
|
+
if (!prev) {
|
|
66
|
+
byOp.set(op, { ok, error: ok ? '' : err, minIndex: index });
|
|
67
|
+
} else {
|
|
68
|
+
byOp.set(op, {
|
|
69
|
+
ok: prev.ok && ok,
|
|
70
|
+
error: prev.error || err,
|
|
71
|
+
minIndex: Math.min(prev.minIndex, index)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return byOp;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string[]} standardOrder
|
|
81
|
+
* @param {Map<string, { minIndex: number }>} byOp
|
|
82
|
+
* @returns {(a: string, b: string) => number}
|
|
83
|
+
*/
|
|
84
|
+
function capacityOpComparator(standardOrder, byOp) {
|
|
85
|
+
return (a, b) => {
|
|
86
|
+
const ia = byOp.get(a).minIndex;
|
|
87
|
+
const ib = byOp.get(b).minIndex;
|
|
88
|
+
if (ia !== ib) return ia - ib;
|
|
89
|
+
const ra = standardOperationRank(standardOrder, a);
|
|
90
|
+
const rb = standardOperationRank(standardOrder, b);
|
|
91
|
+
if (ra !== rb) return ra - rb;
|
|
92
|
+
return a.localeCompare(b);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string[]} lines
|
|
98
|
+
* @param {Map<string, { ok: boolean, error: string }>} byOp
|
|
99
|
+
* @param {string[]} opsSorted
|
|
100
|
+
*/
|
|
101
|
+
function appendCapacityOperationsLines(lines, byOp, opsSorted) {
|
|
102
|
+
lines.push('');
|
|
103
|
+
lines.push(chalk.blue.bold('Capacity operations:'));
|
|
104
|
+
for (const op of opsSorted) {
|
|
105
|
+
const row = byOp.get(op);
|
|
106
|
+
const label = formatCapacityOperationLabel(op);
|
|
107
|
+
const sym = row.ok ? successGlyph() : failureGlyph();
|
|
108
|
+
const tail = row.ok ? '' : chalk.red(` — ${row.error || 'failed'}`);
|
|
109
|
+
lines.push(` ${sym} ${chalk.white(label)}${tail}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* List capacity scenario outcomes from the capacity step (order: scenario index `#`, then schema op order).
|
|
115
|
+
* @param {string[]} lines
|
|
116
|
+
* @param {Object} envelope
|
|
117
|
+
*/
|
|
118
|
+
function pushCapacityOperationsSummaryLines(lines, envelope) {
|
|
119
|
+
const capStep = findCapacityStep(envelope);
|
|
120
|
+
if (!capStep || !capStep.evidence || !Array.isArray(capStep.evidence.datasources)) return;
|
|
121
|
+
|
|
122
|
+
const dk =
|
|
123
|
+
envelope && envelope.datasourceKey !== undefined && envelope.datasourceKey !== null
|
|
124
|
+
? String(envelope.datasourceKey)
|
|
125
|
+
: '';
|
|
126
|
+
const dsRows = filterCapacityDatasourceRows(capStep.evidence.datasources, dk);
|
|
127
|
+
const byOp = mergeCapacityDetailsByOp(dsRows);
|
|
128
|
+
if (!byOp.size) return;
|
|
129
|
+
|
|
130
|
+
const { standardOrder } = getCipCapacityDisplayConfig();
|
|
131
|
+
const opsSorted = Array.from(byOp.keys()).sort(capacityOpComparator(standardOrder, byOp));
|
|
132
|
+
appendCapacityOperationsLines(lines, byOp, opsSorted);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} capacityKey
|
|
137
|
+
* @returns {string|null}
|
|
138
|
+
*/
|
|
139
|
+
function parseCapacityScenarioOp(capacityKey) {
|
|
140
|
+
const p = parseCapacityDetailKey(String(capacityKey));
|
|
141
|
+
return p ? p.op : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
pushCapacityOperationsSummaryLines,
|
|
146
|
+
formatCapacityOperationLabel,
|
|
147
|
+
parseCapacityScenarioOp,
|
|
148
|
+
parseCapacityDetailKey
|
|
149
|
+
};
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
const { SEP, appendReferenceLayoutLines } = require('./datasource-test-run-display');
|
|
8
8
|
const { buildDebugEnvelopeSlice } = require('./datasource-test-run-debug-slice');
|
|
9
|
+
const {
|
|
10
|
+
pushCapacityOperationsSummaryLines,
|
|
11
|
+
parseCapacityScenarioOp,
|
|
12
|
+
parseCapacityDetailKey,
|
|
13
|
+
formatCapacityOperationLabel
|
|
14
|
+
} = require('./datasource-test-run-capacity-operations');
|
|
9
15
|
|
|
10
16
|
const FULL_MAX_BYTES_PER_STRING = 8192;
|
|
11
17
|
const RAW_MAX_STRING_TTY = 65536;
|
|
@@ -74,9 +80,140 @@ function redactDebugText(text) {
|
|
|
74
80
|
.replace(/("authorization"\s*:\s*")[^"]*(")/g, '$1[REDACTED]$2');
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
function getBestEffortStepList(envelope) {
|
|
84
|
+
const v = envelope && envelope.validation;
|
|
85
|
+
const metrics = v && v.metricsOutput;
|
|
86
|
+
const steps = metrics && Array.isArray(metrics.steps) ? metrics.steps : [];
|
|
87
|
+
if (steps.length) return steps;
|
|
88
|
+
const dbg = envelope && envelope.debug;
|
|
89
|
+
const stepDebug =
|
|
90
|
+
dbg && dbg.e2eAsyncDebug && Array.isArray(dbg.e2eAsyncDebug.stepDebug) ? dbg.e2eAsyncDebug.stepDebug : [];
|
|
91
|
+
return stepDebug;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findStepByName(steps, name) {
|
|
95
|
+
return steps.find(s => s && String(s.name || s.step) === name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sumNumber(rows, key) {
|
|
99
|
+
return rows.reduce((acc, r) => acc + (Number(r && r[key]) || 0), 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function appendE2eWorkerHeadLine(lines, timing) {
|
|
103
|
+
const work = timing.durationSeconds;
|
|
104
|
+
if (work === undefined || work === null || work === '') return;
|
|
105
|
+
const w = Number(work);
|
|
106
|
+
if (Number.isNaN(w)) return;
|
|
107
|
+
let head = `E2E worker: ~${w.toFixed(3)}s`;
|
|
108
|
+
const wall = timing.wallClockSeconds;
|
|
109
|
+
if (wall !== undefined && wall !== null && wall !== '') {
|
|
110
|
+
const wl = Number(wall);
|
|
111
|
+
if (!Number.isNaN(wl)) {
|
|
112
|
+
head += ` (wall ~${wl.toFixed(3)}s)`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
lines.push(head);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function appendE2eStepDurationLines(lines, timing) {
|
|
119
|
+
const sd = timing.stepDurations;
|
|
120
|
+
if (!Array.isArray(sd) || !sd.length) return;
|
|
121
|
+
for (const row of sd) {
|
|
122
|
+
if (!row || typeof row !== 'object') continue;
|
|
123
|
+
const step = row.step !== undefined && row.step !== null ? String(row.step) : '?';
|
|
124
|
+
const sec =
|
|
125
|
+
row.durationSeconds !== undefined && row.durationSeconds !== null
|
|
126
|
+
? Number(row.durationSeconds)
|
|
127
|
+
: NaN;
|
|
128
|
+
if (!Number.isNaN(sec)) {
|
|
129
|
+
lines.push(` ${step}: ~${sec.toFixed(3)}s`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getFirstSyncJobPhaseTimings(e2e) {
|
|
135
|
+
const stepDebug = Array.isArray(e2e.stepDebug) ? e2e.stepDebug : [];
|
|
136
|
+
const syncStep = stepDebug.find(s => s && String(s.name) === 'sync');
|
|
137
|
+
const jobs = syncStep && syncStep.evidence && syncStep.evidence.jobs;
|
|
138
|
+
const first = Array.isArray(jobs) && jobs.length ? jobs[0] : null;
|
|
139
|
+
const audit = first && first.audit;
|
|
140
|
+
const pt = audit && typeof audit === 'object' ? audit.phaseTimingsSeconds : null;
|
|
141
|
+
return pt && typeof pt === 'object' ? pt : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatPhaseTimingParts(phaseTimings) {
|
|
145
|
+
const order = ['phase1', 'phase2', 'phase3', 'phase4'];
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (const k of order) {
|
|
148
|
+
const raw = phaseTimings[k];
|
|
149
|
+
if (raw === undefined || raw === null) continue;
|
|
150
|
+
const v = Number(raw);
|
|
151
|
+
if (!Number.isNaN(v)) {
|
|
152
|
+
parts.push(`${k} ~${v.toFixed(3)}s`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return parts;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function appendSyncPhaseTimingsFromE2e(lines, e2e) {
|
|
159
|
+
const phaseTimings = getFirstSyncJobPhaseTimings(e2e);
|
|
160
|
+
if (!phaseTimings) return;
|
|
161
|
+
const parts = formatPhaseTimingParts(phaseTimings);
|
|
162
|
+
if (parts.length) {
|
|
163
|
+
lines.push(`Sync phases (first job): ${parts.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* E2E poll envelope: `debug.e2eAsyncDebug` from dataplane run store (timing + stepDebug).
|
|
169
|
+
* @param {string[]} lines
|
|
170
|
+
* @param {Object} envelope
|
|
171
|
+
*/
|
|
172
|
+
function pushE2eTimingSummaryLines(lines, envelope) {
|
|
173
|
+
const dbg = envelope && envelope.debug;
|
|
174
|
+
const e2e = dbg && dbg.e2eAsyncDebug;
|
|
175
|
+
if (!e2e || typeof e2e !== 'object') return;
|
|
176
|
+
|
|
177
|
+
const timing = e2e.timing;
|
|
178
|
+
if (timing && typeof timing === 'object') {
|
|
179
|
+
appendE2eWorkerHeadLine(lines, timing);
|
|
180
|
+
appendE2eStepDurationLines(lines, timing);
|
|
181
|
+
}
|
|
182
|
+
appendSyncPhaseTimingsFromE2e(lines, e2e);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function pushSyncSummaryLines(lines, envelope) {
|
|
186
|
+
const steps = getBestEffortStepList(envelope);
|
|
187
|
+
const syncStep = findStepByName(steps, 'sync');
|
|
188
|
+
const syncStatusStep = findStepByName(steps, 'sync_status');
|
|
189
|
+
const persistenceStep = findStepByName(steps, 'persistence');
|
|
190
|
+
|
|
191
|
+
if (syncStep && syncStep.evidence && Array.isArray(syncStep.evidence.jobs)) {
|
|
192
|
+
const jobs = syncStep.evidence.jobs;
|
|
193
|
+
const processed = sumNumber(jobs, 'recordsProcessed');
|
|
194
|
+
const total = sumNumber(jobs, 'totalRecords');
|
|
195
|
+
lines.push(`Sync: ${processed}/${total} processed`);
|
|
196
|
+
}
|
|
197
|
+
if (syncStatusStep && syncStatusStep.evidence && Array.isArray(syncStatusStep.evidence.datasources)) {
|
|
198
|
+
const rows = syncStatusStep.evidence.datasources;
|
|
199
|
+
const total = sumNumber(rows, 'totalRecords');
|
|
200
|
+
const active = sumNumber(rows, 'activeRecords');
|
|
201
|
+
lines.push(`Sync status: ${active}/${total} active`);
|
|
202
|
+
}
|
|
203
|
+
if (persistenceStep && persistenceStep.evidence && persistenceStep.evidence.recordCount !== undefined) {
|
|
204
|
+
lines.push(`Persistence: ${persistenceStep.evidence.recordCount} record(s)`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
77
208
|
function pushSummaryRefLines(lines, envelope) {
|
|
78
209
|
const before = lines.length;
|
|
79
|
-
|
|
210
|
+
try {
|
|
211
|
+
pushSyncSummaryLines(lines, envelope);
|
|
212
|
+
pushE2eTimingSummaryLines(lines, envelope);
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore best-effort debug summary extraction
|
|
215
|
+
}
|
|
216
|
+
appendReferenceLayoutLines(lines, envelope, { maxRefChars: 600, includeDebugMeta: true });
|
|
80
217
|
if (lines.length === before) {
|
|
81
218
|
lines.push('(No audit or debug references on this report.)');
|
|
82
219
|
}
|
|
@@ -130,6 +267,11 @@ module.exports = {
|
|
|
130
267
|
resolveDebugDisplayMode,
|
|
131
268
|
truncateUtf8String,
|
|
132
269
|
formatDatasourceTestRunDebugBlock,
|
|
270
|
+
pushE2eTimingSummaryLines,
|
|
271
|
+
pushCapacityOperationsSummaryLines,
|
|
272
|
+
parseCapacityScenarioOp,
|
|
273
|
+
parseCapacityDetailKey,
|
|
274
|
+
formatCapacityOperationLabel,
|
|
133
275
|
FULL_MAX_BYTES_PER_STRING,
|
|
134
276
|
RAW_MAX_STRING_TTY,
|
|
135
277
|
RAW_MAX_STRING_PIPE,
|
|
@@ -7,12 +7,23 @@
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const { sectionTitle, headerKeyValue, colorAggregateGlyph, successGlyph, failureGlyph } = require('./cli-test-layout-chalk');
|
|
9
9
|
const { appendCertificateTTY } = require('./datasource-test-run-certificate-tty');
|
|
10
|
+
const { buildTtyMetaLines: buildTtyMetaLinesCore } = require('./datasource-test-run-tty-meta-lines');
|
|
10
11
|
|
|
11
12
|
const SEP = '────────────────────────────────';
|
|
12
13
|
|
|
13
14
|
/** @type {number} */
|
|
14
15
|
const DEFAULT_MAX_REF_CHARS = 200;
|
|
15
16
|
|
|
17
|
+
// UI-only: round float seconds tokens like "1.1713749s" -> "1.171s".
|
|
18
|
+
function formatSecondsText(text) {
|
|
19
|
+
const txt = String(text);
|
|
20
|
+
return txt.replace(/(\d+\.\d+)(?=s\b)/g, m => {
|
|
21
|
+
const n = Number(m);
|
|
22
|
+
if (!Number.isFinite(n)) return m;
|
|
23
|
+
return n.toFixed(3);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* @param {'ok'|'warn'|'fail'|'skipped'} status
|
|
18
29
|
* @returns {string}
|
|
@@ -144,23 +155,11 @@ function formatCapabilityFocusSection(envelope, focusKey) {
|
|
|
144
155
|
|
|
145
156
|
/**
|
|
146
157
|
* @param {Object} envelope
|
|
158
|
+
* @param {{ includeDebugExecutionSummary?: boolean }} [ttyOptions]
|
|
147
159
|
* @returns {string[]}
|
|
148
160
|
*/
|
|
149
|
-
function buildTtyMetaLines(envelope) {
|
|
150
|
-
|
|
151
|
-
lines.push(
|
|
152
|
-
headerKeyValue('Datasource:', `${envelope.datasourceKey} (${envelope.systemKey})`)
|
|
153
|
-
);
|
|
154
|
-
lines.push(headerKeyValue('Run:', String(envelope.runType)));
|
|
155
|
-
lines.push(formatEnvelopeStatusLine(envelope));
|
|
156
|
-
const rid = envelope.runId || envelope.testRunId;
|
|
157
|
-
if (rid) lines.push(`${chalk.gray('Run ID:')} ${chalk.cyan(String(rid))}`);
|
|
158
|
-
if (envelope.reportCompleteness && envelope.reportCompleteness !== 'full') {
|
|
159
|
-
lines.push(
|
|
160
|
-
`${chalk.gray('Report:')} ${chalk.yellow(String(envelope.reportCompleteness))}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
return lines;
|
|
161
|
+
function buildTtyMetaLines(envelope, ttyOptions = {}) {
|
|
162
|
+
return buildTtyMetaLinesCore(envelope, formatEnvelopeStatusLine, ttyOptions);
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
/**
|
|
@@ -236,16 +235,6 @@ function appendDebugPayloadRefLines(lines, dbg, maxRefChars) {
|
|
|
236
235
|
*/
|
|
237
236
|
function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
238
237
|
let added = false;
|
|
239
|
-
function formatSecondsText(s) {
|
|
240
|
-
const txt = String(s);
|
|
241
|
-
// UI-only: reduce noisy float seconds to 3 decimals, e.g. "1.1713749s" -> "1.171s".
|
|
242
|
-
// Applies only to number tokens immediately followed by "s".
|
|
243
|
-
return txt.replace(/(\d+\.\d+)(?=s\b)/g, m => {
|
|
244
|
-
const n = Number(m);
|
|
245
|
-
if (!Number.isFinite(n)) return m;
|
|
246
|
-
return n.toFixed(3);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
238
|
if (dbg.mode) {
|
|
250
239
|
lines.push(
|
|
251
240
|
`${chalk.blue.bold('debug.mode:')} ${chalk.white(String(dbg.mode))}`
|
|
@@ -272,6 +261,7 @@ function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
|
272
261
|
*/
|
|
273
262
|
function appendReferenceLayoutLines(lines, envelope, opts = {}) {
|
|
274
263
|
const maxRefChars = opts.maxRefChars ?? DEFAULT_MAX_REF_CHARS;
|
|
264
|
+
const includeDebugMeta = opts.includeDebugMeta === true;
|
|
275
265
|
const audit = envelope && envelope.audit;
|
|
276
266
|
const dbg = envelope && envelope.debug;
|
|
277
267
|
const parts = [];
|
|
@@ -287,7 +277,9 @@ function appendReferenceLayoutLines(lines, envelope, opts = {}) {
|
|
|
287
277
|
|
|
288
278
|
if (dbg && typeof dbg === 'object') {
|
|
289
279
|
parts.push(appendStringRefBlock(lines, 'debug.executionIds:', dbg.executionIds, maxRefChars));
|
|
290
|
-
|
|
280
|
+
if (includeDebugMeta) {
|
|
281
|
+
parts.push(appendDebugMetaLines(lines, dbg, maxRefChars));
|
|
282
|
+
}
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
return parts.some(Boolean);
|
|
@@ -329,6 +321,8 @@ function appendIntegrationStepLines(lines, envelope) {
|
|
|
329
321
|
const integ = envelope && envelope.integration;
|
|
330
322
|
const steps = integ && Array.isArray(integ.stepResults) ? integ.stepResults : [];
|
|
331
323
|
if (!steps.length) return;
|
|
324
|
+
lines.push('');
|
|
325
|
+
lines.push(chalk.gray(SEP));
|
|
332
326
|
lines.push(sectionTitle('Integration steps:'));
|
|
333
327
|
for (const st of steps) {
|
|
334
328
|
lines.push(formatIntegrationStepLine(st));
|
|
@@ -422,22 +416,27 @@ function formatDatasourceTestRunSummary(envelope, options = {}) {
|
|
|
422
416
|
/**
|
|
423
417
|
* Default TTY block (header + verdict line + short summary + optional completeness).
|
|
424
418
|
* @param {Object} envelope
|
|
425
|
-
* @param {{
|
|
419
|
+
* @param {{
|
|
420
|
+
* focusCapabilityKey?: string,
|
|
421
|
+
* includeRefs?: boolean,
|
|
422
|
+
* includeDebugExecutionSummary?: boolean
|
|
423
|
+
* }} [options] - When focusCapabilityKey set (e.g. --capability), append single-cap block (plan §2.3).
|
|
424
|
+
* includeDebugExecutionSummary: show dataplane `debug.executionSummary` in header (TTY debug appendix uses CLI `--debug`).
|
|
426
425
|
* @returns {string}
|
|
427
426
|
*/
|
|
428
427
|
function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
429
428
|
if (!envelope || typeof envelope !== 'object') return '';
|
|
430
|
-
const lines = [
|
|
429
|
+
const lines = [
|
|
430
|
+
...buildTtyMetaLines(envelope, {
|
|
431
|
+
includeDebugExecutionSummary: options.includeDebugExecutionSummary === true
|
|
432
|
+
})
|
|
433
|
+
];
|
|
431
434
|
appendNoCapabilitiesReportedLine(lines, envelope, options);
|
|
432
435
|
lines.push('');
|
|
433
436
|
lines.push(sectionTitle('Verdict:'));
|
|
434
437
|
lines.push(chalk.white(pickExecutiveVerdictLine(envelope)));
|
|
435
438
|
appendCertificateTTY(lines, envelope);
|
|
436
|
-
lines
|
|
437
|
-
lines.push(chalk.gray(SEP));
|
|
438
|
-
if (appendReferenceLayoutLines(lines, envelope, { maxRefChars: 160 })) {
|
|
439
|
-
lines.push('');
|
|
440
|
-
}
|
|
439
|
+
appendRefsSectionIfEnabled(lines, envelope, options);
|
|
441
440
|
appendValidationIssueLines(lines, envelope);
|
|
442
441
|
appendIntegrationStepLines(lines, envelope);
|
|
443
442
|
const focus = normalizedFocusCapabilityKey(options.focusCapabilityKey);
|
|
@@ -447,6 +446,20 @@ function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
|
447
446
|
return lines.join('\n');
|
|
448
447
|
}
|
|
449
448
|
|
|
449
|
+
function appendRefsSectionIfEnabled(lines, envelope, options) {
|
|
450
|
+
if (options.includeRefs !== true) return;
|
|
451
|
+
const before = lines.length;
|
|
452
|
+
lines.push('');
|
|
453
|
+
lines.push(chalk.gray(SEP));
|
|
454
|
+
const added = appendReferenceLayoutLines(lines, envelope, { maxRefChars: 160, includeDebugMeta: false });
|
|
455
|
+
if (added) {
|
|
456
|
+
lines.push('');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// remove the blank + separator when no refs exist
|
|
460
|
+
lines.length = before;
|
|
461
|
+
}
|
|
462
|
+
|
|
450
463
|
module.exports = {
|
|
451
464
|
formatDatasourceTestRunSummary,
|
|
452
465
|
formatDatasourceTestRunTTY,
|
|
@@ -42,7 +42,12 @@ function emitCapabilityScopeDiagnostics(envelope, opts = {}) {
|
|
|
42
42
|
* @param {string} [options.requestedCapabilityKey]
|
|
43
43
|
*/
|
|
44
44
|
function printDatasourceTestRunForTTY(envelope, options = {}) {
|
|
45
|
-
const
|
|
45
|
+
const mode = resolveDebugDisplayMode(options.debug);
|
|
46
|
+
const displayOpts = {
|
|
47
|
+
focusCapabilityKey: options.requestedCapabilityKey,
|
|
48
|
+
includeRefs: Boolean(mode),
|
|
49
|
+
includeDebugExecutionSummary: Boolean(mode)
|
|
50
|
+
};
|
|
46
51
|
if (options.json) {
|
|
47
52
|
logger.log(JSON.stringify(envelope));
|
|
48
53
|
return;
|
|
@@ -52,7 +57,6 @@ function printDatasourceTestRunForTTY(envelope, options = {}) {
|
|
|
52
57
|
} else {
|
|
53
58
|
logger.log(formatDatasourceTestRunTTY(envelope, displayOpts));
|
|
54
59
|
}
|
|
55
|
-
const mode = resolveDebugDisplayMode(options.debug);
|
|
56
60
|
if (mode) {
|
|
57
61
|
const appendix = formatDatasourceTestRunDebugBlock(envelope, mode, process.stdout.isTTY);
|
|
58
62
|
if (appendix) logger.log(appendix);
|