@aifabrix/builder 2.51.1 → 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 (69) hide show
  1. package/lib/app/run-container-start.js +4 -0
  2. package/lib/app/run-deploy-job.js +135 -27
  3. package/lib/app/run-env-compose.js +32 -0
  4. package/lib/app/run-env-recovery.js +35 -0
  5. package/lib/app/run-helpers.js +5 -6
  6. package/lib/app/run.js +11 -3
  7. package/lib/channels/approval-guides/chatgpt.js +12 -9
  8. package/lib/channels/channel-targets.js +18 -1
  9. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  10. package/lib/cli/infra-guided-footers.js +1 -1
  11. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  12. package/lib/cli/infra-guided.js +65 -56
  13. package/lib/cli/setup-app.help.js +4 -0
  14. package/lib/cli/setup-app.test-commands.js +59 -28
  15. package/lib/cli/setup-external-system.js +50 -12
  16. package/lib/commands/channel-add-setup.js +8 -2
  17. package/lib/commands/channel.js +16 -5
  18. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  19. package/lib/commands/governance-verify-external.js +2 -5
  20. package/lib/commands/lifecycle-external.js +14 -7
  21. package/lib/commands/role-assistant.js +66 -8
  22. package/lib/commands/test-governance-external.js +2 -5
  23. package/lib/commands/up-miso.js +61 -24
  24. package/lib/commands/upload-data-sync-finalize.js +20 -3
  25. package/lib/commands/verify-governance-external.js +2 -5
  26. package/lib/commands/verify-operations-external.js +2 -5
  27. package/lib/commands/verify-operations-step-plan.js +31 -0
  28. package/lib/commands/verify-trust-command-action.js +2 -5
  29. package/lib/datasource/datasource-identifier.js +4 -3
  30. package/lib/datasource/integration-context.js +3 -3
  31. package/lib/datasource/resolve-app.js +90 -8
  32. package/lib/datasource/test-e2e.js +2 -2
  33. package/lib/datasource/unified-validation-run-body.js +15 -1
  34. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  35. package/lib/external-system/deploy-helpers.js +9 -5
  36. package/lib/external-system/deploy.js +2 -1
  37. package/lib/external-system/test-system-level.js +21 -3
  38. package/lib/external-system/test.js +4 -4
  39. package/lib/generator/builders.js +9 -15
  40. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  41. package/lib/role-assistant/knowledge-sync.js +25 -5
  42. package/lib/role-assistant/test-runner-workhub-answers.js +44 -26
  43. package/lib/role-assistant/test-runner-workhub.js +21 -7
  44. package/lib/schema/application-schema.json +10 -28
  45. package/lib/schema/infra.parameter.yaml +0 -20
  46. package/lib/utils/certify-external-workspace.js +40 -0
  47. package/lib/utils/docker-run-ephemeral.js +77 -17
  48. package/lib/utils/health-check-public-warn.js +31 -13
  49. package/lib/utils/health-check.js +11 -1
  50. package/lib/utils/paths.js +13 -15
  51. package/lib/utils/role-assistant-paths.js +76 -14
  52. package/lib/utils/upload-sync-options.js +7 -4
  53. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  54. package/lib/validation/validate.js +54 -19
  55. package/package.json +1 -1
  56. package/templates/applications/builder-api/application.yaml +3 -3
  57. package/templates/applications/dataplane/application.yaml +3 -4
  58. package/templates/applications/dataplane/env.template +2 -2
  59. package/templates/applications/dataplane/rbac.yaml +10 -10
  60. package/templates/applications/keycloak/application.yaml +2 -2
  61. package/templates/applications/keycloak/env.template +5 -2
  62. package/templates/applications/miso-controller/application.yaml +3 -3
  63. package/templates/applications/miso-controller/env.template +16 -3
  64. package/templates/channels/chatgpt/install.template.md.hbs +75 -37
  65. package/templates/channels/chatgpt/integration/README.md +3 -2
  66. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  67. package/templates/channels/slack/install.template.md.hbs +6 -1
  68. package/templates/python/docker-compose.hbs +10 -0
  69. package/templates/typescript/docker-compose.hbs +10 -0
@@ -23,12 +23,9 @@ const { syncLocalIfRequested } = require('./test-e2e-external');
23
23
  */
