@aifabrix/builder 2.44.3 → 2.44.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) 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 +31 -15
  5. package/lib/api/certificates.api.js +21 -3
  6. package/lib/api/types/wizard.types.js +2 -1
  7. package/lib/certification/post-unified-cert-sync.js +13 -2
  8. package/lib/certification/sync-after-external-command.js +6 -3
  9. package/lib/certification/sync-system-certification.js +60 -14
  10. package/lib/cli/setup-app.help.js +1 -1
  11. package/lib/cli/setup-app.test-commands.js +75 -39
  12. package/lib/cli/setup-infra.js +6 -2
  13. package/lib/cli/setup-utility.js +20 -1
  14. package/lib/commands/datasource-unified-test-cli.js +81 -46
  15. package/lib/commands/datasource-unified-test-cli.options.js +4 -2
  16. package/lib/commands/datasource.js +3 -31
  17. package/lib/commands/repair-datasource-keys.js +1 -1
  18. package/lib/commands/repair-datasource-openapi.js +57 -0
  19. package/lib/commands/repair-datasource.js +5 -0
  20. package/lib/commands/repair-internal.js +2 -4
  21. package/lib/commands/repair-rbac.js +25 -2
  22. package/lib/commands/repair.js +2 -19
  23. package/lib/commands/test-e2e-external.js +9 -9
  24. package/lib/commands/up-common.js +25 -0
  25. package/lib/commands/upload.js +18 -4
  26. package/lib/commands/wizard-core.js +53 -11
  27. package/lib/commands/wizard-dataplane.js +14 -6
  28. package/lib/commands/wizard-entity-selection.js +71 -14
  29. package/lib/commands/wizard-headless.js +5 -2
  30. package/lib/commands/wizard-helpers.js +13 -1
  31. package/lib/commands/wizard.js +208 -60
  32. package/lib/datasource/datasource-validate-display.js +162 -0
  33. package/lib/datasource/datasource-validate-summary.js +194 -0
  34. package/lib/datasource/test-e2e.js +65 -37
  35. package/lib/datasource/unified-validation-run-body.js +1 -2
  36. package/lib/datasource/validate.js +14 -6
  37. package/lib/external-system/test.js +12 -8
  38. package/lib/generator/external-controller-manifest.js +12 -2
  39. package/lib/generator/wizard-prompts.js +7 -1
  40. package/lib/generator/wizard.js +34 -0
  41. package/lib/schema/cip-capacity-display.fallback.json +7 -0
  42. package/lib/schema/datasource-test-run.schema.json +79 -1
  43. package/lib/schema/external-datasource.schema.json +94 -2
  44. package/lib/schema/flag-map-validation-run.json +1 -2
  45. package/lib/schema/type/document-storage.json +83 -3
  46. package/lib/schema/wizard-config.schema.json +1 -1
  47. package/lib/utils/configuration-env-resolver.js +38 -0
  48. package/lib/utils/dataplane-resolver.js +3 -2
  49. package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
  50. package/lib/utils/datasource-test-run-debug-display.js +143 -1
  51. package/lib/utils/datasource-test-run-display.js +46 -33
  52. package/lib/utils/datasource-test-run-tty-log.js +6 -2
  53. package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
  54. package/lib/utils/error-formatter.js +32 -2
  55. package/lib/utils/external-readme.js +47 -3
  56. package/lib/utils/external-system-readiness-core.js +39 -0
  57. package/lib/utils/external-system-readiness-deploy-display.js +2 -3
  58. package/lib/utils/external-system-readiness-display-internals.js +3 -2
  59. package/lib/utils/external-system-system-test-tty.js +33 -9
  60. package/lib/utils/external-system-validators.js +62 -5
  61. package/lib/utils/load-cip-capacity-display-config.js +130 -0
  62. package/lib/utils/paths.js +10 -3
  63. package/lib/utils/schema-resolver.js +98 -2
  64. package/lib/utils/urls-local-registry.js +52 -10
  65. package/lib/utils/validation-run-poll.js +15 -4
  66. package/lib/utils/validation-run-request.js +4 -6
  67. package/lib/validation/dimension-display-helpers.js +60 -0
  68. package/lib/validation/validate-display-log-helpers.js +39 -0
  69. package/lib/validation/validate-display.js +89 -83
  70. package/package.json +1 -1
  71. package/templates/applications/miso-controller/env.template +6 -6
  72. package/templates/external-system/README.md.hbs +58 -32
