@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
@@ -8,6 +8,7 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const path = require('path');
11
12
  const logger = require('../utils/logger');
12
13
  const { handleCommandError } = require('../utils/cli-utils');
13
14
  const {
@@ -22,6 +23,11 @@ const {
22
23
  loadRoleAssistantTestCase,
23
24
  evaluateRoleAssistantTestCase
24
25
  } = require('../role-assistant/test-cases');
26
+ const {
27
+ resolveCasesRoot,
28
+ discoverSuiteCaseFiles,
29
+ filterDiscoveredCases
30
+ } = require('../role-assistant/test-suite');
25
31
  const {
26
32
  isExecutableRoleAssistantCase,
27
33
  runRoleAssistantCaseLifecycle,
@@ -32,12 +38,17 @@ const { collectString } = require('./governance');
32
38
  const {
33
39
  sectionTitle,
34
40
  headerKeyValue,
35
- formatSuccessLine,
36
- formatSuccessParagraph,
37
- formatWarningLine,
38
- formatBlockingError,
39
41
  metadata
40
42
  } = require('../utils/cli-test-layout-chalk');
43
+ const {
44
+ formatStepsRunSummary,
45
+ buildRoleAssistantTestEnvelope,
46
+ displayRoleAssistantTestSummary
47
+ } = require('./role-assistant-test-output');
48
+ const {
49
+ bareRoleKey,
50
+ roleAssistantSystemKey
51
+ } = require('../utils/role-assistant-paths');
41
52
 
42
53
  function roleAssistantHelpAfter() {
43
54
  return `
@@ -46,8 +57,9 @@ Examples:
46
57
  $ aifabrix role-assistant test customer-support-bot --case smoke
47
58
  $ aifabrix role-assistant test customer-support-bot --case tests/customer-address-change.yaml
48
59
  $ aifabrix role-assistant test customer-support-bot --subject-email ops@example.com
49
- $ aifabrix role-assistant test customer-support-bot --json
50
- $ aifabrix role-assistant test customer-support-bot --publish --force
60
+ $ aifabrix role-assistant test customer-support-bot --suite 001
61
+ $ aifabrix role-assistant test customer-support-bot --list-cases
62
+ $ aifabrix role-assistant test customer-support-bot --suite 001 --fail-fast --json
51
63
 
52
64
  Notes:
53
65
  - Uses the public/running Role Assistant on the dataplane by default (does not publish local datasources).
@@ -55,6 +67,7 @@ Notes:
55
67
  - Runs lifecycle checks, then prints a portal deep link for full step-by-step status.
56
68
  - Case files with input.message/input.content execute Work Hub plan→accept (or work.start when executionPath: intent).
57
69
  - answers[] and approvals[] are matched to runtime wait payloads (questionKey/requestKey/waitReason).
70
+ - --suite runs all YAML under tests/role-assistant/ra-<roleKey>/<suite>/ (or --cases-root).
58
71
  - Use the portal URL for detailed human review while automation reads CLI/JSON output.
59
72
  `;
60
73
  }
@@ -91,97 +104,123 @@ function normalizeRoleAssistantTestOptions(options, cmd) {
91
104
  certificationProfile: options.certificationProfile || 'bronze',
92
105
  case: trimOptionalString(options.case),
93
106
  testsDir: trimOptionalString(options.testsDir),
107
+ suite: trimOptionalString(options.suite),
108
+ casesRoot: trimOptionalString(options.casesRoot),
109
+ listCases: optionOrFlag(options, 'listCases', cmd, '--list-cases'),
110
+ failFast: optionOrFlag(options, 'failFast', cmd, '--fail-fast'),
94
111
  run: true
95
112
  };
96
113
  }
97
114
 
98
- function buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult = null) {
99
- const report = result.report || {};
100
- const envelope = {
101
- roleKey,
102
- command: 'role-assistant test',
103
- verdict,
104
- portalUrl,
105
- openApiPublished: result.openApiPublished === true,
106
- certification: report.certification || null,
107
- recommendations: Array.isArray(report.recommendations) ? report.recommendations : [],
108
- warnings: Array.isArray(report.warnings) ? report.warnings : [],
109
- stepsRun: result.stepsRun || [],
110
- case: caseResult
111
- ? {
112
- caseId: caseResult.caseId,
113
- sourceFile: caseResult.sourceFile,
114
- passed: caseResult.passed,
115
- lifecycleState: caseResult.lifecycleState,
116
- assertions: caseResult.assertions,
117
- failedAssertions: caseResult.failedAssertions,
118
- warnings: Array.isArray(caseResult.warnings) ? caseResult.warnings : [],
119
- execution: caseResult.execution || null
120
- }
121
- : null
122
- };
123
- if (opts.verbose || opts.debug) {
124
- envelope.report = report;
115
+ function computeRoleAssistantExitCode(verdict, caseResult) {
116
+ if (Array.isArray(caseResult)) {
117
+ if (caseResult.some((row) => row && row.passed === false)) {
118
+ return 2;
119
+ }
120
+ return 0;
125
121
  }
126
- if (opts.debug) {
127
- envelope.debug = {
128
- observation: caseResult ? caseResult.observation : null,
129
- lifecycleResult: result
130
- };
122
+ if (caseResult) {
123
+ return caseResult.passed ? 0 : 2;
131
124
  }
132
- return envelope;
125
+ if (verdict === VERDICT.VERIFIED) {
126
+ return 0;
127
+ }
128
+ return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
133
129
  }
134
130
 
135
- function displayCaseResultSummary(caseResult) {
136
- if (!caseResult) {
137
- return;
131
+ function listRoleAssistantCases(roleKey, opts) {
132
+ const root = resolveCasesRoot(roleKey, { casesRoot: opts.casesRoot || undefined });
133
+ const rows = discoverSuiteCaseFiles(root, { suite: opts.suite || undefined });
134
+ const filtered = filterDiscoveredCases(rows, opts.case);
135
+ if (opts.json) {
136
+ logger.log(
137
+ JSON.stringify(
138
+ {
139
+ roleKey: bareRoleKey(roleKey),
140
+ command: 'role-assistant test',
141
+ mode: 'list-cases',
142
+ casesRoot: root,
143
+ cases: filtered.map((r) => ({
144
+ suite: r.suite,
145
+ file: r.file,
146
+ path: r.absPath,
147
+ caseIdHint: r.caseIdHint
148
+ }))
149
+ },
150
+ null,
151
+ 2
152
+ )
153
+ );
154
+ } else {
155
+ logger.log(sectionTitle('ROLE ASSISTANT CASES'));
156
+ logger.log(headerKeyValue('Cases root:', root));
157
+ for (const row of filtered) {
158
+ logger.log(`${row.suite}\t${row.file}\t${row.absPath}`);
159
+ }
160
+ logger.log(metadata(`total ${filtered.length}`));
138
161
  }
139
- logger.log(headerKeyValue('Case:', caseResult.caseId));
140
- logger.log(headerKeyValue('Case file:', caseResult.sourceFile));
141
- logger.log(headerKeyValue('Observed state:', caseResult.lifecycleState));
142
- if (caseResult.execution) {
143
- logger.log(headerKeyValue('Execution id:', caseResult.execution.processExecutionId || 'n/a'));
144
- logger.log(headerKeyValue('Final runtime status:', caseResult.execution.finalStatus || 'n/a'));
162
+ return 0;
163
+ }
164
+
165
+ async function resolveSuiteCaseResults(systemKey, opts, result, verdict, portalUrl) {
166
+ const root = resolveCasesRoot(systemKey, { casesRoot: opts.casesRoot || undefined });
167
+ const discovered = discoverSuiteCaseFiles(root, { suite: opts.suite || undefined });
168
+ const selected = filterDiscoveredCases(discovered, opts.case);
169
+ if (selected.length === 0) {
170
+ throw new Error(
171
+ `No role-assistant cases found under ${root}` +
172
+ (opts.suite ? ` suite=${opts.suite}` : '')
173
+ );
145
174
  }
146
- if (caseResult.passed) {
147
- logger.log(formatSuccessLine('Case assertions passed.'));
148
- const warnings = Array.isArray(caseResult.warnings) ? caseResult.warnings : [];
149
- for (const row of warnings) {
150
- logger.log(formatWarningLine(row.message || String(row)));
175
+ const outcomes = [];
176
+ for (const row of selected) {
177
+ const caseOpts = { ...opts, case: row.absPath, testsDir: path.dirname(row.absPath) };
178
+ const one = await resolveRoleAssistantCaseResult(systemKey, caseOpts, result, verdict, portalUrl);
179
+ outcomes.push(one);
180
+ if (opts.failFast && one && one.passed === false) {
181
+ break;
151
182
  }
152
- return;
153
- }
154
- logger.log(formatBlockingError('Case assertions failed.'));
155
- for (const row of caseResult.failedAssertions) {
156
- logger.log(formatWarningLine(row.message));
157
183
  }
184
+ return outcomes;
158
185
  }
159
186
 
160
- function displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult = null) {
161
- logger.log('');
162
- logger.log(sectionTitle(`${String(roleKey).toUpperCase()} ROLE ASSISTANT TEST`));
163
- logger.log('');
164
- logger.log(headerKeyValue('Role key:', roleKey));
165
- if (portalUrl) {
166
- logger.log(headerKeyValue('Portal:', portalUrl));
187
+ async function runRoleAssistantTestAction(roleKey, options, cmd) {
188
+ const opts = normalizeRoleAssistantTestOptions(options, cmd);
189
+ const systemKey = roleAssistantSystemKey(roleKey);
190
+ const displayKey = bareRoleKey(roleKey);
191
+
192
+ if (opts.listCases) {
193
+ return listRoleAssistantCases(systemKey, opts);
194
+ }
195
+
196
+ const result = await runLifecycleForExternalSystem(systemKey, opts);
197
+ const verdict = lifecycleVerdictFromReport(result.report || {});
198
+ const portalUrl = buildExternalSystemUiUrl(result.dataplaneUiBaseUrl, systemKey);
199
+
200
+ let caseResult = null;
201
+ if (opts.suite) {
202
+ caseResult = await resolveSuiteCaseResults(systemKey, opts, result, verdict, portalUrl);
167
203
  } else {
168
- logger.log(formatWarningLine('Portal link unavailable. Configure dataplane public URL and retry.'));
204
+ caseResult = await resolveRoleAssistantCaseResult(systemKey, opts, result, verdict, portalUrl);
169
205
  }
170
- const verdictMessage =
171
- verdict === VERDICT.VERIFIED
172
- ? 'Lifecycle checks passed. Open portal for full execution steps and logs.'
173
- : 'Lifecycle checks are not fully verified. Open portal to inspect blocking steps.';
174
- if (verdict === VERDICT.VERIFIED) {
175
- logger.log(formatSuccessParagraph(verdictMessage));
206
+
207
+ if (opts.json) {
208
+ logger.log(
209
+ JSON.stringify(
210
+ buildRoleAssistantTestEnvelope(displayKey, verdict, portalUrl, result, opts, caseResult),
211
+ null,
212
+ 2
213
+ )
214
+ );
176
215
  } else {
177
- logger.log(formatWarningLine(verdictMessage));
216
+ displayRoleAssistantTestSummary(displayKey, verdict, portalUrl, result, caseResult);
178
217
  }
179
- const steps = Array.isArray(result.stepsRun) ? result.stepsRun : [];
180
- if (steps.length > 0) {
181
- const labels = steps.map((row) => (row && row.pillar ? row.pillar : String(row)));
182
- logger.log(metadata(`Steps run: ${labels.join(', ')}`));
218
+
219
+ const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
220
+ if (exitCode !== 0) {
221
+ process.exit(exitCode);
183
222
  }
184
- displayCaseResultSummary(caseResult);
223
+ return exitCode;
185
224
  }
186
225
 
187
226
  async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, portalUrl) {
@@ -218,41 +257,6 @@ async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, po
218
257
  return evaluation;
219
258
  }
220
259
 
221
- function computeRoleAssistantExitCode(verdict, caseResult) {
222
- if (caseResult) {
223
- return caseResult.passed ? 0 : 2;
224
- }
225
- if (verdict === VERDICT.VERIFIED) {
226
- return 0;
227
- }
228
- return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
229
- }
230
-
231
- async function runRoleAssistantTestAction(roleKey, options, cmd) {
232
- const opts = normalizeRoleAssistantTestOptions(options, cmd);
233
- const result = await runLifecycleForExternalSystem(roleKey, opts);
234
- const verdict = lifecycleVerdictFromReport(result.report || {});
235
- const portalUrl = buildExternalSystemUiUrl(result.dataplaneUiBaseUrl, roleKey);
236
- const caseResult = await resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, portalUrl);
237
-
238
- if (opts.json) {
239
- logger.log(
240
- JSON.stringify(
241
- buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult),
242
- null,
243
- 2
244
- )
245
- );
246
- } else {
247
- displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult);
248
- }
249
-
250
- const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
251
- if (exitCode !== 0) {
252
- process.exit(exitCode);
253
- }
254
- }
255
-
256
260
  async function roleAssistantTestHandler(roleKey, options, cmd) {
257
261
  try {
258
262
  await runRoleAssistantTestAction(roleKey, options, cmd);
@@ -273,6 +277,16 @@ function setupRoleAssistantCommands(program) {
273
277
  .option('-e, --env <env>', 'Environment: dev, tst, or pro', 'dev')
274
278
  .option('--case <caseIdOrPath>', 'Run and evaluate one role-assistant test case')
275
279
  .option('--tests-dir <dir>', 'Override role-assistant test case directory')
280
+ .option(
281
+ '--suite <suiteId>',
282
+ 'Run all YAML cases under tests/role-assistant/ra-<roleKey>/<suiteId>/'
283
+ )
284
+ .option(
285
+ '--cases-root <dir>',
286
+ 'Override cases root (default: tests/role-assistant/ra-<roleKey>/)'
287
+ )
288
+ .option('--list-cases', 'List discoverable case files and exit')
289
+ .option('--fail-fast', 'Stop suite run after the first failing case')
276
290
  .option('-v, --verbose', 'Include full lifecycle report payload in --json output')
277
291
  .option('-d, --debug', 'Write debug logs under role-assistant/<roleKey>/logs/')
278
292
  .option(
@@ -309,5 +323,8 @@ module.exports = {
309
323
  normalizeRoleAssistantTestOptions,
310
324
  buildRoleAssistantTestEnvelope,
311
325
  computeRoleAssistantExitCode,
312
- resolveRoleAssistantCaseResult
326
+ resolveRoleAssistantCaseResult,
327
+ resolveSuiteCaseResults,
328
+ listRoleAssistantCases,
329
+ formatStepsRunSummary
313
330
  };
@@ -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,
@@ -6,7 +6,7 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const { capabilitiesIncludes } = require('./capability-manifest');
9
+ const { capabilitiesIncludes, capabilityKeyFromEntry } = require('./capability-manifest');
10
10
 
11
11
  /**
12
12
  * @param {object|undefined} operations
@@ -44,6 +44,16 @@ function resolveSingleOpsKey(operations, logicalKey, label) {
44
44
  );
45
45
  }
46
46
 
47
+ /**
48
+ * @param {unknown} entry
49
+ * @param {string} userKey
50
+ * @returns {boolean}
51
+ */
52
+ function capabilityEntryMatches(entry, userKey) {
53
+ const key = capabilityKeyFromEntry(entry);
54
+ return Boolean(key) && key.toLowerCase() === String(userKey).toLowerCase();
55
+ }
56
+
47
57
  /**
48
58
  * For --from on copy: when capabilities[] is non-empty, the name must appear there (case-insensitive).
49
59
  *
@@ -56,14 +66,14 @@ function resolveSourceCapabilityForCopy(doc, userKey) {
56
66
  const u = String(userKey).trim();
57
67
  const caps = doc.capabilities;
58
68
  if (Array.isArray(caps) && caps.length > 0) {
59
- const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
69
+ const hit = caps.find((c) => capabilityEntryMatches(c, u));
60
70
  if (hit === undefined) {
61
71
  throw new Error(
62
72
  `Source capability "${userKey}" not found in capabilities[]. ` +
63
73
  'Use a name that appears in capabilities[] (case-insensitive match is allowed).'
64
74
  );
65
75
  }
66
- return String(hit);
76
+ return capabilityKeyFromEntry(hit) || u;
67
77
  }
68
78
  return u;
69
79
  }
@@ -79,9 +89,9 @@ function resolveLogicalNameForRemove(doc, userKey) {
79
89
  const u = String(userKey).trim();
80
90
  const caps = doc.capabilities;
81
91
  if (Array.isArray(caps) && caps.length > 0) {
82
- const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
92
+ const hit = caps.find((c) => capabilityEntryMatches(c, u));
83
93
  if (hit !== undefined) {
84
- return String(hit);
94
+ return capabilityKeyFromEntry(hit) || u;
85
95
  }
86
96
  }
87
97
  return u;