@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
@@ -11,12 +11,45 @@
11
11
  const {
12
12
  formatSuccessLine,
13
13
  formatSuccessParagraph,
14
+ formatWarningLine,
14
15
  sectionTitle,
15
16
  headerKeyValue
16
17
  } = require('../utils/cli-test-layout-chalk');
17
18
  const logger = require('../utils/logger');
18
19
  const { generateExternalReadmeContent } = require('../utils/external-readme');
19
20
  const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
21
+ const {
22
+ getCwdIntegrationRoot,
23
+ getCwdBuilderRoot,
24
+ getAppsMaterializationParent
25
+ } = require('../utils/paths');
26
+ const {
27
+ SYSTEM_KIND_ENTERPRISE_KNOWLEDGE,
28
+ getCwdEnterpriseKnowledgeRoot,
29
+ getEnterpriseKnowledgeRoot
30
+ } = require('../utils/enterprise-knowledge-paths');
31
+ const {
32
+ SYSTEM_KIND_DIGITAL_WORKER,
33
+ getCwdRoleAssistantRoot,
34
+ getRoleAssistantRoot
35
+ } = require('../utils/role-assistant-paths');
36
+ const path = require('path');
37
+
38
+ /** Runtime/API fields that must not appear on external-system manifests. */
39
+ const NON_MANIFEST_SYSTEM_KEYS = ['showOpenApiDocs'];
40
+
41
+ /**
42
+ * True when cwd looks like a product/workspace repo (integration, builder, or EK tree).
43
+ * @returns {boolean}
44
+ */
45
+ function cwdHasWorkspaceTree() {
46
+ return Boolean(
47
+ getCwdIntegrationRoot() ||
48
+ getCwdBuilderRoot() ||
49
+ getCwdEnterpriseKnowledgeRoot() ||
50
+ getCwdRoleAssistantRoot()
51
+ );
52
+ }
20
53
 
21
54
  /**
22
55
  * Validates system type from downloaded application
@@ -68,6 +101,61 @@ function validateDownloadedData(application, dataSources) {
68
101
  }
69
102
  }
70
103
 
104
+ /**
105
+ * Strip dataplane runtime fields that are not part of external-system.schema.json.
106
+ * @param {Object} application
107
+ * @returns {Object}
108
+ */
109
+ function sanitizeDownloadedApplication(application) {
110
+ if (!application || typeof application !== 'object') {
111
+ return application;
112
+ }
113
+ const cleaned = { ...application };
114
+ for (const key of NON_MANIFEST_SYSTEM_KEYS) {
115
+ delete cleaned[key];
116
+ }
117
+ return cleaned;
118
+ }
119
+
120
+ /**
121
+ * Download destination:
122
+ * - If cwd has `integration/`, `builder/`, or `enterprise-knowledge/` → write under cwd
123
+ * - Else → `aifabrix-work` / materialization parent
124
+ * Ignores materialization orphans when cwd has a workspace tree so re-download lands in-repo.
125
+ * @param {string} systemKey
126
+ * @param {Object} application
127
+ * @returns {string}
128
+ */
129
+ function resolveDownloadDestinationPath(systemKey, application) {
130
+ const kind =
131
+ application && typeof application.systemKind === 'string'
132
+ ? application.systemKind.trim()
133
+ : '';
134
+ const useCwd = cwdHasWorkspaceTree();
135
+ if (kind === SYSTEM_KIND_DIGITAL_WORKER) {
136
+ if (useCwd) {
137
+ const cwdRoleAssistant = getCwdRoleAssistantRoot();
138
+ const roleAssistantRoot = cwdRoleAssistant || path.join(path.resolve(process.cwd()), 'role-assistant');
139
+ return path.join(roleAssistantRoot, systemKey);
140
+ }
141
+ return path.join(getRoleAssistantRoot(), systemKey);
142
+ }
143
+ if (kind === SYSTEM_KIND_ENTERPRISE_KNOWLEDGE) {
144
+ if (useCwd) {
145
+ const cwdEk = getCwdEnterpriseKnowledgeRoot();
146
+ const ekRoot = cwdEk || path.join(path.resolve(process.cwd()), 'enterprise-knowledge');
147
+ return path.join(ekRoot, systemKey);
148
+ }
149
+ return path.join(getEnterpriseKnowledgeRoot(), systemKey);
150
+ }
151
+ if (useCwd) {
152
+ const cwdInt = getCwdIntegrationRoot();
153
+ const intRoot = cwdInt || path.join(path.resolve(process.cwd()), 'integration');
154
+ return path.join(intRoot, systemKey);
155
+ }
156
+ return path.join(getAppsMaterializationParent(), 'integration', systemKey);
157
+ }
158
+
71
159
  function logDownloadCommandHeader(systemKey) {
72
160
  logger.log('');
73
161
  logger.log(sectionTitle('Download'));
@@ -83,13 +171,31 @@ function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
83
171
  logger.log(headerKeyValue('Location:', finalPath));
84
172
  }
