@aifabrix/builder 2.51.0 → 2.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-container-start.js +4 -0
  14. package/lib/app/run-deploy-job.js +216 -0
  15. package/lib/app/run-env-compose.js +32 -0
  16. package/lib/app/run-env-recovery.js +35 -0
  17. package/lib/app/run-helpers.js +15 -9
  18. package/lib/app/run.js +18 -2
  19. package/lib/channels/add-channel.js +54 -23
  20. package/lib/channels/approval-guides/chatgpt.js +13 -9
  21. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  22. package/lib/channels/channel-artifact-generator.js +14 -2
  23. package/lib/channels/channel-credential.js +3 -1
  24. package/lib/channels/channel-probe-evidence.js +79 -0
  25. package/lib/channels/channel-probe.js +265 -13
  26. package/lib/channels/channel-runtime-resolver.js +120 -29
  27. package/lib/channels/channel-status-display.js +44 -0
  28. package/lib/channels/channel-targets.js +25 -1
  29. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  30. package/lib/channels/microsoft-copilot-package.js +280 -0
  31. package/lib/channels/runtime-tool-catalog.js +165 -0
  32. package/lib/cli/index.js +2 -0
  33. package/lib/cli/infra-guided-footers.js +1 -1
  34. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  35. package/lib/cli/infra-guided.js +65 -56
  36. package/lib/cli/setup-app.help.js +4 -0
  37. package/lib/cli/setup-app.js +4 -0
  38. package/lib/cli/setup-app.test-commands.js +59 -28
  39. package/lib/cli/setup-external-system-help.js +8 -2
  40. package/lib/cli/setup-external-system.js +62 -12
  41. package/lib/commands/app-test.js +2 -14
  42. package/lib/commands/channel-add-setup.js +8 -2
  43. package/lib/commands/channel.js +16 -5
  44. package/lib/commands/datasource-load-export-cli.js +6 -1
  45. package/lib/commands/deploy-gate-failure-details.js +19 -17
  46. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  47. package/lib/commands/dev-down.js +2 -6
  48. package/lib/commands/evidence-kind.js +65 -15
  49. package/lib/commands/governance-generate-external.js +56 -21
  50. package/lib/commands/governance-verify-external.js +2 -5
  51. package/lib/commands/lifecycle-external.js +21 -8
  52. package/lib/commands/repair-datasource-apply.js +10 -1
  53. package/lib/commands/repair-datasource-run.js +4 -2
  54. package/lib/commands/repair-datasource-test-payload.js +80 -19
  55. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  56. package/lib/commands/repair-internal.js +9 -1
  57. package/lib/commands/repair-openapi-sync.js +34 -17
  58. package/lib/commands/role-assistant.js +371 -0
  59. package/lib/commands/test-governance-external.js +2 -5
  60. package/lib/commands/up-miso.js +61 -24
  61. package/lib/commands/upload-data-sync-finalize.js +25 -7
  62. package/lib/commands/upload-sidecar-followups.js +144 -47
  63. package/lib/commands/upload.js +43 -6
  64. package/lib/commands/verify-governance-combined-external.js +73 -9
  65. package/lib/commands/verify-governance-db.js +80 -25
  66. package/lib/commands/verify-governance-external.js +31 -10
  67. package/lib/commands/verify-operations-external.js +2 -5
  68. package/lib/commands/verify-operations-step-plan.js +31 -0
  69. package/lib/commands/verify-trust-command-action.js +2 -5
  70. package/lib/commands/wizard-core-helpers.js +7 -3
  71. package/lib/commands/wizard-core.js +78 -9
  72. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  73. package/lib/datasource/bulk-loader-service.js +5 -1
  74. package/lib/datasource/datasource-identifier.js +4 -3
  75. package/lib/datasource/datasource-validate-summary.js +2 -2
  76. package/lib/datasource/integration-context.js +3 -3
  77. package/lib/datasource/resolve-app.js +90 -8
  78. package/lib/datasource/test-e2e.js +2 -2
  79. package/lib/datasource/unified-validation-run-body.js +15 -1
  80. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  81. package/lib/datasource/validate.js +13 -6
  82. package/lib/external-system/deploy-helpers.js +39 -1
  83. package/lib/external-system/deploy.js +25 -11
  84. package/lib/external-system/download-helpers.js +111 -2
  85. package/lib/external-system/download-role-assistant.js +55 -0
  86. package/lib/external-system/download.js +45 -26
  87. package/lib/external-system/test-helpers.js +1 -1
  88. package/lib/external-system/test-system-level.js +21 -3
  89. package/lib/external-system/test.js +43 -7
  90. package/lib/generator/builders.js +12 -15
  91. package/lib/generator/wizard.js +51 -4
  92. package/lib/lifecycle/access-control-abac.js +121 -0
  93. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  94. package/lib/lifecycle/product-model.js +3 -2
  95. package/lib/lifecycle/report-display.js +14 -2
  96. package/lib/programmatic/capability-registry.js +20 -1
  97. package/lib/programmatic/command-options.js +1 -0
  98. package/lib/programmatic/index.js +4 -0
  99. package/lib/programmatic/manifest-patch-section.js +48 -9
  100. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  101. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  102. package/lib/programmatic/openapi-component-schemas.js +16 -1
  103. package/lib/programmatic/openapi-description-enricher.js +3 -1
  104. package/lib/programmatic/openapi-descriptions.js +55 -5
  105. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  106. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  107. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  108. package/lib/programmatic/openapi-request-schemas.js +36 -1
  109. package/lib/programmatic/openapi-result-schemas.js +1 -1
  110. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  111. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  112. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  113. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  114. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  115. package/lib/programmatic/openapi-spec.js +47 -28
  116. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  117. package/lib/programmatic/request-timeout.js +3 -0
  118. package/lib/programmatic/route-handler-map.js +89 -2
  119. package/lib/programmatic/run-certify.js +15 -2
  120. package/lib/programmatic/run-deploy.js +74 -0
  121. package/lib/programmatic/run-evidence-authoring.js +80 -0
  122. package/lib/programmatic/run-protection.js +124 -0
  123. package/lib/programmatic/run-template-workspace.js +28 -1
  124. package/lib/programmatic/workspace-context.js +6 -0
  125. package/lib/protection/auth-context.js +48 -6
  126. package/lib/protection/paths.js +22 -0
  127. package/lib/protection/run-protection-create-helpers.js +22 -1
  128. package/lib/protection/run-protection-create.js +1 -1
  129. package/lib/protection/sync-after-upload.js +1 -1
  130. package/lib/role-assistant/evidence-sync.js +450 -0
  131. package/lib/role-assistant/github-workflow.js +96 -0
  132. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  133. package/lib/role-assistant/knowledge-sync.js +485 -0
  134. package/lib/role-assistant/settings.js +126 -0
  135. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  136. package/lib/role-assistant/test-cases-honesty.js +184 -0
  137. package/lib/role-assistant/test-cases.js +364 -0
  138. package/lib/role-assistant/test-runner-workhub-answers.js +221 -0
  139. package/lib/role-assistant/test-runner-workhub.js +399 -0
  140. package/lib/role-assistant/test-runner.js +500 -0
  141. package/lib/schema/application-schema.json +24 -28
  142. package/lib/schema/deployment-rules.yaml +1 -0
  143. package/lib/schema/external-datasource.schema.json +38 -4
  144. package/lib/schema/external-system.schema.json +4 -4
  145. package/lib/schema/role-assistant-settings.schema.json +108 -0
  146. package/lib/utils/api.js +52 -27
  147. package/lib/utils/app-path-resolve.js +72 -24
  148. package/lib/utils/authentication-shape.js +3 -1
  149. package/lib/utils/certify-external-workspace.js +40 -0
  150. package/lib/utils/deployment-validation.js +28 -3
  151. package/lib/utils/docker-run-ephemeral.js +115 -0
  152. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  153. package/lib/utils/env-copy-keycloak.js +196 -0
  154. package/lib/utils/env-copy.js +34 -1
  155. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  156. package/lib/utils/health-check-public-warn.js +31 -13
  157. package/lib/utils/health-check.js +11 -1
  158. package/lib/utils/help-builder.js +1 -0
  159. package/lib/utils/paths.js +80 -38
  160. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  161. package/lib/utils/role-assistant-paths.js +160 -0
  162. package/lib/utils/schema-loader.js +41 -0
  163. package/lib/utils/schema-resolver.js +36 -4
  164. package/lib/utils/token-manager.js +38 -29
  165. package/lib/utils/upload-sync-options.js +7 -4
  166. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  167. package/lib/validation/external-manifest-validator.js +31 -8
  168. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  169. package/lib/validation/validate-external-file.js +8 -1
  170. package/lib/validation/validate.js +54 -19
  171. package/package.json +2 -1
  172. package/scripts/export-builder-api-evidence.js +1 -1
  173. package/templates/applications/builder-api/application.yaml +3 -3
  174. package/templates/applications/dataplane/application.yaml +24 -17
  175. package/templates/applications/dataplane/env.template +2 -2
  176. package/templates/applications/dataplane/rbac.yaml +24 -10
  177. package/templates/applications/keycloak/application.yaml +2 -2
  178. package/templates/applications/keycloak/env.template +5 -2
  179. package/templates/applications/miso-controller/application.yaml +3 -3
  180. package/templates/applications/miso-controller/env.template +17 -2
  181. package/templates/channels/chatgpt/install.template.md.hbs +82 -26
  182. package/templates/channels/chatgpt/integration/README.md +11 -7
  183. package/templates/channels/chatgpt/integration/application.json +2 -2
  184. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  185. package/templates/channels/chatgpt/integration/env.template +1 -1
  186. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  187. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  188. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  189. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  190. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  191. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  192. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  193. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  194. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  195. package/templates/channels/slack/install.template.md.hbs +6 -1
  196. package/templates/python/docker-compose.hbs +10 -0
  197. package/templates/typescript/docker-compose.hbs +10 -0
  198. /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
