@aifabrix/builder 2.51.0 → 2.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-deploy-job.js +108 -0
  14. package/lib/app/run-helpers.js +10 -3
  15. package/lib/app/run.js +8 -0
  16. package/lib/channels/add-channel.js +54 -23
  17. package/lib/channels/approval-guides/chatgpt.js +8 -7
  18. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  19. package/lib/channels/channel-artifact-generator.js +14 -2
  20. package/lib/channels/channel-credential.js +3 -1
  21. package/lib/channels/channel-probe-evidence.js +79 -0
  22. package/lib/channels/channel-probe.js +265 -13
  23. package/lib/channels/channel-runtime-resolver.js +120 -29
  24. package/lib/channels/channel-status-display.js +44 -0
  25. package/lib/channels/channel-targets.js +7 -0
  26. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  27. package/lib/channels/microsoft-copilot-package.js +280 -0
  28. package/lib/channels/runtime-tool-catalog.js +165 -0
  29. package/lib/cli/index.js +2 -0
  30. package/lib/cli/setup-app.js +4 -0
  31. package/lib/cli/setup-external-system-help.js +8 -2
  32. package/lib/cli/setup-external-system.js +12 -0
  33. package/lib/commands/app-test.js +2 -14
  34. package/lib/commands/datasource-load-export-cli.js +6 -1
  35. package/lib/commands/deploy-gate-failure-details.js +19 -17
  36. package/lib/commands/dev-down.js +2 -6
  37. package/lib/commands/evidence-kind.js +65 -15
  38. package/lib/commands/governance-generate-external.js +56 -21
  39. package/lib/commands/lifecycle-external.js +10 -4
  40. package/lib/commands/repair-datasource-apply.js +10 -1
  41. package/lib/commands/repair-datasource-run.js +4 -2
  42. package/lib/commands/repair-datasource-test-payload.js +80 -19
  43. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  44. package/lib/commands/repair-internal.js +9 -1
  45. package/lib/commands/repair-openapi-sync.js +34 -17
  46. package/lib/commands/role-assistant.js +313 -0
  47. package/lib/commands/upload-data-sync-finalize.js +5 -4
  48. package/lib/commands/upload-sidecar-followups.js +144 -47
  49. package/lib/commands/upload.js +43 -6
  50. package/lib/commands/verify-governance-combined-external.js +73 -9
  51. package/lib/commands/verify-governance-db.js +80 -25
  52. package/lib/commands/verify-governance-external.js +29 -5
  53. package/lib/commands/wizard-core-helpers.js +7 -3
  54. package/lib/commands/wizard-core.js +78 -9
  55. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  56. package/lib/datasource/bulk-loader-service.js +5 -1
  57. package/lib/datasource/datasource-validate-summary.js +2 -2
  58. package/lib/datasource/validate.js +13 -6
  59. package/lib/external-system/deploy-helpers.js +35 -1
  60. package/lib/external-system/deploy.js +23 -10
  61. package/lib/external-system/download-helpers.js +111 -2
  62. package/lib/external-system/download-role-assistant.js +55 -0
  63. package/lib/external-system/download.js +45 -26
  64. package/lib/external-system/test-helpers.js +1 -1
  65. package/lib/external-system/test.js +39 -3
  66. package/lib/generator/builders.js +3 -0
  67. package/lib/generator/wizard.js +51 -4
  68. package/lib/lifecycle/access-control-abac.js +121 -0
  69. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  70. package/lib/lifecycle/product-model.js +3 -2
  71. package/lib/lifecycle/report-display.js +14 -2
  72. package/lib/programmatic/capability-registry.js +20 -1
  73. package/lib/programmatic/command-options.js +1 -0
  74. package/lib/programmatic/index.js +4 -0
  75. package/lib/programmatic/manifest-patch-section.js +48 -9
  76. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  77. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  78. package/lib/programmatic/openapi-component-schemas.js +16 -1
  79. package/lib/programmatic/openapi-description-enricher.js +3 -1
  80. package/lib/programmatic/openapi-descriptions.js +55 -5
  81. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  82. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  83. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  84. package/lib/programmatic/openapi-request-schemas.js +36 -1
  85. package/lib/programmatic/openapi-result-schemas.js +1 -1
  86. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  87. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  88. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  89. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  90. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  91. package/lib/programmatic/openapi-spec.js +47 -28
  92. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  93. package/lib/programmatic/request-timeout.js +3 -0
  94. package/lib/programmatic/route-handler-map.js +89 -2
  95. package/lib/programmatic/run-certify.js +15 -2
  96. package/lib/programmatic/run-deploy.js +74 -0
  97. package/lib/programmatic/run-evidence-authoring.js +80 -0
  98. package/lib/programmatic/run-protection.js +124 -0
  99. package/lib/programmatic/run-template-workspace.js +28 -1
  100. package/lib/programmatic/workspace-context.js +6 -0
  101. package/lib/protection/auth-context.js +48 -6
  102. package/lib/protection/paths.js +22 -0
  103. package/lib/protection/run-protection-create-helpers.js +22 -1
  104. package/lib/protection/run-protection-create.js +1 -1
  105. package/lib/protection/sync-after-upload.js +1 -1
  106. package/lib/role-assistant/evidence-sync.js +450 -0
  107. package/lib/role-assistant/github-workflow.js +96 -0
  108. package/lib/role-assistant/knowledge-sync.js +465 -0
  109. package/lib/role-assistant/settings.js +126 -0
  110. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  111. package/lib/role-assistant/test-cases-honesty.js +184 -0
  112. package/lib/role-assistant/test-cases.js +364 -0
  113. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  114. package/lib/role-assistant/test-runner-workhub.js +385 -0
  115. package/lib/role-assistant/test-runner.js +500 -0
  116. package/lib/schema/application-schema.json +16 -2
  117. package/lib/schema/deployment-rules.yaml +1 -0
  118. package/lib/schema/external-datasource.schema.json +38 -4
  119. package/lib/schema/external-system.schema.json +4 -4
  120. package/lib/schema/infra.parameter.yaml +20 -0
  121. package/lib/schema/role-assistant-settings.schema.json +108 -0
  122. package/lib/utils/api.js +52 -27
  123. package/lib/utils/app-path-resolve.js +72 -24
  124. package/lib/utils/authentication-shape.js +3 -1
  125. package/lib/utils/deployment-validation.js +28 -3
  126. package/lib/utils/docker-run-ephemeral.js +55 -0
  127. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  128. package/lib/utils/env-copy-keycloak.js +196 -0
  129. package/lib/utils/env-copy.js +34 -1
  130. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  131. package/lib/utils/help-builder.js +1 -0
  132. package/lib/utils/paths.js +67 -23
  133. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  134. package/lib/utils/role-assistant-paths.js +98 -0
  135. package/lib/utils/schema-loader.js +41 -0
  136. package/lib/utils/schema-resolver.js +36 -4
  137. package/lib/utils/token-manager.js +38 -29
  138. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  139. package/lib/validation/external-manifest-validator.js +31 -8
  140. package/lib/validation/validate-external-file.js +8 -1
  141. package/package.json +2 -1
  142. package/scripts/export-builder-api-evidence.js +1 -1
  143. package/templates/applications/dataplane/application.yaml +23 -15
  144. package/templates/applications/dataplane/env.template +1 -1
  145. package/templates/applications/dataplane/rbac.yaml +14 -0
  146. package/templates/applications/miso-controller/application.yaml +1 -1
  147. package/templates/applications/miso-controller/env.template +3 -1
  148. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  149. package/templates/channels/chatgpt/integration/README.md +10 -7
  150. package/templates/channels/chatgpt/integration/application.json +2 -2
  151. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  152. package/templates/channels/chatgpt/integration/env.template +1 -1
  153. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  154. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  155. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  156. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  157. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  158. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  159. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  160. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  161. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  162. /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