85
173
 
174
+ /**
175
+ * Convert YAML components to JSON after download. On validation failure, keep takeout
176
+ * and warn — do not discard downloaded files.
177
+ * @param {string} systemKey
178
+ * @returns {Promise<{converted: boolean, error?: string}>}
179
+ */
86
180
  async function runConvertToJsonIfRequested(systemKey) {
87
181
  const { runConvert } = require('../commands/convert');
88
182
  try {
89
183
  await runConvert(systemKey, { format: 'json', force: true });
90
184
  logger.log(formatSuccessLine('Converted component files to JSON'));
185
+ return { converted: true };
91
186
  } catch (convertErr) {
92
- throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
187
+ const message = convertErr && convertErr.message ? convertErr.message : String(convertErr);
188
+ logger.log(
189
+ formatWarningLine(
190
+ `Download saved files, but convert to JSON failed: ${message}`
191
+ )
192
+ );
193
+ logger.log(
194
+ formatWarningLine(
195
+ 'Fix schema errors in the downloaded files, then run: aifabrix convert <systemKey> --format json'
196
+ )
197
+ );
198
+ return { converted: false, error: message };
93
199
  }
94
200
  }
95
201
 
@@ -183,10 +289,13 @@ function generateReadme(systemKey, application, dataSources) {
183
289
  module.exports = {
184
290
  validateSystemType,
185
291
  validateDownloadedData,
292
+ sanitizeDownloadedApplication,
293
+ resolveDownloadDestinationPath,
186
294
  logDownloadCommandHeader,
187
295
  displayDownloadSuccess,
188
296
  runConvertToJsonIfRequested,
189
297
  generateVariablesYaml,
190
- generateReadme
298
+ generateReadme,
299
+ NON_MANIFEST_SYSTEM_KEYS
191
300
  };
192
301
 
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Role Assistant sidecar handling during external-system download.
3
+ *
4
+ * @fileoverview Post-download evidence/knowledge/workflow actions for digitalWorker systems
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const logger = require('../utils/logger');
12
+ const { formatWarningLine } = require('../utils/cli-test-layout-chalk');
13
+ const { exportRoleAssistantKnowledge, logKnowledgeExportSummary } = require('../role-assistant/knowledge-sync');
14
+ const { exportRoleAssistantEvidence, logRoleAssistantEvidenceExportSummary } = require('../role-assistant/evidence-sync');
15
+
16
+ async function maybeExportRoleAssistantEvidenceArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options) {
17
+ try {
18
+ const evidenceResult = await exportRoleAssistantEvidence({
19
+ systemKey,
20
+ dataplaneUrl,
21
+ authConfig,
22
+ manifest,
23
+ pathOptions: options.pathOptions || {}
24
+ });
25
+ logRoleAssistantEvidenceExportSummary(systemKey, evidenceResult, { verbose: options.verbose === true });
26
+ } catch (evidenceErr) {
27
+ logger.log(formatWarningLine(`Role assistant evidence export skipped: ${evidenceErr.message}`));
28
+ }
29
+ }
30
+
31
+ async function maybeExportRoleAssistantKnowledgeArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options) {
32
+ try {
33
+ const knowledgeResult = await exportRoleAssistantKnowledge({
34
+ systemKey,
35
+ dataplaneUrl,
36
+ authConfig,
37
+ manifest,
38
+ environment: options.env || options.environment || 'dev',
39
+ pathOptions: options.pathOptions || {}
40
+ });
41
+ logKnowledgeExportSummary(systemKey, knowledgeResult, { verbose: options.verbose === true });
42
+ } catch (knowledgeErr) {
43
+ logger.log(formatWarningLine(`Role assistant knowledge export skipped: ${knowledgeErr.message}`));
44
+ }
45
+ }
46
+
47
+ async function runRoleAssistantDownloadSidecars(params) {
48
+ const { systemKey, manifest, dataplaneUrl, authConfig, options = {} } = params;
49
+ await maybeExportRoleAssistantEvidenceArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options);
50
+ await maybeExportRoleAssistantKnowledgeArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options);
51
+ }
52
+
53
+ module.exports = {
54
+ runRoleAssistantDownloadSidecars
55
+ };
@@ -32,7 +32,6 @@ const yaml = require('js-yaml');
32
32
  const { getExternalSystemConfig } = require('../api/external-systems.api');
