@aifabrix/builder 2.51.0 → 2.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) 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-container-start.js +4 -0
  14. package/lib/app/run-deploy-job.js +216 -0
  15. package/lib/app/run-env-compose.js +32 -0
  16. package/lib/app/run-env-recovery.js +35 -0
  17. package/lib/app/run-helpers.js +15 -9
  18. package/lib/app/run.js +18 -2
  19. package/lib/channels/add-channel.js +54 -23
  20. package/lib/channels/approval-guides/chatgpt.js +13 -9
  21. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  22. package/lib/channels/channel-artifact-generator.js +14 -2
  23. package/lib/channels/channel-credential.js +3 -1
  24. package/lib/channels/channel-probe-evidence.js +79 -0
  25. package/lib/channels/channel-probe.js +265 -13
  26. package/lib/channels/channel-runtime-resolver.js +120 -29
  27. package/lib/channels/channel-status-display.js +44 -0
  28. package/lib/channels/channel-targets.js +25 -1
  29. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  30. package/lib/channels/microsoft-copilot-package.js +280 -0
  31. package/lib/channels/runtime-tool-catalog.js +165 -0
  32. package/lib/cli/index.js +2 -0
  33. package/lib/cli/infra-guided-footers.js +1 -1
  34. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  35. package/lib/cli/infra-guided.js +65 -56
  36. package/lib/cli/setup-app.help.js +4 -0
  37. package/lib/cli/setup-app.js +4 -0
  38. package/lib/cli/setup-app.test-commands.js +59 -28
  39. package/lib/cli/setup-external-system-help.js +8 -2
  40. package/lib/cli/setup-external-system.js +62 -12
  41. package/lib/commands/app-test.js +2 -14
  42. package/lib/commands/channel-add-setup.js +8 -2
  43. package/lib/commands/channel.js +16 -5
  44. package/lib/commands/datasource-load-export-cli.js +6 -1
  45. package/lib/commands/deploy-gate-failure-details.js +19 -17
  46. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  47. package/lib/commands/dev-down.js +2 -6
  48. package/lib/commands/evidence-kind.js +65 -15
  49. package/lib/commands/governance-generate-external.js +56 -21
  50. package/lib/commands/governance-verify-external.js +2 -5
  51. package/lib/commands/lifecycle-external.js +21 -8
  52. package/lib/commands/repair-datasource-apply.js +10 -1
  53. package/lib/commands/repair-datasource-run.js +4 -2
  54. package/lib/commands/repair-datasource-test-payload.js +80 -19
  55. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  56. package/lib/commands/repair-internal.js +9 -1
  57. package/lib/commands/repair-openapi-sync.js +34 -17
  58. package/lib/commands/role-assistant.js +371 -0
  59. package/lib/commands/test-governance-external.js +2 -5
  60. package/lib/commands/up-miso.js +61 -24
  61. package/lib/commands/upload-data-sync-finalize.js +25 -7
  62. package/lib/commands/upload-sidecar-followups.js +144 -47
  63. package/lib/commands/upload.js +43 -6
  64. package/lib/commands/verify-governance-combined-external.js +73 -9
  65. package/lib/commands/verify-governance-db.js +80 -25
  66. package/lib/commands/verify-governance-external.js +31 -10
  67. package/lib/commands/verify-operations-external.js +2 -5
  68. package/lib/commands/verify-operations-step-plan.js +31 -0
  69. package/lib/commands/verify-trust-command-action.js +2 -5
  70. package/lib/commands/wizard-core-helpers.js +7 -3
  71. package/lib/commands/wizard-core.js +78 -9
  72. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  73. package/lib/datasource/bulk-loader-service.js +5 -1
  74. package/lib/datasource/datasource-identifier.js +4 -3
  75. package/lib/datasource/datasource-validate-summary.js +2 -2
  76. package/lib/datasource/integration-context.js +3 -3
  77. package/lib/datasource/resolve-app.js +90 -8
  78. package/lib/datasource/test-e2e.js +2 -2
  79. package/lib/datasource/unified-validation-run-body.js +15 -1
  80. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  81. package/lib/datasource/validate.js +13 -6
  82. package/lib/external-system/deploy-helpers.js +39 -1
  83. package/lib/external-system/deploy.js +25 -11
  84. package/lib/external-system/download-helpers.js +111 -2
  85. package/lib/external-system/download-role-assistant.js +55 -0
  86. package/lib/external-system/download.js +45 -26
  87. package/lib/external-system/test-helpers.js +1 -1
  88. package/lib/external-system/test-system-level.js +21 -3
  89. package/lib/external-system/test.js +43 -7
  90. package/lib/generator/builders.js +12 -15
  91. package/lib/generator/wizard.js +51 -4
  92. package/lib/lifecycle/access-control-abac.js +121 -0
  93. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  94. package/lib/lifecycle/product-model.js +3 -2
  95. package/lib/lifecycle/report-display.js +14 -2
  96. package/lib/programmatic/capability-registry.js +20 -1
  97. package/lib/programmatic/command-options.js +1 -0
  98. package/lib/programmatic/index.js +4 -0
  99. package/lib/programmatic/manifest-patch-section.js +48 -9
  100. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  101. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  102. package/lib/programmatic/openapi-component-schemas.js +16 -1
  103. package/lib/programmatic/openapi-description-enricher.js +3 -1
  104. package/lib/programmatic/openapi-descriptions.js +55 -5
  105. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  106. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  107. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  108. package/lib/programmatic/openapi-request-schemas.js +36 -1
  109. package/lib/programmatic/openapi-result-schemas.js +1 -1
  110. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  111. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  112. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  113. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  114. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  115. package/lib/programmatic/openapi-spec.js +47 -28
  116. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  117. package/lib/programmatic/request-timeout.js +3 -0
  118. package/lib/programmatic/route-handler-map.js +89 -2
  119. package/lib/programmatic/run-certify.js +15 -2
  120. package/lib/programmatic/run-deploy.js +74 -0
  121. package/lib/programmatic/run-evidence-authoring.js +80 -0
  122. package/lib/programmatic/run-protection.js +124 -0
  123. package/lib/programmatic/run-template-workspace.js +28 -1
  124. package/lib/programmatic/workspace-context.js +6 -0
  125. package/lib/protection/auth-context.js +48 -6
  126. package/lib/protection/paths.js +22 -0
  127. package/lib/protection/run-protection-create-helpers.js +22 -1
  128. package/lib/protection/run-protection-create.js +1 -1
  129. package/lib/protection/sync-after-upload.js +1 -1
  130. package/lib/role-assistant/evidence-sync.js +450 -0
  131. package/lib/role-assistant/github-workflow.js +96 -0
  132. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  133. package/lib/role-assistant/knowledge-sync.js +485 -0
  134. package/lib/role-assistant/settings.js +126 -0
  135. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  136. package/lib/role-assistant/test-cases-honesty.js +184 -0
  137. package/lib/role-assistant/test-cases.js +364 -0
  138. package/lib/role-assistant/test-runner-workhub-answers.js +221 -0
  139. package/lib/role-assistant/test-runner-workhub.js +399 -0
  140. package/lib/role-assistant/test-runner.js +500 -0
  141. package/lib/schema/application-schema.json +24 -28
  142. package/lib/schema/deployment-rules.yaml +1 -0
  143. package/lib/schema/external-datasource.schema.json +38 -4
  144. package/lib/schema/external-system.schema.json +4 -4
  145. package/lib/schema/role-assistant-settings.schema.json +108 -0
  146. package/lib/utils/api.js +52 -27
  147. package/lib/utils/app-path-resolve.js +72 -24
  148. package/lib/utils/authentication-shape.js +3 -1
  149. package/lib/utils/certify-external-workspace.js +40 -0
  150. package/lib/utils/deployment-validation.js +28 -3
  151. package/lib/utils/docker-run-ephemeral.js +115 -0
  152. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  153. package/lib/utils/env-copy-keycloak.js +196 -0
  154. package/lib/utils/env-copy.js +34 -1
  155. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  156. package/lib/utils/health-check-public-warn.js +31 -13
  157. package/lib/utils/health-check.js +11 -1
  158. package/lib/utils/help-builder.js +1 -0
  159. package/lib/utils/paths.js +80 -38
  160. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  161. package/lib/utils/role-assistant-paths.js +160 -0
  162. package/lib/utils/schema-loader.js +41 -0
  163. package/lib/utils/schema-resolver.js +36 -4
  164. package/lib/utils/token-manager.js +38 -29
  165. package/lib/utils/upload-sync-options.js +7 -4
  166. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  167. package/lib/validation/external-manifest-validator.js +31 -8
  168. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  169. package/lib/validation/validate-external-file.js +8 -1
  170. package/lib/validation/validate.js +54 -19
  171. package/package.json +2 -1
  172. package/scripts/export-builder-api-evidence.js +1 -1
  173. package/templates/applications/builder-api/application.yaml +3 -3
  174. package/templates/applications/dataplane/application.yaml +24 -17
  175. package/templates/applications/dataplane/env.template +2 -2
  176. package/templates/applications/dataplane/rbac.yaml +24 -10
  177. package/templates/applications/keycloak/application.yaml +2 -2
  178. package/templates/applications/keycloak/env.template +5 -2
  179. package/templates/applications/miso-controller/application.yaml +3 -3
  180. package/templates/applications/miso-controller/env.template +17 -2
  181. package/templates/channels/chatgpt/install.template.md.hbs +82 -26
  182. package/templates/channels/chatgpt/integration/README.md +11 -7
  183. package/templates/channels/chatgpt/integration/application.json +2 -2
  184. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  185. package/templates/channels/chatgpt/integration/env.template +1 -1
  186. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  187. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  188. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  189. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  190. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  191. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  192. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  193. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  194. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  195. package/templates/channels/slack/install.template.md.hbs +6 -1
  196. package/templates/python/docker-compose.hbs +10 -0
  197. package/templates/typescript/docker-compose.hbs +10 -0
  198. /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Probe BTA Evidence authoring reachability for channel installs (plan 163 T7).
