@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
@@ -67,6 +67,11 @@ const ISOLATED_SUITE_ENTRIES = [
67
67
  ['role-assistant-knowledge-sync', ['**/tests/lib/role-assistant/knowledge-sync.test.js']],
68
68
  ['role-assistant-evidence-sync', ['**/tests/lib/role-assistant/evidence-sync.test.js']],
69
69
  ['role-assistant-test-cases', ['**/tests/lib/role-assistant/test-cases.test.js']],
70
+ ['role-assistant-test-suite', ['**/tests/lib/role-assistant/test-suite.test.js']],
71
+ [
72
+ 'role-assistant-workhub-answers',
73
+ ['**/tests/lib/role-assistant/test-runner-workhub-answers.test.js']
74
+ ],
70
75
  ['governance-fixture-subjects', ['**/tests/lib/commands/governance-fixture-subjects.test.js']],
71
76
  ['enterprise-knowledge-generate-manifest', ['**/tests/lib/commands/enterprise-knowledge-generate-manifest.test.js']],
72
77
  ['plan-prevalidate-gates', ['**/tests/lib/plan-prevalidate-gates.test.js']],
@@ -152,7 +157,19 @@ const ISOLATED_SUITE_ENTRIES = [
152
157
  [
153
158
  '**/tests/lib/channels/channel-integration-generator.test.js',
154
159
  '**/tests/lib/channels/channel-artifact-generator.test.js',
155
- '**/tests/lib/channels/channel-integration-template.test.js'
160
+ '**/tests/lib/channels/channel-integration-template.test.js',
161
+ '**/tests/lib/channels/add-channel.test.js',
162
+ '**/tests/lib/channels/microsoft-copilot-package-quality.test.js',
163
+ '**/tests/lib/channels/channel-branding.test.js'
164
+ ]
165
+ ],
166
+ [
167
+ 'integration-definition-real-fs',
168
+ [
169
+ '**/tests/lib/validation/integration-definition-schema.test.js',
170
+ '**/tests/lib/integration-definition/apply.test.js',
171
+ '**/tests/lib/integration-definition/from-discovery.test.js',
172
+ '**/tests/lib/integration-definition/emit-suite-bid.test.js'
156
173
  ]
157
174
  ],
158
175
  [
@@ -175,6 +192,10 @@ const ISOLATED_SUITE_ENTRIES = [
175
192
  '**/tests/lib/scripts/sync-builder-api-template.test.js',
176
193
  '**/tests/lib/evidence/evidence-service.test.js'
177
194
  ]
195
+ ],
196
+ [
197
+ 'enrich-openapi-operations',
198
+ ['**/tests/lib/integration-definition/enrich-openapi-operations.test.js']
178
199
  ]
179
200
  ];
180
201
 
@@ -224,7 +245,9 @@ function buildDefaultTestPathIgnorePatterns(includeLocalTests) {
224
245
  '/tests/integration/',
225
246
  '\\\\tests\\\\integration\\\\',
226
247
  '/tests/manual/',
227
- '\\\\tests\\\\manual\\\\'
248
+ '\\\\tests\\\\manual\\\\',
249
+ '/builder/',
250
+ '\\\\builder\\\\'
228
251
  ];
