@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.
Files changed (162) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-deploy-job.js +108 -0
  14. package/lib/app/run-helpers.js +10 -3
  15. package/lib/app/run.js +8 -0
  16. package/lib/channels/add-channel.js +54 -23
  17. package/lib/channels/approval-guides/chatgpt.js +8 -7
  18. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  19. package/lib/channels/channel-artifact-generator.js +14 -2
  20. package/lib/channels/channel-credential.js +3 -1
  21. package/lib/channels/channel-probe-evidence.js +79 -0
  22. package/lib/channels/channel-probe.js +265 -13
  23. package/lib/channels/channel-runtime-resolver.js +120 -29
  24. package/lib/channels/channel-status-display.js +44 -0
  25. package/lib/channels/channel-targets.js +7 -0
  26. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  27. package/lib/channels/microsoft-copilot-package.js +280 -0
  28. package/lib/channels/runtime-tool-catalog.js +165 -0
  29. package/lib/cli/index.js +2 -0
  30. package/lib/cli/setup-app.js +4 -0
  31. package/lib/cli/setup-external-system-help.js +8 -2
  32. package/lib/cli/setup-external-system.js +12 -0
  33. package/lib/commands/app-test.js +2 -14
  34. package/lib/commands/datasource-load-export-cli.js +6 -1
  35. package/lib/commands/deploy-gate-failure-details.js +19 -17
  36. package/lib/commands/dev-down.js +2 -6
  37. package/lib/commands/evidence-kind.js +65 -15
  38. package/lib/commands/governance-generate-external.js +56 -21
  39. package/lib/commands/lifecycle-external.js +10 -4
  40. package/lib/commands/repair-datasource-apply.js +10 -1
  41. package/lib/commands/repair-datasource-run.js +4 -2
  42. package/lib/commands/repair-datasource-test-payload.js +80 -19
  43. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  44. package/lib/commands/repair-internal.js +9 -1
  45. package/lib/commands/repair-openapi-sync.js +34 -17
  46. package/lib/commands/role-assistant.js +313 -0
  47. package/lib/commands/upload-data-sync-finalize.js +5 -4
  48. package/lib/commands/upload-sidecar-followups.js +144 -47
  49. package/lib/commands/upload.js +43 -6
  50. package/lib/commands/verify-governance-combined-external.js +73 -9
  51. package/lib/commands/verify-governance-db.js +80 -25
  52. package/lib/commands/verify-governance-external.js +29 -5
  53. package/lib/commands/wizard-core-helpers.js +7 -3
  54. package/lib/commands/wizard-core.js +78 -9
  55. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  56. package/lib/datasource/bulk-loader-service.js +5 -1
  57. package/lib/datasource/datasource-validate-summary.js +2 -2
  58. package/lib/datasource/validate.js +13 -6
  59. package/lib/external-system/deploy-helpers.js +35 -1
  60. package/lib/external-system/deploy.js +23 -10
  61. package/lib/external-system/download-helpers.js +111 -2
  62. package/lib/external-system/download-role-assistant.js +55 -0
  63. package/lib/external-system/download.js +45 -26
  64. package/lib/external-system/test-helpers.js +1 -1
  65. package/lib/external-system/test.js +39 -3
  66. package/lib/generator/builders.js +3 -0
  67. package/lib/generator/wizard.js +51 -4
  68. package/lib/lifecycle/access-control-abac.js +121 -0
  69. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  70. package/lib/lifecycle/product-model.js +3 -2
  71. package/lib/lifecycle/report-display.js +14 -2
  72. package/lib/programmatic/capability-registry.js +20 -1
  73. package/lib/programmatic/command-options.js +1 -0
  74. package/lib/programmatic/index.js +4 -0
  75. package/lib/programmatic/manifest-patch-section.js +48 -9
  76. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  77. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  78. package/lib/programmatic/openapi-component-schemas.js +16 -1
  79. package/lib/programmatic/openapi-description-enricher.js +3 -1
  80. package/lib/programmatic/openapi-descriptions.js +55 -5
  81. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  82. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  83. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  84. package/lib/programmatic/openapi-request-schemas.js +36 -1
  85. package/lib/programmatic/openapi-result-schemas.js +1 -1
  86. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  87. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  88. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  89. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  90. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  91. package/lib/programmatic/openapi-spec.js +47 -28
  92. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  93. package/lib/programmatic/request-timeout.js +3 -0
  94. package/lib/programmatic/route-handler-map.js +89 -2
  95. package/lib/programmatic/run-certify.js +15 -2
  96. package/lib/programmatic/run-deploy.js +74 -0
  97. package/lib/programmatic/run-evidence-authoring.js +80 -0
  98. package/lib/programmatic/run-protection.js +124 -0
  99. package/lib/programmatic/run-template-workspace.js +28 -1
  100. package/lib/programmatic/workspace-context.js +6 -0
  101. package/lib/protection/auth-context.js +48 -6
  102. package/lib/protection/paths.js +22 -0
  103. package/lib/protection/run-protection-create-helpers.js +22 -1
  104. package/lib/protection/run-protection-create.js +1 -1
  105. package/lib/protection/sync-after-upload.js +1 -1
  106. package/lib/role-assistant/evidence-sync.js +450 -0
  107. package/lib/role-assistant/github-workflow.js +96 -0
  108. package/lib/role-assistant/knowledge-sync.js +465 -0
  109. package/lib/role-assistant/settings.js +126 -0
  110. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  111. package/lib/role-assistant/test-cases-honesty.js +184 -0
  112. package/lib/role-assistant/test-cases.js +364 -0
  113. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  114. package/lib/role-assistant/test-runner-workhub.js +385 -0
  115. package/lib/role-assistant/test-runner.js +500 -0
  116. package/lib/schema/application-schema.json +16 -2
  117. package/lib/schema/deployment-rules.yaml +1 -0
  118. package/lib/schema/external-datasource.schema.json +38 -4
  119. package/lib/schema/external-system.schema.json +4 -4
  120. package/lib/schema/infra.parameter.yaml +20 -0
  121. package/lib/schema/role-assistant-settings.schema.json +108 -0
  122. package/lib/utils/api.js +52 -27
  123. package/lib/utils/app-path-resolve.js +72 -24
  124. package/lib/utils/authentication-shape.js +3 -1
  125. package/lib/utils/deployment-validation.js +28 -3
  126. package/lib/utils/docker-run-ephemeral.js +55 -0
  127. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  128. package/lib/utils/env-copy-keycloak.js +196 -0
  129. package/lib/utils/env-copy.js +34 -1
  130. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  131. package/lib/utils/help-builder.js +1 -0
  132. package/lib/utils/paths.js +67 -23
  133. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  134. package/lib/utils/role-assistant-paths.js +98 -0
  135. package/lib/utils/schema-loader.js +41 -0
  136. package/lib/utils/schema-resolver.js +36 -4
  137. package/lib/utils/token-manager.js +38 -29
  138. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  139. package/lib/validation/external-manifest-validator.js +31 -8
  140. package/lib/validation/validate-external-file.js +8 -1
  141. package/package.json +2 -1
  142. package/scripts/export-builder-api-evidence.js +1 -1
  143. package/templates/applications/dataplane/application.yaml +23 -15
  144. package/templates/applications/dataplane/env.template +1 -1
  145. package/templates/applications/dataplane/rbac.yaml +14 -0
  146. package/templates/applications/miso-controller/application.yaml +1 -1
  147. package/templates/applications/miso-controller/env.template +3 -1
  148. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  149. package/templates/channels/chatgpt/integration/README.md +10 -7
  150. package/templates/channels/chatgpt/integration/application.json +2 -2
  151. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  152. package/templates/channels/chatgpt/integration/env.template +1 -1
  153. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  154. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  155. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  156. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  157. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  158. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  159. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  160. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  161. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  162. /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
