@aifabrix/builder 2.51.1 → 2.52.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 (187) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -12,8 +12,6 @@ const path = require('path');
12
12
  const crypto = require('crypto');
13
13
  const fs = require('../internal/fs-real-sync');
14
14
  const yaml = require('js-yaml');
15
- const logger = require('../utils/logger');
16
- const { formatSuccessLine, formatWarningLine, metadata } = require('../utils/cli-test-layout-chalk');
17
15
  const { listDocuments, validateDocuments, bulkDocuments } = require('../api/datasources-extended.api');
18
16
  const {
19
17
  extractDocumentStorageKeyFromAssistantDetails,
@@ -28,6 +26,15 @@ const {
28
26
  resolveDocumentStorageKeyFromRoleAssistantSettings,
29
27
  resolveRoleAssistantEnvironmentSettings
30
28
  } = require('./settings');
29
+ const {
30
+ knowledgeSearchMetadataFromFrontmatter,
31
+ splitMarkdownFrontmatter
32
+ } = require('./knowledge-markdown-frontmatter');
33
+ const {
34
+ logKnowledgeImportSummary,
35
+ logDocumentStorageSettingsPushSummary,
36
+ logKnowledgeExportSummary
37
+ } = require('./knowledge-sync-logging');
31
38
 
32
39
  function resolveSystemNode(input) {
33
40
  if (!input || typeof input !== 'object') {
@@ -76,44 +83,91 @@ async function resolveDocumentStorageKeyFromDataplane(dataplaneUrl, authConfig,
76
83
  }
77
84
 
78
85
  /**
79
- * Push role-assistant.yaml knowledge.documentStorageKey to dataplane Assistants settings.
86
+ * Map role-assistant.yaml debug.* → Assistants settings PATCH fields.
87
+ * includePromptTrace → fullDebugEnabled (LLM prompt capture gate).
88
+ * enabled → debugEnabled (runtime debug traces).
89
+ * @param {Object|null|undefined} debugSection
90
+ * @returns {{ debugEnabled?: boolean, fullDebugEnabled?: boolean }}
91
+ */
92
+ function mapRoleAssistantDebugToAssistantsSettings(debugSection) {
93
+ if (!debugSection || typeof debugSection !== 'object') {
94
+ return {};
95
+ }
96
+ const body = {};
97
+ if (typeof debugSection.enabled === 'boolean') {
98
+ body.debugEnabled = debugSection.enabled;
99
+ }
100
+ if (typeof debugSection.includePromptTrace === 'boolean') {
101
+ body.fullDebugEnabled = debugSection.includePromptTrace;
102
+ }
103
+ return body;
104
+ }
105
+
106
+ /**
107
+ * Push role-assistant.yaml knowledge + debug settings to dataplane Assistants.
80
108
  * Called on upload/deploy so the operator UI and CLI stay aligned.
81
109
  */
82
- async function pushRoleAssistantDocumentStorageSettings(params) {
110
+ function resolveAssistantSettingsPush(params) {
83
111
  const {
84
112
  systemKey,
85
- dataplaneUrl,
86
- authConfig,
87
113
  environment = 'dev',
88
- pathOptions = {},
89
- dryRun = false
114
+ pathOptions = {}
90
115
  } = params;
91
116
  const settingsEnv = resolveRoleAssistantSettingsEnvKey(environment);
92
117
  const settings = readRoleAssistantSettings(systemKey, { required: false, pathOptions });
93
118
  if (!settings) {
94
- return { skipped: true, reason: 'missing-role-assistant-settings' };
119
+ return { result: { skipped: true, reason: 'missing-role-assistant-settings' } };
95
120
  }
96
121
  const section = resolveRoleAssistantEnvironmentSettings(settings, settingsEnv, {
97
122
  required: false
98
123
  });
99
124
  if (!section) {
100
- return { skipped: true, reason: 'missing-env-settings' };
125
+ return { result: { skipped: true, reason: 'missing-env-settings' } };
101
126
  }
102
127
  const documentStorageKey =
103
128
  resolveDocumentStorageKeyFromRoleAssistantSettings(settings, settingsEnv) || '';
104
- if (dryRun) {
129
+ const debugBody = mapRoleAssistantDebugToAssistantsSettings(section.debug);
130
+ if (!documentStorageKey && Object.keys(debugBody).length === 0) {
131
+ return { result: { skipped: true, reason: 'no-document-storage-key', documentStorageKey: null } };
132
+ }
133
+ const patchBody = { ...debugBody };
134
+ if (documentStorageKey) {
135
+ patchBody.documentStorageKey = documentStorageKey;
136
+ }
137
+ return { documentStorageKey, debugBody, patchBody };
138
+ }
139
+
140
+ async function applyAssistantSettingsPush(dataplaneUrl, systemKey, authConfig, settingsPush) {
141
+ const { documentStorageKey, debugBody, patchBody } = settingsPush;
142
+ const hasDocumentStorageKey = documentStorageKey !== '';
143
+ const hasDebugSettings = Object.keys(debugBody).length > 0;
144
+ let response = await updateAssistantSettings(
145
+ dataplaneUrl,
146
+ systemKey,
147
+ authConfig,
148
+ patchBody
149
+ );
150
+ if (response && response.success === false && hasDocumentStorageKey && hasDebugSettings) {
151
+ response = await updateAssistantSettings(
152
+ dataplaneUrl,
153
+ systemKey,
154
+ authConfig,
155
+ debugBody
156
+ );
157
+ if (response && response.success === false) {
158
+ throw new Error(
159
+ response.formattedError ||
160
+ response.error ||
161
+ 'Failed to update Assistants debug settings'
162
+ );
163
+ }
105
164
  return {
106
165
  skipped: false,
107
- dryRun: true,
108
- documentStorageKey: documentStorageKey || null
166
+ documentStorageKey: null,
167
+ documentStoragePushFailed: true,
168
+ ...debugBody
109
169
  };
110
170
  }
111
- if (!dataplaneUrl || !authConfig) {
112
- return { skipped: true, reason: 'missing-dataplane-auth' };
113
- }
114
- const response = await updateAssistantSettings(dataplaneUrl, systemKey, authConfig, {
115
- documentStorageKey
116
- });
117
171
  if (response && response.success === false) {
118
172
  throw new Error(
119
173
  response.formattedError ||
@@ -123,10 +177,27 @@ async function pushRoleAssistantDocumentStorageSettings(params) {
123
177
  }
124
178
  return {
125
179
  skipped: false,
126
- documentStorageKey: documentStorageKey || null
180
+ documentStorageKey: documentStorageKey || null,
181
+ ...debugBody
127
182
  };
128
183
  }
129
184
 
185
+ async function pushRoleAssistantDocumentStorageSettings(params) {
186
+ const { systemKey, dataplaneUrl, authConfig, dryRun = false } = params;
187
+ const settingsPush = resolveAssistantSettingsPush(params);
188
+ if (settingsPush.result) {
189
+ return settingsPush.result;
190
+ }
191
+ const { documentStorageKey, debugBody } = settingsPush;
192
+ if (dryRun) {
193
+ return { skipped: false, dryRun: true, documentStorageKey: documentStorageKey || null, ...debugBody };
194
+ }
195
+ if (!dataplaneUrl || !authConfig) {
196
+ return { skipped: true, reason: 'missing-dataplane-auth' };
197
+ }
198
+ return applyAssistantSettingsPush(dataplaneUrl, systemKey, authConfig, settingsPush);
199
+ }
200
+
130
201
  /**
131
202
  * Resolve Knowledge document storage key.
132
203
  * Order: role-assistant.yaml → dataplane Assistants settings → package dataSources.
@@ -182,20 +253,27 @@ function normalizeDocumentExternalId(knowledgeRoot, filePath) {
182
253
  }
183
254
 
184
255
  function buildDocumentRecord(knowledgeRoot, filePath) {
185
- const content = fs.readFileSync(filePath, 'utf8');
256
+ const raw = fs.readFileSync(filePath, 'utf8');
257
+ const { frontmatter, body } = splitMarkdownFrontmatter(raw);
258
+ const searchMeta = knowledgeSearchMetadataFromFrontmatter(frontmatter);
186
259
  const externalId = normalizeDocumentExternalId(knowledgeRoot, filePath);
187
- const title = path.basename(externalId);
260
+ const title =
261
+ (typeof searchMeta.title === 'string' && searchMeta.title) || path.basename(externalId);
262
+ const content = typeof body === 'string' ? body : raw;
188
263
  const contentHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
189
264
  const metadata = {
190
265
  externalId,
191
- title,
192
266
  mimeType: 'text/markdown',
193
267
  sourcePath: path.relative(knowledgeRoot, filePath).replace(/\\/g, '/'),
194
- sourceType: 'role-assistant-markdown'
268
+ sourceType: 'role-assistant-markdown',
269
+ ...searchMeta,
270
+ title
195
271
  };
196
272
  return {
197
273
  key: externalId,
198
- name: title,
274
+ // Prefer sourcePath (*.md) so binary storage uses a known markdown extension
275
+ // instead of inventing ".guide" from path-like keys or titles.
276
+ name: metadata.sourcePath || `${externalId}.md`,
199
277
  contentHash,
200
278
  metadata,
201
279
  binaryBase64: Buffer.from(content, 'utf8').toString('base64'),
@@ -388,74 +466,11 @@ async function exportRoleAssistantKnowledge(params) {
388
466
  return { skipped: false, datasourceKey, count: rows.length, knowledgePath, settingsPath };
389
467
  }
390
468
 
391
- function logKnowledgeImportSummary(systemKey, result, options = {}) {
392
- if (result.skipped) {
393
- if (options.verbose === true) {
394
- logger.log(metadata(`Knowledge sync skipped (${systemKey}): ${result.reason}`));
395
- }
396
- return;
397
- }
398
- if (result.dryRun) {
399
- logger.log(
400
- formatWarningLine(
401
- `Role assistant knowledge dry-run: ${result.fileCount} file(s), ${result.recordCount} record(s), datasource ${result.datasourceKey}`
402
- )
403
- );
404
- return;
405
- }
406
- logger.log(
407
- formatSuccessLine(
408
- `Role assistant knowledge synced (${result.datasourceKey}): ${result.recordCount} record(s) from ${result.fileCount} file(s).`
409
- )
410
- );
411
- }
412
-
413
- function logDocumentStorageSettingsPushSummary(systemKey, result, options = {}) {
414
- if (result.skipped) {
415
- if (options.verbose === true) {
416
- logger.log(
417
- metadata(`Knowledge document storage settings skipped (${systemKey}): ${result.reason}`)
418
- );
419
- }
420
- return;
421
- }
422
- if (result.dryRun) {
423
- logger.log(
424
- formatWarningLine(
425
- `Knowledge document storage settings dry-run (${systemKey}): ${
426
- result.documentStorageKey || '(clear)'
427
- }`
428
- )
429
- );
430
- return;
431
- }
432
- logger.log(
433
- formatSuccessLine(
434
- `Knowledge document storage settings updated (${systemKey}): ${
435
- result.documentStorageKey || '(cleared)'
436
- }`
437
- )
438
- );
439
- }
440
-
441
- function logKnowledgeExportSummary(systemKey, result, options = {}) {
442
- if (result.skipped) {
443
- if (options.verbose === true) {
444
- logger.log(metadata(`Knowledge export skipped (${systemKey}): ${result.reason}`));
445
- }
446
- return;
447
- }
448
- logger.log(
449
- formatSuccessLine(
450
- `Role assistant knowledge exported (${result.datasourceKey}): ${result.count} document(s) to ${result.knowledgePath}.`
451
- )
452
- );
453
- }
454
-
455
469
  module.exports = {
456
470
  isDigitalWorkerModel,
457
471
  resolveRoleAssistantSettingsEnvKey,
458
472
  resolveRoleAssistantDocumentStorageKey,
473
+ mapRoleAssistantDebugToAssistantsSettings,
459
474
  pushRoleAssistantDocumentStorageSettings,
460
475
  importRoleAssistantKnowledge,
461
476
  exportRoleAssistantKnowledge,
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Evaluate expected.bid assertions for suite 002 hard GP.
3
+ * @fileoverview BID emission / apply expectation checks
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ function asArray(value) {
9
+ return Array.isArray(value) ? value : [];
10
+ }
11
+
12
+ function softWrap(assertion, soft) {
13
+ if (!assertion || assertion.passed || !soft) {
14
+ return assertion;
15
+ }
16
+ return {
17
+ ...assertion,
18
+ passed: true,
19
+ soft: true,
20
+ warn: true,
21
+ message: `${assertion.message} (soft — warning only)`
22
+ };
23
+ }
24
+
25
+ function findInteraction(interactions, type) {
26
+ return asArray(interactions).find((row) => row && row.type === type) || null;
27
+ }
28
+
29
+ function pushBidAssertion(assertions, soft, id, passed, okMsg, failMsg) {
30
+ assertions.push(
31
+ softWrap(
32
+ {
33
+ id,
34
+ passed,
35
+ message: passed ? okMsg : failMsg
36
+ },
37
+ soft
38
+ )
39
+ );
40
+ }
41
+
42
+ function hasEmitOrApply(observed) {
43
+ return Boolean(observed.emitRow || observed.applyRow || observed.emitted);
44
+ }
45
+
46
+ function collectBidSchemaAssertions(expectedBid, observed, assertions, soft) {
47
+ const { emitRow, applyRow, emitted } = observed;
48
+ if (expectedBid.schemaName) {
49
+ const actualSchema = (emitted && emitted.schemaName) || expectedBid.schemaName;
50
+ pushBidAssertion(
51
+ assertions,
52
+ soft,
53
+ 'expected.bid.schemaName',
54
+ hasEmitOrApply(observed) && actualSchema === expectedBid.schemaName,
55
+ `BID schemaName observed (${expectedBid.schemaName}).`,
56
+ `BID schemaName "${expectedBid.schemaName}" not observed in suite emit/apply.`
57
+ );
58
+ }
59
+ if (expectedBid.schemaVersion) {
60
+ const version = emitted && emitted.schemaVersion;
61
+ const versionOk =
62
+ version === null || version === undefined || version === expectedBid.schemaVersion;
63
+ pushBidAssertion(
64
+ assertions,
65
+ soft,
66
+ 'expected.bid.schemaVersion',
67
+ hasEmitOrApply({ emitRow, applyRow, emitted }) && versionOk,
68
+ `BID schemaVersion "${expectedBid.schemaVersion}" accepted.`,
69
+ `BID schemaVersion "${expectedBid.schemaVersion}" not observed.`
70
+ );
71
+ }
72
+ }
73
+
74
+ function assertDefinitionHash(expectedBid, observed, assertions, soft) {
75
+ if (expectedBid.requireDefinitionHash !== true) {
76
+ return;
77
+ }
78
+ const { emitRow, applyRow, emitted } = observed;
79
+ const hash =
80
+ (emitted && emitted.definitionHash) ||
81
+ (emitRow && emitRow.definitionHash) ||
82
+ (applyRow && applyRow.definitionHash) ||
83
+ '';
84
+ pushBidAssertion(
85
+ assertions,
86
+ soft,
87
+ 'expected.bid.requireDefinitionHash',
88
+ typeof hash === 'string' && hash.startsWith('sha256:'),
89
+ `BID definitionHash present (${String(hash).slice(0, 18)}…).`,
90
+ 'BID definitionHash (sha256:…) not observed on emit/apply.'
91
+ );
92
+ }
93
+
94
+ function assertSuiteRunId(expectedBid, observed, assertions, soft) {
95
+ if (expectedBid.requireSuiteRunId !== true) {
96
+ return;
97
+ }
98
+ const { emitRow, applyRow, emitted } = observed;
99
+ const suiteRunId =
100
+ (emitted && emitted.meta && emitted.meta.suiteRunId) ||
101
+ (emitRow && emitRow.suiteRunId) ||
102
+ (applyRow && applyRow.suiteRunId) ||
103
+ '';
104
+ pushBidAssertion(
105
+ assertions,
106
+ soft,
107
+ 'expected.bid.requireSuiteRunId',
108
+ Boolean(String(suiteRunId).trim()),
109
+ `BID suiteRunId present (${suiteRunId}).`,
110
+ 'BID meta.suiteRunId not observed on emit/apply.'
111
+ );
112
+ }
113
+
114
+ function collectBidProvenanceAssertions(expectedBid, observed, assertions, soft) {
115
+ assertDefinitionHash(expectedBid, observed, assertions, soft);
116
+ assertSuiteRunId(expectedBid, observed, assertions, soft);
117
+ }
118
+
119
+ function collectBidPathAssertions(expectedBid, observed, assertions, soft) {
120
+ if (expectedBid.forbidGoldPaths === true) {
121
+ const { emitRow, applyRow, emitted } = observed;
122
+ const blob = JSON.stringify(emitted || emitRow || applyRow || {});
123
+ const banned = /test-e2e-hubspot|knownPlatform|gold-package/i.test(blob);
124
+ pushBidAssertion(
125
+ assertions,
126
+ soft,
127
+ 'expected.bid.forbidGoldPaths',
128
+ !banned,
129
+ 'BID trail has no gold/knownPlatform generation paths.',
130
+ 'BID trail references forbidden gold/knownPlatform generation paths.'
131
+ );
132
+ }
133
+ }
134
+
135
+ function collectBidAssertions(expectedBid, observation, options = {}) {
136
+ if (!expectedBid || typeof expectedBid !== 'object') {
137
+ return [];
138
+ }
139
+ const interactions = asArray(observation.interactions || observation.workInteractions);
140
+ const observed = {
141
+ emitRow: findInteraction(interactions, 'bid-emit'),
142
+ applyRow: findInteraction(interactions, 'bid-apply'),
143
+ emitted: observation.emittedBid || null
144
+ };
145
+ const assertions = [];
146
+ const soft = options.soft === true;
147
+ collectBidSchemaAssertions(expectedBid, observed, assertions, soft);
148
+ collectBidProvenanceAssertions(expectedBid, observed, assertions, soft);
149
+ collectBidPathAssertions(expectedBid, observed, assertions, soft);
150
+ return assertions;
151
+ }
152
+
153
+ module.exports = {
154
+ collectBidAssertions
155
+ };
@@ -9,6 +9,7 @@ const {
9
9
  buildHonestyObservation,
10
10
  evaluateHonestyExpectations
11
11
  } = require('./test-cases-honesty');
12
+ const { collectBidAssertions } = require('./test-cases-bid');
12
13
 
13
14
  function getValueAtPath(obj, dottedPath) {
14
15
  const parts = String(dottedPath || '').split('.').filter(Boolean);
@@ -109,7 +110,13 @@ function buildCaseObservation(result, lifecycleVerdict, portalUrl) {
109
110
  processExecutionId: result?.processExecutionId || null,
110
111
  workResult,
111
112
  workResultError: result?.workResultError || null,
112
- honesty: buildHonestyObservation(workResult)
113
+ honesty: buildHonestyObservation(workResult),
114
+ interactions: Array.isArray(result?.interactions)
115
+ ? result.interactions
116
+ : Array.isArray(result?.workInteractions)
117
+ ? result.workInteractions
118
+ : [],
119
+ emittedBid: result?.emittedBid || null
113
120
  };
114
121
  }
115
122
 
@@ -146,6 +153,7 @@ function collectCaseAssertions(expected, observation) {
146
153
  assertions.push(...evaluateFactExpectations(expected.facts, observation));
147
154
  assertions.push(...evaluateProhibitedExpectations(expected.prohibited, observation));
148
155
  assertions.push(...evaluateHonestyExpectations(expected.honesty, observation, { soft }));
156
+ assertions.push(...collectBidAssertions(expected.bid, observation, { soft }));
149
157
  return assertions;
150
158
  }
151
159
 
@@ -68,7 +68,18 @@ function buildHonestyObservation(workResult) {
68
68
  };
69
69
  }
70
70
 
71
- function pushHonestyAssertion(assertions, id, passed, message) {
71
+ function pushHonestyAssertion(assertions, id, passed, message, options = {}) {
72
+ const soft = options.soft === true;
73
+ if (soft && !passed) {
74
+ assertions.push({
75
+ id,
76
+ passed: true,
77
+ soft: true,
78
+ warn: true,
79
+ message: `${message} (soft — warning only)`
80
+ });
81
+ return;
82
+ }
72
83
  assertions.push({ id, passed, message });
73
84
  }
74
85
 
@@ -87,7 +98,7 @@ function assertWorkResultPresent(assertions, honesty, observation) {
87
98
  );
88
99
  }
89
100
 
90
- function assertSchemaVersion(assertions, honestySpec, honesty) {
101
+ function assertSchemaVersion(assertions, honestySpec, honesty, soft) {
91
102
  if (!honestySpec.schemaVersion) {
92
103
  return;
93
104
  }
@@ -99,11 +110,12 @@ function assertSchemaVersion(assertions, honestySpec, honesty) {
99
110
  passed,
100
111
  passed
101
112
  ? `schemaVersion matched "${expected}".`
102
- : `schemaVersion expected "${expected}" but got ${JSON.stringify(honesty.schemaVersion)}.`
113
+ : `schemaVersion expected "${expected}" but got ${JSON.stringify(honesty.schemaVersion)}.`,
114
+ { soft }
103
115
  );
104
116
  }
105
117
 
106
- function assertCompletion(assertions, honestySpec, honesty) {
118
+ function assertCompletion(assertions, honestySpec, honesty, soft) {
107
119
  if (honestySpec.requireCompletion === false) {
108
120
  return;
109
121
  }
@@ -115,11 +127,12 @@ function assertCompletion(assertions, honestySpec, honesty) {
115
127
  stateOk && goalOk,
116
128
  stateOk && goalOk
117
129
  ? `completion.state/goalStatus honest (${honesty.completionState}/${honesty.goalStatus}).`
118
- : `completion missing or invalid: state=${JSON.stringify(honesty.completionState)} goalStatus=${JSON.stringify(honesty.goalStatus)}.`
130
+ : `completion missing or invalid: state=${JSON.stringify(honesty.completionState)} goalStatus=${JSON.stringify(honesty.goalStatus)}.`,
131
+ { soft }
119
132
  );
120
133
  }
121
134
 
122
- function assertContribution(assertions, honestySpec, honesty) {
135
+ function assertContribution(assertions, honestySpec, honesty, soft) {
123
136
  if (honestySpec.requireContributionObservation === false) {
124
137
  return;
125
138
  }
@@ -130,7 +143,8 @@ function assertContribution(assertions, honestySpec, honesty) {
130
143
  ok,
131
144
  ok
132
145
  ? `contributionObservation.status honest (${honesty.contributionStatus}).`
133
- : `contributionObservation.status missing or invalid: ${JSON.stringify(honesty.contributionStatus)}.`
146
+ : `contributionObservation.status missing or invalid: ${JSON.stringify(honesty.contributionStatus)}.`,
147
+ { soft }
134
148
  );
135
149
  }
136
150
 
@@ -171,9 +185,9 @@ function evaluateHonestyExpectations(honestySpec, observation, options = {}) {
171
185
  // Missing Output is always a hard failure when required — soft does not waive it.
172
186
  return assertions;
173
187
  }
174
- assertSchemaVersion(assertions, honestySpec, honesty);
175
- assertCompletion(assertions, honestySpec, honesty);
176
- assertContribution(assertions, honestySpec, honesty);
188
+ assertSchemaVersion(assertions, honestySpec, honesty, soft);
189
+ assertCompletion(assertions, honestySpec, honesty, soft);
190
+ assertContribution(assertions, honestySpec, honesty, soft);
177
191
  assertMinResourceChanges(assertions, honestySpec, honesty, soft);
178
192
  return assertions;
179
193
  }
@@ -331,6 +331,32 @@ function collectCapabilitiesFromWorkSteps(out, steps) {
331
331
  }
332
332
  }
333
333
 
334
+ /**
335
+ * Work-result artifactIndex often carries CIP fingerprints like
336
+ * `capabilityKey=customer.createBasic|operationId=createbasic` when steps[] is empty.
337
+ * @param {Set<string>} out
338
+ * @param {unknown} artifacts
339
+ */
340
+ function collectCapabilitiesFromArtifactIndex(out, artifacts) {
341
+ if (!Array.isArray(artifacts)) return;
342
+ for (const row of artifacts) {
343
+ if (!row || typeof row !== 'object') continue;
344
+ addCapabilityToken(out, row.capabilityKey);
345
+ addCapabilityToken(out, row.capability);
346
+ const fingerprint = typeof row.fingerprint === 'string' ? row.fingerprint : '';
347
+ const match = fingerprint.match(/(?:^|[|,])capabilityKey=([^|,]+)/);
348
+ if (match) {
349
+ addCapabilityToken(out, match[1]);
350
+ }
351
+ const artifactRef = typeof row.artifactRef === 'string' ? row.artifactRef : '';
352
+ // cip-log:fallback:customer.create:create:<id>
353
+ const refMatch = artifactRef.match(/^cip-log:[^:]+:([^:]+):/);
354
+ if (refMatch) {
355
+ addCapabilityToken(out, refMatch[1]);
356
+ }
357
+ }
358
+ }
359
+
334
360
  function collectObservedCapabilities(result) {
335
361
  const out = new Set();
336
362
  const steps = Array.isArray(result?.stepsRun) ? result.stepsRun : [];
@@ -341,6 +367,8 @@ function collectObservedCapabilities(result) {
341
367
  }
342
368
  collectCapabilitiesFromWorkSteps(out, result?.steps);
343
369
  collectCapabilitiesFromWorkSteps(out, result?.workResult?.steps);
370
+ collectCapabilitiesFromArtifactIndex(out, result?.workResult?.artifactIndex);
371
+ collectCapabilitiesFromArtifactIndex(out, result?.artifactIndex);
344
372
  addCapabilityToken(out, result?.task?.capabilityKey);
345
373
  addCapabilityToken(out, result?.task?.capability);
346
374
  if (result?.task?.approvalContext && typeof result.task.approvalContext === 'object') {
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Soft-loop BID apply after workspace.createsystem (suite 002 / plan 512.4).
3
+ * @fileoverview Apply input.bidPath into builder-api workspace when Runtime COM
4
+ * cannot yet invoke template.applyintegrationdefinition (RA facade not refreshed).
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('../internal/fs-real-sync');
10
+ const path = require('path');
11
+ const {
12
+ loadCaseBidDocument,
13
+ resolveTargetSystemKey
14
+ } = require('./test-runner-bid-document');
15
+
16
+ /**
17
+ * @returns {string}
18
+ */
19
+ function resolveBuilderApiBaseUrl() {
20
+ const fromEnv = String(process.env.BUILDER_API_URL || process.env.BUILDER_URL || '').trim();
21
+ if (fromEnv) {
22
+ return fromEnv.replace(/\/$/, '');
23
+ }
24
+ let developerId = 0;
25
+ try {
26
+ const yaml = require('js-yaml');
27
+ const cfgPath = path.join(
28
+ process.env.HOME || '',
29
+ '.aifabrix',
30
+ 'config.yaml'
31
+ );
32
+ if (fs.existsSync(cfgPath)) {
33
+ const cfg = yaml.load(fs.readFileSync(cfgPath, 'utf8')) || {};
34
+ developerId = Number(cfg['developer-id'] || cfg.developerId || 0) || 0;
35
+ }
36
+ } catch (_err) {
37
+ developerId = 0;
38
+ }
39
+ return `http://localhost:${3005 + developerId * 100}`;
40
+ }
41
+
42
+ /**
43
+ * @param {Object} authConfig
44
+ * @returns {string}
45
+ */
46
+ function extractBearer(authConfig) {
47
+ if (!authConfig || typeof authConfig !== 'object') {
48
+ return '';
49
+ }
50
+ if (typeof authConfig.token === 'string' && authConfig.token.trim()) {
51
+ return authConfig.token.trim();
52
+ }
53
+ if (typeof authConfig.bearerToken === 'string' && authConfig.bearerToken.trim()) {
54
+ return authConfig.bearerToken.trim();
55
+ }
56
+ const header = authConfig.headers && authConfig.headers.Authorization;
57
+ const m = /^Bearer\s+(.+)$/i.exec(String(header || ''));
58
+ return m ? m[1].trim() : '';
59
+ }
60
+
61
+ /**
62
+ * @param {Object} testCase
63
+ * @param {Object} wait
64
+ * @returns {boolean}
65
+ */
66
+ function shouldApplyBidAfterWait(testCase, wait) {
67
+ const bid = loadCaseBidDocument(testCase);
68
+ if (!bid) {
69
+ return false;
70
+ }
71
+ const capability = String(
72
+ wait.questionKey || wait.objectRef?.capabilityKey || wait.prompt || ''
73
+ ).toLowerCase();
74
+ return (
75
+ capability.includes('createsystem') ||
76
+ capability.includes('applyintegrationdefinition')
77
+ );
78
+ }
79
+
80
+ /**
81
+ * @param {Object} authConfig
82
+ * @param {Object} testCase
83
+ * @returns {Promise<Object|null>}
84
+ */
85
+ async function applyCaseBidViaBuilderApi(authConfig, testCase) {
86
+ const bid = loadCaseBidDocument(testCase);
87
+ const systemKey = resolveTargetSystemKey(testCase);
88
+ const token = extractBearer(authConfig);
89
+ if (!bid || !systemKey || !token) {
90
+ return null;
91
+ }
92
+ const url = `${resolveBuilderApiBaseUrl()}/api/v1/builder/systems/${encodeURIComponent(systemKey)}/integration-definition/apply`;
93
+ const res = await fetch(url, {
94
+ method: 'POST',
95
+ headers: {
96
+ Authorization: `Bearer ${token}`,
97
+ 'Content-Type': 'application/json'
98
+ },
99
+ body: JSON.stringify({ integrationDefinition: bid, force: true })
100
+ });
101
+ const text = await res.text();
102
+ let body = null;
103
+ try {
104
+ body = text ? JSON.parse(text) : null;
105
+ } catch (_err) {
106
+ body = { raw: text };
107
+ }
108
+ if (!res.ok) {
109
+ const err = new Error(
110
+ `BID apply failed HTTP ${res.status}: ${typeof body === 'object' ? JSON.stringify(body).slice(0, 300) : text}`
111
+ );
112
+ err.status = res.status;
113
+ throw err;
114
+ }
115
+ return body && body.data ? body.data : body;
116
+ }
117
+
118
+ module.exports = {
119
+ resolveBuilderApiBaseUrl,
120
+ shouldApplyBidAfterWait,
121
+ applyCaseBidViaBuilderApi
122
+ };