@@ -32,6 +32,35 @@ function toKeySegment(str) {
32
32
  return sanitized || 'default';
33
33
  }
34
34
 
35
+ /**
36
+ * Align authentication.security kv:// namespaces and credentialKey with the final system key.
37
+ * Dataplane normalizes this before respond; when appName overrides a spec-derived key (e.g.
38
+ * OpenAPI title "Companies"), the builder still must rewrite nested auth so it matches env.template.
39
+ *
40
+ * @param {Object|null|undefined} authentication - authentication block from dataplane
41
+ * @param {string} systemKey - Final external system key (integration app name)
42
+ * @param {string} [authDisplayName] - Credential display name (typically title-cased app name)
43
+ */
44
+ function normalizeAuthenticationToSystemKey(authentication, systemKey, authDisplayName) {
45
+ if (!authentication || typeof authentication !== 'object') return;
46
+ authentication.credentialKey = `${systemKey}-cred`;
47
+ const security = authentication.security;
48
+ if (security && typeof security === 'object') {
49
+ for (const k of Object.keys(security)) {
50
+ const v = security[k];
51
+ if (typeof v === 'string' && v.startsWith('kv://')) {
52
+ const rest = v.slice(5);
53
+ const idx = rest.indexOf('/');
54
+ const suffix = idx >= 0 ? rest.slice(idx + 1) : '';
55
+ security[k] = suffix ? `kv://${systemKey}/${suffix}` : `kv://${systemKey}`;
56
+ }
57
+ }
58
+ }
59
+ if (authDisplayName) {
60
+ authentication.displayName = authDisplayName;
61
+ }
62
+ }
63
+
35
64
  /**
36
65
  * Generate files from dataplane-generated wizard configurations
37
66
  * @async
@@ -182,6 +211,11 @@ async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
182
211
  const originalSystemKey = systemConfig.key || finalSystemKey;
183
212
  const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
184
213
  const updatedSystemConfig = { ...systemConfig, key: finalSystemKey, displayName: appDisplayName };
214
+ normalizeAuthenticationToSystemKey(
215
+ updatedSystemConfig.authentication,
216
+ finalSystemKey,
217
+ appDisplayName
218
+ );
185
219
  const originalPrefix = `${originalSystemKey}-`;
186
220
  const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
187
221
  let newKey;
@@ -0,0 +1,7 @@
1
+ {
2
+ "$comment": "Used when external-datasource.schema.json is not on disk. standardOperationOrder must match $defs.cipDefinition.properties.operations.properties key order in dataplane app/schemas/json/external-datasource.schema.json.",
3
+ "standardOperationOrder": ["list", "get", "create", "update", "delete"],
4
+ "displayAliases": {
5
+ "create": "insert (create)"
6
+ }
7
+ }
@@ -9,7 +9,7 @@
9
9
  "reportVersion": {
10
10
  "type": "string",
11
11
  "description": "Semver-style contract version for this envelope. Policy: additive fields => minor bump within same major; removed/renamed fields => major bump. Route migrations must cite target reportVersion.",
12
- "example": "1.1.0"
12
+ "example": "1.3.0"
13
13
  },
14
14
  "runType": {
15
15
  "type": "string",
@@ -51,6 +51,10 @@
51
51
  "certificate": {
52
52
  "$ref": "#/$defs/CertificateResult"
53
53
  },
54
+ "certificateIssuance": {
55
+ "$ref": "#/$defs/CertificateIssuanceResult",
56
+ "description": "Integration-certificate auto-issue outcome for this datasource scope (VALIDATION_AUTO_ISSUE_INTEGRATION_CERTIFICATE)."
57
+ },
54
58
  "capabilitySummary": {
55
59
  "$ref": "#/$defs/CapabilitySummary"
56
60
  },
@@ -69,9 +73,50 @@
69
73
  },
70
74
  "developer": {
71
75
  "$ref": "#/$defs/DeveloperSummary"
76
+ },
77
+ "datasourceSummaries": {
78
+ "type": "array",
79
+ "description": "Optional per-datasource rollups when the underlying run evaluated multiple datasources (e.g. validationScope=externalSystem).",
80
+ "items": {
81
+ "$ref": "#/$defs/DatasourceSummary"
82
+ }
72
83
  }
73
84
  },
74
85
  "$defs": {
86
+ "DatasourceSummary": {
87
+ "type": "object",
88
+ "required": ["datasourceKey", "status"],
89
+ "additionalProperties": false,
90
+ "description": "Lightweight per-datasource rollup for system-scoped runs.",
91
+ "properties": {
92
+ "datasourceKey": {
93
+ "type": "string",
94
+ "description": "Datasource key within the system."
95
+ },
96
+ "status": {
97
+ "type": "string",
98
+ "description": "Datasource rollup: ok | warn | fail | skipped.",
99
+ "enum": ["ok", "warn", "fail", "skipped"]
100
+ },
101
+ "validationStatus": {
102
+ "type": "string",
103
+ "description": "Validation layer rollup for this datasource.",
104
+ "enum": ["ok", "warn", "fail"]
105
+ },
106
+ "certificateStatus": {
107
+ "type": "string",
108
+ "description": "Certification rollup for this datasource.",
109
+ "enum": ["passed", "not_passed"]
110
+ },
111
+ "issues": {
112
+ "type": "array",
113
+ "description": "Blocking/warning issues for this datasource.",
114
+ "items": {
115
+ "$ref": "#/$defs/Issue"
116
+ }
117
+ }
118
+ }
119
+ },
75
120
  "ValidationLayerResult": {
76
121
  "type": "object",
77
122
  "required": ["status"],
@@ -161,6 +206,39 @@
161
206
  }
162
207
  }
163
208
  },
209
+ "CertificateIssuanceResult": {
210
+ "type": "object",
211
+ "additionalProperties": false,
212
+ "required": ["status", "systemKey", "datasourceKey"],
213
+ "description": "Structured result when the dataplane evaluates integration-certificate auto-issue (safe for clients and logs).",
214
+ "properties": {
215
+ "status": {
216
+ "type": "string",
217
+ "enum": ["issued", "reusedActive", "skipped", "failed"],
218
+ "description": "issued | reusedActive | skipped | failed."
219
+ },
220
+ "reasonCode": {
221
+ "type": "string",
222
+ "description": "Stable machine code when status is skipped or failed."
223
+ },
224
+ "message": {
225
+ "type": "string",
226
+ "description": "Human-readable summary."
227
+ },
228
+ "validationHint": {
229
+ "type": "string",
230
+ "description": "Optional remediation hint."
231
+ },
232
+ "systemKey": {
233
+ "type": "string",
234
+ "description": "External system key for this issuance scope."
235
+ },
236
+ "datasourceKey": {
237
+ "type": "string",
238
+ "description": "Datasource key for this issuance scope."
239
+ }
240
+ }
241
+ },
164
242
  "CertificateResult": {
165
243
  "type": "object",
166
244
  "required": ["status"],
@@ -7,12 +7,12 @@
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"2.4.5",
10
+ "version":"2.4.6",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2026-04-07T00:00:00Z",
15
+ "updatedAt":"2026-04-20T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
18
  "maxVersion":"3.0.0",
@@ -175,6 +175,14 @@
175
175
  "exposed.profiles object profiles: attributes may be an object map (property names = field keys; values ignored), matching runtime exposed_profile_field_names and CIP/ExposedFieldsValidator behavior."
176
176
  ],
177
177
  "breaking":false
178
+ },
179
+ {
180
+ "version":"2.4.6",
181
+ "date":"2026-04-24T00:00:00Z",
182
+ "changes":[
183
+ "Added optional fetch.httpResponseNormalization for manifest-driven GET response shaping (302 Location follow, JSON redirect envelopes, nested JSON string URL follow-up)."
184
+ ],
185
+ "breaking":false
178
186
  }
179
187
  ]
180
188
  },
@@ -705,6 +713,10 @@
705
713
  "input":{
706
714
  "type":"object",
707
715
  "additionalProperties":true
716
+ },
717
+ "order":{
718
+ "type":"integer",
719
+ "description":"Optional stable sort hint for scenario execution / diagnostics."
708
720
  }
709
721
  },
710
722
  "additionalProperties":false
@@ -950,6 +962,83 @@
950
962
  }
951
963
  ],
952
964
  "$defs":{
965
+ "httpResponseNormalization":{
966
+ "type":"object",
967
+ "description":"Optional GET response shaping for miso-client adapter dicts and native 302 Location follows. Omitted = disabled.",
968
+ "additionalProperties":false,
969
+ "properties":{
970
+ "applyWhenUrlContains":{
971
+ "type":"array",
972
+ "items":{
973
+ "type":"string",
974
+ "minLength":1
975
+ },
976
+ "description":"When non-empty, normalization applies only if the request URL (path plus query, lowercased) contains at least one substring (case-insensitive)."
977
+ },
978
+ "followRedirectJsonEnvelope":{
979
+ "type":"object",
980
+ "additionalProperties":false,
981
+ "properties":{
982
+ "enabled":{
983
+ "type":"boolean",
984
+ "default":false
985
+ },
986
+ "locationHostSuffixAllowlist":{
987
+ "type":"array",
988
+ "items":{
989
+ "type":"string",
990
+ "minLength":1
991
+ },
992
+ "description":"Location header URL must contain one of these substrings (case-insensitive) before an unauthenticated follow-up GET."
993
+ }
994
+ }
995
+ },
996
+ "followNestedJsonStringUrl":{
997
+ "type":"object",
998
+ "additionalProperties":false,
999
+ "properties":{
1000
+ "enabled":{
1001
+ "type":"boolean",
1002
+ "default":false
1003
+ },
1004
+ "jsonKeys":{
1005
+ "type":"array",
1006
+ "items":{
1007
+ "type":"string",
1008
+ "minLength":1
1009
+ },
1010
+ "description":"JSON object key names searched depth-first for a non-empty string URL."
1011
+ },
1012
+ "targetUrlHostSuffixAllowlist":{
1013
+ "type":"array",
1014
+ "items":{
1015
+ "type":"string",
1016
+ "minLength":1
1017
+ },
1018
+ "description":"Discovered URL must contain one of these substrings before an unauthenticated follow-up GET."
1019
+ }
1020
+ }
1021
+ },
1022
+ "followNativeHttpRedirect302":{
1023
+ "type":"object",
1024
+ "additionalProperties":false,
1025
+ "properties":{
1026
+ "enabled":{
1027
+ "type":"boolean",
1028
+ "default":false
1029
+ },
1030
+ "locationHostSuffixAllowlist":{
1031
+ "type":"array",
1032
+ "items":{
1033
+ "type":"string",
1034
+ "minLength":1
1035
+ },
1036
+ "description":"Native httpx 302 Location URL must contain one of these substrings before follow-up GET."
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ },
953
1042
  "metadataSchemaNode":{
954
1043
  "type":"object",
955
1044
  "description":"Strict supported subset of JSON Schema for metadataSchema.",
@@ -1829,6 +1918,9 @@
1829
1918
  "additionalProperties":{
1830
1919
  "type":"string"
1831
1920
  }
1921
+ },
1922
+ "httpResponseNormalization":{
1923
+ "$ref":"#/$defs/httpResponseNormalization"
1832
1924
  }
1833
1925
  },
1834
1926
  "allOf":[
@@ -11,8 +11,7 @@
11
11
  { "cliFlag": "--timeout <ms>", "requestField": null, "note": "Client aggregate HTTP budget (POST + polls)" },
12
12
  { "cliFlag": "--debug [level]", "requestField": "includeDebug", "default": false, "note": "summary|full|raw affects TTY appendix only; any presence sets includeDebug" },
13
13
  { "cliFlag": "--no-async", "requestField": "asyncRun", "note": "When true on CLI, omit asyncRun or set false; sync-only POST" },
14
- { "cliFlag": "--test-crud", "requestField": "e2eOptions.testCrud", "runType": "e2e" },
15
- { "cliFlag": "--record-id", "requestField": "e2eOptions.recordId", "runType": "e2e" },
14
+ { "cliFlag": "--no-run-scenarios", "requestField": "e2eOptions.runScenarios", "runType": "e2e" },
16
15
  { "cliFlag": "--no-cleanup", "requestField": "e2eOptions.cleanup", "runType": "e2e" },
17
16
  { "cliFlag": "--primary-key-value", "requestField": "e2eOptions.primaryKeyValue", "runType": "e2e" },
18
17
  { "cliFlag": "--capability <key>", "requestField": "e2eOptions.capabilityKeys", "runType": "e2e", "note": "Single key forwarded as capabilityKeys array in request body when set" },
@@ -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.3.0",
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-04-22T00:00:00Z",
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 so vector search returns hits immediately. When false, ingestion runs later (e.g. Celery task or on approval). Set true for E2E tests that validate vector step."
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
@@ -184,7 +184,7 @@
184
184
  "type": "string",
185
185
  "description": "User intent (any descriptive text, e.g., 'sales-focused CRM integration')",
186
186
  "minLength": 1,
187
- "maxLength": 500
187
+ "maxLength": 1000
188
188
  },
189
189
  "fieldOnboardingLevel": {
190
190
  "type": "string",
@@ -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
+ };