33
33
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
34
34
  const { getConfig } = require('../core/config');
35
- const { getIntegrationPath } = require('../utils/paths');
36
35
  const { retemplateConfigurationForDownload, retemplateAllVariableDefinitions } = require('../utils/configuration-env-resolver');
37
36
  const { syncRuntimeConfigurationFromDataplane } = require('../utils/runtime-configuration-download');
38
37
  const logger = require('../utils/logger');
@@ -41,10 +40,13 @@ const generator = require('../generator');
41
40
  const {
42
41
  validateSystemType,
43
42
  validateDownloadedData,
43
+ sanitizeDownloadedApplication,
44
+ resolveDownloadDestinationPath,
44
45
  logDownloadCommandHeader,
45
46
  displayDownloadSuccess,
46
47
  runConvertToJsonIfRequested
47
48
  } = require('./download-helpers');
49
+ const { runRoleAssistantDownloadSidecars } = require('./download-role-assistant');
48
50
 
49
51
  /**
50
52
  * Setup authentication and get dataplane URL
@@ -117,7 +119,11 @@ async function downloadFullManifest(dataplaneUrl, systemKey, authConfig, options
117
119
  throw new Error('Application configuration not found in download response');
118
120
  }
119
121
 
120
- return { application, dataSources, version };
122
+ return {
123
+ application: sanitizeDownloadedApplication(application),
124
+ dataSources,
125
+ version
126
+ };
121
127
  }
122
128
 
123
129
  /**
@@ -304,9 +310,8 @@ async function applyRetemplateToSystemFile(systemKey, systemFilePath) {
304
310
  * @returns {Promise<string>} Final destination path
305
311
  * @throws {Error} If processing fails
306
312
  */
307
- async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
313
+ async function processDownloadedSystem(systemKey, manifest, finalPath, splitOptions = {}) {
308
314
  const { application, dataSources, version } = manifest;
309
- const finalPath = getIntegrationPath(systemKey);
310
315
 
311
316
  logger.log(formatProgress('Creating integration directory…'));
312
317
  logger.log(metadata(finalPath));
@@ -331,6 +336,28 @@ async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
331
336
  return finalPath;
332
337
  }
333
338
 
