@aifabrix/builder 2.50.0 → 2.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
  2. package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
  3. package/integration/hubspot-test/create-hubspot.js +1 -16
  4. package/jest.isolated-projects.js +77 -1
  5. package/jest.projects.js +5 -426
  6. package/lib/api/assistants.api.js +67 -0
  7. package/lib/api/datasources-extended.api.js +6 -6
  8. package/lib/api/evidence-authoring-helpers.js +267 -0
  9. package/lib/api/evidence-authoring.api.js +337 -0
  10. package/lib/api/evidence-kinds.api.js +128 -0
  11. package/lib/api/evidence.api.js +57 -1
  12. package/lib/api/index.js +24 -7
  13. package/lib/api/pipeline.api.js +47 -3
  14. package/lib/api/records-bulk.api.js +28 -3
  15. package/lib/api/resource-types.api.js +4 -4
  16. package/lib/api/roles.api.js +121 -0
  17. package/lib/api/types/evidence-kinds.types.js +42 -0
  18. package/lib/api/types/evidence.types.js +156 -8
  19. package/lib/api/types/roles.types.js +36 -0
  20. package/lib/api/work.api.js +283 -0
  21. package/lib/app/certification-verify-rows.js +1 -1
  22. package/lib/app/run-deploy-job.js +108 -0
  23. package/lib/app/run-helpers.js +10 -3
  24. package/lib/app/run.js +8 -0
  25. package/lib/app/show.js +2 -2
  26. package/lib/certification/merge-certification-from-artifact.js +1 -1
  27. package/lib/channels/add-channel.js +54 -23
  28. package/lib/channels/approval-guides/chatgpt.js +8 -7
  29. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  30. package/lib/channels/channel-artifact-generator.js +16 -3
  31. package/lib/channels/channel-credential.js +3 -1
  32. package/lib/channels/channel-probe-evidence.js +79 -0
  33. package/lib/channels/channel-probe.js +265 -13
  34. package/lib/channels/channel-runtime-resolver.js +120 -29
  35. package/lib/channels/channel-status-display.js +44 -0
  36. package/lib/channels/channel-targets.js +7 -0
  37. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  38. package/lib/channels/microsoft-copilot-package.js +280 -0
  39. package/lib/channels/runtime-tool-catalog.js +165 -0
  40. package/lib/cli/index.js +6 -0
  41. package/lib/cli/setup-app.js +26 -5
  42. package/lib/cli/setup-dev.js +23 -7
  43. package/lib/cli/setup-external-system-help.js +8 -2
  44. package/lib/cli/setup-external-system.js +12 -0
  45. package/lib/cli/setup-utility-help-after.js +12 -0
  46. package/lib/cli/setup-utility-validate.js +41 -2
  47. package/lib/commands/app-test.js +2 -14
  48. package/lib/commands/datasource-load-export-cli.js +6 -1
  49. package/lib/commands/deploy-gate-failure-details.js +23 -0
  50. package/lib/commands/deploy-gate-orchestrator.js +64 -18
  51. package/lib/commands/dev-cli-handlers.js +6 -3
  52. package/lib/commands/dev-down.js +2 -6
  53. package/lib/commands/dev-init.js +7 -109
  54. package/lib/commands/dev-refresh.js +197 -0
  55. package/lib/commands/evidence-kind.js +478 -0
  56. package/lib/commands/governance-command-action.js +4 -2
  57. package/lib/commands/governance-generate-external.js +56 -21
  58. package/lib/commands/lifecycle-external.js +10 -4
  59. package/lib/commands/repair-datasource-apply.js +10 -1
  60. package/lib/commands/repair-datasource-run.js +4 -2
  61. package/lib/commands/repair-datasource-test-payload.js +80 -19
  62. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  63. package/lib/commands/repair-internal.js +9 -1
  64. package/lib/commands/repair-openapi-export.js +1 -1
  65. package/lib/commands/repair-openapi-sync.js +43 -18
  66. package/lib/commands/resource-type.js +101 -18
  67. package/lib/commands/role-assistant.js +313 -0
  68. package/lib/commands/role.js +452 -0
  69. package/lib/commands/setup.js +1 -1
  70. package/lib/commands/upload-data-sync-finalize.js +5 -4
  71. package/lib/commands/upload-sidecar-followups.js +144 -47
  72. package/lib/commands/upload.js +43 -6
  73. package/lib/commands/validate-json.js +230 -0
  74. package/lib/commands/verify-governance-combined-external.js +73 -9
  75. package/lib/commands/verify-governance-db.js +80 -25
  76. package/lib/commands/verify-governance-external.js +29 -5
  77. package/lib/commands/verify-operations-step-plan.js +78 -40
  78. package/lib/commands/wizard-core-helpers.js +7 -3
  79. package/lib/commands/wizard-core.js +78 -9
  80. package/lib/core/audit-logger.js +1 -1
  81. package/lib/core/secrets-load.js +1 -3
  82. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  83. package/lib/datasource/bulk-loader-service.js +5 -1
  84. package/lib/datasource/capability/relate-operations.js +1 -1
  85. package/lib/datasource/datasource-validate-summary.js +2 -2
  86. package/lib/datasource/validate.js +13 -6
  87. package/lib/external-system/deploy-helpers.js +35 -1
  88. package/lib/external-system/deploy.js +23 -10
  89. package/lib/external-system/download-helpers.js +111 -2
  90. package/lib/external-system/download-role-assistant.js +55 -0
  91. package/lib/external-system/download.js +45 -26
  92. package/lib/external-system/test-helpers.js +1 -1
  93. package/lib/external-system/test.js +39 -3
  94. package/lib/generator/builders.js +3 -0
  95. package/lib/generator/helpers.js +13 -5
  96. package/lib/generator/split-variables.js +9 -3
  97. package/lib/generator/wizard.js +51 -4
  98. package/lib/infrastructure/helpers-docker-check.js +6 -0
  99. package/lib/infrastructure/helpers.js +1 -1
  100. package/lib/lifecycle/access-control-abac.js +121 -0
  101. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  102. package/lib/lifecycle/product-model.js +3 -2
  103. package/lib/lifecycle/report-display.js +14 -2
  104. package/lib/programmatic/auth-config.js +3 -2
  105. package/lib/programmatic/bearer-auth.js +1 -1
  106. package/lib/programmatic/capability-registry.js +20 -1
  107. package/lib/programmatic/command-options.js +1 -0
  108. package/lib/programmatic/enterprise-mcp-client.js +18 -2
  109. package/lib/programmatic/index.js +4 -0
  110. package/lib/programmatic/manifest-patch-section.js +48 -9
  111. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  112. package/lib/programmatic/manifest-section-registry.js +18 -1
  113. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  114. package/lib/programmatic/openapi-component-schemas.js +16 -1
  115. package/lib/programmatic/openapi-description-enricher.js +3 -1
  116. package/lib/programmatic/openapi-descriptions.js +55 -5
  117. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  118. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  119. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  120. package/lib/programmatic/openapi-request-schemas.js +36 -1
  121. package/lib/programmatic/openapi-result-schemas.js +29 -12
  122. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  123. package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
  124. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  125. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  126. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  127. package/lib/programmatic/openapi-schema-helpers.js +16 -1
  128. package/lib/programmatic/openapi-spec.js +48 -29
  129. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  130. package/lib/programmatic/request-timeout.js +3 -0
  131. package/lib/programmatic/route-handler-map.js +89 -2
  132. package/lib/programmatic/run-certify.js +15 -2
  133. package/lib/programmatic/run-deploy.js +74 -0
  134. package/lib/programmatic/run-evidence-authoring.js +80 -0
  135. package/lib/programmatic/run-protection.js +124 -0
  136. package/lib/programmatic/run-repository-emcp.js +11 -3
  137. package/lib/programmatic/run-template-workspace.js +42 -2
  138. package/lib/programmatic/workspace-context.js +6 -0
  139. package/lib/protection/auth-context.js +48 -6
  140. package/lib/protection/paths.js +22 -0
  141. package/lib/protection/run-protection-create-helpers.js +22 -1
  142. package/lib/protection/run-protection-create.js +1 -1
  143. package/lib/protection/sync-after-upload.js +1 -1
  144. package/lib/resolvers/evidence-kind-file.js +230 -0
  145. package/lib/resolvers/resource-type-file.js +36 -1
  146. package/lib/resolvers/role-file.js +227 -0
  147. package/lib/role-assistant/evidence-sync.js +450 -0
  148. package/lib/role-assistant/github-workflow.js +96 -0
  149. package/lib/role-assistant/knowledge-sync.js +465 -0
  150. package/lib/role-assistant/settings.js +126 -0
  151. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  152. package/lib/role-assistant/test-cases-honesty.js +184 -0
  153. package/lib/role-assistant/test-cases.js +364 -0
  154. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  155. package/lib/role-assistant/test-runner-workhub.js +385 -0
  156. package/lib/role-assistant/test-runner.js +500 -0
  157. package/lib/schema/application-schema.json +36 -2
  158. package/lib/schema/deployment-rules.yaml +1 -0
  159. package/lib/schema/external-datasource.schema.json +121 -4
  160. package/lib/schema/external-system.schema.json +20 -6
  161. package/lib/schema/infra.parameter.yaml +45 -1
  162. package/lib/schema/role-assistant-settings.schema.json +108 -0
  163. package/lib/schema/type/credential-field-registry.json +19 -3
  164. package/lib/utils/api.js +52 -21
  165. package/lib/utils/app-path-resolve.js +72 -24
  166. package/lib/utils/app-register-config.js +4 -3
  167. package/lib/utils/authentication-shape.js +3 -1
  168. package/lib/utils/cli-sync-options.js +59 -3
  169. package/lib/utils/cli-utils.js +11 -3
  170. package/lib/utils/deployment-validation.js +28 -3
  171. package/lib/utils/dev-ca-install.js +5 -6
  172. package/lib/utils/dev-config.js +2 -2
  173. package/lib/utils/dev-list-sort.js +145 -0
  174. package/lib/utils/dev-platform-tls.js +0 -63
  175. package/lib/utils/docker-build.js +46 -27
  176. package/lib/utils/docker-daemon-tls-ca.js +1 -1
  177. package/lib/utils/docker-exec.js +91 -6
  178. package/lib/utils/docker-run-ephemeral.js +55 -0
  179. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  180. package/lib/utils/env-copy-keycloak.js +196 -0
  181. package/lib/utils/env-copy.js +35 -4
  182. package/lib/utils/error-formatters/error-parser.js +0 -3
  183. package/lib/utils/external-env-template.js +1 -1
  184. package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
  185. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  186. package/lib/utils/health-check-request.js +2 -3
  187. package/lib/utils/help-builder.js +4 -0
  188. package/lib/utils/image-name.js +22 -4
  189. package/lib/utils/logger.js +37 -4
  190. package/lib/utils/manifest-source-emit.js +1 -1
  191. package/lib/utils/paths.js +98 -26
  192. package/lib/utils/pipeline-upload-auth-retry.js +19 -3
  193. package/lib/utils/postgres-platform-bootstrap.js +24 -17
  194. package/lib/utils/postgres-wipe.js +5 -2
  195. package/lib/utils/remote-dev-auth.js +39 -4
  196. package/lib/utils/resolve-docker-image-ref.js +36 -6
  197. package/lib/utils/role-assistant-paths.js +98 -0
  198. package/lib/utils/schema-loader.js +41 -0
  199. package/lib/utils/schema-resolver.js +36 -4
  200. package/lib/utils/token-manager.js +47 -22
  201. package/lib/utils/upload-sync-options.js +17 -3
  202. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  203. package/lib/validation/external-manifest-validator.js +31 -8
  204. package/lib/validation/openapi-contract-surface-validator.js +4 -0
  205. package/lib/validation/validate-external-file.js +8 -1
  206. package/lib/validation/validator.js +6 -5
  207. package/package.json +35 -12
  208. package/scripts/export-builder-api-evidence.js +3 -3
  209. package/scripts/lib/plan-actor.js +2 -2
  210. package/scripts/lib/plan-meta.js +1 -1
  211. package/scripts/lib/plan-parse.js +1 -1
  212. package/scripts/lib/plan-prevalidate-gates.js +12 -1
  213. package/scripts/lib/plan-run-report.js +13 -2
  214. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
  215. package/templates/applications/dataplane/application.yaml +47 -17
  216. package/templates/applications/dataplane/env.template +28 -9
  217. package/templates/applications/dataplane/rbac.yaml +78 -2
  218. package/templates/applications/miso-controller/application.yaml +10 -1
  219. package/templates/applications/miso-controller/env.template +13 -8
  220. package/templates/applications/miso-controller/rbac.yaml +72 -0
  221. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  222. package/templates/channels/chatgpt/integration/README.md +10 -7
  223. package/templates/channels/chatgpt/integration/application.json +2 -2
  224. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  225. package/templates/channels/chatgpt/integration/env.template +1 -1
  226. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  227. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  228. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  229. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  230. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  231. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  232. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  233. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  234. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  235. package/templates/channels/slack/integration/slack-system.json +60 -2
  236. package/templates/python/requirements.txt +1 -1
