@aifabrix/builder 2.52.0 → 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.
- package/jest.isolated-projects.js +25 -2
- package/jest.projects.js +4 -1
- package/lib/api/datasources-extended.api.js +8 -8
- package/lib/api/keycloak.api.js +92 -0
- package/lib/api/token-providers.api.js +77 -0
- package/lib/api/types/keycloak.types.js +23 -0
- package/lib/api/types/token-providers.types.js +65 -0
- package/lib/app/down.js +16 -1
- package/lib/channels/add-channel.js +126 -19
- package/lib/channels/approval-guides/chatgpt.js +1 -1
- package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
- package/lib/channels/channel-add-ensure.js +128 -0
- package/lib/channels/channel-artifact-generator.js +11 -1
- package/lib/channels/channel-branding.js +94 -0
- package/lib/channels/channel-credential.js +8 -0
- package/lib/channels/channel-discovery.js +330 -0
- package/lib/channels/channel-external-system.js +1 -1
- package/lib/channels/channel-runtime-resolver.js +103 -14
- package/lib/channels/channel-status-display.js +7 -1
- package/lib/channels/microsoft-copilot-channel-add.js +138 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
- package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
- package/lib/channels/microsoft-copilot-icons.js +74 -0
- package/lib/channels/microsoft-copilot-limits.js +84 -0
- package/lib/channels/microsoft-copilot-package.js +130 -49
- package/lib/channels/microsoft-copilot-validate.js +197 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/infra-guided.js +13 -6
- package/lib/cli/setup-infra.js +31 -4
- package/lib/commands/channel-add-setup.js +28 -9
- package/lib/commands/channel.js +14 -10
- package/lib/commands/integration-definition.js +158 -0
- package/lib/commands/repair-auth-apply.js +19 -5
- package/lib/commands/repair-datasource-sync-operations.js +4 -1
- package/lib/commands/role-assistant-test-output.js +200 -0
- package/lib/commands/role-assistant.js +125 -166
- package/lib/datasource/capability/capability-resolve.js +15 -5
- package/lib/datasource/capability/copy-operations.js +14 -4
- package/lib/infrastructure/index.js +19 -4
- package/lib/integration-definition/apply.js +478 -0
- package/lib/integration-definition/canonical-json.js +51 -0
- package/lib/integration-definition/emit-suite-bid.js +94 -0
- package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
- package/lib/integration-definition/enrich-openapi-operations.js +434 -0
- package/lib/integration-definition/from-discovery.js +339 -0
- package/lib/integration-definition/hash.js +37 -0
- package/lib/integration-definition/index.js +37 -0
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
- package/lib/integration-definition/resource-type-catalog.js +59 -0
- package/lib/integration-definition/system-kind-enum.js +40 -0
- package/lib/integration-definition/validate.js +148 -0
- package/lib/programmatic/builder-api-request.js +5 -1
- package/lib/programmatic/builder-help-governance.js +3 -2
- package/lib/programmatic/capability-registry.js +19 -0
- package/lib/programmatic/openapi-component-schemas.js +6 -1
- package/lib/programmatic/openapi-descriptions.js +40 -0
- package/lib/programmatic/openapi-envelope-examples.js +269 -0
- package/lib/programmatic/openapi-operation-bindings.js +45 -0
- package/lib/programmatic/openapi-request-schemas.js +14 -0
- package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
- package/lib/programmatic/openapi-result-schemas.js +46 -99
- package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
- package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
- package/lib/programmatic/openapi-schema-helpers.js +9 -9
- package/lib/programmatic/openapi-spec.js +11 -1
- package/lib/programmatic/orchestration-openapi-meta.js +10 -0
- package/lib/programmatic/route-handler-map.js +29 -1
- package/lib/programmatic/run-channel-package.js +236 -0
- package/lib/programmatic/run-integration-definition.js +69 -0
- package/lib/role-assistant/knowledge-sync-logging.js +58 -0
- package/lib/role-assistant/knowledge-sync.js +88 -93
- package/lib/role-assistant/test-cases-bid.js +155 -0
- package/lib/role-assistant/test-cases-evaluate.js +9 -1
- package/lib/role-assistant/test-cases-honesty.js +24 -10
- package/lib/role-assistant/test-cases.js +28 -0
- package/lib/role-assistant/test-runner-bid-apply.js +122 -0
- package/lib/role-assistant/test-runner-bid-document.js +278 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +245 -20
- package/lib/role-assistant/test-runner-workhub.js +100 -18
- package/lib/role-assistant/test-suite.js +119 -0
- package/lib/schema/application-schema.json +56 -6
- package/lib/schema/external-datasource.schema.json +103 -17
- package/lib/schema/external-system.schema.json +54 -3
- package/lib/schema/integration-definition.schema.json +195 -0
- package/lib/utils/authentication-shape.js +6 -0
- package/lib/utils/docker-build.js +3 -3
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/promote-dev-docker-image.js +97 -18
- package/lib/utils/transient-developer-id.js +28 -0
- package/package.json +1 -1
- package/scripts/pnpm/af.mjs +24 -9
- package/scripts/pnpm/help.mjs +1 -1
- package/templates/applications/dataplane/application.yaml +3 -2
- package/templates/applications/dataplane/env.template +7 -6
- package/templates/applications/miso-controller/application.yaml +23 -9
- package/templates/applications/miso-controller/env.template +8 -2
- package/templates/channels/README.md +11 -1
- package/templates/channels/brand/color-192.png +0 -0
- package/templates/channels/brand/dark/128.png +0 -0
- package/templates/channels/brand/dark/192.png +0 -0
- package/templates/channels/brand/dark/256.png +0 -0
- package/templates/channels/brand/dark/32.png +0 -0
- package/templates/channels/brand/dark/512.png +0 -0
- package/templates/channels/brand/dark/64.png +0 -0
- package/templates/channels/brand/light/128.png +0 -0
- package/templates/channels/brand/light/192.png +0 -0
- package/templates/channels/brand/light/256.png +0 -0
- package/templates/channels/brand/light/32.png +0 -0
- package/templates/channels/brand/light/512.png +0 -0
- package/templates/channels/brand/light/64.png +0 -0
- package/templates/channels/brand/mono-white/128.png +0 -0
- package/templates/channels/brand/mono-white/192.png +0 -0
- package/templates/channels/brand/mono-white/256.png +0 -0
- package/templates/channels/brand/mono-white/32.png +0 -0
- package/templates/channels/brand/mono-white/512.png +0 -0
- package/templates/channels/brand/mono-white/64.png +0 -0
- package/templates/channels/brand/outline-32.png +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +6 -4
- package/templates/channels/chatgpt/integration/README.md +4 -5
- package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
- package/templates/channels/microsoft-copilot/integration/README.md +2 -3
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
- package/templates/channels/slack/integration/README.md +2 -4
- package/templates/channels/teams-bot/install.template.md.hbs +4 -0
- package/templates/channels/teams-bot/integration/README.md +3 -5
- package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
- /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
|
@@ -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
|
+
};
|
|
@@ -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,68 +38,18 @@ 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');
|
|
41
48
|
const {
|
|
42
49
|
bareRoleKey,
|
|
43
50
|
roleAssistantSystemKey
|
|
44
51
|
} = require('../utils/role-assistant-paths');
|
|
45
52
|
|
|
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
53
|
function roleAssistantHelpAfter() {
|
|
98
54
|
return `
|
|
99
55
|
Examples:
|
|
@@ -101,8 +57,9 @@ Examples:
|
|
|
101
57
|
$ aifabrix role-assistant test customer-support-bot --case smoke
|
|
102
58
|
$ aifabrix role-assistant test customer-support-bot --case tests/customer-address-change.yaml
|
|
103
59
|
$ aifabrix role-assistant test customer-support-bot --subject-email ops@example.com
|
|
104
|
-
$ aifabrix role-assistant test customer-support-bot --
|
|
105
|
-
$ aifabrix role-assistant test customer-support-bot --
|
|
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
|
|
106
63
|
|
|
107
64
|
Notes:
|
|
108
65
|
- Uses the public/running Role Assistant on the dataplane by default (does not publish local datasources).
|
|
@@ -110,6 +67,7 @@ Notes:
|
|
|
110
67
|
- Runs lifecycle checks, then prints a portal deep link for full step-by-step status.
|
|
111
68
|
- Case files with input.message/input.content execute Work Hub plan→accept (or work.start when executionPath: intent).
|
|
112
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).
|
|
113
71
|
- Use the portal URL for detailed human review while automation reads CLI/JSON output.
|
|
114
72
|
`;
|
|
115
73
|
}
|
|
@@ -146,96 +104,123 @@ function normalizeRoleAssistantTestOptions(options, cmd) {
|
|
|
146
104
|
certificationProfile: options.certificationProfile || 'bronze',
|
|
147
105
|
case: trimOptionalString(options.case),
|
|
148
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'),
|
|
149
111
|
run: true
|
|
150
112
|
};
|
|
151
113
|
}
|
|
152
114
|
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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;
|
|
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;
|
|
180
121
|
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
122
|
+
if (caseResult) {
|
|
123
|
+
return caseResult.passed ? 0 : 2;
|
|
124
|
+
}
|
|
125
|
+
if (verdict === VERDICT.VERIFIED) {
|
|
126
|
+
return 0;
|
|
186
127
|
}
|
|
187
|
-
return
|
|
128
|
+
return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
|
|
188
129
|
}
|
|
189
130
|
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
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}`));
|
|
193
161
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
);
|
|
200
174
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
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;
|
|
206
182
|
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
logger.log(formatBlockingError('Case assertions failed.'));
|
|
210
|
-
for (const row of caseResult.failedAssertions) {
|
|
211
|
-
logger.log(formatWarningLine(row.message));
|
|
212
183
|
}
|
|
184
|
+
return outcomes;
|
|
213
185
|
}
|
|
214
186
|
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
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);
|
|
222
203
|
} else {
|
|
223
|
-
|
|
204
|
+
caseResult = await resolveRoleAssistantCaseResult(systemKey, opts, result, verdict, portalUrl);
|
|
224
205
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
);
|
|
231
215
|
} else {
|
|
232
|
-
|
|
216
|
+
displayRoleAssistantTestSummary(displayKey, verdict, portalUrl, result, caseResult);
|
|
233
217
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
218
|
+
|
|
219
|
+
const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
|
|
220
|
+
if (exitCode !== 0) {
|
|
221
|
+
process.exit(exitCode);
|
|
237
222
|
}
|
|
238
|
-
|
|
223
|
+
return exitCode;
|
|
239
224
|
}
|
|
240
225
|
|
|
241
226
|
async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, portalUrl) {
|
|
@@ -272,44 +257,6 @@ async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, po
|
|
|
272
257
|
return evaluation;
|
|
273
258
|
}
|
|
274
259
|
|
|
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
260
|
async function roleAssistantTestHandler(roleKey, options, cmd) {
|
|
314
261
|
try {
|
|
315
262
|
await runRoleAssistantTestAction(roleKey, options, cmd);
|
|
@@ -330,6 +277,16 @@ function setupRoleAssistantCommands(program) {
|
|
|
330
277
|
.option('-e, --env <env>', 'Environment: dev, tst, or pro', 'dev')
|
|
331
278
|
.option('--case <caseIdOrPath>', 'Run and evaluate one role-assistant test case')
|
|
332
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')
|
|
333
290
|
.option('-v, --verbose', 'Include full lifecycle report payload in --json output')
|
|
334
291
|
.option('-d, --debug', 'Write debug logs under role-assistant/<roleKey>/logs/')
|
|
335
292
|
.option(
|
|
@@ -367,5 +324,7 @@ module.exports = {
|
|
|
367
324
|
buildRoleAssistantTestEnvelope,
|
|
368
325
|
computeRoleAssistantExitCode,
|
|
369
326
|
resolveRoleAssistantCaseResult,
|
|
327
|
+
resolveSuiteCaseResults,
|
|
328
|
+
listRoleAssistantCases,
|
|
370
329
|
formatStepsRunSummary
|
|
371
330
|
};
|
|
@@ -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) =>
|
|
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
|
|
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) =>
|
|
92
|
+
const hit = caps.find((c) => capabilityEntryMatches(c, u));
|
|
83
93
|
if (hit !== undefined) {
|
|
84
|
-
return
|
|
94
|
+
return capabilityKeyFromEntry(hit) || u;
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
return u;
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { rewriteCapabilityReferences } = require('./reference-rewrite');
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
capabilityKeyFromEntry,
|
|
12
|
+
capabilitiesIncludes,
|
|
13
|
+
buildCapabilityObject
|
|
14
|
+
} = require('./capability-manifest');
|
|
11
15
|
const { jsonPointerPath } = require('./json-pointer');
|
|
12
16
|
const { buildBasicExposureProfileArray } = require('./basic-exposure');
|
|
13
17
|
const {
|
|
@@ -256,14 +260,20 @@ function appendCapabilityList(doc, resolvedAs, patchOperations, updatedSections)
|
|
|
256
260
|
if (!Array.isArray(doc.capabilities)) {
|
|
257
261
|
doc.capabilities = [];
|
|
258
262
|
}
|
|
259
|
-
if (doc.capabilities
|
|
263
|
+
if (capabilitiesIncludes(doc.capabilities, resolvedAs)) {
|
|
260
264
|
return;
|
|
261
265
|
}
|
|
262
|
-
doc.capabilities.
|
|
266
|
+
const usesObjects = doc.capabilities.some(
|
|
267
|
+
(entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
268
|
+
);
|
|
269
|
+
const value = usesObjects
|
|
270
|
+
? buildCapabilityObject(resolvedAs, doc.displayName || doc.key || 'records')
|
|
271
|
+
: resolvedAs;
|
|
272
|
+
doc.capabilities.push(value);
|
|
263
273
|
patchOperations.push({
|
|
264
274
|
op: 'add',
|
|
265
275
|
path: '/capabilities/-',
|
|
266
|
-
value
|
|
276
|
+
value
|
|
267
277
|
});
|
|
268
278
|
updatedSections.push(`capabilities[]: ${resolvedAs}`);
|
|
269
279
|
}
|