- logger.log(
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
- * @async
16
- * @param {string} systemKey
15
+ * @param {string[]} targetKeys
16
+ * @param {Object} ctx
17
17
  * @param {Object} options
18
- * @returns {Promise<Object>}
18
+ * @returns {Promise<Array>}
19
19
  */
20
- async function runGovernanceGenerateForExternalSystem(systemKey, options = {}) {
21
- const subjects = buildSubjectsFromOptions(options);
22
- if (subjects.length === 0) {
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 < keys.length; index += 1) {
39
- const dsKey = keys[index];
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}/${keys.length})`);
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
- if (report.operations?.verdict !== VERDICT.VERIFIED) {
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
- if (!appType || appType.baseDir !== 'integration') {
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 integration folders only (integration/<systemKey>/).'
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 (const fileName of datasourceFiles) {
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
- if (node.properties && typeof node.properties === 'object') {
75
- const out = {};
76
- for (const [k, sub] of Object.entries(node.properties)) {
77
- out[k] = placeholderFromSchemaNode(sub);
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
- * @param {Object} parsed - Datasource config
100
- * @param {string[]} pathParts - Path under raw payload
101
- * @param {Object} [attrConfig] - Attribute config (optional type fallback)
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
- if (node) return placeholderFromSchemaNode(node);
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 = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]);
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 false;
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
- const entries = fs.readdirSync(appPath);
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 existingKeys = await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig);
143
- const uploadedThisRun = new Set();
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 documentKey = readDatasourceDocumentKey(appPath, fileName);
150
- if (!documentKey || uploadedThisRun.has(documentKey)) continue;
151
- if (!forceUpload && existingKeys.has(documentKey)) continue;
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 };