229
252
  const globs = [
230
253
  ...ISOLATED_SUITE_ENTRIES.flatMap((entry) => entry[1]),
package/jest.projects.js CHANGED
@@ -52,7 +52,10 @@ const defaultProject = {
52
52
  ],
53
53
  testPathIgnorePatterns: buildDefaultTestPathIgnorePatterns(),
54
54
  modulePathIgnorePatterns: [
55
- '<rootDir>/docker-builder-api-staging/'
55
+ '<rootDir>/docker-builder-api-staging/',
56
+ // Generated app dirs under builder/ (except builder-api) — avoid haste/transform
57
+ // picking up junk written by generator/cli suites (e.g. builder/testapp).
58
+ '<rootDir>/builder/(?!builder-api(?:/|$))'
56
59
  ],
57
60
  setupFiles: ['<rootDir>/tests/capture-real-fs.js'],
58
61
  setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
@@ -69,28 +69,28 @@ async function detachPolicy(dataplaneUrl, sourceIdOrKey, policyIdOrKey, authConf
69
69
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
70
70
  return await client.delete(`/api/v1/external/${sourceIdOrKey}/policies/${policyIdOrKey}`);
71
71
  }
72
- /** @requiresPermission {Dataplane} external-system:read (sync) */
72
+ /** @requiresPermission {Dataplane} external-data-source:read (sync under /data-storage) */
73
73
  async function listSyncJobs(dataplaneUrl, sourceIdOrKey, authConfig, options = {}) {
74
74
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
75
- return await client.get(`/api/v1/external/${sourceIdOrKey}/sync`, { params: options });
75
+ return await client.get(`/api/v1/data-storage/${sourceIdOrKey}/sync`, { params: options });
76
76
  }
77
77
  async function createSyncJob(dataplaneUrl, sourceIdOrKey, authConfig, syncData) {
78
78
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
79
- return await client.post(`/api/v1/external/${sourceIdOrKey}/sync`, { body: syncData });
79
+ return await client.post(`/api/v1/data-storage/${sourceIdOrKey}/sync`, { body: syncData });
80
80
  }
81
- /** @requiresPermission {Dataplane} external-system:read */
81
+ /** @requiresPermission {Dataplane} external-data-source:read */
82
82
  async function getSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig) {
83
83
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
84
- return await client.get(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}`);
84
+ return await client.get(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}`);
85
85
  }
86
- /** @requiresPermission {Dataplane} external-system:update */
86
+ /** @requiresPermission {Dataplane} external-data-source:sync */
87
87
  async function updateSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig, updateData) {
88
88
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
89
- return await client.put(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}`, { body: updateData });
89
+ return await client.put(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}`, { body: updateData });
90
90
  }
91
91
  async function executeSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig) {
92
92
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
93
- return await client.post(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}/execute`);
93
+ return await client.post(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}/execute`);
94
94
  }
95
95
  /** @requiresPermission {Dataplane} external-data-source:sync */
