@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.
Files changed (162) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-deploy-job.js +108 -0
  14. package/lib/app/run-helpers.js +10 -3
  15. package/lib/app/run.js +8 -0
  16. package/lib/channels/add-channel.js +54 -23
  17. package/lib/channels/approval-guides/chatgpt.js +8 -7
  18. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  19. package/lib/channels/channel-artifact-generator.js +14 -2
  20. package/lib/channels/channel-credential.js +3 -1
  21. package/lib/channels/channel-probe-evidence.js +79 -0
  22. package/lib/channels/channel-probe.js +265 -13
  23. package/lib/channels/channel-runtime-resolver.js +120 -29
  24. package/lib/channels/channel-status-display.js +44 -0
  25. package/lib/channels/channel-targets.js +7 -0
  26. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  27. package/lib/channels/microsoft-copilot-package.js +280 -0
  28. package/lib/channels/runtime-tool-catalog.js +165 -0
  29. package/lib/cli/index.js +2 -0
  30. package/lib/cli/setup-app.js +4 -0
  31. package/lib/cli/setup-external-system-help.js +8 -2
  32. package/lib/cli/setup-external-system.js +12 -0
  33. package/lib/commands/app-test.js +2 -14
  34. package/lib/commands/datasource-load-export-cli.js +6 -1
  35. package/lib/commands/deploy-gate-failure-details.js +19 -17
  36. package/lib/commands/dev-down.js +2 -6
  37. package/lib/commands/evidence-kind.js +65 -15
  38. package/lib/commands/governance-generate-external.js +56 -21
  39. package/lib/commands/lifecycle-external.js +10 -4
  40. package/lib/commands/repair-datasource-apply.js +10 -1
  41. package/lib/commands/repair-datasource-run.js +4 -2
  42. package/lib/commands/repair-datasource-test-payload.js +80 -19
  43. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  44. package/lib/commands/repair-internal.js +9 -1
  45. package/lib/commands/repair-openapi-sync.js +34 -17
  46. package/lib/commands/role-assistant.js +313 -0
  47. package/lib/commands/upload-data-sync-finalize.js +5 -4
  48. package/lib/commands/upload-sidecar-followups.js +144 -47
  49. package/lib/commands/upload.js +43 -6
  50. package/lib/commands/verify-governance-combined-external.js +73 -9
  51. package/lib/commands/verify-governance-db.js +80 -25
  52. package/lib/commands/verify-governance-external.js +29 -5
  53. package/lib/commands/wizard-core-helpers.js +7 -3
  54. package/lib/commands/wizard-core.js +78 -9
  55. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  56. package/lib/datasource/bulk-loader-service.js +5 -1
  57. package/lib/datasource/datasource-validate-summary.js +2 -2
  58. package/lib/datasource/validate.js +13 -6
  59. package/lib/external-system/deploy-helpers.js +35 -1
  60. package/lib/external-system/deploy.js +23 -10
  61. package/lib/external-system/download-helpers.js +111 -2
  62. package/lib/external-system/download-role-assistant.js +55 -0
  63. package/lib/external-system/download.js +45 -26
  64. package/lib/external-system/test-helpers.js +1 -1
  65. package/lib/external-system/test.js +39 -3
  66. package/lib/generator/builders.js +3 -0
  67. package/lib/generator/wizard.js +51 -4
  68. package/lib/lifecycle/access-control-abac.js +121 -0
  69. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  70. package/lib/lifecycle/product-model.js +3 -2
  71. package/lib/lifecycle/report-display.js +14 -2
  72. package/lib/programmatic/capability-registry.js +20 -1
  73. package/lib/programmatic/command-options.js +1 -0
  74. package/lib/programmatic/index.js +4 -0
  75. package/lib/programmatic/manifest-patch-section.js +48 -9
  76. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  77. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  78. package/lib/programmatic/openapi-component-schemas.js +16 -1
  79. package/lib/programmatic/openapi-description-enricher.js +3 -1
  80. package/lib/programmatic/openapi-descriptions.js +55 -5
  81. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  82. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  83. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  84. package/lib/programmatic/openapi-request-schemas.js +36 -1
  85. package/lib/programmatic/openapi-result-schemas.js +1 -1
  86. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  87. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  88. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  89. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  90. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  91. package/lib/programmatic/openapi-spec.js +47 -28
  92. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  93. package/lib/programmatic/request-timeout.js +3 -0
  94. package/lib/programmatic/route-handler-map.js +89 -2
  95. package/lib/programmatic/run-certify.js +15 -2
  96. package/lib/programmatic/run-deploy.js +74 -0
  97. package/lib/programmatic/run-evidence-authoring.js +80 -0
  98. package/lib/programmatic/run-protection.js +124 -0
  99. package/lib/programmatic/run-template-workspace.js +28 -1
  100. package/lib/programmatic/workspace-context.js +6 -0
  101. package/lib/protection/auth-context.js +48 -6
  102. package/lib/protection/paths.js +22 -0
  103. package/lib/protection/run-protection-create-helpers.js +22 -1
  104. package/lib/protection/run-protection-create.js +1 -1
  105. package/lib/protection/sync-after-upload.js +1 -1
  106. package/lib/role-assistant/evidence-sync.js +450 -0
  107. package/lib/role-assistant/github-workflow.js +96 -0
  108. package/lib/role-assistant/knowledge-sync.js +465 -0
  109. package/lib/role-assistant/settings.js +126 -0
  110. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  111. package/lib/role-assistant/test-cases-honesty.js +184 -0
  112. package/lib/role-assistant/test-cases.js +364 -0
  113. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  114. package/lib/role-assistant/test-runner-workhub.js +385 -0
  115. package/lib/role-assistant/test-runner.js +500 -0
  116. package/lib/schema/application-schema.json +16 -2
  117. package/lib/schema/deployment-rules.yaml +1 -0
  118. package/lib/schema/external-datasource.schema.json +38 -4
  119. package/lib/schema/external-system.schema.json +4 -4
  120. package/lib/schema/infra.parameter.yaml +20 -0
  121. package/lib/schema/role-assistant-settings.schema.json +108 -0
  122. package/lib/utils/api.js +52 -27
  123. package/lib/utils/app-path-resolve.js +72 -24
  124. package/lib/utils/authentication-shape.js +3 -1
  125. package/lib/utils/deployment-validation.js +28 -3
  126. package/lib/utils/docker-run-ephemeral.js +55 -0
  127. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  128. package/lib/utils/env-copy-keycloak.js +196 -0
  129. package/lib/utils/env-copy.js +34 -1
  130. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  131. package/lib/utils/help-builder.js +1 -0
  132. package/lib/utils/paths.js +67 -23
  133. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  134. package/lib/utils/role-assistant-paths.js +98 -0
  135. package/lib/utils/schema-loader.js +41 -0
  136. package/lib/utils/schema-resolver.js +36 -4
  137. package/lib/utils/token-manager.js +38 -29
  138. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  139. package/lib/validation/external-manifest-validator.js +31 -8
  140. package/lib/validation/validate-external-file.js +8 -1
  141. package/package.json +2 -1
  142. package/scripts/export-builder-api-evidence.js +1 -1
  143. package/templates/applications/dataplane/application.yaml +23 -15
  144. package/templates/applications/dataplane/env.template +1 -1
  145. package/templates/applications/dataplane/rbac.yaml +14 -0
  146. package/templates/applications/miso-controller/application.yaml +1 -1
  147. package/templates/applications/miso-controller/env.template +3 -1
  148. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  149. package/templates/channels/chatgpt/integration/README.md +10 -7
  150. package/templates/channels/chatgpt/integration/application.json +2 -2
  151. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  152. package/templates/channels/chatgpt/integration/env.template +1 -1
  153. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  154. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  155. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  156. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  157. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  158. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  159. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  160. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  161. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  162. /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,465 @@
