@aifabrix/builder 2.51.1 → 2.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -16,29 +16,44 @@ const {
16
16
  sectionTitle,
17
17
  metadata
18
18
  } = require('../utils/cli-test-layout-chalk');
19
- const { CHANNEL_TARGET_KEYS, isChannelTargetKey } = require('../channels/channel-targets');
19
+ const {
20
+ formatSupportedChannelTargetsHelp,
21
+ isChannelTargetKey
22
+ } = require('../channels/channel-targets');
20
23
  const { scaffoldChannelIntegration } = require('../channels/channel-integration-generator');
21
24
  const { registerChannelAddCommand } = require('./channel-add-setup');
22
25
 
23
26
  const CHANNEL_HELP_AFTER = `
24
27
  Subcommands:
25
- scaffold <target> Create integration/<target>/ files locally (step 1)
26
- add <target> Channel install guide + manifest/credential patch (step 3)
28
+ add <target> One command: scaffold (if needed) → upload (if needed/--force) → install
29
+ scaffold <target> Optional: create integration/<target>/ files only (local)
30
+
31
+ ${formatSupportedChannelTargetsHelp()}
27
32
 
28
- Targets:
29
- teams-bot, slack, microsoft-copilot, chatgpt
30
- web-embed (experimental — scaffold with --allow-experimental)
33
+ Typical flow:
34
+ aifabrix channel add slack --name "Elsa" --probe
31
35
 
32
- Typical flow (channels are connected systems, not controller apps):
36
+ # Force re-upload of the communication system, then reinstall channel
37
+ aifabrix channel add slack --name "Elsa" --force --probe
38
+
39
+ Optional low-level steps (usually unnecessary):
33
40
  aifabrix channel scaffold slack
34
41
  aifabrix upload slack --force
35
- aifabrix channel add slack --name "Elsa" --probe
36
42
 
37
43
  Controller pipeline apps only: aifabrix app register · aifabrix app list
38
44
 
39
45
  Requires: aifabrix login (for add)
40
46
  `;
41
47
 
48
+ const CHANNEL_SCAFFOLD_HELP_AFTER = `
49
+ ${formatSupportedChannelTargetsHelp()}
50
+
51
+ Prefer one command (scaffold + upload + install):
52
+ aifabrix channel add slack --name "Your Assistant" --probe
53
+
54
+ scaffold alone only writes local files under integration/<target>/.
55
+ `;
56
+
42
57
  /**
43
58
  * @param {import('commander').Command} program
44
59
  */