@@ -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
  }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Optional build.deployJob — one-shot before long-running aifabrix run start.
3
+ *
4
+ * @fileoverview Run deployJob once per aifabrix run
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const config = require('../core/config');
10
+ const logger = require('../utils/logger');
11
+ const {
12
+ infoLine,
13
+ formatSuccessLine,
14
+ formatBlockingError,
15
+ metadata
16
+ } = require('../utils/cli-test-layout-chalk');
17
+ const { runDockerRunEphemeral } = require('../utils/docker-run-ephemeral');
18
+ const { getInfraBridgeNetworkName } = require('../infrastructure/orphan-infra-docker-teardown');
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
+
24
+ /**
25
+ * Resolve trimmed deployJob from app config, or null if unset.
26
+ * @param {Object} appConfig - Application configuration
27
+ * @returns {string|null}
28
+ */
29
+ function resolveDeployJobCommand(appConfig) {
30
+ const raw = appConfig?.build?.deployJob;
31
+ if (typeof raw !== 'string') {
32
+ return null;
33
+ }
34
+ const trimmed = raw.trim();
35
+ return trimmed ? trimmed : null;
36
+ }
37
+
38
+ /**
39
+ * Developer infra bridge so ephemeral jobs resolve compose DNS (e.g. postgres).
40
+ * @returns {Promise<string>}
41
+ */
42
+ async function resolveDeployJobNetwork() {
43
+ const developerId = await config.getDeveloperId();
44
+ return getInfraBridgeNetworkName(developerId);
45
+ }
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
+
156
+ /**
157
+ * If build.deployJob is set, run it once in an ephemeral container (same image + env).
158
+ * Fail-closed on non-zero exit. No-op when unset.
159
+ *
160
+ * When `devMountPath` is set (aifabrix run --reload), bind-mount that path at `/app`
161
+ * so migrate/deploy sees the same tree as the long-running reload container.
162
+ *
163
+ * @async
164
+ * @param {Object} params
165
+ * @param {Object} params.appConfig - Application configuration
166
+ * @param {string} params.fullImage - Image:tag used for the run
167
+ * @param {string|null} [params.runEnvPath] - Ephemeral .env.run path for --env-file
168
+ * @param {string|null} [params.devMountPath] - Host path mounted at /app for --reload
169
+ * @param {boolean} [params.debug]
170
+ * @param {boolean} [params.quiet] - Unused legacy flag; quiet is default unless debug
171
+ * @returns {Promise<void>}
172
+ * @throws {Error} If deployJob exits non-zero
173
+ */
174
+ async function runDeployJobIfConfigured({
175
+ appConfig,
176
+ fullImage,
177
+ runEnvPath = null,
178
+ devMountPath = null,
179
+ debug = false,
180
+ quiet = false
181
+ }) {
182
+ const cmd = resolveDeployJobCommand(appConfig);
183
+ if (!cmd) {
184
+ return;
185
+ }
186
+ if (!fullImage || typeof fullImage !== 'string') {
187
+ throw new Error('build.deployJob requires a resolved container image');
188
+ }
189
+ const network = await resolveDeployJobNetwork();
190
+ const volumeMounts = _deployJobVolumeMounts(devMountPath);
191
+ const quietJob = shouldQuietDeployJob({ quiet, debug });
192
+ _logDeployJobStart({ fullImage, cmd, network, volumeMounts, debug });
193
+ const result = await runDockerRunEphemeral(fullImage, cmd, runEnvPath, {
194
+ network,
195
+ volumeMounts,
196
+ quiet: quietJob
197
+ });
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
+ }
205
+ }
206
+ logger.log(formatSuccessLine('build.deployJob completed'));
207
+ }
208
+
209
+ module.exports = {
210
+ resolveDeployJobCommand,
211
+ runDeployJobIfConfigured,
212
+ shouldQuietDeployJob,
213
+ emitDeployJobSummaryLines,
214
+ formatDeployJobMigrateSummary,
215
+ summarizeDeployJobMigrateOutput
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
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Last-moment run env recovery for the shared applications workspace.
3
+ *
4
+ * @fileoverview Builds the callback used immediately before Docker Compose starts
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const runEnvCompose = require('./run-env-compose');
12
+
13
+ /**
14
+ * Build a recovery hook for run env files in the shared workspace.
15
+ * @param {string} appName - Application name
16
+ * @param {Object} prepared - Prepared run context
17
+ * @returns {Function} Async recovery hook
18
+ */
19
+ function buildRunEnvRecovery(appName, prepared) {
20
+ return () =>
21
+ runEnvCompose.ensureRunEnvFilesPresent(
22
+ appName,
23
+ prepared.appConfig,
24
+ prepared.runEnvPath,
25
+ prepared.runEnvAdminPath,
26
+ prepared.appConfig.developerId,
27
+ {
28
+ runEnvKey: prepared.mergedRunOptions.env,
29
+ effectiveEnvironmentScopedResources:
30
+ prepared.mergedRunOptions.effectiveEnvironmentScopedResources
31
+ }
32
+ );
33
+ }
34
+
35
+ module.exports = { buildRunEnvRecovery };
@@ -34,7 +34,12 @@ const runEnvCompose = require('./run-env-compose');
34
34
  const { getAppInfraRequirements } = require('./run-infra-requirements');
35
35
  const { resolveRunImage } = require('./run-resolve-image');
36
36
  const { startContainer } = require('./run-container-start');
37
- const { resolveEnvOutputPath, writeEnvOutputForReload, writeEnvOutputForLocal } = require('../utils/env-copy');
37
+ const {
38
+ resolveEnvOutputPath,
39
+ resolveEnvLocalOutputPath,
40
+ writeEnvOutputForReload,
41
+ writeEnvOutputForLocal
42
+ } = require('../utils/env-copy');
38
43
  const { resolveVersionForApp } = require('../utils/image-version');
39
44
  const healthCheckUtil = require('../utils/health-check');
40
45
  const { computeTraefikPublicAppUrl } = require('../utils/health-check-url');
@@ -248,18 +253,17 @@ async function checkInfraHealthOrThrow(debug, appConfig) {
248
253
  }
249
254
 
250
255
  /**
251
- * Ensure dev directory exists
256
+ * Refresh the shared developer run directory for the current application.
257
+ *
258
+ * The applications-dev-* directory is shared by all applications. Always copy
259
+ * the selected app so files from a previous run cannot leak into this run.
252
260
  * @async
253
261
  * @param {string} appName - Application name
254
262
  * @param {string} developerId - Developer ID
255
263
  * @returns {Promise<string>} Dev directory path
256
264
  */
257
265
  async function ensureDevDirectory(appName, developerId) {
258
- const devDir = buildCopy.getDevDirectory(appName, developerId);
259
- if (!fsSync.existsSync(devDir)) {
260
- await buildCopy.copyBuilderToDevDirectory(appName, developerId);
261
- }
262
- return devDir;
266
+ return buildCopy.copyBuilderToDevDirectory(appName, developerId);
263
267
  }
264
268
 
265
269
  /**
@@ -303,8 +307,8 @@ async function generateComposeFile(appName, appConfig, composeOptions, devDir, l
303
307
 
304
308
  /**
305
309
  * Writes `build.envOutputPath` after each `aifabrix run`: **local**-flavored env for the host/IDE
306
- * when not using `--reload`; with **`--reload`**, merges container `.env.run` into the existing file
307
- * (preserves resolve comments) without appending run-only keys missing from the template.
310
+ * when not using `--reload`; with **`--reload`**, merges container `.env.run` into `.env` and also
311
+ * writes a sibling **`.env.local`** with localhost / published-port URLs for host CLI and manual tests.
308
312
  *
309
313
  * @async
310
314
  * @param {string} appName - Application name
@@ -326,6 +330,8 @@ async function writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, option
326
330
  }
327
331
  if (options && options.reload === true) {
328
332
  await writeEnvOutputForReload(outputPath, runEnvPath, appName);
333
+ const localOutputPath = resolveEnvLocalOutputPath(outputPath);
334
+ await writeEnvOutputForLocal(appName, localOutputPath);
329
335
  } else {
330
336
  await writeEnvOutputForLocal(appName, outputPath);
331
337
  }
package/lib/app/run.js CHANGED
@@ -28,6 +28,7 @@ const helpers = require('./run-helpers');
28
28
  const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
29
  const { logRestartDevMountSummary } = require('./restart-display');
30
30
  const { checkAndStopContainer } = require('./run-stop-containers');
31
+ const { buildRunEnvRecovery } = require('./run-env-recovery');
31
32
  const { execWithDockerEnv } = require('../utils/docker-exec');
32
33
  const { resolveRunProxyPersistEnabled, resolveRunEffectiveNoProxy } = require('../utils/run-cli-flags');
33
34
 
@@ -178,7 +179,8 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
178
179
  runEnvPath = null,
179
180
  runEnvAdminPath = null,
180
181
  runOptions = {},
181
- devMountPath = null
182
+ devMountPath = null,
183
+ ensureRunEnvFiles = null
182
184
  } = opts;
183
185
  const misoEnvironment = opts.misoEnvironment ?? composeGenerator.resolveMisoEnvironment(runOptions);
184
186
  try {
@@ -188,6 +190,7 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
188
190
  runEnvAdminPath,
189
191
  runOptions,
190
192
  devMountPath,
193
+ ensureRunEnvFiles,
191
194
  misoEnvironment
192
195
  });
193
196
  await helpers.displayRunStatus(
@@ -404,13 +407,26 @@ async function runApp(appName, options = {}) {
404
407
  logger.log(chalk.gray(`[DEBUG] Compose file generated: ${prepared.tempComposePath}`));
405
408
  }
406
409
  logReloadDevSummary(Boolean(prepared.mergedRunOptions.reload), prepared.mergedRunOptions.reloadSyncSummary);
410
+ const { runDeployJobIfConfigured } = require('./run-deploy-job');
411
+ await runDeployJobIfConfigured({
412
+ appConfig: prepared.appConfig,
413
+ fullImage: prepared.mergedRunOptions.image,
414
+ runEnvPath: prepared.runEnvPath,
415
+ devMountPath: prepared.devMountPath || null,
416
+ debug,
417
+ // Guided installers already show a top-level spinner; keep migrate/deployJob logs out of TTY.
418
+ quiet:
419
+ options.silentPoll === true || typeof options.onPollText === 'function'
420
+ });
421
+ const ensureRunEnvFiles = buildRunEnvRecovery(appName, prepared);
407
422
  await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
408
423
  debug,
409
424
  runEnvPath: prepared.runEnvPath,
410
425
  runEnvAdminPath: prepared.runEnvAdminPath,
411
426
  runOptions: prepared.mergedRunOptions,
412
427
  devMountPath: prepared.devMountPath,
413
- runScopeOpts: prepared.runScopeOpts
428
+ runScopeOpts: prepared.runScopeOpts,
429
+ ensureRunEnvFiles
414
430
  });
415
431
  } catch (error) {
416
432
  if (debug) {
@@ -12,6 +12,7 @@ const { getChannelTarget, getDefaultChannelDescription } = require('./channel-ta
12
12
  const { resolveAppDescription } = require('./channel-description');
13
13
  const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
14
14
  const { generateChannelArtifact } = require('./channel-artifact-generator');
15
+ const { fetchRuntimeLayerBToolNames } = require('./runtime-tool-catalog');
15
16
  const {
16
17
  getExistingChannelExtension,
17
18
  buildChannelExtensionBlock,
@@ -66,7 +67,9 @@ function toValidationDisplay(validation) {
66
67
  callbackReachable: validation.callbackReachable,
67
68
  credentialValid: validation.credentialValid,
68
69
  assistantCatalogReachable: validation.assistantCatalogReachable,
69
- inboundVerification: Object.values(validation.inboundVerification).every(Boolean)
70
+ inboundVerification: Object.values(validation.inboundVerification).every(Boolean),
71
+ probeTiers: validation.probeTiers || null,
72
+ runtimeChecks: validation.runtimeChecks || null
70
73
  };
71
74
  }
72
75
 
@@ -156,7 +159,13 @@ async function patchConfigWithAudit(ctx, channelTarget, channelBlock, status, ar
156
159
  async function maybeRunProbe(ctx, channelBlock, channelTarget, probe) {
157
160
  if (!probe) return null;
158
161
  const validation = await runChannelProbe(
159
- { dataplaneUrl: ctx.dataplaneUrl, authConfig: ctx.authConfig },
162
+ {
163
+ dataplaneUrl: ctx.dataplaneUrl,
164
+ authConfig: ctx.authConfig,
165
+ environment: ctx.environment,
166
+ controllerUrl: ctx.controllerUrl,
167
+ channelTarget
168
+ },
160
169
  channelBlock
161
170
  );
162
171
  if (validation.passed) {
@@ -268,6 +277,42 @@ function finishDryRunInstall(params) {
268
277
  return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
269
278
  }
270
279
 
280
+ /**
281
+ * @param {Object} params
282
+ * @returns {Promise<{ artifact: Object, channelBlock: Object, status: string }>}
283
+ */
284
+ async function buildInstallArtifacts(params) {
285
+ const { channelTarget, options, target, existing, name, baseName, description, ctx } = params;
286
+ const runtimeToolNames =
287
+ channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot'
288
+ ? await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget)
289
+ : [];
290
+ const runtimeValues = resolveChannelRuntimeValues({
291
+ dataplaneBaseUrl: ctx.dataplaneUrl,
292
+ channelTarget,
293
+ appName: name,
294
+ baseName,
295
+ environment: ctx.environment,
296
+ appDescription: description,
297
+ icon: options.icon,
298
+ commandPrefix: options.commandPrefix,
299
+ layerBToolNames: runtimeToolNames
300
+ });
301
+ const projectRoot = options.projectRoot || process.cwd();
302
+ const artifact = generateArtifactWithAudit(channelTarget, {
303
+ projectRoot,
304
+ channelTarget,
305
+ runtimeValues,
306
+ dryRun: options.dryRun === true
307
+ });
308
+ const status = resolveInstallStatus(target, existing, options.dryRun === true);
309
+ const channelBlock = buildChannelExtensionBlock(
310
+ { name, baseName, description, artifactHash: artifact.artifactHash, status },
311
+ target
312
+ );
313
+ return { artifact, channelBlock, status };
314
+ }
315
+
271
316
  /**
272
317
  * @param {string} channelTarget
273
318
  * @param {Object} options
@@ -289,31 +334,17 @@ async function runAddChannel(channelTarget, options = {}) {
289
334
  });
290
335
  if (skipped) return skipped;
291
336
 
292
- const runtimeValues = resolveChannelRuntimeValues({
293
- dataplaneBaseUrl: ctx.dataplaneUrl,
337
+ const { artifact, channelBlock, status } = await buildInstallArtifacts({
294
338
  channelTarget,
295
- appName: name,
339
+ options,
340
+ target,
341
+ existing,
342
+ name,
296
343
  baseName,
297
- environment: ctx.environment,
298
- appDescription: description,
299
- icon: options.icon,
300
- commandPrefix: options.commandPrefix
301
- });
302
-
303
- const projectRoot = options.projectRoot || process.cwd();
304
- const artifact = generateArtifactWithAudit(channelTarget, {
305
- projectRoot,
306
- channelTarget,
307
- runtimeValues,
308
- dryRun: options.dryRun === true
344
+ description,
345
+ ctx
309
346
  });
310
347
 
311
- const status = resolveInstallStatus(target, existing, options.dryRun === true);
312
- const channelBlock = buildChannelExtensionBlock(
313
- { name, baseName, description, artifactHash: artifact.artifactHash, status },
314
- target
315
- );
316
-
317
348
  if (options.dryRun) {
318
349
  return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
319
350
  }
@@ -16,16 +16,20 @@ const { formatCredentialPortalStep } = require('./credential-portal');
16
16
  */
