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