@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
@@ -72,7 +72,7 @@
72
72
  "date": "2026-06-15T00:00:00Z",
73
73
  "changes": [
74
74
  "Added systemKind digitalWorker to x-systemKindCatalog (enterpriseContext group).",
75
- "Extended subscriptionFilter with roleKeys, includeSystemKinds, and excludeSystemKinds for Digital Worker generation inputs."
75
+ "Extended subscriptionFilter with roleKeys, includeSystemKinds, and excludeSystemKinds for Role Assistant generation inputs."
76
76
  ],
77
77
  "breaking": false
78
78
  },
@@ -184,7 +184,7 @@
184
184
  "$defs": {
185
185
  "subscriptionFilter": {
186
186
  "type": "object",
187
- "description": "Shared filter engine shape for subscription triggers and Digital Worker generation inputs.",
187
+ "description": "Shared filter engine shape for subscription triggers and Role Assistant generation inputs.",
188
188
  "properties": {
189
189
  "resourceTypes": {
190
190
  "type": "array",
@@ -217,7 +217,7 @@
217
217
  "minLength": 1
218
218
  },
219
219
  "minItems": 1,
220
- "description": "Role keys that scope Digital Worker or subscription generation."
220
+ "description": "Role keys that scope Role Assistant or subscription generation."
221
221
  },
222
222
  "includeSystemKinds": {
223
223
  "type": "array",
@@ -978,7 +978,7 @@
978
978
  },
979
979
  {
980
980
  "key": "digitalWorker",
981
- "displayName": "Digital Worker",
981
+ "displayName": "Role Assistant",
982
982
  "group": "enterpriseContext"
983
983
  },
984
984
  {
@@ -0,0 +1,108 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "aifabrix://schema/role-assistant-settings",
4
+ "title": "Role Assistant Settings",
5
+ "description": "Schema for role-assistant.yaml used by digitalWorker packages.",
6
+ "type": "object",
7
+ "required": [
8
+ "apiVersion",
9
+ "version",
10
+ "kind",
11
+ "roleKey"
12
+ ],
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "apiVersion": {
16
+ "type": "string",
17
+ "const": "aifabrix.ai/v1"
18
+ },
19
+ "version": {
20
+ "type": "string",
21
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
22
+ },
23
+ "kind": {
24
+ "type": "string",
25
+ "const": "RoleAssistantSettings"
26
+ },
27
+ "roleKey": {
28
+ "type": "string",
29
+ "pattern": "^[a-z0-9-_]+$"
30
+ },
31
+ "dev": {
32
+ "$ref": "#/$defs/environmentSettings"
33
+ },
34
+ "tst": {
35
+ "$ref": "#/$defs/environmentSettings"
36
+ },
37
+ "pro": {
38
+ "$ref": "#/$defs/environmentSettings"
39
+ }
40
+ },
41
+ "$defs": {
42
+ "environmentSettings": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "properties": {
46
+ "runtime": {
47
+ "$ref": "#/$defs/runtime"
48
+ },
49
+ "debug": {
50
+ "$ref": "#/$defs/debug"
51
+ },
52
+ "knowledge": {
53
+ "$ref": "#/$defs/knowledge"
54
+ }
55
+ }
56
+ },
57
+ "runtime": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "properties": {
61
+ "provider": {
62
+ "type": "string"
63
+ },
64
+ "model": {
65
+ "type": "string"
66
+ },
67
+ "temperature": {
68
+ "type": "number",
69
+ "minimum": 0,
70
+ "maximum": 2
71
+ },
72
+ "maxLoops": {
73
+ "type": "integer",
74
+ "minimum": 1,
75
+ "maximum": 1000
76
+ }
77
+ }
78
+ },
79
+ "debug": {
80
+ "type": "object",
81
+ "additionalProperties": false,
82
+ "properties": {
83
+ "enabled": {
84
+ "type": "boolean"
85
+ },
86
+ "includeRuntimeLog": {
87
+ "type": "boolean"
88
+ },
89
+ "includePromptTrace": {
90
+ "type": "boolean"
91
+ },
92
+ "includeCapabilityTrace": {
93
+ "type": "boolean"
94
+ }
95
+ }
96
+ },
97
+ "knowledge": {
98
+ "type": "object",
99
+ "additionalProperties": false,
100
+ "properties": {
101
+ "documentStorageKey": {
102
+ "type": "string",
103
+ "pattern": "^[a-z0-9-_]+$"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
package/lib/utils/api.js CHANGED
@@ -381,12 +381,11 @@ function extractControllerUrl(url) {
381
381
  return match ? match[1] : url;
382
382
  }
383
383
  }
384
-
385
384
  /**
386
- * Set auth header on headers object: Bearer for user token, x-client-token for application token.
385
+ * Set auth header on headers object: Bearer for user token, x-client-token for app token.
387
386
  * @param {Object} headers - Headers object to mutate
388
387
  * @param {string} token - Token value
389
- * @param {string} authType - 'bearer' or 'client-token'
388
+ * @param {string} authType - 'bearer' | 'client-token'
390
389
  */
391
390
  function setAuthHeader(headers, token, authType) {
392
391
  if (!token) return;
@@ -396,7 +395,6 @@ function setAuthHeader(headers, token, authType) {
396
395
  headers['Authorization'] = `Bearer ${token}`;
397
396
  }
398
397
  }
399
-
400
398
  /**
401
399
  * Make an authenticated API call with user token (Bearer) or application token (x-client-token).
402
400
  * Automatically refreshes device token on 401 when user Bearer was used.
@@ -408,7 +406,48 @@ function setAuthHeader(headers, token, authType) {
408
406
  * @param {string} [tokenOrAuthConfig.controller] - Controller URL for token refresh (if object)
409
407
  * @returns {Promise<Object>} Response object
410
408
  */
411
- // eslint-disable-next-line max-statements
409
+ /**
410
+ * Retry once after device-token refresh on 401 (Bearer only; never for client-token/preserveBearer).
411
+ * @param {Object} response
412
+ * @param {string} url
413
+ * @param {Object} options
414
+ * @param {Object} headers
415
+ * @param {string|null} authControllerUrl
416
+ * @returns {Promise<Object>}
417
+ */
418
+ async function retryApiCallAfterDeviceTokenRefresh(
419
+ response,
420
+ url,
421
+ options,
422
+ headers,
423
+ authControllerUrl
424
+ ) {
425
+ try {
426
+ const { forceRefreshDeviceToken } = require('./token-manager');
427
+ const refreshedToken = await forceRefreshDeviceToken(
428
+ authControllerUrl || extractControllerUrl(url)
429
+ );
430
+ if (refreshedToken?.token) {
431
+ headers.Authorization = `Bearer ${refreshedToken.token}`;
432
+ return await makeApiCall(url, { ...options, headers });
433
+ }
434
+ const authError =
435
+ 'Authentication failed: Token expired and refresh failed. Please login again using: aifabrix login';
436
+ return { ...response, error: authError, formattedError: authError };
437
+ } catch (refreshError) {
438
+ const authError = `Authentication failed: ${refreshError.message || String(refreshError)}. Please login again using: aifabrix login`;
439
+ return { ...response, error: authError, formattedError: authError };
440
+ }
441
+ }
442
+ function shouldRefreshBearerOn401(response, authType, tokenOrAuthConfig) {
443
+ return (
444
+ !response.success &&
445
+ response.status === 401 &&
446
+ authType !== 'client-token' &&
447
+ !(typeof tokenOrAuthConfig === 'object' && tokenOrAuthConfig?.preserveBearer)
448
+ );
449
+ }
450
+
412
451
  async function authenticatedApiCall(url, options = {}, tokenOrAuthConfig) {
413
452
  const isStringToken = typeof tokenOrAuthConfig === 'string';
414
453
  const token = isStringToken ? tokenOrAuthConfig : tokenOrAuthConfig?.token;
@@ -426,32 +465,18 @@ async function authenticatedApiCall(url, options = {}, tokenOrAuthConfig) {
426
465
  headers
427
466
  });
428
467
 
429
- // Only attempt device token refresh on 401 when user Bearer token was used (not for client-token).
430
- // Token-forward (builder-api): never refresh from mount device store — keep the caller JWT.
431
- if (
432
- !response.success &&
433
- response.status === 401 &&
434
- authType !== 'client-token' &&
435
- !(typeof tokenOrAuthConfig === 'object' && tokenOrAuthConfig?.preserveBearer)
436
- ) {
437
- try {
438
- const { forceRefreshDeviceToken } = require('./token-manager');
439
- const refreshedToken = await forceRefreshDeviceToken(authControllerUrl || extractControllerUrl(url));
440
- if (refreshedToken?.token) {
441
- headers['Authorization'] = `Bearer ${refreshedToken.token}`;
442
- return await makeApiCall(url, { ...options, headers });
443
- }
444
- const authError = 'Authentication failed: Token expired and refresh failed. Please login again using: aifabrix login';
445
- return { ...response, error: authError, formattedError: authError };
446
- } catch (refreshError) {
447
- const authError = `Authentication failed: ${refreshError.message || String(refreshError)}. Please login again using: aifabrix login`;
448
- return { ...response, error: authError, formattedError: authError };
449
- }
468
+ if (shouldRefreshBearerOn401(response, authType, tokenOrAuthConfig)) {
469
+ return retryApiCallAfterDeviceTokenRefresh(
470
+ response,
471
+ url,
472
+ options,
473
+ headers,
474
+ authControllerUrl
475
+ );
450
476
  }
451
477
 
452
478
  return response;
453
479
  }
454
-
455
480
  const {
456
481
  initiateDeviceCodeFlow,
457
482
  pollDeviceCodeToken,
@@ -15,6 +15,11 @@ const {
15
15
  SYSTEM_KIND_ENTERPRISE_KNOWLEDGE,
16
16
  getEnterpriseKnowledgePath
17
17
  } = require('./enterprise-knowledge-paths');
18
+ const {
19
+ WORKSPACE_ROLE_ASSISTANT,
20
+ SYSTEM_KIND_DIGITAL_WORKER,
21
+ getRoleAssistantPath
22
+ } = require('./role-assistant-paths');
18
23
 
19
24
  const WORKSPACE_INTEGRATION = 'integration';
20
25
 
@@ -62,10 +67,14 @@ function normalizeWorkspaceOverride(raw) {
62
67
  return null;
63
68
  }
64
69
  const v = String(raw).trim().toLowerCase();
65
- if (v === WORKSPACE_INTEGRATION || v === WORKSPACE_ENTERPRISE_KNOWLEDGE) {
70
+ if (
71
+ v === WORKSPACE_INTEGRATION ||
72
+ v === WORKSPACE_ENTERPRISE_KNOWLEDGE ||
73
+ v === WORKSPACE_ROLE_ASSISTANT
74
+ ) {
66
75
  return v;
67
76
  }
68
- throw new Error(`Unknown workspace "${raw}". Use integration or enterprise-knowledge.`);
77
+ throw new Error(`Unknown workspace "${raw}". Use integration, enterprise-knowledge, or role-assistant.`);
69
78
  }
70
79
 
71
80
  function workspacePair(appPath, baseDir) {
@@ -76,47 +85,84 @@ function resolveFromWorkspaceOverride(workspaceOverride, appName, getIntegration
76
85
  if (workspaceOverride === WORKSPACE_ENTERPRISE_KNOWLEDGE) {
77
86
  return workspacePair(getEnterpriseKnowledgePath(appName), WORKSPACE_ENTERPRISE_KNOWLEDGE);
78
87
  }
88
+ if (workspaceOverride === WORKSPACE_ROLE_ASSISTANT) {
89
+ return workspacePair(getRoleAssistantPath(appName), WORKSPACE_ROLE_ASSISTANT);
90
+ }
79
91
  if (workspaceOverride === WORKSPACE_INTEGRATION) {
80
92
  return workspacePair(getIntegrationPath(appName), WORKSPACE_INTEGRATION);
81
93
  }
82
94
  return null;
83
95
  }
84
96
 
85
- function resolveAmbiguousWorkspace(ekPath, intPath, explicitKind, appName) {
86
- const kind =
87
- explicitKind ||
88
- readSystemKindFromAppDir(ekPath, appName) ||
89
- readSystemKindFromAppDir(intPath, appName);
97
+ function resolveWorkspaceFromSystemKind(kind, ekPath, intPath, roleAssistantPath) {
90
98
  if (kind === SYSTEM_KIND_ENTERPRISE_KNOWLEDGE) {
91
99
  return workspacePair(ekPath, WORKSPACE_ENTERPRISE_KNOWLEDGE);
92
100
  }
101
+ if (kind === SYSTEM_KIND_DIGITAL_WORKER) {
102
+ return workspacePair(roleAssistantPath, WORKSPACE_ROLE_ASSISTANT);
103
+ }
93
104
  return workspacePair(intPath, WORKSPACE_INTEGRATION);
94
105
  }
95
106
 
96
- function resolveFromExistingPaths(ekPath, intPath, ekExists, intExists, explicitKind, appName) {
97
- if (ekExists && !intExists) {
98
- return workspacePair(ekPath, WORKSPACE_ENTERPRISE_KNOWLEDGE);
99
- }
100
- if (intExists && !ekExists) {
107
+ function resolveAmbiguousWorkspace(ekPath, intPath, roleAssistantPath, explicitKind, appName) {
108
+ const kind =
109
+ explicitKind ||
110
+ readSystemKindFromAppDir(roleAssistantPath, appName) ||
111
+ readSystemKindFromAppDir(ekPath, appName) ||
112
+ readSystemKindFromAppDir(intPath, appName);
113
+ return resolveWorkspaceFromSystemKind(kind, ekPath, intPath, roleAssistantPath);
114
+ }
115
+
116
+ function resolveFromExistingPaths(paths, existence, explicitKind, appName) {
117
+ const { ekPath, intPath, roleAssistantPath } = paths;
118
+ const { ekExists, intExists, roleAssistantExists } = existence;
119
+ const existingCount = [ekExists, intExists, roleAssistantExists].filter(Boolean).length;
120
+ if (existingCount === 1) {
121
+ if (ekExists) {
122
+ return workspacePair(ekPath, WORKSPACE_ENTERPRISE_KNOWLEDGE);
123
+ }
124
+ if (roleAssistantExists) {
125
+ return workspacePair(roleAssistantPath, WORKSPACE_ROLE_ASSISTANT);
126
+ }
101
127
  return workspacePair(intPath, WORKSPACE_INTEGRATION);
102
128
  }
103
- if (ekExists && intExists) {
104
- return resolveAmbiguousWorkspace(ekPath, intPath, explicitKind, appName);
129
+ if (existingCount > 1) {
130
+ return resolveAmbiguousWorkspace(ekPath, intPath, roleAssistantPath, explicitKind, appName);
105
131
  }
106
- const kind = explicitKind || readSystemKindFromAppDir(intPath, appName);
107
- if (kind === SYSTEM_KIND_ENTERPRISE_KNOWLEDGE) {
108
- return workspacePair(ekPath, WORKSPACE_ENTERPRISE_KNOWLEDGE);
109
- }
110
- return workspacePair(intPath, WORKSPACE_INTEGRATION);
132
+ const kind =
133
+ explicitKind ||
134
+ readSystemKindFromAppDir(roleAssistantPath, appName) ||
135
+ readSystemKindFromAppDir(ekPath, appName) ||
136
+ readSystemKindFromAppDir(intPath, appName);
137
+ return resolveWorkspaceFromSystemKind(kind, ekPath, intPath, roleAssistantPath);
138
+ }
139
+
140
+ function resolveFromExistingPathsBackwardCompatible(
141
+ ekPath,
142
+ intPath,
143
+ roleAssistantPath,
144
+ explicitKind,
145
+ appName
146
+ ) {
147
+ return resolveFromExistingPaths(
148
+ { ekPath, intPath, roleAssistantPath },
149
+ {
150
+ ekExists: isExistingDirectory(ekPath),
151
+ intExists: isExistingDirectory(intPath),
152
+ roleAssistantExists: isExistingDirectory(roleAssistantPath)
153
+ },
154
+ explicitKind,
155
+ appName
156
+ );
111
157
  }
112
158
 
113
159
  /**
114
160
  * Resolves authoring tree for an external system or Enterprise Knowledge anchor.
115
161
  * @param {string} appName - App or anchor key
116
162
  * @param {Object} [options]
117
- * @param {string} [options.workspace] - integration | enterprise-knowledge
163
+ * @param {string} [options.workspace] - integration | enterprise-knowledge | role-assistant
118
164
  * @param {string} [options.systemKind] - Explicit systemKind override
119
- * @returns {{ appPath: string, baseDir: 'integration' | 'enterprise-knowledge' }}
165
+ * @returns {{ appPath: string, baseDir: 'integration' | 'enterprise-knowledge' | 'role-assistant' }}
120
166
  */
121
167
  function resolveAppWorkspace(appName, options = {}) {
122
168
  if (!appName || typeof appName !== 'string') {
@@ -137,11 +183,11 @@ function resolveAppWorkspace(appName, options = {}) {
137
183
 
138
184
  const ekPath = getEnterpriseKnowledgePath(appName);
139
185
  const intPath = getIntegrationPath(appName);
140
- return resolveFromExistingPaths(
186
+ const roleAssistantPath = getRoleAssistantPath(appName);
187
+ return resolveFromExistingPathsBackwardCompatible(
141
188
  ekPath,
142
189
  intPath,
143
- isExistingDirectory(ekPath),
144
- isExistingDirectory(intPath),
190
+ roleAssistantPath,
145
191
  explicitKind,
146
192
  appName
147
193
  );
@@ -160,7 +206,9 @@ function resolveExternalSystemPath(appName, options = {}) {
160
206
  module.exports = {
161
207
  WORKSPACE_INTEGRATION,
162
208
  WORKSPACE_ENTERPRISE_KNOWLEDGE,
209
+ WORKSPACE_ROLE_ASSISTANT,
163
210
  SYSTEM_KIND_ENTERPRISE_KNOWLEDGE,
211
+ SYSTEM_KIND_DIGITAL_WORKER,
164
212
  readSystemKindFromAppDir,
165
213
  normalizeWorkspaceOverride,
166
214
  resolveAppWorkspace,
@@ -64,7 +64,9 @@ function buildFlatCredentialTemplate(systemKey, authType) {
64
64
  security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
65
65
  },
66
66
  apikey: {
67
- variables: { baseUrl: base, headerName: 'X-API-Key' },
67
+ // Match credential-field-registry placeholders (Authorization + Bearer).
68
+ // Integrations that need a literal custom header set headerName after repair.
69
+ variables: { baseUrl: base, headerName: 'Authorization', prefix: 'Bearer' },
68
70
  security: { apiKey: kvPath('apiKey') }
69
71
  },
70
72
  bearerToken: {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview Workspace roots allowed for external certify / verify CLI commands.
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const CERTIFY_EXTERNAL_BASE_DIRS = new Set([
8
+ 'integration',
9
+ 'role-assistant',
10
+ 'enterprise-knowledge'
11
+ ]);
12
+
13
+ /**
14
+ * @param {string|null|undefined} baseDir
15
+ * @returns {boolean}
16
+ */
17
+ function isCertifyExternalWorkspace(baseDir) {
18
+ return CERTIFY_EXTERNAL_BASE_DIRS.has(baseDir);
19
+ }
20
+
21
+ /**
22
+ * @param {string|null|undefined} baseDir
23
+ * @param {string} commandLabel - e.g. verify-operations
24
+ * @returns {void}
25
+ * @throws {Error}
26
+ */
27
+ function assertCertifyExternalWorkspace(baseDir, commandLabel) {
28
+ if (!isCertifyExternalWorkspace(baseDir)) {
29
+ throw new Error(
30
+ `${commandLabel} applies to external authoring folders only ` +
31
+ '(integration/<systemKey>/, role-assistant/<roleKey>/, or enterprise-knowledge/<key>/).'
32
+ );
33
+ }
34
+ }
35
+
36
+ module.exports = {
37
+ CERTIFY_EXTERNAL_BASE_DIRS,
38
+ isCertifyExternalWorkspace,
39
+ assertCertifyExternalWorkspace
40
+ };
@@ -8,6 +8,27 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ /**
12
+ * True when URL is http to a Docker Compose service DNS name (single hostname label).
13
+ * Example: http://miso-controller:3000/miso — used by builder-api in compose, not public HTTP.
14
+ *
15
+ * @param {string} url
16
+ * @returns {boolean}
17
+ */
18
+ function isDockerComposeServiceHttpUrl(url) {
19
+ try {
20
+ const parsed = new URL(url);
21
+ if (parsed.protocol !== 'http:') {
22
+ return false;
23
+ }
24
+ const host = parsed.hostname || '';
25
+ // Compose DNS: one label, no dots (miso-controller, dataplane, …)
26
+ return Boolean(host) && !host.includes('.') && host !== 'localhost';
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
11
32
  /**
12
33
  * Validates and sanitizes controller URL
13
34
  * Enforces HTTPS-only communication for security
@@ -20,9 +41,13 @@ function validateControllerUrl(url) {
20
41
  throw new Error('Controller URL is required and must be a string');
21
42
  }
22
43
 
23
- // Must use HTTPS for security (allow http://localhost for local development)
24
- if (!url.startsWith('https://') && !url.startsWith('http://localhost')) {
25
- throw new Error('Controller URL must use HTTPS (https://) or http://localhost');
44
+ // HTTPS required in public networks; allow localhost and compose-service HTTP for local Docker
45
+ const allowedHttp =
46
+ url.startsWith('http://localhost') || isDockerComposeServiceHttpUrl(url);
47
+ if (!url.startsWith('https://') && !allowedHttp) {
48
+ throw new Error(
49
+ 'Controller URL must use HTTPS (https://), http://localhost, or a Docker Compose service HTTP URL'
50
+ );
26
51
  }
27
52
 
28
53
  // Basic URL format validation
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Ephemeral docker run (--rm) for one-shot commands (test, install, deployJob).
3
+ *
4
+ * @fileoverview Shared docker run --rm helper
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const { spawn } = require('child_process');
10
+ const { getDockerExecEnv } = require('./remote-docker-env');
11
+
12
+ /**
13
+ * Run a shell command in an ephemeral container; optional --env-file / --network / bind mounts.
14
+ * Attach to the developer infra bridge when the command needs compose DNS
15
+ * (e.g. hostname `postgres` in DATABASE_URL).
16
+ * @param {string} fullImage - Image:tag
17
+ * @param {string} cmd - Shell command (passed to sh -c)
18
+ * @param {string|null} [envFilePath] - Path for docker run --env-file
19
+ * @param {{
20
+ * network?: string|null,
21
+ * volumeMounts?: Array<{ host: string, container: string }>,
22
+ * quiet?: boolean
23
+ * }} [options] - quiet=true pipes stdio (installer-friendly; dump on failure)
24
+ * @returns {Promise<number|{ code: number, output: string }>} Exit code, or code+output when quiet
25
+ */
26
+ async function runDockerRunEphemeral(fullImage, cmd, envFilePath = null, options = {}) {
27
+ const dockerEnv = await getDockerExecEnv();
28
+ const network =
29
+ options && typeof options.network === 'string' && options.network.trim()
30
+ ? options.network.trim()
31
+ : null;
32
+ const volumeMounts = Array.isArray(options?.volumeMounts) ? options.volumeMounts : [];
33
+ const quiet = options?.quiet === true;
34
+ const args = _buildDockerRunArgs(fullImage, cmd, envFilePath, network, volumeMounts);
35
+ if (quiet) {
36
+ return _spawnDockerQuiet(args, dockerEnv);
37
+ }
38
+ return _spawnDockerInherit(args, dockerEnv);
39
+ }
40
+
41
+ /**
42
+ * @param {string} fullImage
43
+ * @param {string} cmd
44
+ * @param {string|null} envFilePath
45
+ * @param {string|null} network
46
+ * @param {Array<{ host: string, container: string }>} volumeMounts
47
+ * @returns {string[]}
48
+ */
49
+ function _buildDockerRunArgs(fullImage, cmd, envFilePath, network, volumeMounts) {
50
+ const args = ['run', '--rm'];
51
+ if (network) {
52
+ args.push('--network', network);
53
+ }
54
+ if (envFilePath) {
55
+ args.push('--env-file', envFilePath);
56
+ }
57
+ for (const mount of volumeMounts) {
58
+ const host = typeof mount?.host === 'string' ? mount.host.trim() : '';
59
+ const container = typeof mount?.container === 'string' ? mount.container.trim() : '';
60
+ if (host && container) {
61
+ args.push('-v', `${host}:${container}`);
62
+ }
63
+ }
64
+ args.push(fullImage, 'sh', '-c', cmd);
65
+ return args;
66
+ }
67
+
68
+ /**
69
+ * @param {string[]} args
70
+ * @param {NodeJS.ProcessEnv} dockerEnv
71
+ * @returns {Promise<number>}
72
+ */
73
+ function _spawnDockerInherit(args, dockerEnv) {
74
+ return new Promise((resolve) => {
75
+ const proc = spawn('docker', args, {
76
+ stdio: 'inherit',
77
+ shell: false,
78
+ env: dockerEnv
79
+ });
80
+ proc.on('close', (code) => resolve(code !== null ? code : 1));
81
+ proc.on('error', () => resolve(1));
82
+ });
83
+ }
84
+
85
+ /**
86
+ * @param {string[]} args
87
+ * @param {NodeJS.ProcessEnv} dockerEnv
88
+ * @returns {Promise<{ code: number, output: string }>}
89
+ */
90
+ function _spawnDockerQuiet(args, dockerEnv) {
91
+ return new Promise((resolve) => {
92
+ const proc = spawn('docker', args, {
93
+ stdio: ['ignore', 'pipe', 'pipe'],
94
+ shell: false,
95
+ env: dockerEnv
96
+ });
97
+ const chunks = [];
98
+ const push = (buf) => {
99
+ if (buf && buf.length) {
100
+ chunks.push(buf);
101
+ }
102
+ };
103
+ proc.stdout.on('data', push);
104
+ proc.stderr.on('data', push);
105
+ proc.on('close', (code) => {
106
+ resolve({
107
+ code: code !== null ? code : 1,
108
+ output: Buffer.concat(chunks).toString('utf8')
109
+ });
110
+ });
111
+ proc.on('error', () => resolve({ code: 1, output: '' }));
112
+ });
113
+ }
114
+
115
+ module.exports = { runDockerRunEphemeral };
@@ -62,8 +62,22 @@ function getEnterpriseKnowledgeRoot() {
62
62
  return path.join(getMaterializationParent(), WORKSPACE_ENTERPRISE_KNOWLEDGE);
63
63
  }
64
64
 
65
+ function isExistingDirectory(disk, dirPath) {
66
+ try {
67
+ if (!dirPath || !disk.existsSync(dirPath)) {
68
+ return false;
69
+ }
70
+ const st = disk.statSync(dirPath);
71
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
65
77
  /**
66
- * Resolves enterprise-knowledge/<anchorKey> (cwd-first, then materialization parent).
78
+ * Resolves enterprise-knowledge/<anchorKey>.
79
+ * Prefer an existing cwd or materialization folder; when `cwd/enterprise-knowledge/` exists and
80
+ * neither copy exists yet, return the cwd path (download/create target).
67
81
  * @param {string} anchorKey - Enterprise Knowledge anchor key
68
82
  * @returns {string}
69
83
  */
@@ -71,19 +85,22 @@ function getEnterpriseKnowledgePath(anchorKey) {
71
85
  if (!anchorKey || typeof anchorKey !== 'string') {
72
86
  throw new Error('Anchor key is required and must be a string');
73
87
  }
74
- const cwdHit = path.join(path.resolve(process.cwd()), WORKSPACE_ENTERPRISE_KNOWLEDGE, anchorKey);
75
- try {
76
- const disk = nodeFs();
77
- if (disk.existsSync(cwdHit)) {
78
- const st = disk.statSync(cwdHit);
79
- if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
80
- return cwdHit;
81
- }
88
+ const disk = nodeFs();
89
+ const cwdRoot = getCwdEnterpriseKnowledgeRoot();
90
+ const matApp = path.join(getMaterializationParent(), WORKSPACE_ENTERPRISE_KNOWLEDGE, anchorKey);
91
+ if (cwdRoot) {
92
+ const cwdApp = path.join(cwdRoot, anchorKey);
93
+ if (isExistingDirectory(disk, cwdApp)) {
94
+ return cwdApp;
82
95
  }
83
- } catch {
84
- // ignore
85
96
  }
86
- return path.join(getMaterializationParent(), WORKSPACE_ENTERPRISE_KNOWLEDGE, anchorKey);
97
+ if (isExistingDirectory(disk, matApp)) {
98
+ return matApp;
99
+ }
100
+ if (cwdRoot) {
101
+ return path.join(cwdRoot, anchorKey);
102
+ }
103
+ return matApp;
87
104
  }
88
105
 
89
106
  function isEnterpriseKnowledgeDirectory(disk, fullPath) {