339
+ async function maybeSyncRuntimeConfigurationForDownload(systemKey, manifest, finalPath, dataplaneUrl, authConfig) {
340
+ try {
341
+ const configSync = await syncRuntimeConfigurationFromDataplane({
342
+ dataplaneUrl,
343
+ authConfig,
344
+ systemKey,
345
+ integrationPath: finalPath,
346
+ dataSources: manifest.dataSources
347
+ });
348
+ if (configSync.merged > 0) {
349
+ const nameList = configSync.names.length ? ` (${configSync.names.join(', ')})` : '';
350
+ logger.log(
351
+ formatSuccessLine(
352
+ `Merged ${configSync.merged} runtime configuration variable(s) into env.template${nameList}.`
353
+ )
354
+ );
355
+ }
356
+ } catch (syncErr) {
357
+ logger.log(formatWarningLine(`Runtime configuration sync skipped: ${syncErr.message}`));
358
+ }
359
+ }
360
+
334
361
  /**
335
362
  * Post-split download steps: governance packs, optional JSON convert, runtime config sync.
336
363
  * @param {Object} params
@@ -348,7 +375,8 @@ async function finalizeDownloadArtifacts({
348
375
  finalPath,
349
376
  dataplaneUrl,
350
377
  authConfig,
351
- format
378
+ format,
379
+ options = {}
352
380
  }) {
353
381
  const { downloadGovernanceScenarioPacksFromManifest } = require('../lifecycle/scenario-upload');
354
382
  await downloadGovernanceScenarioPacksFromManifest(
@@ -360,25 +388,14 @@ async function finalizeDownloadArtifacts({
360
388
  if (format === 'json') {
361
389
  await runConvertToJsonIfRequested(systemKey);
362
390
  }
363
- try {
364
- const configSync = await syncRuntimeConfigurationFromDataplane({
365
- dataplaneUrl,
366
- authConfig,
367
- systemKey,
368
- integrationPath: finalPath,
369
- dataSources: manifest.dataSources
370
- });
371
- if (configSync.merged > 0) {
372
- const nameList = configSync.names.length ? ` (${configSync.names.join(', ')})` : '';
373
- logger.log(
374
- formatSuccessLine(
375
- `Merged ${configSync.merged} runtime configuration variable(s) into env.template${nameList}.`
376
- )
377
- );
378
- }
379
- } catch (syncErr) {
380
- logger.log(formatWarningLine(`Runtime configuration sync skipped: ${syncErr.message}`));
381
- }
391
+ await maybeSyncRuntimeConfigurationForDownload(systemKey, manifest, finalPath, dataplaneUrl, authConfig);
392
+ await runRoleAssistantDownloadSidecars({
393
+ systemKey,
394
+ manifest,
395
+ dataplaneUrl,
396
+ authConfig,
397
+ options
398
+ });
382
399
  displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
383
400
  }
384
401
 
@@ -408,10 +425,11 @@ async function downloadExternalSystem(systemKey, options = {}) {
408
425
 
409
426
  const manifest = await downloadFullManifest(dataplaneUrl, systemKey, authConfig, options);
410
427
  validateAndLogDownloadedData(manifest.application, manifest.dataSources);
411
- const finalPath = getIntegrationPath(systemKey);
428
+ const finalPath = resolveDownloadDestinationPath(systemKey, manifest.application);
412
429
  await processDownloadedSystem(
413
430
  systemKey,
414
431
  manifest,
432
+ finalPath,
415
433
  await resolveDownloadSplitOptions(finalPath, options)
416
434
  );
417
435
  await finalizeDownloadArtifacts({
@@ -420,7 +438,8 @@ async function downloadExternalSystem(systemKey, options = {}) {
420
438
  finalPath,
421
439
  dataplaneUrl,
422
440
  authConfig,
423
- format
441
+ format,
442
+ options
424
443
  });
425
444
  } catch (error) {
426
445
  throw new Error(`Failed to download external system: ${error.message}`);
@@ -78,7 +78,7 @@ function validateSystemFilesForTest(systemFiles, results) {
78
78
  * @param {Function} determineDatasourcesToTest - Function to determine datasources to test
79
79
  */
