@aifabrix/builder 2.50.0 → 2.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/plans/157.1-security-quality-violations-by-type.csv +374 -0
- package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
- package/integration/hubspot-test/create-hubspot.js +1 -16
- package/lib/api/evidence-kinds.api.js +128 -0
- package/lib/api/pipeline.api.js +5 -1
- package/lib/api/resource-types.api.js +4 -4
- package/lib/api/roles.api.js +121 -0
- package/lib/api/types/evidence-kinds.types.js +42 -0
- package/lib/api/types/roles.types.js +36 -0
- package/lib/app/certification-verify-rows.js +1 -1
- package/lib/app/show.js +2 -2
- package/lib/certification/merge-certification-from-artifact.js +1 -1
- package/lib/channels/channel-artifact-generator.js +2 -1
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +22 -5
- package/lib/cli/setup-dev.js +23 -7
- package/lib/cli/setup-utility-help-after.js +12 -0
- package/lib/cli/setup-utility-validate.js +41 -2
- package/lib/commands/deploy-gate-failure-details.js +21 -0
- package/lib/commands/deploy-gate-orchestrator.js +64 -18
- package/lib/commands/dev-cli-handlers.js +6 -3
- package/lib/commands/dev-init.js +7 -109
- package/lib/commands/dev-refresh.js +197 -0
- package/lib/commands/evidence-kind.js +428 -0
- package/lib/commands/governance-command-action.js +4 -2
- package/lib/commands/repair-openapi-export.js +1 -1
- package/lib/commands/repair-openapi-sync.js +14 -6
- package/lib/commands/resource-type.js +101 -18
- package/lib/commands/role.js +452 -0
- package/lib/commands/setup.js +1 -1
- package/lib/commands/validate-json.js +230 -0
- package/lib/commands/verify-operations-step-plan.js +78 -40
- package/lib/core/audit-logger.js +1 -1
- package/lib/core/secrets-load.js +1 -3
- package/lib/datasource/capability/relate-operations.js +1 -1
- package/lib/generator/helpers.js +13 -5
- package/lib/generator/split-variables.js +9 -3
- package/lib/infrastructure/helpers-docker-check.js +6 -0
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/programmatic/auth-config.js +3 -2
- package/lib/programmatic/bearer-auth.js +1 -1
- package/lib/programmatic/enterprise-mcp-client.js +18 -2
- package/lib/programmatic/manifest-section-registry.js +18 -1
- package/lib/programmatic/openapi-result-schemas.js +28 -11
- package/lib/programmatic/openapi-schema-descriptions-core.js +5 -2
- package/lib/programmatic/openapi-schema-helpers.js +16 -1
- package/lib/programmatic/openapi-spec.js +1 -1
- package/lib/programmatic/run-repository-emcp.js +11 -3
- package/lib/programmatic/run-template-workspace.js +14 -1
- package/lib/resolvers/evidence-kind-file.js +230 -0
- package/lib/resolvers/resource-type-file.js +36 -1
- package/lib/resolvers/role-file.js +227 -0
- package/lib/schema/application-schema.json +22 -2
- package/lib/schema/external-datasource.schema.json +85 -2
- package/lib/schema/external-system.schema.json +16 -2
- package/lib/schema/infra.parameter.yaml +25 -1
- package/lib/schema/type/credential-field-registry.json +19 -3
- package/lib/utils/api.js +8 -2
- package/lib/utils/app-register-config.js +4 -3
- package/lib/utils/cli-sync-options.js +59 -3
- package/lib/utils/cli-utils.js +11 -3
- package/lib/utils/dev-ca-install.js +5 -6
- package/lib/utils/dev-config.js +2 -2
- package/lib/utils/dev-list-sort.js +145 -0
- package/lib/utils/dev-platform-tls.js +0 -63
- package/lib/utils/docker-build.js +46 -27
- package/lib/utils/docker-daemon-tls-ca.js +1 -1
- package/lib/utils/docker-exec.js +91 -6
- package/lib/utils/env-copy.js +1 -3
- package/lib/utils/error-formatters/error-parser.js +0 -3
- package/lib/utils/external-env-template.js +1 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
- package/lib/utils/health-check-request.js +2 -3
- package/lib/utils/help-builder.js +3 -0
- package/lib/utils/image-name.js +22 -4
- package/lib/utils/logger.js +37 -4
- package/lib/utils/manifest-source-emit.js +1 -1
- package/lib/utils/paths.js +32 -4
- package/lib/utils/pipeline-upload-auth-retry.js +4 -0
- package/lib/utils/postgres-platform-bootstrap.js +24 -17
- package/lib/utils/postgres-wipe.js +5 -2
- package/lib/utils/remote-dev-auth.js +39 -4
- package/lib/utils/resolve-docker-image-ref.js +36 -6
- package/lib/utils/token-manager.js +16 -0
- package/lib/utils/upload-sync-options.js +17 -3
- package/lib/validation/openapi-contract-surface-validator.js +4 -0
- package/lib/validation/validator.js +6 -5
- package/package.json +34 -12
- package/scripts/export-builder-api-evidence.js +2 -2
- package/scripts/lib/plan-actor.js +2 -2
- package/scripts/lib/plan-meta.js +1 -1
- package/scripts/lib/plan-parse.js +1 -1
- package/scripts/lib/plan-prevalidate-gates.js +12 -1
- package/scripts/lib/plan-run-report.js +13 -2
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
- package/templates/applications/dataplane/application.yaml +26 -4
- package/templates/applications/dataplane/env.template +28 -9
- package/templates/applications/dataplane/rbac.yaml +64 -2
- package/templates/applications/miso-controller/application.yaml +10 -1
- package/templates/applications/miso-controller/env.template +10 -7
- package/templates/applications/miso-controller/rbac.yaml +72 -0
- package/templates/channels/slack/integration/slack-system.json +60 -2
- package/templates/python/requirements.txt +1 -1
|
@@ -24,6 +24,17 @@ function expandConfiguredSecretsTilde(filePath) {
|
|
|
24
24
|
return filePath;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** One auto-renew attempt per CLI process (shared by concurrent getRemoteDevAuth callers). */
|
|
28
|
+
let autoRenewPromise = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Reset process-level auto-renew gate (tests only).
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
function resetDevCertAutoRenewGateForTests() {
|
|
35
|
+
autoRenewPromise = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Single API object so resolveSharedSecretsEndpoint and callers share one getRemoteDevAuth
|
|
29
40
|
* (Jest spies and partial mocks work reliably).
|
|
@@ -39,12 +50,11 @@ const remoteDevAuth = {
|
|
|
39
50
|
},
|
|
40
51
|
|
|
41
52
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Use for cert-authenticated dev API calls (settings, users, ssh-keys, secrets).
|
|
53
|
+
* Read remote server URL + client cert from disk without auto-renew.
|
|
54
|
+
* Use when renewing (avoids recursion) or when only a local presence check is needed.
|
|
45
55
|
* @returns {Promise<{ serverUrl: string, clientCertPem: string, serverCaPem: string|null }|null>}
|
|
46
56
|
*/
|
|
47
|
-
async
|
|
57
|
+
async readRemoteDevAuth() {
|
|
48
58
|
const serverUrl = await config.getRemoteServer();
|
|
49
59
|
if (!serverUrl) return null;
|
|
50
60
|
const devId = await config.getDeveloperId();
|
|
@@ -55,6 +65,30 @@ const remoteDevAuth = {
|
|
|
55
65
|
return { serverUrl, clientCertPem, serverCaPem };
|
|
56
66
|
},
|
|
57
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Get Builder Server URL, client cert PEM, and optional dev root CA when remote is configured; otherwise null.
|
|
70
|
+
* When the client cert is still valid but expires within 14 days, renews it once per process before returning.
|
|
71
|
+
* serverCaPem comes from ca.pem (saved at dev init); required for Node TLS to private-CA servers.
|
|
72
|
+
* Use for cert-authenticated dev API calls (settings, users, ssh-keys, secrets).
|
|
73
|
+
* @returns {Promise<{ serverUrl: string, clientCertPem: string, serverCaPem: string|null }|null>}
|
|
74
|
+
*/
|
|
75
|
+
async getRemoteDevAuth() {
|
|
76
|
+
const existing = await remoteDevAuth.readRemoteDevAuth();
|
|
77
|
+
if (!existing) return null;
|
|
78
|
+
if (!autoRenewPromise) {
|
|
79
|
+
autoRenewPromise = (async() => {
|
|
80
|
+
const { ensureDevCertFreshIfDue } = require('../commands/dev-refresh');
|
|
81
|
+
return ensureDevCertFreshIfDue({ quiet: false });
|
|
82
|
+
})();
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await autoRenewPromise;
|
|
86
|
+
} catch {
|
|
87
|
+
// ensureDevCertFreshIfDue already handles failures; keep using current cert
|
|
88
|
+
}
|
|
89
|
+
return remoteDevAuth.readRemoteDevAuth();
|
|
90
|
+
},
|
|
91
|
+
|
|
58
92
|
/**
|
|
59
93
|
* Resolve where shared secrets read/write should go. After dev init, config may still hold a
|
|
60
94
|
* server-side filesystem path from GET /api/dev/settings (not an http URL) if merge did not rewrite it.
|
|
@@ -148,3 +182,4 @@ function normalizeSharedSecretsFilePath(configured) {
|
|
|
148
182
|
}
|
|
149
183
|
|
|
150
184
|
module.exports = remoteDevAuth;
|
|
185
|
+
module.exports.resetDevCertAutoRenewGateForTests = resetDevCertAutoRenewGateForTests;
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const { parseImageOverride } = require('./parse-image-ref');
|
|
15
|
+
const DOCKER_REPOSITORY_RE = /^[a-z0-9][a-z0-9._/-]*$/i;
|
|
16
|
+
const DOCKER_TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} repository
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function assertSafeRepository(repository) {
|
|
23
|
+
const trimmed = String(repository || '').trim();
|
|
24
|
+
if (!trimmed || !DOCKER_REPOSITORY_RE.test(trimmed)) {
|
|
25
|
+
throw new Error(`Invalid Docker repository path: ${repository}`);
|
|
26
|
+
}
|
|
27
|
+
return trimmed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} tag
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function assertSafeTag(tag) {
|
|
35
|
+
const trimmed = String(tag || '').trim();
|
|
36
|
+
if (!trimmed || !DOCKER_TAG_RE.test(trimmed)) {
|
|
37
|
+
throw new Error(`Invalid Docker image tag: ${tag}`);
|
|
38
|
+
}
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
15
41
|
|
|
16
42
|
/**
|
|
17
43
|
* Repository path without host (same rules as compose-generator getImageName).
|
|
@@ -59,7 +85,11 @@ function normalizeDockerRegistryPrefix(registry) {
|
|
|
59
85
|
if (!t) {
|
|
60
86
|
return '';
|
|
61
87
|
}
|
|
62
|
-
|
|
88
|
+
let end = t.length;
|
|
89
|
+
while (end > 0 && t[end - 1] === '/') {
|
|
90
|
+
end -= 1;
|
|
91
|
+
}
|
|
92
|
+
return t.slice(0, end);
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
/**
|
|
@@ -81,18 +111,18 @@ function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
|
|
|
81
111
|
if (opts.image) {
|
|
82
112
|
const parsed = parseImageOverride(opts.image);
|
|
83
113
|
return {
|
|
84
|
-
imageName: parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName),
|
|
85
|
-
imageTag: parsed ? parsed.tag : tagFromOpts || imageTagFromConfig(appConfig)
|
|
114
|
+
imageName: assertSafeRepository(parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName)),
|
|
115
|
+
imageTag: assertSafeTag(parsed ? parsed.tag : tagFromOpts || imageTagFromConfig(appConfig))
|
|
86
116
|
};
|
|
87
117
|
}
|
|
88
118
|
|
|
89
|
-
const baseRepo = getRepositoryPathFromConfig(appConfig, appName);
|
|
90
|
-
const imageTag = tagFromOpts || imageTagFromConfig(appConfig);
|
|
119
|
+
const baseRepo = assertSafeRepository(getRepositoryPathFromConfig(appConfig, appName));
|
|
120
|
+
const imageTag = assertSafeTag(tagFromOpts || imageTagFromConfig(appConfig));
|
|
91
121
|
const prefix =
|
|
92
122
|
normalizeDockerRegistryPrefix(opts.registry) ||
|
|
93
123
|
normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
|
|
94
124
|
if (prefix) {
|
|
95
|
-
return { imageName: `${prefix}/${baseRepo}
|
|
125
|
+
return { imageName: assertSafeRepository(`${prefix}/${baseRepo}`), imageTag };
|
|
96
126
|
}
|
|
97
127
|
return { imageName: baseRepo, imageTag };
|
|
98
128
|
}
|
|
@@ -409,13 +409,29 @@ function applyLabDataplaneApiKeyBearer(authConfig) {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
function requireBearerForDataplanePipeline(authConfig) {
|
|
412
|
+
if (!authConfig || typeof authConfig !== 'object') {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
412
416
|
if (authConfig.type === 'client-token' && authConfig.token) {
|
|
413
417
|
return;
|
|
414
418
|
}
|
|
415
419
|
|
|
420
|
+
// Token-forward (builder-api / bearerForward): never substitute lab API_KEY for the caller JWT.
|
|
421
|
+
if (authConfig.preserveBearer && authConfig.token) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
416
425
|
const preferAppClientToken =
|
|
417
426
|
String(process.env.AIFABRIX_DEPLOYMENT_AUTH || '').toLowerCase() === 'client-credentials';
|
|
418
427
|
|
|
428
|
+
const existing = authConfig.token ? String(authConfig.token) : '';
|
|
429
|
+
const looksLikeJwt = existing.split('.').length === 3 && existing.length > 80;
|
|
430
|
+
// Enterprise path: keep a real OAuth JWT; do not overwrite with process.env.API_KEY.
|
|
431
|
+
if (looksLikeJwt) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
419
435
|
if (!preferAppClientToken && process.env.API_KEY) {
|
|
420
436
|
const apiKey = String(process.env.API_KEY);
|
|
421
437
|
if (authConfig.type === 'bearer' && authConfig.token !== apiKey) {
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {Object} [options] - CLI options from test / sync callers
|
|
13
|
-
* @returns {{ minimal: true, verbose: boolean, silentResolve: true, syncMode: true, force?: true }}
|
|
13
|
+
* @returns {{ minimal: true, verbose: boolean, silentResolve: true, syncMode: true, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
|
|
14
14
|
*/
|
|
15
15
|
function buildMinimalUploadSyncOptions(options = {}) {
|
|
16
|
+
const deployPreflight = options.uploadContext === 'deploy-preflight';
|
|
16
17
|
const out = {
|
|
17
18
|
minimal: true,
|
|
18
19
|
// CLI -v on verify-* / test-* is for run output, not preflight upload banners.
|
|
@@ -20,14 +21,27 @@ function buildMinimalUploadSyncOptions(options = {}) {
|
|
|
20
21
|
silentResolve: true,
|
|
21
22
|
syncMode: true,
|
|
22
23
|
skipDataSync: true,
|
|
23
|
-
suppressPipelineSpinner:
|
|
24
|
+
suppressPipelineSpinner:
|
|
25
|
+
options.suppressPipelineSpinner === true || deployPreflight === true
|
|
24
26
|
};
|
|
25
27
|
if (options.force === true) {
|
|
26
28
|
out.force = true;
|
|
27
29
|
}
|
|
28
|
-
if (options.suppressSummary === true) {
|
|
30
|
+
if (options.suppressSummary === true || deployPreflight === true) {
|
|
29
31
|
out.suppressSummary = true;
|
|
30
32
|
}
|
|
33
|
+
if (deployPreflight) {
|
|
34
|
+
out.uploadContext = 'deploy-preflight';
|
|
35
|
+
}
|
|
36
|
+
// Builder-api / programmatic callers: keep request Bearer — do not fall back to
|
|
37
|
+
// getDeploymentAuth against a stale mount AIFABRIX_CONFIG device token.
|
|
38
|
+
if (options.authConfig && options.dataplaneUrl) {
|
|
39
|
+
out.authConfig = options.authConfig;
|
|
40
|
+
out.dataplaneUrl = options.dataplaneUrl;
|
|
41
|
+
if (options.environment) {
|
|
42
|
+
out.environment = options.environment;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
return out;
|
|
32
46
|
}
|
|
33
47
|
|
|
@@ -307,6 +307,10 @@ function resolveCachedVendorSpec(documentKey, prefix, appPath, systemKey, vendor
|
|
|
307
307
|
function validateServiceContractVendorOpenApi(ds, prefix, appPath, systemKey, vendorCache) {
|
|
308
308
|
const errors = [];
|
|
309
309
|
const warnings = [];
|
|
310
|
+
// Python/CIP handlers without a vendor OpenAPI binding are valid serviceContracts.
|
|
311
|
+
if (!hasVendorOpenapiOnManifest(ds)) {
|
|
312
|
+
return { errors, warnings };
|
|
313
|
+
}
|
|
310
314
|
const documentKey =
|
|
311
315
|
typeof ds.openapi?.documentKey === 'string' ? ds.openapi.documentKey.trim() : '';
|
|
312
316
|
if (!documentKey) {
|
|
@@ -223,10 +223,11 @@ async function validateRbac(appName, options = {}) {
|
|
|
223
223
|
throw new Error(`Invalid syntax in ${basename}: ${error.message}`);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
];
|
|
226
|
+
const [roleErrors, permissionErrors] = await Promise.all([
|
|
227
|
+
Promise.resolve(validateRoles(rbac.roles)),
|
|
228
|
+
Promise.resolve(validatePermissions(rbac.permissions))
|
|
229
|
+
]);
|
|
230
|
+
const errors = [...roleErrors, ...permissionErrors];
|
|
230
231
|
|
|
231
232
|
return {
|
|
232
233
|
valid: errors.length === 0,
|
|
@@ -275,7 +276,7 @@ async function validateEnvTemplate(appName, options = {}) {
|
|
|
275
276
|
if (!trimmed.includes('=')) {
|
|
276
277
|
errors.push(`Line ${index + 1}: Invalid environment variable format (missing =)`);
|
|
277
278
|
} else {
|
|
278
|
-
const [key
|
|
279
|
+
const [key] = trimmed.split('=', 2);
|
|
279
280
|
// Trim key to handle whitespace issues
|
|
280
281
|
// Empty values are allowed (_value can be empty string or undefined)
|
|
281
282
|
const trimmedKey = key ? key.trim() : '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -85,36 +85,58 @@
|
|
|
85
85
|
"cache": true,
|
|
86
86
|
"temp-dir": ".nyc_output"
|
|
87
87
|
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"@babel/core": "^7.29.7",
|
|
90
|
+
"@babel/plugin-transform-modules-systemjs": "^7.29.7",
|
|
91
|
+
"fast-uri": "^4.1.0",
|
|
92
|
+
"flatted": "^3.4.2",
|
|
93
|
+
"follow-redirects": "^1.16.0",
|
|
94
|
+
"form-data": "^4.0.6",
|
|
95
|
+
"lodash": "^4.18.1",
|
|
96
|
+
"minimatch": "^10.2.5",
|
|
97
|
+
"picomatch": "^4.0.5",
|
|
98
|
+
"qs": "^6.15.3"
|
|
99
|
+
},
|
|
88
100
|
"pnpm": {
|
|
89
101
|
"overrides": {
|
|
90
|
-
"@bcoe/v8-coverage": "^1.0.2"
|
|
102
|
+
"@bcoe/v8-coverage": "^1.0.2",
|
|
103
|
+
"@babel/core": "^7.29.7",
|
|
104
|
+
"@babel/plugin-transform-modules-systemjs": "^7.29.7",
|
|
105
|
+
"fast-uri": "^4.1.0",
|
|
106
|
+
"flatted": "^3.4.2",
|
|
107
|
+
"follow-redirects": "^1.16.0",
|
|
108
|
+
"form-data": "^4.0.6",
|
|
109
|
+
"lodash": "^4.18.1",
|
|
110
|
+
"minimatch": "^10.2.5",
|
|
111
|
+
"picomatch": "^4.0.5",
|
|
112
|
+
"qs": "^6.15.3"
|
|
91
113
|
}
|
|
92
114
|
},
|
|
93
115
|
"dependencies": {
|
|
94
116
|
"@aifabrix/miso-client": "^4.19.0",
|
|
95
|
-
"ajv": "^8.
|
|
117
|
+
"ajv": "^8.20.0",
|
|
96
118
|
"ajv-formats": "^3.0.1",
|
|
97
|
-
"axios": "^1.
|
|
119
|
+
"axios": "^1.18.1",
|
|
98
120
|
"chalk": "^4.1.2",
|
|
99
121
|
"commander": "^11.1.0",
|
|
100
|
-
"handlebars": "^4.7.
|
|
122
|
+
"handlebars": "^4.7.9",
|
|
101
123
|
"inquirer": "^8.2.5",
|
|
102
124
|
"inquirer-autocomplete-prompt": "^2.0.0",
|
|
103
|
-
"js-yaml": "^4.
|
|
125
|
+
"js-yaml": "^4.3.0",
|
|
104
126
|
"ora": "^5.4.1",
|
|
105
|
-
"yaml": "^2.
|
|
127
|
+
"yaml": "^2.9.0"
|
|
106
128
|
},
|
|
107
129
|
"devDependencies": {
|
|
108
130
|
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
|
|
109
|
-
"@babel/preset-env": "^7.29.
|
|
131
|
+
"@babel/preset-env": "^7.29.7",
|
|
110
132
|
"@bcoe/v8-coverage": "^1.0.2",
|
|
111
|
-
"@nestjs/common": "^
|
|
112
|
-
"@nestjs/core": "^
|
|
113
|
-
"@nestjs/platform-express": "^
|
|
133
|
+
"@nestjs/common": "^11.1.28",
|
|
134
|
+
"@nestjs/core": "^11.1.28",
|
|
135
|
+
"@nestjs/platform-express": "^11.1.28",
|
|
114
136
|
"@types/node": "^20.10.0",
|
|
115
137
|
"cross-env": "^10.1.0",
|
|
116
138
|
"eslint": "^8.55.0",
|
|
117
|
-
"jest": "^30.2
|
|
139
|
+
"jest": "^30.4.2",
|
|
118
140
|
"markdownlint-cli": "^0.49.0",
|
|
119
141
|
"nyc": "^18.0.0",
|
|
120
142
|
"reflect-metadata": "^0.2.2",
|
|
@@ -18,8 +18,8 @@ const DEFAULT_OUT = path.join(
|
|
|
18
18
|
'..',
|
|
19
19
|
'aifabrix-dataplane',
|
|
20
20
|
'integration',
|
|
21
|
-
'
|
|
22
|
-
'
|
|
21
|
+
'builder-api',
|
|
22
|
+
'builder-api-evidence.json'
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
const EVIDENCE_PATTERNS = path.join(REPO_ROOT, 'docs', 'builder-help', 'evidence-patterns.json');
|
|
@@ -80,9 +80,9 @@ function loadActor() {
|
|
|
80
80
|
function saveActor(displayName, email) {
|
|
81
81
|
const actorPath = resolveActorPath();
|
|
82
82
|
fs.mkdirSync(path.dirname(actorPath), { recursive: true });
|
|
83
|
-
const lines = [`displayName:
|
|
83
|
+
const lines = [`displayName: ${JSON.stringify(displayName)}`];
|
|
84
84
|
if (email) {
|
|
85
|
-
lines.push(`email:
|
|
85
|
+
lines.push(`email: ${JSON.stringify(email)}`);
|
|
86
86
|
}
|
|
87
87
|
fs.writeFileSync(actorPath, `${lines.join('\n')}\n`, 'utf8');
|
|
88
88
|
return actorPath;
|
package/scripts/lib/plan-meta.js
CHANGED
|
@@ -87,7 +87,7 @@ function patchPlanMeta(planPath, key, value) {
|
|
|
87
87
|
die('Plan has no frontmatter to patch');
|
|
88
88
|
}
|
|
89
89
|
let yaml = match[1];
|
|
90
|
-
const quoted =
|
|
90
|
+
const quoted = JSON.stringify(String(value));
|
|
91
91
|
const re = new RegExp(`^${key}:.*$`, 'm');
|
|
92
92
|
if (re.test(yaml)) {
|
|
93
93
|
yaml = yaml.replace(re, `${key}: ${quoted}`);
|
|
@@ -111,7 +111,7 @@ function loadPlanFile(planPath) {
|
|
|
111
111
|
if (!match) {
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
|
-
const meta = parseSimpleYaml(match[1])
|
|
114
|
+
const meta = parseSimpleYaml(match[1]);
|
|
115
115
|
if (Array.isArray(meta.todos)) {
|
|
116
116
|
meta.todos = normalizeTodos(meta.todos);
|
|
117
117
|
}
|
|
@@ -17,6 +17,17 @@ const { loadPlanFile, hasSurface } = require('./plan-parse');
|
|
|
17
17
|
const REPO_ROOT = path.resolve(__dirname, '../..');
|
|
18
18
|
const PREVALIDATE_REPORT_DIR = path.join(REPO_ROOT, 'temp/plan-prevalidation');
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} value
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function escapeMarkdownCell(value) {
|
|
25
|
+
return String(value ?? '')
|
|
26
|
+
.replace(/\r?\n+/g, ' ')
|
|
27
|
+
.replace(/\|/g, '\\|')
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* @typedef {{ label: string, status: 'pass' | 'fail' | 'warn' | 'skip', detail?: string }} GateResult
|
|
22
33
|
*/
|
|
@@ -299,7 +310,7 @@ function buildPrevalidateReport(params) {
|
|
|
299
310
|
'| --- | --- | --- |'
|
|
300
311
|
];
|
|
301
312
|
for (const g of gates) {
|
|
302
|
-
lines.push(`| ${g.label} | ${g.status} | ${(g.detail || '')
|
|
313
|
+
lines.push(`| ${escapeMarkdownCell(g.label)} | ${escapeMarkdownCell(g.status)} | ${escapeMarkdownCell(g.detail || '')} |`);
|
|
303
314
|
}
|
|
304
315
|
return `${lines.join('\n')}\n`;
|
|
305
316
|
}
|
|
@@ -8,6 +8,17 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { REPORT_DIR } = require('./plan-config');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {unknown} value
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function escapeMarkdownCell(value) {
|
|
16
|
+
return String(value ?? '')
|
|
17
|
+
.replace(/\r?\n+/g, ' ')
|
|
18
|
+
.replace(/\|/g, '\\|')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
/**
|
|
12
23
|
* @param {object} params
|
|
13
24
|
* @returns {string}
|
|
@@ -44,13 +55,13 @@ function buildSummaryMarkdown(params) {
|
|
|
44
55
|
|
|
45
56
|
for (const g of gates) {
|
|
46
57
|
const icon = g.status === 'pass' ? 'pass' : g.status === 'warn' ? 'warn' : 'FAIL';
|
|
47
|
-
lines.push(`| ${g.label} | ${icon} | ${(g.detail || '')
|
|
58
|
+
lines.push(`| ${escapeMarkdownCell(g.label)} | ${icon} | ${escapeMarkdownCell(g.detail || '')} |`);
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
lines.push('', '## Todos', '', '| ID | Status | Content |', '| --- | --- | --- |');
|
|
51
62
|
for (const t of todos) {
|
|
52
63
|
lines.push(
|
|
53
|
-
`| ${t.id} | ${t.status} | ${
|
|
64
|
+
`| ${escapeMarkdownCell(t.id)} | ${escapeMarkdownCell(t.status)} | ${escapeMarkdownCell(t.content).slice(0, 100)} |`
|
|
54
65
|
);
|
|
55
66
|
}
|
|
56
67
|
|
|
@@ -10,8 +10,8 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../
|
|
|
10
10
|
const MIN_AIFABRIX_VERSION = '2.45.0';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @param {string} version
|
|
14
|
-
* @returns {number[]}
|
|
13
|
+
* @param {string} version - Semver string.
|
|
14
|
+
* @returns {number[]} Parsed numeric semver segments.
|
|
15
15
|
*/
|
|
16
16
|
function parseSemver(version) {
|
|
17
17
|
return String(version)
|
|
@@ -21,9 +21,9 @@ function parseSemver(version) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* @param {string} left
|
|
25
|
-
* @param {string} right
|
|
26
|
-
* @returns {number}
|
|
24
|
+
* @param {string} left - Left semver.
|
|
25
|
+
* @param {string} right - Right semver.
|
|
26
|
+
* @returns {number} Compare result (1, 0, -1).
|
|
27
27
|
*/
|
|
28
28
|
function compareSemver(left, right) {
|
|
29
29
|
const a = parseSemver(left);
|
|
@@ -38,8 +38,8 @@ function compareSemver(left, right) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* @param {string} bin
|
|
42
|
-
* @returns {string|null}
|
|
41
|
+
* @param {string} bin - Candidate executable path.
|
|
42
|
+
* @returns {string|null} CLI version output or null.
|
|
43
43
|
*/
|
|
44
44
|
function readAifabrixVersion(bin) {
|
|
45
45
|
try {
|
|
@@ -50,7 +50,7 @@ function readAifabrixVersion(bin) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* @returns {string[]}
|
|
53
|
+
* @returns {string[]} Candidate aifabrix binary paths.
|
|
54
54
|
*/
|
|
55
55
|
function collectAifabrixCandidates() {
|
|
56
56
|
const candidates = [];
|
|
@@ -85,14 +85,14 @@ function collectAifabrixCandidates() {
|
|
|
85
85
|
/**
|
|
86
86
|
* Prefer this repo's bin/aifabrix.js when developing the builder package locally.
|
|
87
87
|
*
|
|
88
|
-
* @returns {string}
|
|
88
|
+
* @returns {string} Resolved aifabrix executable path.
|
|
89
89
|
*/
|
|
90
90
|
export function resolveAifabrixBin() {
|
|
91
91
|
let bestBin = null;
|
|
92
92
|
let bestVersion = null;
|
|
93
93
|
|
|
94
94
|
for (const candidate of collectAifabrixCandidates()) {
|
|
95
|
-
let resolved
|
|
95
|
+
let resolved;
|
|
96
96
|
try {
|
|
97
97
|
if (fs.existsSync(candidate)) {
|
|
98
98
|
resolved = fs.realpathSync(candidate);
|
|
@@ -5,8 +5,7 @@ app:
|
|
|
5
5
|
description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
|
|
6
6
|
type: webapp
|
|
7
7
|
language: python # Explicitly specify Python language
|
|
8
|
-
version: 2.0.
|
|
9
|
-
|
|
8
|
+
version: 2.0.10
|
|
10
9
|
# Image Configuration
|
|
11
10
|
# Set tag to match your build (e.g. aifabrix build dataplane -t 1.0.0 then tag: 1.0.0)
|
|
12
11
|
# Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
|
|
@@ -69,7 +68,7 @@ build:
|
|
|
69
68
|
# Pulled-image local compose (no bind-mount): drives `command` in generated docker-compose. Override for non-standard images (e.g. stub ACR tags).
|
|
70
69
|
imageRun: exec python -m uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-3001}
|
|
71
70
|
# Image policy (442.0): explicit Dockerfile COPY; worker/beat via python -m app.core.*
|
|
72
|
-
# integration/ in image:
|
|
71
|
+
# integration/ in image: only onboarding-catalog.json keys (+ deploy/application per key).
|
|
73
72
|
worker: true
|
|
74
73
|
imageRunWorker: exec python -m app.core.celery_worker_main
|
|
75
74
|
imageRunBeat: exec python -m app.core.celery_beat_main
|
|
@@ -83,7 +82,9 @@ build:
|
|
|
83
82
|
# NOT included (auto-configured):
|
|
84
83
|
# - Database URLs, Redis → auto-generated during Azure install
|
|
85
84
|
# - MISO_*, KEYCLOAK_* → auto-generated during Azure install
|
|
86
|
-
# - AI params
|
|
85
|
+
# - AI params / provider secrets → LLM external systems + secret store
|
|
86
|
+
# (never product OPENAI_* / AZURE_OPENAI_*). ≥2 providers →
|
|
87
|
+
# ENTERPRISE_LLM_DEFAULT_SELECTOR (portal LLM category).
|
|
87
88
|
# - ENVIRONMENT → set by deployment target
|
|
88
89
|
|
|
89
90
|
configuration:
|
|
@@ -91,6 +92,7 @@ configuration:
|
|
|
91
92
|
# Operations & Debugging
|
|
92
93
|
# -------------------------------------------------------------------------
|
|
93
94
|
- name: LOG_LEVEL
|
|
95
|
+
category: Operations & Debugging
|
|
94
96
|
portalInput:
|
|
95
97
|
field: select
|
|
96
98
|
label: "Log Level"
|
|
@@ -101,6 +103,7 @@ configuration:
|
|
|
101
103
|
- ERROR
|
|
102
104
|
|
|
103
105
|
- name: ABAC_AUDIT_DETAIL_LEVEL
|
|
106
|
+
category: Operations & Debugging
|
|
104
107
|
portalInput:
|
|
105
108
|
field: select
|
|
106
109
|
label: "ABAC Audit Detail Level"
|
|
@@ -109,6 +112,7 @@ configuration:
|
|
|
109
112
|
- detailed
|
|
110
113
|
|
|
111
114
|
- name: RBAC_AUDIT_DETAIL_LEVEL
|
|
115
|
+
category: Operations & Debugging
|
|
112
116
|
portalInput:
|
|
113
117
|
field: select
|
|
114
118
|
label: "RBAC Audit Detail Level"
|
|
@@ -118,6 +122,7 @@ configuration:
|
|
|
118
122
|
- explain
|
|
119
123
|
|
|
120
124
|
- name: TRUST_CUSTOMER_POLICY_LEVEL
|
|
125
|
+
category: Trust
|
|
121
126
|
portalInput:
|
|
122
127
|
field: select
|
|
123
128
|
label: "Trust policy level (no-policy defaults)"
|
|
@@ -127,6 +132,7 @@ configuration:
|
|
|
127
132
|
- relaxed
|
|
128
133
|
|
|
129
134
|
- name: TRUST_PUBLISH_GATE_ENABLED
|
|
135
|
+
category: Trust
|
|
130
136
|
portalInput:
|
|
131
137
|
field: select
|
|
132
138
|
label: "Enforce trust gate on datasource publish"
|
|
@@ -135,6 +141,7 @@ configuration:
|
|
|
135
141
|
- "true"
|
|
136
142
|
|
|
137
143
|
- name: TRUST_PROMOTE_GATE_ENABLED
|
|
144
|
+
category: Trust
|
|
138
145
|
portalInput:
|
|
139
146
|
field: select
|
|
140
147
|
label: "Enforce trust gate on external system publish (promote scope)"
|
|
@@ -143,6 +150,7 @@ configuration:
|
|
|
143
150
|
- "true"
|
|
144
151
|
|
|
145
152
|
- name: TRUST_RUNTIME_GATE_ENABLED
|
|
153
|
+
category: Trust
|
|
146
154
|
portalInput:
|
|
147
155
|
field: select
|
|
148
156
|
label: "Enforce trust gate on CIP execution and workforce generation inputs (runtime scope)"
|
|
@@ -151,6 +159,7 @@ configuration:
|
|
|
151
159
|
- "true"
|
|
152
160
|
|
|
153
161
|
- name: TRUST_AI_EXPOSURE_GATE_ENABLED
|
|
162
|
+
category: Trust
|
|
154
163
|
portalInput:
|
|
155
164
|
field: select
|
|
156
165
|
label: "Enforce trust gate on AI document-storage prompt generation"
|
|
@@ -164,24 +173,28 @@ configuration:
|
|
|
164
173
|
# Tune based on customer data volumes and external API characteristics
|
|
165
174
|
|
|
166
175
|
- name: CIP_EXECUTION_MAX_RECORDS
|
|
176
|
+
category: CIP Execution
|
|
167
177
|
portalInput:
|
|
168
178
|
field: text
|
|
169
179
|
label: "Max Records per CIP Execution"
|
|
170
180
|
placeholder: "100000"
|
|
171
181
|
|
|
172
182
|
- name: CIP_EXECUTION_MAX_RESPONSE_SIZE_MB
|
|
183
|
+
category: CIP Execution
|
|
173
184
|
portalInput:
|
|
174
185
|
field: text
|
|
175
186
|
label: "Max Response Size (MB)"
|
|
176
187
|
placeholder: "100.0"
|
|
177
188
|
|
|
178
189
|
- name: CIP_EXECUTION_OPERATION_TIMEOUT
|
|
190
|
+
category: CIP Execution
|
|
179
191
|
portalInput:
|
|
180
192
|
field: text
|
|
181
193
|
label: "Operation Timeout (seconds)"
|
|
182
194
|
placeholder: "300.0"
|
|
183
195
|
|
|
184
196
|
- name: CIP_EXECUTION_HTTP_TIMEOUT
|
|
197
|
+
category: CIP Execution
|
|
185
198
|
portalInput:
|
|
186
199
|
field: text
|
|
187
200
|
label: "HTTP Request Timeout (seconds)"
|
|
@@ -193,13 +206,22 @@ configuration:
|
|
|
193
206
|
# Tune to match external API rate limits
|
|
194
207
|
|
|
195
208
|
- name: CIP_EXECUTION_RATE_LIMIT_REQUESTS_PER_SECOND
|
|
209
|
+
category: CIP Rate Limiting
|
|
196
210
|
portalInput:
|
|
197
211
|
field: text
|
|
198
212
|
label: "Rate Limit (requests/second)"
|
|
199
213
|
placeholder: "10.0"
|
|
200
214
|
|
|
201
215
|
- name: CIP_EXECUTION_RATE_LIMIT_BURST_SIZE
|
|
216
|
+
category: CIP Rate Limiting
|
|
202
217
|
portalInput:
|
|
203
218
|
field: text
|
|
204
219
|
label: "Rate Limit Burst Size"
|
|
205
220
|
placeholder: "20"
|
|
221
|
+
|
|
222
|
+
- name: ENTERPRISE_LLM_DEFAULT_SELECTOR
|
|
223
|
+
category: LLM
|
|
224
|
+
portalInput:
|
|
225
|
+
field: text
|
|
226
|
+
label: "Default LLM provider (system key when two or more providers)"
|
|
227
|
+
placeholder: "openai"
|