@aifabrix/builder 2.51.1 → 2.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * microsoft-copilot channel-add helpers (Entra SSO form context for operator errors).
3
+ *
4
+ * @fileoverview Keep add-channel.js under size limits
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { resolveChannelAuthContext } = require('./channel-auth-context');
12
+ const {
13
+ normalizeBaseChannelName,
14
+ resolveChannelDisplayName
15
+ } = require('./channel-display-name');
16
+ const { getDefaultChannelDescription } = require('./channel-targets');
17
+ const { resolveAppDescription } = require('./channel-description');
18
+ const { loadChannelPackageContext } = require('./channel-discovery');
19
+ const { getInboundMicrosoftEntraIdProvider } = require('../api/token-providers.api');
20
+ const { getKeycloakEntraIdentityProvider } = require('../api/keycloak.api');
21
+ const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
22
+ const { assertOauthPluginVaultReferenceId } = require('./microsoft-copilot-entra-sso');
23
+
24
+ /**
25
+ * @param {Object} options
26
+ * @returns {boolean}
27
+ */
28
+ function hasOauthPluginVaultReference(options) {
29
+ return (
30
+ typeof options.oauthPluginVaultReferenceId === 'string' &&
31
+ Boolean(options.oauthPluginVaultReferenceId.trim())
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Load Trust source + Keycloak Entra IdP for Copilot packaging (null on miss/error).
37
+ * @param {Object} ctx
38
+ * @returns {Promise<{ entraProvider: Object|null, entraIdentityProvider: Object|null }>}
39
+ */
40
+ async function loadMicrosoftCopilotEntraContext(ctx) {
41
+ let entraProvider = null;
42
+ let entraIdentityProvider = null;
43
+ if (!ctx?.controllerUrl) {
44
+ return { entraProvider, entraIdentityProvider };
45
+ }
46
+ try {
47
+ entraProvider = await getInboundMicrosoftEntraIdProvider(ctx.controllerUrl, ctx.authConfig);
48
+ } catch {
49
+ entraProvider = null;
50
+ }
51
+ try {
52
+ entraIdentityProvider = await getKeycloakEntraIdentityProvider(
53
+ ctx.controllerUrl,
54
+ ctx.authConfig
55
+ );
56
+ } catch {
57
+ entraIdentityProvider = null;
58
+ }
59
+ return { entraProvider, entraIdentityProvider };
60
+ }
61
+
62
+ /**
63
+ * @param {Object} params
64
+ * @returns {Promise<Object>}
65
+ */
66
+ async function resolveCopilotFormContextForError(params) {
67
+ const { options, ctx, name, baseName, description } = params;
68
+ let packageContext = null;
69
+ try {
70
+ packageContext = await loadChannelPackageContext({
71
+ dataplaneBaseUrl: ctx.dataplaneUrl,
72
+ authConfig: ctx.authConfig,
73
+ channelTarget: 'microsoft-copilot',
74
+ branding: { displayName: name, description }
75
+ });
76
+ } catch {
77
+ packageContext = null;
78
+ }
79
+ const { entraProvider, entraIdentityProvider } = await loadMicrosoftCopilotEntraContext(ctx);
80
+ return resolveChannelRuntimeValues({
81
+ dataplaneBaseUrl: ctx.dataplaneUrl,
82
+ channelTarget: 'microsoft-copilot',
83
+ appName: name,
84
+ baseName,
85
+ environment: ctx.environment,
86
+ appDescription: description,
87
+ packageContext,
88
+ entraProvider,
89
+ entraIdentityProvider,
90
+ oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId
91
+ });
92
+ }
93
+
94
+ /**
95
+ * @param {Object} params
96
+ * @throws {Error}
97
+ */
98
+ async function assertMicrosoftCopilotOauthReference(params) {
99
+ const { options } = params;
100
+ if (hasOauthPluginVaultReference(options)) {
101
+ return;
102
+ }
103
+ const formContext = await resolveCopilotFormContextForError(params);
104
+ assertOauthPluginVaultReferenceId({ ...options, formContext });
105
+ }
106
+
107
+ /**
108
+ * Fail before force upload when Teams Entra SSO auth-config id is missing.
109
+ * @param {Object} options
110
+ * @throws {Error}
111
+ */
112
+ async function assertMicrosoftCopilotOauthReferenceBeforeForce(options) {
113
+ if (hasOauthPluginVaultReference(options)) {
114
+ return;
115
+ }
116
+ const ctx = await resolveChannelAuthContext('microsoft-copilot', { silent: options.quiet });
117
+ const baseName = normalizeBaseChannelName(options.name);
118
+ const name = resolveChannelDisplayName(baseName, ctx.environment);
119
+ const rawDescription =
120
+ options.description && String(options.description).trim()
121
+ ? String(options.description).trim()
122
+ : getDefaultChannelDescription();
123
+ await assertMicrosoftCopilotOauthReference({
124
+ options,
125
+ ctx,
126
+ name,
127
+ baseName,
128
+ description: resolveAppDescription('microsoft-copilot', rawDescription)
129
+ });
130
+ }
131
+
132
+ module.exports = {
133
+ hasOauthPluginVaultReference,
134
+ loadMicrosoftCopilotEntraContext,
135
+ resolveCopilotFormContextForError,
136
+ assertMicrosoftCopilotOauthReference,
137
+ assertMicrosoftCopilotOauthReferenceBeforeForce
138
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Microsoft Copilot Entra SSO form field resolution + template rendering.
3
+ *
4
+ * @fileoverview Template-backed Teams Entra SSO registration guidance
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('../internal/fs-real-sync');
12
+ const path = require('path');
13
+ const Handlebars = require('handlebars');
14
+ const { BUILDER_ROOT } = require('./channel-integration-template');
15
+
16
+ const ENTRA_SSO_REGISTRATION_URL =
17
+ 'https://dev.teams.microsoft.com/tools/entra-configuration';
18
+ const TEMPLATES_DIR = path.join(BUILDER_ROOT, 'templates', 'channels', 'microsoft-copilot');
19
+
20
+ /**
21
+ * @param {string} issuer
22
+ * @returns {string|null}
23
+ */
24
+ function extractTenantIdFromIssuer(issuer) {
25
+ if (typeof issuer !== 'string' || !issuer.trim()) {
26
+ return null;
27
+ }
28
+ const match = issuer.match(
29
+ /login\.microsoftonline\.com\/([0-9a-fA-F-]{36})(?:\/|$)/i
30
+ );
31
+ return match ? match[1] : null;
32
+ }
33
+
34
+ /**
35
+ * @param {string} [url]
36
+ * @returns {string|null}
37
+ */
38
+ function originFromUrl(url) {
39
+ if (typeof url !== 'string' || !url.trim()) {
40
+ return null;
41
+ }
42
+ try {
43
+ const parsed = new URL(url.trim());
44
+ return `${parsed.protocol}//${parsed.host}`;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Prefer Keycloak status enterpriseAppId (Entra SSO application/client id).
52
+ * @param {Object|null} entraIdentityProvider
53
+ * @param {Object|null} entraProvider
54
+ * @param {Object|null} auth
55
+ * @returns {string|null}
56
+ */
57
+ function resolveEntraSsoClientId(entraIdentityProvider, entraProvider, auth) {
58
+ if (
59
+ entraIdentityProvider &&
60
+ typeof entraIdentityProvider.enterpriseAppId === 'string' &&
61
+ entraIdentityProvider.enterpriseAppId.trim()
62
+ ) {
63
+ return entraIdentityProvider.enterpriseAppId.trim();
64
+ }
65
+ if (entraProvider && typeof entraProvider.clientId === 'string' && entraProvider.clientId.trim()) {
66
+ return entraProvider.clientId.trim();
67
+ }
68
+ if (auth && typeof auth.clientId === 'string' && auth.clientId.trim()) {
69
+ return auth.clientId.trim();
70
+ }
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * @param {Object} resolved
76
+ * @param {Object|null} entraIdentityProvider
77
+ * @param {Object|null} entraProvider
78
+ * @param {Object|null} auth
79
+ * @returns {string|null}
80
+ */
81
+ function resolveEntraTenantId(resolved, entraIdentityProvider, entraProvider, auth) {
82
+ if (
83
+ entraIdentityProvider &&
84
+ typeof entraIdentityProvider.tenantId === 'string' &&
85
+ entraIdentityProvider.tenantId.trim()
86
+ ) {
87
+ return entraIdentityProvider.tenantId.trim();
88
+ }
89
+ return extractTenantIdFromIssuer(
90
+ resolved.entraIssuer || entraProvider?.issuer || auth?.issuer || null
91
+ );
92
+ }
93
+
94
+ /**
95
+ * @param {Object} resolved
96
+ * @param {Object} [params]
97
+ * @returns {void}
98
+ */
99
+ function applyCopilotEntraSsoFormFields(resolved, params = {}) {
100
+ const entraProvider = params.entraProvider || null;
101
+ const entraIdentityProvider = params.entraIdentityProvider || null;
102
+ const auth = resolved.discoveryAuthentication || null;
103
+ resolved.entraSsoRegistrationUrl = ENTRA_SSO_REGISTRATION_URL;
104
+ resolved.entraSsoBaseUrl =
105
+ originFromUrl(resolved.dataplaneBaseUrl) || originFromUrl(resolved.mcpUrl) || null;
106
+ resolved.entraSsoClientId = resolveEntraSsoClientId(
107
+ entraIdentityProvider,
108
+ entraProvider,
109
+ auth
110
+ );
111
+ const scopes = Array.isArray(resolved.scopes) ? resolved.scopes : [];
112
+ resolved.entraSsoScope = scopes.length > 0 ? scopes.join(', ') : null;
113
+ resolved.entraTenantId = resolveEntraTenantId(
114
+ resolved,
115
+ entraIdentityProvider,
116
+ entraProvider,
117
+ auth
118
+ );
119
+ if (entraIdentityProvider?.enterpriseAppId) {
120
+ resolved.entraEnterpriseAppId = String(entraIdentityProvider.enterpriseAppId).trim();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * @param {string} fileName
126
+ * @returns {string}
127
+ */
128
+ function readCopilotTemplate(fileName) {
129
+ const templatePath = path.join(TEMPLATES_DIR, fileName);
130
+ if (!fs.existsSync(templatePath)) {
131
+ throw new Error(`Microsoft Copilot template not found: ${templatePath}`);
132
+ }
133
+ return fs.readFileSync(templatePath, 'utf8');
134
+ }
135
+
136
+ /**
137
+ * @param {string} fileName
138
+ * @param {Object} context
139
+ * @returns {string}
140
+ */
141
+ function renderCopilotChannelTemplate(fileName, context) {
142
+ const hb = Handlebars.create();
143
+ hb.registerPartial('entraSsoRegistration', readCopilotTemplate('entra-sso-registration.template.md.hbs'));
144
+ const template = hb.compile(readCopilotTemplate(fileName));
145
+ return template(context).trim();
146
+ }
147
+
148
+ /**
149
+ * Operator-facing error when Teams Entra SSO auth-config id is missing.
150
+ * Prose lives in templates — not inline CLI copy.
151
+ * @param {Object|string} [context]
152
+ * @returns {string}
153
+ */
154
+ function formatOauthPluginVaultReferenceRequiredError(context = {}) {
155
+ const values = typeof context === 'string' ? { appName: context, baseName: context } : context || {};
156
+ const appName =
157
+ (typeof values.appName === 'string' && values.appName.trim()) ||
158
+ (typeof values.baseName === 'string' && values.baseName.trim()) ||
159
+ 'Your Assistant';
160
+ const baseName =
161
+ (typeof values.baseName === 'string' && values.baseName.trim()) || appName;
162
+ return renderCopilotChannelTemplate('oauth-plugin-vault-required.template.md.hbs', {
163
+ ...values,
164
+ appName,
165
+ baseName,
166
+ channelTarget: 'microsoft-copilot',
167
+ entraSsoRegistrationUrl: values.entraSsoRegistrationUrl || ENTRA_SSO_REGISTRATION_URL
168
+ });
169
+ }
170
+
171
+ module.exports = {
172
+ ENTRA_SSO_REGISTRATION_URL,
173
+ extractTenantIdFromIssuer,
174
+ originFromUrl,
175
+ resolveEntraSsoClientId,
176
+ resolveEntraTenantId,
177
+ applyCopilotEntraSsoFormFields,
178
+ renderCopilotChannelTemplate,
179
+ formatOauthPluginVaultReferenceRequiredError
180
+ };
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Microsoft Copilot Entra SSO / trustedIssuer packaging helpers (plan 169.0).
3
+ *
4
+ * @fileoverview Readiness, scopes, and auth selection for microsoft-copilot
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const TRUSTED_ISSUER = 'trustedIssuer';
12
+ const CUSTOMER_OAUTH = 'customerOAuth';
13
+ const OPENID_ONLY = new Set(['openid', 'profile', 'offline_access']);
14
+
15
+ /**
16
+ * @param {*} value
17
+ * @returns {boolean}
18
+ */
19
+ function isPresent(value) {
20
+ return value !== undefined && value !== null && value !== '';
21
+ }
22
+
23
+ /**
24
+ * @param {string} channelTarget
25
+ * @returns {boolean}
26
+ */
27
+ function skipsChannelOauthCredential(channelTarget) {
28
+ return channelTarget === 'microsoft-copilot';
29
+ }
30
+
31
+ /**
32
+ * @param {unknown} scopes
33
+ * @returns {string[]}
34
+ */
35
+ function normalizeScopeList(scopes) {
36
+ if (!Array.isArray(scopes)) {
37
+ return [];
38
+ }
39
+ return scopes.map((s) => String(s).trim()).filter(Boolean);
40
+ }
41
+
42
+ /**
43
+ * True when scopes are empty or only OIDC login scopes (insufficient for Runtime MCP).
44
+ * @param {string[]} scopes
45
+ * @returns {boolean}
46
+ */
47
+ function isOpenIdOnlyOrEmptyScopes(scopes) {
48
+ const list = normalizeScopeList(scopes);
49
+ if (list.length === 0) {
50
+ return true;
51
+ }
52
+ return list.every((scope) => OPENID_ONLY.has(scope));
53
+ }
54
+
55
+ /**
56
+ * @param {Object|null|undefined} auth
57
+ * @returns {string|null}
58
+ */
59
+ function resolveRecommendedCapability(auth) {
60
+ const value = auth && typeof auth.recommendedCapability === 'string'
61
+ ? auth.recommendedCapability.trim()
62
+ : '';
63
+ return value || null;
64
+ }
65
+
66
+ /**
67
+ * Map adapter auth for Copilot packaging: keep trustedIssuer public fields;
68
+ * drop Keycloak/customerOAuth AS metadata from the package surface.
69
+ * @param {Object|null} auth
70
+ * @returns {Object|null}
71
+ */
72
+ function mapCopilotPackageAuthentication(auth) {
73
+ if (!auth || typeof auth !== 'object') {
74
+ return null;
75
+ }
76
+ const recommended = resolveRecommendedCapability(auth);
77
+ const base = {
78
+ recommendedCapability: recommended,
79
+ issuer: auth.issuer || null,
80
+ openidConfigurationUrl: auth.openidConfigurationUrl || null,
81
+ protectedResourceMetadataUrl: auth.protectedResourceMetadataUrl || null,
82
+ resource: auth.resource || auth.apiResource || null,
83
+ operatorNote: auth.operatorNote || null,
84
+ pkceRequired: auth.pkceRequired === true,
85
+ audience: isPresent(auth.audience) ? auth.audience : null,
86
+ scopes: normalizeScopeList(auth.scopes || auth.allowedScopes)
87
+ };
88
+ if (recommended === TRUSTED_ISSUER) {
89
+ return {
90
+ ...base,
91
+ authorizationServerMetadataUrl: null,
92
+ clientRegistrationModes: []
93
+ };
94
+ }
95
+ return {
96
+ ...base,
97
+ authorizationServerMetadataUrl: auth.authorizationServerMetadataUrl || null,
98
+ clientRegistrationModes: Array.isArray(auth.clientRegistrationModes)
99
+ ? [...auth.clientRegistrationModes]
100
+ : []
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Resolve Runtime MCP Entra scopes — never default to openid/profile/offline_access.
106
+ * @param {Object} params
107
+ * @param {string[]} [params.explicitScopes]
108
+ * @param {Object|null} [params.packageContext]
109
+ * @param {Object|null} [params.entraProvider]
110
+ * @returns {string[]}
111
+ */
112
+ function resolveCopilotEntraScopes(params) {
113
+ const explicit = normalizeScopeList(params.explicitScopes);
114
+ if (explicit.length > 0 && !isOpenIdOnlyOrEmptyScopes(explicit)) {
115
+ return explicit;
116
+ }
117
+ const fromProvider = normalizeScopeList(params.entraProvider?.allowedScopes);
118
+ if (fromProvider.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromProvider)) {
119
+ return fromProvider;
120
+ }
121
+ const auth = params.packageContext?.authentication || null;
122
+ const fromAuth = normalizeScopeList(auth?.scopes || auth?.allowedScopes);
123
+ if (fromAuth.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromAuth)) {
124
+ return fromAuth;
125
+ }
126
+ const resource = typeof auth?.resource === 'string' ? auth.resource.trim() : '';
127
+ if (resource.startsWith('api://')) {
128
+ return [resource];
129
+ }
130
+ return [];
131
+ }
132
+
133
+ /**
134
+ * @param {Object} params
135
+ * @returns {string|null}
136
+ */
137
+ function firstCopilotEntraBlocker(params) {
138
+ const {
139
+ discoveryReady,
140
+ discoveryBlocker,
141
+ recommended,
142
+ referenceId,
143
+ issuer,
144
+ audience,
145
+ scopes
146
+ } = params;
147
+ if (!discoveryReady) {
148
+ return discoveryBlocker || 'remoteMcpUnavailable';
149
+ }
150
+ if (recommended !== TRUSTED_ISSUER) {
151
+ return recommended === CUSTOMER_OAUTH
152
+ ? 'customerOAuthNotAllowed'
153
+ : 'trustedIssuerRequired';
154
+ }
155
+ if (!referenceId) {
156
+ return 'oauthPluginVaultReferenceRequired';
157
+ }
158
+ if (!issuer) {
159
+ return 'entraIssuerRequired';
160
+ }
161
+ if (!isPresent(audience)) {
162
+ return 'runtimeMcpAudienceRequired';
163
+ }
164
+ if (isOpenIdOnlyOrEmptyScopes(scopes)) {
165
+ return 'runtimeMcpScopeRequired';
166
+ }
167
+ return null;
168
+ }
169
+
170
+ /**
171
+ * @param {Object|null} auth
172
+ * @param {Object|null} entraProvider
173
+ * @returns {{ audience: * , issuer: string|null }}
174
+ */
175
+ function resolveAudienceAndIssuer(auth, entraProvider) {
176
+ const audience = isPresent(entraProvider?.audience)
177
+ ? entraProvider.audience
178
+ : isPresent(auth?.audience)
179
+ ? auth.audience
180
+ : null;
181
+ const issuer =
182
+ (entraProvider && entraProvider.issuer) || (auth && auth.issuer) || null;
183
+ return { audience, issuer };
184
+ }
185
+
186
+ /**
187
+ * @param {Object} params
188
+ * @returns {{ installReady: boolean, blocker: string|null, packageAuthentication: Object|null }}
189
+ */
190
+ function evaluateCopilotEntraSsoReadiness(params) {
191
+ const packageContext = params.packageContext || null;
192
+ const auth = mapCopilotPackageAuthentication(packageContext?.authentication || null);
193
+ const recommended = resolveRecommendedCapability(auth);
194
+ const referenceId =
195
+ typeof params.oauthPluginVaultReferenceId === 'string'
196
+ ? params.oauthPluginVaultReferenceId.trim()
197
+ : '';
198
+ const scopes = normalizeScopeList(params.scopes);
199
+ const { audience, issuer } = resolveAudienceAndIssuer(auth, params.entraProvider || null);
200
+
201
+ if (auth) {
202
+ auth.issuer = issuer;
203
+ auth.audience = audience;
204
+ auth.scopes = scopes;
205
+ }
206
+
207
+ const blocker = firstCopilotEntraBlocker({
208
+ discoveryReady: packageContext?.installReady === true,
209
+ discoveryBlocker: packageContext?.blocker || null,
210
+ recommended,
211
+ referenceId,
212
+ issuer,
213
+ audience,
214
+ scopes
215
+ });
216
+ return {
217
+ installReady: blocker === null,
218
+ blocker,
219
+ packageAuthentication: auth
220
+ };
221
+ }
222
+
223
+ const {
224
+ formatOauthPluginVaultReferenceRequiredError
225
+ } = require('./microsoft-copilot-entra-sso-form');
226
+
227
+ /**
228
+ * @param {Object} [options]
229
+ * @throws {Error}
230
+ */
231
+ function assertOauthPluginVaultReferenceId(options = {}) {
232
+ const ref = options.oauthPluginVaultReferenceId;
233
+ if (typeof ref === 'string' && ref.trim()) {
234
+ return;
235
+ }
236
+ throw new Error(formatOauthPluginVaultReferenceRequiredError(options.formContext || options));
237
+ }
238
+
239
+ /**
240
+ * @param {Object} [runtimeValues]
241
+ * @throws {Error}
242
+ */
243
+ function assertCopilotPackageOauthPluginVaultReference(runtimeValues = {}) {
244
+ const ref = runtimeValues.oauthPluginVaultReferenceId;
245
+ if (typeof ref === 'string' && ref.trim()) {
246
+ return;
247
+ }
248
+ throw new Error(formatOauthPluginVaultReferenceRequiredError(runtimeValues));
249
+ }
250
+
251
+ module.exports = {
252
+ TRUSTED_ISSUER,
253
+ CUSTOMER_OAUTH,
254
+ skipsChannelOauthCredential,
255
+ normalizeScopeList,
256
+ isOpenIdOnlyOrEmptyScopes,
257
+ resolveRecommendedCapability,
258
+ mapCopilotPackageAuthentication,
259
+ resolveCopilotEntraScopes,
260
+ evaluateCopilotEntraSsoReadiness,
261
+ formatOauthPluginVaultReferenceRequiredError,
262
+ assertOauthPluginVaultReferenceId,
263
+ assertCopilotPackageOauthPluginVaultReference
264
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Valid Microsoft Teams / Copilot package icons (192×192 color, 32×32 outline).
3
+ *
4
+ * Defaults from templates/channels/brand (plan 167.0):
5
+ * - color-192.png ← light/192 (color app icon)
6
+ * - outline-32.png ← mono-white/32 (Teams outline)
7
+ * Variant packs: light/, dark/, mono-white/ at 32–512.
8
+ *
9
+ * @fileoverview Loads AI Fabrix brand PNGs from templates/channels/brand
10
+ * @author AI Fabrix Team
11
+ * @version 2.1.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const path = require('path');
17
+ const fs = require('../internal/fs-real-sync');
18
+ const { BUILDER_ROOT } = require('./channel-integration-template');
19
+
20
+ const BRAND_DIR = path.join(BUILDER_ROOT, 'templates', 'channels', 'brand');
21
+ const DEFAULT_COLOR_PATH = path.join(BRAND_DIR, 'color-192.png');
22
+ const DEFAULT_OUTLINE_PATH = path.join(BRAND_DIR, 'outline-32.png');
23
+
24
+ /**
25
+ * @param {string} filePath
26
+ * @returns {Buffer}
27
+ */
28
+ function readPngFile(filePath) {
29
+ if (!fs.existsSync(filePath)) {
30
+ throw new Error(`Brand icon not found: ${filePath}`);
31
+ }
32
+ return fs.readFileSync(filePath);
33
+ }
34
+
35
+ /**
36
+ * @param {{ colorPath?: string, outlinePath?: string }} [options]
37
+ * @returns {{ color: Buffer, outline: Buffer }}
38
+ */
39
+ function getMicrosoftCopilotIcons(options = {}) {
40
+ const colorPath =
41
+ typeof options.colorPath === 'string' && options.colorPath.trim()
42
+ ? options.colorPath.trim()
43
+ : DEFAULT_COLOR_PATH;
44
+ const outlinePath =
45
+ typeof options.outlinePath === 'string' && options.outlinePath.trim()
46
+ ? options.outlinePath.trim()
47
+ : DEFAULT_OUTLINE_PATH;
48
+ return {
49
+ color: readPngFile(colorPath),
50
+ outline: readPngFile(outlinePath)
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Read PNG IHDR width/height without external deps.
56
+ * @param {Buffer} buf
57
+ * @returns {{ width: number, height: number } | null}
58
+ */
59
+ function readPngDimensions(buf) {
60
+ if (!Buffer.isBuffer(buf) || buf.length < 24) return null;
61
+ if (buf.toString('hex', 0, 8) !== '89504e470d0a1a0a') return null;
62
+ return {
63
+ width: buf.readUInt32BE(16),
64
+ height: buf.readUInt32BE(20)
65
+ };
66
+ }
67
+
68
+ module.exports = {
69
+ getMicrosoftCopilotIcons,
70
+ readPngDimensions,
71
+ BRAND_DIR,
72
+ DEFAULT_COLOR_PATH,
73
+ DEFAULT_OUTLINE_PATH
74
+ };