1
+ /**
2
+ * Role Assistant knowledge import/export for document storage datasources.
3
+ *
4
+ * @fileoverview Sync local role-assistant knowledge/*.md with dataplane document storage
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const crypto = require('crypto');
13
+ const fs = require('../internal/fs-real-sync');
14
+ const yaml = require('js-yaml');
15
+ const logger = require('../utils/logger');
16
+ const { formatSuccessLine, formatWarningLine, metadata } = require('../utils/cli-test-layout-chalk');
17
+ const { listDocuments, validateDocuments, bulkDocuments } = require('../api/datasources-extended.api');
18
+ const {
19
+ extractDocumentStorageKeyFromAssistantDetails,
20
+ getAssistantDetails,
21
+ updateAssistantSettings
22
+ } = require('../api/assistants.api');
23
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
24
+ const {
25
+ SYSTEM_KIND_DIGITAL_WORKER,
26
+ getRoleAssistantYamlPath,
27
+ readRoleAssistantSettings,
28
+ resolveDocumentStorageKeyFromRoleAssistantSettings,
29
+ resolveRoleAssistantEnvironmentSettings
30
+ } = require('./settings');
31
+
32
+ function resolveSystemNode(input) {
33
+ if (!input || typeof input !== 'object') {
34
+ return null;
35
+ }
36
+ return input.system || input.application || null;
37
+ }
38
+
39
+ function isDigitalWorkerModel(input) {
40
+ const systemNode = resolveSystemNode(input);
41
+ if (!systemNode || typeof systemNode.systemKind !== 'string') {
42
+ return false;
43
+ }
44
+ return systemNode.systemKind.trim() === SYSTEM_KIND_DIGITAL_WORKER;
45
+ }
46
+
47
+ function resolveDatasourceKeyFromCollection(dataSources) {
48
+ if (!Array.isArray(dataSources)) {
49
+ return null;
50
+ }
51
+ for (const row of dataSources) {
52
+ if (!row || typeof row !== 'object') {
53
+ continue;
54
+ }
55
+ if (String(row.entityType || '').trim() !== 'documentStorage') {
56
+ continue;
57
+ }
58
+ const candidate = String(row.key || row.datasourceKey || '').trim();
59
+ if (candidate) {
60
+ return candidate;
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+
66
+ async function resolveDocumentStorageKeyFromDataplane(dataplaneUrl, authConfig, systemKey) {
67
+ if (!dataplaneUrl || !authConfig || !systemKey) {
68
+ return null;
69
+ }
70
+ try {
71
+ const response = await getAssistantDetails(dataplaneUrl, systemKey, authConfig);
72
+ return extractDocumentStorageKeyFromAssistantDetails(response);
73
+ } catch (_error) {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Push role-assistant.yaml knowledge.documentStorageKey to dataplane Assistants settings.
80
+ * Called on upload/deploy so the operator UI and CLI stay aligned.
81
+ */
82
+ async function pushRoleAssistantDocumentStorageSettings(params) {
83
+ const {
84
+ systemKey,
85
+ dataplaneUrl,
86
+ authConfig,
87
+ environment = 'dev',
88
+ pathOptions = {},
89
+ dryRun = false
90
+ } = params;
91
+ const settingsEnv = resolveRoleAssistantSettingsEnvKey(environment);
92
+ const settings = readRoleAssistantSettings(systemKey, { required: false, pathOptions });
93
+ if (!settings) {
94
+ return { skipped: true, reason: 'missing-role-assistant-settings' };
95
+ }
96
+ const section = resolveRoleAssistantEnvironmentSettings(settings, settingsEnv, {
97
+ required: false
98
+ });
99
+ if (!section) {
100
+ return { skipped: true, reason: 'missing-env-settings' };
101
+ }
102
+ const documentStorageKey =
103
+ resolveDocumentStorageKeyFromRoleAssistantSettings(settings, settingsEnv) || '';
104
+ if (dryRun) {
105
+ return {
106
+ skipped: false,
107
+ dryRun: true,
108
+ documentStorageKey: documentStorageKey || null
109
+ };
110
+ }
111
+ if (!dataplaneUrl || !authConfig) {
112
+ return { skipped: true, reason: 'missing-dataplane-auth' };
113
+ }
114
+ const response = await updateAssistantSettings(dataplaneUrl, systemKey, authConfig, {
115
+ documentStorageKey
116
+ });
117
+ if (response && response.success === false) {
118
+ throw new Error(
119
+ response.formattedError ||
120
+ response.error ||
121
+ 'Failed to update Knowledge document storage settings'
122
+ );
123
+ }
124
+ return {
125
+ skipped: false,
126
+ documentStorageKey: documentStorageKey || null
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Resolve Knowledge document storage key.
132
+ * Order: role-assistant.yaml → dataplane Assistants settings → package dataSources.
133
+ */
134
+ async function resolveRoleAssistantDocumentStorageKey({
135
+ systemKey,
136
+ model,
137
+ environment,
138
+ pathOptions,
139
+ dataplaneUrl,
140
+ authConfig
141
+ }) {
142
+ const settingsEnv = resolveRoleAssistantSettingsEnvKey(environment);
143
+ const settings = readRoleAssistantSettings(systemKey, { required: false, pathOptions });
144
+ const settingsKey = resolveDocumentStorageKeyFromRoleAssistantSettings(settings, settingsEnv);
145
+ if (settingsKey) {
146
+ return settingsKey;
147
+ }
148
+ const fromApi = await resolveDocumentStorageKeyFromDataplane(
149
+ dataplaneUrl,
150
+ authConfig,
151
+ systemKey
152
+ );
153
+ if (fromApi) {
154
+ return fromApi;
155
+ }
156
+ return resolveDatasourceKeyFromCollection(model && model.dataSources);
157
+ }
158
+
159
+ function collectKnowledgeMarkdownFiles(dirPath) {
160
+ if (!fs.existsSync(dirPath)) {
161
+ return [];
162
+ }
163
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
164
+ const files = [];
165
+ for (const entry of entries) {
166
+ const fullPath = path.join(dirPath, entry.name);
167
+ if (entry.isDirectory()) {
168
+ files.push(...collectKnowledgeMarkdownFiles(fullPath));
169
+ continue;
170
+ }
171
+ const lower = entry.name.toLowerCase();
172
+ if (lower.endsWith('.md') || lower.endsWith('.markdown')) {
173
+ files.push(fullPath);
174
+ }
175
+ }
176
+ return files;
177
+ }
178
+
179
+ function normalizeDocumentExternalId(knowledgeRoot, filePath) {
180
+ const relative = path.relative(knowledgeRoot, filePath).replace(/\\/g, '/');
181
+ return relative.replace(/\.(md|markdown)$/i, '');
182
+ }
183
+
184
+ function buildDocumentRecord(knowledgeRoot, filePath) {
185
+ const content = fs.readFileSync(filePath, 'utf8');
186
+ const externalId = normalizeDocumentExternalId(knowledgeRoot, filePath);
187
+ const title = path.basename(externalId);
188
+ const contentHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
189
+ const metadata = {
190
+ externalId,
191
+ title,
192
+ mimeType: 'text/markdown',
193
+ sourcePath: path.relative(knowledgeRoot, filePath).replace(/\\/g, '/'),
194
+ sourceType: 'role-assistant-markdown'
195
+ };
196
+ return {
197
+ key: externalId,
198
+ name: title,
199
+ contentHash,
200
+ metadata,
201
+ binaryBase64: Buffer.from(content, 'utf8').toString('base64'),
202
+ // retained for export/debug consumers that still read flat fields
203
+ externalId,
204
+ title,
205
+ content
206
+ };
207
+ }
208
+
209
+ function toDocumentValidatePayload(record) {
210
+ return {
211
+ key: record.key,
212
+ contentHash: record.contentHash,
213
+ metadata: record.metadata || {}
214
+ };
215
+ }
216
+
217
+ function toDocumentBulkPayload(record) {
218
+ return {
219
+ key: record.key,
220
+ name: record.name || record.title || record.key,
221
+ contentHash: record.contentHash,
222
+ metadata: record.metadata || {},
223
+ binaryBase64: record.binaryBase64
224
+ };
225
+ }
226
+
227
+ function extractDocumentRows(payload) {
228
+ const candidates = [
229
+ payload?.data?.items,
230
+ payload?.data?.rows,
231
+ payload?.data,
232
+ payload?.items,
233
+ payload?.rows
234
+ ];
235
+ for (const candidate of candidates) {
236
+ if (Array.isArray(candidate)) {
237
+ return candidate;
238
+ }
239
+ }
240
+ return [];
241
+ }
242
+
243
+ function extractDocumentContent(row) {
244
+ const values = [row?.content, row?.text, row?.body, row?.markdown, row?.document];
245
+ for (const value of values) {
246
+ if (typeof value === 'string') {
247
+ return value;
248
+ }
249
+ }
250
+ return '';
251
+ }
252
+
253
+ function resolveDocumentFileName(row, index) {
254
+ const seed = row?.externalId || row?.documentKey || row?.key || row?.id || row?.title || `doc-${index + 1}`;
255
+ const normalized = String(seed).trim().replace(/[^\w.-/]/g, '-').replace(/\/+/g, '/');
256
+ const filePath = normalized.endsWith('.md') ? normalized : `${normalized}.md`;
257
+ return filePath.startsWith('/') ? filePath.slice(1) : filePath;
258
+ }
259
+
260
+ function resolveRoleAssistantSettingsEnvKey(environment) {
261
+ const token = String(environment || '').trim().toLowerCase();
262
+ return token === 'dev' || token === 'tst' || token === 'pro' ? token : 'dev';
263
+ }
264
+
265
+ function writeRoleAssistantSettingsFile(systemKey, docStorageKey, environment = 'dev', pathOptions = {}) {
266
+ const roleAssistantPath = resolveExternalSystemPath(systemKey, pathOptions);
267
+ const roleAssistantYamlPath = getRoleAssistantYamlPath(systemKey, { pathOptions });
268
+ if (fs.existsSync(roleAssistantYamlPath)) {
269
+ return roleAssistantYamlPath;
270
+ }
271
+ const envKey = resolveRoleAssistantSettingsEnvKey(environment);
272
+ fs.mkdirSync(roleAssistantPath, { recursive: true });
273
+ const payload = {
274
+ apiVersion: 'aifabrix.ai/v1',
275
+ version: '1.0.0',
276
+ kind: 'RoleAssistantSettings',
277
+ roleKey: systemKey,
278
+ [envKey]: {
279
+ knowledge: docStorageKey ? { documentStorageKey: docStorageKey } : {}
280
+ }
281
+ };
282
+ fs.writeFileSync(roleAssistantYamlPath, yaml.dump(payload, { lineWidth: -1 }), 'utf8');
283
+ return roleAssistantYamlPath;
284
+ }
285
+
286
+ async function uploadRoleAssistantKnowledgeRecords(dataplaneUrl, datasourceKey, authConfig, records) {
287
+ const validateBody = records.map(toDocumentValidatePayload);
288
+ const validationResult = await validateDocuments(
289
+ dataplaneUrl,
290
+ datasourceKey,
291
+ authConfig,
292
+ validateBody
293
+ );
294
+ if (!validationResult || validationResult.success === false) {
295
+ throw new Error(validationResult?.formattedError || validationResult?.error || 'Knowledge validation failed');
296
+ }
297
+ const bulkBody = records.map(toDocumentBulkPayload);
298
+ const uploadResult = await bulkDocuments(dataplaneUrl, datasourceKey, authConfig, bulkBody);
299
+ if (!uploadResult || uploadResult.success === false) {
300
+ throw new Error(uploadResult?.formattedError || uploadResult?.error || 'Knowledge upload failed');
301
+ }
302
+ const payload = uploadResult.data && typeof uploadResult.data === 'object' ? uploadResult.data : uploadResult;
303
+ return Number(payload.inserted || payload.upserted || payload.recordsProcessed || bulkBody.length || 0);
304
+ }
305
+
306
+ async function importRoleAssistantKnowledge(params) {
307
+ const {
308
+ systemKey,
309
+ dataplaneUrl,
310
+ authConfig,
311
+ model,
312
+ environment = 'dev',
313
+ pathOptions = {},
314
+ dryRun = false
315
+ } = params;
316
+ if (!isDigitalWorkerModel(model)) {
317
+ return { skipped: true, reason: 'not-role-assistant' };
318
+ }
319
+ const datasourceKey = await resolveRoleAssistantDocumentStorageKey({
320
+ systemKey,
321
+ model,
322
+ environment,
323
+ pathOptions,
324
+ dataplaneUrl,
325
+ authConfig
326
+ });
327
+ if (!datasourceKey) {
328
+ return { skipped: true, reason: 'missing-document-storage-datasource' };
329
+ }
330
+ const appPath = resolveExternalSystemPath(systemKey, pathOptions);
331
+ const knowledgePath = path.join(appPath, 'knowledge');
332
+ const files = collectKnowledgeMarkdownFiles(knowledgePath);
333
+ const records = files.map((filePath) => buildDocumentRecord(knowledgePath, filePath));
334
+ if (records.length === 0) {
335
+ return { skipped: true, reason: 'no-knowledge-files', datasourceKey };
336
+ }
337
+ if (dryRun) {
338
+ return { skipped: false, dryRun: true, datasourceKey, fileCount: files.length, recordCount: records.length };
339
+ }
340
+ const uploaded = await uploadRoleAssistantKnowledgeRecords(
341
+ dataplaneUrl,
342
+ datasourceKey,
343
+ authConfig,
344
+ records
345
+ );
346
+ return {
347
+ skipped: false,
348
+ datasourceKey,
349
+ fileCount: files.length,
350
+ recordCount: records.length,
351
+ uploaded
352
+ };
353
+ }
354
+
355
+ async function exportRoleAssistantKnowledge(params) {
356
+ const { systemKey, dataplaneUrl, authConfig, manifest, environment = 'dev', pathOptions = {} } = params;
357
+ const model = { system: manifest?.application, dataSources: manifest?.dataSources || [] };
358
+ if (!isDigitalWorkerModel(model)) {
359
+ return { skipped: true, reason: 'not-role-assistant' };
360
+ }
361
+ const datasourceKey = await resolveRoleAssistantDocumentStorageKey({
362
+ systemKey,
363
+ model,
364
+ environment,
365
+ pathOptions,
366
+ dataplaneUrl,
367
+ authConfig
368
+ });
369
+ if (!datasourceKey) {
370
+ const settingsPath = writeRoleAssistantSettingsFile(systemKey, null, environment, pathOptions);
371
+ return { skipped: true, reason: 'missing-document-storage-datasource', settingsPath };
372
+ }
373
+ const appPath = resolveExternalSystemPath(systemKey, pathOptions);
374
+ const knowledgePath = path.join(appPath, 'knowledge');
375
+ fs.mkdirSync(knowledgePath, { recursive: true });
376
+ const response = await listDocuments(dataplaneUrl, datasourceKey, authConfig, { limit: 1000 });
377
+ if (!response || response.success === false) {
378
+ throw new Error(response?.formattedError || response?.error || 'Knowledge export failed');
379
+ }
380
+ const rows = extractDocumentRows(response);
381
+ rows.forEach((row, index) => {
382
+ const relativeFile = resolveDocumentFileName(row, index);
383
+ const fullPath = path.join(knowledgePath, relativeFile);
384
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
385
+ fs.writeFileSync(fullPath, `${extractDocumentContent(row)}\n`, 'utf8');
386
+ });
387
+ const settingsPath = writeRoleAssistantSettingsFile(systemKey, datasourceKey, environment, pathOptions);
388
+ return { skipped: false, datasourceKey, count: rows.length, knowledgePath, settingsPath };
389
+ }
390
+
391
+ function logKnowledgeImportSummary(systemKey, result, options = {}) {
392
+ if (result.skipped) {
393
+ if (options.verbose === true) {
394
+ logger.log(metadata(`Knowledge sync skipped (${systemKey}): ${result.reason}`));
395
+ }
396
+ return;
397
+ }
398
+ if (result.dryRun) {
399
+ logger.log(
400
+ formatWarningLine(
401
+ `Role assistant knowledge dry-run: ${result.fileCount} file(s), ${result.recordCount} record(s), datasource ${result.datasourceKey}`
402
+ )
403
+ );
404
+ return;
405
+ }
406
+ logger.log(
407
+ formatSuccessLine(
408
+ `Role assistant knowledge synced (${result.datasourceKey}): ${result.recordCount} record(s) from ${result.fileCount} file(s).`
409
+ )
410
+ );
411
+ }
412
+
413
+ function logDocumentStorageSettingsPushSummary(systemKey, result, options = {}) {
414
+ if (result.skipped) {
415
+ if (options.verbose === true) {
416
+ logger.log(
417
+ metadata(`Knowledge document storage settings skipped (${systemKey}): ${result.reason}`)
418
+ );
419
+ }
420
+ return;
421
+ }
422
+ if (result.dryRun) {
423
+ logger.log(
424
+ formatWarningLine(
425
+ `Knowledge document storage settings dry-run (${systemKey}): ${
426
+ result.documentStorageKey || '(clear)'
427
+ }`
428
+ )
429
+ );
430
+ return;
431
+ }
432
+ logger.log(
433
+ formatSuccessLine(
434
+ `Knowledge document storage settings updated (${systemKey}): ${
435
+ result.documentStorageKey || '(cleared)'
436
+ }`
437
+ )
438
+ );
439
+ }
440
+
441
+ function logKnowledgeExportSummary(systemKey, result, options = {}) {
442
+ if (result.skipped) {
443
+ if (options.verbose === true) {
444
+ logger.log(metadata(`Knowledge export skipped (${systemKey}): ${result.reason}`));
445
+ }
446
+ return;
447
+ }
448
+ logger.log(
449
+ formatSuccessLine(
450
+ `Role assistant knowledge exported (${result.datasourceKey}): ${result.count} document(s) to ${result.knowledgePath}.`
451
+ )
452
+ );
453
+ }
454
+
455
+ module.exports = {
456
+ isDigitalWorkerModel,
457
+ resolveRoleAssistantSettingsEnvKey,
458
+ resolveRoleAssistantDocumentStorageKey,
459
+ pushRoleAssistantDocumentStorageSettings,
460
+ importRoleAssistantKnowledge,
461
+ exportRoleAssistantKnowledge,
462
+ logKnowledgeImportSummary,
463
+ logDocumentStorageSettingsPushSummary,
464
+ logKnowledgeExportSummary
465
+ };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Role Assistant settings reader and validator.
3
+ *
4
+ * @fileoverview role-assistant.yaml parsing for digitalWorker packages
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('../internal/fs-real-sync');
13
+ const yaml = require('js-yaml');
14
+ const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
15
+ const { loadRoleAssistantSettingsSchema } = require('../utils/schema-loader');
16
+ const { SYSTEM_KIND_DIGITAL_WORKER } = require('../utils/role-assistant-paths');
17
+
18
+ function getRoleAssistantYamlPath(systemKey, options = {}) {
19
+ const appPath = resolveExternalSystemPath(systemKey, options.pathOptions || {});
20
+ const yamlPath = path.join(appPath, 'role-assistant.yaml');
21
+ if (fs.existsSync(yamlPath)) {
22
+ return yamlPath;
23
+ }
24
+ const ymlPath = path.join(appPath, 'role-assistant.yml');
25
+ if (fs.existsSync(ymlPath)) {
26
+ return ymlPath;
27
+ }
28
+ return yamlPath;
29
+ }
30
+
31
+ function isDigitalWorkerManifest(manifest) {
32
+ return Boolean(
33
+ manifest &&
34
+ manifest.system &&
35
+ typeof manifest.system.systemKind === 'string' &&
36
+ manifest.system.systemKind.trim() === SYSTEM_KIND_DIGITAL_WORKER
37
+ );
38
+ }
39
+
40
+ function parseRoleAssistantYaml(roleAssistantPath) {
41
+ const content = fs.readFileSync(roleAssistantPath, 'utf8');
42
+ const parsed = yaml.load(content);
43
+ if (!parsed || typeof parsed !== 'object') {
44
+ throw new Error(`Invalid role-assistant.yaml content: ${roleAssistantPath}`);
45
+ }
46
+ return parsed;
47
+ }
48
+
49
+ function formatAjvErrors(errors) {
50
+ if (!Array.isArray(errors) || errors.length === 0) {
51
+ return ['role-assistant.yaml schema validation failed'];
52
+ }
53
+ return errors.map((item) => {
54
+ const location = item.instancePath || '(root)';
55
+ return `${location} ${item.message}`;
56
+ });
57
+ }
58
+
59
+ function validateRoleAssistantSettings(settings, roleAssistantPath) {
60
+ const validate = loadRoleAssistantSettingsSchema();
61
+ const valid = validate(settings);
62
+ if (!valid) {
63
+ const lines = formatAjvErrors(validate.errors).join('; ');
64
+ throw new Error(`Invalid role-assistant.yaml (${roleAssistantPath}): ${lines}`);
65
+ }
66
+ }
67
+
68
+ function readRoleAssistantSettings(systemKey, options = {}) {
69
+ const filePath = getRoleAssistantYamlPath(systemKey, options);
70
+ if (!fs.existsSync(filePath)) {
71
+ if (options.required === true) {
72
+ throw new Error(`Missing required role-assistant.yaml: ${filePath}`);
73
+ }
74
+ return null;
75
+ }
76
+ const settings = parseRoleAssistantYaml(filePath);
77
+ validateRoleAssistantSettings(settings, filePath);
78
+ return settings;
79
+ }
80
+
81
+ function resolveRoleAssistantEnvironmentSettings(settings, envName, options = {}) {
82
+ if (!settings || typeof settings !== 'object') {
83
+ return null;
84
+ }
85
+ const env = String(envName || '').trim() || 'dev';
86
+ const section = settings[env];
87
+ if (!section) {
88
+ if (options.required !== false) {
89
+ throw new Error(
90
+ `role-assistant.yaml is missing "${env}" section. Add "${env}" with runtime/debug/knowledge settings.`
91
+ );
92
+ }
93
+ return null;
94
+ }
95
+ return section;
96
+ }
97
+
98
+ function resolveDocumentStorageKeyFromRoleAssistantSettings(settings, envName) {
99
+ const section = resolveRoleAssistantEnvironmentSettings(settings, envName, { required: false });
100
+ const key = section && section.knowledge && section.knowledge.documentStorageKey;
101
+ if (typeof key !== 'string' || !key.trim()) {
102
+ return null;
103
+ }
104
+ return key.trim();
105
+ }
106
+
107
+ function resolveRoleAssistantSettingsVersion(settings) {
108
+ if (!settings || typeof settings !== 'object') {
109
+ return null;
110
+ }
111
+ const value = settings.version;
112
+ if (typeof value !== 'string' || !value.trim()) {
113
+ return null;
114
+ }
115
+ return value.trim();
116
+ }
117
+
118
+ module.exports = {
119
+ SYSTEM_KIND_DIGITAL_WORKER,
120
+ getRoleAssistantYamlPath,
121
+ isDigitalWorkerManifest,
122
+ readRoleAssistantSettings,
123
+ resolveRoleAssistantEnvironmentSettings,
124
+ resolveDocumentStorageKeyFromRoleAssistantSettings,
125
+ resolveRoleAssistantSettingsVersion
126
+ };