17
17
  function buildChatGptApprovalGuide(ctx) {
18
18
  return {
19
- portalUrl: 'https://platform.openai.com/',
19
+ portalUrl: 'https://chatgpt.com/',
20
20
  steps: [
21
- 'Sign in to OpenAI Platform (https://platform.openai.com/) or your ChatGPT Enterprise workspace admin console.',
22
- 'Create a custom GPT, workspace connector, or MCP integration using the generated manifest (path shown below).',
23
- 'Configure the OpenAPI specification URL and MCP endpoint from INSTALL.md (dataplane ChatGPT routes).',
24
- 'Complete OAuth or API key setup as required by your ChatGPT deployment model.',
25
- formatCredentialPortalStep(ctx, 'the API key or OAuth client credentials'),
26
- 'Publish the connector to your workspace and complete admin approval if your org requires it.',
27
- `After ChatGPT lists the connector as active, re-run: ${ctx.resumeCommand}`,
28
- 'Optional: add --probe when dataplane ChatGPT runtime routes are live on your environment.'
21
+ 'Sign in to the ChatGPT web app as a workspace admin or owner.',
22
+ 'Enable Developer mode for custom MCP apps (Business: Workspace settings → Apps, or Settings → Apps → Advanced settings; Enterprise/Edu: grant Developer mode under Permissions & roles → Connected data first).',
23
+ 'Create a custom MCP app with the Remote MCP URL from INSTALL.md (channel-chatgpt.json is a deployment reference, not a plugin archive).',
24
+ 'Select OAuth (authorization-code + PKCE S256). Do not configure a custom API key for a remote custom MCP app.',
25
+ 'Scan tools, complete OAuth if prompted, review discovered Runtime Layer B tools, then save the app as a draft.',
26
+ formatCredentialPortalStep(
27
+ ctx,
28
+ `${ctx.credentialKey || 'channel'} credential references (not secrets to paste into ChatGPT)`
29
+ ),
30
+ 'Test representative workflows, then publish the app and configure workspace access and action controls.',
31
+ `After ChatGPT lists the app as active, re-run: ${ctx.resumeCommand}`,
32
+ 'Optional: use --probe for AI Fabrix-side Runtime checks; ChatGPT Scan tools / OAuth / publication remain workspace-admin responsibilities.'
29
33
  ]
30
34
  };
31
35
  }
