@aifabrix/builder 2.51.0 → 2.51.1
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/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-sync.js +34 -17
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/wizard.js +51 -4
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-spec.js +47 -28
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-template-workspace.js +28 -1
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +16 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/infra.parameter.yaml +20 -0
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +67 -23
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/validate-external-file.js +8 -1
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/dataplane/application.yaml +23 -15
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +14 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
|
@@ -299,6 +299,67 @@ function setupEvidenceKindCreateCommand(cmd) {
|
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* @param {object} contribution
|
|
304
|
+
* @returns {string}
|
|
305
|
+
*/
|
|
306
|
+
function formatEvidenceKindContributionLine(contribution) {
|
|
307
|
+
const parts = [
|
|
308
|
+
contribution.key || '—',
|
|
309
|
+
contribution.typeRef || null,
|
|
310
|
+
contribution.referenceValue !== null
|
|
311
|
+
&& contribution.referenceValue !== undefined
|
|
312
|
+
&& contribution.unit
|
|
313
|
+
? `${contribution.referenceValue} ${contribution.unit}`
|
|
314
|
+
: contribution.unit || null,
|
|
315
|
+
contribution.primary ? 'primary' : null,
|
|
316
|
+
contribution.attributionKey || null,
|
|
317
|
+
contribution.attributionBoundary || null
|
|
318
|
+
].filter(Boolean);
|
|
319
|
+
return ` - ${parts.join(' · ')}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @param {object} row
|
|
324
|
+
* @returns {void}
|
|
325
|
+
*/
|
|
326
|
+
function logEvidenceKindContributions(row) {
|
|
327
|
+
const contributions = row?.metadata?.contributions;
|
|
328
|
+
if (!Array.isArray(contributions) || contributions.length === 0) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
logger.log(headerKeyValue('Contributions:', `${contributions.length}`));
|
|
332
|
+
contributions.forEach((item) => {
|
|
333
|
+
logger.log(formatEvidenceKindContributionLine(item));
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @param {object} row
|
|
339
|
+
* @param {string} environment
|
|
340
|
+
* @param {string} controllerUrl
|
|
341
|
+
* @returns {void}
|
|
342
|
+
*/
|
|
343
|
+
function displayEvidenceKindDetail(row, environment, controllerUrl) {
|
|
344
|
+
logger.log(
|
|
345
|
+
chalk.bold(`\n📎 Evidence kind in ${environment} environment (${controllerUrl}):\n`)
|
|
346
|
+
);
|
|
347
|
+
logger.log(headerKeyValue('Key:', row.key || '—'));
|
|
348
|
+
logger.log(headerKeyValue('Display:', row.displayName || '—'));
|
|
349
|
+
logger.log(headerKeyValue('Domain:', row.domainKey || '—'));
|
|
350
|
+
logger.log(headerKeyValue('Purpose:', row.purpose || '—'));
|
|
351
|
+
logger.log(headerKeyValue('Severity:', row.defaultSeverity || '—'));
|
|
352
|
+
logger.log(headerKeyValue('Active:', row.isActive === false ? 'false' : 'true'));
|
|
353
|
+
if (row.source) logger.log(headerKeyValue('Source:', row.source));
|
|
354
|
+
if (row.requiresApproval !== undefined) {
|
|
355
|
+
logger.log(headerKeyValue('Requires approval:', row.requiresApproval ? 'true' : 'false'));
|
|
356
|
+
}
|
|
357
|
+
if (row.expectedUse) logger.log(headerKeyValue('Expected use:', row.expectedUse));
|
|
358
|
+
if (row.description) logger.log(headerKeyValue('Description:', row.description));
|
|
359
|
+
logEvidenceKindContributions(row);
|
|
360
|
+
logger.log('');
|
|
361
|
+
}
|
|
362
|
+
|
|
302
363
|
function setupEvidenceKindGetCommand(cmd) {
|
|
303
364
|
cmd
|
|
304
365
|
.command('get <evidenceKindIdOrKey>')
|
|
@@ -310,20 +371,7 @@ function setupEvidenceKindGetCommand(cmd) {
|
|
|
310
371
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
311
372
|
const res = await getEvidenceKind(controllerUrl, authConfig, String(evidenceKindIdOrKey).trim());
|
|
312
373
|
const row = unwrapControllerData(res);
|
|
313
|
-
|
|
314
|
-
chalk.bold(
|
|
315
|
-
`\n📎 Evidence kind in ${environment} environment (${headerControllerUrl}):\n`
|
|
316
|
-
)
|
|
317
|
-
);
|
|
318
|
-
logger.log(headerKeyValue('Key:', row.key || '—'));
|
|
319
|
-
logger.log(headerKeyValue('Display:', row.displayName || '—'));
|
|
320
|
-
logger.log(headerKeyValue('Domain:', row.domainKey || '—'));
|
|
321
|
-
logger.log(headerKeyValue('Purpose:', row.purpose || '—'));
|
|
322
|
-
logger.log(headerKeyValue('Severity:', row.defaultSeverity || '—'));
|
|
323
|
-
logger.log(headerKeyValue('Active:', row.isActive === false ? 'false' : 'true'));
|
|
324
|
-
if (row.expectedUse) logger.log(headerKeyValue('Expected use:', row.expectedUse));
|
|
325
|
-
if (row.description) logger.log(headerKeyValue('Description:', row.description));
|
|
326
|
-
logger.log('');
|
|
374
|
+
displayEvidenceKindDetail(row, environment, headerControllerUrl);
|
|
327
375
|
} catch (e) {
|
|
328
376
|
logger.error(formatBlockingError(e.message));
|
|
329
377
|
process.exit(1);
|
|
@@ -424,5 +472,7 @@ module.exports = {
|
|
|
424
472
|
unwrapControllerData,
|
|
425
473
|
importEvidenceKindRows,
|
|
426
474
|
mapEvidenceKindFileToApiPayload,
|
|
427
|
-
mapEvidenceKindApiToFileRow
|
|
475
|
+
mapEvidenceKindApiToFileRow,
|
|
476
|
+
displayEvidenceKindDetail,
|
|
477
|
+
formatEvidenceKindContributionLine
|
|
428
478
|
};
|
|
@@ -12,33 +12,21 @@ const {
|
|
|
12
12
|
const { runWithVerifyStepProgress } = require('../lifecycle/verify-step-progress');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @
|
|
16
|
-
* @param {
|
|
15
|
+
* @param {string[]} targetKeys
|
|
16
|
+
* @param {Object} ctx
|
|
17
17
|
* @param {Object} options
|
|
18
|
-
* @returns {Promise<
|
|
18
|
+
* @returns {Promise<Array>}
|
|
19
19
|
*/
|
|
20
|
-
async function
|
|
21
|
-
const subjects =
|
|
22
|
-
|
|
23
|
-
throw new Error(
|
|
24
|
-
'At least one --subject-user-id or --subject-email is required for governance generate'
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const { resolvedKey, keys, authConfig, dataplaneUrl } =
|
|
29
|
-
await resolveGovernanceIntegrationContext(systemKey, options);
|
|
30
|
-
|
|
31
|
-
const profile = (options.certificationProfile || 'bronze').trim().toLowerCase();
|
|
32
|
-
const maxSamples = Number(options.maxSamplesPerPartition) || 25;
|
|
33
|
-
|
|
34
|
-
const results = await runWithVerifyStepProgress(
|
|
20
|
+
async function generatePacksForDatasourceKeys(targetKeys, ctx, options) {
|
|
21
|
+
const { dataplaneUrl, authConfig, resolvedKey, subjects, profile, maxSamples } = ctx;
|
|
22
|
+
return runWithVerifyStepProgress(
|
|
35
23
|
'Generating governance scenario packs',
|
|
36
24
|
async progress => {
|
|
37
25
|
const rows = [];
|
|
38
|
-
for (let index = 0; index <
|
|
39
|
-
const dsKey =
|
|
26
|
+
for (let index = 0; index < targetKeys.length; index += 1) {
|
|
27
|
+
const dsKey = targetKeys[index];
|
|
40
28
|
if (typeof progress.setLabel === 'function') {
|
|
41
|
-
progress.setLabel(`Generating pack: ${dsKey} (${index + 1}/${
|
|
29
|
+
progress.setLabel(`Generating pack: ${dsKey} (${index + 1}/${targetKeys.length})`);
|
|
42
30
|
}
|
|
43
31
|
const row = await generateDatasourceGovernancePack(
|
|
44
32
|
dataplaneUrl,
|
|
@@ -57,6 +45,53 @@ async function runGovernanceGenerateForExternalSystem(systemKey, options = {}) {
|
|
|
57
45
|
},
|
|
58
46
|
options
|
|
59
47
|
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @async
|
|
52
|
+
* @param {string} systemKey
|
|
53
|
+
* @param {Object} options
|
|
54
|
+
* @returns {Promise<Object>}
|
|
55
|
+
*/
|
|
56
|
+
async function runGovernanceGenerateForExternalSystem(systemKey, options = {}) {
|
|
57
|
+
const subjects = buildSubjectsFromOptions(options);
|
|
58
|
+
if (subjects.length === 0) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'At least one --subject-user-id or --subject-email is required for governance generate'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { resolvedKey, keys, authConfig, dataplaneUrl } =
|
|
65
|
+
await resolveGovernanceIntegrationContext(systemKey, options);
|
|
66
|
+
|
|
67
|
+
const {
|
|
68
|
+
filterAbacRequiredDatasourceKeys
|
|
69
|
+
} = require('../lifecycle/access-control-abac');
|
|
70
|
+
const targetKeys = Array.isArray(options.datasourceKeys) && options.datasourceKeys.length
|
|
71
|
+
? options.datasourceKeys
|
|
72
|
+
: filterAbacRequiredDatasourceKeys(resolvedKey, keys);
|
|
73
|
+
|
|
74
|
+
if (targetKeys.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
systemKey: resolvedKey,
|
|
77
|
+
command: 'governance generate',
|
|
78
|
+
generated: 0,
|
|
79
|
+
results: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const results = await generatePacksForDatasourceKeys(
|
|
84
|
+
targetKeys,
|
|
85
|
+
{
|
|
86
|
+
dataplaneUrl,
|
|
87
|
+
authConfig,
|
|
88
|
+
resolvedKey,
|
|
89
|
+
subjects,
|
|
90
|
+
profile: (options.certificationProfile || 'bronze').trim().toLowerCase(),
|
|
91
|
+
maxSamples: Number(options.maxSamplesPerPartition) || 25
|
|
92
|
+
},
|
|
93
|
+
options
|
|
94
|
+
);
|
|
60
95
|
|
|
61
96
|
return {
|
|
62
97
|
systemKey: resolvedKey,
|
|
@@ -98,13 +98,18 @@ async function runLifecycleGovernanceCombinedIfNeeded(
|
|
|
98
98
|
*/
|
|
99
99
|
async function runMissingVerifyStepsLocally(systemKey, report, options) {
|
|
100
100
|
const runOpts = { ...options, noPublish: true };
|
|
101
|
-
|
|
101
|
+
const pathsUtil = require('../utils/paths');
|
|
102
|
+
const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
|
|
103
|
+
const isRoleAssistantPackage = appType?.baseDir === 'role-assistant';
|
|
104
|
+
// verify-operations is integration/-only; Role Assistant packages use dataplane lifecycle report.
|
|
105
|
+
if (report.operations?.verdict !== VERDICT.VERIFIED && !isRoleAssistantPackage) {
|
|
102
106
|
await runVerifyOperationsForExternalSystem(systemKey, runOpts);
|
|
103
107
|
}
|
|
104
108
|
if (report.trust?.verdict !== VERDICT.VERIFIED) {
|
|
105
109
|
await runVerifyTrustForExternalSystem(systemKey, runOpts);
|
|
106
110
|
}
|
|
107
|
-
if (report.governance?.verdict !== VERDICT.VERIFIED
|
|
111
|
+
if (report.governance?.verdict !== VERDICT.VERIFIED
|
|
112
|
+
&& report.governance?.verdict !== VERDICT.NOT_APPLICABLE) {
|
|
108
113
|
if (lifecycleHasGovernanceSubjects(options)) {
|
|
109
114
|
const { runVerifyGovernanceCombined } = require('./verify-governance-combined-external');
|
|
110
115
|
await runVerifyGovernanceCombined(
|
|
@@ -243,9 +248,10 @@ async function executeLifecycleWork(systemKey, options = {}) {
|
|
|
243
248
|
async function runLifecycleForExternalSystem(systemKey, options = {}) {
|
|
244
249
|
const pathsUtil = require('../utils/paths');
|
|
245
250
|
const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
|
|
246
|
-
|
|
251
|
+
const allowedRoots = new Set(['integration', 'role-assistant']);
|
|
252
|
+
if (!appType || !allowedRoots.has(appType.baseDir)) {
|
|
247
253
|
throw new Error(
|
|
248
|
-
'lifecycle applies to external
|
|
254
|
+
'lifecycle applies to external authoring folders only (integration/<systemKey>/ or role-assistant/<roleKey>/).'
|
|
249
255
|
);
|
|
250
256
|
}
|
|
251
257
|
|
|
@@ -84,7 +84,16 @@ function applyBaseDatasourceRepairs(parsed, none, changes, options, helpers) {
|
|
|
84
84
|
updated = helpers.repairMetadataSchemaFromAttributes(parsed, changes) || updated;
|
|
85
85
|
const warnings = Array.isArray(options.warnings) ? options.warnings : [];
|
|
86
86
|
updated = repairWebhookSection(parsed, changes, warnings) || updated;
|
|
87
|
-
updated = repairSyncBridgeSection(parsed, changes, warnings) || updated;
|
|
87
|
+
updated = repairSyncBridgeSection(parsed, changes, warnings, options) || updated;
|
|
88
|
+
if (
|
|
89
|
+
Number.isInteger(options.businessOrder) &&
|
|
90
|
+
options.businessOrder >= 1 &&
|
|
91
|
+
(parsed.businessOrder === undefined || parsed.businessOrder === null)
|
|
92
|
+
) {
|
|
93
|
+
parsed.businessOrder = options.businessOrder;
|
|
94
|
+
changes.push(`Set businessOrder ${options.businessOrder} (absent; datasource repair order)`);
|
|
95
|
+
updated = true;
|
|
96
|
+
}
|
|
88
97
|
collectWebhookSyncBridgeSystemWarnings(parsed, options.systemParsed, warnings);
|
|
89
98
|
return updated;
|
|
90
99
|
}
|
|
@@ -30,7 +30,8 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
|
|
|
30
30
|
if (!datasourceFiles || datasourceFiles.length === 0) return false;
|
|
31
31
|
let updated = false;
|
|
32
32
|
const warnings = Array.isArray(context.warnings) ? context.warnings : [];
|
|
33
|
-
for (
|
|
33
|
+
for (let i = 0; i < datasourceFiles.length; i += 1) {
|
|
34
|
+
const fileName = datasourceFiles[i];
|
|
34
35
|
const filePath = path.join(appPath, fileName);
|
|
35
36
|
if (!fs.existsSync(filePath)) continue;
|
|
36
37
|
try {
|
|
@@ -41,7 +42,8 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
|
|
|
41
42
|
test: options.test,
|
|
42
43
|
operations: options.operations,
|
|
43
44
|
systemParsed: context.systemParsed,
|
|
44
|
-
warnings
|
|
45
|
+
warnings,
|
|
46
|
+
businessOrder: i + 1
|
|
45
47
|
});
|
|
46
48
|
if (fileUpdated) {
|
|
47
49
|
updated = true;
|
|
@@ -57,30 +57,51 @@ function getSchemaNodeAtPath(rootSchema, pathParts) {
|
|
|
57
57
|
return node;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Format / pattern samples for string-like schema nodes.
|
|
62
|
+
* @param {Object} node - JSON Schema node
|
|
63
|
+
* @returns {*|undefined}
|
|
64
|
+
*/
|
|
65
|
+
function placeholderFromFormatOrPattern(node) {
|
|
66
|
+
const samples = {
|
|
67
|
+
'date-time': '2025-01-15T10:30:00Z',
|
|
68
|
+
date: '2025-01-15',
|
|
69
|
+
uuid: '00000000-0000-4000-8000-000000000001',
|
|
70
|
+
uri: 'https://example.com/objects/1',
|
|
71
|
+
'uri-reference': 'https://example.com/objects/1',
|
|
72
|
+
email: 'sample@example.com'
|
|
73
|
+
};
|
|
74
|
+
if (node.format && Object.prototype.hasOwnProperty.call(samples, node.format)) {
|
|
75
|
+
return samples[node.format];
|
|
76
|
+
}
|
|
77
|
+
if (typeof node.pattern === 'string' && /\\d/.test(node.pattern)) {
|
|
78
|
+
return '+1 555 0100';
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
/**
|
|
61
84
|
* @param {Object|null|undefined} node - JSON Schema node
|
|
62
85
|
* @returns {*}
|
|
63
86
|
*/
|
|
64
87
|
function placeholderFromSchemaNode(node) {
|
|
65
|
-
if (!node || typeof node !== 'object') return '';
|
|
88
|
+
if (!node || typeof node !== 'object') return 'sample';
|
|
89
|
+
const fromFormat = placeholderFromFormatOrPattern(node);
|
|
90
|
+
if (fromFormat !== undefined) return fromFormat;
|
|
66
91
|
const type = primarySchemaType(node.type);
|
|
67
|
-
if (node.format === 'date-time') return '2025-01-15T10:30:00Z';
|
|
68
|
-
if (node.format === 'date') return '2025-01-15';
|
|
69
|
-
if (node.format === 'uuid') return '00000000-0000-4000-8000-000000000001';
|
|
70
92
|
if (type === 'boolean') return false;
|
|
71
93
|
if (type === 'integer' || type === 'number') return 0;
|
|
72
94
|
if (type === 'array') return [];
|
|
73
95
|
if (type === 'object') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return out;
|
|
96
|
+
const props = node.properties;
|
|
97
|
+
if (!props || typeof props !== 'object') return {};
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const [k, sub] of Object.entries(props)) {
|
|
100
|
+
out[k] = placeholderFromSchemaNode(sub);
|
|
80
101
|
}
|
|
81
|
-
return
|
|
102
|
+
return out;
|
|
82
103
|
}
|
|
83
|
-
return '';
|
|
104
|
+
return 'sample';
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
/**
|
|
@@ -92,18 +113,35 @@ function placeholderForType(type) {
|
|
|
92
113
|
if (type === 'boolean') return false;
|
|
93
114
|
if (type === 'array') return [];
|
|
94
115
|
if (type === 'object') return {};
|
|
95
|
-
return '';
|
|
116
|
+
return 'sample';
|
|
96
117
|
}
|
|
97
118
|
|
|
98
119
|
/**
|
|
99
|
-
*
|
|
100
|
-
* @param {
|
|
101
|
-
* @param {Object}
|
|
102
|
-
* @returns {
|
|
120
|
+
* Prefer schema nodes that carry format/pattern over bare type-only nodes.
|
|
121
|
+
* @param {Object|null|undefined} a
|
|
122
|
+
* @param {Object|null|undefined} b
|
|
123
|
+
* @returns {Object|null}
|
|
103
124
|
*/
|
|
125
|
+
function pickRicherSchemaNode(a, b) {
|
|
126
|
+
const score = node => {
|
|
127
|
+
if (!node || typeof node !== 'object') return -1;
|
|
128
|
+
let s = 0;
|
|
129
|
+
if (node.format) s += 2;
|
|
130
|
+
if (node.pattern) s += 2;
|
|
131
|
+
if (node.type) s += 1;
|
|
132
|
+
return s;
|
|
133
|
+
};
|
|
134
|
+
if (score(a) < 0 && score(b) < 0) return null;
|
|
135
|
+
return score(a) >= score(b) ? a : b;
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
function resolvePlaceholderForPath(parsed, pathParts, attrConfig) {
|
|
105
139
|
const node = getSchemaNodeAtPath(parsed?.metadataSchema, pathParts);
|
|
106
|
-
|
|
140
|
+
// HubSpot-style: materialized root prop (phone/email/url) may be richer than nested.
|
|
141
|
+
const leaf = pathParts[pathParts.length - 1];
|
|
142
|
+
const rootNode = parsed?.metadataSchema?.properties?.[leaf];
|
|
143
|
+
const picked = pickRicherSchemaNode(node, rootNode);
|
|
144
|
+
if (picked) return placeholderFromSchemaNode(picked);
|
|
107
145
|
return placeholderForType(attrConfig?.type || 'string');
|
|
108
146
|
}
|
|
109
147
|
|
|
@@ -154,7 +192,10 @@ function enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate)
|
|
|
154
192
|
payloadTemplate.properties = placeholderFromSchemaNode(propertiesNode);
|
|
155
193
|
}
|
|
156
194
|
if (payloadTemplate.properties[leaf] === undefined || payloadTemplate.properties[leaf] === '') {
|
|
157
|
-
const leafNode =
|
|
195
|
+
const leafNode = pickRicherSchemaNode(
|
|
196
|
+
getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]),
|
|
197
|
+
parsed?.metadataSchema?.properties?.[leaf]
|
|
198
|
+
);
|
|
158
199
|
payloadTemplate.properties[leaf] = leafNode
|
|
159
200
|
? placeholderFromSchemaNode(leafNode)
|
|
160
201
|
: 'sample';
|
|
@@ -209,6 +250,24 @@ function buildPathEntriesFromAttributes(parsed, attrs) {
|
|
|
209
250
|
* @param {Array} pathEntries - From buildPathEntriesFromAttributes
|
|
210
251
|
* @returns {{ payloadTemplate: Object, expectedResult: Object }}
|
|
211
252
|
*/
|
|
253
|
+
/**
|
|
254
|
+
* Ensure metadataSchema.required root keys exist on payloadTemplate.
|
|
255
|
+
* @param {Object} parsed
|
|
256
|
+
* @param {Object} payloadTemplate
|
|
257
|
+
*/
|
|
258
|
+
function ensureRequiredRootPlaceholders(parsed, payloadTemplate) {
|
|
259
|
+
const required = parsed?.metadataSchema?.required;
|
|
260
|
+
const props = parsed?.metadataSchema?.properties;
|
|
261
|
+
if (!Array.isArray(required) || !props || typeof props !== 'object') return;
|
|
262
|
+
for (const key of required) {
|
|
263
|
+
if (!key || typeof key !== 'string') continue;
|
|
264
|
+
const existing = payloadTemplate[key];
|
|
265
|
+
if (existing !== undefined && existing !== '') continue;
|
|
266
|
+
const node = props[key];
|
|
267
|
+
payloadTemplate[key] = node ? placeholderFromSchemaNode(node) : 'sample';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
212
271
|
function buildTemplatesFromPathEntries(parsed, pathEntries) {
|
|
213
272
|
const payloadTemplate = {};
|
|
214
273
|
const expectedResult = {};
|
|
@@ -223,6 +282,7 @@ function buildTemplatesFromPathEntries(parsed, pathEntries) {
|
|
|
223
282
|
setNestedValue(payloadTemplate, parts, resolvePlaceholderForPath(parsed, parts, config));
|
|
224
283
|
}
|
|
225
284
|
enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate);
|
|
285
|
+
ensureRequiredRootPlaceholders(parsed, payloadTemplate);
|
|
226
286
|
return { payloadTemplate, expectedResult };
|
|
227
287
|
}
|
|
228
288
|
|
|
@@ -255,5 +315,6 @@ module.exports = {
|
|
|
255
315
|
placeholderFromSchemaNode,
|
|
256
316
|
placeholderForType,
|
|
257
317
|
resolvePlaceholderForPath,
|
|
318
|
+
ensureRequiredRootPlaceholders,
|
|
258
319
|
repairTestPayload
|
|
259
320
|
};
|
|
@@ -88,21 +88,59 @@ function ensureSyncBridgeExternalIdMetadata(parsed, changes) {
|
|
|
88
88
|
return true;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Default syncBridge when --sync repair runs and the field is absent.
|
|
93
|
+
* Explicit values (including disabled) are never overwritten.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} parsed
|
|
96
|
+
* @param {Object} [options]
|
|
97
|
+
* @returns {'master' | 'disabled' | null}
|
|
98
|
+
*/
|
|
99
|
+
function defaultSyncBridgeForSyncRepair(parsed, options = {}) {
|
|
100
|
+
if (options.sync !== true) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(parsed || {}, 'syncBridge')) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const entityType = String(parsed?.entityType || '').trim();
|
|
107
|
+
if (entityType === 'none') {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const resourceType = String(parsed?.resourceType || '').trim();
|
|
111
|
+
// Owner/employee catalogs typically do not own sync propagation.
|
|
112
|
+
if (resourceType === 'employee') {
|
|
113
|
+
return 'disabled';
|
|
114
|
+
}
|
|
115
|
+
if (entityType === 'recordStorage' || entityType === '') {
|
|
116
|
+
return 'master';
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
91
121
|
/**
|
|
92
122
|
* @param {object} parsed
|
|
93
123
|
* @param {string[]} changes
|
|
94
124
|
* @param {string[]} warnings
|
|
125
|
+
* @param {Object} [options]
|
|
95
126
|
* @returns {boolean}
|
|
96
127
|
*/
|
|
97
|
-
function repairSyncBridgeSection(parsed, changes, warnings) {
|
|
128
|
+
function repairSyncBridgeSection(parsed, changes, warnings, options = {}) {
|
|
129
|
+
let updated = false;
|
|
130
|
+
const seeded = defaultSyncBridgeForSyncRepair(parsed, options);
|
|
131
|
+
if (seeded) {
|
|
132
|
+
parsed.syncBridge = seeded;
|
|
133
|
+
changes.push(`Set syncBridge default ${seeded} (--sync repair; field was absent)`);
|
|
134
|
+
updated = true;
|
|
135
|
+
}
|
|
98
136
|
const mode = parsed?.syncBridge || 'disabled';
|
|
99
137
|
if (mode === 'disabled') {
|
|
100
|
-
return
|
|
138
|
+
return updated;
|
|
101
139
|
}
|
|
102
140
|
if (mode === 'master' && !parsed.resourceType) {
|
|
103
141
|
warnings.push('syncBridge=master requires resourceType for propagation grouping.');
|
|
104
142
|
}
|
|
105
|
-
return ensureSyncBridgeExternalIdMetadata(parsed, changes);
|
|
143
|
+
return ensureSyncBridgeExternalIdMetadata(parsed, changes) || updated;
|
|
106
144
|
}
|
|
107
145
|
|
|
108
146
|
/**
|
|
@@ -128,6 +166,7 @@ function collectWebhookSyncBridgeSystemWarnings(parsed, systemParsed, warnings)
|
|
|
128
166
|
module.exports = {
|
|
129
167
|
repairWebhookSection,
|
|
130
168
|
repairSyncBridgeSection,
|
|
169
|
+
defaultSyncBridgeForSyncRepair,
|
|
131
170
|
collectWebhookSyncBridgeSystemWarnings,
|
|
132
171
|
systemHasWebhookInboundAuth
|
|
133
172
|
};
|
|
@@ -28,7 +28,15 @@ function discoverIntegrationFiles(appPath) {
|
|
|
28
28
|
if (!fs.existsSync(appPath)) {
|
|
29
29
|
return { systemFiles: [], datasourceFiles: [] };
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
let entries = [];
|
|
32
|
+
try {
|
|
33
|
+
entries = fs.readdirSync(appPath);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error && error.code === 'ENOENT') {
|
|
36
|
+
return { systemFiles: [], datasourceFiles: [] };
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
32
40
|
const systemFiles = [];
|
|
33
41
|
const datasourceFiles = [];
|
|
34
42
|
for (const name of entries) {
|
|
@@ -123,6 +123,28 @@ function readDatasourceDocumentKey(appPath, datasourceFileName) {
|
|
|
123
123
|
return openapi && typeof openapi.documentKey === 'string' ? openapi.documentKey : null;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @returns {Promise<'uploaded'|'skipped'|'skip'>}
|
|
128
|
+
*/
|
|
129
|
+
async function syncOneDatasourceOpenApiFile(ctx, fileName) {
|
|
130
|
+
const documentKey = readDatasourceDocumentKey(ctx.appPath, fileName);
|
|
131
|
+
if (!documentKey || ctx.uploadedThisRun.has(documentKey)) return 'skip';
|
|
132
|
+
if (!ctx.forceUpload && ctx.existingKeys.has(documentKey)) return 'skip';
|
|
133
|
+
|
|
134
|
+
const localPath = documentKeyToLocalOpenApiPath(ctx.appPath, ctx.systemKey, documentKey);
|
|
135
|
+
if (!(await fileExistsAsync(localPath))) return 'skipped';
|
|
136
|
+
|
|
137
|
+
await uploadOneOpenApiFile(
|
|
138
|
+
ctx.dataplaneUrl,
|
|
139
|
+
ctx.authConfig,
|
|
140
|
+
ctx.systemKey,
|
|
141
|
+
localPath,
|
|
142
|
+
documentKey
|
|
143
|
+
);
|
|
144
|
+
ctx.uploadedThisRun.add(documentKey);
|
|
145
|
+
return 'uploaded';
|
|
146
|
+
}
|
|
147
|
+
|
|
126
148
|
/**
|
|
127
149
|
* @param {Object} opts
|
|
128
150
|
* @param {string} opts.appPath
|
|
@@ -139,26 +161,21 @@ async function syncOpenApiFilesForMcp(opts) {
|
|
|
139
161
|
dataplaneUrl: opts.dataplaneUrl,
|
|
140
162
|
authConfig: opts.authConfig
|
|
141
163
|
});
|
|
142
|
-
const
|
|
143
|
-
|
|
164
|
+
const ctx = {
|
|
165
|
+
appPath,
|
|
166
|
+
systemKey,
|
|
167
|
+
dataplaneUrl,
|
|
168
|
+
authConfig,
|
|
169
|
+
existingKeys: await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig),
|
|
170
|
+
uploadedThisRun: new Set(),
|
|
171
|
+
forceUpload: opts.force === true
|
|
172
|
+
};
|
|
144
173
|
let uploaded = 0;
|
|
145
174
|
let skipped = 0;
|
|
146
|
-
const forceUpload = opts.force === true;
|
|
147
|
-
|
|
148
175
|
for (const fileName of datasourceFiles || []) {
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
const localPath = documentKeyToLocalOpenApiPath(appPath, systemKey, documentKey);
|
|
154
|
-
if (!(await fileExistsAsync(localPath))) {
|
|
155
|
-
skipped += 1;
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
await uploadOneOpenApiFile(dataplaneUrl, authConfig, systemKey, localPath, documentKey);
|
|
160
|
-
uploadedThisRun.add(documentKey);
|
|
161
|
-
uploaded += 1;
|
|
176
|
+
const outcome = await syncOneDatasourceOpenApiFile(ctx, fileName);
|
|
177
|
+
if (outcome === 'uploaded') uploaded += 1;
|
|
178
|
+
if (outcome === 'skipped') skipped += 1;
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
return { uploaded, skipped };
|