@aifabrix/builder 2.51.0 → 2.51.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 +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-sync.js +34 -17
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/wizard.js +51 -4
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-spec.js +47 -28
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-template-workspace.js +28 -1
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +16 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/infra.parameter.yaml +20 -0
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +67 -23
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/validate-external-file.js +8 -1
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/dataplane/application.yaml +23 -15
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +14 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- /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,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role assistant test case loading and assertion helpers.
|
|
3
|
+
* @fileoverview Parse role-assistant/<roleKey>/tests/*.yaml and evaluate expectations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('../internal/fs-real-sync');
|
|
9
|
+
const yaml = require('js-yaml');
|
|
10
|
+
const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
|
|
11
|
+
const { VERDICT } = require('../lifecycle/product-model');
|
|
12
|
+
const {
|
|
13
|
+
buildHonestyObservation,
|
|
14
|
+
evaluateHonestyExpectations
|
|
15
|
+
} = require('./test-cases-honesty');
|
|
16
|
+
function loadNodeFsSnapshot() {
|
|
17
|
+
if (global.__AIFABRIX_NODE_FS_UNMOCKED__) {
|
|
18
|
+
return global.__AIFABRIX_NODE_FS_UNMOCKED__;
|
|
19
|
+
}
|
|
20
|
+
return typeof jest !== 'undefined' && typeof jest.requireActual === 'function'
|
|
21
|
+
? jest.requireActual('node:fs')
|
|
22
|
+
: require('node:fs');
|
|
23
|
+
}
|
|
24
|
+
const nodeFs = loadNodeFsSnapshot();
|
|
25
|
+
const SUPPORTED_LIFECYCLE_STATES = new Set(['completed', 'question', 'approval-required', 'dependency', 'safe-stop', 'rejected', 'failed']);
|
|
26
|
+
const LIFECYCLE_STATE_ALIASES = Object.freeze({
|
|
27
|
+
completed: ['completed', 'complete', 'done', 'success', 'verified'],
|
|
28
|
+
question: ['question', 'question_required', 'awaiting_question', 'pending_question'],
|
|
29
|
+
'approval-required': ['approval_required', 'awaiting_approval', 'pending_approval', 'requires_approval'],
|
|
30
|
+
dependency: ['dependency', 'dependency_wait', 'dependency_blocked', 'awaiting_dependency'],
|
|
31
|
+
'safe-stop': ['safe_stop', 'safe_stopped', 'policy_blocked', 'halted', 'paused'],
|
|
32
|
+
rejected: ['rejected', 'denied', 'declined', 'policy_rejected'],
|
|
33
|
+
failed: ['failed', 'error', 'errored', 'timeout', 'cancelled', 'canceled']
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const WORK_STATUS_TO_LIFECYCLE = Object.freeze({
|
|
37
|
+
completed: 'completed', waiting: 'question', running: 'dependency', stopped: 'safe-stop'
|
|
38
|
+
});
|
|
39
|
+
const WAIT_REASON_TO_LIFECYCLE = Object.freeze({
|
|
40
|
+
ask: 'question', approval: 'approval-required', ownerroute: 'dependency',
|
|
41
|
+
realityconfirm: 'question', knowledgeconfirm: 'question'
|
|
42
|
+
});
|
|
43
|
+
const WORKER_TASK_STATUS_TO_LIFECYCLE = Object.freeze({
|
|
44
|
+
draft: 'dependency', pendingapproval: 'approval-required', approved: 'dependency',
|
|
45
|
+
running: 'dependency', completed: 'completed', failed: 'failed',
|
|
46
|
+
cancelled: 'rejected', canceled: 'rejected'
|
|
47
|
+
});
|
|
48
|
+
const WORKER_APPROVAL_STATE_TO_LIFECYCLE = Object.freeze({
|
|
49
|
+
pending: 'approval-required', approved: 'dependency', rejected: 'rejected'
|
|
50
|
+
});
|
|
51
|
+
function safeExistsSync(filePath) {
|
|
52
|
+
// Prefer unmocked node:fs so Jest worker fs mocks cannot hide real temp files.
|
|
53
|
+
return nodeFs.existsSync(filePath) || fs.existsSync(filePath);
|
|
54
|
+
}
|
|
55
|
+
function safeReadFileSync(filePath, encoding) {
|
|
56
|
+
if (nodeFs.existsSync(filePath)) {
|
|
57
|
+
return nodeFs.readFileSync(filePath, encoding);
|
|
58
|
+
}
|
|
59
|
+
return fs.readFileSync(filePath, encoding);
|
|
60
|
+
}
|
|
61
|
+
function safeReaddirSync(dirPath) {
|
|
62
|
+
if (nodeFs.existsSync(dirPath)) {
|
|
63
|
+
return nodeFs.readdirSync(dirPath);
|
|
64
|
+
}
|
|
65
|
+
return fs.readdirSync(dirPath);
|
|
66
|
+
}
|
|
67
|
+
function roleAssistantTestsDir(roleKey, options = {}) {
|
|
68
|
+
if (options.testsDir) {
|
|
69
|
+
return path.resolve(String(options.testsDir));
|
|
70
|
+
}
|
|
71
|
+
const root = resolveExternalSystemPath(roleKey, options.pathOptions || {});
|
|
72
|
+
return path.join(root, 'tests');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseCaseYamlFile(filePath) {
|
|
76
|
+
const raw = safeReadFileSync(filePath, 'utf8');
|
|
77
|
+
const parsed = yaml.load(raw);
|
|
78
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
79
|
+
throw new Error(`Test case must be an object: ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeCaseFromFile(filePath, parsed) {
|
|
85
|
+
const fileBase = path.basename(filePath, path.extname(filePath));
|
|
86
|
+
const caseId = String(parsed.caseId || fileBase).trim();
|
|
87
|
+
if (!caseId) {
|
|
88
|
+
throw new Error(`Test case is missing caseId: ${filePath}`);
|
|
89
|
+
}
|
|
90
|
+
const expected = parsed.expected && typeof parsed.expected === 'object' ? parsed.expected : {};
|
|
91
|
+
if (
|
|
92
|
+
expected.lifecycleState &&
|
|
93
|
+
!SUPPORTED_LIFECYCLE_STATES.has(String(expected.lifecycleState).trim())
|
|
94
|
+
) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Unsupported expected.lifecycleState "${expected.lifecycleState}" in ${filePath}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
caseId,
|
|
101
|
+
input: parsed.input || {},
|
|
102
|
+
answers: Array.isArray(parsed.answers) ? parsed.answers : [],
|
|
103
|
+
approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
|
|
104
|
+
expected,
|
|
105
|
+
sourceFile: filePath
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function listTestCaseFiles(testsDir) {
|
|
110
|
+
if (!safeExistsSync(testsDir)) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
return safeReaddirSync(testsDir)
|
|
114
|
+
.filter((entry) => /\.(ya?ml)$/i.test(entry))
|
|
115
|
+
.map((entry) => path.join(testsDir, entry));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function looksLikePathSelector(selector) {
|
|
119
|
+
return (
|
|
120
|
+
selector.includes('/') ||
|
|
121
|
+
selector.includes('\\') ||
|
|
122
|
+
selector.startsWith('.') ||
|
|
123
|
+
/\.[a-z0-9]+$/i.test(selector)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findCaseFileBySelector(files, selector) {
|
|
128
|
+
if (!selector) {
|
|
129
|
+
return files.length > 0 ? files[0] : null;
|
|
130
|
+
}
|
|
131
|
+
const normalized = String(selector).trim();
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
return files.length > 0 ? files[0] : null;
|
|
134
|
+
}
|
|
135
|
+
const pathLikeSelector = looksLikePathSelector(normalized);
|
|
136
|
+
const byName = files.find((filePath) => {
|
|
137
|
+
const base = path.basename(filePath, path.extname(filePath));
|
|
138
|
+
return base === normalized;
|
|
139
|
+
});
|
|
140
|
+
if (byName) {
|
|
141
|
+
return byName;
|
|
142
|
+
}
|
|
143
|
+
for (const filePath of files) {
|
|
144
|
+
const parsed = parseCaseYamlFile(filePath);
|
|
145
|
+
const caseId = String(parsed.caseId || '').trim();
|
|
146
|
+
if (caseId === normalized) {
|
|
147
|
+
return filePath;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (pathLikeSelector && safeExistsSync(normalized)) {
|
|
151
|
+
return path.resolve(normalized);
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function fallbackCaseFileBySelector(testsDir, selector) {
|
|
157
|
+
const normalized = typeof selector === 'string' ? selector.trim() : '';
|
|
158
|
+
if (!normalized) return null;
|
|
159
|
+
const candidates = [path.join(testsDir, normalized), path.join(testsDir, `${normalized}.yaml`), path.join(testsDir, `${normalized}.yml`)];
|
|
160
|
+
return candidates.find((candidate) => safeExistsSync(candidate)) || null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function loadRoleAssistantTestCase(roleKey, selector, options = {}) {
|
|
164
|
+
const testsDir = roleAssistantTestsDir(roleKey, options);
|
|
165
|
+
const files = listTestCaseFiles(testsDir);
|
|
166
|
+
const filePath = findCaseFileBySelector(files, selector) || fallbackCaseFileBySelector(testsDir, selector);
|
|
167
|
+
if (!filePath) {
|
|
168
|
+
const lookup = selector ? `"${selector}"` : '(default case)';
|
|
169
|
+
throw new Error(`Role assistant test case ${lookup} not found in ${testsDir}`);
|
|
170
|
+
}
|
|
171
|
+
const parsed = parseCaseYamlFile(filePath);
|
|
172
|
+
return normalizeCaseFromFile(filePath, parsed);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeLifecycleStateToken(value) {
|
|
176
|
+
if (typeof value !== 'string') {
|
|
177
|
+
return '';
|
|
178
|
+
}
|
|
179
|
+
return value
|
|
180
|
+
.trim()
|
|
181
|
+
.toLowerCase()
|
|
182
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
183
|
+
.replace(/^_+|_+$/g, '');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function lifecycleStateFromToken(token) {
|
|
187
|
+
if (!token) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
for (const [state, aliases] of Object.entries(LIFECYCLE_STATE_ALIASES)) {
|
|
191
|
+
for (const alias of aliases) {
|
|
192
|
+
if (token === alias || token.startsWith(`${alias}_`)) {
|
|
193
|
+
return state;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function lifecycleStateFromSignalValue(value) {
|
|
201
|
+
if (value === null || value === undefined) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
if (typeof value === 'string') {
|
|
205
|
+
return lifecycleStateFromToken(normalizeLifecycleStateToken(value));
|
|
206
|
+
}
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
for (const row of value) {
|
|
209
|
+
const state = lifecycleStateFromSignalValue(row);
|
|
210
|
+
if (state) {
|
|
211
|
+
return state;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (typeof value === 'object') {
|
|
217
|
+
const fields = [
|
|
218
|
+
'lifecycleState',
|
|
219
|
+
'state',
|
|
220
|
+
'status',
|
|
221
|
+
'code',
|
|
222
|
+
'type',
|
|
223
|
+
'reason',
|
|
224
|
+
'kind',
|
|
225
|
+
'category',
|
|
226
|
+
'errorCode'
|
|
227
|
+
];
|
|
228
|
+
for (const field of fields) {
|
|
229
|
+
const state = lifecycleStateFromSignalValue(value[field]);
|
|
230
|
+
if (state) {
|
|
231
|
+
return state;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function mapWorkStatusFromResult(result) {
|
|
239
|
+
const rawStatus = normalizeLifecycleStateToken(result?.status);
|
|
240
|
+
if (rawStatus && WORK_STATUS_TO_LIFECYCLE[rawStatus]) {
|
|
241
|
+
const waitReason = normalizeLifecycleStateToken(result?.wait?.waitReason || '');
|
|
242
|
+
if (rawStatus === 'waiting' && waitReason && WAIT_REASON_TO_LIFECYCLE[waitReason]) {
|
|
243
|
+
return WAIT_REASON_TO_LIFECYCLE[waitReason];
|
|
244
|
+
}
|
|
245
|
+
return WORK_STATUS_TO_LIFECYCLE[rawStatus];
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function mapWorkerTaskStateFromResult(result) {
|
|
251
|
+
const statusCandidates = [result?.task?.status, result?.status, result?.workerTask?.status];
|
|
252
|
+
for (const row of statusCandidates) {
|
|
253
|
+
const token = normalizeLifecycleStateToken(row);
|
|
254
|
+
if (token && WORKER_TASK_STATUS_TO_LIFECYCLE[token]) {
|
|
255
|
+
return WORKER_TASK_STATUS_TO_LIFECYCLE[token];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const approvalCandidates = [result?.task?.approvalState, result?.approvalState, result?.workerTask?.approvalState];
|
|
259
|
+
for (const row of approvalCandidates) {
|
|
260
|
+
const token = normalizeLifecycleStateToken(row);
|
|
261
|
+
if (token && WORKER_APPROVAL_STATE_TO_LIFECYCLE[token]) {
|
|
262
|
+
return WORKER_APPROVAL_STATE_TO_LIFECYCLE[token];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resolveLifecycleStateFromResult(result) {
|
|
269
|
+
const workState = mapWorkStatusFromResult(result);
|
|
270
|
+
if (workState) {
|
|
271
|
+
return workState;
|
|
272
|
+
}
|
|
273
|
+
const workerTaskState = mapWorkerTaskStateFromResult(result);
|
|
274
|
+
if (workerTaskState) {
|
|
275
|
+
return workerTaskState;
|
|
276
|
+
}
|
|
277
|
+
const topLevelCandidates = [
|
|
278
|
+
result?.lifecycleState, result?.state, result?.status, result?.lifecycle?.state,
|
|
279
|
+
result?.report?.lifecycleState, result?.report?.state, result?.report?.status,
|
|
280
|
+
result?.report?.lifecycle?.state, result?.report?.runtime?.state, result?.report?.work?.state
|
|
281
|
+
];
|
|
282
|
+
for (const candidate of topLevelCandidates) {
|
|
283
|
+
const state = lifecycleStateFromSignalValue(candidate);
|
|
284
|
+
if (state) {
|
|
285
|
+
return state;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const steps = Array.isArray(result?.stepsRun) ? result.stepsRun : [];
|
|
289
|
+
for (const row of steps) {
|
|
290
|
+
const stepCandidates = [
|
|
291
|
+
row?.lifecycleState, row?.state, row?.status, row?.code, row?.errorCode, row?.reason,
|
|
292
|
+
row?.kind, row?.type, row?.error, row?.failure, row?.details, row?.result
|
|
293
|
+
];
|
|
294
|
+
for (const candidate of stepCandidates) {
|
|
295
|
+
const state = lifecycleStateFromSignalValue(candidate);
|
|
296
|
+
if (state) {
|
|
297
|
+
return state;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function deriveLifecycleState(result, lifecycleVerdict) {
|
|
305
|
+
if (lifecycleVerdict === VERDICT.VERIFIED) {
|
|
306
|
+
return 'completed';
|
|
307
|
+
}
|
|
308
|
+
const structuredState = resolveLifecycleStateFromResult(result);
|
|
309
|
+
if (structuredState) {
|
|
310
|
+
return structuredState;
|
|
311
|
+
}
|
|
312
|
+
if (lifecycleVerdict === VERDICT.NOT_VERIFIED) return 'safe-stop';
|
|
313
|
+
return 'failed';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function addCapabilityToken(set, value) {
|
|
317
|
+
if (typeof value === 'string' && value.trim()) set.add(value.trim());
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function collectCapabilitiesFromWorkSteps(out, steps) {
|
|
321
|
+
if (!Array.isArray(steps)) return;
|
|
322
|
+
for (const row of steps) {
|
|
323
|
+
if (!row || typeof row !== 'object') continue;
|
|
324
|
+
addCapabilityToken(out, row.capabilityKey);
|
|
325
|
+
addCapabilityToken(out, row.capability);
|
|
326
|
+
addCapabilityToken(out, row.capabilityId);
|
|
327
|
+
if (row.executionOutcome && typeof row.executionOutcome === 'object') {
|
|
328
|
+
addCapabilityToken(out, row.executionOutcome.capabilityKey);
|
|
329
|
+
addCapabilityToken(out, row.executionOutcome.capability);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function collectObservedCapabilities(result) {
|
|
335
|
+
const out = new Set();
|
|
336
|
+
const steps = Array.isArray(result?.stepsRun) ? result.stepsRun : [];
|
|
337
|
+
for (const row of steps) {
|
|
338
|
+
if (row && typeof row.pillar === 'string' && row.pillar.trim()) {
|
|
339
|
+
out.add(row.pillar.trim());
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
collectCapabilitiesFromWorkSteps(out, result?.steps);
|
|
343
|
+
collectCapabilitiesFromWorkSteps(out, result?.workResult?.steps);
|
|
344
|
+
addCapabilityToken(out, result?.task?.capabilityKey);
|
|
345
|
+
addCapabilityToken(out, result?.task?.capability);
|
|
346
|
+
if (result?.task?.approvalContext && typeof result.task.approvalContext === 'object') {
|
|
347
|
+
addCapabilityToken(out, result.task.approvalContext.capabilityKey);
|
|
348
|
+
addCapabilityToken(out, result.task.approvalContext.capability);
|
|
349
|
+
}
|
|
350
|
+
return [...out];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = {
|
|
354
|
+
SUPPORTED_LIFECYCLE_STATES,
|
|
355
|
+
roleAssistantTestsDir,
|
|
356
|
+
loadRoleAssistantTestCase,
|
|
357
|
+
deriveLifecycleState,
|
|
358
|
+
collectObservedCapabilities,
|
|
359
|
+
buildHonestyObservation,
|
|
360
|
+
evaluateHonestyExpectations
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const { evaluateRoleAssistantTestCase } = require('./test-cases-evaluate');
|
|
364
|
+
module.exports.evaluateRoleAssistantTestCase = evaluateRoleAssistantTestCase;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answer selection / synthesis for Work Hub human.respond waits.
|
|
3
|
+
* @fileoverview RA Work Hub wait answers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function resolveWorkHubAnswerValue(answer) {
|
|
9
|
+
if (answer === null || answer === undefined) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (typeof answer !== 'object') {
|
|
13
|
+
return answer;
|
|
14
|
+
}
|
|
15
|
+
if (answer.value !== undefined) {
|
|
16
|
+
return answer.value;
|
|
17
|
+
}
|
|
18
|
+
if (answer.answer !== undefined) {
|
|
19
|
+
return answer.answer;
|
|
20
|
+
}
|
|
21
|
+
if (answer.respond && typeof answer.respond === 'object' && answer.respond.value !== undefined) {
|
|
22
|
+
return answer.respond.value;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pickAnswerForWait(answers, wait) {
|
|
28
|
+
const questionKey = String(wait.questionKey || wait.requestKey || '').trim().toLowerCase();
|
|
29
|
+
if (!questionKey) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of answers) {
|
|
33
|
+
if (!entry || typeof entry !== 'object') {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const when = entry.when && typeof entry.when === 'object' ? entry.when : {};
|
|
37
|
+
const matchKey = String(when.questionKey || when.requestKey || when.key || '')
|
|
38
|
+
.trim()
|
|
39
|
+
.toLowerCase();
|
|
40
|
+
if (matchKey && matchKey === questionKey) {
|
|
41
|
+
return entry;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseFieldsFromPrompt(prompt) {
|
|
48
|
+
const match = /\(fields:\s*([^)]+)\)/i.exec(String(prompt || ''));
|
|
49
|
+
if (!match) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
return match[1]
|
|
53
|
+
.split(',')
|
|
54
|
+
.map((row) => row.trim())
|
|
55
|
+
.filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractObjectiveHints(objective) {
|
|
59
|
+
const text = String(objective || '');
|
|
60
|
+
const emailMatch = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.exec(text);
|
|
61
|
+
const email = emailMatch ? emailMatch[0] : '';
|
|
62
|
+
const domain = email.includes('@') ? email.split('@')[1] : '';
|
|
63
|
+
const companyMatch =
|
|
64
|
+
/\b([A-Z][\w.&'-]*(?:\s+[\w.&'-]+){0,4}\s+Ltd\.?)\b/.exec(text) ||
|
|
65
|
+
/(?:for|customer)\s+([A-Z][\w.&'-]*(?:\s+[\w.&'-]+){0,3})/i.exec(text);
|
|
66
|
+
const name = companyMatch ? companyMatch[1].replace(/[.,]+$/, '').trim() : '';
|
|
67
|
+
const addressMatch = /(?:to|address)\s+(.+?)\s*$/i.exec(text);
|
|
68
|
+
const address = addressMatch ? addressMatch[1].trim() : '';
|
|
69
|
+
return { email, domain, name, address };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fillFieldsFromHints(fields, hints) {
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const key of fields) {
|
|
75
|
+
const lower = key.toLowerCase();
|
|
76
|
+
if (hints[lower]) {
|
|
77
|
+
out[key] = hints[lower];
|
|
78
|
+
} else if (lower.includes('mail') && hints.email) {
|
|
79
|
+
out[key] = hints.email;
|
|
80
|
+
} else if (lower.includes('name') && hints.name) {
|
|
81
|
+
out[key] = hints.name;
|
|
82
|
+
} else if (lower.includes('domain') && hints.domain) {
|
|
83
|
+
out[key] = hints.domain;
|
|
84
|
+
} else if (lower.includes('address') && hints.address) {
|
|
85
|
+
out[key] = hints.address;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function synthesizeFromCapability(capability, hints) {
|
|
92
|
+
const out = {};
|
|
93
|
+
if (capability.includes('contact')) {
|
|
94
|
+
if (hints.email) {
|
|
95
|
+
out.email = hints.email;
|
|
96
|
+
}
|
|
97
|
+
if (hints.name || hints.email) {
|
|
98
|
+
out.firstname = String(hints.email || 'user').split('@')[0];
|
|
99
|
+
out.lastname = hints.name || 'Contact';
|
|
100
|
+
}
|
|
101
|
+
} else if (capability.includes('deal')) {
|
|
102
|
+
if (hints.name) {
|
|
103
|
+
out.dealname = hints.name;
|
|
104
|
+
}
|
|
105
|
+
} else if (capability.includes('customer') || capability.includes('compan')) {
|
|
106
|
+
if (hints.name) {
|
|
107
|
+
out.name = hints.name;
|
|
108
|
+
}
|
|
109
|
+
if (hints.domain) {
|
|
110
|
+
out.domain = hints.domain;
|
|
111
|
+
}
|
|
112
|
+
if (hints.address) {
|
|
113
|
+
out.address = hints.address;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (hints.name) {
|
|
117
|
+
out.name = hints.name;
|
|
118
|
+
}
|
|
119
|
+
if (hints.email) {
|
|
120
|
+
out.email = hints.email;
|
|
121
|
+
}
|
|
122
|
+
if (hints.domain) {
|
|
123
|
+
out.domain = hints.domain;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function synthesizeWaitAnswer(wait, statusRow, objective) {
|
|
130
|
+
const prompt = String(wait.prompt || statusRow?.message || '').trim();
|
|
131
|
+
const hints = extractObjectiveHints(objective);
|
|
132
|
+
const fields = parseFieldsFromPrompt(prompt);
|
|
133
|
+
if (fields.length > 0) {
|
|
134
|
+
return fillFieldsFromHints(fields, hints);
|
|
135
|
+
}
|
|
136
|
+
const kind = String(wait.objectRef?.kind || '').trim();
|
|
137
|
+
const isMissingInput =
|
|
138
|
+
kind === 'missingRequestBody' ||
|
|
139
|
+
kind === 'missingPathParams' ||
|
|
140
|
+
/missing required (request body|path params)/i.test(prompt);
|
|
141
|
+
if (!isMissingInput) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const capability = String(wait.questionKey || wait.objectRef?.capabilityKey || '').toLowerCase();
|
|
145
|
+
return synthesizeFromCapability(capability, hints);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveRespondValue(testCase, wait, statusRow, objective) {
|
|
149
|
+
const answers = Array.isArray(testCase.answers) ? testCase.answers : [];
|
|
150
|
+
const matched = pickAnswerForWait(answers, wait) || answers[0] || null;
|
|
151
|
+
const fromCase = matched ? resolveWorkHubAnswerValue(matched) : undefined;
|
|
152
|
+
if (fromCase !== undefined) {
|
|
153
|
+
return fromCase;
|
|
154
|
+
}
|
|
155
|
+
return synthesizeWaitAnswer(wait, statusRow, objective);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function waitRespondFingerprint(wait) {
|
|
159
|
+
return String(
|
|
160
|
+
wait.requestFingerprint || wait.requestKey || wait.questionKey || wait.prompt || 'wait'
|
|
161
|
+
).trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function alreadyResponded(interactions, fingerprint) {
|
|
165
|
+
return interactions.some(
|
|
166
|
+
(row) => row && row.type === 'respond' && row.fingerprint === fingerprint
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildRespondPayload(peId, wait, testCase, statusRow, interactions, objective) {
|
|
171
|
+
const answerType =
|
|
172
|
+
String(wait.answerType || wait.waitReason || 'ask').toLowerCase() === 'approval'
|
|
173
|
+
? 'approval'
|
|
174
|
+
: 'ask';
|
|
175
|
+
const payload = {
|
|
176
|
+
respondIdempotencyKey: `ra-workhub-${peId}-${interactions.length}`,
|
|
177
|
+
answerType,
|
|
178
|
+
requestVersion: wait.requestVersion || undefined,
|
|
179
|
+
stateVersionRef: wait.stateVersionRef || undefined,
|
|
180
|
+
requestFingerprint: wait.requestFingerprint || undefined
|
|
181
|
+
};
|
|
182
|
+
if (answerType === 'approval') {
|
|
183
|
+
payload.approvalDecision = 'approved';
|
|
184
|
+
return payload;
|
|
185
|
+
}
|
|
186
|
+
const value = resolveRespondValue(testCase, wait, statusRow, objective);
|
|
187
|
+
if (value === undefined) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
payload.value = value;
|
|
191
|
+
return payload;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
resolveWorkHubAnswerValue,
|
|
196
|
+
pickAnswerForWait,
|
|
197
|
+
synthesizeWaitAnswer,
|
|
198
|
+
extractObjectiveHints,
|
|
199
|
+
resolveRespondValue,
|
|
200
|
+
waitRespondFingerprint,
|
|
201
|
+
alreadyResponded,
|
|
202
|
+
buildRespondPayload
|
|
203
|
+
};
|