@@ -19,14 +19,13 @@ function buildMicrosoftCopilotApprovalGuide(ctx) {
19
19
  portalUrl: 'https://admin.cloud.microsoft.com/',
20
20
  steps: [
21
21
  'Sign in to Microsoft 365 admin (https://admin.cloud.microsoft.com/) or Copilot Studio as a tenant admin.',
22
- 'Create or open a Copilot extensibility project (declarative agent or API plugin).',
23
- 'Import or configure using the generated manifest artifact (path shown below).',
24
- 'Point the OpenAPI / action endpoints to the URLs in INSTALL.md (dataplane Copilot routes).',
25
- 'Register OAuth client credentials for the API provider connection if the platform prompts for them.',
26
- formatCredentialPortalStep(ctx, 'the OAuth client ID and client secret'),
27
- 'Submit for M365 admin approval and publish to your organization when review completes.',
28
- `After Copilot shows the extension as available, re-run: ${ctx.resumeCommand}`,
29
- 'Optional: add --probe when dataplane Copilot runtime routes are live on your environment.'
22
+ 'Upload or sideload the generated appPackage.<env>.zip package artifact.',
23
+ 'Review declarative agent + Remote MCP plugin details and approve tenant distribution.',
24
+ 'Configure Entra/OAuth consent and user assignment for target groups.',
25
+ formatCredentialPortalStep(ctx, 'Copilot channel credential references (no secrets in package files)'),
26
+ 'Verify Runtime MCP endpoint and Layer B tool visibility using INSTALL.md guidance.',
27
+ `After Copilot shows the package as available, re-run: ${ctx.resumeCommand}`,
28
+ 'Optional: use --probe for AI Fabrix-side checks; tenant upload/approval remains Microsoft-admin responsibility.'
30
29
  ]
31
30
  };
