@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,160 @@
1
+ /**
2
+ * Role Assistant workspace path helpers.
3
+ *
4
+ * @fileoverview Paths under role-assistant/ for Role Assistant packages
5
+ * @author AI Fabrix Team
6
+ * @version 1.1.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const { nodeFs } = require('../internal/node-fs');
14
+
15
+ const WORKSPACE_ROLE_ASSISTANT = 'role-assistant';
16
+ const SYSTEM_KIND_DIGITAL_WORKER = 'digitalWorker';
17
+ const ROLE_ASSISTANT_SYSTEM_PREFIX = 'ra-';
18
+
19
+ function getMaterializationParent() {
20
+ const work = process.env.AIFABRIX_WORK;
21
+ if (work && typeof work === 'string' && work.trim()) {
22
+ return path.resolve(work.trim());
23
+ }
24
+ if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
25
+ return path.resolve(process.env.AIFABRIX_HOME.trim());
26
+ }
27
+ return path.join(os.homedir(), '.aifabrix');
28
+ }
29
+
30
+ function getCwdRoleAssistantRoot() {
31
+ const p = path.join(path.resolve(process.cwd()), WORKSPACE_ROLE_ASSISTANT);
32
+ try {
33
+ const disk = nodeFs();
34
+ if (disk.existsSync(p)) {
35
+ const st = disk.statSync(p);
36
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
37
+ return p;
38
+ }
39
+ }
40
+ } catch {
41
+ // ignore
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function getRoleAssistantRoot() {
47
+ const cwdRoot = getCwdRoleAssistantRoot();
48
+ if (cwdRoot) {
49
+ return cwdRoot;
50
+ }
51
+ return path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT);
52
+ }
53
+
54
+ function isExistingDirectory(disk, dirPath) {
55
+ try {
56
+ if (!dirPath || !disk.existsSync(dirPath)) {
57
+ return false;
58
+ }
59
+ const st = disk.statSync(dirPath);
60
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Bare business roleKey (strips leading ra- if present).
68
+ * @param {string} roleKeyOrSystemKey
69
+ * @returns {string}
70
+ */
71
+ function bareRoleKey(roleKeyOrSystemKey) {
72
+ const raw = String(roleKeyOrSystemKey || '').trim();
73
+ if (raw.toLowerCase().startsWith(ROLE_ASSISTANT_SYSTEM_PREFIX)) {
74
+ return raw.slice(ROLE_ASSISTANT_SYSTEM_PREFIX.length);
75
+ }
76
+ return raw;
77
+ }
78
+
79
+ /**
80
+ * Folder / ExternalSystem key for a Role Assistant: ra-{roleKey}.
81
+ * @param {string} roleKeyOrSystemKey
82
+ * @returns {string}
83
+ */
84
+ function roleAssistantSystemKey(roleKeyOrSystemKey) {
85
+ const bare = bareRoleKey(roleKeyOrSystemKey);
86
+ if (!bare) {
87
+ return '';
88
+ }
89
+ return `${ROLE_ASSISTANT_SYSTEM_PREFIX}${bare}`;
90
+ }
91
+
92
+ /**
93
+ * Candidate folder names under role-assistant/ (prefer ra-, then legacy bare).
94
+ * @param {string} roleKeyOrSystemKey
95
+ * @returns {string[]}
96
+ */
97
+ function roleAssistantFolderCandidates(roleKeyOrSystemKey) {
98
+ const withRa = roleAssistantSystemKey(roleKeyOrSystemKey);
99
+ const bare = bareRoleKey(roleKeyOrSystemKey);
100
+ const out = [];
101
+ if (withRa) {
102
+ out.push(withRa);
103
+ }
104
+ if (bare && bare !== withRa) {
105
+ out.push(bare);
106
+ }
107
+ return out;
108
+ }
109
+
110
+ function firstExistingUnderRoot(disk, root, folderNames) {
111
+ if (!root) {
112
+ return null;
113
+ }
114
+ for (const name of folderNames) {
115
+ const candidate = path.join(root, name);
116
+ if (isExistingDirectory(disk, candidate)) {
117
+ return candidate;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Resolves role-assistant/ra-{roleKey} (preferred) or legacy bare folder.
125
+ * @param {string} roleKey
126
+ * @returns {string}
127
+ */
128
+ function getRoleAssistantPath(roleKey) {
129
+ if (!roleKey || typeof roleKey !== 'string') {
130
+ throw new Error('Role key is required and must be a string');
131
+ }
132
+ const disk = nodeFs();
133
+ const folders = roleAssistantFolderCandidates(roleKey);
134
+ const preferred = roleAssistantSystemKey(roleKey);
135
+ const cwdRoot = getCwdRoleAssistantRoot();
136
+ const matRoot = path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT);
137
+
138
+ const existing =
139
+ firstExistingUnderRoot(disk, cwdRoot, folders) ||
140
+ firstExistingUnderRoot(disk, matRoot, folders);
141
+ if (existing) {
142
+ return existing;
143
+ }
144
+ if (cwdRoot) {
145
+ return path.join(cwdRoot, preferred);
146
+ }
147
+ return path.join(matRoot, preferred);
148
+ }
149
+
150
+ module.exports = {
151
+ WORKSPACE_ROLE_ASSISTANT,
152
+ SYSTEM_KIND_DIGITAL_WORKER,
153
+ ROLE_ASSISTANT_SYSTEM_PREFIX,
154
+ getCwdRoleAssistantRoot,
155
+ getRoleAssistantRoot,
156
+ getRoleAssistantPath,
157
+ bareRoleKey,
158
+ roleAssistantSystemKey,
159
+ roleAssistantFolderCandidates
160
+ };
@@ -18,6 +18,7 @@ const addFormats = require('ajv-formats');
18
18
  // These are reset when module is reloaded (for testing)
19
19
  let externalSystemValidator = null;
20
20
  let externalDataSourceValidator = null;
21
+ let roleAssistantSettingsValidator = null;
21
22
 
22
23
  /**
23
24
  * Reset validators cache (for testing)
@@ -26,6 +27,7 @@ let externalDataSourceValidator = null;
26
27
  function resetValidators() {
27
28
  externalSystemValidator = null;
28
29
  externalDataSourceValidator = null;
30
+ roleAssistantSettingsValidator = null;
29
31
  }
30
32
 
31
33
  /**
@@ -138,6 +140,38 @@ function loadExternalDataSourceSchema() {
138
140
  return externalDataSourceValidator;
139
141
  }
140
142
 
143
+ /**
144
+ * Loads and compiles role-assistant settings schema.
145
+ * @function loadRoleAssistantSettingsSchema
146
+ * @returns {Function}
147
+ */
148
+ function loadRoleAssistantSettingsSchema() {
149
+ if (roleAssistantSettingsValidator) {
150
+ return roleAssistantSettingsValidator;
151
+ }
152
+
153
+ const schemaPath = path.join(__dirname, '..', 'schema', 'role-assistant-settings.schema.json');
154
+ let schema;
155
+ try {
156
+ const schemaContent = fsRealSync.readFileSync(schemaPath, 'utf8');
157
+ schema = JSON.parse(schemaContent);
158
+ } catch (error) {
159
+ try {
160
+ schema = require('../schema/role-assistant-settings.schema.json');
161
+ } catch {
162
+ if (error && error.code === 'ENOENT') {
163
+ throw new Error(`Role assistant settings schema not found: ${schemaPath}`);
164
+ }
165
+ throw new Error(`Invalid JSON in role-assistant-settings.schema.json: ${error.message}`);
166
+ }
167
+ }
168
+
169
+ const ajv = new Ajv({ allErrors: true, strict: false, strictSchema: false });
170
+ addFormats(ajv);
171
+ roleAssistantSettingsValidator = ajv.compile(schema);
172
+ return roleAssistantSettingsValidator;
173
+ }
174
+
141
175
  /**
142
176
  * Detect schema type from $id field
143
177
  * @param {Object} parsed - Parsed JSON object
@@ -157,6 +191,9 @@ function detectFromId(parsed) {
157
191
  if (parsed.$id.includes('application-schema')) {
158
192
  return 'application';
159
193
  }
194
+ if (parsed.$id.includes('role-assistant-settings')) {
195
+ return 'role-assistant-settings';
196
+ }
160
197
 
161
198
  return null;
162
199
  }
@@ -239,6 +276,9 @@ function detectFromApplicationFields(parsed) {
239
276
  */
240
277
  function detectFromFilename(filePath) {
241
278
  const fileName = path.basename(filePath).toLowerCase();
279
+ if (fileName === 'role-assistant.yaml' || fileName === 'role-assistant.yml') {
280
+ return 'role-assistant-settings';
281
+ }
242
282
  if (fileName.includes('external-system') || fileName.includes('external_system')) {
243
283
  return 'external-system';
244
284
  }
@@ -347,6 +387,7 @@ function detectSchemaType(filePath, content) {
347
387
  module.exports = {
348
388
  loadExternalSystemSchema,
349
389
  loadExternalDataSourceSchema,
390
+ loadRoleAssistantSettingsSchema,
350
391
  registerExternalDatasourceAjvDependencies,
351
392
  detectSchemaType,
352
393
  detectSchemaTypeFromParsed,
@@ -175,6 +175,18 @@ function resolveDatasourceFiles(schemaBasePath, datasourceFiles) {
175
175
  return datasourceFiles.map(fileName => resolveSingleExternalFile(schemaBasePath, fileName, 'datasource'));
176
176
  }
177
177
 
178
+ function resolveRoleAssistantSettingsFile(appPath) {
179
+ const yamlPath = path.join(appPath, 'role-assistant.yaml');
180
+ if (fs.existsSync(yamlPath)) {
181
+ return resolveSingleExternalFile(appPath, 'role-assistant.yaml', 'role-assistant-settings');
182
+ }
183
+ const ymlPath = path.join(appPath, 'role-assistant.yml');
184
+ if (fs.existsSync(ymlPath)) {
185
+ return resolveSingleExternalFile(appPath, 'role-assistant.yml', 'role-assistant-settings');
186
+ }
187
+ return resolveSingleExternalFile(appPath, 'role-assistant.yaml', 'role-assistant-settings');
188
+ }
189
+
178
190
  /**
179
191
  * @param {string} schemaBasePath
180
192
  * @param {string[]} systemFileNames
@@ -282,6 +294,21 @@ async function loadAndValidateVariables(appPath) {
282
294
  }
283
295
  }
284
296
 
297
+ function readSystemKindFromFirstSystemFile(schemaBasePath, systemFileNames) {
298
+ if (!systemFileNames.length) {
299
+ return '';
300
+ }
301
+ try {
302
+ const firstSystemPath = path.join(schemaBasePath, systemFileNames[0]);
303
+ const parsedSystem = loadConfigFile(firstSystemPath);
304
+ return parsedSystem && typeof parsedSystem.systemKind === 'string'
305
+ ? parsedSystem.systemKind
306
+ : '';
307
+ } catch {
308
+ return '';
309
+ }
310
+ }
311
+
285
312
  async function resolveExternalFiles(appName, options = {}) {
286
313
  if (!appName || typeof appName !== 'string') {
287
314
  throw new Error('App name is required and must be a string');
@@ -295,15 +322,20 @@ async function resolveExternalFiles(appName, options = {}) {
295
322
  }
296
323
 
297
324
  const schemaBasePath = await resolveSchemaBasePath(appName, options);
298
- const systemFiles = resolveSystemFiles(schemaBasePath, variables.externalIntegration.systems);
325
+ const systemFileNames = variables.externalIntegration.systems || [];
326
+ const systemFiles = resolveSystemFiles(schemaBasePath, systemFileNames);
327
+ const systemKind = readSystemKindFromFirstSystemFile(schemaBasePath, systemFileNames);
299
328
  const orderedNames = orderDatasourceFileNamesBySystemKeys(
300
329
  schemaBasePath,
301
- variables.externalIntegration.systems,
330
+ systemFileNames,
302
331
  variables.externalIntegration.dataSources || []
303
332
  );
304
333
  const datasourceFiles = resolveDatasourceFiles(schemaBasePath, orderedNames);
305
-
306
- return [...systemFiles, ...datasourceFiles];
334
+ const extraFiles = [];
335
+ if (String(systemKind).trim() === 'digitalWorker') {
336
+ extraFiles.push(resolveRoleAssistantSettingsFile(appPath));
337
+ }
338
+ return [...systemFiles, ...datasourceFiles, ...extraFiles];
307
339
  }
308
340
 
309
341
  module.exports = {
@@ -408,53 +408,62 @@ function applyLabDataplaneApiKeyBearer(authConfig) {
408
408
  return authConfig;
409
409
  }
410
410
 
411
+ function looksLikeJwtBearer(token) {
412
+ const existing = token ? String(token) : '';
413
+ return existing.split('.').length === 3 && existing.length > 80;
414
+ }
415
+
416
+ function preferClientCredentialsDeploymentAuth() {
417
+ return String(process.env.AIFABRIX_DEPLOYMENT_AUTH || '').toLowerCase() === 'client-credentials';
418
+ }
419
+
420
+ /**
421
+ * @param {Object} authConfig
422
+ * @returns {boolean} true when a substitution was applied
423
+ */
424
+ function trySubstituteEnvApiKeyBearer(authConfig) {
425
+ if (!process.env.API_KEY) {
426
+ return false;
427
+ }
428
+ const apiKey = String(process.env.API_KEY);
429
+ if (authConfig.type === 'bearer' && authConfig.token !== apiKey) {
430
+ authConfig.token = apiKey;
431
+ return true;
432
+ }
433
+ if (authConfig.type === 'client-token') {
434
+ authConfig.type = 'bearer';
435
+ authConfig.token = apiKey;
436
+ return true;
437
+ }
438
+ if (!authConfig.token) {
439
+ authConfig.type = 'bearer';
440
+ authConfig.token = apiKey;
441
+ return true;
442
+ }
443
+ return false;
444
+ }
445
+
411
446
  function requireBearerForDataplanePipeline(authConfig) {
412
447
  if (!authConfig || typeof authConfig !== 'object') {
413
448
  return;
414
449
  }
415
-
416
450
  if (authConfig.type === 'client-token' && authConfig.token) {
417
451
  return;
418
452
  }
419
-
420
453
  // Token-forward (builder-api / bearerForward): never substitute lab API_KEY for the caller JWT.
421
454
  if (authConfig.preserveBearer && authConfig.token) {
422
455
  return;
423
456
  }
424
-
425
- const preferAppClientToken =
426
- String(process.env.AIFABRIX_DEPLOYMENT_AUTH || '').toLowerCase() === 'client-credentials';
427
-
428
- const existing = authConfig.token ? String(authConfig.token) : '';
429
- const looksLikeJwt = existing.split('.').length === 3 && existing.length > 80;
430
457
  // Enterprise path: keep a real OAuth JWT; do not overwrite with process.env.API_KEY.
431
- if (looksLikeJwt) {
458
+ if (looksLikeJwtBearer(authConfig.token)) {
432
459
  return;
433
460
  }
434
-
435
- if (!preferAppClientToken && process.env.API_KEY) {
436
- const apiKey = String(process.env.API_KEY);
437
- if (authConfig.type === 'bearer' && authConfig.token !== apiKey) {
438
- authConfig.token = apiKey;
439
- return;
440
- }
441
- if (authConfig.type === 'client-token') {
442
- authConfig.type = 'bearer';
443
- authConfig.token = apiKey;
444
- return;
445
- }
446
- }
447
-
448
- if (authConfig.token) {
461
+ if (!preferClientCredentialsDeploymentAuth() && trySubstituteEnvApiKeyBearer(authConfig)) {
449
462
  return;
450
463
  }
451
-
452
- if (!preferAppClientToken && process.env.API_KEY) {
453
- authConfig.type = 'bearer';
454
- authConfig.token = String(process.env.API_KEY);
464
+ if (authConfig.token) {
455
465
  return;
456
466
  }
457
-
458
467
  if (authConfig.clientId || authConfig.clientSecret) {
459
468
  throw new Error(
460
469
  'Dataplane pipeline endpoints require OAuth2 (Bearer token) or x-client-token. ' +
@@ -10,7 +10,7 @@
10
10
 
11
11
  /**
12
12
  * @param {Object} [options] - CLI options from test / sync callers
13
- * @returns {{ minimal: true, verbose: boolean, silentResolve: true, syncMode: true, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
13
+ * @returns {{ minimal: true, verbose: boolean, silentResolve: boolean, syncMode: boolean, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
14
14
  */
15
15
  function buildMinimalUploadSyncOptions(options = {}) {
16
16
  const deployPreflight = options.uploadContext === 'deploy-preflight';
@@ -18,11 +18,14 @@ function buildMinimalUploadSyncOptions(options = {}) {
18
18
  minimal: true,
19
19
  // CLI -v on verify-* / test-* is for run output, not preflight upload banners.
20
20
  verbose: false,
21
- silentResolve: true,
22
- syncMode: true,
21
+ // Deploy preflight should match `af upload` progress (URL + Publishing spinner).
22
+ // Verify/test --sync stays silent so parent command owns the progress line.
23
+ silentResolve: !deployPreflight,
24
+ syncMode: !deployPreflight,
23
25
  skipDataSync: true,
26
+ // Explicit suppress wins; deploy-preflight never suppresses the Publishing spinner.
24
27
  suppressPipelineSpinner:
25
- options.suppressPipelineSpinner === true || deployPreflight === true
28
+ options.suppressPipelineSpinner === true && !deployPreflight
26
29
  };
27
30
  if (options.force === true) {
28
31
  out.force = true;
@@ -25,6 +25,42 @@ function tryReadApplicationYamlAt(cfgPath) {
25
25
  }
26
26
  }
27
27
 
28
+ /**
29
+ * @param {unknown} value
30
+ * @returns {boolean}
31
+ */
32
+ function isPositivePortValue(value) {
33
+ if (typeof value === 'number' && value > 0) {
34
+ return true;
35
+ }
36
+ return typeof value === 'string' && /^\d+$/.test(String(value).trim());
37
+ }
38
+
39
+ /**
40
+ * True when a manifest can authoritatively supply listen/port for url:// expansion.
41
+ * External-system `application.json` (upload payload) has no HTTP listen port — using it
42
+ * for `url://builder-api-internal` falls through to {@link DECLARATIVE_URL_INFRA_DEFAULTS}
43
+ * (3000) and breaks Docker CIP against the real builder-api container (3005).
44
+ *
45
+ * @param {object|null|undefined} doc
46
+ * @returns {boolean}
47
+ */
48
+ function isDeclarativeUrlPortAuthorityDoc(doc) {
49
+ if (!doc || typeof doc !== 'object') {
50
+ return false;
51
+ }
52
+ if ((doc.app && doc.app.type) === 'external') {
53
+ return false;
54
+ }
55
+ if (doc.externalIntegration && typeof doc.externalIntegration === 'object') {
56
+ return false;
57
+ }
58
+ return (
59
+ isPositivePortValue(doc.port) ||
60
+ isPositivePortValue(doc.build && doc.build.containerPort)
61
+ );
62
+ }
63
+
28
64
  /**
29
65
  * Candidate paths for cross-app url:// resolution (low → high priority).
30
66
  * Platform system apps prefer materialized `~/.aifabrix-work|home/builder` over CLI package `builder/` stubs.
@@ -83,7 +119,7 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
83
119
  if (appKey === current && ctx.variablesPath) {
84
120
  const vp = path.resolve(String(ctx.variablesPath));
85
121
  const fromVars = tryReadApplicationYamlAt(vp);
86
- if (fromVars) {
122
+ if (isDeclarativeUrlPortAuthorityDoc(fromVars)) {
87
123
  return fromVars;
88
124
  }
89
125
  }
@@ -105,5 +141,6 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
105
141
 
106
142
  module.exports = {
107
143
  collectApplicationYamlPathsForUrlResolve,
144
+ isDeclarativeUrlPortAuthorityDoc,
108
145
  loadApplicationYamlDocForUrlResolve
109
146
  };
@@ -217,6 +217,36 @@ function validateDatasourceSystemKeyAlignment(manifest, errors) {
217
217
  });
218
218
  }
219
219
 
220
+ /**
221
+ * Vendor OpenAPI contract surface is for Connected Systems. Role Assistants
222
+ * (digitalWorker) promote settings + Evidence — skip blocking OpenAPI vendor checks.
223
+ * @param {Object} manifest
224
+ * @param {Object} options
225
+ * @param {string[]} errors
226
+ * @param {string[]} warnings
227
+ */
228
+ function applyExternalContractSurfaceChecks(manifest, options, errors, warnings) {
229
+ if (manifest.type !== 'external' || !Array.isArray(manifest.dataSources) || !manifest.system?.key) {
230
+ return;
231
+ }
232
+ const appPath = options.appPath;
233
+ if (typeof appPath !== 'string' || !appPath) {
234
+ return;
235
+ }
236
+ const systemKind =
237
+ typeof manifest.system.systemKind === 'string' ? manifest.system.systemKind.trim() : '';
238
+ if (systemKind === 'digitalWorker') {
239
+ warnings.push(
240
+ 'Role Assistant (digitalWorker): vendor OpenAPI contract checks skipped ' +
241
+ '(settings + evidence path; run repair --api only when binding vendor OpenAPI).'
242
+ );
243
+ return;
244
+ }
245
+ const contract = validateAppContractSurface(appPath, manifest.system.key, manifest.dataSources);
246
+ errors.push(...contract.errors);
247
+ warnings.push(...contract.warnings);
248
+ }
249
+
220
250
  /**
221
251
  * Validates controller deployment manifest for external systems
222
252
  * Validates manifest structure and inline system/dataSources against their schemas
@@ -251,14 +281,7 @@ async function validateControllerManifest(manifest, options = {}) {
251
281
  validateDatasources(manifest, ajv, externalDatasourceSchema, errors, warnings);
252
282
  validateDatasourceSystemKeyAlignment(manifest, errors);
253
283
  errors.push(...validateManifestRuntimeConfiguration(manifest));
254
- if (manifest.type === 'external' && Array.isArray(manifest.dataSources) && manifest.system?.key) {
255
- const appPath = options.appPath;
256
- if (typeof appPath === 'string' && appPath) {
257
- const contract = validateAppContractSurface(appPath, manifest.system.key, manifest.dataSources);
258
- errors.push(...contract.errors);
259
- warnings.push(...contract.warnings);
260
- }
261
- }
284
+ applyExternalContractSurfaceChecks(manifest, options, errors, warnings);
262
285
  validateConditionalRequirements(manifest, errors, warnings);
263
286
  validateRequiredFields(manifest, errors);
264
287
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Frozen ExternalSystem key prefixes (Role Assistant / Enterprise Knowledge).
3
+ *
4
+ * @fileoverview Reject hand-authored ra-/ek- keys under integration/
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const RESERVED_SYSTEM_KEY_PREFIXES = ['ra-', 'ek-'];
12
+
13
+ /**
14
+ * @param {string|null|undefined} systemKey
15
+ * @returns {boolean}
16
+ */
17
+ function hasReservedPlatformSystemKeyPrefix(systemKey) {
18
+ const normalized = String(systemKey || '')
19
+ .trim()
20
+ .toLowerCase();
21
+ if (!normalized) {
22
+ return false;
23
+ }
24
+ return RESERVED_SYSTEM_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix));
25
+ }
26
+
27
+ /**
28
+ * Connected Systems under integration/ must not use reserved prefixes.
29
+ * Role Assistant packages live under role-assistant/ra-*.
30
+ *
31
+ * @param {string|null|undefined} systemKey
32
+ * @param {string|null|undefined} workspaceBaseDir - integration | role-assistant | …
33
+ * @returns {string|null} Error message or null when allowed
34
+ */
35
+ function reservedSystemKeyError(systemKey, workspaceBaseDir) {
36
+ if (!hasReservedPlatformSystemKeyPrefix(systemKey)) {
37
+ return null;
38
+ }
39
+ const workspace = String(workspaceBaseDir || '')
40
+ .trim()
41
+ .toLowerCase();
42
+ if (workspace === 'role-assistant' || workspace === 'enterprise-knowledge') {
43
+ return null;
44
+ }
45
+ return (
46
+ `System key '${systemKey}' uses a reserved prefix (ra- or ek-). ` +
47
+ 'Role Assistant packages use role-assistant/ra-{roleKey}/; ' +
48
+ 'Enterprise Knowledge uses ek- keys from platform generators only.'
49
+ );
50
+ }
51
+
52
+ module.exports = {
53
+ RESERVED_SYSTEM_KEY_PREFIXES,
54
+ hasReservedPlatformSystemKeyPrefix,
55
+ reservedSystemKeyError
56
+ };
@@ -10,7 +10,11 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const yaml = require('js-yaml');
13
- const { loadExternalSystemSchema, loadExternalDataSourceSchema } = require('../utils/schema-loader');
13
+ const {
14
+ loadExternalSystemSchema,
15
+ loadExternalDataSourceSchema,
16
+ loadRoleAssistantSettingsSchema
17
+ } = require('../utils/schema-loader');
14
18
  const { formatValidationErrors } = require('../utils/error-formatter');
15
19
  const { validateFieldReferences } = require('../datasource/field-reference-validator');
16
20
  const { validateAbac } = require('../datasource/abac-validator');
@@ -53,6 +57,9 @@ function getValidatorForType(normalizedType) {
53
57
  if (normalizedType === 'datasource') {
54
58
  return loadExternalDataSourceSchema();
55
59
  }
60
+ if (normalizedType === 'role-assistant-settings') {
61
+ return loadRoleAssistantSettingsSchema();
62
+ }
56
63
  throw new Error(`Unknown file type: ${normalizedType}`);
57
64
  }
58
65