@aifabrix/builder 2.53.1 → 2.54.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 (321) hide show
  1. package/bin/aifabrix.js +2 -2
  2. package/lib/api/assistants.api.js +8 -5
  3. package/lib/api/channel-identity.api.js +41 -0
  4. package/lib/api/evidence-authoring.api.js +5 -1
  5. package/lib/api/evidence.api.js +2 -0
  6. package/lib/api/types/evidence.types.js +2 -0
  7. package/lib/api/work-search.api.js +136 -0
  8. package/lib/channels/add-channel.js +163 -126
  9. package/lib/channels/approval-guides/chatgpt.js +14 -18
  10. package/lib/channels/approval-guides/claude.js +29 -0
  11. package/lib/channels/approval-guides/index.js +88 -22
  12. package/lib/channels/approval-guides/mcp-client.js +27 -0
  13. package/lib/channels/approval-guides/microsoft-copilot.js +52 -3
  14. package/lib/channels/approval-guides/slack.js +10 -10
  15. package/lib/channels/approval-guides/teams-bot.js +8 -12
  16. package/lib/channels/channel-add-continuations.js +91 -0
  17. package/lib/channels/channel-add-ensure.js +124 -27
  18. package/lib/channels/channel-add-existing.js +160 -28
  19. package/lib/channels/channel-add-failure.js +60 -0
  20. package/lib/channels/channel-add-remaining.js +486 -0
  21. package/lib/channels/channel-add-tty.js +190 -0
  22. package/lib/channels/channel-add-wizard-config.js +138 -0
  23. package/lib/channels/channel-add-wizard.js +250 -0
  24. package/lib/channels/channel-artifact-generator.js +27 -13
  25. package/lib/channels/channel-cardinality.js +83 -0
  26. package/lib/channels/channel-catalog-icon.js +45 -0
  27. package/lib/channels/channel-cli-instructions.js +75 -0
  28. package/lib/channels/channel-configuration-map.js +213 -0
  29. package/lib/channels/channel-credential.js +11 -14
  30. package/lib/channels/channel-definition-project.js +101 -0
  31. package/lib/channels/channel-definition-registry.js +154 -0
  32. package/lib/channels/channel-description.js +59 -2
  33. package/lib/channels/channel-discovery.js +16 -2
  34. package/lib/channels/channel-external-system.js +86 -9
  35. package/lib/channels/channel-install-envelope-actions.js +88 -0
  36. package/lib/channels/channel-install-envelope-microsoft.js +155 -0
  37. package/lib/channels/channel-install-envelope.js +356 -0
  38. package/lib/channels/channel-install-help-md.js +172 -0
  39. package/lib/channels/channel-install-http-payload.js +62 -0
  40. package/lib/channels/channel-install-inventory.js +115 -0
  41. package/lib/channels/channel-install-profiles.js +90 -0
  42. package/lib/channels/channel-instance-collision.js +118 -0
  43. package/lib/channels/channel-instance-key.js +100 -0
  44. package/lib/channels/channel-instance-materialize.js +240 -0
  45. package/lib/channels/channel-integration-generator.js +16 -9
  46. package/lib/channels/channel-integration-template.js +101 -22
  47. package/lib/channels/channel-inventory-fetch.js +79 -0
  48. package/lib/channels/channel-oauth-preflight.js +104 -0
  49. package/lib/channels/channel-package-version.js +76 -0
  50. package/lib/channels/channel-predefined-client.js +154 -0
  51. package/lib/channels/channel-probe.js +7 -2
  52. package/lib/channels/channel-runtime-resolver.js +180 -72
  53. package/lib/channels/channel-status-display.js +86 -16
  54. package/lib/channels/channel-targets.js +106 -92
  55. package/lib/channels/chatgpt-callback.js +318 -0
  56. package/lib/channels/chatgpt-channel-add.js +229 -0
  57. package/lib/channels/chatgpt-preflight.js +396 -0
  58. package/lib/channels/chatgpt-runtime-contract.js +17 -0
  59. package/lib/channels/chatgpt-setup-values.js +124 -0
  60. package/lib/channels/claude-callback.js +67 -0
  61. package/lib/channels/claude-channel-add.js +254 -0
  62. package/lib/channels/claude-runtime-contract.js +40 -0
  63. package/lib/channels/claude-runtime.js +58 -0
  64. package/lib/channels/claude-setup-values.js +89 -0
  65. package/lib/channels/mcp-client/config-merge.js +148 -0
  66. package/lib/channels/mcp-client/ensure-client.js +231 -0
  67. package/lib/channels/mcp-client/paths.js +53 -0
  68. package/lib/channels/mcp-client/profiles.js +122 -0
  69. package/lib/channels/mcp-client/redirect-uris.js +99 -0
  70. package/lib/channels/mcp-client/runtime.js +59 -0
  71. package/lib/channels/mcp-client-channel-add.js +186 -0
  72. package/lib/channels/microsoft-copilot-channel-add.js +117 -7
  73. package/lib/channels/microsoft-copilot-entra-identifier-uris.js +187 -0
  74. package/lib/channels/microsoft-copilot-entra-sso-form.js +106 -18
  75. package/lib/channels/microsoft-copilot-entra-sso.js +31 -2
  76. package/lib/channels/microsoft-copilot-package-prior.js +107 -0
  77. package/lib/channels/microsoft-copilot-package.js +29 -28
  78. package/lib/channels/microsoft-provisioning/a365.js +80 -0
  79. package/lib/channels/microsoft-provisioning/auth-config.js +240 -0
  80. package/lib/channels/microsoft-provisioning/auth.js +179 -0
  81. package/lib/channels/microsoft-provisioning/cli.js +71 -0
  82. package/lib/channels/microsoft-provisioning/constants.js +75 -0
  83. package/lib/channels/microsoft-provisioning/errors.js +117 -0
  84. package/lib/channels/microsoft-provisioning/index.js +55 -0
  85. package/lib/channels/microsoft-provisioning/install-atk.js +158 -0
  86. package/lib/channels/microsoft-provisioning/install-teams.js +115 -0
  87. package/lib/channels/microsoft-provisioning/login-portal-device.js +236 -0
  88. package/lib/channels/microsoft-provisioning/login-portal-parse.js +71 -0
  89. package/lib/channels/microsoft-provisioning/login-portal-teams.js +193 -0
  90. package/lib/channels/microsoft-provisioning/login-portal.js +230 -0
  91. package/lib/channels/microsoft-provisioning/persist.js +110 -0
  92. package/lib/channels/microsoft-provisioning/teams-bot-args.js +119 -0
  93. package/lib/channels/microsoft-provisioning/teams-bot-controller.js +123 -0
  94. package/lib/channels/microsoft-provisioning/teams-bot-parse.js +134 -0
  95. package/lib/channels/microsoft-provisioning/teams-bot-persist.js +113 -0
  96. package/lib/channels/microsoft-provisioning/teams-bot-provision.js +234 -0
  97. package/lib/channels/microsoft-provisioning/teams-bot-secrets.js +45 -0
  98. package/lib/channels/microsoft-provisioning/teams-cli.js +82 -0
  99. package/lib/channels/runtime-tool-catalog.js +5 -2
  100. package/lib/channels/slack-channel-add-portal.js +130 -0
  101. package/lib/channels/slack-channel-add-tty-auth.js +95 -0
  102. package/lib/channels/slack-channel-add.js +258 -0
  103. package/lib/channels/slack-provisioning/apps.js +181 -0
  104. package/lib/channels/slack-provisioning/auth-store.js +110 -0
  105. package/lib/channels/slack-provisioning/auth-ticket.js +185 -0
  106. package/lib/channels/slack-provisioning/auth.js +115 -0
  107. package/lib/channels/slack-provisioning/callbacks.js +41 -0
  108. package/lib/channels/slack-provisioning/cli.js +226 -0
  109. package/lib/channels/slack-provisioning/constants.js +37 -0
  110. package/lib/channels/slack-provisioning/credentials.js +169 -0
  111. package/lib/channels/slack-provisioning/curl-shim.js +132 -0
  112. package/lib/channels/slack-provisioning/developer-install.js +118 -0
  113. package/lib/channels/slack-provisioning/env-file.js +120 -0
  114. package/lib/channels/slack-provisioning/errors.js +47 -0
  115. package/lib/channels/slack-provisioning/go-ca-bundle.js +137 -0
  116. package/lib/channels/slack-provisioning/index.js +250 -0
  117. package/lib/channels/slack-provisioning/install-cli.js +323 -0
  118. package/lib/channels/slack-provisioning/manifest.js +89 -0
  119. package/lib/channels/slack-provisioning/oauth-lock.js +33 -0
  120. package/lib/channels/slack-provisioning/offer-cli.js +113 -0
  121. package/lib/channels/slack-provisioning/onboard-members.js +122 -0
  122. package/lib/channels/slack-provisioning/onboard.js +89 -0
  123. package/lib/channels/slack-provisioning/persist.js +95 -0
  124. package/lib/channels/slack-provisioning/remaining-urls.js +158 -0
  125. package/lib/channels/slack-provisioning/with-curl-on-path.js +48 -0
  126. package/lib/channels/teams-bot-channel-add.js +94 -0
  127. package/lib/channels/vendor-dataplane-url.js +249 -0
  128. package/lib/cli/setup-certification.help.js +2 -0
  129. package/lib/cli/setup-external-system-help.js +2 -0
  130. package/lib/cli/setup-external-system.js +1 -1
  131. package/lib/commands/channel-add-help.js +366 -0
  132. package/lib/commands/channel-add-setup.js +162 -80
  133. package/lib/commands/channel-configure.js +118 -0
  134. package/lib/commands/channel.js +10 -1
  135. package/lib/commands/governance-upload-external.js +6 -3
  136. package/lib/commands/lifecycle-command-action.js +2 -5
  137. package/lib/commands/lifecycle-external.js +93 -21
  138. package/lib/commands/repair-datasource-test-payload.js +4 -2
  139. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +23 -2
  140. package/lib/commands/repair-datasource.js +3 -0
  141. package/lib/commands/repair-external-run.js +59 -25
  142. package/lib/commands/repair-openapi-rest-runtime.js +127 -0
  143. package/lib/commands/repair-orchestration-tag-merge.js +339 -0
  144. package/lib/commands/repair-rbac-catalog-defaults.js +96 -0
  145. package/lib/commands/repair-rbac-platform-preset.js +26 -15
  146. package/lib/commands/repair-rbac-reader-roles.js +8 -21
  147. package/lib/commands/repair-rbac.js +10 -34
  148. package/lib/commands/repair-system-permissions.js +24 -22
  149. package/lib/commands/role-assistant-test-output.js +14 -0
  150. package/lib/commands/role-assistant.js +59 -21
  151. package/lib/commands/test-e2e-external.js +148 -21
  152. package/lib/commands/upload-credential-push-log.js +2 -1
  153. package/lib/commands/upload-publish-context.js +1 -1
  154. package/lib/commands/upload-role-assistant-shell.js +124 -0
  155. package/lib/commands/upload-sidecar-followups.js +6 -3
  156. package/lib/commands/upload.js +18 -3
  157. package/lib/core/secrets-docker-env.js +4 -1
  158. package/lib/core/secrets-env-content.js +17 -3
  159. package/lib/datasource/capability/capability-manifest.js +0 -3
  160. package/lib/datasource/capability/copy-test-payload.js +3 -0
  161. package/lib/external-system/deploy-helpers.js +26 -1
  162. package/lib/external-system/deploy.js +10 -6
  163. package/lib/external-system/download-helpers.js +29 -2
  164. package/lib/external-system/download-role-assistant.js +83 -4
  165. package/lib/external-system/download.js +40 -30
  166. package/lib/external-system/sync-deploy-manifest.js +3 -2
  167. package/lib/external-system/upload-identity.js +42 -0
  168. package/lib/generator/index.js +19 -15
  169. package/lib/generator/split.js +13 -2
  170. package/lib/lifecycle/role-assistant-governance.js +146 -0
  171. package/lib/lifecycle/scenario-upload.js +181 -30
  172. package/lib/programmatic/builder-api-cors.js +143 -0
  173. package/lib/programmatic/builder-api-request.js +6 -0
  174. package/lib/programmatic/builder-api-route-dispatch.js +56 -42
  175. package/lib/programmatic/builder-api-server.js +8 -1
  176. package/lib/programmatic/capability-registry.js +54 -0
  177. package/lib/programmatic/channel-install-help-resolve.js +359 -0
  178. package/lib/programmatic/channel-install-session.js +218 -0
  179. package/lib/programmatic/help-content/channel-onboarding.md +2 -0
  180. package/lib/programmatic/help-content/cip-overview.md +3 -1
  181. package/lib/programmatic/http-raw-response.js +62 -0
  182. package/lib/programmatic/openapi-channel-install-bindings.js +179 -0
  183. package/lib/programmatic/openapi-channel-install-schemas.js +286 -0
  184. package/lib/programmatic/openapi-component-schemas.js +18 -1
  185. package/lib/programmatic/openapi-description-enricher.js +3 -1
  186. package/lib/programmatic/openapi-descriptions.js +74 -3
  187. package/lib/programmatic/openapi-envelope-examples.js +117 -0
  188. package/lib/programmatic/openapi-operation-bindings.js +4 -1
  189. package/lib/programmatic/openapi-schema-descriptions-channel.js +316 -0
  190. package/lib/programmatic/openapi-spec.js +12 -8
  191. package/lib/programmatic/platform-rbac-catalog.js +36 -18
  192. package/lib/programmatic/route-handler-map.js +51 -1
  193. package/lib/programmatic/route-handlers-context.js +2 -1
  194. package/lib/programmatic/run-auth-permissions.js +54 -0
  195. package/lib/programmatic/run-channel-catalog.js +196 -0
  196. package/lib/programmatic/run-channel-install-artifacts.js +137 -0
  197. package/lib/programmatic/run-channel-install-copilot.js +353 -0
  198. package/lib/programmatic/run-channel-install-options.js +232 -0
  199. package/lib/programmatic/run-channel-install-teams-bot.js +204 -0
  200. package/lib/programmatic/run-channel-install.js +346 -0
  201. package/lib/programmatic/run-channel-package.js +87 -7
  202. package/lib/programmatic/workspace-context.js +2 -0
  203. package/lib/role-assistant/evidence-sync.js +4 -2
  204. package/lib/role-assistant/evidence-validation-issues.js +51 -0
  205. package/lib/role-assistant/knowledge-sync.js +10 -4
  206. package/lib/role-assistant/settings.js +49 -2
  207. package/lib/role-assistant/test-cases-evaluate.js +30 -2
  208. package/lib/role-assistant/test-cases-evidence.js +175 -0
  209. package/lib/role-assistant/test-cases-search.js +229 -0
  210. package/lib/role-assistant/test-cases.js +37 -4
  211. package/lib/role-assistant/test-runner-search.js +149 -0
  212. package/lib/role-assistant/test-runner-workhub-helpers.js +40 -2
  213. package/lib/role-assistant/test-runner-workhub-soft.js +9 -3
  214. package/lib/role-assistant/test-runner-workhub.js +29 -11
  215. package/lib/role-assistant/test-runner.js +16 -1
  216. package/lib/schema/external-datasource.schema.json +112 -18
  217. package/lib/schema/external-system.schema.json +159 -2
  218. package/lib/schema/mcp-client-profile.schema.json +103 -0
  219. package/lib/utils/app-path-resolve.js +4 -0
  220. package/lib/utils/authentication-env-template.js +44 -6
  221. package/lib/utils/credential-secrets-env.js +6 -1
  222. package/lib/utils/env-copy.js +19 -7
  223. package/lib/utils/external-env-template.js +20 -2
  224. package/lib/utils/paths.js +18 -1
  225. package/lib/utils/platform-shell-package.js +69 -0
  226. package/lib/utils/port-resolver.js +50 -1
  227. package/lib/utils/rewrite-name-flag.js +20 -0
  228. package/lib/utils/role-assistant-paths.js +13 -10
  229. package/lib/utils/schema-resolver.js +11 -1
  230. package/lib/utils/secrets-helpers.js +12 -21
  231. package/lib/utils/token-manager.js +10 -5
  232. package/lib/validation/validate.js +40 -0
  233. package/package.json +1 -1
  234. package/templates/README.md +1 -1
  235. package/templates/applications/builder-api/Dockerfile +1 -1
  236. package/templates/applications/builder-api/env.template +7 -0
  237. package/templates/applications/dataplane/application.yaml +6 -5
  238. package/templates/applications/dataplane/env.template +2 -2
  239. package/templates/applications/dataplane/rbac.yaml +7 -2
  240. package/templates/applications/keycloak/Dockerfile +30 -4
  241. package/templates/applications/keycloak/application.yaml +2 -2
  242. package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +896 -0
  243. package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +19 -0
  244. package/templates/applications/keycloak/env.template +3 -0
  245. package/templates/applications/miso-controller/application.yaml +1 -2
  246. package/templates/applications/miso-controller/env.template +2 -0
  247. package/templates/channels/README.md +5 -5
  248. package/templates/channels/chatgpt/TROUBLESHOOTING.md +51 -0
  249. package/templates/channels/chatgpt/channel.json +59 -0
  250. package/templates/channels/chatgpt/icon.svg +1 -0
  251. package/templates/channels/chatgpt/install.template.md.hbs +20 -95
  252. package/templates/channels/chatgpt/integration/README.md +1 -1
  253. package/templates/channels/chatgpt/integration/application.json +2 -2
  254. package/templates/channels/chatgpt/integration/chatgpt-system.json +49 -4
  255. package/templates/channels/chatgpt/integration/env.template +2 -4
  256. package/templates/channels/chatgpt/integration/rbac.json +13 -0
  257. package/templates/channels/claude/TROUBLESHOOTING.md +41 -0
  258. package/templates/channels/claude/channel.json +59 -0
  259. package/templates/channels/claude/icon.svg +1 -0
  260. package/templates/channels/claude/install.template.md.hbs +21 -0
  261. package/templates/channels/claude/integration/README.md +14 -0
  262. package/templates/channels/claude/integration/application.json +15 -0
  263. package/templates/channels/claude/integration/claude-system.json +59 -0
  264. package/templates/channels/claude/integration/env.template +2 -0
  265. package/templates/channels/claude/integration/rbac.json +13 -0
  266. package/templates/channels/claude/manifest.template.json.hbs +40 -0
  267. package/templates/channels/mcp-client/channel.json +72 -0
  268. package/templates/channels/mcp-client/icon.svg +1 -0
  269. package/templates/channels/mcp-client/install.template.md.hbs +24 -0
  270. package/templates/channels/mcp-client/integration/README.md +13 -0
  271. package/templates/channels/mcp-client/integration/application.json +15 -0
  272. package/templates/channels/mcp-client/integration/env.template +2 -0
  273. package/templates/channels/mcp-client/integration/mcp-client-system.json +72 -0
  274. package/templates/channels/mcp-client/integration/rbac.json +13 -0
  275. package/templates/channels/mcp-client/manifest.template.json.hbs +41 -0
  276. package/templates/channels/mcp-client/profiles/cursor.json +26 -0
  277. package/templates/channels/mcp-client/profiles/github-copilot.json +29 -0
  278. package/templates/channels/mcp-client/profiles/vscode.json +29 -0
  279. package/templates/channels/microsoft-copilot/channel.json +73 -0
  280. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +8 -4
  281. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +65 -5
  282. package/templates/channels/microsoft-copilot/icon.svg +1 -0
  283. package/templates/channels/microsoft-copilot/install.template.md.hbs +9 -9
  284. package/templates/channels/microsoft-copilot/integration/README.md +1 -1
  285. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  286. package/templates/channels/microsoft-copilot/integration/env.template +3 -6
  287. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +63 -4
  288. package/templates/channels/microsoft-copilot/integration/rbac.json +13 -0
  289. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +5 -5
  290. package/templates/channels/slack/channel.json +223 -0
  291. package/templates/channels/slack/icon.svg +1 -0
  292. package/templates/channels/slack/install.template.md.hbs +36 -28
  293. package/templates/channels/slack/integration/README.md +245 -22
  294. package/templates/channels/slack/integration/application.json +44 -7
  295. package/templates/channels/slack/integration/env.template +15 -6
  296. package/templates/channels/slack/integration/openapi/openapi.json +126 -0
  297. package/templates/channels/slack/integration/rbac.json +84 -0
  298. package/templates/channels/slack/integration/slack-datasource-commands.json +154 -0
  299. package/templates/channels/slack/integration/slack-datasource-conversations.json +28 -3
  300. package/templates/channels/slack/integration/slack-datasource-events.json +154 -0
  301. package/templates/channels/slack/integration/slack-datasource-interactive.json +154 -0
  302. package/templates/channels/slack/integration/slack-datasource-messages.json +40 -2
  303. package/templates/channels/slack/integration/slack-system.json +155 -77
  304. package/templates/channels/slack/manifest.template.json.hbs +5 -2
  305. package/templates/channels/teams-bot/channel.json +161 -0
  306. package/templates/channels/teams-bot/icon.svg +1 -0
  307. package/templates/channels/teams-bot/install.template.md.hbs +12 -13
  308. package/templates/channels/teams-bot/integration/README.md +5 -4
  309. package/templates/channels/teams-bot/integration/application.json +6 -4
  310. package/templates/channels/teams-bot/integration/env.template +5 -4
  311. package/templates/channels/teams-bot/integration/openapi/openapi.json +68 -0
  312. package/templates/channels/teams-bot/integration/rbac.json +21 -0
  313. package/templates/channels/teams-bot/integration/teams-bot-datasource-activities.json +154 -0
  314. package/templates/channels/teams-bot/integration/teams-bot-system.json +128 -23
  315. package/templates/channels/web-embed/channel.json +60 -0
  316. package/templates/channels/web-embed/install.template.md.hbs +1 -1
  317. package/templates/channels/web-embed/integration/README.md +2 -2
  318. package/templates/channels/web-embed/integration/application.json +2 -2
  319. package/templates/channels/web-embed/integration/env.template +1 -1
  320. package/templates/channels/web-embed/integration/rbac.json +13 -0
  321. package/templates/channels/web-embed/integration/web-embed-system.json +50 -4