@@ -46,7 +61,7 @@ function setupChannelCommands(program) {
46
61
  const channel = program
47
62
  .command('channel')
48
63
  .description(
49
- 'Assistant channels (Slack, Teams, Copilot, ChatGPT) — scaffold integration and install'
64
+ 'Assistant channels — one-command add (scaffold → upload → install), plus optional scaffold'
50
65
  )
51
66
  .addHelpText('after', CHANNEL_HELP_AFTER);
52
67
 
@@ -57,11 +72,12 @@ function setupChannelCommands(program) {
57
72
  )
58
73
  .option('--force', 'Overwrite generated scaffold files when folder exists')
59
74
  .option('--allow-experimental', 'Allow web-embed scaffold')
75
+ .addHelpText('after', CHANNEL_SCAFFOLD_HELP_AFTER)
60
76
  .action(async(channelTarget, options) => {
61
77
  try {
62
78
  if (!isChannelTargetKey(channelTarget)) {
63
79
  throw new Error(
64
- `Unknown channel target "${channelTarget}". Expected: ${CHANNEL_TARGET_KEYS.join(', ')}`
80
+ `Unknown channel target "${channelTarget}". ${formatSupportedChannelTargetsHelp()}`
65
81
  );
66
82
  }
67
83
  const result = await scaffoldChannelIntegration(channelTarget, {
@@ -75,8 +91,7 @@ function setupChannelCommands(program) {
75
91
  logger.log(metadata(file));
76
92
  }
77
93
  logger.log('');
78
- logger.log(chalk.white('Next steps:'));
79
- logger.log(chalk.gray(` aifabrix upload ${channelTarget} --force`));
94
+ logger.log(chalk.white('Next step (recommended):'));
80
95
  logger.log(
81
96
  chalk.gray(` aifabrix channel add ${channelTarget} --name "Your Assistant" --probe`)
82
97
  );
@@ -142,7 +142,6 @@ async function runDeployBaselineSteps(systemKey, options) {
142
142
  uploadStatus: 'published',
143
143
  minimal: true,
144
144
  suppressSummary: true,
145
- suppressPipelineSpinner: true,
146
145
  uploadContext: 'deploy-preflight'
147
146
  });
148
147
  stepOptions = mergeUploadAuthOptions({ ...options, uploadStatus: 'published' }, uploadCtx);
@@ -322,7 +321,9 @@ function inferGatePillarFromLabel(label) {
322
321
  * @returns {Promise<{ baseline: { unitPassed: boolean, integrationPassed: boolean }, gates: Object[] }>}
323
322
  */
324
323
  async function runDeployGateOrchestrator(systemKey, options = {}) {
325
- const quiet = options.quietDeployUi === true || options.verbose !== true;
324
+ // Default: show unit/integration/publish progress (parity with upload).
325
+ // Only hush when caller opts in (json / explicit quietDeployUi).
326
+ const quiet = options.quietDeployUi === true;
326
327
  if (!quiet) {
327
328
  logger.log('');
328
329
  logger.log(sectionTitle('Deploy preflight'));
@@ -20,12 +20,9 @@ const { VERDICT, verdictFromPassed } = require('../lifecycle/product-model');
20
20
  */
21
21
  async function runGovernanceVerifyForExternalSystem(systemKey, options = {}) {
22
22
  const pathsUtil = require('../utils/paths');
23
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
23
24
  const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
24
- if (!appType || appType.baseDir !== 'integration') {
25
- throw new Error(
26
- 'governance verify applies to external integration folders only (integration/<systemKey>/).'
27
- );
28
- }
25
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'governance verify');
29
26
 
30
27
  if (options.pack) {
31
28
  const { runTestGovernanceForExternalSystem } = require('./test-governance-external');
@@ -0,0 +1,158 @@
1
+ /**
2
+ * CLI: integration-definition validate | apply
3
+ * @fileoverview BT → Builder BID commands (no section PATCH orchestration)
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /* eslint-disable no-console */
9
+
10
+ const chalk = require('chalk');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const {
14
+ applyIntegrationDefinition,
15
+ validateIntegrationDefinition,
16
+ withDefinitionHash
17
+ } = require('../integration-definition');
18
+ const { handleCommandError } = require('../utils/cli-utils');
19
+ const {
20
+ formatSuccessLine,
21
+ formatBlockingError,
22
+ failureGlyph
23
+ } = require('../utils/cli-layout-chalk');
24
+
25
+ const PARENT_HELP_AFTER = `
26
+
27
+ Examples:
28
+ $ aifabrix integration-definition validate ./bid.json
29
+ $ aifabrix integration-definition apply ./bid.json --dry-run
30
+
31
+ BID is the minimal business create language (systemKind, resourceType, propertyKeys).
32
+ Apply materializes integration/<systemKey>/ package files (EDS/system).
33
+ AI catalog: GET /help?topic=resourceTypeCatalog on builder-api.
34
+ `;
35
+
36
+ const VALIDATE_HELP_AFTER = `
37
+
38
+ Examples:
39
+ $ aifabrix integration-definition validate ./bid.json
40
+ $ aifabrix integration-definition validate ./bid.json --fix-hash
41
+
42
+ Checks JSON Schema, definitionHash, product rules, and active resourceType catalog keys.
43
+ `;
44
+
45
+ const APPLY_HELP_AFTER = `
46
+
47
+ Examples:
48
+ $ aifabrix integration-definition apply ./bid.json
49
+ $ aifabrix integration-definition apply ./bid.json --dry-run
50
+ $ aifabrix integration-definition apply ./bid.json --force
51
+ $ aifabrix integration-definition apply ./bid.json --output-dir /tmp/out
52
+ `;
53
+
54
+ /**
55
+ * @param {string} filePath
56
+ * @returns {Record<string, unknown>}
57
+ */
58
+ function loadBidFile(filePath) {
59
+ const abs = path.resolve(filePath);
60
+ if (!fs.existsSync(abs)) {
61
+ throw new Error(`BID file not found: ${abs}`);
62
+ }
63
+ return JSON.parse(fs.readFileSync(abs, 'utf8'));
64
+ }
65
+
66
+ /**
67
+ * @param {string} file
68
+ * @param {{ fixHash?: boolean }} options
69
+ * @returns {Promise<void>}
70
+ */
71
+ async function runValidateCommand(file, options) {
72
+ let bid = loadBidFile(file);
73
+ if (options.fixHash) {
74
+ bid = withDefinitionHash(bid);
75
+ fs.writeFileSync(path.resolve(file), `${JSON.stringify(bid, null, 2)}\n`, 'utf8');
76
+ console.log(formatSuccessLine(`Wrote definitionHash to ${file}`));
77
+ }
78
+ const result = validateIntegrationDefinition(bid);
79
+ if (!result.valid) {
80
+ console.error(formatBlockingError('BID invalid'));
81
+ for (const err of result.errors) {
82
+ console.error(chalk.red(` ${failureGlyph()} ${err}`));
83
+ }
84
+ process.exit(1);
85
+ }
86
+ console.log(formatSuccessLine('BID valid'));
87
+ console.log(` definitionId: ${result.bid.definitionId}`);
88
+ console.log(` definitionHash: ${result.bid.definitionHash}`);
89
+ }
90
+
91
+ /**
92
+ * @param {string} file
93
+ * @param {{ outputDir?: string, dryRun?: boolean, force?: boolean }} options
94
+ * @returns {Promise<void>}
95
+ */
96
+ async function runApplyCommand(file, options) {
97
+ const bid = loadBidFile(file);
98
+ const out = await applyIntegrationDefinition(bid, {
99
+ outputDir: options.outputDir,
100
+ dryRun: Boolean(options.dryRun),
101
+ force: Boolean(options.force)
102
+ });
103
+ console.log(formatSuccessLine(`BID apply ${out.status}`));
104
+ console.log(` systemKey: ${out.systemKey}`);
105
+ console.log(` outputDir: ${out.outputDir}`);
106
+ console.log(` definitionHash: ${out.definitionHash}`);
107
+ if (out.files) {
108
+ console.log(` files: ${out.files.join(', ')}`);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * @param {import('commander').Command} program
114
+ */
115
+ function setupIntegrationDefinitionCommands(program) {
116
+ const cmd = program
117
+ .command('integration-definition')
118
+ .description(
119
+ 'Validate or apply a Builder Integration Definition (BID) — minimal business create language for integration packages'
120
+ )
121
+ .addHelpText('after', PARENT_HELP_AFTER);
122
+
123
+ cmd
124
+ .command('validate <file>')
125
+ .description(
126
+ 'Validate BID JSON (schema, definitionHash, product rules, resourceType catalog)'
127
+ )
128
+ .option('--fix-hash', 'Rewrite file with computed definitionHash')
129
+ .addHelpText('after', VALIDATE_HELP_AFTER)
130
+ .action(async(file, options) => {
131
+ try {
132
+ await runValidateCommand(file, options);
133
+ } catch (error) {
134
+ handleCommandError(error, 'integration-definition validate');
135
+ }
136
+ });
137
+
138
+ cmd
139
+ .command('apply <file>')
140
+ .description(
141
+ 'Idempotently apply BID to integration/<systemKey>/ (Builder-owned EDS/system writes)'
142
+ )
143
+ .option('--output-dir <dir>', 'Override output directory')
144
+ .option('--force', 'Rewrite package even when definitionHash matches stamp')
145
+ .option('--dry-run', 'Show planned files without writing')
146
+ .addHelpText('after', APPLY_HELP_AFTER)
147
+ .action(async(file, options) => {
148
+ try {
149
+ await runApplyCommand(file, options);
150
+ } catch (error) {
151
+ handleCommandError(error, 'integration-definition apply');
152
+ }
153
+ });
154
+ }
155
+
156
+ module.exports = {
157
+ setupIntegrationDefinitionCommands
158
+ };
@@ -247,23 +247,30 @@ async function executeLifecycleWork(systemKey, options = {}) {
247
247
  */
248
248
  async function runLifecycleForExternalSystem(systemKey, options = {}) {
249
249
  const pathsUtil = require('../utils/paths');
250
+ const { assertCertifyExternalWorkspace } = require('../utils/certify-external-workspace');
250
251
  const appType = await pathsUtil.detectAppType(systemKey).catch(() => null);
251
- const allowedRoots = new Set(['integration', 'role-assistant']);
252
- if (!appType || !allowedRoots.has(appType.baseDir)) {
253
- throw new Error(
254
- 'lifecycle applies to external authoring folders only (integration/<systemKey>/ or role-assistant/<roleKey>/).'
255
- );
252
+ assertCertifyExternalWorkspace(appType && appType.baseDir, 'lifecycle');
253
+
254
+ let resolvedKey = systemKey;
255
+ if (appType.baseDir === 'role-assistant') {
256
+ const { resolveExternalIntegrationContext } = require('./test-e2e-external');
257
+ const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
258
+ try {
259
+ resolvedKey = resolveExternalIntegrationContext(systemKey).systemKey;
260
+ } catch {
261
+ resolvedKey = roleAssistantSystemKey(systemKey);
262
+ }
256
263
  }
257
264
 
258
265
  if (options.run === true) {
259
266
  return runWithVerifyStepProgress(
260
267
  'Processing',
261
- async(progress) => executeLifecycleWork(systemKey, { ...options, lifecycleProgress: progress }),
268
+ async(progress) => executeLifecycleWork(resolvedKey, { ...options, lifecycleProgress: progress }),
262
269
  { json: options.json === true }
263
270
  );
264
271
  }
265
272
 
266
- return executeLifecycleWork(systemKey, options);
273
+ return executeLifecycleWork(resolvedKey, options);
267
274
  }
268
275
 
269
276
  module.exports = {
@@ -28,6 +28,7 @@ const ALLOWED_REPAIR_AUTH = [
28
28
  'oidc',
29
29
  'hmac',
30
30
  'none',
31
+ 'system',
31
32
  'bearerforward'
32
33
  ];
33
34
 
@@ -57,8 +58,12 @@ function isAllowedRepairAuth(authOption) {
57
58
  */
58
59
  function buildAuthenticationForRepair(systemKey, authOption) {
59
60
  const normalized = normalizeRepairAuthOption(authOption);
61
+ if (normalized === 'system') {
62
+ return buildSystemAuthentication();
63
+ }
60
64
  if (normalized === 'bearerforward') {
61
- return buildBearerForwardAuthentication();
65
+ // Legacy alias: prefer authType system for platform facades.
66
+ return buildSystemAuthentication();
62
67
  }
63
68
  const schemaMethod =
64
69
  normalized === 'queryparam'
@@ -70,11 +75,19 @@ function buildAuthenticationForRepair(systemKey, authOption) {
70
75
  }
71
76
 
72
77
  /**
73
- * Facade APIs that forward the caller Bearer token — no workspace kv secret.
74
- * @returns {{ authType: 'none' }}
78
+ * Platform-managed downstream authentication (no workspace kv secret).
79
+ * @returns {{ authType: 'system' }}
80
+ */
81
+ function buildSystemAuthentication() {
82
+ return { authType: 'system' };
83
+ }
84
+
85
+ /**
86
+ * @deprecated Use buildSystemAuthentication — bearerForward is not a public authType.
87
+ * @returns {{ authType: 'system' }}
75
88
  */
76
89
  function buildBearerForwardAuthentication() {
77
- return { authType: 'none' };
90
+ return buildSystemAuthentication();
78
91
  }
79
92
 
80
93
  /**
@@ -99,7 +112,7 @@ function mergePreservedAuthVariables(mergedVariables, existingVars) {
99
112
  function repairAuthChangeLabel(authOption) {
100
113
  const normalized = normalizeRepairAuthOption(authOption);
101
114
  if (normalized === 'bearertoken' || normalized === 'bearerkey') return 'bearerToken';
102
- if (normalized === 'bearerforward') return 'bearerForward';
115
+ if (normalized === 'bearerforward') return 'system';
103
116
  return normalized;
104
117
  }
105
118
 
@@ -157,6 +170,7 @@ module.exports = {
157
170
  normalizeRepairAuthOption,
158
171
  isAllowedRepairAuth,
159
172
  buildAuthenticationForRepair,
173
+ buildSystemAuthentication,
160
174
  buildBearerForwardAuthentication,
161
175
  mergePreservedAuthVariables,
162
176
  repairAuthChangeLabel,
@@ -108,7 +108,10 @@ function ensureSyncOperationScaffold(parsed) {
108
108
  parsed.execution = { engine: 'cip' };
109
109
  }
110
110
  if (!parsed.execution.cip || typeof parsed.execution.cip !== 'object') {
111
- parsed.execution.cip = { operations: {} };
111
+ parsed.execution.cip = { version: '1.0', operations: {} };
112
+ }
113
+ if (!parsed.execution.cip.version) {
114
+ parsed.execution.cip.version = '1.0';
112
115
  }
113
116
  if (!parsed.execution.cip.operations || typeof parsed.execution.cip.operations !== 'object') {
114
117
  parsed.execution.cip.operations = {};
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Role Assistant test CLI envelope + human summary output.
3
+ * @fileoverview Split from role-assistant.js for max-lines / complexity
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const logger = require('../utils/logger');
9
+ const { VERDICT } = require('../lifecycle/product-model');
10
+ const {
11
+ sectionTitle,
12
+ headerKeyValue,
13
+ formatSuccessLine,
14
+ formatSuccessParagraph,
15
+ formatWarningLine,
16
+ formatBlockingError,
17
+ metadata
18
+ } = require('../utils/cli-test-layout-chalk');
19
+
20
+ /**
21
+ * Bucket a lifecycle step status into ok | failed | skipped | other.
22
+ * @param {string} status
23
+ * @returns {'ok'|'failed'|'skipped'|'other'}
24
+ */
25
+ function bucketStepStatus(status) {
26
+ const normalized = String(status || 'ok').toLowerCase();
27
+ if (normalized === 'ok' || normalized === 'verified' || normalized === 'passed') {
28
+ return 'ok';
29
+ }
30
+ if (normalized === 'failed' || normalized === 'error') {
31
+ return 'failed';
32
+ }
33
+ if (normalized === 'skipped' || normalized === 'n/a' || normalized === 'not_applicable') {
34
+ return 'skipped';
35
+ }
36
+ return 'other';
37
+ }
38
+
39
+ /**
40
+ * Summarize lifecycle stepsRun rows (API uses step/status/datasourceKey, not pillar).
41
+ * @param {Array<Object|string>} steps
42
+ * @returns {string}
43
+ */
44
+ function formatStepsRunSummary(steps) {
45
+ const byStep = Object.create(null);
46
+ for (const row of steps) {
47
+ if (!row || typeof row !== 'object') {
48
+ const label = String(row);
49
+ byStep[label] = byStep[label] || { ok: 0, failed: 0, skipped: 0, other: 0 };
50
+ byStep[label].other += 1;
51
+ continue;
52
+ }
53
+ const stepName = String(row.step || row.pillar || row.name || 'step').trim() || 'step';
54
+ const status = row.status || (row.success === false ? 'failed' : 'ok');
55
+ byStep[stepName] = byStep[stepName] || { ok: 0, failed: 0, skipped: 0, other: 0 };
56
+ byStep[stepName][bucketStepStatus(status)] += 1;
57
+ }
58
+ return Object.keys(byStep)
59
+ .map((stepName) => {
60
+ const counts = byStep[stepName];
61
+ const parts = [];
62
+ if (counts.ok) parts.push(`${counts.ok} ok`);
63
+ if (counts.failed) parts.push(`${counts.failed} failed`);
64
+ if (counts.skipped) parts.push(`${counts.skipped} skipped`);
65
+ if (counts.other) parts.push(`${counts.other} other`);
66
+ return parts.length > 0 ? `${stepName} (${parts.join(', ')})` : stepName;
67
+ })
68
+ .join('; ');
69
+ }
70
+
71
+ function mapCaseEnvelopeRow(row) {
72
+ return {
73
+ caseId: row.caseId,
74
+ sourceFile: row.sourceFile,
75
+ passed: row.passed,
76
+ lifecycleState: row.lifecycleState,
77
+ assertions: row.assertions,
78
+ failedAssertions: row.failedAssertions,
79
+ warnings: Array.isArray(row.warnings) ? row.warnings : [],
80
+ execution: row.execution || null
81
+ };
82
+ }
83
+
84
+ function attachEnvelopeCases(envelope, caseResult) {
85
+ if (Array.isArray(caseResult)) {
86
+ envelope.cases = caseResult.map(mapCaseEnvelopeRow);
87
+ const passed = envelope.cases.filter((c) => c.passed).length;
88
+ envelope.summary = {
89
+ total: envelope.cases.length,
90
+ passed,
91
+ failed: envelope.cases.length - passed
92
+ };
93
+ envelope.case = envelope.cases.length === 1 ? envelope.cases[0] : null;
94
+ return;
95
+ }
96
+ if (caseResult) {
97
+ envelope.case = mapCaseEnvelopeRow(caseResult);
98
+ }
99
+ }
100
+
101
+ function buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult = null) {
102
+ const report = result.report || {};
103
+ const multiCase = Array.isArray(caseResult) && caseResult.length > 1;
104
+ const envelope = {
105
+ roleKey,
106
+ command: 'role-assistant test',
107
+ mode: opts.suite || multiCase ? 'suite' : 'single',
108
+ suite: opts.suite || null,
109
+ verdict,
110
+ portalUrl,
111
+ openApiPublished: result.openApiPublished === true,
112
+ certification: report.certification || null,
113
+ recommendations: Array.isArray(report.recommendations) ? report.recommendations : [],
114
+ warnings: Array.isArray(report.warnings) ? report.warnings : [],
115
+ stepsRun: result.stepsRun || [],
116
+ case: null,
117
+ cases: null,
118
+ summary: null
119
+ };
120
+ attachEnvelopeCases(envelope, caseResult);
121
+ if (opts.verbose || opts.debug) {
122
+ envelope.report = report;
123
+ }
124
+ if (opts.debug) {
125
+ envelope.debug = {
126
+ observation: caseResult && !Array.isArray(caseResult) ? caseResult.observation : null,
127
+ lifecycleResult: result
128
+ };
129
+ }
130
+ return envelope;
131
+ }
132
+
133
+ function displayCaseResultSummary(caseResult) {
134
+ if (!caseResult) {
135
+ return;
136
+ }
137
+ logger.log(headerKeyValue('Case:', caseResult.caseId));
138
+ logger.log(headerKeyValue('Case file:', caseResult.sourceFile));
139
+ logger.log(headerKeyValue('Observed state:', caseResult.lifecycleState));
140
+ if (caseResult.execution) {
141
+ logger.log(headerKeyValue('Execution id:', caseResult.execution.processExecutionId || 'n/a'));
142
+ logger.log(headerKeyValue('Final runtime status:', caseResult.execution.finalStatus || 'n/a'));
143
+ }
144
+ if (caseResult.passed) {
145
+ logger.log(formatSuccessLine('Case assertions passed.'));
146
+ const warnings = Array.isArray(caseResult.warnings) ? caseResult.warnings : [];
147
+ for (const row of warnings) {
148
+ logger.log(formatWarningLine(row.message || String(row)));
149
+ }
150
+ return;
151
+ }
152
+ logger.log(formatBlockingError('Case assertions failed.'));
153
+ for (const row of caseResult.failedAssertions) {
154
+ logger.log(formatWarningLine(row.message));
155
+ }
156
+ }
157
+
158
+ function displayPortalAndVerdict(portalUrl, verdict) {
159
+ if (portalUrl) {
160
+ logger.log(headerKeyValue('Portal:', portalUrl));
161
+ } else {
162
+ logger.log(formatWarningLine('Portal link unavailable. Configure dataplane public URL and retry.'));
163
+ }
164
+ const verdictMessage =
165
+ verdict === VERDICT.VERIFIED
166
+ ? 'Lifecycle checks passed. Open portal for full execution steps and logs.'
167
+ : 'Lifecycle checks are not fully verified. Open portal to inspect blocking steps.';
168
+ if (verdict === VERDICT.VERIFIED) {
169
+ logger.log(formatSuccessParagraph(verdictMessage));
170
+ } else {
171
+ logger.log(formatWarningLine(verdictMessage));
172
+ }
173
+ }
174
+
175
+ function displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult = null) {
176
+ logger.log('');
177
+ logger.log(sectionTitle(`${String(roleKey).toUpperCase()} ROLE ASSISTANT TEST`));
178
+ logger.log('');
179
+ logger.log(headerKeyValue('Role key:', roleKey));
180
+ displayPortalAndVerdict(portalUrl, verdict);
181
+ const steps = Array.isArray(result.stepsRun) ? result.stepsRun : [];
182
+ if (steps.length > 0) {
183
+ logger.log(metadata(`Steps run: ${formatStepsRunSummary(steps)}`));
184
+ }
185
+ if (Array.isArray(caseResult)) {
186
+ for (const row of caseResult) {
187
+ displayCaseResultSummary(row);
188
+ }
189
+ const passed = caseResult.filter((c) => c.passed).length;
190
+ logger.log(headerKeyValue('Suite summary:', `${passed}/${caseResult.length} passed`));
191
+ return;
192
+ }
193
+ displayCaseResultSummary(caseResult);
194
+ }
195
+
196
+ module.exports = {
197
+ formatStepsRunSummary,
198
+ buildRoleAssistantTestEnvelope,
199
+ displayRoleAssistantTestSummary
200
+ };