80
80
  function validateDatasourceFilesForTest(datasourceFiles, systemFiles, results, options, validateSingleDatasource, determineDatasourcesToTest) {
81
- const datasourcesToTest = determineDatasourcesToTest(datasourceFiles, options.datasource);
81
+ const datasourcesToTest = determineDatasourcesToTest(datasourceFiles, options.datasource, options);
82
82
  const systemKey = systemFiles.length > 0 ? systemFiles[0].data.key : null;
83
83
 
84
84
  for (const datasourceFile of datasourcesToTest) {
@@ -12,7 +12,7 @@ const path = require('path');
12
12
  const chalk = require('chalk');
13
13
  const logger = require('../utils/logger');
14
14
  const { writeTestLog } = require('../utils/test-log-writer');
15
- const { getIntegrationPath } = require('../utils/paths');
15
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
16
16
  const { runUnifiedDatasourceValidation } = require('../datasource/unified-validation-run');
17
17
 
18
18
  async function runOneDatasourceKey(appName, datasourceKey, opts) {
@@ -61,16 +61,34 @@ async function runOneDatasourceKey(appName, datasourceKey, opts) {
61
61
  };
62
62
  }
63
63
  const env = uni.envelope;
64
- const ok = env && typeof env.status === 'string' ? env.status !== 'fail' : false;
64
+ const ok = isDatasourceRunSuccessful(env);
65
65
  return { key: datasourceKey, success: ok, skipped: false, datasourceTestRun: env };
66
66
  } catch (err) {
67
67
  return { key: datasourceKey, success: false, skipped: false, error: err.message };
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * Prefer explicit envelope status; soft-pass when validation+certificate succeeded
73
+ * (documentStorage packs may report overall fail only for missing live evidence).
74
+ * @param {Object|null|undefined} env
75
+ * @returns {boolean}
76
+ */
77
+ function isDatasourceRunSuccessful(env) {
78
+ if (!env || typeof env.status !== 'string') {
79
+ return false;
80
+ }
81
+ if (env.status !== 'fail') {
82
+ return true;
83
+ }
84
+ const validationOk = env.validation && env.validation.status === 'ok';
85
+ const certPassed = env.certificate && env.certificate.status === 'passed';
86
+ return Boolean(validationOk && certPassed);
87
+ }
88
+
71
89
  async function maybeWriteSystemRunLog(appName, systemKey, datasourceKeys, runType, debug, payload) {
72
90
  if (!debug) return;
73
- const appPath = getIntegrationPath(appName);
91
+ const appPath = resolveExternalSystemPath(appName);
74
92
  const integrationDir = path.dirname(appPath);
75
93
  const logPath = await writeTestLog(
76
94
  appName,
@@ -39,6 +39,20 @@ const { runSystemLevelTest } = require('./test-system-level');
39
39
  const { shouldUseSystemLevelIntegrationCall } = require('./integration-test-dispatch');
40
40
  const { cliOptsSkipPublish } = require('../utils/cli-sync-options');
41
41
 
42
+ const EMPTY_DATASOURCE_ALLOWED_KINDS = new Set(['enterpriseKnowledge', 'digitalWorker']);
43
+
44
+ function resolvePrimarySystemKind(systemFiles) {
45
+ if (!Array.isArray(systemFiles) || systemFiles.length === 0) {
46
+ return '';
47
+ }
48
+ const kind = systemFiles[0] && systemFiles[0].data ? systemFiles[0].data.systemKind : '';
49
+ return typeof kind === 'string' ? kind.trim() : '';
50
+ }
51
+
52
+ function allowEmptyDatasourcesForKind(systemKind) {
53
+ return EMPTY_DATASOURCE_ALLOWED_KINDS.has(String(systemKind || '').trim());
54
+ }
55
+
42
56
  /**
43
57
  * Loads and parses application config file
44
58
  * @async
@@ -320,11 +334,25 @@ async function testExternalSystem(appName, options = {}) {
320
334
 
321
335
  // Load files
322
336
  const { variables: _variables, systemFiles, datasourceFiles } = await loadExternalSystemFiles(appName);
337
+ const systemKind = resolvePrimarySystemKind(systemFiles);
338
+ const allowEmptyDatasources = allowEmptyDatasourcesForKind(systemKind);
323
339
 
324
340
  const results = initializeTestResults();
325
341
 
326
342
  validateSystemFilesForTest(systemFiles, results);
327
- validateDatasourceFilesForTest(datasourceFiles, systemFiles, results, options, validateSingleDatasource, determineDatasourcesToTest);
343
+ validateDatasourceFilesForTest(
344
+ datasourceFiles,
345
+ systemFiles,
346
+ results,
347
+ { ...options, allowEmptyDatasources, systemKindForEmptyDatasources: systemKind },
348
+ validateSingleDatasource,
349
+ determineDatasourcesToTest
350
+ );
351
+ if (allowEmptyDatasources && results.datasourceResults.length === 0) {
352
+ results.warnings.push(
353
+ `No datasources defined; integration validation is N/A for systemKind "${systemKind}".`
354
+ );
355
+ }
328
356
 
329
357
  results.systemKey = systemFiles.length > 0 ? systemFiles[0].data.key : appName;
330
358
  results.appName = appName;
@@ -383,12 +411,15 @@ async function testExternalSystem(appName, options = {}) {
383
411
  * @returns {Array} Filtered datasource files
384
412
  * @throws {Error} If no datasources found
385
413
  */
386
- function determineDatasourcesToTest(datasourceFiles, datasourceFilter) {
414
+ function determineDatasourcesToTest(datasourceFiles, datasourceFilter, options = {}) {
387
415
  const datasourcesToTest = datasourceFilter
388
416
  ? datasourceFiles.filter(ds => ds.data.key === datasourceFilter || path.basename(ds.path).includes(datasourceFilter))
389
417
  : datasourceFiles;
390
418
 
391
419
  if (datasourcesToTest.length === 0) {
420
+ if (options.allowEmptyDatasources === true) {
421
+ return [];
422
+ }
392
423
  throw new Error('No datasources found to test');
393
424
  }
394
425
 
@@ -429,13 +460,18 @@ async function prepareIntegrationTestEnvironment(appName, options) {
429
460
  }
430
461
 
431
462
  const systemKey = systemFiles[0].data.key;
463
+ const systemKind = resolvePrimarySystemKind(systemFiles);
464
+ const allowEmptyDatasources = allowEmptyDatasourcesForKind(systemKind);
432
465
 
433
466
  // Setup authentication and dataplane URL
434
467
  const config = await getConfig();
435
468
  const { authConfig, dataplaneUrl } = await setupIntegrationTestAuth(appName, options, config);
436
469
 
437
470
  // Determine datasources to test
438
- const datasourcesToTest = determineDatasourcesToTest(datasourceFiles, options.datasource);
471
+ const datasourcesToTest = determineDatasourcesToTest(datasourceFiles, options.datasource, {
472
+ allowEmptyDatasources,
473
+ systemKindForEmptyDatasources: systemKind
474
+ });
439
475
 
440
476
  // Load custom payload if provided
441
477
  const customPayload = await testHelpers.loadCustomPayload(options.payload);
@@ -497,8 +533,8 @@ async function testExternalSystemIntegration(appName, options = {}) {
497
533
  if (options.debug) {
498
534
  try {
499
535
  const { writeTestLog } = require('../utils/test-log-writer');
500
- const { getIntegrationPath } = require('../utils/paths');
501
- const appPath = getIntegrationPath(appName);
536
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
537
+ const appPath = resolveExternalSystemPath(appName);
502
538
  await writeTestLog(appName, { request: { systemKey }, error: err.message }, 'test-integration', path.dirname(appPath));
503
539
  } catch (_) { /* ignore */ }
504
540
  }
@@ -525,8 +561,8 @@ async function testExternalSystemIntegration(appName, options = {}) {
525
561
  if (options.debug && results.datasourceResults.length > 0) {
526
562
  try {
527
563
  const { writeTestLog } = require('../utils/test-log-writer');
528
- const { getIntegrationPath } = require('../utils/paths');
529
- const appPath = getIntegrationPath(appName);
564
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
565
+ const appPath = resolveExternalSystemPath(appName);
530
566
  await writeTestLog(appName, {
531
567
  request: { systemKey, datasource: options.datasource, includeDebug: true },
532
568
  response: results
@@ -27,24 +27,18 @@ function sanitizeAuthType(authType) {
27
27
  }
28
28
 
29
29
  /**
30
- * Filters configuration based on registry mode
31
- * When registryMode is "external", only DOCKER_REGISTRY_SERVER_* variables are allowed
30
+ * Historically (incorrectly) stripped all non-DOCKER_REGISTRY_* vars when
31
+ * registryMode was "external", which wiped platform app env/portal config on
32
+ * `aifabrix json`. External only means image pull source — keep full configuration.
33
+ * DOCKER_REGISTRY_SERVER_* may still appear when present in env.template.
34
+ *
32
35
  * @function filterConfigurationByRegistryMode
33
36
  * @param {Array} configuration - Environment configuration
34
- * @param {string} registryMode - Registry mode ('external' or 'internal')
35
- * @returns {Array} Filtered configuration
37
+ * @param {string} _registryMode - Registry mode ('external' or 'acr'); unused
38
+ * @returns {Array} Configuration unchanged
36
39
  */
37
- function filterConfigurationByRegistryMode(configuration, registryMode) {
38
- if (registryMode !== 'external') {
39
- return configuration;
40
- }
41
-
42
- const allowedDockerRegistryVars = [
43
- 'DOCKER_REGISTRY_SERVER_URL',
44
- 'DOCKER_REGISTRY_SERVER_USERNAME',
45
- 'DOCKER_REGISTRY_SERVER_PASSWORD'
46
- ];
47
- return configuration.filter(config => allowedDockerRegistryVars.includes(config.name));
40
+ function filterConfigurationByRegistryMode(configuration, _registryMode) {
41
+ return Array.isArray(configuration) ? configuration : [];
48
42
  }
49
43
 
50
44
  function buildImageReference(variables) {
@@ -276,6 +270,9 @@ function validateBuildFields(build) {
276
270
  if (typeof build.imageRunBeat === 'string' && build.imageRunBeat.trim()) {
277
271
  buildConfig.imageRunBeat = build.imageRunBeat.trim();
278
272
  }
273
+ if (typeof build.deployJob === 'string' && build.deployJob.trim()) {
274
+ buildConfig.deployJob = build.deployJob.trim();
275
+ }
279
276
 
280
277
  return Object.keys(buildConfig).length > 0 ? buildConfig : null;
281
278
  }
@@ -213,12 +213,21 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
213
213
  const fmt = format === 'json' ? 'json' : 'yaml';
214
214
  const ext = FORMAT_EXT[fmt] || '.yaml';
215
215
  const { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs } = await prepareWizardContext(appName, systemConfig, datasourceConfigs);
216
+ const newDatasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs, fmt);
217
+ const existingDatasourceFileNames = listExistingDatasourceFileNames(appPath, finalSystemKey, ext);
218
+ const datasourceFileNames = [...new Set([...existingDatasourceFileNames, ...newDatasourceFileNames])];
219
+ const existingDatasourceKeys = datasourceFileNames
220
+ .map((name) => datasourceFileNameToKey(name, finalSystemKey, ext))
221
+ .filter(Boolean);
216
222
  const systemConfigWithDataSourcesKeys = {
217
223
  ...updatedSystemConfig,
218
- dataSources: updatedDatasourceConfigs.map(ds => ds.key)
224
+ dataSources: [...new Set([
225
+ ...(Array.isArray(updatedSystemConfig.dataSources) ? updatedSystemConfig.dataSources : []),
226
+ ...existingDatasourceKeys,
227
+ ...updatedDatasourceConfigs.map((ds) => ds.key).filter(Boolean)
228
+ ])]
219
229
  };
220
230
  const systemFilePath = await writeSystemYamlFile(appPath, finalSystemKey, systemConfigWithDataSourcesKeys, fmt);
221
- const datasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs, fmt);
222
231
  const systemFileName = `${finalSystemKey}-system${ext}`;
223
232
  const configFiles = await generateConfigFilesForWizard({
224
233
  appPath,
@@ -234,7 +243,7 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
234
243
  return {
235
244
  appPath,
236
245
  systemFilePath,
237
- datasourceFilePaths: datasourceFileNames.map(name => path.join(appPath, name)),
246
+ datasourceFilePaths: newDatasourceFileNames.map(name => path.join(appPath, name)),
238
247
  ...configFiles
239
248
  };
240
249
  } catch (error) {
@@ -242,6 +251,40 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
242
251
  }
243
252
  }
244
253
 
254
+ /**
255
+ * List existing datasource files so add-datasource merges instead of replacing.
256
+ * @param {string} appPath
257
+ * @param {string} finalSystemKey
258
+ * @param {string} ext
259
+ * @returns {string[]}
260
+ */
261
+ function listExistingDatasourceFileNames(appPath, finalSystemKey, ext) {
262
+ const fsSync = require('fs');
263
+ if (!fsSync.existsSync(appPath)) {
264
+ return [];
265
+ }
266
+ const prefix = `${finalSystemKey}-datasource-`;
267
+ return fsSync
268
+ .readdirSync(appPath)
269
+ .filter((name) => name.startsWith(prefix) && name.endsWith(ext));
270
+ }
271
+
272
+ /**
273
+ * Map `systemKey-datasource-companies.json` → `systemKey-companies`.
274
+ * @param {string} fileName
275
+ * @param {string} finalSystemKey
276
+ * @param {string} ext
277
+ * @returns {string|null}
278
+ */
279
+ function datasourceFileNameToKey(fileName, finalSystemKey, ext) {
280
+ const prefix = `${finalSystemKey}-datasource-`;
281
+ if (!fileName.startsWith(prefix) || !fileName.endsWith(ext)) {
282
+ return null;
283
+ }
284
+ const segment = fileName.slice(prefix.length, fileName.length - ext.length);
285
+ return segment ? `${finalSystemKey}-${segment}` : null;
286
+ }
287
+
245
288
  function mergeAppAndExternalIntegration(variables, appName, systemFileName, datasourceFileNames, systemConfig) {
246
289
  if (!variables.app) {
247
290
  variables.app = {
@@ -257,10 +300,14 @@ function mergeAppAndExternalIntegration(variables, appName, systemFileName, data
257
300
  if (!variables.deployment) {
258
301
  variables.deployment = { controllerUrl: '', environment: 'dev' };
259
302
  }
303
+ const existingDs = Array.isArray(variables.externalIntegration?.dataSources)
304
+ ? variables.externalIntegration.dataSources
305
+ : [];
306
+ const mergedDataSources = [...new Set([...existingDs, ...datasourceFileNames])];
260
307
  variables.externalIntegration = {
261
308
  schemaBasePath: './',
262
309
  systems: [systemFileName],
263
- dataSources: datasourceFileNames,
310
+ dataSources: mergedDataSources,
264
311
  autopublish: true,
265
312
  version: systemConfig.version || '1.0.0'
266
313
  };