@@ -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
  /**
@@ -8,6 +8,11 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const {
12
+ CHATGPT_RUNTIME_SYSTEM_KEY,
13
+ COPILOT_RUNTIME_SYSTEM_KEY
14
+ } = require('./chatgpt-runtime-contract');
15
+
11
16
  /** @typedef {'teams-bot'|'slack'|'microsoft-copilot'|'chatgpt'|'web-embed'} ChannelTargetKey */
12
17
 
13
18
  const DEFAULT_CAPABILITIES = ['chat', 'notify', 'documentApproval'];
@@ -44,6 +49,7 @@ const CHANNEL_TARGETS = {
44
49
  artifactFileName: 'channel-microsoft-copilot.json',
45
50
  artifactFormat: 'json',
46
51
  credentialKey: 'microsoft-copilot-channel',
52
+ routeSystemKey: COPILOT_RUNTIME_SYSTEM_KEY,
47
53
  capabilities: [...DEFAULT_CAPABILITIES, 'enterpriseActions'],
48
54
  approvalRequired: true,
49
55
  experimental: false
@@ -55,6 +61,7 @@ const CHANNEL_TARGETS = {
55
61
  artifactFileName: 'channel-chatgpt.json',
56
62
  artifactFormat: 'json',
57
63
  credentialKey: 'chatgpt-channel',
64
+ routeSystemKey: CHATGPT_RUNTIME_SYSTEM_KEY,
58
65
  capabilities: ['chat', 'enterpriseActions'],
59
66
  approvalRequired: true,
60
67
  experimental: false
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ChatGPT custom MCP app runtime contract constants (plan 163).
3
+ *
4
+ * @fileoverview Runtime-orchestration MCP contract fields used by chatgpt channel templates and probes
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const RUNTIME_ORCHESTRATION_SYSTEM_KEY = 'runtime-orchestration';
12
+
13
+ const CHATGPT_RUNTIME_SYSTEM_KEY = RUNTIME_ORCHESTRATION_SYSTEM_KEY;
14
+ const CHATGPT_RUNTIME_TRANSPORT = 'streamable-http';
15
+ const CHATGPT_RUNTIME_AUTH_MODE = 'workspace-managed-oauth-or-api-key';
16
+
17
+ const COPILOT_RUNTIME_SYSTEM_KEY = RUNTIME_ORCHESTRATION_SYSTEM_KEY;
18
+ const COPILOT_RUNTIME_TRANSPORT = 'streamable-http';
19
+ const COPILOT_RUNTIME_AUTH_MODE = 'microsoft-entra-sso-or-oauth';
20
+
21
+ module.exports = {
22
+ RUNTIME_ORCHESTRATION_SYSTEM_KEY,
23
+ CHATGPT_RUNTIME_SYSTEM_KEY,
24
+ CHATGPT_RUNTIME_TRANSPORT,
25
+ CHATGPT_RUNTIME_AUTH_MODE,
26
+ COPILOT_RUNTIME_SYSTEM_KEY,
27
+ COPILOT_RUNTIME_TRANSPORT,
28
+ COPILOT_RUNTIME_AUTH_MODE
29
+ };