@@ -12,6 +12,7 @@ const fs = require('../internal/fs-real-sync');
12
12
  const path = require('path');
13
13
  const { ensureIntegrationDirectory } = require('./workspace-context');
14
14
  const { resolveChannelRuntimeValues } = require('../channels/channel-runtime-resolver');
15
+ const { vendorFacingUrlsFromCtx } = require('../channels/vendor-dataplane-url');
15
16
  const { generateChannelArtifact } = require('../channels/channel-artifact-generator');
16
17
  const { loadChannelPackageContext } = require('../channels/channel-discovery');
17
18
  const {
@@ -20,6 +21,10 @@ const {
20
21
  mergeChannelBranding
21
22
  } = require('../channels/channel-branding');
22
23
  const { isChannelTargetKey } = require('../channels/channel-targets');
24
+ const {
25
+ httpError,
26
+ readChannelInstallSessionForDefinition
27
+ } = require('./channel-install-session');
23
28
 
24
29
  /**
25
30
  * @param {import('./workspace-context').WorkspaceContext} ctx
@@ -43,6 +48,16 @@ function requireChannelTarget(systemKey) {
43
48
  return systemKey;
44
49
  }
45
50
 
51
+ /**
52
+ * Catalog packaging key: instance paths (name slug) still read dist/channels/<catalog>/.
53
+ * @param {import('./workspace-context').WorkspaceContext} ctx
54
+ * @returns {string}
55
+ */
56
+ function packagingChannelTarget(ctx) {
57
+ const { resolveInstallDefinitionKey } = require('./channel-install-help-resolve');
58
+ return requireChannelTarget(resolveInstallDefinitionKey(ctx));
59
+ }
60
+
46
61
  /**
47
62
  * @param {import('./workspace-context').WorkspaceContext} ctx
48
63
  * @returns {{ type: string, token: string }}
@@ -82,8 +97,13 @@ function generatePackageArtifact(ctx, body, channelTarget, branding, dataplaneUr
82
97
  branding.displayName || body.name || packageContext.displayName || channelTarget;
83
98
  const description =
84
99
  branding.description || body.description || packageContext.description || '';
100
+ const vendor = vendorFacingUrlsFromCtx({
101
+ ...ctx,
102
+ dataplaneUrl
103
+ });
85
104
  const runtimeValues = resolveChannelRuntimeValues({
86
- dataplaneBaseUrl: dataplaneUrl,
105
+ dataplaneBaseUrl: vendor.dataplaneBaseUrl,
106
+ controllerUrl: vendor.controllerUrl,
87
107
  channelTarget,
88
108
  appName,
89
109
  baseName: body.baseName || appName,
@@ -91,6 +111,7 @@ function generatePackageArtifact(ctx, body, channelTarget, branding, dataplaneUr
91
111
  appDescription: description,
92
112
  layerBToolNames: packageContext.governedToolNames || [],
93
113
  packageContext,
114
+ oauthPluginVaultReferenceId: body.oauthPluginVaultReferenceId,
94
115
  iconColorPath: branding.iconColorPath,
95
116
  iconOutlinePath: branding.iconOutlinePath
96
117
  });
@@ -108,7 +129,7 @@ function generatePackageArtifact(ctx, body, channelTarget, branding, dataplaneUr
108
129
  * @returns {Promise<Object>}
109
130
  */
110
131
  async function buildChannelPackage(ctx, body = {}) {
111
- const channelTarget = requireChannelTarget(ctx.systemKey);
132
+ const channelTarget = packagingChannelTarget(ctx);
112
133
  ensureIntegrationDirectory(ctx);
113
134
  const dir = workspaceSystemDir(ctx);
114
135
  const branding = mergeChannelBranding(readChannelBranding(dir), body.branding || {});
@@ -151,7 +172,7 @@ async function buildChannelPackage(ctx, body = {}) {
151
172
  * @returns {Object}
152
173
  */
153
174
  function getChannelPackageSummary(ctx) {
154
- const channelTarget = requireChannelTarget(ctx.systemKey);
175
+ const channelTarget = packagingChannelTarget(ctx);
155
176
  const projectRoot = ctx.getUserRoot();
156
177
  const summaryPath = path.join(
157
178
  projectRoot,
@@ -161,7 +182,7 @@ function getChannelPackageSummary(ctx) {
161
182
  `channel-${channelTarget}.json`
162
183
  );
163
184
  if (!fs.existsSync(summaryPath)) {
164
- throw new Error(`Channel package summary not found: ${summaryPath}`);
185
+ throw httpError(`Channel package summary not found: ${summaryPath}`, 404);
165
186
  }
166
187
  const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
167
188
  return {
@@ -177,8 +198,8 @@ function getChannelPackageSummary(ctx) {
177
198
  * @param {import('./workspace-context').WorkspaceContext} ctx
178
199
  * @returns {{ fileName: string, contentType: string, byteLength: number, zipBase64: string }}
179
200
  */
180
- function getChannelPackageZip(ctx) {
181
- const channelTarget = requireChannelTarget(ctx.systemKey);
201
+ function readChannelPackageZip(ctx) {
202
+ const channelTarget = packagingChannelTarget(ctx);
182
203
  const summary = getChannelPackageSummary(ctx).summary;
183
204
  const zipPath =
184
205
  typeof summary?.links?.zip === 'string' && summary.links.zip
@@ -192,7 +213,7 @@ function getChannelPackageZip(ctx) {
192
213
  `appPackage.${summary.environment || 'dev'}.zip`
193
214
  );
194
215
  if (!fs.existsSync(zipPath)) {
195
- throw new Error(`Channel package ZIP not found: ${zipPath}`);
216
+ throw httpError(`Channel package ZIP not found: ${zipPath}`, 404);
196
217
  }
197
218
  const buffer = fs.readFileSync(zipPath);
198
219
  return {
@@ -203,6 +224,65 @@ function getChannelPackageZip(ctx) {
203
224
  };
204
225
  }
205
226
 
227
+ /**
228
+ * @param {Object|null} session
229
+ * @returns {Object}
230
+ */
231
+ function packageBodyFromSession(session) {
232
+ if (!session || typeof session !== 'object') {
233
+ return {};
234
+ }
235
+ return {
236
+ name: session.displayName || session.name,
237
+ oauthPluginVaultReferenceId: session.oauthPluginVaultReferenceId
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Portal ZIP GET must not 500 with CLI `--oauth-plugin-vault-reference-id` text.
243
+ * @param {unknown} err
244
+ * @returns {unknown}
245
+ */
246
+ function mapCopilotPackageHttpError(err) {
247
+ if (err && err.statusCode) {
248
+ return err;
249
+ }
250
+ const msg = String((err && err.message) || '');
251
+ if (/oauth-plugin-vault-reference-id|OAuthPluginVault/i.test(msg)) {
252
+ return httpError(
253
+ 'Copilot app package is not ready until Teams Entra SSO registration is stored on this install.',
254
+ 409
255
+ );
256
+ }
257
+ return err;
258
+ }
259
+
260
+ /**
261
+ * Rebuild the Copilot ZIP when the workspace lost dist/ after an image recreate.
262
+ * @param {import('./workspace-context').WorkspaceContext} ctx
263
+ * @returns {Promise<{ fileName: string, contentType: string, byteLength: number, zipBase64: string }>}
264
+ */
265
+ async function getChannelPackageZip(ctx) {
266
+ let summaryMissing = false;
267
+ try {
268
+ getChannelPackageSummary(ctx);
269
+ } catch (err) {
270
+ if (!err || err.statusCode !== 404) {
271
+ throw mapCopilotPackageHttpError(err);
272
+ }
273
+ summaryMissing = true;
274
+ }
275
+ if (summaryMissing) {
276
+ try {
277
+ const session = readChannelInstallSessionForDefinition(ctx, packagingChannelTarget(ctx));
278
+ await buildChannelPackage(ctx, packageBodyFromSession(session));
279
+ } catch (err) {
280
+ throw mapCopilotPackageHttpError(err);
281
+ }
282
+ }
283
+ return readChannelPackageZip(ctx);
284
+ }
285
+
206
286
  /**
207
287
  * @param {import('./workspace-context').WorkspaceContext} ctx
208
288
  * @param {Object} body
@@ -51,6 +51,7 @@ function resolveWorkspaceRoot(segment) {
51
51
  * @param {string} [params.environment]
52
52
  * @param {string} [params.controllerUrl]
53
53
  * @param {string} [params.dataplaneUrl]
54
+ * @param {string} [params.publicOrigin]
54
55
  * @returns {WorkspaceContext}
55
56
  */
56
57
  function createWorkspaceContext(params) {
@@ -88,6 +89,7 @@ function createWorkspaceContext(params) {
88
89
  process.env.CONTROLLER_URL ||
89
90
  '',
90
91
  dataplaneUrl: params.dataplaneUrl || process.env.DATAPLANE_URL || '',
92
+ publicOrigin: typeof params.publicOrigin === 'string' ? params.publicOrigin.trim() : '',
91
93
  bearerToken,
92
94
  getUserRoot: () => userRoot,
93
95
  getIntegrationRoot: () => integrationRoot,
@@ -22,6 +22,7 @@ const {
22
22
  } = require('../api/evidence.api');
23
23
  const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
24
24
  const { SYSTEM_KIND_DIGITAL_WORKER } = require('./settings');
25
+ const { formatEvidenceValidationIssues } = require('./evidence-validation-issues');
25
26
 
26
27
  const EVIDENCE_DIR_NAME = 'evidence';
27
28
  const EVIDENCE_EXPORT_FILE = 'evidence.json';
@@ -288,8 +289,9 @@ function isActivatedEvidenceRow(row) {
288
289
  async function runEvidenceLifecycleForRef(dataplaneUrl, authConfig, evidenceRef) {
289
290
  const validation = await validateEvidenceCandidate(dataplaneUrl, authConfig, evidenceRef);
290
291
  if (validationFailed(validation)) {
291
- const issues = Array.isArray(validation?.issues) ? validation.issues.length : 0;
292
- throw new Error(`Validation failed for evidence ${evidenceRef} (${issues} issue${issues === 1 ? '' : 's'}).`);
292
+ throw new Error(
293
+ `Validation failed for evidence ${evidenceRef} (${formatEvidenceValidationIssues(validation?.issues)}).`
294
+ );
293
295
  }
294
296
  if (String(validation?.status || '').trim().toLowerCase() !== VALIDATION_PASSED) {
295
297
  throw new Error(`Validation status for evidence ${evidenceRef} is "${validation?.status || 'unknown'}".`);
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Format Evidence validate issues[] for CLI errors.
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ function formatOneEvidenceIssue(issue) {
8
+ if (issue === undefined || issue === null) {
9
+ return '';
10
+ }
11
+ if (typeof issue !== 'object') {
12
+ return String(issue);
13
+ }
14
+ const bits = [];
15
+ if (issue.field) {
16
+ bits.push(String(issue.field));
17
+ }
18
+ if (issue.code) {
19
+ bits.push(String(issue.code));
20
+ }
21
+ if (issue.message) {
22
+ bits.push(String(issue.message));
23
+ }
24
+ if (bits.length > 0) {
25
+ return bits.join(' — ');
26
+ }
27
+ try {
28
+ return JSON.stringify(issue);
29
+ } catch {
30
+ return String(issue);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @param {unknown} issues
36
+ * @returns {string}
37
+ */
38
+ function formatEvidenceValidationIssues(issues) {
39
+ if (!Array.isArray(issues) || issues.length === 0) {
40
+ return '0 issues';
41
+ }
42
+ const parts = issues.map(formatOneEvidenceIssue).filter(Boolean);
43
+ const noun = issues.length === 1 ? 'issue' : 'issues';
44
+ const detail = parts.length > 0 ? `: ${parts.join('; ')}` : '';
45
+ return `${issues.length} ${noun}${detail}`;
46
+ }
47
+
48
+ module.exports = {
49
+ formatOneEvidenceIssue,
50
+ formatEvidenceValidationIssues
51
+ };
@@ -19,6 +19,7 @@ const {
19
19
  updateAssistantSettings
20
20
  } = require('../api/assistants.api');
21
21
  const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
22
+ const { bareRoleKey } = require('../utils/role-assistant-paths');
22
23
  const {
23
24
  SYSTEM_KIND_DIGITAL_WORKER,
24
25
  getRoleAssistantYamlPath,
@@ -75,7 +76,11 @@ async function resolveDocumentStorageKeyFromDataplane(dataplaneUrl, authConfig,
75
76
  return null;
76
77
  }
77
78
  try {
78
- const response = await getAssistantDetails(dataplaneUrl, systemKey, authConfig);
79
+ const response = await getAssistantDetails(
80
+ dataplaneUrl,
81
+ systemKey,
82
+ authConfig
83
+ );
79
84
  return extractDocumentStorageKeyFromAssistantDetails(response);
80
85
  } catch (_error) {
81
86
  return null;
@@ -141,16 +146,17 @@ async function applyAssistantSettingsPush(dataplaneUrl, systemKey, authConfig, s
141
146
  const { documentStorageKey, debugBody, patchBody } = settingsPush;
142
147
  const hasDocumentStorageKey = documentStorageKey !== '';
143
148
  const hasDebugSettings = Object.keys(debugBody).length > 0;
149
+ const assistantKey = String(systemKey || '').trim();
144
150
  let response = await updateAssistantSettings(
145
151
  dataplaneUrl,
146
- systemKey,
152
+ assistantKey,
147
153
  authConfig,
148
154
  patchBody
149
155
  );
150
156
  if (response && response.success === false && hasDocumentStorageKey && hasDebugSettings) {
151
157
  response = await updateAssistantSettings(
152
158
  dataplaneUrl,
153
- systemKey,
159
+ assistantKey,
154
160
  authConfig,
155
161
  debugBody
156
162
  );
@@ -352,7 +358,7 @@ function writeRoleAssistantSettingsFile(systemKey, docStorageKey, environment =
352
358
  apiVersion: 'aifabrix.ai/v1',
353
359
  version: '1.0.0',
354
360
  kind: 'RoleAssistantSettings',
355
- roleKey: systemKey,
361
+ roleKey: bareRoleKey(systemKey),
356
362
  [envKey]: {
357
363
  knowledge: docStorageKey ? { documentStorageKey: docStorageKey } : {}
358
364
  }
@@ -13,7 +13,11 @@ const fs = require('../internal/fs-real-sync');
13
13
  const yaml = require('js-yaml');
14
14
  const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
15
15
  const { loadRoleAssistantSettingsSchema } = require('../utils/schema-loader');
16
- const { SYSTEM_KIND_DIGITAL_WORKER } = require('../utils/role-assistant-paths');
16
+ const {
17
+ SYSTEM_KIND_DIGITAL_WORKER,
18
+ WORKSPACE_ROLE_ASSISTANT,
19
+ bareRoleKey
20
+ } = require('../utils/role-assistant-paths');
17
21
 
18
22
  function getRoleAssistantYamlPath(systemKey, options = {}) {
19
23
  const appPath = resolveExternalSystemPath(systemKey, options.pathOptions || {});
@@ -115,6 +119,48 @@ function resolveRoleAssistantSettingsVersion(settings) {
115
119
  return value.trim();
116
120
  }
117
121
 
122
+ function defaultRoleAssistantSettings(systemKey) {
123
+ return {
124
+ apiVersion: 'aifabrix.ai/v1',
125
+ version: '1.0.0',
126
+ kind: 'RoleAssistantSettings',
127
+ roleKey: bareRoleKey(systemKey)
128
+ };
129
+ }
130
+
131
+ function mergeRoleAssistantSettings(existing, systemKey) {
132
+ const next = existing && typeof existing === 'object' ? { ...existing } : {};
133
+ const defaults = defaultRoleAssistantSettings(systemKey);
134
+ return {
135
+ ...defaults,
136
+ ...next,
137
+ apiVersion: defaults.apiVersion,
138
+ kind: defaults.kind,
139
+ roleKey: defaults.roleKey
140
+ };
141
+ }
142
+
143
+ function writeOrMergeRoleAssistantSettings(systemKey, options = {}) {
144
+ const filePath = getRoleAssistantYamlPath(systemKey, {
145
+ ...options,
146
+ pathOptions: {
147
+ ...(options.pathOptions || {}),
148
+ workspace: WORKSPACE_ROLE_ASSISTANT
149
+ }
150
+ });
151
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
152
+ let payload = defaultRoleAssistantSettings(systemKey);
153
+ if (!options.force && fs.existsSync(filePath)) {
154
+ try {
155
+ payload = mergeRoleAssistantSettings(parseRoleAssistantYaml(filePath), systemKey);
156
+ } catch {
157
+ payload = defaultRoleAssistantSettings(systemKey);
158
+ }
159
+ }
160
+ fs.writeFileSync(filePath, yaml.dump(payload, { lineWidth: -1 }), 'utf8');
161
+ return filePath;
162
+ }
163
+
118
164
  module.exports = {
119
165
  SYSTEM_KIND_DIGITAL_WORKER,
120
166
  getRoleAssistantYamlPath,
@@ -122,5 +168,6 @@ module.exports = {
122
168
  readRoleAssistantSettings,
123
169
  resolveRoleAssistantEnvironmentSettings,
124
170
  resolveDocumentStorageKeyFromRoleAssistantSettings,
125
- resolveRoleAssistantSettingsVersion
171
+ resolveRoleAssistantSettingsVersion,
172
+ writeOrMergeRoleAssistantSettings
126
173
  };
@@ -10,6 +10,14 @@ const {
10
10
  evaluateHonestyExpectations
11
11
  } = require('./test-cases-honesty');
12
12
  const { collectBidAssertions } = require('./test-cases-bid');
13
+ const {
14
+ evaluateEvidenceExpectations,
15
+ evaluateForbiddenCapabilities
16
+ } = require('./test-cases-evidence');
17
+ const {
18
+ evaluateSearchExpectations,
19
+ evaluateSkillsExpectations
20
+ } = require('./test-cases-search');
13
21
 
14
22
  function getValueAtPath(obj, dottedPath) {
15
23
  const parts = String(dottedPath || '').split('.').filter(Boolean);
@@ -111,6 +119,10 @@ function buildCaseObservation(result, lifecycleVerdict, portalUrl) {
111
119
  workResult,
112
120
  workResultError: result?.workResultError || null,
113
121
  honesty: buildHonestyObservation(workResult),
122
+ evidencePreflight: result?.evidencePreflight || null,
123
+ search: result?.search || null,
124
+ skills: result?.skills || null,
125
+ roleRef: result?.roleRef || null,
114
126
  interactions: Array.isArray(result?.interactions)
115
127
  ? result.interactions
116
128
  : Array.isArray(result?.workInteractions)
@@ -136,11 +148,15 @@ function softWrapLifecycleAssertion(lifecycleAssertion, soft) {
136
148
  };
137
149
  }
138
150
 
151
+ function softWrapFailedAssertion(assertion, soft) {
152
+ return softWrapLifecycleAssertion(assertion, soft);
153
+ }
154
+
139
155
  function collectCaseAssertions(expected, observation) {
140
156
  const soft = expected.soft === true;
141
157
  const observedCapabilities = observation.capabilities?.observed || [];
142
158
  const assertions = [];
143
- const lifecycleAssertion = softWrapLifecycleAssertion(
159
+ const lifecycleAssertion = softWrapFailedAssertion(
144
160
  evaluateLifecycleStateExpectation(expected.lifecycleState, observation.lifecycleState),
145
161
  soft
146
162
  );
@@ -148,12 +164,24 @@ function collectCaseAssertions(expected, observation) {
148
164
  assertions.push(lifecycleAssertion);
149
165
  }
150
166
  assertions.push(
151
- ...evaluateCapabilityExpectations(expected?.capabilities?.required, observedCapabilities)
167
+ ...evaluateCapabilityExpectations(
168
+ expected?.capabilities?.required,
169
+ observedCapabilities
170
+ ).map((row) => softWrapFailedAssertion(row, soft))
171
+ );
172
+ assertions.push(
173
+ ...evaluateForbiddenCapabilities(
174
+ expected?.capabilities?.forbidden,
175
+ observedCapabilities
176
+ ).map((row) => softWrapFailedAssertion(row, soft))
152
177
  );
153
178
  assertions.push(...evaluateFactExpectations(expected.facts, observation));
154
179
  assertions.push(...evaluateProhibitedExpectations(expected.prohibited, observation));
155
180
  assertions.push(...evaluateHonestyExpectations(expected.honesty, observation, { soft }));
156
181
  assertions.push(...collectBidAssertions(expected.bid, observation, { soft }));
182
+ assertions.push(...evaluateEvidenceExpectations(expected.evidence, observation));
183
+ assertions.push(...evaluateSearchExpectations(expected.search, observation));
184
+ assertions.push(...evaluateSkillsExpectations(expected.skills, observation));
157
185
  return assertions;
158
186
  }
159
187
 
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Role Assistant case Evidence preflight — live dataplane rows only.
3
+ * @fileoverview Hard-stop when required Evidence keys are missing or not active
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { listEvidence } = require('../api/evidence.api');
9
+ const { bareRoleKey } = require('../utils/role-assistant-paths');
10
+
11
+ function asStringList(value) {
12
+ if (!Array.isArray(value)) {
13
+ return [];
14
+ }
15
+ return value.map((item) => String(item || '').trim()).filter(Boolean);
16
+ }
17
+
18
+ function evidenceSpecFromCase(testCase) {
19
+ const preflight = testCase?.preflight?.evidence;
20
+ const expected = testCase?.expected?.evidence;
21
+ const requiredKeys = asStringList(preflight?.requiredKeys).concat(
22
+ asStringList(expected?.requiredKeys)
23
+ );
24
+ const unique = [...new Set(requiredKeys)];
25
+ if (unique.length === 0) {
26
+ return null;
27
+ }
28
+ const roleKey =
29
+ String(preflight?.roleKey || expected?.roleKey || '').trim() ||
30
+ bareRoleKey(testCase?.input?.businessRoleRef || '') ||
31
+ '';
32
+ const resourceType = String(preflight?.resourceType || expected?.resourceType || '').trim();
33
+ return {
34
+ requiredKeys: unique,
35
+ roleKey,
36
+ resourceType,
37
+ requireStatus: String(preflight?.requireStatus || expected?.requireStatus || 'active')
38
+ .trim()
39
+ .toLowerCase() || 'active'
40
+ };
41
+ }
42
+
43
+ function rowKey(row) {
44
+ return String(row?.key || row?.evidenceKey || '').trim();
45
+ }
46
+
47
+ function rowStatus(row) {
48
+ return String(row?.status || '').trim().toLowerCase();
49
+ }
50
+
51
+ function isActiveStatus(status, requiredStatus) {
52
+ if (requiredStatus === 'active') {
53
+ return !status || status === 'active';
54
+ }
55
+ return status === requiredStatus;
56
+ }
57
+
58
+ function findMissingEvidenceKeys(spec, rows) {
59
+ const byKey = new Map();
60
+ for (const row of rows) {
61
+ const key = rowKey(row);
62
+ if (key) {
63
+ byKey.set(key, row);
64
+ }
65
+ }
66
+ const missing = [];
67
+ for (const key of spec.requiredKeys) {
68
+ const row = byKey.get(key);
69
+ if (!row) {
70
+ missing.push(`${key} (not in dataplane)`);
71
+ continue;
72
+ }
73
+ const status = rowStatus(row);
74
+ if (!isActiveStatus(status, spec.requireStatus)) {
75
+ missing.push(`${key} (status=${status || 'unknown'}, need ${spec.requireStatus})`);
76
+ }
77
+ }
78
+ return missing;
79
+ }
80
+
81
+ async function loadLiveEvidenceRows(authContext, spec) {
82
+ const dataplaneUrl = authContext?.dataplaneUrl;
83
+ const authConfig = authContext?.authConfig;
84
+ if (!dataplaneUrl || !authConfig) {
85
+ throw new Error(
86
+ 'Evidence preflight failed: dataplane URL or auth is missing. Run aifabrix login and retry.'
87
+ );
88
+ }
89
+ const params = {
90
+ roleKey: spec.roleKey || undefined,
91
+ resourceType: spec.resourceType || undefined,
92
+ status: spec.requireStatus === 'active' ? 'active' : spec.requireStatus,
93
+ page: 1,
94
+ pageSize: 100
95
+ };
96
+ try {
97
+ return await listEvidence(dataplaneUrl, authConfig, params);
98
+ } catch (error) {
99
+ const detail = error && error.message ? error.message : String(error);
100
+ throw new Error(`Evidence preflight failed: could not read Evidence from the dataplane (${detail}).`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Hard-stop before work.start when the case requires live Evidence.
106
+ * @returns {Promise<{ skipped: boolean, requiredKeys: string[], rows: Object[] }>}
107
+ */
108
+ async function assertRoleAssistantEvidencePreflight(testCase, authContext) {
109
+ const spec = evidenceSpecFromCase(testCase);
110
+ if (!spec) {
111
+ return { skipped: true, requiredKeys: [], rows: [] };
112
+ }
113
+ if (!spec.roleKey) {
114
+ throw new Error(
115
+ 'Evidence preflight failed: roleKey is required (preflight.evidence.roleKey or input.businessRoleRef).'
116
+ );
117
+ }
118
+ const rows = await loadLiveEvidenceRows(authContext, spec);
119
+ const missing = findMissingEvidenceKeys(spec, rows);
120
+ if (missing.length > 0) {
121
+ throw new Error(
122
+ 'Evidence preflight failed: required Evidence is not active in the dataplane: ' +
123
+ `${missing.join('; ')}. Upload the Role Assistant package to the target dataplane, ` +
124
+ 'then retry. Hard stop.'
125
+ );
126
+ }
127
+ return { skipped: false, requiredKeys: spec.requiredKeys, rows };
128
+ }
129
+
130
+ function evaluateEvidenceExpectations(evidenceSpec, observation) {
131
+ const requiredKeys = asStringList(evidenceSpec?.requiredKeys);
132
+ if (requiredKeys.length === 0) {
133
+ return [];
134
+ }
135
+ const preflight = observation?.evidencePreflight || {};
136
+ const liveKeys = new Set(
137
+ (Array.isArray(preflight.rows) ? preflight.rows : []).map(rowKey).filter(Boolean)
138
+ );
139
+ return requiredKeys.map((key) => {
140
+ const passed = liveKeys.has(key);
141
+ return {
142
+ id: `expected.evidence.requiredKeys:${key}`,
143
+ passed,
144
+ message: passed
145
+ ? `Evidence key "${key}" was active in the dataplane at test start.`
146
+ : `Evidence key "${key}" was not active in the dataplane at test start.`
147
+ };
148
+ });
149
+ }
150
+
151
+ function evaluateForbiddenCapabilities(forbiddenList, observedCapabilities) {
152
+ if (!Array.isArray(forbiddenList) || forbiddenList.length === 0) {
153
+ return [];
154
+ }
155
+ const observedSet = new Set(observedCapabilities);
156
+ return forbiddenList.map((capability) => {
157
+ const key = String(capability || '').trim();
158
+ const passed = !observedSet.has(key);
159
+ return {
160
+ id: `expected.capabilities.forbidden:${key}`,
161
+ passed,
162
+ message: passed
163
+ ? `Forbidden capability "${key}" was not observed.`
164
+ : `Forbidden capability "${key}" was observed.`
165
+ };
166
+ });
167
+ }
168
+
169
+ module.exports = {
170
+ evidenceSpecFromCase,
171
+ findMissingEvidenceKeys,
172
+ assertRoleAssistantEvidencePreflight,
173
+ evaluateEvidenceExpectations,
174
+ evaluateForbiddenCapabilities
175
+ };