96
96
  async function validateDocuments(dataplaneUrl, sourceIdOrKey, authConfig, validateData) {
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @fileoverview Keycloak status API (Controller) — Entra IdP ids for packaging
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { ApiClient } = require('./index');
10
+
11
+ const KEYCLOAK_STATUS_PATH = '/api/v1/keycloak/status';
12
+
13
+ /**
14
+ * @param {Object} response
15
+ * @returns {import('./types/keycloak.types').KeycloakStatus|null}
16
+ */
17
+ function extractKeycloakStatus(response) {
18
+ if (!response || typeof response !== 'object' || response.success === false) {
19
+ return null;
20
+ }
21
+ const body = response.data ?? response;
22
+ const data = body?.data !== undefined ? body.data : body;
23
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
24
+ return null;
25
+ }
26
+ return data;
27
+ }
28
+
29
+ /**
30
+ * @param {unknown} value
31
+ * @returns {string|null}
32
+ */
33
+ function trimNonEmptyString(value) {
34
+ if (typeof value !== 'string') {
35
+ return null;
36
+ }
37
+ const trimmed = value.trim();
38
+ return trimmed || null;
39
+ }
40
+
41
+ /**
42
+ * @param {import('./types/keycloak.types').KeycloakStatus|null} status
43
+ * @returns {import('./types/keycloak.types').KeycloakEntraIdentityProvider|null}
44
+ */
45
+ function extractEntraIdentityProvider(status) {
46
+ const raw = status?.entraIdentityProvider;
47
+ if (!raw || typeof raw !== 'object') {
48
+ return null;
49
+ }
50
+ const enterpriseAppId = trimNonEmptyString(raw.enterpriseAppId);
51
+ if (!enterpriseAppId) {
52
+ return null;
53
+ }
54
+ const tenantId = trimNonEmptyString(raw.tenantId);
55
+ return tenantId ? { enterpriseAppId, tenantId } : { enterpriseAppId };
56
+ }
57
+
58
+ /**
59
+ * GET /api/v1/keycloak/status
60
+ * @requiresPermission {Controller} admin:keycloak
61
+ * @async
62
+ * @param {string} controllerUrl - Miso Controller base URL
63
+ * @param {Object} authConfig - Authentication configuration (device Bearer)
64
+ * @returns {Promise<import('./types/keycloak.types').KeycloakStatus|null>}
65
+ */
66
+ async function getKeycloakStatus(controllerUrl, authConfig) {
67
+ const client = new ApiClient(controllerUrl, authConfig);
68
+ const response = await client.get(KEYCLOAK_STATUS_PATH);
69
+ return extractKeycloakStatus(response);
70
+ }
71
+
72
+ /**
73
+ * Resolve Entra SSO enterprise app id from Keycloak status, or null when missing.
74
+ * Does not invent ids.
75
+ * @requiresPermission {Controller} admin:keycloak
76
+ * @async
77
+ * @param {string} controllerUrl - Miso Controller base URL
78
+ * @param {Object} authConfig - Authentication configuration
79
+ * @returns {Promise<import('./types/keycloak.types').KeycloakEntraIdentityProvider|null>}
80
+ */
81
+ async function getKeycloakEntraIdentityProvider(controllerUrl, authConfig) {
82
+ const status = await getKeycloakStatus(controllerUrl, authConfig);
83
+ return extractEntraIdentityProvider(status);
84
+ }
85
+
86
+ module.exports = {
87
+ KEYCLOAK_STATUS_PATH,
88
+ getKeycloakStatus,
89
+ getKeycloakEntraIdentityProvider,
90
+ extractKeycloakStatus,
91
+ extractEntraIdentityProvider
92
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview Admin token providers API (Controller Trust sources)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { ApiClient } = require('./index');
10
+
11
+ const ADMIN_BASE = '/api/v1/admin/token-providers';
12
+ const MICROSOFT_ENTRA_ID_KEY = 'microsoft-entra-id';
13
+
14
+ /**
15
+ * @param {Object} response
16
+ * @returns {import('./types/token-providers.types').TokenProvider[]}
17
+ */
18
+ function extractTokenProviders(response) {
19
+ const body = response?.data ?? response;
20
+ const data = body?.data ?? body;
21
+ if (Array.isArray(data)) {
22
+ return data;
23
+ }
24
+ if (Array.isArray(data?.items)) {
25
+ return data.items;
26
+ }
27
+ return [];
28
+ }
29
+
30
+ /**
31
+ * List admin token providers (paginated).
32
+ * GET /api/v1/admin/token-providers
33
+ * @requiresPermission {Controller} token-provider:read
34
+ * @async
35
+ * @param {string} controllerUrl - Miso Controller base URL
36
+ * @param {Object} authConfig - Authentication configuration (device Bearer)
37
+ * @param {import('./types/token-providers.types').ListAdminTokenProvidersOptions} options
38
+ * @returns {Promise<Object>} Controller list response
39
+ * @throws {Error} If direction is missing or request fails
40
+ */
41
+ async function listAdminTokenProviders(controllerUrl, authConfig, options = {}) {
42
+ const direction = options?.direction;
43
+ if (direction !== 'inbound' && direction !== 'outbound') {
44
+ throw new Error('direction is required and must be "inbound" or "outbound"');
45
+ }
46
+ const client = new ApiClient(controllerUrl, authConfig);
47
+ const params = { ...options, direction };
48
+ return await client.get(ADMIN_BASE, { params });
49
+ }
50
+
51
+ /**
52
+ * Resolve inbound Microsoft Entra ID Trust source, or null when missing.
53
+ * Does not invent issuer, audience, or scopes.
54
+ * @requiresPermission {Controller} token-provider:read
55
+ * @async
56
+ * @param {string} controllerUrl - Miso Controller base URL
57
+ * @param {Object} authConfig - Authentication configuration
58
+ * @returns {Promise<import('./types/token-providers.types').TokenProvider|null>}
59
+ */
60
+ async function getInboundMicrosoftEntraIdProvider(controllerUrl, authConfig) {
61
+ const response = await listAdminTokenProviders(controllerUrl, authConfig, {
62
+ direction: 'inbound',
63
+ search: MICROSOFT_ENTRA_ID_KEY,
64
+ page: 1,
65
+ pageSize: 50
66
+ });
67
+ const items = extractTokenProviders(response);
68
+ const exact = items.find((item) => item && item.key === MICROSOFT_ENTRA_ID_KEY);
69
+ return exact || null;
70
+ }
71
+
72
+ module.exports = {
73
+ listAdminTokenProviders,
74
+ getInboundMicrosoftEntraIdProvider,
75
+ extractTokenProviders,
76
+ MICROSOFT_ENTRA_ID_KEY
77
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Keycloak status API type definitions (Controller)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ /**
8
+ * Entra identity provider fragment from Keycloak status.
9
+ * @typedef {Object} KeycloakEntraIdentityProvider
10
+ * @property {string} enterpriseAppId - Entra SSO application (client) id
11
+ * @property {string} [tenantId] - Entra tenant id when known
12
+ */
13
+
14
+ /**
15
+ * Keycloak configuration status payload (`data` of GET /api/v1/keycloak/status).
16
+ * @typedef {Object} KeycloakStatus
17
+ * @property {string} [realm]
18
+ * @property {string} [status]
19
+ * @property {string} [controllerClientId]
20
+ * @property {KeycloakEntraIdentityProvider} [entraIdentityProvider]
21
+ */
22
+
23
+ module.exports = {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Token provider API type definitions (Controller admin Trust sources)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ /**
8
+ * Token provider direction
9
+ * @typedef {'inbound'|'outbound'} TokenProviderDirection
10
+ */
11
+
12
+ /**
13
+ * Token provider subject type
14
+ * @typedef {'user'|'service'|'both'} TokenProviderSubjectType
15
+ */
16
+
17
+ /**
18
+ * Allowed operation on a token provider
19
+ * @typedef {'mcp.invoke'|'dataplane.read'|'dataplane.write'} TokenProviderAllowedOperation
20
+ */
21
+
22
+ /**
23
+ * Admin token provider list item (Controller TokenProvider schema)
24
+ * @typedef {Object} TokenProvider
25
+ * @property {string} id - Unique identifier (cuid)
26
+ * @property {string} key - Unique slug (e.g. microsoft-entra-id)
27
+ * @property {string} displayName - Human-readable name
28
+ * @property {string} issuer - JWT issuer URL (iss claim)
29
+ * @property {string} jwksUri - JWKS endpoint for key verification
30
+ * @property {string|null} [audience] - Expected audience claim (may be null)
31
+ * @property {TokenProviderSubjectType} subjectType - Subject type
32
+ * @property {TokenProviderDirection} direction - inbound or outbound
33
+ * @property {string|null} [identityClaim] - Claim used for user identity
34
+ * @property {string|null} [emailClaim] - Claim used for email
35
+ * @property {boolean} [autoProvision] - Whether to auto-create users
36
+ * @property {string|null} [serviceUserKey] - Service user key for service flow
37
+ * @property {TokenProviderAllowedOperation[]} [allowedOperations] - Allowed operations
38
+ * @property {string[]} [allowedScopes] - OAuth scopes (may be empty)
39
+ * @property {boolean} enabled - Whether this provider is enabled
40
+ * @property {string} [createdAt] - Created at
41
+ * @property {string} [updatedAt] - Updated at
42
+ */
43
+
44
+ /**
45
+ * List admin token providers query options
46
+ * @typedef {Object} ListAdminTokenProvidersOptions
47
+ * @property {TokenProviderDirection} direction - Required by OpenAPI
48
+ * @property {string} [search] - Search across key, displayName, issuer
49
+ * @property {number} [page] - Page number
50
+ * @property {number} [pageSize] - Page size
51
+ * @property {string} [sort] - Sort expression
52
+ * @property {string} [filter] - Filter expression
53
+ * @property {string} [enabled] - 'true' | 'false'
54
+ * @property {TokenProviderSubjectType} [subjectType] - Subject type filter
55
+ */
56
+
57
+ /**
58
+ * Paginated list response for admin token providers
59
+ * @typedef {Object} ListAdminTokenProvidersResponse
60
+ * @property {TokenProvider[]} data - Provider rows
61
+ * @property {Object} meta - Pagination meta
62
+ * @property {Object} [links] - Pagination links
63
+ */
64
+
65
+ module.exports = {};
package/lib/app/down.js CHANGED
@@ -111,6 +111,20 @@ async function stopAppContainers(appName, companionKeys, developerId) {
111
111
  }
112
112
  }
113
113
 
114
+ /**
115
+ * Resolve developer id for downApp (optional override does not change config).
116
+ * @param {Object} options
117
+ * @returns {Promise<string>}
118
+ */
119
+ async function resolveDownAppDeveloperId(options) {
120
+ const override = options && options.developerId;
121
+ if (override === undefined || override === null || override === '') {
122
+ return config.getDeveloperId();
123
+ }
124
+ const { validateAndNormalizeDeveloperId } = require('../core/config-normalize');
125
+ return validateAndNormalizeDeveloperId(override);
126
+ }
127
+
114
128
  /**
115
129
  * Stops and removes a specific application's container.
116
130
  * If --volumes is passed, attempts to remove the app's named Docker volume.
@@ -122,6 +136,7 @@ async function stopAppContainers(appName, companionKeys, developerId) {
122
136
  * @param {string} appName - Application name
123
137
  * @param {Object} options - Options
124
138
  * @param {boolean} [options.volumes=false] - Remove Docker volume data
139
+ * @param {string|number} [options.developerId] - Target developer for this stop only
125
140
  * @returns {Promise<void>} Resolves when operation completes
126
141
  * @throws {Error} If validation fails or Docker operations error (other than missing volume)
127
142
  *
@@ -136,7 +151,7 @@ async function downApp(appName, options = {}) {
136
151
  validateAppName(appName);
137
152
 
138
153
  const volumes = !!options.volumes;
139
- const developerId = await config.getDeveloperId();
154
+ const developerId = await resolveDownAppDeveloperId(options);
140
155
  const appConfig = loadBuilderApplicationConfig(appName);
141
156
  const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
142
157
 
@@ -141,6 +141,7 @@ async function startContainer(appName, composePath, port, appConfig = null, opts
141
141
  debug = false,
142
142
  runEnvPath = null,
143
143
  runEnvAdminPath = null,
144
+ ensureRunEnvFiles = null,
144
145
  runOptions = {},
145
146
  devMountPath = null,
146
147
  misoEnvironment = 'dev'
@@ -166,6 +167,9 @@ async function startContainer(appName, composePath, port, appConfig = null, opts
166
167
  }
167
168
 
168
169
  if (!usedDockerRunFallback) {
170
+ if (typeof ensureRunEnvFiles === 'function') {
171
+ await ensureRunEnvFiles();
172
+ }
169
173
  const env = await prepareContainerEnv(debug);
170
174
  await executeComposeUp(composeCmdBase, composePath, env, debug);
171
175
  }
@@ -11,11 +11,16 @@ const logger = require('../utils/logger');
11
11
  const {
12
12
  infoLine,
13
13
  formatSuccessLine,
14
- formatBlockingError
14
+ formatBlockingError,
15
+ metadata
15
16
  } = require('../utils/cli-test-layout-chalk');
16
17
  const { runDockerRunEphemeral } = require('../utils/docker-run-ephemeral');
17
18
  const { getInfraBridgeNetworkName } = require('../infrastructure/orphan-infra-docker-teardown');
18
19
 
20
+ /** Migrate status lines: `[system] head=… applied=3 pending=[]` */
21
+ const DEPLOY_JOB_STATUS_RE =
22
+ /^\[([^\]]+)\]\s+head=\S+\s+applied=(\d+)\s+pending=(\[[^\]]*\])/i;
23
+
19
24
  /**
20
25
  * Resolve trimmed deployJob from app config, or null if unset.
21
26
  * @param {Object} appConfig - Application configuration
@@ -39,6 +44,115 @@ async function resolveDeployJobNetwork() {
39
44
  return getInfraBridgeNetworkName(developerId);
40
45
  }
41
46
 
47
+ /**
48
+ * Quiet docker stdio by default (hide app bootstrap warnings). Full inherit only with --debug.
49
+ * @param {{ quiet?: boolean, debug?: boolean }} params
50
+ * @returns {boolean}
51
+ */
52
+ function shouldQuietDeployJob(params = {}) {
53
+ if (params.debug === true) {
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+
59
+ /**
60
+ * Parse migrate CLI status lines into totals (no per-chain head/pending dump).
61
+ * @param {string} output
62
+ * @returns {{ chains: number, applied: number, pending: number }|null}
63
+ */
64
+ function summarizeDeployJobMigrateOutput(output) {
65
+ if (typeof output !== 'string' || !output.trim()) {
66
+ return null;
67
+ }
68
+ let chains = 0;
69
+ let applied = 0;
70
+ let pending = 0;
71
+ for (const line of output.split(/\r?\n/)) {
72
+ const match = DEPLOY_JOB_STATUS_RE.exec(line.trim());
73
+ if (!match) {
74
+ continue;
75
+ }
76
+ chains += 1;
77
+ applied += Number.parseInt(match[2], 10) || 0;
78
+ const inner = match[3].slice(1, -1).trim();
79
+ if (inner) {
80
+ pending += inner.split(',').filter((part) => part.trim()).length;
81
+ }
82
+ }
83
+ if (chains === 0) {
84
+ return null;
85
+ }
86
+ return { chains, applied, pending };
87
+ }
88
+
89
+ /**
90
+ * One-line migrate totals from quiet deployJob output (replaces per-chain dump).
91
+ * @param {string} output
92
+ * @returns {string|null} Summary text for the completed line, or null
93
+ */
94
+ function formatDeployJobMigrateSummary(output) {
95
+ const summary = summarizeDeployJobMigrateOutput(output);
96
+ if (!summary) {
97
+ return null;
98
+ }
99
+ return `${summary.applied} applied, ${summary.pending} pending (${summary.chains} chains)`;
100
+ }
101
+
102
+ /**
103
+ * @deprecated Prefer formatDeployJobMigrateSummary — kept for tests.
104
+ * @param {string} output
105
+ */
106
+ function emitDeployJobSummaryLines(output) {
107
+ const text = formatDeployJobMigrateSummary(output);
108
+ if (text) {
109
+ logger.log(metadata(`DB migrate: ${text}`));
110
+ }
111
+ }
112
+
113
+ /**
114
+ * @param {string|null|undefined} devMountPath
115
+ * @returns {Array<{ host: string, container: string }>}
116
+ */
117
+ function _deployJobVolumeMounts(devMountPath) {
118
+ if (typeof devMountPath !== 'string' || !devMountPath.trim()) {
119
+ return [];
120
+ }
121
+ return [{ host: devMountPath.trim(), container: '/app' }];
122
+ }
123
+
124
+ /**
125
+ * Always announce the job; debug adds command/network/mount detail.
126
+ * @param {{ fullImage: string, cmd: string, network: string, volumeMounts: Array, debug: boolean }} params
127
+ */
128
+ function _logDeployJobStart(params) {
129
+ logger.log(infoLine(`Running build.deployJob once before start (${params.fullImage})…`));
130
+ if (!params.debug) {
131
+ return;
132
+ }
133
+ logger.log(infoLine(`deployJob: ${params.cmd}`));
134
+ logger.log(infoLine(`deployJob network: ${params.network}`));
135
+ if (params.volumeMounts.length) {
136
+ logger.log(infoLine(`deployJob mount: ${params.volumeMounts[0].host} -> /app`));
137
+ }
138
+ }
139
+
140
+ /**
141
+ * @param {{ result: any, quietJob: boolean }} params
142
+ * @throws {Error}
143
+ */
144
+ function _assertDeployJobExit(params) {
145
+ const code = params.quietJob ? params.result.code : params.result;
146
+ if (code === 0) {
147
+ return;
148
+ }
149
+ if (params.quietJob && params.result.output) {
150
+ logger.log(params.result.output.trimEnd());
151
+ }
152
+ logger.log(formatBlockingError(`build.deployJob failed with exit code ${code}`));
153
+ throw new Error(`build.deployJob failed with exit code ${code}`);
154
+ }
155
+
42
156
  /**
43
157
  * If build.deployJob is set, run it once in an ephemeral container (same image + env).
44
158
  * Fail-closed on non-zero exit. No-op when unset.
@@ -53,6 +167,7 @@ async function resolveDeployJobNetwork() {
53
167
  * @param {string|null} [params.runEnvPath] - Ephemeral .env.run path for --env-file
54
168
  * @param {string|null} [params.devMountPath] - Host path mounted at /app for --reload
55
169
  * @param {boolean} [params.debug]
170
+ * @param {boolean} [params.quiet] - Unused legacy flag; quiet is default unless debug
56
171
  * @returns {Promise<void>}
57
172
  * @throws {Error} If deployJob exits non-zero
58
173
  */
@@ -61,7 +176,8 @@ async function runDeployJobIfConfigured({
61
176
  fullImage,
62
177
  runEnvPath = null,
63
178
  devMountPath = null,
64
- debug = false
179
+ debug = false,
180
+ quiet = false
65
181
  }) {
66
182
  const cmd = resolveDeployJobCommand(appConfig);
67
183
  if (!cmd) {
@@ -72,37 +188,29 @@ async function runDeployJobIfConfigured({
72
188
  }
73
189
  const network = await resolveDeployJobNetwork();
74
190
  const volumeMounts = _deployJobVolumeMounts(devMountPath);
75
- logger.log(infoLine(`Running build.deployJob once before start (${fullImage})…`));
76
- if (debug) {
77
- logger.log(infoLine(`deployJob: ${cmd}`));
78
- logger.log(infoLine(`deployJob network: ${network}`));
79
- if (volumeMounts.length) {
80
- logger.log(infoLine(`deployJob mount: ${volumeMounts[0].host} -> /app`));
81
- }
82
- }
83
- const code = await runDockerRunEphemeral(fullImage, cmd, runEnvPath, {
191
+ const quietJob = shouldQuietDeployJob({ quiet, debug });
192
+ _logDeployJobStart({ fullImage, cmd, network, volumeMounts, debug });
193
+ const result = await runDockerRunEphemeral(fullImage, cmd, runEnvPath, {
84
194
  network,
85
- volumeMounts
195
+ volumeMounts,
196
+ quiet: quietJob
86
197
  });
87
- if (code !== 0) {
88
- logger.log(formatBlockingError(`build.deployJob failed with exit code ${code}`));
89
- throw new Error(`build.deployJob failed with exit code ${code}`);
198
+ _assertDeployJobExit({ result, quietJob });
199
+ if (quietJob && result.output) {
200
+ const migrateSummary = formatDeployJobMigrateSummary(result.output);
201
+ if (migrateSummary) {
202
+ logger.log(formatSuccessLine(`build.deployJob completed — ${migrateSummary}`));
203
+ return;
204
+ }
90
205
  }
91
206
  logger.log(formatSuccessLine('build.deployJob completed'));
92
207
  }
93
208
 
94
- /**
95
- * @param {string|null|undefined} devMountPath
96
- * @returns {Array<{ host: string, container: string }>}
97
- */
98
- function _deployJobVolumeMounts(devMountPath) {
99
- if (typeof devMountPath !== 'string' || !devMountPath.trim()) {
100
- return [];
101
- }
102
- return [{ host: devMountPath.trim(), container: '/app' }];
103
- }
104
-
105
209
  module.exports = {
106
210
  resolveDeployJobCommand,
107
- runDeployJobIfConfigured
211
+ runDeployJobIfConfigured,
212
+ shouldQuietDeployJob,
213
+ emitDeployJobSummaryLines,
214
+ formatDeployJobMigrateSummary,
215
+ summarizeDeployJobMigrateOutput
108
216
  };
@@ -315,6 +315,37 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId
315
315
  return { runEnvPath, runEnvAdminPath };
316
316
  }
317
317
 
318
+ /**
319
+ * Rebuild run env files when another use of the shared applications directory removed them.
320
+ * @async
321
+ * @param {string} appName - Application name
322
+ * @param {Object} appConfig - Application configuration
323
+ * @param {string} runEnvPath - Expected .env.run path
324
+ * @param {string} runEnvAdminPath - Expected .env.run.admin path
325
+ * @param {number|string} developerId - Developer ID
326
+ * @param {Object|null} scopeOpts - Environment-scoped run options
327
+ * @returns {Promise<void>}
328
+ */
329
+ async function ensureRunEnvFilesPresent(
330
+ appName,
331
+ appConfig,
332
+ runEnvPath,
333
+ runEnvAdminPath,
334
+ developerId,
335
+ scopeOpts = null
336
+ ) {
337
+ if (fsSync.existsSync(runEnvPath) && fsSync.existsSync(runEnvAdminPath)) {
338
+ return;
339
+ }
340
+ await buildMergedRunEnvAndWrite(
341
+ appName,
342
+ appConfig,
343
+ path.dirname(runEnvPath),
344
+ developerId,
345
+ scopeOpts
346
+ );
347
+ }
348
+
318
349
  /**
319
350
  * Assert generated compose does not contain password literals in environment (ISO 27K).
320
351
  * @param {string} composeContent - Generated docker-compose content
@@ -330,6 +361,7 @@ function assertNoPasswordLiteralsInCompose(composeContent) {
330
361
  module.exports = {
331
362
  cleanApplicationsDir,
332
363
  buildMergedRunEnvAndWrite,
364
+ ensureRunEnvFilesPresent,
333
365
  assertNoPasswordLiteralsInCompose,
334
366
  ensureRunSecretsForApp
335
367
  };