@aifabrix/builder 2.51.0 → 2.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-container-start.js +4 -0
  14. package/lib/app/run-deploy-job.js +216 -0
  15. package/lib/app/run-env-compose.js +32 -0
  16. package/lib/app/run-env-recovery.js +35 -0
  17. package/lib/app/run-helpers.js +15 -9
  18. package/lib/app/run.js +18 -2
  19. package/lib/channels/add-channel.js +54 -23
  20. package/lib/channels/approval-guides/chatgpt.js +13 -9
  21. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  22. package/lib/channels/channel-artifact-generator.js +14 -2
  23. package/lib/channels/channel-credential.js +3 -1
  24. package/lib/channels/channel-probe-evidence.js +79 -0
  25. package/lib/channels/channel-probe.js +265 -13
  26. package/lib/channels/channel-runtime-resolver.js +120 -29
  27. package/lib/channels/channel-status-display.js +44 -0
  28. package/lib/channels/channel-targets.js +25 -1
  29. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  30. package/lib/channels/microsoft-copilot-package.js +280 -0
  31. package/lib/channels/runtime-tool-catalog.js +165 -0
  32. package/lib/cli/index.js +2 -0
  33. package/lib/cli/infra-guided-footers.js +1 -1
  34. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  35. package/lib/cli/infra-guided.js +65 -56
  36. package/lib/cli/setup-app.help.js +4 -0
  37. package/lib/cli/setup-app.js +4 -0
  38. package/lib/cli/setup-app.test-commands.js +59 -28
  39. package/lib/cli/setup-external-system-help.js +8 -2
  40. package/lib/cli/setup-external-system.js +62 -12
  41. package/lib/commands/app-test.js +2 -14
  42. package/lib/commands/channel-add-setup.js +8 -2
  43. package/lib/commands/channel.js +16 -5
  44. package/lib/commands/datasource-load-export-cli.js +6 -1
  45. package/lib/commands/deploy-gate-failure-details.js +19 -17
  46. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  47. package/lib/commands/dev-down.js +2 -6
  48. package/lib/commands/evidence-kind.js +65 -15
  49. package/lib/commands/governance-generate-external.js +56 -21
  50. package/lib/commands/governance-verify-external.js +2 -5
  51. package/lib/commands/lifecycle-external.js +21 -8
  52. package/lib/commands/repair-datasource-apply.js +10 -1
  53. package/lib/commands/repair-datasource-run.js +4 -2
  54. package/lib/commands/repair-datasource-test-payload.js +80 -19
  55. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  56. package/lib/commands/repair-internal.js +9 -1
  57. package/lib/commands/repair-openapi-sync.js +34 -17
  58. package/lib/commands/role-assistant.js +371 -0
  59. package/lib/commands/test-governance-external.js +2 -5
  60. package/lib/commands/up-miso.js +61 -24
  61. package/lib/commands/upload-data-sync-finalize.js +25 -7
  62. package/lib/commands/upload-sidecar-followups.js +144 -47
  63. package/lib/commands/upload.js +43 -6
  64. package/lib/commands/verify-governance-combined-external.js +73 -9
  65. package/lib/commands/verify-governance-db.js +80 -25
  66. package/lib/commands/verify-governance-external.js +31 -10
  67. package/lib/commands/verify-operations-external.js +2 -5
  68. package/lib/commands/verify-operations-step-plan.js +31 -0
  69. package/lib/commands/verify-trust-command-action.js +2 -5
  70. package/lib/commands/wizard-core-helpers.js +7 -3
  71. package/lib/commands/wizard-core.js +78 -9
  72. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  73. package/lib/datasource/bulk-loader-service.js +5 -1
  74. package/lib/datasource/datasource-identifier.js +4 -3
  75. package/lib/datasource/datasource-validate-summary.js +2 -2
  76. package/lib/datasource/integration-context.js +3 -3
  77. package/lib/datasource/resolve-app.js +90 -8
  78. package/lib/datasource/test-e2e.js +2 -2
  79. package/lib/datasource/unified-validation-run-body.js +15 -1
  80. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  81. package/lib/datasource/validate.js +13 -6
  82. package/lib/external-system/deploy-helpers.js +39 -1
  83. package/lib/external-system/deploy.js +25 -11
  84. package/lib/external-system/download-helpers.js +111 -2
  85. package/lib/external-system/download-role-assistant.js +55 -0
  86. package/lib/external-system/download.js +45 -26
  87. package/lib/external-system/test-helpers.js +1 -1
  88. package/lib/external-system/test-system-level.js +21 -3
  89. package/lib/external-system/test.js +43 -7
  90. package/lib/generator/builders.js +12 -15
  91. package/lib/generator/wizard.js +51 -4
  92. package/lib/lifecycle/access-control-abac.js +121 -0
  93. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  94. package/lib/lifecycle/product-model.js +3 -2
  95. package/lib/lifecycle/report-display.js +14 -2
  96. package/lib/programmatic/capability-registry.js +20 -1
  97. package/lib/programmatic/command-options.js +1 -0
  98. package/lib/programmatic/index.js +4 -0
  99. package/lib/programmatic/manifest-patch-section.js +48 -9
  100. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  101. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  102. package/lib/programmatic/openapi-component-schemas.js +16 -1
  103. package/lib/programmatic/openapi-description-enricher.js +3 -1
  104. package/lib/programmatic/openapi-descriptions.js +55 -5
  105. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  106. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  107. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  108. package/lib/programmatic/openapi-request-schemas.js +36 -1
  109. package/lib/programmatic/openapi-result-schemas.js +1 -1
  110. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  111. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  112. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  113. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  114. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  115. package/lib/programmatic/openapi-spec.js +47 -28
  116. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  117. package/lib/programmatic/request-timeout.js +3 -0
  118. package/lib/programmatic/route-handler-map.js +89 -2
  119. package/lib/programmatic/run-certify.js +15 -2
  120. package/lib/programmatic/run-deploy.js +74 -0
  121. package/lib/programmatic/run-evidence-authoring.js +80 -0
  122. package/lib/programmatic/run-protection.js +124 -0
  123. package/lib/programmatic/run-template-workspace.js +28 -1
  124. package/lib/programmatic/workspace-context.js +6 -0
  125. package/lib/protection/auth-context.js +48 -6
  126. package/lib/protection/paths.js +22 -0
  127. package/lib/protection/run-protection-create-helpers.js +22 -1
  128. package/lib/protection/run-protection-create.js +1 -1
  129. package/lib/protection/sync-after-upload.js +1 -1
  130. package/lib/role-assistant/evidence-sync.js +450 -0
  131. package/lib/role-assistant/github-workflow.js +96 -0
  132. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  133. package/lib/role-assistant/knowledge-sync.js +485 -0
  134. package/lib/role-assistant/settings.js +126 -0
  135. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  136. package/lib/role-assistant/test-cases-honesty.js +184 -0
  137. package/lib/role-assistant/test-cases.js +364 -0
  138. package/lib/role-assistant/test-runner-workhub-answers.js +221 -0
  139. package/lib/role-assistant/test-runner-workhub.js +399 -0
  140. package/lib/role-assistant/test-runner.js +500 -0
  141. package/lib/schema/application-schema.json +24 -28
  142. package/lib/schema/deployment-rules.yaml +1 -0
  143. package/lib/schema/external-datasource.schema.json +38 -4
  144. package/lib/schema/external-system.schema.json +4 -4
  145. package/lib/schema/role-assistant-settings.schema.json +108 -0
  146. package/lib/utils/api.js +52 -27
  147. package/lib/utils/app-path-resolve.js +72 -24
  148. package/lib/utils/authentication-shape.js +3 -1
  149. package/lib/utils/certify-external-workspace.js +40 -0
  150. package/lib/utils/deployment-validation.js +28 -3
  151. package/lib/utils/docker-run-ephemeral.js +115 -0
  152. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  153. package/lib/utils/env-copy-keycloak.js +196 -0
  154. package/lib/utils/env-copy.js +34 -1
  155. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  156. package/lib/utils/health-check-public-warn.js +31 -13
  157. package/lib/utils/health-check.js +11 -1
  158. package/lib/utils/help-builder.js +1 -0
  159. package/lib/utils/paths.js +80 -38
  160. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  161. package/lib/utils/role-assistant-paths.js +160 -0
  162. package/lib/utils/schema-loader.js +41 -0
  163. package/lib/utils/schema-resolver.js +36 -4
  164. package/lib/utils/token-manager.js +38 -29
  165. package/lib/utils/upload-sync-options.js +7 -4
  166. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  167. package/lib/validation/external-manifest-validator.js +31 -8
  168. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  169. package/lib/validation/validate-external-file.js +8 -1
  170. package/lib/validation/validate.js +54 -19
  171. package/package.json +2 -1
  172. package/scripts/export-builder-api-evidence.js +1 -1
  173. package/templates/applications/builder-api/application.yaml +3 -3
  174. package/templates/applications/dataplane/application.yaml +24 -17
  175. package/templates/applications/dataplane/env.template +2 -2
  176. package/templates/applications/dataplane/rbac.yaml +24 -10
  177. package/templates/applications/keycloak/application.yaml +2 -2
  178. package/templates/applications/keycloak/env.template +5 -2
  179. package/templates/applications/miso-controller/application.yaml +3 -3
  180. package/templates/applications/miso-controller/env.template +17 -2
  181. package/templates/channels/chatgpt/install.template.md.hbs +82 -26
  182. package/templates/channels/chatgpt/integration/README.md +11 -7
  183. package/templates/channels/chatgpt/integration/application.json +2 -2
  184. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  185. package/templates/channels/chatgpt/integration/env.template +1 -1
  186. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  187. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  188. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  189. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  190. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  191. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  192. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  193. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  194. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  195. package/templates/channels/slack/install.template.md.hbs +6 -1
  196. package/templates/python/docker-compose.hbs +10 -0
  197. package/templates/typescript/docker-compose.hbs +10 -0
  198. /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Role Assistant CLI command group.
