@aifabrix/builder 2.50.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.
Files changed (236) hide show
  1. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
  2. package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
  3. package/integration/hubspot-test/create-hubspot.js +1 -16
  4. package/jest.isolated-projects.js +77 -1
  5. package/jest.projects.js +5 -426
  6. package/lib/api/assistants.api.js +67 -0
  7. package/lib/api/datasources-extended.api.js +6 -6
  8. package/lib/api/evidence-authoring-helpers.js +267 -0
  9. package/lib/api/evidence-authoring.api.js +337 -0
  10. package/lib/api/evidence-kinds.api.js +128 -0
  11. package/lib/api/evidence.api.js +57 -1
  12. package/lib/api/index.js +24 -7
  13. package/lib/api/pipeline.api.js +47 -3
  14. package/lib/api/records-bulk.api.js +28 -3
  15. package/lib/api/resource-types.api.js +4 -4
  16. package/lib/api/roles.api.js +121 -0
  17. package/lib/api/types/evidence-kinds.types.js +42 -0
  18. package/lib/api/types/evidence.types.js +156 -8
  19. package/lib/api/types/roles.types.js +36 -0
  20. package/lib/api/work.api.js +283 -0
  21. package/lib/app/certification-verify-rows.js +1 -1
  22. package/lib/app/run-deploy-job.js +108 -0
  23. package/lib/app/run-helpers.js +10 -3
  24. package/lib/app/run.js +8 -0
  25. package/lib/app/show.js +2 -2
  26. package/lib/certification/merge-certification-from-artifact.js +1 -1
  27. package/lib/channels/add-channel.js +54 -23
  28. package/lib/channels/approval-guides/chatgpt.js +8 -7
  29. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  30. package/lib/channels/channel-artifact-generator.js +16 -3
  31. package/lib/channels/channel-credential.js +3 -1
  32. package/lib/channels/channel-probe-evidence.js +79 -0
  33. package/lib/channels/channel-probe.js +265 -13
  34. package/lib/channels/channel-runtime-resolver.js +120 -29
  35. package/lib/channels/channel-status-display.js +44 -0
  36. package/lib/channels/channel-targets.js +7 -0
  37. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  38. package/lib/channels/microsoft-copilot-package.js +280 -0
  39. package/lib/channels/runtime-tool-catalog.js +165 -0
  40. package/lib/cli/index.js +6 -0
  41. package/lib/cli/setup-app.js +26 -5
  42. package/lib/cli/setup-dev.js +23 -7
  43. package/lib/cli/setup-external-system-help.js +8 -2
  44. package/lib/cli/setup-external-system.js +12 -0
  45. package/lib/cli/setup-utility-help-after.js +12 -0
  46. package/lib/cli/setup-utility-validate.js +41 -2
  47. package/lib/commands/app-test.js +2 -14
  48. package/lib/commands/datasource-load-export-cli.js +6 -1
  49. package/lib/commands/deploy-gate-failure-details.js +23 -0
  50. package/lib/commands/deploy-gate-orchestrator.js +64 -18
  51. package/lib/commands/dev-cli-handlers.js +6 -3
  52. package/lib/commands/dev-down.js +2 -6
  53. package/lib/commands/dev-init.js +7 -109
  54. package/lib/commands/dev-refresh.js +197 -0
  55. package/lib/commands/evidence-kind.js +478 -0
  56. package/lib/commands/governance-command-action.js +4 -2
  57. package/lib/commands/governance-generate-external.js +56 -21
  58. package/lib/commands/lifecycle-external.js +10 -4
  59. package/lib/commands/repair-datasource-apply.js +10 -1
  60. package/lib/commands/repair-datasource-run.js +4 -2
  61. package/lib/commands/repair-datasource-test-payload.js +80 -19
  62. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  63. package/lib/commands/repair-internal.js +9 -1
  64. package/lib/commands/repair-openapi-export.js +1 -1
  65. package/lib/commands/repair-openapi-sync.js +43 -18
  66. package/lib/commands/resource-type.js +101 -18
  67. package/lib/commands/role-assistant.js +313 -0
  68. package/lib/commands/role.js +452 -0
  69. package/lib/commands/setup.js +1 -1
  70. package/lib/commands/upload-data-sync-finalize.js +5 -4
  71. package/lib/commands/upload-sidecar-followups.js +144 -47
  72. package/lib/commands/upload.js +43 -6
  73. package/lib/commands/validate-json.js +230 -0
  74. package/lib/commands/verify-governance-combined-external.js +73 -9
  75. package/lib/commands/verify-governance-db.js +80 -25
  76. package/lib/commands/verify-governance-external.js +29 -5
  77. package/lib/commands/verify-operations-step-plan.js +78 -40
  78. package/lib/commands/wizard-core-helpers.js +7 -3
  79. package/lib/commands/wizard-core.js +78 -9
  80. package/lib/core/audit-logger.js +1 -1
  81. package/lib/core/secrets-load.js +1 -3
  82. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  83. package/lib/datasource/bulk-loader-service.js +5 -1
  84. package/lib/datasource/capability/relate-operations.js +1 -1
  85. package/lib/datasource/datasource-validate-summary.js +2 -2
  86. package/lib/datasource/validate.js +13 -6
  87. package/lib/external-system/deploy-helpers.js +35 -1
  88. package/lib/external-system/deploy.js +23 -10
  89. package/lib/external-system/download-helpers.js +111 -2
  90. package/lib/external-system/download-role-assistant.js +55 -0
  91. package/lib/external-system/download.js +45 -26
  92. package/lib/external-system/test-helpers.js +1 -1
  93. package/lib/external-system/test.js +39 -3
  94. package/lib/generator/builders.js +3 -0
  95. package/lib/generator/helpers.js +13 -5
  96. package/lib/generator/split-variables.js +9 -3
  97. package/lib/generator/wizard.js +51 -4
  98. package/lib/infrastructure/helpers-docker-check.js +6 -0
  99. package/lib/infrastructure/helpers.js +1 -1
  100. package/lib/lifecycle/access-control-abac.js +121 -0
  101. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  102. package/lib/lifecycle/product-model.js +3 -2
  103. package/lib/lifecycle/report-display.js +14 -2
  104. package/lib/programmatic/auth-config.js +3 -2
  105. package/lib/programmatic/bearer-auth.js +1 -1
  106. package/lib/programmatic/capability-registry.js +20 -1
  107. package/lib/programmatic/command-options.js +1 -0
  108. package/lib/programmatic/enterprise-mcp-client.js +18 -2
  109. package/lib/programmatic/index.js +4 -0
  110. package/lib/programmatic/manifest-patch-section.js +48 -9
  111. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  112. package/lib/programmatic/manifest-section-registry.js +18 -1
  113. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  114. package/lib/programmatic/openapi-component-schemas.js +16 -1
  115. package/lib/programmatic/openapi-description-enricher.js +3 -1
  116. package/lib/programmatic/openapi-descriptions.js +55 -5
  117. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  118. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  119. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  120. package/lib/programmatic/openapi-request-schemas.js +36 -1
  121. package/lib/programmatic/openapi-result-schemas.js +29 -12
  122. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  123. package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
  124. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  125. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  126. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  127. package/lib/programmatic/openapi-schema-helpers.js +16 -1
  128. package/lib/programmatic/openapi-spec.js +48 -29
  129. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  130. package/lib/programmatic/request-timeout.js +3 -0
  131. package/lib/programmatic/route-handler-map.js +89 -2
  132. package/lib/programmatic/run-certify.js +15 -2
  133. package/lib/programmatic/run-deploy.js +74 -0
  134. package/lib/programmatic/run-evidence-authoring.js +80 -0
  135. package/lib/programmatic/run-protection.js +124 -0
  136. package/lib/programmatic/run-repository-emcp.js +11 -3
  137. package/lib/programmatic/run-template-workspace.js +42 -2
  138. package/lib/programmatic/workspace-context.js +6 -0
  139. package/lib/protection/auth-context.js +48 -6
  140. package/lib/protection/paths.js +22 -0
  141. package/lib/protection/run-protection-create-helpers.js +22 -1
  142. package/lib/protection/run-protection-create.js +1 -1
  143. package/lib/protection/sync-after-upload.js +1 -1
  144. package/lib/resolvers/evidence-kind-file.js +230 -0
  145. package/lib/resolvers/resource-type-file.js +36 -1
  146. package/lib/resolvers/role-file.js +227 -0
  147. package/lib/role-assistant/evidence-sync.js +450 -0
  148. package/lib/role-assistant/github-workflow.js +96 -0
  149. package/lib/role-assistant/knowledge-sync.js +465 -0
  150. package/lib/role-assistant/settings.js +126 -0
  151. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  152. package/lib/role-assistant/test-cases-honesty.js +184 -0
  153. package/lib/role-assistant/test-cases.js +364 -0
  154. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  155. package/lib/role-assistant/test-runner-workhub.js +385 -0
  156. package/lib/role-assistant/test-runner.js +500 -0
  157. package/lib/schema/application-schema.json +36 -2
  158. package/lib/schema/deployment-rules.yaml +1 -0
  159. package/lib/schema/external-datasource.schema.json +121 -4
  160. package/lib/schema/external-system.schema.json +20 -6
  161. package/lib/schema/infra.parameter.yaml +45 -1
  162. package/lib/schema/role-assistant-settings.schema.json +108 -0
  163. package/lib/schema/type/credential-field-registry.json +19 -3
  164. package/lib/utils/api.js +52 -21
  165. package/lib/utils/app-path-resolve.js +72 -24
  166. package/lib/utils/app-register-config.js +4 -3
  167. package/lib/utils/authentication-shape.js +3 -1
  168. package/lib/utils/cli-sync-options.js +59 -3
  169. package/lib/utils/cli-utils.js +11 -3
  170. package/lib/utils/deployment-validation.js +28 -3
  171. package/lib/utils/dev-ca-install.js +5 -6
  172. package/lib/utils/dev-config.js +2 -2
  173. package/lib/utils/dev-list-sort.js +145 -0
  174. package/lib/utils/dev-platform-tls.js +0 -63
  175. package/lib/utils/docker-build.js +46 -27
  176. package/lib/utils/docker-daemon-tls-ca.js +1 -1
  177. package/lib/utils/docker-exec.js +91 -6
  178. package/lib/utils/docker-run-ephemeral.js +55 -0
  179. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  180. package/lib/utils/env-copy-keycloak.js +196 -0
  181. package/lib/utils/env-copy.js +35 -4
  182. package/lib/utils/error-formatters/error-parser.js +0 -3
  183. package/lib/utils/external-env-template.js +1 -1
  184. package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
  185. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  186. package/lib/utils/health-check-request.js +2 -3
  187. package/lib/utils/help-builder.js +4 -0
  188. package/lib/utils/image-name.js +22 -4
  189. package/lib/utils/logger.js +37 -4
  190. package/lib/utils/manifest-source-emit.js +1 -1
  191. package/lib/utils/paths.js +98 -26
  192. package/lib/utils/pipeline-upload-auth-retry.js +19 -3
  193. package/lib/utils/postgres-platform-bootstrap.js +24 -17
  194. package/lib/utils/postgres-wipe.js +5 -2
  195. package/lib/utils/remote-dev-auth.js +39 -4
  196. package/lib/utils/resolve-docker-image-ref.js +36 -6
  197. package/lib/utils/role-assistant-paths.js +98 -0
  198. package/lib/utils/schema-loader.js +41 -0
  199. package/lib/utils/schema-resolver.js +36 -4
  200. package/lib/utils/token-manager.js +47 -22
  201. package/lib/utils/upload-sync-options.js +17 -3
  202. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  203. package/lib/validation/external-manifest-validator.js +31 -8
  204. package/lib/validation/openapi-contract-surface-validator.js +4 -0
  205. package/lib/validation/validate-external-file.js +8 -1
  206. package/lib/validation/validator.js +6 -5
  207. package/package.json +35 -12
  208. package/scripts/export-builder-api-evidence.js +3 -3
  209. package/scripts/lib/plan-actor.js +2 -2
  210. package/scripts/lib/plan-meta.js +1 -1
  211. package/scripts/lib/plan-parse.js +1 -1
  212. package/scripts/lib/plan-prevalidate-gates.js +12 -1
  213. package/scripts/lib/plan-run-report.js +13 -2
  214. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
  215. package/templates/applications/dataplane/application.yaml +47 -17
  216. package/templates/applications/dataplane/env.template +28 -9
  217. package/templates/applications/dataplane/rbac.yaml +78 -2
  218. package/templates/applications/miso-controller/application.yaml +10 -1
  219. package/templates/applications/miso-controller/env.template +13 -8
  220. package/templates/applications/miso-controller/rbac.yaml +72 -0
  221. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  222. package/templates/channels/chatgpt/integration/README.md +10 -7
  223. package/templates/channels/chatgpt/integration/application.json +2 -2
  224. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  225. package/templates/channels/chatgpt/integration/env.template +1 -1
  226. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  227. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  228. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  229. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  230. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  231. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  232. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  233. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  234. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  235. package/templates/channels/slack/integration/slack-system.json +60 -2
  236. package/templates/python/requirements.txt +1 -1
@@ -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
+ };