@aifabrix/builder 2.44.1 → 2.44.3
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/lib/api/types/certificates.types.js +1 -1
- package/lib/app/show-display.js +60 -16
- package/lib/certification/merge-certification-from-artifact.js +46 -16
- package/lib/core/secrets.js +47 -13
- package/lib/datasource/log-viewer.js +30 -6
- package/lib/schema/external-system.schema.json +29 -6
- package/lib/utils/app-service-env-from-builder.js +47 -6
- package/lib/utils/config-paths.js +4 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +50 -20
- package/lib/utils/urls-local-registry.js +36 -12
- package/lib/validation/external-manifest-validator.js +22 -15
- package/package.json +1 -1
- package/templates/applications/dataplane/env.template +2 -1
package/.npmrc.token
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
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
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* @property {string} [issuedBy]
|
|
23
23
|
* @property {string} [licenseLevelIssuer]
|
|
24
24
|
* @property {string} [dataplaneVersion]
|
|
25
|
-
* @property {
|
|
25
|
+
* @property {'RS256'} [algorithm] Integration certificate signing algorithm (RS256 only)
|
|
26
26
|
* @property {string|null} [publicKey]
|
|
27
27
|
* @property {string|null} [publicKeyFingerprint]
|
|
28
28
|
* @property {Object} [metadata]
|
package/lib/app/show-display.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const logger = require('../utils/logger');
|
|
14
|
-
const { truncatePublicKeyPreview } = require('./certification-show-enrich');
|
|
15
14
|
|
|
16
15
|
function logSourceAndHeader(summary) {
|
|
17
16
|
const isOffline = summary.source === 'offline';
|
|
@@ -263,23 +262,51 @@ function displayExternalAppBlock(summary) {
|
|
|
263
262
|
}
|
|
264
263
|
|
|
265
264
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param {
|
|
265
|
+
* Colored certification outcome from `status` string.
|
|
266
|
+
* @param {string} raw - lowercased status
|
|
267
|
+
* @returns {string}
|
|
268
268
|
*/
|
|
269
|
-
function
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
269
|
+
function formatCertificationOutcome(raw) {
|
|
270
|
+
if (raw === 'passed') return chalk.green('passed');
|
|
271
|
+
if (raw === 'not_passed') return chalk.red('not_passed');
|
|
272
|
+
if (raw === 'pending') return chalk.yellow('pending');
|
|
273
|
+
return raw || '—';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Certification status line: enabled ✔/✖ plus outcome (passed / not_passed / pending).
|
|
278
|
+
* @param {Object} c - certification block from system file
|
|
279
|
+
* @returns {string}
|
|
280
|
+
*/
|
|
281
|
+
function formatCertificationStatusLine(c) {
|
|
282
|
+
const enabledTrue = c.enabled === true;
|
|
283
|
+
const enabledSym = enabledTrue ? chalk.green('✔') : chalk.red('✖');
|
|
284
|
+
const raw = (c.status && String(c.status).toLowerCase()) || '';
|
|
285
|
+
const outcome = formatCertificationOutcome(raw);
|
|
286
|
+
return ` Status: ${enabledSym} ${outcome}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Local certification fields from system file (TTY block).
|
|
291
|
+
* @param {Object} c
|
|
292
|
+
*/
|
|
293
|
+
function logCertificationLocalFileDetails(c) {
|
|
294
|
+
logger.log(formatCertificationStatusLine(c));
|
|
295
|
+
const levelStr =
|
|
296
|
+
c.level === undefined || c.level === null ? '' : String(c.level).trim();
|
|
297
|
+
if (levelStr) {
|
|
298
|
+
logger.log(` Level: ${levelStr}`);
|
|
282
299
|
}
|
|
300
|
+
logger.log(` algorithm: ${c.algorithm ?? '—'}`);
|
|
301
|
+
logger.log(` issuer: ${c.issuer ?? '—'}`);
|
|
302
|
+
logger.log(` version: ${c.version ?? '—'}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Dataplane certification verify rows (when `--verify-cert` populated summary).
|
|
307
|
+
* @param {Object} summary
|
|
308
|
+
*/
|
|
309
|
+
function logCertificationVerifySection(summary) {
|
|
283
310
|
if (summary.certificationVerifyRows && summary.certificationVerifyRows.length > 0) {
|
|
284
311
|
logger.log('');
|
|
285
312
|
logger.log('🪪 Certification verify (dataplane)');
|
|
@@ -303,6 +330,23 @@ function logCertificationSection(summary) {
|
|
|
303
330
|
}
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Local certification + optional verify rows (external integrations).
|
|
335
|
+
* @param {Object} summary
|
|
336
|
+
*/
|
|
337
|
+
function logCertificationSection(summary) {
|
|
338
|
+
if (!summary.isExternal) return;
|
|
339
|
+
logger.log('');
|
|
340
|
+
logger.log('🪪 Certification (local system file)');
|
|
341
|
+
const c = summary.localCertification;
|
|
342
|
+
if (!c || typeof c !== 'object') {
|
|
343
|
+
logger.log(' (none or unreadable)');
|
|
344
|
+
} else {
|
|
345
|
+
logCertificationLocalFileDetails(c);
|
|
346
|
+
}
|
|
347
|
+
logCertificationVerifySection(summary);
|
|
348
|
+
}
|
|
349
|
+
|
|
306
350
|
/**
|
|
307
351
|
* Format and print human-readable show output (offline or online).
|
|
308
352
|
* @param {Object} summary - Unified summary (buildOfflineSummaryFromDeployJson or buildOnlineSummary)
|
|
@@ -38,14 +38,14 @@ function pickArtifactForCertificationMerge(artifacts) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* @param {string} algorithmUpper
|
|
42
41
|
* @param {import('../api/types/certificates.types').CertificateArtifactResponse} art
|
|
42
|
+
* @param {Object} ex
|
|
43
43
|
* @returns {string}
|
|
44
44
|
*/
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return
|
|
45
|
+
function resolvePublicKey(art, ex) {
|
|
46
|
+
const fromArt = trimOrEmpty(art.publicKey);
|
|
47
|
+
if (fromArt) return fromArt;
|
|
48
|
+
return trimOrEmpty(ex.publicKey);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -53,13 +53,34 @@ function hs256DevPublicKeyPlaceholder(algorithmUpper, art) {
|
|
|
53
53
|
* @param {Object} ex
|
|
54
54
|
* @returns {string}
|
|
55
55
|
*/
|
|
56
|
-
function
|
|
57
|
-
const fromArt = trimOrEmpty(art.
|
|
56
|
+
function resolvePublicKeyFingerprint(art, ex) {
|
|
57
|
+
const fromArt = trimOrEmpty(art.publicKeyFingerprint);
|
|
58
58
|
if (fromArt) return fromArt;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
return trimOrEmpty(ex.publicKeyFingerprint);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} raw
|
|
64
|
+
* @returns {string} Normalized digest or empty when invalid
|
|
65
|
+
*/
|
|
66
|
+
function normalizeContractHash(raw) {
|
|
67
|
+
const s = trimOrEmpty(raw);
|
|
68
|
+
return CONTRACT_HASH_PATTERN.test(s) ? s : '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {import('../api/types/certificates.types').CertificateArtifactResponse} art
|
|
73
|
+
* @param {Object} ex
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
function resolveContractHash(art, ex) {
|
|
77
|
+
return (
|
|
78
|
+
normalizeContractHash(art.contractHash) ||
|
|
79
|
+
normalizeContractHash(art.integrationHash) ||
|
|
80
|
+
normalizeContractHash(ex.contractHash) ||
|
|
81
|
+
normalizeContractHash(ex.integrationHash) ||
|
|
82
|
+
''
|
|
83
|
+
);
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
/**
|
|
@@ -92,6 +113,8 @@ function resolveVersion(art, ex) {
|
|
|
92
113
|
|
|
93
114
|
const CERTIFICATION_LEVELS = new Set(['BRONZE', 'SILVER', 'GOLD', 'PLATINUM']);
|
|
94
115
|
const CERTIFICATION_STATUSES = new Set(['passed', 'not_passed', 'pending']);
|
|
116
|
+
/** Matches external-system.schema.json `certification.contractHash` pattern. */
|
|
117
|
+
const CONTRACT_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
95
118
|
|
|
96
119
|
/**
|
|
97
120
|
* @param {string} raw
|
|
@@ -138,9 +161,10 @@ function resolveStatus(_art, ex) {
|
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
/**
|
|
141
|
-
* Build `certification` object matching **external-system.schema.json** (required: enabled, publicKey, algorithm, issuer, version; optional status, level).
|
|
164
|
+
* Build `certification` object matching **external-system.schema.json** (required: enabled, publicKey, algorithm, issuer, version; optional status, level, publicKeyFingerprint, contractHash).
|
|
142
165
|
* Fills gaps from `existingCertification` when the artifact omits publishable fields (common when dataplane redacts `publicKey`).
|
|
143
|
-
*
|
|
166
|
+
* Dataplane issues **RS256** certificates with PEM `publicKey` and optional `publicKeyFingerprint` (sha256:… of SPKI); merge output uses **RS256** only.
|
|
167
|
+
* Optional **contractHash** is copied from the certificate `contractHash` or legacy `integrationHash` when it matches `sha256:` + 64 hex.
|
|
144
168
|
*
|
|
145
169
|
* @param {import('../api/types/certificates.types').CertificateArtifactResponse|null} artifact
|
|
146
170
|
* @param {Object|null|undefined} existingCertification - Current `system.certification`
|
|
@@ -160,13 +184,13 @@ function buildCertificationFromArtifact(artifact, existingCertification) {
|
|
|
160
184
|
const versionStr = resolveVersion(art, ex);
|
|
161
185
|
if (!versionStr) return null;
|
|
162
186
|
|
|
163
|
-
const
|
|
164
|
-
const
|
|
187
|
+
const publicKeyFingerprint = resolvePublicKeyFingerprint(art, ex);
|
|
188
|
+
const contractHash = resolveContractHash(art, ex);
|
|
165
189
|
|
|
166
190
|
const out = {
|
|
167
191
|
enabled: true,
|
|
168
192
|
publicKey,
|
|
169
|
-
algorithm,
|
|
193
|
+
algorithm: 'RS256',
|
|
170
194
|
issuer,
|
|
171
195
|
version: versionStr,
|
|
172
196
|
status: resolveStatus(art, ex)
|
|
@@ -175,6 +199,12 @@ function buildCertificationFromArtifact(artifact, existingCertification) {
|
|
|
175
199
|
if (level) {
|
|
176
200
|
out.level = level;
|
|
177
201
|
}
|
|
202
|
+
if (publicKeyFingerprint) {
|
|
203
|
+
out.publicKeyFingerprint = publicKeyFingerprint;
|
|
204
|
+
}
|
|
205
|
+
if (contractHash) {
|
|
206
|
+
out.contractHash = contractHash;
|
|
207
|
+
}
|
|
178
208
|
return out;
|
|
179
209
|
}
|
|
180
210
|
|
package/lib/core/secrets.js
CHANGED
|
@@ -224,6 +224,52 @@ async function loadMergedConfigAndUserSecrets() {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* @returns {string[]}
|
|
229
|
+
*/
|
|
230
|
+
function collectBuilderSecretsYamlPaths() {
|
|
231
|
+
const projectRoot = pathsUtil.getProjectRoot();
|
|
232
|
+
const candidates = [];
|
|
233
|
+
if (projectRoot) {
|
|
234
|
+
candidates.push(path.join(projectRoot, 'builder', 'secrets.local.yaml'));
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const alt = path.join(pathsUtil.getBuilderRoot(), 'secrets.local.yaml');
|
|
238
|
+
if (!candidates.length || path.resolve(candidates[0]) !== path.resolve(alt)) {
|
|
239
|
+
candidates.push(alt);
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
/* ignore */
|
|
243
|
+
}
|
|
244
|
+
return candidates;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Merge `builder/secrets.local.yaml` from project root and from {@link pathsUtil.getBuilderRoot} when distinct.
|
|
249
|
+
* @param {Object|null|undefined} merged
|
|
250
|
+
* @returns {Object|null|undefined}
|
|
251
|
+
*/
|
|
252
|
+
function mergeBuilderSecretsLocalFiles(merged) {
|
|
253
|
+
try {
|
|
254
|
+
const seen = new Set();
|
|
255
|
+
let out = merged;
|
|
256
|
+
for (const builderPath of collectBuilderSecretsYamlPaths()) {
|
|
257
|
+
if (!builderPath || seen.has(path.resolve(builderPath))) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
seen.add(path.resolve(builderPath));
|
|
261
|
+
if (fs.existsSync(builderPath)) {
|
|
262
|
+
ensureSecureFilePermissions(builderPath);
|
|
263
|
+
const builderSecrets = mergeUserWithConfigFile(out || {}, builderPath);
|
|
264
|
+
if (builderSecrets) out = builderSecrets;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
} catch {
|
|
269
|
+
return merged;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
227
273
|
/**
|
|
228
274
|
* Loads merged secrets using config/user cascade, builder file merge, and default fallback.
|
|
229
275
|
* @async
|
|
@@ -238,19 +284,7 @@ async function loadSecretsWithFallbacks() {
|
|
|
238
284
|
}
|
|
239
285
|
merged = await applyCanonicalSecretsOverride(merged);
|
|
240
286
|
}
|
|
241
|
-
|
|
242
|
-
const projectRoot = pathsUtil.getProjectRoot();
|
|
243
|
-
if (projectRoot) {
|
|
244
|
-
const builderPath = path.join(projectRoot, 'builder', 'secrets.local.yaml');
|
|
245
|
-
if (fs.existsSync(builderPath)) {
|
|
246
|
-
ensureSecureFilePermissions(builderPath);
|
|
247
|
-
const builderSecrets = mergeUserWithConfigFile(merged || {}, builderPath);
|
|
248
|
-
if (builderSecrets) merged = builderSecrets;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch {
|
|
252
|
-
// Ignore (e.g. no project root or read error)
|
|
253
|
-
}
|
|
287
|
+
merged = mergeBuilderSecretsLocalFiles(merged);
|
|
254
288
|
if (Object.keys(merged).length === 0) {
|
|
255
289
|
merged = loadDefaultSecrets();
|
|
256
290
|
}
|
|
@@ -278,6 +278,34 @@ function formatLogContent(parsed, logType, fileName) {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Parse saved debug log JSON (strict). Always throws an Error whose message starts with
|
|
283
|
+
* "Invalid JSON" so CLI/tests get a stable contract.
|
|
284
|
+
* @param {string} content
|
|
285
|
+
* @param {string} logPath
|
|
286
|
+
* @returns {Object}
|
|
287
|
+
*/
|
|
288
|
+
function parseDatasourceLogJson(content, logPath) {
|
|
289
|
+
if (content === undefined || content === null) {
|
|
290
|
+
throw new Error(`Invalid JSON in ${logPath}: empty content`);
|
|
291
|
+
}
|
|
292
|
+
const text = String(content).replace(/^\uFEFF/, '').trim();
|
|
293
|
+
if (!text) {
|
|
294
|
+
throw new Error(`Invalid JSON in ${logPath}: empty file`);
|
|
295
|
+
}
|
|
296
|
+
let parsed;
|
|
297
|
+
try {
|
|
298
|
+
parsed = JSON.parse(text);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
301
|
+
throw new Error(`Invalid JSON in ${logPath}: ${detail}`);
|
|
302
|
+
}
|
|
303
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
304
|
+
throw new Error(`Invalid JSON in ${logPath}: expected a JSON object at the root`);
|
|
305
|
+
}
|
|
306
|
+
return parsed;
|
|
307
|
+
}
|
|
308
|
+
|
|
281
309
|
/**
|
|
282
310
|
* Run log viewer: resolve log file, read, parse, format and print
|
|
283
311
|
* @async
|
|
@@ -322,12 +350,7 @@ async function runLogViewer(datasourceKey, options) {
|
|
|
322
350
|
fileName = path.basename(logPath);
|
|
323
351
|
}
|
|
324
352
|
const content = await fsp.readFile(logPath, 'utf8');
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
parsed = JSON.parse(content);
|
|
328
|
-
} catch (err) {
|
|
329
|
-
throw new Error(`Invalid JSON in ${logPath}: ${err.message}`);
|
|
330
|
-
}
|
|
353
|
+
const parsed = parseDatasourceLogJson(content, logPath);
|
|
331
354
|
formatLogContent(parsed, logType, fileName);
|
|
332
355
|
}
|
|
333
356
|
|
|
@@ -339,5 +362,6 @@ module.exports = {
|
|
|
339
362
|
formatE2ELog,
|
|
340
363
|
formatIntegrationLog,
|
|
341
364
|
formatStructuralTestLog,
|
|
365
|
+
parseDatasourceLogJson,
|
|
342
366
|
runLogViewer
|
|
343
367
|
};
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"key":"external-system-schema",
|
|
8
8
|
"name":"External System Configuration Schema",
|
|
9
9
|
"description":"JSON schema for validating ExternalSystem configuration files",
|
|
10
|
-
"version":"1.6.
|
|
10
|
+
"version":"1.6.2",
|
|
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-
|
|
15
|
+
"updatedAt":"2026-04-23T00:00:00Z",
|
|
16
16
|
"compatibility":{
|
|
17
17
|
"minVersion":"1.4.0",
|
|
18
18
|
"maxVersion":"2.0.0",
|
|
@@ -29,6 +29,20 @@
|
|
|
29
29
|
|
|
30
30
|
],
|
|
31
31
|
"changelog":[
|
|
32
|
+
{
|
|
33
|
+
"version":"1.6.2",
|
|
34
|
+
"date":"2026-04-23T00:00:00Z",
|
|
35
|
+
"changes":[
|
|
36
|
+
"certification.algorithm: RS256 only (removed HS256 from enum)"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version":"1.6.1",
|
|
41
|
+
"date":"2026-04-22T00:00:00Z",
|
|
42
|
+
"changes":[
|
|
43
|
+
"Optional certification.publicKeyFingerprint (sha256: hex of SPKI DER) for RS256 public key verification"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
32
46
|
{
|
|
33
47
|
"version":"1.6.0",
|
|
34
48
|
"date":"2026-04-09T00:00:00Z",
|
|
@@ -679,15 +693,24 @@
|
|
|
679
693
|
"publicKey":{
|
|
680
694
|
"type":"string",
|
|
681
695
|
"minLength":1,
|
|
682
|
-
"description":"Public verification material (SPKI PEM for RS256
|
|
696
|
+
"description":"Public verification material (SPKI PEM for RS256). Private keys must never appear in config."
|
|
697
|
+
},
|
|
698
|
+
"publicKeyFingerprint":{
|
|
699
|
+
"type":"string",
|
|
700
|
+
"pattern":"^sha256:[0-9a-f]{64}$",
|
|
701
|
+
"description":"SHA-256 fingerprint of the SubjectPublicKeyInfo DER (same convention as integration certificate publicKeyFingerprint); must match embedded publicKey."
|
|
702
|
+
},
|
|
703
|
+
"contractHash":{
|
|
704
|
+
"type":"string",
|
|
705
|
+
"pattern":"^sha256:[0-9a-f]{64}$",
|
|
706
|
+
"description":"Optional copy of the active integration certificate contractHash (certification contract material digest) for drift checks; should match the dataplane active trusted certificate when present."
|
|
683
707
|
},
|
|
684
708
|
"algorithm":{
|
|
685
709
|
"type":"string",
|
|
686
710
|
"enum":[
|
|
687
|
-
"RS256"
|
|
688
|
-
"HS256"
|
|
711
|
+
"RS256"
|
|
689
712
|
],
|
|
690
|
-
"description":"Signing algorithm for certificate verification (RS256
|
|
713
|
+
"description":"Signing algorithm for integration certificate verification (RS256 only)."
|
|
691
714
|
},
|
|
692
715
|
"issuer":{
|
|
693
716
|
"type":"string",
|
|
@@ -12,8 +12,21 @@
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const fsRealSync = require('../internal/fs-real-sync');
|
|
15
|
+
const pathsUtil = require('./paths');
|
|
15
16
|
const { localHostPort } = require('./declarative-url-ports');
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} p
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function isExistingDirSync(p) {
|
|
23
|
+
try {
|
|
24
|
+
return Boolean(p && fsRealSync.existsSync(p) && fsRealSync.statSync(p).isDirectory());
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* Maps application.yaml `app.key` to env var prefix (MISO_HOST, DATAPLANE_PORT, …).
|
|
19
32
|
* Generic keys not listed are skipped (no guessed PREFIX).
|
|
@@ -110,12 +123,11 @@ function mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName) {
|
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/**
|
|
113
|
-
* @param {string}
|
|
126
|
+
* @param {string} builderDir
|
|
114
127
|
* @returns {Array<{ doc: object, folderName: string }>}
|
|
115
128
|
*/
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
|
|
129
|
+
function listBuilderApplicationDocsInDir(builderDir) {
|
|
130
|
+
if (!isExistingDirSync(builderDir)) {
|
|
119
131
|
return [];
|
|
120
132
|
}
|
|
121
133
|
const out = [];
|
|
@@ -140,6 +152,33 @@ function listBuilderApplicationDocs(projectRoot) {
|
|
|
140
152
|
return out;
|
|
141
153
|
}
|
|
142
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Prefer projectRoot/builder when present (unit tests, in-repo runs). When missing but projectRoot
|
|
157
|
+
* is the detected CLI package root (global npm install omits builder/), fall back to
|
|
158
|
+
* {@link pathsUtil.getBuilderRoot}.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectRoot
|
|
161
|
+
* @returns {string[]}
|
|
162
|
+
*/
|
|
163
|
+
function collectBuilderScanDirs(projectRoot) {
|
|
164
|
+
const legacy = path.join(projectRoot, 'builder');
|
|
165
|
+
if (isExistingDirSync(legacy)) {
|
|
166
|
+
return [legacy];
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const detected = pathsUtil.getProjectRoot();
|
|
170
|
+
if (detected && path.resolve(projectRoot) === path.resolve(detected)) {
|
|
171
|
+
const br = pathsUtil.getBuilderRoot();
|
|
172
|
+
if (isExistingDirSync(br)) {
|
|
173
|
+
return [br];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
143
182
|
/**
|
|
144
183
|
* @param {string|null|undefined} projectRoot
|
|
145
184
|
* @returns {{ docker: Record<string, unknown>, local: Record<string, unknown> }}
|
|
@@ -150,8 +189,10 @@ function buildAppServiceEnvOverlay(projectRoot) {
|
|
|
150
189
|
if (!projectRoot || !fsRealSync.existsSync(projectRoot)) {
|
|
151
190
|
return { docker: overlayDocker, local: overlayLocal };
|
|
152
191
|
}
|
|
153
|
-
for (const
|
|
154
|
-
|
|
192
|
+
for (const builderDir of collectBuilderScanDirs(projectRoot)) {
|
|
193
|
+
for (const { doc, folderName } of listBuilderApplicationDocsInDir(builderDir)) {
|
|
194
|
+
mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName);
|
|
195
|
+
}
|
|
155
196
|
}
|
|
156
197
|
return { docker: overlayDocker, local: overlayLocal };
|
|
157
198
|
}
|
|
@@ -216,6 +216,10 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
216
216
|
if (fromProject) {
|
|
217
217
|
return fromProject;
|
|
218
218
|
}
|
|
219
|
+
const fromEffective = tryDir(pathsMod.getBuilderRoot());
|
|
220
|
+
if (fromEffective) {
|
|
221
|
+
return fromEffective;
|
|
222
|
+
}
|
|
219
223
|
const work = pathsMod.getAifabrixWork();
|
|
220
224
|
return tryDir(work);
|
|
221
225
|
}
|
|
@@ -8,10 +8,50 @@ const path = require('path');
|
|
|
8
8
|
const yaml = require('js-yaml');
|
|
9
9
|
const fsRealSync = require('../internal/fs-real-sync');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} cfgPath
|
|
13
|
+
* @returns {object|null}
|
|
14
|
+
*/
|
|
15
|
+
function tryReadApplicationYamlAt(cfgPath) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fsRealSync.existsSync(cfgPath)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const raw = fsRealSync.readFileSync(cfgPath, 'utf8');
|
|
21
|
+
const doc = yaml.load(raw);
|
|
22
|
+
return doc && typeof doc === 'object' ? doc : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Prefer projectRoot/builder (fixtures, tests); then {@link pathsUtil.getBuilderPath} (global npm).
|
|
30
|
+
* @param {string} appKey
|
|
31
|
+
* @param {object} pathsUtil
|
|
32
|
+
* @returns {string[]}
|
|
33
|
+
*/
|
|
34
|
+
function collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil) {
|
|
35
|
+
const list = [];
|
|
36
|
+
const root = pathsUtil.getProjectRoot();
|
|
37
|
+
if (root) {
|
|
38
|
+
list.push(path.resolve(path.join(root, 'builder', appKey, 'application.yaml')));
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const viaBuilder = path.resolve(path.join(pathsUtil.getBuilderPath(appKey), 'application.yaml'));
|
|
42
|
+
if (!list.length || path.resolve(list[0]) !== viaBuilder) {
|
|
43
|
+
list.push(viaBuilder);
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
/* ignore getBuilderPath errors */
|
|
47
|
+
}
|
|
48
|
+
return list;
|
|
49
|
+
}
|
|
50
|
+
|
|
11
51
|
/**
|
|
12
52
|
* @param {string} appKey
|
|
13
53
|
* @param {Object} ctx
|
|
14
|
-
* @param {object} pathsUtil - paths module (getProjectRoot)
|
|
54
|
+
* @param {object} pathsUtil - paths module (getProjectRoot, getBuilderPath)
|
|
15
55
|
* @returns {object|null}
|
|
16
56
|
*/
|
|
17
57
|
function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
|
|
@@ -19,28 +59,18 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
|
|
|
19
59
|
const current = ctx.currentAppKey || '';
|
|
20
60
|
if (appKey === current && ctx.variablesPath) {
|
|
21
61
|
const vp = path.resolve(String(ctx.variablesPath));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (doc && typeof doc === 'object') {
|
|
26
|
-
return doc;
|
|
27
|
-
}
|
|
28
|
-
} catch {
|
|
29
|
-
// Fall through to builder-relative resolution
|
|
62
|
+
const fromVars = tryReadApplicationYamlAt(vp);
|
|
63
|
+
if (fromVars) {
|
|
64
|
+
return fromVars;
|
|
30
65
|
}
|
|
31
66
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const raw = fsRealSync.readFileSync(cfgPath, 'utf8');
|
|
39
|
-
const doc = yaml.load(raw);
|
|
40
|
-
return doc && typeof doc === 'object' ? doc : null;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
67
|
+
for (const cfgPath of collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil)) {
|
|
68
|
+
const doc = tryReadApplicationYamlAt(cfgPath);
|
|
69
|
+
if (doc) {
|
|
70
|
+
return doc;
|
|
71
|
+
}
|
|
43
72
|
}
|
|
73
|
+
return null;
|
|
44
74
|
} catch {
|
|
45
75
|
return null;
|
|
46
76
|
}
|
|
@@ -156,19 +156,12 @@ function mergeDocIntoRegistry(merged, doc, folderName) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
*
|
|
160
|
-
* @param {string
|
|
161
|
-
* @returns {Object} Updated registry
|
|
159
|
+
* @param {Object} merged
|
|
160
|
+
* @param {string} builderDir
|
|
162
161
|
*/
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (!root) {
|
|
167
|
-
return writeMergedRegistry(merged);
|
|
168
|
-
}
|
|
169
|
-
const builderDir = path.join(root, 'builder');
|
|
170
|
-
if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
|
|
171
|
-
return writeMergedRegistry(merged);
|
|
162
|
+
function mergeBuilderDirIntoRegistry(merged, builderDir) {
|
|
163
|
+
if (!builderDir || !fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
|
|
164
|
+
return;
|
|
172
165
|
}
|
|
173
166
|
for (const ent of fsRealSync.readdirSync(builderDir, { withFileTypes: true })) {
|
|
174
167
|
if (!ent.isDirectory()) {
|
|
@@ -180,6 +173,37 @@ function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
|
|
|
180
173
|
}
|
|
181
174
|
mergeDocIntoRegistry(merged, doc, ent.name);
|
|
182
175
|
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Merge scan results into registry (does not remove stale keys).
|
|
180
|
+
* @param {string|null} projectRoot - getProjectRoot() or null (same semantics as projectRoot || getProjectRoot())
|
|
181
|
+
* @returns {Object} Updated registry
|
|
182
|
+
*/
|
|
183
|
+
function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
|
|
184
|
+
const root = projectRoot || pathsUtil.getProjectRoot();
|
|
185
|
+
const merged = { ...readUrlsLocalRegistrySync() };
|
|
186
|
+
if (!root) {
|
|
187
|
+
return writeMergedRegistry(merged);
|
|
188
|
+
}
|
|
189
|
+
// Published npm tarball omits builder/ under the package root (.npmignore). Global installs must
|
|
190
|
+
// still refresh from the real builder tree (AIFABRIX_BUILDER_DIR or integration base + builder).
|
|
191
|
+
const legacyBuilderDir = path.join(root, 'builder');
|
|
192
|
+
const effectiveBuilderDir = pathsUtil.getBuilderRoot();
|
|
193
|
+
const builderDirs = [legacyBuilderDir];
|
|
194
|
+
try {
|
|
195
|
+
if (
|
|
196
|
+
effectiveBuilderDir &&
|
|
197
|
+
path.resolve(effectiveBuilderDir) !== path.resolve(legacyBuilderDir)
|
|
198
|
+
) {
|
|
199
|
+
builderDirs.push(effectiveBuilderDir);
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
/* ignore path resolution errors */
|
|
203
|
+
}
|
|
204
|
+
for (const builderDir of builderDirs) {
|
|
205
|
+
mergeBuilderDirIntoRegistry(merged, builderDir);
|
|
206
|
+
}
|
|
183
207
|
return writeMergedRegistry(merged);
|
|
184
208
|
}
|
|
185
209
|
|
|
@@ -18,27 +18,16 @@ const { validateFieldReferences } = require('../datasource/field-reference-valid
|
|
|
18
18
|
const { validateAbac } = require('../datasource/abac-validator');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
23
|
-
* @function setupAjvWithSchemas
|
|
24
|
-
* @returns {Promise<Object>} AJV instance and schemas
|
|
21
|
+
* Load and normalize external-system + external-datasource schema objects for manifest AJV.
|
|
22
|
+
* @returns {{ externalSystemSchema: object, externalDatasourceSchema: object }}
|
|
25
23
|
*/
|
|
26
|
-
|
|
27
|
-
const ajv = new Ajv({
|
|
28
|
-
allErrors: true,
|
|
29
|
-
strict: false,
|
|
30
|
-
removeAdditional: false
|
|
31
|
-
});
|
|
32
|
-
addFormats(ajv);
|
|
33
|
-
|
|
34
|
-
// Load raw schema objects (not compiled validators)
|
|
24
|
+
function loadManifestExternalSchemas() {
|
|
35
25
|
const externalSystemSchemaPath = path.join(__dirname, '..', 'schema', 'external-system.schema.json');
|
|
36
26
|
const externalDatasourceSchemaPath = path.join(__dirname, '..', 'schema', 'external-datasource.schema.json');
|
|
37
27
|
|
|
38
28
|
const externalSystemSchema = JSON.parse(fs.readFileSync(externalSystemSchemaPath, 'utf8'));
|
|
39
29
|
let externalDatasourceSchema = JSON.parse(fs.readFileSync(externalDatasourceSchemaPath, 'utf8'));
|
|
40
30
|
|
|
41
|
-
// Remove $schema for draft-2020-12 to avoid AJV issues
|
|
42
31
|
if (externalDatasourceSchema.$schema && externalDatasourceSchema.$schema.includes('2020-12')) {
|
|
43
32
|
const schemaCopy = { ...externalDatasourceSchema };
|
|
44
33
|
delete schemaCopy.$schema;
|
|
@@ -52,7 +41,25 @@ async function setupAjvWithSchemas() {
|
|
|
52
41
|
throw new Error('External datasource schema is missing required $id');
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
|
|
44
|
+
return { externalSystemSchema, externalDatasourceSchema };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sets up AJV validator with external schemas
|
|
49
|
+
* @async
|
|
50
|
+
* @function setupAjvWithSchemas
|
|
51
|
+
* @returns {Promise<Object>} AJV instance and schemas
|
|
52
|
+
*/
|
|
53
|
+
async function setupAjvWithSchemas() {
|
|
54
|
+
const ajv = new Ajv({
|
|
55
|
+
allErrors: true,
|
|
56
|
+
strict: false,
|
|
57
|
+
removeAdditional: false
|
|
58
|
+
});
|
|
59
|
+
addFormats(ajv);
|
|
60
|
+
|
|
61
|
+
const { externalSystemSchema, externalDatasourceSchema } = loadManifestExternalSchemas();
|
|
62
|
+
|
|
56
63
|
ajv.addSchema(require('../schema/type/document-storage.json'));
|
|
57
64
|
ajv.addSchema(require('../schema/type/message-service.json'));
|
|
58
65
|
ajv.addSchema(require('../schema/type/vector-store.json'));
|
package/package.json
CHANGED
|
@@ -178,7 +178,8 @@ OPENTELEMETRY_ENDPOINT=
|
|
|
178
178
|
# =============================================================================
|
|
179
179
|
# Read by PemRsaCertificateSigner.from_environment in app/validation/certificates/signer.py.
|
|
180
180
|
# When CERTIFICATE_PRIVATE_KEY and CERTIFICATE_PUBLIC_KEY are both set (non-empty PEM), the
|
|
181
|
-
# engine uses RS256
|
|
181
|
+
# engine uses RS256. When unset and LOCAL_MODE=true, a process-local ephemeral RSA keypair is used
|
|
182
|
+
# (still RS256, with publicKey + publicKeyFingerprint on issued certs). Non-local requires PEM keys.
|
|
182
183
|
# PEM values are often multi-line; resolve via secret store / deploy pipeline (kv://) or inject as env.
|
|
183
184
|
CERTIFICATE_PRIVATE_KEY=
|
|
184
185
|
CERTIFICATE_PUBLIC_KEY=
|