24
24
  async function runTestGovernanceForExternalSystem(externalSystem, options = {}) {
25
25
  const pathsUtil = require('../utils/paths');
26
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
26
27
  const appType = await pathsUtil.detectAppType(externalSystem).catch(() => null);
27
- if (!appType || appType.baseDir !== 'integration') {
28
- throw new Error(
29
- 'verify-governance applies to external integration folders only (integration/<systemKey>/).'
30
- );
31
- }
28
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'verify-governance');
32
29
 
33
30
  const systemKey = externalSystem;
34
31
  const packPath = resolveGovernancePackPath(systemKey, {
@@ -41,40 +41,73 @@ function parseImageOptions(imageOpts) {
41
41
  return map;
42
42
  }
43
43
 
44
- /**
45
- * Build run options and run keycloak, then miso-controller
46
- * @async
47
- * @param {Object} options - Commander options (image, registry, registryMode)
48
- */
49
- async function runMisoApps(options) {
50
- const imageMap = parseImageOptions(options.image);
44
+ function buildPlatformCommonRunOpts(options) {
51
45
  const useBaseImage = options.base !== false;
52
- const common = {
46
+ return {
53
47
  registry: options.registry,
54
48
  registryMode: options.registryMode,
55
49
  skipEnvOutputPath: true,
56
50
  skipInfraCheck: true,
57
51
  base: useBaseImage,
58
- skipManifestMetadataLine: true
52
+ skipManifestMetadataLine: true,
53
+ ...(options.platformInstall === true ? { silentPoll: true } : {})
59
54
  };
60
- const platformPollOpts =
61
- options.platformInstall === true
62
- ? { silentPoll: true }
63
- : {};
64
- const keycloakRunOpts = { ...common, ...platformPollOpts };
55
+ }
56
+
57
+ function applyDeferredPublicUrlWarning(runOpts, options) {
58
+ if (typeof options.onDeferredPublicUrlWarning !== 'function') {
59
+ return runOpts;
60
+ }
61
+ return {
62
+ ...runOpts,
63
+ deferPublicUrlWarning: true,
64
+ onDeferredPublicUrlWarning: options.onDeferredPublicUrlWarning
65
+ };
66
+ }
67
+
68
+ function buildKeycloakRunOpts(options, imageMap) {
69
+ const runOpts = applyDeferredPublicUrlWarning(buildPlatformCommonRunOpts(options), options);
65
70
  if (imageMap.keycloak) {
66
- keycloakRunOpts.image = imageMap.keycloak;
71
+ runOpts.image = imageMap.keycloak;
67
72
  }
68
- const misoRunOpts = { ...common, ...platformPollOpts };
73
+ return runOpts;
74
+ }
75
+
76
+ function buildMisoControllerRunOpts(options, imageMap) {
77
+ const runOpts = applyDeferredPublicUrlWarning(buildPlatformCommonRunOpts(options), options);
69
78
  if (imageMap['miso-controller']) {
70
- misoRunOpts.image = imageMap['miso-controller'];
79
+ runOpts.image = imageMap['miso-controller'];
71
80
  }
81
+ if (typeof options.onMisoPollText === 'function') {
82
+ runOpts.onPollText = options.onMisoPollText;
83
+ delete runOpts.silentPoll;
84
+ }
85
+ return runOpts;
86
+ }
87
+
88
+ /**
89
+ * Build run options and run keycloak, then miso-controller
90
+ * @async
91
+ * @param {Object} options - Commander options (image, registry, registryMode)
92
+ */
93
+ async function runMisoApps(options) {
94
+ const imageMap = parseImageOptions(options.image);
95
+ const keycloakRunOpts = buildKeycloakRunOpts(options, imageMap);
96
+ const misoRunOpts = buildMisoControllerRunOpts(options, imageMap);
72
97
  const { emitSystemBuilderAppManifestLineIfTTY } = require('../utils/manifest-source-emit');
73
- logger.log(chalk.blue('Starting keycloak...'));
74
- emitSystemBuilderAppManifestLineIfTTY(logger, 'keycloak');
98
+ const platformInstall = options.platformInstall === true;
99
+ if (!platformInstall) {
100
+ logger.log(chalk.blue('Starting keycloak...'));
101
+ emitSystemBuilderAppManifestLineIfTTY(logger, 'keycloak');
102
+ }
75
103
  await app.runApp('keycloak', keycloakRunOpts);
76
- logger.log(chalk.blue('Starting miso-controller...'));
77
- emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
104
+ if (typeof options.onAfterKeycloak === 'function') {
105
+ options.onAfterKeycloak();
106
+ }
107
+ if (!platformInstall) {
108
+ logger.log(chalk.blue('Starting miso-controller...'));
109
+ emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
110
+ }
78
111
  await app.runApp('miso-controller', misoRunOpts);
79
112
  }
80
113
 
@@ -91,7 +124,9 @@ async function runMisoApps(options) {
91
124
  * @throws {Error} If infra not up or any step fails
92
125
  */
93
126
  async function handleUpMiso(options = {}) {
94
- logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
127
+ if (options.platformInstall !== true) {
128
+ logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
129
+ }
95
130
  await assertDevInfraUp();
96
131
  await ensureAppFromTemplate('keycloak');
97
132
  await ensureAppFromTemplate('miso-controller');
@@ -103,8 +138,10 @@ async function handleUpMiso(options = {}) {
103
138
  patchEnvOutputPathForDeployOnly('miso-controller');
104
139
  refreshUrlsLocalRegistryForCurrentProject();
105
140
  await runMisoApps(options);
106
- logger.log(formatSuccessParagraph('up-miso complete. Keycloak and miso-controller are running.') +
107
- chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
141
+ if (options.platformInstall !== true) {
142
+ logger.log(formatSuccessParagraph('up-miso complete. Keycloak and miso-controller are running.') +
143
+ chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
144
+ }
108
145
  }
109
146
 
110
147
  module.exports = { handleUpMiso, parseImageOptions };
@@ -8,9 +8,18 @@
8
8
 
9
9
  const { runPostPublishDataSync } = require('./run-post-publish-data-sync');
10
10
  const { runUploadSidecarFollowups } = require('./upload-sidecar-followups');
11
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
11
+ const { formatSuccessLine, formatWarningLine, metadata } = require('../utils/cli-test-layout-chalk');
12
12
  const logger = require('../utils/logger');
13
- const { isQuietMinimalSync } = require('../utils/upload-sync-options');
13
+
14
+ /**
15
+ * Internal verify/test --sync / deploy-preflight paths suppress upload banners.
16
+ * Default CLI `aifabrix upload` uses minimal=true but must still announce "unchanged".
17
+ * @param {Object} [options]
18
+ * @returns {boolean}
19
+ */
20
+ function suppressUnchangedUploadBanner(options = {}) {
21
+ return options.syncMode === true || options.suppressSummary === true;
22
+ }
14
23
 
15
24
  /**
16
25
  * @async
@@ -37,8 +46,16 @@ async function finalizeUploadPublishResult(ctx, payload, options) {
37
46
  */
38
47
  async function finishUnchangedUploadPublication(params) {
39
48
  const { systemKey, options, payload, dataplaneUrl, authConfig, environment } = params;
40
- if (!isQuietMinimalSync(options)) {
49
+ if (!suppressUnchangedUploadBanner(options)) {
50
+ logger.log('');
41
51
  logger.log(formatSuccessLine('Config unchanged on dataplane — upload skipped.'));
52
+ logger.log(metadata(`System: ${systemKey}`));
53
+ if (options.probe === true) {
54
+ logger.log(
55
+ formatWarningLine('Probe skipped (nothing published). Use --force to republish, then --probe.')
56
+ );
57
+ }
58
+ logger.log('');
42
59
  }
43
60
  const sidecar = await runUploadSidecarFollowups({
44
61
  systemKey,
@@ -53,12 +53,9 @@ function buildDbPackResult(resolvedKey, datasourceRows) {
53
53
  */
54
54
  async function runVerifyGovernanceForExternalSystem(systemKey, options = {}) {
55
55
  const pathsUtil = require('../utils/paths');
56
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
56
57
  const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
57
- if (!appType || appType.baseDir !== 'integration') {
58
- throw new Error(
59
- 'verify-governance applies to external integration folders only (integration/<systemKey>/).'
60
- );
61
- }
58
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'verify-governance');
62
59
 
63
60
  if (options.pack) {
64
61
  const runPayload = await runTestGovernanceForExternalSystem(systemKey, options);
@@ -162,12 +162,9 @@ async function buildOperationsResult({
162
162
  */
163
163
  async function assertIntegrationExternalSystem(systemKey) {
164
164
  const pathsUtil = require('../utils/paths');
165
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
165
166
  const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
166
- if (!appType || appType.baseDir !== 'integration') {
167
- throw new Error(
168
- 'verify-operations applies to external integration folders only (integration/<systemKey>/).'
169
- );
170
- }
167
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'verify-operations');
171
168
  }
172
169
 
173
170
  /**
@@ -66,6 +66,17 @@ function buildE2eStep(systemKey, options) {
66
66
  ok: () => true
67
67
  };
68
68
  }
69
+ if (shouldSkipE2eForRoleAssistant(systemKey, options)) {
70
+ return {
71
+ key: 'e2e',
72
+ run: async() => ({
73
+ success: true,
74
+ skipped: true,
75
+ reason: 'role-assistant-auth-none'
76
+ }),
77
+ ok: () => true
78
+ };
79
+ }
69
80
  return {
70
81
  key: 'e2e',
71
82
  run: (progress) =>
@@ -88,6 +99,26 @@ function buildE2eStep(systemKey, options) {
88
99
  };
89
100
  }
90
101
 
102
+ /**
103
+ * Role Assistant packages use authType none + nested builder-api CIP.
104
+ * Live capacity E2E expects a system baseUrl credential and is not the RA certify path.
105
+ * @param {string} systemKey
106
+ * @param {Object} options
107
+ * @returns {boolean}
108
+ */
109
+ function shouldSkipE2eForRoleAssistant(systemKey, options) {
110
+ if (options.forceE2e === true) {
111
+ return false;
112
+ }
113
+ try {
114
+ const { resolveAppWorkspace } = require('../utils/app-path-resolve');
115
+ const { baseDir } = resolveAppWorkspace(systemKey, {});
116
+ return baseDir === 'role-assistant';
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
91
122
  /**
92
123
  * @param {string} systemKey
93
124
  * @param {Object} options
@@ -17,12 +17,9 @@ const { buildJsonEnvelope } = require('../lifecycle/json-envelope');
17
17
  async function runVerifyTrustCommandAction(appName, options, cmd) {
18
18
  const opts = normalizeTestTrustCliOptions(options, cmd);
19
19
  const pathsUtil = require('../utils/paths');
20
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
20
21
  const appType = await pathsUtil.detectAppType(appName).catch(() => null);
21
- if (!appType || appType.baseDir !== 'integration') {
22
- throw new Error(
23
- 'verify-trust applies to external integration folders only (integration/<systemKey>/).'
24
- );
25
- }
22
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'verify-trust');
26
23
 
27
24
  const result = await runVerifyTrustForExternalSystem(appName, {
28
25
  env: opts.env,
@@ -7,7 +7,7 @@
7
7
  'use strict';
8
8
 
9
9
  const path = require('path');
10
- const { getIntegrationPath } = require('../utils/paths');
10
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
11
11
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
12
12
  const { loadConfigFile } = require('../utils/config-format');
13
13
 
@@ -65,12 +65,13 @@ function pickIntegrationAppForDatasourceIdentifier(identifier, appNames) {
65
65
  }
66
66
 
67
67
  /**
68
- * List manifest datasource files for an integration app.
68
+ * List manifest datasource files for an external authoring app
69
+ * (integration/, role-assistant/, or enterprise-knowledge/).
69
70
  * @param {string} appKey
70
71
  * @returns {{ appPath: string, schemaBasePath: string, datasourceFiles: string[] }|null}
71
72
  */
72
73
  function listDatasourceFilesForApp(appKey) {
73
- const appPath = getIntegrationPath(appKey);
74
+ const appPath = resolveExternalSystemPath(appKey);
74
75
  let config;
75
76
  try {
76
77
  const configPath = resolveApplicationConfigPath(appPath);
@@ -6,7 +6,7 @@
6
6
 
7
7
  const path = require('path');
8
8
  const fs = require('fs').promises;
9
- const { getIntegrationPath } = require('../utils/paths');
9
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
10
10
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
11
11
  const { loadConfigFile } = require('../utils/config-format');
12
12
  const {
@@ -15,11 +15,11 @@ const {
15
15
  } = require('./datasource-identifier');
16
16
 
17
17
  /**
18
- * @param {string} appKey - Integration app key
18
+ * @param {string} appKey - Integration / role-assistant / EK app key
19
19
  * @returns {Promise<string>} systemKey
20
20
  */
21
21
  async function getSystemKeyFromAppKey(appKey) {
22
- const appPath = getIntegrationPath(appKey);
22
+ const appPath = resolveExternalSystemPath(appKey);
23
23
  const configPath = resolveApplicationConfigPath(appPath);
24
24
  const config = loadConfigFile(configPath);
25
25
  if (!config.externalIntegration || !config.externalIntegration.systems || config.externalIntegration.systems.length === 0) {
@@ -2,15 +2,20 @@
2
2
  * Resolve app key for datasource commands from explicit --app, cwd, scan, or key parse.
3
3
  * @fileoverview App resolution for datasource test-e2e, test-integration, log-e2e, log-integration
4
4
  * @author AI Fabrix Team
5
- * @version 2.0.0
5
+ * @version 2.1.0
6
6
  */
7
7
 
8
+ 'use strict';
9
+
8
10
  const fs = require('fs');
9
11
  const {
10
- getIntegrationPath,
11
12
  listIntegrationAppNames,
13
+ listEnterpriseKnowledgeAnchors,
14
+ getRoleAssistantRoot,
15
+ getCwdRoleAssistantRoot,
12
16
  resolveIntegrationAppKeyFromCwd
13
17
  } = require('../utils/paths');
18
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
14
19
  const {
15
20
  appHasDatasourceIdentifier,
16
21
  pickIntegrationAppForDatasourceIdentifier
@@ -21,6 +26,80 @@ function appHasDatasourceKey(appKey, datasourceKey) {
21
26
  return appHasDatasourceIdentifier(appKey, datasourceKey);
22
27
  }
23
28
 
29
+ /**
30
+ * @param {string} appKey
31
+ * @returns {boolean}
32
+ */
33
+ function externalAppPathExists(appKey) {
34
+ try {
35
+ const appPath = resolveExternalSystemPath(appKey);
36
+ return Boolean(appPath && fs.existsSync(appPath));
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {string} root
44
+ * @param {Set<string>} diskNames
45
+ */
46
+ function addRoleAssistantAppDirs(root, diskNames) {
47
+ if (!root || !fs.existsSync(root)) {
48
+ return;
49
+ }
50
+ let entries;
51
+ try {
52
+ entries = fs.readdirSync(root);
53
+ } catch {
54
+ return;
55
+ }
56
+ const pathJoin = require('path').join;
57
+ for (const name of entries) {
58
+ if (!name || name.startsWith('.')) {
59
+ continue;
60
+ }
61
+ try {
62
+ if (fs.statSync(pathJoin(root, name)).isDirectory()) {
63
+ diskNames.add(name);
64
+ }
65
+ } catch {
66
+ // skip
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * List app folders under role-assistant/ (cwd + materialization).
73
+ * @returns {string[]}
74
+ */
75
+ function listRoleAssistantAppNames() {
76
+ const diskNames = new Set();
77
+ const roots = new Set();
78
+ const cwdRoot = getCwdRoleAssistantRoot();
79
+ if (cwdRoot) {
80
+ roots.add(cwdRoot);
81
+ }
82
+ roots.add(getRoleAssistantRoot());
83
+ for (const root of roots) {
84
+ addRoleAssistantAppDirs(root, diskNames);
85
+ }
86
+ return [...diskNames].sort();
87
+ }
88
+
89
+ /**
90
+ * External authoring apps: integration + role-assistant + enterprise-knowledge.
91
+ * @returns {string[]}
92
+ */
93
+ function listExternalAuthoringAppNames() {
94
+ return [
95
+ ...new Set([
96
+ ...listIntegrationAppNames(),
97
+ ...listRoleAssistantAppNames(),
98
+ ...listEnterpriseKnowledgeAnchors()
99
+ ])
100
+ ].sort();
101
+ }
102
+
24
103
  /**
25
104
  * @param {string} key
26
105
  * @param {string} [explicitApp]
@@ -31,7 +110,7 @@ function resolveFromExplicitApp(key, explicitApp) {
31
110
  return null;
32
111
  }
33
112
  const appKey = explicitApp.trim();
34
- if (fs.existsSync(getIntegrationPath(appKey))) {
113
+ if (externalAppPathExists(appKey)) {
35
114
  return { appKey };
36
115
  }
37
116
  return null;
@@ -71,15 +150,16 @@ function resolveFromPrefixOrSegments(key, appNames) {
71
150
  return null;
72
151
  }
73
152
  const candidate = segments.slice(0, -1).join('-');
74
- const candidatePath = getIntegrationPath(candidate);
75
- if (fs.existsSync(candidatePath) && appHasDatasourceIdentifier(candidate, key)) {
153
+ if (externalAppPathExists(candidate) && appHasDatasourceIdentifier(candidate, key)) {
76
154
  return { appKey: candidate };
77
155
  }
78
156
  return null;
79
157
  }
80
158
 
81
159
  const RESOLVE_APP_HINT =
82
- 'Could not determine app context. Use --app <app>, a datasource key, a datasource JSON filename (e.g. myapp-datasource-contacts), or run from integration/<systemKey>/ directory.';
160
+ 'Could not determine app context. Use --app <app>, a datasource key, a datasource JSON filename ' +
161
+ '(e.g. myapp-datasource-contacts), or run from integration/<systemKey>/, ' +
162
+ 'role-assistant/<systemKey>/, or enterprise-knowledge/<systemKey>/.';
83
163
 
84
164
  /**
85
165
  * Resolve app key for a datasource: explicit --app, cwd, scan by key, or parse key convention.
@@ -108,7 +188,7 @@ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
108
188
  return { appKey: fromCwd };
109
189
  }
110
190
 
111
- const appNames = listIntegrationAppNames();
191
+ const appNames = listExternalAuthoringAppNames();
112
192
  const scanned = resolveFromAppScan(key, appNames);
113
193
  if (scanned) {
114
194
  return scanned;
@@ -124,5 +204,7 @@ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
124
204
 
125
205
  module.exports = {
126
206
  resolveAppKeyForDatasource,
127
- appHasDatasourceKey
207
+ appHasDatasourceKey,
208
+ listExternalAuthoringAppNames,
209
+ listRoleAssistantAppNames
128
210
  };
@@ -9,7 +9,6 @@ const path = require('path');
9
9
  const fs = require('fs').promises;
10
10
  const chalk = require('chalk');
11
11
  const logger = require('../utils/logger');
12
- const { getIntegrationPath } = require('../utils/paths');
13
12
  const { resolveAppKeyForDatasource } = require('./resolve-app');
14
13
  const { formatProgress, formatSuccessLine, infoLine } = require('../utils/cli-test-layout-chalk');
15
14
  const { runUnifiedDatasourceValidation } = require('./unified-validation-run');
@@ -94,7 +93,8 @@ async function resolvePrimaryKeyValue(value) {
94
93
  }
95
94
 
96
95
  function e2eIntegrationLogDir(appKey) {
97
- return path.dirname(getIntegrationPath(appKey));
96
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
97
+ return path.dirname(resolveExternalSystemPath(appKey));
98
98
  }
99
99
 
100
100
  /**
@@ -12,6 +12,16 @@ const {
12
12
  const testHelpers = require('../utils/external-system-test-helpers');
13
13
  const { withStageOnValidationRunBody } = require('../utils/validation-run-stage');
14
14
 
15
+ /**
16
+ * @param {Object} datasource
17
+ * @returns {boolean}
18
+ */
19
+ function isVoidCipInput(datasource) {
20
+ const execution = datasource && typeof datasource === 'object' ? datasource.execution : null;
21
+ const cip = execution && typeof execution === 'object' ? execution.cip : null;
22
+ return String((cip && cip.inputType) || '').trim().toLowerCase() === 'void';
23
+ }
24
+
15
25
  /**
16
26
  * @param {Object} params
17
27
  * @param {string} params.systemKey
@@ -31,7 +41,11 @@ async function buildUnifiedValidationBody(params) {
31
41
  const customPayload = await testHelpers.loadCustomPayload(payloadPath);
32
42
  payloadTemplate = testHelpers.determinePayloadTemplate(datasource, datasourceKey, customPayload);
33
43
  if (runType === 'integration' && !payloadTemplate) {
34
- throw new Error(`No test payload found for datasource '${datasourceKey}'`);
44
+ if (isVoidCipInput(datasource)) {
45
+ payloadTemplate = {};
46
+ } else {
47
+ throw new Error(`No test payload found for datasource '${datasourceKey}'`);
48
+ }
35
49
  }
36
50
  }
37
51
 
@@ -4,7 +4,7 @@
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
- const { getIntegrationPath } = require('../utils/paths');
7
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
8
8
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
9
9
  const { loadConfigFile } = require('../utils/config-format');
10
10
  const {
@@ -48,7 +48,7 @@ function findDatasourceFileByIdentifier(appPath, schemaBasePath, datasourceFiles
48
48
  * @returns {{ datasource: Object, datasourcePath: string, datasourceKey: string }}
49
49
  */
50
50
  function loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey) {
51
- const appPath = getIntegrationPath(appKey);
51
+ const appPath = resolveExternalSystemPath(appKey);
52
52
  const config = loadConfigFile(resolveApplicationConfigPath(appPath));
53
53
  const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
54
54
  const datasourceFiles = config.externalIntegration?.dataSources || [];
@@ -142,7 +142,7 @@ function extractSystemKey(systemFilePath) {
142
142
  * @param {Object} manifest - Controller manifest
143
143
  * @returns {Promise<Object|null>} Upload result, or null when not a digital worker
144
144
  */
145
- async function uploadDigitalWorkerIfNeeded(appName, options, compact, manifest) {
145
+ async function uploadDigitalWorkerIfNeeded(appName, options, _compact, manifest) {
146
146
  const systemKind =
147
147
  manifest && manifest.system && typeof manifest.system.systemKind === 'string'
148
148
  ? manifest.system.systemKind.trim()
@@ -150,9 +150,9 @@ async function uploadDigitalWorkerIfNeeded(appName, options, compact, manifest)
150
150
  if (systemKind !== 'digitalWorker') {
151
151
  return null;
152
152
  }
153
- if (!compact) {
154
- const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
155
- const logger = require('../utils/logger');
153
+ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
154
+ const logger = require('../utils/logger');
155
+ if (options.json !== true) {
156
156
  logger.log(
157
157
  formatSuccessParagraph(
158
158
  'Role Assistant deployment uses upload pipeline (controller deploy is skipped).'
@@ -162,7 +162,11 @@ async function uploadDigitalWorkerIfNeeded(appName, options, compact, manifest)
162
162
  const { uploadExternalSystem } = require('../commands/upload');
163
163
  return uploadExternalSystem(appName, {
164
164
  ...options,
165
- uploadStatus: 'published'
165
+ uploadStatus: 'published',
166
+ // Match standalone `af upload` progress for the final publish.
167
+ silentResolve: false,
168
+ suppressPipelineSpinner: false,
169
+ syncMode: false
166
170
  });
167
171
  }
168
172
 
@@ -432,7 +432,8 @@ async function runExternalSystemDeployFlow(appName, options) {
432
432
  await validateExternalSystemForDeploy(appName, options, compact);
433
433
  const gateOutcomes = await runDeployGateOrchestrator(appName, {
434
434
  ...options,
435
- quietDeployUi: compact
435
+ // Compact deploy still shows step progress; only JSON mode stays fully quiet.
436
+ quietDeployUi: options.json === true
436
437
  });
437
438
  await syncDeployJsonFromSources(appName);
438
439
  const manifest = await generateControllerManifest(appName, options);
@@ -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,
@@ -533,8 +533,8 @@ async function testExternalSystemIntegration(appName, options = {}) {
533
533
  if (options.debug) {
534
534
  try {
535
535
  const { writeTestLog } = require('../utils/test-log-writer');
536
- const { getIntegrationPath } = require('../utils/paths');
537
- const appPath = getIntegrationPath(appName);
536
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
537
+ const appPath = resolveExternalSystemPath(appName);
538
538
  await writeTestLog(appName, { request: { systemKey }, error: err.message }, 'test-integration', path.dirname(appPath));
539
539
  } catch (_) { /* ignore */ }
540
540
  }
@@ -561,8 +561,8 @@ async function testExternalSystemIntegration(appName, options = {}) {
561
561
  if (options.debug && results.datasourceResults.length > 0) {
562
562
  try {
563
563
  const { writeTestLog } = require('../utils/test-log-writer');
564
- const { getIntegrationPath } = require('../utils/paths');
565
- const appPath = getIntegrationPath(appName);
564
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
565
+ const appPath = resolveExternalSystemPath(appName);
566
566
  await writeTestLog(appName, {
567
567
  request: { systemKey, datasource: options.datasource, includeDebug: true },
568
568
  response: results