@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.
- 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-container-start.js +4 -0
- package/lib/app/run-deploy-job.js +216 -0
- package/lib/app/run-env-compose.js +32 -0
- package/lib/app/run-env-recovery.js +35 -0
- package/lib/app/run-helpers.js +15 -9
- package/lib/app/run.js +18 -2
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +13 -9
- 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 +25 -1
- 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/infra-guided-footers.js +1 -1
- package/lib/cli/infra-guided-public-url-warn.js +55 -0
- package/lib/cli/infra-guided.js +65 -56
- package/lib/cli/setup-app.help.js +4 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-app.test-commands.js +59 -28
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +62 -12
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/channel-add-setup.js +8 -2
- package/lib/commands/channel.js +16 -5
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/deploy-gate-orchestrator.js +3 -2
- 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/governance-verify-external.js +2 -5
- package/lib/commands/lifecycle-external.js +21 -8
- 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 +371 -0
- package/lib/commands/test-governance-external.js +2 -5
- package/lib/commands/up-miso.js +61 -24
- package/lib/commands/upload-data-sync-finalize.js +25 -7
- 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 +31 -10
- package/lib/commands/verify-operations-external.js +2 -5
- package/lib/commands/verify-operations-step-plan.js +31 -0
- package/lib/commands/verify-trust-command-action.js +2 -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-identifier.js +4 -3
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/integration-context.js +3 -3
- package/lib/datasource/resolve-app.js +90 -8
- package/lib/datasource/test-e2e.js +2 -2
- package/lib/datasource/unified-validation-run-body.js +15 -1
- package/lib/datasource/unified-validation-run-resolve.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +39 -1
- package/lib/external-system/deploy.js +25 -11
- 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-system-level.js +21 -3
- package/lib/external-system/test.js +43 -7
- package/lib/generator/builders.js +12 -15
- 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-markdown-frontmatter.js +75 -0
- package/lib/role-assistant/knowledge-sync.js +485 -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 +221 -0
- package/lib/role-assistant/test-runner-workhub.js +399 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +24 -28
- 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/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/certify-external-workspace.js +40 -0
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +115 -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/health-check-public-warn.js +31 -13
- package/lib/utils/health-check.js +11 -1
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +80 -38
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +160 -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/upload-sync-options.js +7 -4
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/reserved-system-key-prefixes.js +56 -0
- package/lib/validation/validate-external-file.js +8 -1
- package/lib/validation/validate.js +54 -19
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/builder-api/application.yaml +3 -3
- package/templates/applications/dataplane/application.yaml +24 -17
- package/templates/applications/dataplane/env.template +2 -2
- package/templates/applications/dataplane/rbac.yaml +24 -10
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/env.template +5 -2
- package/templates/applications/miso-controller/application.yaml +3 -3
- package/templates/applications/miso-controller/env.template +17 -2
- package/templates/channels/chatgpt/install.template.md.hbs +82 -26
- package/templates/channels/chatgpt/integration/README.md +11 -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/templates/channels/slack/install.template.md.hbs +6 -1
- package/templates/python/docker-compose.hbs +10 -0
- package/templates/typescript/docker-compose.hbs +10 -0
- /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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper utilities for BTA-facing Evidence authoring APIs
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { unwrapApiData } = require('../utils/external-system-readiness-core');
|
|
10
|
+
|
|
11
|
+
const VALIDATION_STATUS_PASSED = 'passed';
|
|
12
|
+
const VALIDATION_STATUS_FAILED = 'failed';
|
|
13
|
+
const EVIDENCE_WRITABLE_FIELDS = Object.freeze([
|
|
14
|
+
'key',
|
|
15
|
+
'resourceType',
|
|
16
|
+
'roleKey',
|
|
17
|
+
'kindKey',
|
|
18
|
+
'kindVersion',
|
|
19
|
+
'aspect',
|
|
20
|
+
'title',
|
|
21
|
+
'description',
|
|
22
|
+
'when',
|
|
23
|
+
'expectedBehavior',
|
|
24
|
+
'order',
|
|
25
|
+
'status',
|
|
26
|
+
'suggestionExplanation',
|
|
27
|
+
'contributionOverrides',
|
|
28
|
+
'approval'
|
|
29
|
+
]);
|
|
30
|
+
const EVIDENCE_REQUIRED_FIELDS = Object.freeze(['title', 'when', 'expectedBehavior']);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {*} value
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
function trimString(value) {
|
|
37
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {*} res
|
|
42
|
+
* @returns {*}
|
|
43
|
+
*/
|
|
44
|
+
function unwrapBody(res) {
|
|
45
|
+
return unwrapApiData(res) ?? res;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {*} res
|
|
50
|
+
* @param {string} action
|
|
51
|
+
* @returns {*}
|
|
52
|
+
*/
|
|
53
|
+
function assertApiSuccess(res, action) {
|
|
54
|
+
if (res && res.success === false) {
|
|
55
|
+
const message = res.formattedError || res.error || `${action} failed`;
|
|
56
|
+
const err = new Error(message);
|
|
57
|
+
err.status = res.status;
|
|
58
|
+
err.errorData = res.errorData;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
return res;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {*} value
|
|
66
|
+
* @returns {Object[]}
|
|
67
|
+
*/
|
|
68
|
+
function extractCollectionRows(value) {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
const candidates = [
|
|
73
|
+
value?.data?.items,
|
|
74
|
+
value?.data?.rows,
|
|
75
|
+
value?.data?.data,
|
|
76
|
+
value?.items,
|
|
77
|
+
value?.rows,
|
|
78
|
+
value?.data
|
|
79
|
+
];
|
|
80
|
+
for (const candidate of candidates) {
|
|
81
|
+
if (Array.isArray(candidate)) {
|
|
82
|
+
return candidate;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {Object} row
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
function buildEvidenceRowSignature(row) {
|
|
93
|
+
return `${trimString(row?.title)}|${trimString(row?.when)}|${trimString(row?.expectedBehavior)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} row
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
function normalizeEvidenceRef(row) {
|
|
101
|
+
const candidates = [row?.id, row?.key];
|
|
102
|
+
for (const candidate of candidates) {
|
|
103
|
+
const normalized = trimString(candidate);
|
|
104
|
+
if (normalized) {
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {Object[]} rows
|
|
113
|
+
* @returns {Map<string, string[]>}
|
|
114
|
+
*/
|
|
115
|
+
function buildEvidenceReferenceBuckets(rows) {
|
|
116
|
+
const buckets = new Map();
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
const ref = normalizeEvidenceRef(row);
|
|
119
|
+
if (!ref) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const signature = buildEvidenceRowSignature(row);
|
|
123
|
+
if (!signature || signature === '||') {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const refs = buckets.get(signature) || [];
|
|
127
|
+
refs.push(ref);
|
|
128
|
+
buckets.set(signature, refs);
|
|
129
|
+
}
|
|
130
|
+
return buckets;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {Map<string, string[]>} buckets
|
|
135
|
+
* @param {string} signature
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function consumeReferenceForSignature(buckets, signature) {
|
|
139
|
+
const refs = buckets.get(signature);
|
|
140
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
const ref = refs.shift();
|
|
144
|
+
if (refs.length === 0) {
|
|
145
|
+
buckets.delete(signature);
|
|
146
|
+
}
|
|
147
|
+
return ref;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {*} payload
|
|
152
|
+
* @returns {Object[]}
|
|
153
|
+
*/
|
|
154
|
+
function normalizeCandidateRows(payload) {
|
|
155
|
+
if (Array.isArray(payload)) {
|
|
156
|
+
return payload;
|
|
157
|
+
}
|
|
158
|
+
if (payload && Array.isArray(payload.rows)) {
|
|
159
|
+
return payload.rows;
|
|
160
|
+
}
|
|
161
|
+
if (payload && payload.row && typeof payload.row === 'object') {
|
|
162
|
+
return [payload.row];
|
|
163
|
+
}
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {Object[]} rows
|
|
169
|
+
* @param {string} roleKey
|
|
170
|
+
* @returns {{ acceptedRows: Object[], acceptedIndexes: number[], rejected: Object[] }}
|
|
171
|
+
*/
|
|
172
|
+
function partitionUploadRows(rows, roleKey) {
|
|
173
|
+
const acceptedRows = [];
|
|
174
|
+
const acceptedIndexes = [];
|
|
175
|
+
const rejected = [];
|
|
176
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
177
|
+
const row = rows[index];
|
|
178
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
179
|
+
rejected.push({ index, error: 'Row must be an object.' });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const rowRoleKey = trimString(row.roleKey);
|
|
183
|
+
if (rowRoleKey && rowRoleKey !== roleKey) {
|
|
184
|
+
rejected.push({ index, error: `Row roleKey "${rowRoleKey}" does not match "${roleKey}".` });
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
acceptedRows.push({ ...row, roleKey });
|
|
188
|
+
acceptedIndexes.push(index);
|
|
189
|
+
}
|
|
190
|
+
return { acceptedRows, acceptedIndexes, rejected };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {Object} detail
|
|
195
|
+
* @returns {string[]}
|
|
196
|
+
*/
|
|
197
|
+
function extractAssistantCapabilities(detail) {
|
|
198
|
+
const payload = unwrapBody(detail);
|
|
199
|
+
const candidates = [
|
|
200
|
+
payload?.capabilities,
|
|
201
|
+
payload?.assistantCapabilities,
|
|
202
|
+
payload?.data?.capabilities,
|
|
203
|
+
payload?.data?.assistantCapabilities
|
|
204
|
+
];
|
|
205
|
+
for (const candidate of candidates) {
|
|
206
|
+
if (Array.isArray(candidate)) {
|
|
207
|
+
return candidate
|
|
208
|
+
.map((row) => (typeof row === 'string' ? row : (row?.key || row?.name || null)))
|
|
209
|
+
.filter(Boolean);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {Object} params
|
|
217
|
+
* @returns {Object}
|
|
218
|
+
*/
|
|
219
|
+
function buildDownloadQuery(params) {
|
|
220
|
+
const query = {};
|
|
221
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
222
|
+
if (value === undefined || value === null || value === '') {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
query[key] = value;
|
|
226
|
+
}
|
|
227
|
+
return query;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @param {Object} params
|
|
232
|
+
* @returns {import('./types/evidence.types').EvidenceUploadResponse}
|
|
233
|
+
*/
|
|
234
|
+
function buildUploadResult(params) {
|
|
235
|
+
const rejected = params.rejected || [];
|
|
236
|
+
const certified = params.certified || [];
|
|
237
|
+
return {
|
|
238
|
+
roleKey: params.roleKey,
|
|
239
|
+
inserted: Number(params.loadResult?.inserted || 0),
|
|
240
|
+
updated: Number(params.loadResult?.updated || 0),
|
|
241
|
+
accepted: params.accepted || [],
|
|
242
|
+
rejected,
|
|
243
|
+
warnings: params.warnings || [],
|
|
244
|
+
certified,
|
|
245
|
+
readyToActivate: certified.length > 0 && rejected.length === 0
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
VALIDATION_STATUS_PASSED,
|
|
251
|
+
VALIDATION_STATUS_FAILED,
|
|
252
|
+
EVIDENCE_WRITABLE_FIELDS,
|
|
253
|
+
EVIDENCE_REQUIRED_FIELDS,
|
|
254
|
+
trimString,
|
|
255
|
+
unwrapBody,
|
|
256
|
+
assertApiSuccess,
|
|
257
|
+
extractCollectionRows,
|
|
258
|
+
buildEvidenceRowSignature,
|
|
259
|
+
normalizeEvidenceRef,
|
|
260
|
+
buildEvidenceReferenceBuckets,
|
|
261
|
+
consumeReferenceForSignature,
|
|
262
|
+
normalizeCandidateRows,
|
|
263
|
+
partitionUploadRows,
|
|
264
|
+
extractAssistantCapabilities,
|
|
265
|
+
buildDownloadQuery,
|
|
266
|
+
buildUploadResult
|
|
267
|
+
};
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview BTA-facing Evidence authoring APIs (metadata, download, upload, activate)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { createDataplaneApiClient } = require('./index');
|
|
10
|
+
const { listEvidenceKinds } = require('./evidence-kinds.api');
|
|
11
|
+
const { listResourceTypes } = require('./resource-types.api');
|
|
12
|
+
const { getAssistantDetails } = require('./assistants.api');
|
|
13
|
+
const {
|
|
14
|
+
listEvidence,
|
|
15
|
+
loadEvidenceBulk,
|
|
16
|
+
validateEvidenceCandidate,
|
|
17
|
+
certifyEvidenceCandidate,
|
|
18
|
+
activateEvidenceCandidate
|
|
19
|
+
} = require('./evidence.api');
|
|
20
|
+
const {
|
|
21
|
+
VALIDATION_STATUS_PASSED,
|
|
22
|
+
VALIDATION_STATUS_FAILED,
|
|
23
|
+
EVIDENCE_WRITABLE_FIELDS,
|
|
24
|
+
EVIDENCE_REQUIRED_FIELDS,
|
|
25
|
+
trimString,
|
|
26
|
+
unwrapBody,
|
|
27
|
+
assertApiSuccess,
|
|
28
|
+
extractCollectionRows,
|
|
29
|
+
buildEvidenceRowSignature,
|
|
30
|
+
normalizeEvidenceRef,
|
|
31
|
+
buildEvidenceReferenceBuckets,
|
|
32
|
+
consumeReferenceForSignature,
|
|
33
|
+
normalizeCandidateRows,
|
|
34
|
+
partitionUploadRows,
|
|
35
|
+
extractAssistantCapabilities,
|
|
36
|
+
buildDownloadQuery,
|
|
37
|
+
buildUploadResult
|
|
38
|
+
} = require('./evidence-authoring-helpers');
|
|
39
|
+
|
|
40
|
+
const BASE = '/api/v1/evidence';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {Object} ctx
|
|
44
|
+
* @returns {Promise<Map<string, string[]>>}
|
|
45
|
+
*/
|
|
46
|
+
async function listRoleEvidenceReferenceBuckets(ctx) {
|
|
47
|
+
const roleRows = await listEvidence(ctx.dataplaneUrl, ctx.authConfig, { roleKey: ctx.roleKey }, ctx.options);
|
|
48
|
+
return buildEvidenceReferenceBuckets(roleRows);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Object} ctx
|
|
53
|
+
* @param {Object[]} loadedRows
|
|
54
|
+
* @param {Object[]} uploadRows
|
|
55
|
+
* @returns {Promise<string[]>}
|
|
56
|
+
*/
|
|
57
|
+
async function resolveUploadEvidenceRefs(ctx, loadedRows, uploadRows) {
|
|
58
|
+
const refs = [];
|
|
59
|
+
const loadedBuckets = buildEvidenceReferenceBuckets(loadedRows);
|
|
60
|
+
let roleBuckets = null;
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < uploadRows.length; i += 1) {
|
|
63
|
+
const refFromLoad = normalizeEvidenceRef(loadedRows[i]);
|
|
64
|
+
if (refFromLoad) {
|
|
65
|
+
refs.push(refFromLoad);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const signature = buildEvidenceRowSignature(uploadRows[i]);
|
|
69
|
+
let ref = signature ? consumeReferenceForSignature(loadedBuckets, signature) : '';
|
|
70
|
+
if (!ref && signature) {
|
|
71
|
+
if (!roleBuckets) {
|
|
72
|
+
roleBuckets = await listRoleEvidenceReferenceBuckets(ctx);
|
|
73
|
+
}
|
|
74
|
+
ref = consumeReferenceForSignature(roleBuckets, signature);
|
|
75
|
+
}
|
|
76
|
+
refs.push(ref || '');
|
|
77
|
+
}
|
|
78
|
+
return refs;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Object} params
|
|
83
|
+
* @returns {Promise<{ accepted: Object[], rejected: Object[], warnings: Object[], certified: Object[] }>}
|
|
84
|
+
*/
|
|
85
|
+
async function processUploadedRowsLifecycle(params) {
|
|
86
|
+
const { dataplaneUrl, authConfig, options, refs, acceptedRows, acceptedIndexes, rejected, certifyPayload } = params;
|
|
87
|
+
const accepted = [];
|
|
88
|
+
const certified = [];
|
|
89
|
+
const warnings = [];
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < acceptedRows.length; i += 1) {
|
|
92
|
+
const outcome = await processUploadedEvidenceRow({
|
|
93
|
+
dataplaneUrl,
|
|
94
|
+
authConfig,
|
|
95
|
+
options,
|
|
96
|
+
evidenceRef: refs[i],
|
|
97
|
+
rowIndex: acceptedIndexes[i],
|
|
98
|
+
certifyPayload
|
|
99
|
+
});
|
|
100
|
+
applyLifecycleOutcome({ outcome, rejected, warnings, accepted, certified });
|
|
101
|
+
}
|
|
102
|
+
return { accepted, rejected, warnings, certified };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {Object} params
|
|
107
|
+
* @param {Object} params.outcome
|
|
108
|
+
* @param {Object[]} params.rejected
|
|
109
|
+
* @param {Object[]} params.warnings
|
|
110
|
+
* @param {Object[]} params.accepted
|
|
111
|
+
* @param {Object[]} params.certified
|
|
112
|
+
*/
|
|
113
|
+
function applyLifecycleOutcome(params) {
|
|
114
|
+
const { outcome, rejected, warnings, accepted, certified } = params;
|
|
115
|
+
if (outcome.rejection) {
|
|
116
|
+
rejected.push(outcome.rejection);
|
|
117
|
+
}
|
|
118
|
+
if (outcome.warning) {
|
|
119
|
+
warnings.push(outcome.warning);
|
|
120
|
+
}
|
|
121
|
+
if (outcome.accepted) {
|
|
122
|
+
accepted.push(outcome.accepted);
|
|
123
|
+
}
|
|
124
|
+
if (outcome.certified) {
|
|
125
|
+
certified.push(outcome.certified);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {Object} params
|
|
131
|
+
* @returns {Promise<{ rejection?: Object, warning?: Object, accepted?: Object, certified?: Object }>}
|
|
132
|
+
*/
|
|
133
|
+
async function processUploadedEvidenceRow(params) {
|
|
134
|
+
const { dataplaneUrl, authConfig, options, evidenceRef, rowIndex, certifyPayload } = params;
|
|
135
|
+
if (!evidenceRef) {
|
|
136
|
+
return {
|
|
137
|
+
rejection: { index: rowIndex, error: 'Could not resolve uploaded row id/key.' }
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const validation = await validateEvidenceCandidate(dataplaneUrl, authConfig, evidenceRef, options);
|
|
141
|
+
const validationStatus = trimString(validation?.status).toLowerCase();
|
|
142
|
+
if (validationStatus === VALIDATION_STATUS_FAILED || validationStatus !== VALIDATION_STATUS_PASSED) {
|
|
143
|
+
const issues = Array.isArray(validation?.issues) ? validation.issues : [];
|
|
144
|
+
return {
|
|
145
|
+
rejection: {
|
|
146
|
+
index: rowIndex,
|
|
147
|
+
evidenceRef,
|
|
148
|
+
error: `Validation status "${validation?.status || 'unknown'}".`,
|
|
149
|
+
issues
|
|
150
|
+
},
|
|
151
|
+
warning: issues.length > 0 ? { index: rowIndex, evidenceRef, issues } : null
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const certification = await certifyEvidenceCandidate(
|
|
155
|
+
dataplaneUrl,
|
|
156
|
+
authConfig,
|
|
157
|
+
evidenceRef,
|
|
158
|
+
certifyPayload,
|
|
159
|
+
options
|
|
160
|
+
);
|
|
161
|
+
return {
|
|
162
|
+
accepted: { index: rowIndex, evidenceRef, status: VALIDATION_STATUS_PASSED },
|
|
163
|
+
certified: {
|
|
164
|
+
index: rowIndex,
|
|
165
|
+
evidenceRef,
|
|
166
|
+
status: trimString(certification?.status) || 'candidate',
|
|
167
|
+
data: certification
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* BTA-facing metadata API for Evidence authoring.
|
|
174
|
+
*
|
|
175
|
+
* @requiresPermission {Dataplane} worker:read + evidence:read
|
|
176
|
+
* @requiresPermission {Controller} evidence-kinds:read + resource-types:read
|
|
177
|
+
* @async
|
|
178
|
+
* @param {string} dataplaneUrl
|
|
179
|
+
* @param {Object} authConfig
|
|
180
|
+
* @param {string} roleKey
|
|
181
|
+
* @param {Object} [options]
|
|
182
|
+
* @param {string} [options.controllerUrl]
|
|
183
|
+
* @returns {Promise<import('./types/evidence.types').EvidenceAuthoringMetadata>}
|
|
184
|
+
*/
|
|
185
|
+
async function getEvidenceMetadata(dataplaneUrl, authConfig, roleKey, options = {}) {
|
|
186
|
+
const normalizedRoleKey = trimString(roleKey);
|
|
187
|
+
if (!normalizedRoleKey) {
|
|
188
|
+
throw new Error('roleKey is required for evidence.metadata');
|
|
189
|
+
}
|
|
190
|
+
if (!trimString(options.controllerUrl)) {
|
|
191
|
+
throw new Error('controllerUrl is required for evidence.metadata');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const [assistantDetails, evidenceKindsRes, resourceTypesRes, evidenceRows] = await Promise.all([
|
|
195
|
+
getAssistantDetails(dataplaneUrl, normalizedRoleKey, authConfig),
|
|
196
|
+
listEvidenceKinds(options.controllerUrl, authConfig, { page: 1, pageSize: 500 }),
|
|
197
|
+
listResourceTypes(options.controllerUrl, authConfig, { page: 1, pageSize: 500 }),
|
|
198
|
+
listEvidence(dataplaneUrl, authConfig, { roleKey: normalizedRoleKey }, options)
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
const evidenceKinds = extractCollectionRows(evidenceKindsRes).map((row) => ({
|
|
202
|
+
key: row.key || null,
|
|
203
|
+
version: row.version || row.kindVersion || row.metadata?.version || null,
|
|
204
|
+
displayName: row.displayName || null,
|
|
205
|
+
purpose: row.purpose || null
|
|
206
|
+
}));
|
|
207
|
+
const resourceTypes = extractCollectionRows(resourceTypesRes).map((row) => ({
|
|
208
|
+
key: row.key || row.resourceType || null,
|
|
209
|
+
displayName: row.displayName || null,
|
|
210
|
+
category: row.category || null
|
|
211
|
+
}));
|
|
212
|
+
const aspects = Array.from(new Set(evidenceRows.map((row) => trimString(row?.aspect)).filter(Boolean))).sort();
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
roleKey: normalizedRoleKey,
|
|
216
|
+
evidenceKinds,
|
|
217
|
+
aspects,
|
|
218
|
+
resourceTypes,
|
|
219
|
+
assistantCapabilities: extractAssistantCapabilities(assistantDetails),
|
|
220
|
+
writableFields: [...EVIDENCE_WRITABLE_FIELDS],
|
|
221
|
+
requiredFields: [...EVIDENCE_REQUIRED_FIELDS],
|
|
222
|
+
constraints: {
|
|
223
|
+
certification: { validationStatus: VALIDATION_STATUS_PASSED, candidateStatus: 'candidate' },
|
|
224
|
+
activation: { requiresCertified: true, requiresAuthority: true }
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* BTA-facing download/search projection over the existing Evidence list/search route.
|
|
231
|
+
*
|
|
232
|
+
* @requiresPermission {Dataplane} evidence:read
|
|
233
|
+
* @async
|
|
234
|
+
* @param {string} dataplaneUrl
|
|
235
|
+
* @param {Object} authConfig
|
|
236
|
+
* @param {import('./types/evidence.types').EvidenceDownloadParams} params
|
|
237
|
+
* @param {Object} [options]
|
|
238
|
+
* @returns {Promise<import('./types/evidence.types').EvidenceDownloadResponse>}
|
|
239
|
+
*/
|
|
240
|
+
async function downloadEvidence(dataplaneUrl, authConfig, params = {}, options = {}) {
|
|
241
|
+
const roleKey = trimString(params.roleKey);
|
|
242
|
+
if (!roleKey) {
|
|
243
|
+
throw new Error('roleKey is required for evidence.download');
|
|
244
|
+
}
|
|
245
|
+
const client = createDataplaneApiClient(dataplaneUrl, authConfig, {
|
|
246
|
+
controllerUrl: options.controllerUrl || null
|
|
247
|
+
});
|
|
248
|
+
const query = buildDownloadQuery({ ...params, roleKey });
|
|
249
|
+
const res = assertApiSuccess(await client.get(BASE, { params: query }), 'Download evidence');
|
|
250
|
+
const rows = extractCollectionRows(unwrapBody(res));
|
|
251
|
+
return { roleKey, count: rows.length, data: rows };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* BTA-facing upload API: load + validate + certify valid rows, never activate.
|
|
256
|
+
*
|
|
257
|
+
* @requiresPermission {Dataplane} evidence:create + evidence:update
|
|
258
|
+
* @async
|
|
259
|
+
* @param {string} dataplaneUrl
|
|
260
|
+
* @param {Object} authConfig
|
|
261
|
+
* @param {import('./types/evidence.types').EvidenceUploadRequest|Object[]} payload
|
|
262
|
+
* @param {Object} [options]
|
|
263
|
+
* @returns {Promise<import('./types/evidence.types').EvidenceUploadResponse>}
|
|
264
|
+
*/
|
|
265
|
+
async function uploadEvidenceCandidates(dataplaneUrl, authConfig, payload = {}, options = {}) {
|
|
266
|
+
const roleKey = trimString(payload?.roleKey || options?.roleKey);
|
|
267
|
+
if (!roleKey) {
|
|
268
|
+
throw new Error('roleKey is required for evidence.upload');
|
|
269
|
+
}
|
|
270
|
+
const rows = normalizeCandidateRows(payload);
|
|
271
|
+
const { acceptedRows, acceptedIndexes, rejected } = partitionUploadRows(rows, roleKey);
|
|
272
|
+
if (acceptedRows.length === 0) {
|
|
273
|
+
return buildUploadResult({ roleKey, accepted: [], rejected, warnings: [], certified: [] });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const loadResult = await loadEvidenceBulk(dataplaneUrl, authConfig, acceptedRows, options);
|
|
277
|
+
const refs = await resolveUploadEvidenceRefs(
|
|
278
|
+
{ dataplaneUrl, authConfig, roleKey, options },
|
|
279
|
+
Array.isArray(loadResult.data) ? loadResult.data : [],
|
|
280
|
+
acceptedRows
|
|
281
|
+
);
|
|
282
|
+
const certifyPayload = trimString(payload?.comment) ? { comment: trimString(payload.comment) } : {};
|
|
283
|
+
const lifecycle = await processUploadedRowsLifecycle({
|
|
284
|
+
dataplaneUrl,
|
|
285
|
+
authConfig,
|
|
286
|
+
options,
|
|
287
|
+
refs,
|
|
288
|
+
acceptedRows,
|
|
289
|
+
acceptedIndexes,
|
|
290
|
+
rejected,
|
|
291
|
+
certifyPayload
|
|
292
|
+
});
|
|
293
|
+
return buildUploadResult({ roleKey, loadResult, ...lifecycle });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* BTA-facing activation API for one or many human-approved certified evidence IDs.
|
|
298
|
+
*
|
|
299
|
+
* @requiresPermission {Dataplane} evidence:update
|
|
300
|
+
* @async
|
|
301
|
+
* @param {string} dataplaneUrl
|
|
302
|
+
* @param {Object} authConfig
|
|
303
|
+
* @param {import('./types/evidence.types').EvidenceActivateRequest} payload
|
|
304
|
+
* @param {Object} [options]
|
|
305
|
+
* @returns {Promise<import('./types/evidence.types').EvidenceActivateResponse>}
|
|
306
|
+
*/
|
|
307
|
+
async function activateEvidenceCandidates(dataplaneUrl, authConfig, payload = {}, options = {}) {
|
|
308
|
+
const evidenceIds = Array.from(
|
|
309
|
+
new Set(
|
|
310
|
+
(Array.isArray(payload?.evidenceIds) ? payload.evidenceIds : [])
|
|
311
|
+
.map((value) => trimString(value))
|
|
312
|
+
.filter(Boolean)
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
if (evidenceIds.length === 0) {
|
|
316
|
+
throw new Error('evidenceIds[] is required for evidence.activate');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const activated = [];
|
|
320
|
+
const failed = [];
|
|
321
|
+
for (const evidenceId of evidenceIds) {
|
|
322
|
+
try {
|
|
323
|
+
const result = await activateEvidenceCandidate(dataplaneUrl, authConfig, evidenceId, options);
|
|
324
|
+
activated.push({ evidenceId, result });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
failed.push({ evidenceId, error: error?.message || String(error), status: error?.status || null });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return { requested: evidenceIds.length, activated, failed, success: failed.length === 0 };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
getEvidenceMetadata,
|
|
334
|
+
downloadEvidence,
|
|
335
|
+
uploadEvidenceCandidates,
|
|
336
|
+
activateEvidenceCandidates
|
|
337
|
+
};
|
package/lib/api/evidence.api.js
CHANGED
|
@@ -162,11 +162,67 @@ async function loadEvidenceBulk(dataplaneUrl, authConfig, rows, options = {}) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} dataplaneUrl
|
|
167
|
+
* @param {Object} authConfig
|
|
168
|
+
* @param {string} evidenceIdOrKey
|
|
169
|
+
* @param {Object} [options]
|
|
170
|
+
* @returns {Promise<Object>}
|
|
171
|
+
*/
|
|
172
|
+
async function validateEvidenceCandidate(dataplaneUrl, authConfig, evidenceIdOrKey, options = {}) {
|
|
173
|
+
const client = createEvidenceClient(dataplaneUrl, authConfig, options);
|
|
174
|
+
const res = assertApiSuccess(
|
|
175
|
+
await client.post(`${BASE}/${encodeURIComponent(evidenceIdOrKey)}/validate`, { body: {} }),
|
|
176
|
+
'Validate evidence'
|
|
177
|
+
);
|
|
178
|
+
return unwrapBody(res);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {string} dataplaneUrl
|
|
183
|
+
* @param {Object} authConfig
|
|
184
|
+
* @param {string} evidenceIdOrKey
|
|
185
|
+
* @param {{ comment?: string }} [payload]
|
|
186
|
+
* @param {Object} [options]
|
|
187
|
+
* @returns {Promise<Object>}
|
|
188
|
+
*/
|
|
189
|
+
async function certifyEvidenceCandidate(dataplaneUrl, authConfig, evidenceIdOrKey, payload = {}, options = {}) {
|
|
190
|
+
const client = createEvidenceClient(dataplaneUrl, authConfig, options);
|
|
191
|
+
const body = {};
|
|
192
|
+
if (payload && typeof payload.comment === 'string' && payload.comment.trim()) {
|
|
193
|
+
body.comment = payload.comment.trim();
|
|
194
|
+
}
|
|
195
|
+
const res = assertApiSuccess(
|
|
196
|
+
await client.post(`${BASE}/${encodeURIComponent(evidenceIdOrKey)}/certify`, { body }),
|
|
197
|
+
'Certify evidence'
|
|
198
|
+
);
|
|
199
|
+
return unwrapBody(res);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} dataplaneUrl
|
|
204
|
+
* @param {Object} authConfig
|
|
205
|
+
* @param {string} evidenceIdOrKey
|
|
206
|
+
* @param {Object} [options]
|
|
207
|
+
* @returns {Promise<Object>}
|
|
208
|
+
*/
|
|
209
|
+
async function activateEvidenceCandidate(dataplaneUrl, authConfig, evidenceIdOrKey, options = {}) {
|
|
210
|
+
const client = createEvidenceClient(dataplaneUrl, authConfig, options);
|
|
211
|
+
const res = assertApiSuccess(
|
|
212
|
+
await client.post(`${BASE}/${encodeURIComponent(evidenceIdOrKey)}/activate`, { body: {} }),
|
|
213
|
+
'Activate evidence'
|
|
214
|
+
);
|
|
215
|
+
return unwrapBody(res);
|
|
216
|
+
}
|
|
217
|
+
|
|
165
218
|
module.exports = {
|
|
166
219
|
listEvidence,
|
|
167
220
|
getEvidence,
|
|
168
221
|
insertEvidence,
|
|
169
222
|
updateEvidence,
|
|
170
223
|
deleteEvidence,
|
|
171
|
-
loadEvidenceBulk
|
|
224
|
+
loadEvidenceBulk,
|
|
225
|
+
validateEvidenceCandidate,
|
|
226
|
+
certifyEvidenceCandidate,
|
|
227
|
+
activateEvidenceCandidate
|
|
172
228
|
};
|