3
+ *
4
+ * @fileoverview Evidence metadata/download live checks without mutating activate/upload
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const {
12
+ getEvidenceMetadata,
13
+ downloadEvidence
14
+ } = require('../api/evidence-authoring.api');
15
+
16
+ const EVIDENCE_AUTHORING_CAPABILITY_KEYS = Object.freeze([
17
+ 'evidence.metadata',
18
+ 'evidence.download',
19
+ 'evidence.upload',
20
+ 'evidence.activate'
21
+ ]);
22
+
23
+ /**
24
+ * Prefer Business Transformation when present; otherwise first catalog role key.
25
+ *
26
+ * @param {Object[]} catalogItems
27
+ * @returns {string}
28
+ */
29
+ function pickEvidenceRoleKey(catalogItems) {
30
+ const keys = (Array.isArray(catalogItems) ? catalogItems : [])
31
+ .map((item) => String(item?.key || item?.roleKey || item?.roleRef || '').trim())
32
+ .filter(Boolean);
33
+ const preferred = keys.find((key) => key.includes('business-transformation'));
34
+ return preferred || keys[0] || '';
35
+ }
36
+
37
+ /**
38
+ * Live metadata + download prove Evidence path; upload/activate remain contract keys.
39
+ *
40
+ * @async
41
+ * @param {Object} ctx
42
+ * @param {Object[]} catalogItems
43
+ * @returns {Promise<{ reachable: boolean, roleKey: string, capabilityKeys: string[] }>}
44
+ */
45
+ async function probeEvidenceAuthoringReachable(ctx, catalogItems) {
46
+ const roleKey = pickEvidenceRoleKey(catalogItems);
47
+ const capabilityKeys = [...EVIDENCE_AUTHORING_CAPABILITY_KEYS];
48
+ if (!roleKey || !ctx?.dataplaneUrl || !ctx?.authConfig || !ctx?.controllerUrl) {
49
+ return { reachable: false, roleKey, capabilityKeys };
50
+ }
51
+ try {
52
+ const options = { controllerUrl: ctx.controllerUrl };
53
+ const metadata = await getEvidenceMetadata(
54
+ ctx.dataplaneUrl,
55
+ ctx.authConfig,
56
+ roleKey,
57
+ options
58
+ );
59
+ const downloaded = await downloadEvidence(
60
+ ctx.dataplaneUrl,
61
+ ctx.authConfig,
62
+ { roleKey, pageSize: 1 },
63
+ options
64
+ );
65
+ const reachable =
66
+ Boolean(metadata?.roleKey) &&
67
+ Array.isArray(metadata?.evidenceKinds) &&
68
+ Array.isArray(downloaded?.data);
69
+ return { reachable, roleKey, capabilityKeys };
70
+ } catch {
71
+ return { reachable: false, roleKey, capabilityKeys };
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ EVIDENCE_AUTHORING_CAPABILITY_KEYS,
77
+ pickEvidenceRoleKey,
78
+ probeEvidenceAuthoringReachable
79
+ };
@@ -8,8 +8,15 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const { ApiClient } = require('../api/index');
11
+ const { ApiClient, createDataplaneApiClient } = require('../api/index');
12
12
  const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
13
+ const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
14
+ const {
15
+ fetchRuntimeLayerBToolNames,
16
+ isRuntimeMcpGatewayReachable
17
+ } = require('./runtime-tool-catalog');
18
+ const { probeEvidenceAuthoringReachable } = require('./channel-probe-evidence');
19
+ const { RUNTIME_ORCHESTRATION_SYSTEM_KEY } = require('./chatgpt-runtime-contract');
13
20
 
14
21
  /**
15
22
  * @async
@@ -35,12 +42,48 @@ async function isUrlReachable(url) {
35
42
  * @returns {Promise<boolean>}
36
43
  */
37
44
  async function isAssistantCatalogReachable(dataplaneUrl, authConfig) {
38
- const client = require('../api/index').createDataplaneApiClient(dataplaneUrl, authConfig);
45
+ const summary = await fetchAssistantCatalogSummary(dataplaneUrl, authConfig);
46
+ return summary.reachable;
47
+ }
48
+
49
+ /**
50
+ * @param {Object} response
51
+ * @returns {Object[]}
52
+ */
53
+ function extractAssistantCatalogItems(response) {
54
+ const body = response?.data ?? response;
55
+ const data = body?.data ?? body;
56
+ if (Array.isArray(data)) {
57
+ return data;
58
+ }
59
+ if (Array.isArray(data?.items)) {
60
+ return data.items;
61
+ }
62
+ if (Array.isArray(data?.workers)) {
63
+ return data.workers;
64
+ }
65
+ if (Array.isArray(data?.assistants)) {
66
+ return data.assistants;
67
+ }
68
+ return [];
69
+ }
70
+
71
+ /**
72
+ * @async
73
+ * @param {string} dataplaneUrl
74
+ * @param {Object} authConfig
75
+ * @returns {Promise<{ reachable: boolean, items: Object[] }>}
76
+ */
77
+ async function fetchAssistantCatalogSummary(dataplaneUrl, authConfig) {
78
+ const client = createDataplaneApiClient(dataplaneUrl, authConfig);
39
79
  try {
40
80
  const res = await client.get('/api/v1/workers/available', { timeoutMs: 10000 });
41
- return res && res.success !== false;
81
+ return {
82
+ reachable: Boolean(res && res.success !== false),
83
+ items: extractAssistantCatalogItems(res)
84
+ };
42
85
  } catch {
43
- return false;
86
+ return { reachable: false, items: [] };
44
87
  }
45
88
  }
46
89
 
@@ -59,31 +102,240 @@ function evaluateInboundVerification(channelExt) {
59
102
  }
60
103
 
61
104
  /**
62
- * @async
63
105
  * @param {Object} ctx
64
106
  * @param {Object|null} channelExt
65
- * @returns {Promise<Object>}
107
+ * @param {boolean} catalogOk
108
+ * @param {string[]} layerBToolNames
109
+ * @param {Object[]} [catalogItems]
110
+ * @returns {Promise<{ probeTiers: Object, runtimeChecks: Object, evidenceCapabilityKeys?: string[] }>}
66
111
  */
67
- async function runChannelProbe(ctx, channelExt) {
68
- const health = await fetchDataplaneGeneralHealth(ctx.dataplaneUrl, { timeoutMs: 8000 });
69
- const catalogOk = await isAssistantCatalogReachable(ctx.dataplaneUrl, ctx.authConfig);
70
- const callbackOk = health?.status === 'healthy' || health?.status === 'ok';
112
+ /**
113
+ * @param {Object|null} channelExt
114
+ * @param {Object} runtime
115
+ * @param {Object} runtimeChecks
116
+ * @param {Object} evidence
117
+ * @returns {Object}
118
+ */
119
+ function buildChatGptProbePayload(channelExt, runtime, runtimeChecks, evidence) {
120
+ return {
121
+ probeTiers: {
122
+ locallyValidated: Boolean(
123
+ channelExt?.credentialKey &&
124
+ channelExt?.artifactHash &&
125
+ runtime.mcpUrl &&
126
+ !runtime.mcpUrl.includes('/api/v1/chatgpt/')
127
+ ),
128
+ runtimeValidated: Object.values(runtimeChecks).every(Boolean),
129
+ requiresChatGptWorkspaceAdmin: true
130
+ },
131
+ runtimeChecks,
132
+ evidenceCapabilityKeys: evidence.capabilityKeys,
133
+ evidenceRoleKey: evidence.roleKey
134
+ };
135
+ }
71
136
 
72
- const inbound = evaluateInboundVerification(channelExt);
137
+ /**
138
+ * @param {Object} ctx
139
+ * @param {Object|null} channelExt
140
+ * @param {boolean} catalogOk
141
+ * @param {string[]} layerBToolNames
142
+ * @param {Object[]} [catalogItems]
143
+ * @returns {Promise<{ probeTiers: Object, runtimeChecks: Object, evidenceCapabilityKeys?: string[] }>}
144
+ */
145
+ async function buildChatGptProbeDetails(
146
+ ctx,
147
+ channelExt,
148
+ catalogOk,
149
+ layerBToolNames,
150
+ catalogItems = []
151
+ ) {
152
+ const runtime = resolveChannelRuntimeValues({
153
+ dataplaneBaseUrl: ctx.dataplaneUrl,
154
+ channelTarget: 'chatgpt',
155
+ appName: channelExt?.name || 'chatgpt',
156
+ baseName: channelExt?.baseName || channelExt?.name || 'chatgpt',
157
+ appDescription: channelExt?.description || 'ChatGPT custom MCP app',
158
+ layerBToolNames
159
+ });
160
+ const mcpHandshakeReachable = await isRuntimeMcpGatewayReachable(
161
+ ctx.dataplaneUrl,
162
+ ctx.authConfig,
163
+ RUNTIME_ORCHESTRATION_SYSTEM_KEY
164
+ );
165
+ const evidence = await probeEvidenceAuthoringReachable(ctx, catalogItems);
166
+ const runtimeChecks = {
167
+ mcpHandshakeReachable,
168
+ layerBToolNamesDiscovered: Array.isArray(layerBToolNames) && layerBToolNames.length > 0,
169
+ roleAssistantResolutionReachable: catalogOk,
170
+ tenantEnvironmentBoundaryConfirmed: Boolean(ctx.environment || channelExt?.name),
171
+ evidenceAuthoringReachable: evidence.reachable
172
+ };
173
+ return buildChatGptProbePayload(channelExt, runtime, runtimeChecks, evidence);
174
+ }
73
175
 
176
+ /**
177
+ * @param {Object} ctx
178
+ * @param {Object|null} channelExt
179
+ * @param {boolean} catalogOk
180
+ * @param {string[]} layerBToolNames
181
+ * @returns {Promise<{ probeTiers: Object, runtimeChecks: Object }>}
182
+ */
183
+ async function buildMicrosoftCopilotProbeDetails(ctx, channelExt, catalogOk, layerBToolNames) {
184
+ const runtime = resolveChannelRuntimeValues({
185
+ dataplaneBaseUrl: ctx.dataplaneUrl,
186
+ channelTarget: 'microsoft-copilot',
187
+ appName: channelExt?.name || 'microsoft-copilot',
188
+ baseName: channelExt?.baseName || channelExt?.name || 'microsoft-copilot',
189
+ appDescription: channelExt?.description || 'Microsoft Copilot package channel',
190
+ layerBToolNames
191
+ });
192
+ const mcpHandshakeReachable = await isRuntimeMcpGatewayReachable(
193
+ ctx.dataplaneUrl,
194
+ ctx.authConfig,
195
+ RUNTIME_ORCHESTRATION_SYSTEM_KEY
196
+ );
197
+ const runtimeChecks = {
198
+ mcpHandshakeReachable,
199
+ layerBToolNamesDiscovered: Array.isArray(layerBToolNames) && layerBToolNames.length > 0,
200
+ authMetadataConfigured: Boolean(runtime.runtimeAuthMode && channelExt?.credentialKey),
201
+ roleAssistantResolutionReachable: catalogOk,
202
+ tenantEnvironmentBoundaryConfirmed: Boolean(ctx.environment || channelExt?.name)
203
+ };
204
+ return {
205
+ probeTiers: {
206
+ builderValidated: Boolean(
207
+ channelExt?.credentialKey &&
208
+ channelExt?.artifactHash &&
209
+ runtime.mcpUrl &&
210
+ !runtime.mcpUrl.includes('/api/v1/copilot/')
211
+ ),
212
+ runtimeValidated: Object.values(runtimeChecks).every(Boolean),
213
+ requiresMicrosoftAdmin: true
214
+ },
215
+ runtimeChecks
216
+ };
217
+ }
218
+
219
+ /**
220
+ * @param {Object} health
221
+ * @param {boolean} callbackOk
222
+ * @param {boolean} catalogOk
223
+ * @param {Object|null} channelExt
224
+ * @param {Object} inbound
225
+ * @returns {Object}
226
+ */
227
+ function buildBaseProbeResult(health, callbackOk, catalogOk, channelExt, inbound) {
74
228
  return {
75
229
  dataplaneHealth: health?.status || 'unknown',
76
230
  callbackReachable: callbackOk,
77
231
  assistantCatalogReachable: catalogOk,
78
232
  credentialValid: Boolean(channelExt?.credentialKey),
79
- inboundVerification: inbound,
80
- passed: callbackOk && catalogOk && Boolean(channelExt?.credentialKey)
233
+ inboundVerification: inbound
81
234
  };
82
235
  }
83
236
 
237
+ /**
238
+ * @param {Object} base
239
+ * @param {Object} probe
240
+ * @param {Object|null} channelExt
241
+ * @returns {Object}
242
+ */
243
+ function buildCopilotProbeResult(base, probe, channelExt) {
244
+ return {
245
+ ...base,
246
+ ...probe,
247
+ expectedLayerBToolNames: [...(probe.discoveredLayerBToolNames || [])],
248
+ passed:
249
+ probe.probeTiers.builderValidated &&
250
+ probe.probeTiers.runtimeValidated &&
251
+ Boolean(channelExt?.credentialKey)
252
+ };
253
+ }
254
+
255
+ /**
256
+ * @param {Object} base
257
+ * @param {Object} probe
258
+ * @param {Object|null} channelExt
259
+ * @returns {Object}
260
+ */
261
+ function buildChatGptProbeResult(base, probe, channelExt) {
262
+ return {
263
+ ...base,
264
+ ...probe,
265
+ expectedLayerBToolNames: [...(probe.discoveredLayerBToolNames || [])],
266
+ passed:
267
+ probe.probeTiers.locallyValidated &&
268
+ probe.probeTiers.runtimeValidated &&
269
+ Boolean(channelExt?.credentialKey)
270
+ };
271
+ }
272
+
273
+ /**
274
+ * @async
275
+ * @param {Object} ctx
276
+ * @param {Object|null} channelExt
277
+ * @returns {Promise<Object>}
278
+ */
279
+ async function runChannelProbe(ctx, channelExt) {
280
+ const health = await fetchDataplaneGeneralHealth(ctx.dataplaneUrl, { timeoutMs: 8000 });
281
+ const catalog = await fetchAssistantCatalogSummary(ctx.dataplaneUrl, ctx.authConfig);
282
+ const catalogOk = catalog.reachable;
283
+ const callbackOk = health?.status === 'healthy' || health?.status === 'ok';
284
+ const inbound = evaluateInboundVerification(channelExt);
285
+ const base = buildBaseProbeResult(health, callbackOk, catalogOk, channelExt, inbound);
286
+
287
+ if (channelExt?.type !== 'chatgpt' && channelExt?.type !== 'microsoftCopilot') {
288
+ return {
289
+ ...base,
290
+ passed: callbackOk && catalogOk && Boolean(channelExt?.credentialKey)
291
+ };
292
+ }
293
+
294
+ const channelTarget = channelExt?.type === 'chatgpt' ? 'chatgpt' : 'microsoft-copilot';
295
+ const layerBToolNames = await fetchRuntimeLayerBToolNames(
296
+ ctx.dataplaneUrl,
297
+ ctx.authConfig,
298
+ channelTarget
299
+ );
300
+
301
+ if (channelExt?.type === 'microsoftCopilot') {
302
+ const copilotProbe = await buildMicrosoftCopilotProbeDetails(
303
+ ctx,
304
+ channelExt,
305
+ catalogOk,
306
+ layerBToolNames
307
+ );
308
+ return buildCopilotProbeResult(
309
+ base,
310
+ { ...copilotProbe, discoveredLayerBToolNames: layerBToolNames },
311
+ channelExt
312
+ );
313
+ }
314
+
315
+ const chatgptProbe = await buildChatGptProbeDetails(
316
+ ctx,
317
+ channelExt,
318
+ catalogOk,
319
+ layerBToolNames,
320
+ catalog.items
321
+ );
322
+ return buildChatGptProbeResult(
323
+ base,
324
+ { ...chatgptProbe, discoveredLayerBToolNames: layerBToolNames },
325
+ channelExt
326
+ );
327
+ }
328
+
84
329
  module.exports = {
85
330
  isUrlReachable,
86
331
  isAssistantCatalogReachable,
332
+ extractAssistantCatalogItems,
333
+ fetchAssistantCatalogSummary,
87
334
  evaluateInboundVerification,
335
+ buildChatGptProbeDetails,
336
+ buildMicrosoftCopilotProbeDetails,
337
+ buildBaseProbeResult,
338
+ buildCopilotProbeResult,
339
+ buildChatGptProbeResult,
88
340
  runChannelProbe
89
341
  };
@@ -8,8 +8,17 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const crypto = require('crypto');
11
12
  const { getChannelTarget } = require('./channel-targets');
12
13
  const { normalizeSlackAppDescription } = require('./channel-description');
14
+ const {
15
+ CHATGPT_RUNTIME_SYSTEM_KEY,
16
+ CHATGPT_RUNTIME_TRANSPORT,
17
+ CHATGPT_RUNTIME_AUTH_MODE,
18
+ COPILOT_RUNTIME_SYSTEM_KEY,
19
+ COPILOT_RUNTIME_TRANSPORT,
20
+ COPILOT_RUNTIME_AUTH_MODE
21
+ } = require('./chatgpt-runtime-contract');
13
22
 
14
23
  /** Default Slack slash prefix — matches dataplane `templates/slack/command_catalog.yaml`. */
15
24
  const DEFAULT_SLACK_COMMAND_PREFIX = 'af';
@@ -62,13 +71,10 @@ function getEndpointPathSuffixes(channelTarget) {
62
71
  botConfigUrl: '/bot-config'
63
72
  },
64
73
  'microsoft-copilot': {
65
- openapiUrl: '/openapi.yaml',
66
- actionsBaseUrl: '/actions'
74
+ mcpUrl: '/mcp'
67
75
  },
68
76
  chatgpt: {
69
- openapiUrl: '/openapi.yaml',
70
- mcpUrl: '/mcp',
71
- actionsBaseUrl: '/actions'
77
+ mcpUrl: '/mcp'
72
78
  },
73
79
  'web-embed': {
74
80
  configUrl: '/config'
@@ -78,21 +84,12 @@ function getEndpointPathSuffixes(channelTarget) {
78
84
  }
79
85
 
80
86
  /**
81
- * Legacy / non-external-system paths (Copilot/ChatGPT until dataplane routes exist).
87
+ * Legacy / non-external-system paths where fallback is still allowed.
82
88
  * @param {string} channelTarget
83
89
  * @returns {Record<string, string>}
84
90
  */
85
91
  function getLegacyApiPaths(channelTarget) {
86
92
  const legacy = {
87
- 'microsoft-copilot': {
88
- openapiUrl: '/api/v1/copilot/openapi.yaml',
89
- actionsBaseUrl: '/api/v1/copilot/actions'
90
- },
91
- chatgpt: {
92
- openapiUrl: '/api/v1/chatgpt/openapi.yaml',
93
- mcpUrl: '/api/v1/chatgpt/mcp',
94
- actionsBaseUrl: '/api/v1/chatgpt/actions'
95
- },
96
93
  'web-embed': {
97
94
  configUrl: '/api/v1/web-embed/config'
98
95
  }
@@ -120,6 +117,101 @@ function resolveSlackOAuthManifestFields(oauthCallbackUrl) {
120
117
  };
121
118
  }
122
119
 
120
+ /**
121
+ * @param {Object} resolved
122
+ * @param {Object} params
123
+ */
124
+ function applyChatGptRuntimeValues(resolved, params) {
125
+ resolved.runtimeSystemKey = CHATGPT_RUNTIME_SYSTEM_KEY;
126
+ resolved.runtimeTransport = CHATGPT_RUNTIME_TRANSPORT;
127
+ resolved.runtimeAuthMode = CHATGPT_RUNTIME_AUTH_MODE;
128
+ resolved.layerBToolNames = Array.isArray(params.layerBToolNames) ? [...params.layerBToolNames] : [];
129
+ resolved.scopes = Array.isArray(params.scopes) ? [...params.scopes] : [];
130
+ resolved.scopesJson = JSON.stringify(resolved.scopes);
131
+ }
132
+
133
+ /**
134
+ * @param {string} seed
135
+ * @returns {string}
136
+ */
137
+ function buildDeterministicGuid(seed) {
138
+ const hex = crypto.createHash('sha256').update(String(seed || ''), 'utf8').digest('hex');
139
+ const bytes = hex.slice(0, 32).split('');
140
+ bytes[12] = '4';
141
+ bytes[16] = 'a';
142
+ return [
143
+ bytes.slice(0, 8).join(''),
144
+ bytes.slice(8, 12).join(''),
145
+ bytes.slice(12, 16).join(''),
146
+ bytes.slice(16, 20).join(''),
147
+ bytes.slice(20, 32).join('')
148
+ ].join('-');
149
+ }
150
+
151
+ /**
152
+ * @param {Object} resolved
153
+ * @param {Object} params
154
+ */
155
+ function applyMicrosoftCopilotRuntimeValues(resolved, params) {
156
+ resolved.runtimeSystemKey = COPILOT_RUNTIME_SYSTEM_KEY;
157
+ resolved.runtimeTransport = COPILOT_RUNTIME_TRANSPORT;
158
+ resolved.runtimeAuthMode = COPILOT_RUNTIME_AUTH_MODE;
159
+ resolved.layerBToolNames = Array.isArray(params.layerBToolNames) ? [...params.layerBToolNames] : [];
160
+ resolved.scopes = Array.isArray(params.scopes)
161
+ ? [...params.scopes]
162
+ : ['openid', 'profile', 'offline_access'];
163
+ resolved.scopesJson = JSON.stringify(resolved.scopes);
164
+ resolved.packageVersion = typeof params.packageVersion === 'string' ? params.packageVersion : '1.0.0';
165
+ resolved.packageAppId = buildDeterministicGuid(
166
+ `${resolved.channelTarget}:${resolved.baseName}:${resolved.environment}:app`
167
+ );
168
+ resolved.pluginId = buildDeterministicGuid(
169
+ `${resolved.channelTarget}:${resolved.baseName}:${resolved.environment}:plugin`
170
+ );
171
+ resolved.runtimeDomain = '';
172
+ try {
173
+ resolved.runtimeDomain = new URL(resolved.mcpUrl).hostname;
174
+ } catch {
175
+ try {
176
+ resolved.runtimeDomain = new URL(resolved.dataplaneBaseUrl).hostname;
177
+ } catch {
178
+ resolved.runtimeDomain = '';
179
+ }
180
+ }
181
+ }
182
+
183
+ /**
184
+ * @param {Object} resolved
185
+ * @param {string} base
186
+ * @param {string} systemPrefix
187
+ * @param {Record<string, string>} suffixes
188
+ * @param {Record<string, string>} legacy
189
+ */
190
+ function applyResolvedPaths(resolved, base, systemPrefix, suffixes, legacy) {
191
+ for (const [key, suffix] of Object.entries(suffixes)) {
192
+ resolved[key] = joinUrl(base, `${systemPrefix}${suffix}`);
193
+ }
194
+ for (const [key, legacyPath] of Object.entries(legacy)) {
195
+ if (!resolved[key]) {
196
+ resolved[key] = joinUrl(base, legacyPath);
197
+ }
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @param {Object} resolved
203
+ * @param {Object} params
204
+ */
205
+ function applySlackRuntimeValues(resolved, params) {
206
+ const oauthCallbackUrl = params.oauthCallbackUrl || resolved.oauthCallbackUrl;
207
+ Object.assign(resolved, resolveSlackOAuthManifestFields(oauthCallbackUrl));
208
+ const prefix =
209
+ typeof params.commandPrefix === 'string' && params.commandPrefix.trim()
210
+ ? params.commandPrefix.trim()
211
+ : DEFAULT_SLACK_COMMAND_PREFIX;
212
+ resolved.commandPrefix = prefix;
213
+ }
214
+
123
215
  /**
124
216
  * @param {Object} params
125
217
  * @param {string} params.dataplaneBaseUrl
@@ -158,24 +250,18 @@ function resolveChannelRuntimeValues(params) {
158
250
  scopes: []
159
251
  };
160
252
 
161
- for (const [key, suffix] of Object.entries(suffixes)) {
162
- resolved[key] = joinUrl(base, `${systemPrefix}${suffix}`);
253
+ applyResolvedPaths(resolved, base, systemPrefix, suffixes, legacy);
254
+
255
+ if (params.channelTarget === 'slack') {
256
+ applySlackRuntimeValues(resolved, params);
163
257
  }
164
258
 
165
- for (const [key, legacyPath] of Object.entries(legacy)) {
166
- if (!resolved[key]) {
167
- resolved[key] = joinUrl(base, legacyPath);
168
- }
259
+ if (params.channelTarget === 'microsoft-copilot') {
260
+ applyMicrosoftCopilotRuntimeValues(resolved, params);
169
261
  }
170
262
 
171
- if (params.channelTarget === 'slack') {
172
- const oauthCallbackUrl = params.oauthCallbackUrl || resolved.oauthCallbackUrl;
173
- Object.assign(resolved, resolveSlackOAuthManifestFields(oauthCallbackUrl));
174
- const prefix =
175
- typeof params.commandPrefix === 'string' && params.commandPrefix.trim()
176
- ? params.commandPrefix.trim()
177
- : DEFAULT_SLACK_COMMAND_PREFIX;
178
- resolved.commandPrefix = prefix;
263
+ if (params.channelTarget === 'chatgpt') {
264
+ applyChatGptRuntimeValues(resolved, params);
179
265
  }
180
266
 
181
267
  return resolved;
@@ -186,6 +272,11 @@ module.exports = {
186
272
  joinUrl,
187
273
  externalSystemsPrefix,
188
274
  getEndpointPathSuffixes,
275
+ buildDeterministicGuid,
276
+ applyResolvedPaths,
277
+ applySlackRuntimeValues,
278
+ applyMicrosoftCopilotRuntimeValues,
279
+ applyChatGptRuntimeValues,
189
280
  resolveSlackOAuthManifestFields,
190
281
  resolveChannelRuntimeValues,
191
282
  DEFAULT_SLACK_COMMAND_PREFIX
@@ -148,6 +148,50 @@ function displayValidationSection(validation) {
148
148
  if (validation.inboundVerification) {
149
149
  logger.log(formatSuccessLine('Inbound verification configured'));
150
150
  }
151
+ displayProbeTiers(validation.probeTiers);
152
+ displayRuntimeChecks(validation.runtimeChecks);
153
+ }
154
+
155
+ /**
156
+ * @param {Object|null} probeTiers
157
+ */
158
+ function displayProbeTiers(probeTiers) {
159
+ if (!probeTiers) return;
160
+ logger.log('');
161
+ logger.log(metadata('Probe tiers:'));
162
+ const rows = [];
163
+ if (Object.prototype.hasOwnProperty.call(probeTiers, 'builderValidated')) {
164
+ rows.push(['builderValidated', probeTiers.builderValidated, false]);
165
+ }
166
+ if (Object.prototype.hasOwnProperty.call(probeTiers, 'locallyValidated')) {
167
+ rows.push(['locallyValidated', probeTiers.locallyValidated, false]);
168
+ }
169
+ rows.push(['runtimeValidated', probeTiers.runtimeValidated, false]);
170
+ if (Object.prototype.hasOwnProperty.call(probeTiers, 'requiresMicrosoftAdmin')) {
171
+ rows.push(['requiresMicrosoftAdmin', probeTiers.requiresMicrosoftAdmin, true]);
172
+ }
173
+ if (Object.prototype.hasOwnProperty.call(probeTiers, 'requiresChatGptWorkspaceAdmin')) {
174
+ rows.push(['requiresChatGptWorkspaceAdmin', probeTiers.requiresChatGptWorkspaceAdmin, true]);
175
+ }
176
+ for (const [label, value, isPendingGate] of rows) {
177
+ if (isPendingGate) {
178
+ logger.log(value ? formatWarningLine(label) : formatSuccessLine(`${label} (cleared)`));
179
+ continue;
180
+ }
181
+ logger.log(value ? formatSuccessLine(label) : formatWarningLine(`${label} (pending or failed)`));
182
+ }
183
+ }
184
+
185
+ /**
186
+ * @param {Object|null} runtimeChecks
187
+ */
188
+ function displayRuntimeChecks(runtimeChecks) {
189
+ if (!runtimeChecks || typeof runtimeChecks !== 'object') return;
190
+ logger.log('');
191
+ logger.log(metadata('Runtime checks:'));
192
+ for (const [key, value] of Object.entries(runtimeChecks)) {
193
+ logger.log(value ? formatSuccessLine(key) : formatWarningLine(`${key} (failed)`));
194
+ }
151
195
  }
152
196
 
153
197
  /**