@@ -0,0 +1,98 @@
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.0.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
+
18
+ function getMaterializationParent() {
19
+ const work = process.env.AIFABRIX_WORK;
20
+ if (work && typeof work === 'string' && work.trim()) {
21
+ return path.resolve(work.trim());
22
+ }
23
+ if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
24
+ return path.resolve(process.env.AIFABRIX_HOME.trim());
25
+ }
26
+ return path.join(os.homedir(), '.aifabrix');
27
+ }
28
+
29
+ function getCwdRoleAssistantRoot() {
30
+ const p = path.join(path.resolve(process.cwd()), WORKSPACE_ROLE_ASSISTANT);
31
+ try {
32
+ const disk = nodeFs();
33
+ if (disk.existsSync(p)) {
34
+ const st = disk.statSync(p);
35
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
36
+ return p;
37
+ }
38
+ }
39
+ } catch {
40
+ // ignore
41
+ }
42
+ return null;
43
+ }
44
+
45
+ function getRoleAssistantRoot() {
46
+ const cwdRoot = getCwdRoleAssistantRoot();
47
+ if (cwdRoot) {
48
+ return cwdRoot;
49
+ }
50
+ return path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT);
51
+ }
52
+
53
+ function isExistingDirectory(disk, dirPath) {
54
+ try {
55
+ if (!dirPath || !disk.existsSync(dirPath)) {
56
+ return false;
57
+ }
58
+ const st = disk.statSync(dirPath);
59
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Resolves role-assistant/<roleKey> with cwd-first behavior.
67
+ * @param {string} roleKey
68
+ * @returns {string}
69
+ */
70
+ function getRoleAssistantPath(roleKey) {
71
+ if (!roleKey || typeof roleKey !== 'string') {
72
+ throw new Error('Role key is required and must be a string');
73
+ }
74
+ const disk = nodeFs();
75
+ const cwdRoot = getCwdRoleAssistantRoot();
76
+ const matPath = path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT, roleKey);
77
+ if (cwdRoot) {
78
+ const cwdPath = path.join(cwdRoot, roleKey);
79
+ if (isExistingDirectory(disk, cwdPath)) {
80
+ return cwdPath;
81
+ }
82
+ }
83
+ if (isExistingDirectory(disk, matPath)) {
84
+ return matPath;
85
+ }
86
+ if (cwdRoot) {
87
+ return path.join(cwdRoot, roleKey);
88
+ }
89
+ return matPath;
90
+ }
91
+
92
+ module.exports = {
93
+ WORKSPACE_ROLE_ASSISTANT,
94
+ SYSTEM_KIND_DIGITAL_WORKER,
95
+ getCwdRoleAssistantRoot,
96
+ getRoleAssistantRoot,
97
+ getRoleAssistantPath
98
+ };
@@ -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,37 +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) {
447
+ if (!authConfig || typeof authConfig !== 'object') {
448
+ return;
449
+ }
412
450
  if (authConfig.type === 'client-token' && authConfig.token) {
413
451
  return;
414
452
  }
415
-
416
- const preferAppClientToken =
417
- String(process.env.AIFABRIX_DEPLOYMENT_AUTH || '').toLowerCase() === 'client-credentials';
418
-
419
- if (!preferAppClientToken && process.env.API_KEY) {
420
- const apiKey = String(process.env.API_KEY);
421
- if (authConfig.type === 'bearer' && authConfig.token !== apiKey) {
422
- authConfig.token = apiKey;
423
- return;
424
- }
425
- if (authConfig.type === 'client-token') {
426
- authConfig.type = 'bearer';
427
- authConfig.token = apiKey;
428
- return;
429
- }
453
+ // Token-forward (builder-api / bearerForward): never substitute lab API_KEY for the caller JWT.
454
+ if (authConfig.preserveBearer && authConfig.token) {
455
+ return;
430
456
  }
431
-
432
- if (authConfig.token) {
457
+ // Enterprise path: keep a real OAuth JWT; do not overwrite with process.env.API_KEY.
458
+ if (looksLikeJwtBearer(authConfig.token)) {
433
459
  return;
434
460
  }
435
-
436
- if (!preferAppClientToken && process.env.API_KEY) {
437
- authConfig.type = 'bearer';
438
- authConfig.token = String(process.env.API_KEY);
461
+ if (!preferClientCredentialsDeploymentAuth() && trySubstituteEnvApiKeyBearer(authConfig)) {
462
+ return;
463
+ }
464
+ if (authConfig.token) {
439
465
  return;
440
466
  }
441
-
442
467
  if (authConfig.clientId || authConfig.clientSecret) {
443
468
  throw new Error(
444
469
  'Dataplane pipeline endpoints require OAuth2 (Bearer token) or x-client-token. ' +
@@ -10,9 +10,10 @@
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 }}
13
+ * @returns {{ minimal: true, verbose: boolean, silentResolve: true, syncMode: true, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
14
14
  */
15
15
  function buildMinimalUploadSyncOptions(options = {}) {
16
+ const deployPreflight = options.uploadContext === 'deploy-preflight';
16
17
  const out = {
17
18
  minimal: true,
18
19
  // CLI -v on verify-* / test-* is for run output, not preflight upload banners.
@@ -20,14 +21,27 @@ function buildMinimalUploadSyncOptions(options = {}) {
20
21
  silentResolve: true,
21
22
  syncMode: true,
22
23
  skipDataSync: true,
23
- suppressPipelineSpinner: options.suppressPipelineSpinner === true
24
+ suppressPipelineSpinner:
25
+ options.suppressPipelineSpinner === true || deployPreflight === true
24
26
  };
25
27
  if (options.force === true) {
26
28
  out.force = true;
27
29
  }
28
- if (options.suppressSummary === true) {
30
+ if (options.suppressSummary === true || deployPreflight === true) {
29
31
  out.suppressSummary = true;
30
32
  }
33
+ if (deployPreflight) {
34
+ out.uploadContext = 'deploy-preflight';
35
+ }
36
+ // Builder-api / programmatic callers: keep request Bearer — do not fall back to
37
+ // getDeploymentAuth against a stale mount AIFABRIX_CONFIG device token.
38
+ if (options.authConfig && options.dataplaneUrl) {
39
+ out.authConfig = options.authConfig;
40
+ out.dataplaneUrl = options.dataplaneUrl;
41
+ if (options.environment) {
42
+ out.environment = options.environment;
43
+ }
44
+ }
31
45
  return out;
32
46
  }
33
47
 
@@ -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
 
@@ -307,6 +307,10 @@ function resolveCachedVendorSpec(documentKey, prefix, appPath, systemKey, vendor
307
307
  function validateServiceContractVendorOpenApi(ds, prefix, appPath, systemKey, vendorCache) {
308
308
  const errors = [];
309
309
  const warnings = [];
310
+ // Python/CIP handlers without a vendor OpenAPI binding are valid serviceContracts.
311
+ if (!hasVendorOpenapiOnManifest(ds)) {
312
+ return { errors, warnings };
313
+ }
310
314
  const documentKey =
311
315
  typeof ds.openapi?.documentKey === 'string' ? ds.openapi.documentKey.trim() : '';
312
316
  if (!documentKey) {
@@ -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
 
@@ -223,10 +223,11 @@ async function validateRbac(appName, options = {}) {
223
223
  throw new Error(`Invalid syntax in ${basename}: ${error.message}`);
224
224
  }
225
225
 
226
- const errors = [
227
- ...validateRoles(rbac.roles),
228
- ...validatePermissions(rbac.permissions)
229
- ];
226
+ const [roleErrors, permissionErrors] = await Promise.all([
227
+ Promise.resolve(validateRoles(rbac.roles)),
228
+ Promise.resolve(validatePermissions(rbac.permissions))
229
+ ]);
230
+ const errors = [...roleErrors, ...permissionErrors];
230
231
 
231
232
  return {
232
233
  valid: errors.length === 0,
@@ -275,7 +276,7 @@ async function validateEnvTemplate(appName, options = {}) {
275
276
  if (!trimmed.includes('=')) {
276
277
  errors.push(`Line ${index + 1}: Invalid environment variable format (missing =)`);
277
278
  } else {
278
- const [key, _value] = trimmed.split('=', 2);
279
+ const [key] = trimmed.split('=', 2);
279
280
  // Trim key to handle whitespace issues
280
281
  // Empty values are allowed (_value can be empty string or undefined)
281
282
  const trimmedKey = key ? key.trim() : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.50.0",
3
+ "version": "2.51.1",
4
4
  "description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -85,36 +85,59 @@
85
85
  "cache": true,
86
86
  "temp-dir": ".nyc_output"
87
87
  },
88
+ "overrides": {
89
+ "@babel/core": "^7.29.7",
90
+ "@babel/plugin-transform-modules-systemjs": "^7.29.7",
91
+ "fast-uri": "^4.1.0",
92
+ "flatted": "^3.4.2",
93
+ "follow-redirects": "^1.16.0",
94
+ "form-data": "^4.0.6",
95
+ "lodash": "^4.18.1",
96
+ "minimatch": "^10.2.5",
97
+ "picomatch": "^4.0.5",
98
+ "qs": "^6.15.3"
99
+ },
88
100
  "pnpm": {
89
101
  "overrides": {
90
- "@bcoe/v8-coverage": "^1.0.2"
102
+ "@bcoe/v8-coverage": "^1.0.2",
103
+ "@babel/core": "^7.29.7",
104
+ "@babel/plugin-transform-modules-systemjs": "^7.29.7",
105
+ "fast-uri": "^4.1.0",
106
+ "flatted": "^3.4.2",
107
+ "follow-redirects": "^1.16.0",
108
+ "form-data": "^4.0.6",
109
+ "lodash": "^4.18.1",
110
+ "minimatch": "^10.2.5",
111
+ "picomatch": "^4.0.5",
112
+ "qs": "^6.15.3"
91
113
  }
92
114
  },
93
115
  "dependencies": {
94
116
  "@aifabrix/miso-client": "^4.19.0",
95
- "ajv": "^8.12.0",
117
+ "adm-zip": "^0.6.0",
118
+ "ajv": "^8.20.0",
96
119
  "ajv-formats": "^3.0.1",
97
- "axios": "^1.6.0",
120
+ "axios": "^1.18.1",
98
121
  "chalk": "^4.1.2",
99
122
  "commander": "^11.1.0",
100
- "handlebars": "^4.7.8",
123
+ "handlebars": "^4.7.9",
101
124
  "inquirer": "^8.2.5",
102
125
  "inquirer-autocomplete-prompt": "^2.0.0",
103
- "js-yaml": "^4.1.0",
126
+ "js-yaml": "^4.3.0",
104
127
  "ora": "^5.4.1",
105
- "yaml": "^2.4.0"
128
+ "yaml": "^2.9.0"
106
129
  },
107
130
  "devDependencies": {
108
131
  "@babel/plugin-syntax-optional-chaining": "^7.8.3",
109
- "@babel/preset-env": "^7.29.0",
132
+ "@babel/preset-env": "^7.29.7",
110
133
  "@bcoe/v8-coverage": "^1.0.2",
111
- "@nestjs/common": "^10.4.15",
112
- "@nestjs/core": "^10.4.15",
113
- "@nestjs/platform-express": "^10.4.15",
134
+ "@nestjs/common": "^11.1.28",
135
+ "@nestjs/core": "^11.1.28",
136
+ "@nestjs/platform-express": "^11.1.28",
114
137
  "@types/node": "^20.10.0",
115
138
  "cross-env": "^10.1.0",
116
139
  "eslint": "^8.55.0",
117
- "jest": "^30.2.0",
140
+ "jest": "^30.4.2",
118
141
  "markdownlint-cli": "^0.49.0",
119
142
  "nyc": "^18.0.0",
120
143
  "reflect-metadata": "^0.2.2",
@@ -18,8 +18,8 @@ const DEFAULT_OUT = path.join(
18
18
  '..',
19
19
  'aifabrix-dataplane',
20
20
  'integration',
21
- 'aifabrix-builder-api',
22
- 'aifabrix-builder-api-evidence.json'
21
+ 'builder-api',
22
+ 'builder-api-evidence.json'
23
23
  );
24
24
 
25
25
  const EVIDENCE_PATTERNS = path.join(REPO_ROOT, 'docs', 'builder-help', 'evidence-patterns.json');
@@ -238,7 +238,7 @@ function rowsFromManifestGuides() {
238
238
  title: 'Section quality and missing inputs',
239
239
  when: 'GET manifest section returns quality.missingInputs',
240
240
  expectedBehavior:
241
- 'Collect required missing inputs from the user before PATCH. Do not guess values. Include sectionReadToken from GET in PATCH body.',
241
+ 'Collect required missing inputs from the user before PATCH. Do not guess values. sectionReadToken from GET is optional on PATCH (server re-reads when omitted; stale token returns 409). Prefer GET when you need quality or missingInputs.',
242
242
  order: 17
243
243
  })
244
244
  ];
@@ -80,9 +80,9 @@ function loadActor() {
80
80
  function saveActor(displayName, email) {
81
81
  const actorPath = resolveActorPath();
82
82
  fs.mkdirSync(path.dirname(actorPath), { recursive: true });
83
- const lines = [`displayName: "${displayName.replace(/"/g, '\\"')}"`];
83
+ const lines = [`displayName: ${JSON.stringify(displayName)}`];
84
84
  if (email) {
85
- lines.push(`email: "${email.replace(/"/g, '\\"')}"`);
85
+ lines.push(`email: ${JSON.stringify(email)}`);
86
86
  }
87
87
  fs.writeFileSync(actorPath, `${lines.join('\n')}\n`, 'utf8');
88
88
  return actorPath;
@@ -87,7 +87,7 @@ function patchPlanMeta(planPath, key, value) {
87
87
  die('Plan has no frontmatter to patch');
88
88
  }
89
89
  let yaml = match[1];
90
- const quoted = `"${String(value).replace(/"/g, '\\"')}"`;
90
+ const quoted = JSON.stringify(String(value));
91
91
  const re = new RegExp(`^${key}:.*$`, 'm');
92
92
  if (re.test(yaml)) {
93
93
  yaml = yaml.replace(re, `${key}: ${quoted}`);
@@ -111,7 +111,7 @@ function loadPlanFile(planPath) {
111
111
  if (!match) {
112
112
  return null;
113
113
  }
114
- const meta = parseSimpleYaml(match[1]) || {};
114
+ const meta = parseSimpleYaml(match[1]);
115
115
  if (Array.isArray(meta.todos)) {
116
116
  meta.todos = normalizeTodos(meta.todos);
117
117
  }