32
31
  }
@@ -14,6 +14,7 @@ const path = require('path');
14
14
  const Handlebars = require('handlebars');
15
15
  const { getChannelTarget } = require('./channel-targets');
16
16
  const { BUILDER_ROOT } = require('./channel-integration-template');
17
+ const { generateMicrosoftCopilotPackage } = require('./microsoft-copilot-package');
17
18
 
18
19
  const SECRET_PATTERNS = [/xoxb-/i, /xoxp-/i, /client_secret/i, /signing_secret/i, /Bearer\s+/i];
19
20
  const PLACEHOLDER_DOMAIN_RE = /\byour-domain\.com\b/i;
@@ -115,11 +116,22 @@ function renderInstallGuideIfPresent(templatesDir, runtimeValues, outputDir, dry
115
116
  */
116
117
  function generateChannelArtifact(params) {
117
118
  const templatesDir = getChannelTemplatesDir(params.channelTarget);
119
+ const outputPath = getArtifactOutputPath(params.projectRoot, params.channelTarget);
120
+
121
+ if (params.channelTarget === 'microsoft-copilot') {
122
+ return generateMicrosoftCopilotPackage({
123
+ runtimeValues: params.runtimeValues,
124
+ templatesDir,
125
+ outputPath,
126
+ dryRun: params.dryRun === true,
127
+ assertNoSecretsInArtifact,
128
+ renderTemplateFile
129
+ });
130
+ }
131
+
118
132
  const manifestTemplate = path.join(templatesDir, 'manifest.template.json.hbs');
119
133
  const content = renderTemplateFile(manifestTemplate, params.runtimeValues);
120
134
  assertNoSecretsInArtifact(content);
121
-
122
- const outputPath = getArtifactOutputPath(params.projectRoot, params.channelTarget);
123
135
  const artifactHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
124
136
  const outputDir = path.dirname(outputPath);
125
137
 
@@ -48,8 +48,10 @@ const CREDENTIAL_SPECS = {
48
48
  method: 'oauth2',
49
49
  credentialConfig: {
50
50
  type: 'oauth2',
51
- grantType: 'client_credentials',
51
+ grantType: 'authorization_code',
52
+ authorizationUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
52
53
  tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
54
+ scope: 'openid profile offline_access',
53
55
  clientId: 'pending-channel-install',
54
56
  clientSecret: 'pending-channel-install'
55
57
  }