3
+ *
4
+ * @fileoverview role-assistant test command with portal deep-link output
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const logger = require('../utils/logger');
12
+ const { handleCommandError } = require('../utils/cli-utils');
13
+ const {
14
+ runLifecycleForExternalSystem,
15
+ resolveLifecycleAuth
16
+ } = require('./lifecycle-external');
17
+ const { lifecycleVerdictFromReport } = require('./lifecycle-command-action');
18
+ const { buildExternalSystemUiUrl } = require('../lifecycle/dataplane-ui-url');
19
+ const { VERDICT } = require('../lifecycle/product-model');
20
+ const { exitCodeFromVerdict } = require('../lifecycle/exit-codes');
21
+ const {
22
+ loadRoleAssistantTestCase,
23
+ evaluateRoleAssistantTestCase
24
+ } = require('../role-assistant/test-cases');
25
+ const {
26
+ isExecutableRoleAssistantCase,
27
+ runRoleAssistantCaseLifecycle,
28
+ mergeCaseExecutionIntoLifecycleResult,
29
+ lifecycleVerdictFromCaseExecution
30
+ } = require('../role-assistant/test-runner');
31
+ const { collectString } = require('./governance');
32
+ const {
33
+ sectionTitle,
34
+ headerKeyValue,
35
+ formatSuccessLine,
36
+ formatSuccessParagraph,
37
+ formatWarningLine,
38
+ formatBlockingError,
39
+ metadata
40
+ } = require('../utils/cli-test-layout-chalk');
41
+ const {
42
+ bareRoleKey,
43
+ roleAssistantSystemKey
44
+ } = require('../utils/role-assistant-paths');
45
+
46
+ /**
47
+ * Bucket a lifecycle step status into ok | failed | skipped | other.
48
+ * @param {string} status
49
+ * @returns {'ok'|'failed'|'skipped'|'other'}
50
+ */
51
+ function bucketStepStatus(status) {
52
+ const normalized = String(status || 'ok').toLowerCase();
53
+ if (normalized === 'ok' || normalized === 'verified' || normalized === 'passed') {
54
+ return 'ok';
55
+ }
56
+ if (normalized === 'failed' || normalized === 'error') {
57
+ return 'failed';
58
+ }
59
+ if (normalized === 'skipped' || normalized === 'n/a' || normalized === 'not_applicable') {
60
+ return 'skipped';
61
+ }
62
+ return 'other';
63
+ }
64
+
65
+ /**
66
+ * Summarize lifecycle stepsRun rows (API uses step/status/datasourceKey, not pillar).
67
+ * @param {Array<Object|string>} steps
68
+ * @returns {string}
69
+ */
70
+ function formatStepsRunSummary(steps) {
71
+ const byStep = Object.create(null);
72
+ for (const row of steps) {
73
+ if (!row || typeof row !== 'object') {
74
+ const label = String(row);
75
+ byStep[label] = byStep[label] || { ok: 0, failed: 0, skipped: 0, other: 0 };
76
+ byStep[label].other += 1;
77
+ continue;
78
+ }
79
+ const stepName = String(row.step || row.pillar || row.name || 'step').trim() || 'step';
80
+ const status = row.status || (row.success === false ? 'failed' : 'ok');
81
+ byStep[stepName] = byStep[stepName] || { ok: 0, failed: 0, skipped: 0, other: 0 };
82
+ byStep[stepName][bucketStepStatus(status)] += 1;
83
+ }
84
+ return Object.keys(byStep)
85
+ .map((stepName) => {
86
+ const counts = byStep[stepName];
87
+ const parts = [];
88
+ if (counts.ok) parts.push(`${counts.ok} ok`);
89
+ if (counts.failed) parts.push(`${counts.failed} failed`);
90
+ if (counts.skipped) parts.push(`${counts.skipped} skipped`);
91
+ if (counts.other) parts.push(`${counts.other} other`);
92
+ return parts.length > 0 ? `${stepName} (${parts.join(', ')})` : stepName;
93
+ })
94
+ .join('; ');
95
+ }
96
+
97
+ function roleAssistantHelpAfter() {
98
+ return `
99
+ Examples:
100
+ $ aifabrix role-assistant test customer-support-bot
101
+ $ aifabrix role-assistant test customer-support-bot --case smoke
102
+ $ aifabrix role-assistant test customer-support-bot --case tests/customer-address-change.yaml
103
+ $ aifabrix role-assistant test customer-support-bot --subject-email ops@example.com
104
+ $ aifabrix role-assistant test customer-support-bot --json
105
+ $ aifabrix role-assistant test customer-support-bot --publish --force
106
+
107
+ Notes:
108
+ - Uses the public/running Role Assistant on the dataplane by default (does not publish local datasources).
109
+ - Use \`aifabrix upload <roleKey> --env …\` (or \`--publish\`) when you intentionally refresh the package.
110
+ - Runs lifecycle checks, then prints a portal deep link for full step-by-step status.
111
+ - Case files with input.message/input.content execute Work Hub plan→accept (or work.start when executionPath: intent).
112
+ - answers[] and approvals[] are matched to runtime wait payloads (questionKey/requestKey/waitReason).
113
+ - Use the portal URL for detailed human review while automation reads CLI/JSON output.
114
+ `;
115
+ }
116
+
117
+ function rawArgsInclude(cmd, flag) {
118
+ const raw = cmd && Array.isArray(cmd.rawArgs) ? cmd.rawArgs : [];
119
+ return raw.includes(flag);
120
+ }
121
+
122
+ function optionOrFlag(options, key, cmd, ...flags) {
123
+ if (options[key] === true) {
124
+ return true;
125
+ }
126
+ return flags.some((flag) => rawArgsInclude(cmd, flag));
127
+ }
128
+
129
+ function trimOptionalString(value) {
130
+ return value ? String(value).trim() : '';
131
+ }
132
+
133
+ function normalizeRoleAssistantTestOptions(options, cmd) {
134
+ // Role Assistant test uses the public/running digital worker by default.
135
+ // Opt in to publish with --publish (optional --force).
136
+ const wantsPublish = optionOrFlag(options, 'publish', cmd, '--publish');
137
+ return {
138
+ env: options.env || 'dev',
139
+ verbose: optionOrFlag(options, 'verbose', cmd, '-v', '--verbose'),
140
+ debug: optionOrFlag(options, 'debug', cmd, '-d', '--debug'),
141
+ json: optionOrFlag(options, 'json', cmd, '--json'),
142
+ noPublish: !wantsPublish,
143
+ force: optionOrFlag(options, 'force', cmd, '--force'),
144
+ subjectUserId: options.subjectUserId || [],
145
+ subjectEmail: options.subjectEmail || [],
146
+ certificationProfile: options.certificationProfile || 'bronze',
147
+ case: trimOptionalString(options.case),
148
+ testsDir: trimOptionalString(options.testsDir),
149
+ run: true
150
+ };
151
+ }
152
+
153
+ function buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult = null) {
154
+ const report = result.report || {};
155
+ const envelope = {
156
+ roleKey,
157
+ command: 'role-assistant test',
158
+ verdict,
159
+ portalUrl,
160
+ openApiPublished: result.openApiPublished === true,
161
+ certification: report.certification || null,
162
+ recommendations: Array.isArray(report.recommendations) ? report.recommendations : [],
163
+ warnings: Array.isArray(report.warnings) ? report.warnings : [],
164
+ stepsRun: result.stepsRun || [],
165
+ case: caseResult
166
+ ? {
167
+ caseId: caseResult.caseId,
168
+ sourceFile: caseResult.sourceFile,
169
+ passed: caseResult.passed,
170
+ lifecycleState: caseResult.lifecycleState,
171
+ assertions: caseResult.assertions,
172
+ failedAssertions: caseResult.failedAssertions,
173
+ warnings: Array.isArray(caseResult.warnings) ? caseResult.warnings : [],
174
+ execution: caseResult.execution || null
175
+ }
176
+ : null
177
+ };
178
+ if (opts.verbose || opts.debug) {
179
+ envelope.report = report;
180
+ }
181
+ if (opts.debug) {
182
+ envelope.debug = {
183
+ observation: caseResult ? caseResult.observation : null,
184
+ lifecycleResult: result
185
+ };
186
+ }
187
+ return envelope;
188
+ }
189
+
190
+ function displayCaseResultSummary(caseResult) {
191
+ if (!caseResult) {
192
+ return;
193
+ }
194
+ logger.log(headerKeyValue('Case:', caseResult.caseId));
195
+ logger.log(headerKeyValue('Case file:', caseResult.sourceFile));
196
+ logger.log(headerKeyValue('Observed state:', caseResult.lifecycleState));
197
+ if (caseResult.execution) {
198
+ logger.log(headerKeyValue('Execution id:', caseResult.execution.processExecutionId || 'n/a'));
199
+ logger.log(headerKeyValue('Final runtime status:', caseResult.execution.finalStatus || 'n/a'));
200
+ }
201
+ if (caseResult.passed) {
202
+ logger.log(formatSuccessLine('Case assertions passed.'));
203
+ const warnings = Array.isArray(caseResult.warnings) ? caseResult.warnings : [];
204
+ for (const row of warnings) {
205
+ logger.log(formatWarningLine(row.message || String(row)));
206
+ }
207
+ return;
208
+ }
209
+ logger.log(formatBlockingError('Case assertions failed.'));
210
+ for (const row of caseResult.failedAssertions) {
211
+ logger.log(formatWarningLine(row.message));
212
+ }
213
+ }
214
+
215
+ function displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult = null) {
216
+ logger.log('');
217
+ logger.log(sectionTitle(`${String(roleKey).toUpperCase()} ROLE ASSISTANT TEST`));
218
+ logger.log('');
219
+ logger.log(headerKeyValue('Role key:', roleKey));
220
+ if (portalUrl) {
221
+ logger.log(headerKeyValue('Portal:', portalUrl));
222
+ } else {
223
+ logger.log(formatWarningLine('Portal link unavailable. Configure dataplane public URL and retry.'));
224
+ }
225
+ const verdictMessage =
226
+ verdict === VERDICT.VERIFIED
227
+ ? 'Lifecycle checks passed. Open portal for full execution steps and logs.'
228
+ : 'Lifecycle checks are not fully verified. Open portal to inspect blocking steps.';
229
+ if (verdict === VERDICT.VERIFIED) {
230
+ logger.log(formatSuccessParagraph(verdictMessage));
231
+ } else {
232
+ logger.log(formatWarningLine(verdictMessage));
233
+ }
234
+ const steps = Array.isArray(result.stepsRun) ? result.stepsRun : [];
235
+ if (steps.length > 0) {
236
+ logger.log(metadata(`Steps run: ${formatStepsRunSummary(steps)}`));
237
+ }
238
+ displayCaseResultSummary(caseResult);
239
+ }
240
+
241
+ async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, portalUrl) {
242
+ if (!opts.case) {
243
+ return null;
244
+ }
245
+ const roleCase = loadRoleAssistantTestCase(roleKey, opts.case, {
246
+ testsDir: opts.testsDir || null
247
+ });
248
+ if (!isExecutableRoleAssistantCase(roleCase)) {
249
+ return evaluateRoleAssistantTestCase(roleCase, result, verdict, portalUrl);
250
+ }
251
+ const authContext = await resolveLifecycleAuth(roleKey, {
252
+ ...opts,
253
+ run: false,
254
+ noPublish: true
255
+ });
256
+ const executionResult = await runRoleAssistantCaseLifecycle(roleKey, roleCase, authContext, opts);
257
+ const executionVerdict = lifecycleVerdictFromCaseExecution(executionResult, verdict);
258
+ const evaluationInput = mergeCaseExecutionIntoLifecycleResult(result, executionResult);
259
+ const evaluation = evaluateRoleAssistantTestCase(
260
+ roleCase,
261
+ evaluationInput,
262
+ executionVerdict,
263
+ portalUrl
264
+ );
265
+ evaluation.execution = {
266
+ processExecutionId: executionResult.processExecutionId || null,
267
+ finalStatus: executionResult.finalStatus?.status || null,
268
+ waitReason: executionResult.finalStatus?.wait?.waitReason || null,
269
+ interactions: executionResult.interactions || [],
270
+ workResultError: executionResult.workResultError || null
271
+ };
272
+ return evaluation;
273
+ }
274
+
275
+ function computeRoleAssistantExitCode(verdict, caseResult) {
276
+ if (caseResult) {
277
+ return caseResult.passed ? 0 : 2;
278
+ }
279
+ if (verdict === VERDICT.VERIFIED) {
280
+ return 0;
281
+ }
282
+ return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
283
+ }
284
+
285
+ async function runRoleAssistantTestAction(roleKey, options, cmd) {
286
+ const opts = normalizeRoleAssistantTestOptions(options, cmd);
287
+ // Dataplane ExternalSystem key is always ra-{roleKey}; bare roleKey is display/settings only.
288
+ const systemKey = roleAssistantSystemKey(roleKey);
289
+ const displayKey = bareRoleKey(roleKey);
290
+ const result = await runLifecycleForExternalSystem(systemKey, opts);
291
+ const verdict = lifecycleVerdictFromReport(result.report || {});
292
+ const portalUrl = buildExternalSystemUiUrl(result.dataplaneUiBaseUrl, systemKey);
293
+ const caseResult = await resolveRoleAssistantCaseResult(systemKey, opts, result, verdict, portalUrl);
294
+
295
+ if (opts.json) {
296
+ logger.log(
297
+ JSON.stringify(
298
+ buildRoleAssistantTestEnvelope(displayKey, verdict, portalUrl, result, opts, caseResult),
299
+ null,
300
+ 2
301
+ )
302
+ );
303
+ } else {
304
+ displayRoleAssistantTestSummary(displayKey, verdict, portalUrl, result, caseResult);
305
+ }
306
+
307
+ const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
308
+ if (exitCode !== 0) {
309
+ process.exit(exitCode);
310
+ }
311
+ }
312
+
313
+ async function roleAssistantTestHandler(roleKey, options, cmd) {
314
+ try {
315
+ await runRoleAssistantTestAction(roleKey, options, cmd);
316
+ } catch (error) {
317
+ handleCommandError(error, 'role-assistant test');
318
+ process.exit(3);
319
+ }
320
+ }
321
+
322
+ function setupRoleAssistantCommands(program) {
323
+ const roleAssistant = program
324
+ .command('role-assistant')
325
+ .description('Role Assistant lifecycle and portal workflows');
326
+
327
+ roleAssistant
328
+ .command('test <roleKey>')
329
+ .description('Run role-assistant lifecycle checks and print portal deep link')
330
+ .option('-e, --env <env>', 'Environment: dev, tst, or pro', 'dev')
331
+ .option('--case <caseIdOrPath>', 'Run and evaluate one role-assistant test case')
332
+ .option('--tests-dir <dir>', 'Override role-assistant test case directory')
333
+ .option('-v, --verbose', 'Include full lifecycle report payload in --json output')
334
+ .option('-d, --debug', 'Write debug logs under role-assistant/<roleKey>/logs/')
335
+ .option(
336
+ '--publish',
337
+ 'Publish local Role Assistant package before checks (default: use public/running worker)'
338
+ )
339
+ .option('--force', 'Force upload when --publish is set')
340
+ .option(
341
+ '--subject-user-id <id>',
342
+ 'Subject user id for governance generate (repeatable)',
343
+ collectString,
344
+ []
345
+ )
346
+ .option(
347
+ '--subject-email <email>',
348
+ 'Subject email for governance generate (repeatable)',
349
+ collectString,
350
+ []
351
+ )
352
+ .option(
353
+ '--certification-profile <profile>',
354
+ 'Certification profile for governance generate (bronze, silver, gold, ...)',
355
+ 'bronze'
356
+ )
357
+ .option('--json', 'Machine-readable output envelope')
358
+ .addHelpText('after', roleAssistantHelpAfter())
359
+ .action((roleKey, options, cmd) => roleAssistantTestHandler(roleKey, options, cmd));
360
+ }
361
+
362
+ module.exports = {
363
+ setupRoleAssistantCommands,
364
+ roleAssistantTestHandler,
365
+ runRoleAssistantTestAction,
366
+ normalizeRoleAssistantTestOptions,
367
+ buildRoleAssistantTestEnvelope,
368
+ computeRoleAssistantExitCode,
369
+ resolveRoleAssistantCaseResult,
370
+ formatStepsRunSummary
371
+ };
@@ -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
@@ -27,7 +36,7 @@ async function finalizeUploadPublishResult(ctx, payload, options) {
27
36
  options,
28
37
  label: 'Upload data sync'
29
38
  });
30
- return { ...ctx, dataSync };
39
+ return { ...ctx, dataSync, sidecar: ctx.sidecar || null };
31
40
  }
32
41
 
33
42
  /**
@@ -37,18 +46,27 @@ 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
- await runUploadSidecarFollowups({
60
+ const sidecar = await runUploadSidecarFollowups({
44
61
  systemKey,
45
62
  options,
46
63
  payload,
47
64
  dataplaneUrl,
48
- authConfig
65
+ authConfig,
66
+ environment
49
67
  });
50
68
  return finalizeUploadPublishResult(
51
- { dataplaneUrl, authConfig, environment, uploadSkipped: true },
69
+ { dataplaneUrl, authConfig, environment, uploadSkipped: true, sidecar },
52
70
  payload,
53
71
  options
54
72
  );