@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,196 @@
1
+ /**
2
+ * Host .env.local Keycloak loopback URL helpers for env-copy.
3
+ *
4
+ * @fileoverview Resolve developer-scoped Keycloak ports for host-side .env.local
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const yaml = require('js-yaml');
14
+ const config = require('../core/config');
15
+
16
+ /**
17
+ * Read unquoted env value from content.
18
+ * @param {string} content
19
+ * @param {string} key
20
+ * @returns {string}
21
+ */
22
+ function readEnvLineValue(content, key) {
23
+ const re = new RegExp(`^${key}\\s*=\\s*(.*)$`, 'm');
24
+ const m = content.match(re);
25
+ if (!m) return '';
26
+ return m[1].trim().replace(/^["']|["']$/g, '');
27
+ }
28
+
29
+ /**
30
+ * @param {Object} cfgModule - Config module with CONFIG_FILE
31
+ * @returns {number|null}
32
+ */
33
+ function readDeveloperIdFromConfig(cfgModule) {
34
+ const configPath = cfgModule && cfgModule.CONFIG_FILE ? cfgModule.CONFIG_FILE : null;
35
+ if (!configPath || !fs.existsSync(configPath)) {
36
+ return null;
37
+ }
38
+ try {
39
+ const cfgContent = fs.readFileSync(configPath, 'utf8');
40
+ const cfg = yaml.load(cfgContent) || {};
41
+ const raw = cfg['developer-id'];
42
+ if (typeof raw === 'number') {
43
+ return raw;
44
+ }
45
+ if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
46
+ return parseInt(raw, 10);
47
+ }
48
+ } catch {
49
+ // ignore
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Base Keycloak published port from urls.local.yaml (before developer offset).
56
+ * @returns {number|null}
57
+ */
58
+ function readKeycloakBasePortFromUrlsLocal() {
59
+ try {
60
+ const urlsPath = path.join(path.dirname(config.CONFIG_FILE || ''), 'urls.local.yaml');
61
+ if (!fs.existsSync(urlsPath)) return null;
62
+ const doc = yaml.load(fs.readFileSync(urlsPath, 'utf8')) || {};
63
+ const raw = doc['keycloak-port'];
64
+ const n = typeof raw === 'number' ? raw : parseInt(String(raw || ''), 10);
65
+ return Number.isFinite(n) && n > 0 ? n : null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {string} host
73
+ * @returns {string|null} Normalized loopback host or null when not loopback
74
+ */
75
+ function normalizeKeycloakLoopbackHost(host) {
76
+ let next = host;
77
+ if (next === 'keycloak') {
78
+ next = 'localhost';
79
+ }
80
+ const isLoopback =
81
+ next === 'localhost' || next === '127.0.0.1' || next === '::1' || next === '[::1]';
82
+ if (!isLoopback) {
83
+ return null;
84
+ }
85
+ return next === '::1' ? '[::1]' : next;
86
+ }
87
+
88
+ /**
89
+ * @param {string} content
90
+ * @returns {number|null}
91
+ */
92
+ function resolveExplicitKeycloakPort(content) {
93
+ const publicPortRaw = readEnvLineValue(content, 'KEYCLOAK_PUBLIC_PORT');
94
+ if (/^\d+$/.test(publicPortRaw)) {
95
+ return parseInt(publicPortRaw, 10);
96
+ }
97
+ const portRaw = readEnvLineValue(content, 'KEYCLOAK_PORT');
98
+ if (!/^\d+$/.test(portRaw)) {
99
+ return null;
100
+ }
101
+ const portNum = parseInt(portRaw, 10);
102
+ // 8080 is the in-container listen port — not the host publish port
103
+ return portNum !== 8080 ? portNum : null;
104
+ }
105
+
106
+ /**
107
+ * @returns {number|null}
108
+ */
109
+ function resolveDeveloperIdForKeycloakPort() {
110
+ try {
111
+ const raw = process.env.AIFABRIX_DEVELOPERID;
112
+ if (raw && /^[0-9]+$/.test(String(raw).trim())) {
113
+ return parseInt(String(raw).trim(), 10);
114
+ }
115
+ return readDeveloperIdFromConfig(config);
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Published Keycloak host port for this developer (never a fixed 8182).
123
+ * Prefer KEYCLOAK_PUBLIC_PORT, then loopback KEYCLOAK_PORT (if not container 8080),
124
+ * else base from urls.local.yaml / 8082 + developer-id × 100.
125
+ *
126
+ * @param {string} content - Env file content
127
+ * @returns {{ host: string, port: number }|null}
128
+ */
129
+ function resolveHostKeycloakLoopbackEndpoint(content) {
130
+ const { publishedHostPort, parseDeveloperIdNum } = require('./declarative-url-ports');
131
+ const host = normalizeKeycloakLoopbackHost(
132
+ readEnvLineValue(content, 'KEYCLOAK_HOST') || 'localhost'
133
+ );
134
+ if (!host) {
135
+ return null;
136
+ }
137
+ const explicit = resolveExplicitKeycloakPort(content);
138
+ if (explicit !== null) {
139
+ return { host, port: explicit };
140
+ }
141
+ const base = readKeycloakBasePortFromUrlsLocal() || 8082;
142
+ const port = publishedHostPort(base, parseDeveloperIdNum(resolveDeveloperIdForKeycloakPort()));
143
+ return { host, port };
144
+ }
145
+
146
+ /**
147
+ * Upsert KEYCLOAK_PORT and KEYCLOAK_INTERNAL_SERVER_URL for host .env.local.
148
+ * @param {string} content
149
+ * @param {string} host
150
+ * @param {number} port
151
+ * @returns {string}
152
+ */
153
+ function patchKeycloakHostInternalLines(content, host, port) {
154
+ const internalUrl = `http://${host}:${port}/auth`;
155
+ let next = content;
156
+ if (readEnvLineValue(content, 'KEYCLOAK_HOST') === 'keycloak') {
157
+ next = next.replace(/^KEYCLOAK_HOST\s*=.*$/m, 'KEYCLOAK_HOST=localhost');
158
+ }
159
+ if (/^KEYCLOAK_PORT\s*=/m.test(next)) {
160
+ next = next.replace(/^KEYCLOAK_PORT\s*=.*$/m, `KEYCLOAK_PORT=${port}`);
161
+ } else {
162
+ next = `${next.trimEnd()}\nKEYCLOAK_PORT=${port}\n`;
163
+ }
164
+ if (/^KEYCLOAK_INTERNAL_SERVER_URL\s*=/m.test(next)) {
165
+ return next.replace(
166
+ /^KEYCLOAK_INTERNAL_SERVER_URL\s*=.*$/m,
167
+ `KEYCLOAK_INTERNAL_SERVER_URL=${internalUrl}`
168
+ );
169
+ }
170
+ return `${next.trimEnd()}\nKEYCLOAK_INTERNAL_SERVER_URL=${internalUrl}\n`;
171
+ }
172
+
173
+ /**
174
+ * For host `.env.local`, force Keycloak admin/internal base to this developer's
175
+ * published loopback port (`base + developerId × 100`). Never hardcodes a single
176
+ * developer’s port (e.g. 8182).
177
+ *
178
+ * @param {string} content - Env file content
179
+ * @returns {string}
180
+ */
181
+ function applyHostLocalKeycloakInternalUrl(content) {
182
+ if (typeof content !== 'string' || content.length === 0) {
183
+ return content;
184
+ }
185
+ const endpoint = resolveHostKeycloakLoopbackEndpoint(content);
186
+ if (!endpoint) {
187
+ return content;
188
+ }
189
+ return patchKeycloakHostInternalLines(content, endpoint.host, endpoint.port);
190
+ }
191
+
192
+ module.exports = {
193
+ applyHostLocalKeycloakInternalUrl,
194
+ readEnvLineValue,
195
+ resolveHostKeycloakLoopbackEndpoint
196
+ };
@@ -21,6 +21,7 @@ const { rewriteInfraEndpoints } = require('./env-endpoints');
21
21
  const { buildEnvVarMap } = require('./env-map');
22
22
  const { interpolateEnvVars } = require('./secrets-helpers');
23
23
  const { getLocalPort } = require('./port-resolver');
24
+ const { applyHostLocalKeycloakInternalUrl } = require('./env-copy-keycloak');
24
25
 
25
26
  /**
26
27
  * Read developer ID from config file synchronously
@@ -98,6 +99,26 @@ function resolveEnvOutputPath(rawOutputPath, variablesPath) {
98
99
  return outputPath;
99
100
  }
100
101
 
102
+ /**
103
+ * Sibling host-only env path next to `build.envOutputPath` (`.env` → `.env.local`).
104
+ * Used for host CLI / manual tests; must not be mounted into the container.
105
+ *
106
+ * @param {string} envOutputPath - Resolved path ending in `.env`
107
+ * @returns {string} Path ending in `.env.local`
108
+ */
109
+ function resolveEnvLocalOutputPath(envOutputPath) {
110
+ if (typeof envOutputPath !== 'string' || envOutputPath.trim() === '') {
111
+ throw new Error('envOutputPath is required to resolve .env.local');
112
+ }
113
+ if (envOutputPath.endsWith('.env.local')) {
114
+ return envOutputPath;
115
+ }
116
+ if (envOutputPath.endsWith('.env')) {
117
+ return `${envOutputPath}.local`;
118
+ }
119
+ return path.join(path.dirname(envOutputPath), '.env.local');
120
+ }
121
+
101
122
  /**
102
123
  * Writes .env to envOutputPath for `--reload`: merge container `.env.run` into the host file.
103
124
  * Preserves comments when a file already exists (e.g. after `aifabrix resolve`). Keys present only
@@ -149,8 +170,18 @@ async function writeEnvOutputForLocal(appName, outputPath) {
149
170
  const localMap = parseEnvContentToMap(localContent);
150
171
  toWrite = mergeEnvMapIntoContent(existingContent, localMap);
151
172
  }
173
+ const isLocalSibling = path.basename(outputPath) === '.env.local';
174
+ if (isLocalSibling) {
175
+ toWrite = applyHostLocalKeycloakInternalUrl(toWrite);
176
+ }
152
177
  await fsp.writeFile(outputPath, toWrite, { mode: 0o600 });
153
- logger.log(formatSuccessLine(`Wrote .env to envOutputPath (localPort): ${outputPath}`));
178
+ logger.log(
179
+ formatSuccessLine(
180
+ isLocalSibling
181
+ ? `Wrote host .env.local (localhost ports for CLI/tests): ${outputPath}`
182
+ : `Wrote .env to envOutputPath (localPort): ${outputPath}`
183
+ )
184
+ );
154
185
  }
155
186
 
156
187
  /**
@@ -408,6 +439,8 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath,
408
439
  module.exports = {
409
440
  processEnvVariables,
410
441
  resolveEnvOutputPath,
442
+ resolveEnvLocalOutputPath,
443
+ applyHostLocalKeycloakInternalUrl,
411
444
  substituteMntDataForLocal,
412
445
  syncWrittenBuilderEnvToOutputPath,
413
446
  writeEnvOutputForReload,
@@ -248,6 +248,9 @@ function formatGatePillarLine(opts = {}) {
248
248
  if (verdict === VERDICT.FAILED) {
249
249
  return formatBlockingError(`${label} · FAILED${suffix}`);
250
250
  }
251
+ if (verdict === VERDICT.NOT_APPLICABLE) {
252
+ return formatSuccessLine(`${label} · NOT APPLICABLE${suffix}`);
253
+ }
251
254
  return formatWarningLine(`${label} · NOT VERIFIED${suffix}`);
252
255
  }
253
256
 
@@ -38,32 +38,50 @@ async function filterTraefikUrlByDns(traefikUrl, debug, isHostnameResolvableFn)
38
38
  * @param {string} [p.skippedPublicHealthUrl]
39
39
  * @param {string[]} [p.urlsToTry]
40
40
  * @param {number} p.resolvedIndex
41
+ * @returns {string|null} Warning message without leading glyph, or null
41
42
  */
42
- function logPublicHealthUrlWarningIfNeeded(p) {
43
+ function buildPublicHealthUrlWarningMessage(p) {
43
44
  const { skippedPublicHealthUrl, urlsToTry, resolvedIndex } = p;
44
- if (typeof resolvedIndex !== 'number' || resolvedIndex < 0) return;
45
+ if (typeof resolvedIndex !== 'number' || resolvedIndex < 0) return null;
45
46
 
46
47
  if (skippedPublicHealthUrl) {
47
- logger.log(
48
- chalk.yellow(
49
- `⚠ Public URL was not verified (DNS): ${skippedPublicHealthUrl}. ` +
50
- 'The application reported healthy via localhost only. Validate DNS names and Traefik routing for this host.'
51
- )
48
+ return (
49
+ `Public URL was not verified (DNS): ${skippedPublicHealthUrl}. ` +
50
+ 'The application reported healthy via localhost only. Validate DNS names and Traefik routing for this host.'
52
51
  );
53
- return;
54
52
  }
55
53
  if (Array.isArray(urlsToTry) && urlsToTry.length > 1 && resolvedIndex > 0) {
56
54
  const pub = urlsToTry[0];
57
- logger.log(
58
- chalk.yellow(
59
- `⚠ Public URL was not verified: ${pub}. ` +
60
- 'Health checks succeeded via localhost only. Validate Traefik routing, TLS, and DNS.'
61
- )
55
+ return (
56
+ `Public URL was not verified: ${pub}. ` +
57
+ 'Health checks succeeded via localhost only. Validate Traefik routing, TLS, and DNS.'
62
58
  );
63
59
  }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * @param {Object} p
65
+ * @param {string} [p.skippedPublicHealthUrl]
66
+ * @param {string[]} [p.urlsToTry]
67
+ * @param {number} p.resolvedIndex
68
+ * @param {boolean} [p.defer]
69
+ * @param {(message: string) => void} [p.onDeferred]
70
+ */
71
+ function logPublicHealthUrlWarningIfNeeded(p) {
72
+ const message = buildPublicHealthUrlWarningMessage(p);
73
+ if (!message) return;
74
+ if (p && p.defer === true) {
75
+ if (typeof p.onDeferred === 'function') {
76
+ p.onDeferred(message);
77
+ }
78
+ return;
79
+ }
80
+ logger.log(chalk.yellow(`⚠ ${message}`));
64
81
  }
65
82
 
66
83
  module.exports = {
67
84
  filterTraefikUrlByDns,
85
+ buildPublicHealthUrlWarningMessage,
68
86
  logPublicHealthUrlWarningIfNeeded
69
87
  };
@@ -339,6 +339,14 @@ function resolveHealthCheckPollOptions(runOptions = {}) {
339
339
  if (typeof runOptions.onPollText === 'function') {
340
340
  pollOptions.onPollText = runOptions.onPollText;
341
341
  }
342
+ const defer =
343
+ runOptions.deferPublicUrlWarning === true || typeof runOptions.onPollText === 'function';
344
+ if (defer) {
345
+ pollOptions.deferPublicUrlWarning = true;
346
+ if (typeof runOptions.onDeferredPublicUrlWarning === 'function') {
347
+ pollOptions.onDeferredPublicUrlWarning = runOptions.onDeferredPublicUrlWarning;
348
+ }
349
+ }
342
350
  return pollOptions;
343
351
  }
344
352
 
@@ -367,7 +375,9 @@ async function pollUntilHealthCheckPasses({
367
375
  logPublicHealthUrlWarningIfNeeded({
368
376
  skippedPublicHealthUrl,
369
377
  urlsToTry,
370
- resolvedIndex: attemptResult.resolvedIndex
378
+ resolvedIndex: attemptResult.resolvedIndex,
379
+ defer: pollOptions.deferPublicUrlWarning === true,
380
+ onDeferred: pollOptions.onDeferredPublicUrlWarning
371
381
  });
372
382
  return;
373
383
  }
@@ -79,6 +79,7 @@ const CATEGORIES = [
79
79
  { name: 'dimension value', term: 'dimension value <command>' },
80
80
  { name: 'resource-type' },
81
81
  { name: 'role' },
82
+ { name: 'role-assistant' },
82
83
  { name: 'evidence-kind' },
83
84
  { name: 'enterprise-knowledge' },
84
85
  { name: 'identity' },
@@ -382,15 +382,41 @@ function getIntegrationBuilderBaseDir() {
382
382
  }
383
383
 
384
384
  /**
385
- * Returns the default integration root under {@link getAppsMaterializationParent} (listing may also
386
- * scan {@link getCwdIntegrationRoot}; see {@link listIntegrationAppNames}).
385
+ * Returns the default integration root: {@link getCwdIntegrationRoot} when present, else
386
+ * under {@link getAppsMaterializationParent}. Listing may still scan both trees
387
+ * (see {@link listIntegrationAppNames}).
387
388
  *
388
389
  * @returns {string} Absolute path to integration/ directory
389
390
  */
390
391
  function getIntegrationRoot() {
392
+ const cwdRoot = getCwdIntegrationRoot();
393
+ if (cwdRoot) {
394
+ return cwdRoot;
395
+ }
391
396
  return path.join(getAppsMaterializationParent(), 'integration');
392
397
  }
393
398
 
399
+ /**
400
+ * True when path exists and is a directory (best-effort; false on error).
401
+ * @param {string} dirPath
402
+ * @returns {boolean}
403
+ */
404
+ function isExistingDirectoryPath(dirPath) {
405
+ if (!dirPath || typeof dirPath !== 'string') {
406
+ return false;
407
+ }
408
+ try {
409
+ const disk = nodeFs();
410
+ if (!disk.existsSync(dirPath)) {
411
+ return false;
412
+ }
413
+ const st = disk.statSync(dirPath);
414
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
415
+ } catch {
416
+ return false;
417
+ }
418
+ }
419
+
394
420
  /**
395
421
  * Absolute `integration/` next to {@link process.cwd} when that directory exists.
396
422
  *
@@ -574,8 +600,10 @@ function listIntegrationAppNames() {
574
600
  if (cwdRoot) {
575
601
  addBuilderSubdirNamesToSet(disk, cwdRoot, names, withAppConfig(cwdRoot));
576
602
  }
577
- const matInt = getIntegrationRoot();
578
- addBuilderSubdirNamesToSet(disk, matInt, names, withAppConfig(matInt));
603
+ const matInt = path.join(getAppsMaterializationParent(), 'integration');
604
+ if (!cwdRoot || path.resolve(matInt) !== path.resolve(cwdRoot)) {
605
+ addBuilderSubdirNamesToSet(disk, matInt, names, withAppConfig(matInt));
606
+ }
579
607
  return [...names].sort();
580
608
  }
581
609
 
@@ -629,8 +657,11 @@ function listBuilderAppNames() {
629
657
  }
630
658
 
631
659
  /**
632
- * Gets the integration folder path: **`cwd/integration/<appName>`** when that directory exists, else
633
- * **`aifabrix-work` or `aifabrix-home` + `/integration/<appName>`**.
660
+ * Gets the integration folder path.
661
+ * Resolution: existing `cwd/integration/<app>` → existing materialization copy →
662
+ * `cwd/integration/<app>` when `cwd/integration/` exists (new download/create) →
663
+ * materialization parent. Download should use {@link getAppCreatePath} when it must
664
+ * always land under cwd even if a materialization orphan exists.
634
665
  *
635
666
  * @param {string} appName - Application name
636
667
  * @returns {string} Absolute path to integration directory
@@ -648,18 +679,21 @@ function getIntegrationPath(appName) {
648
679
  } catch {
649
680
  // programmatic module optional for CLI-only usage
650
681
  }
651
- const cwdInt = path.join(path.resolve(process.cwd()), 'integration', appName);
652
- try {
653
- if (nodeFs().existsSync(cwdInt)) {
654
- const st = nodeFs().statSync(cwdInt);
655
- if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
656
- return cwdInt;
657
- }
682
+ const cwdRoot = getCwdIntegrationRoot();
683
+ const matApp = path.join(getAppsMaterializationParent(), 'integration', appName);
684
+ if (cwdRoot) {
685
+ const cwdApp = path.join(cwdRoot, appName);
686
+ if (isExistingDirectoryPath(cwdApp)) {
687
+ return cwdApp;
658
688
  }
659
- } catch {
660
- // ignore
661
689
  }
662
- return path.join(getAppsMaterializationParent(), 'integration', appName);
690
+ if (isExistingDirectoryPath(matApp)) {
691
+ return matApp;
692
+ }
693
+ if (cwdRoot) {
694
+ return path.join(cwdRoot, appName);
695
+ }
696
+ return matApp;
663
697
  }
664
698
 
665
699
  /**
@@ -797,13 +831,15 @@ function resolveExistingWorkspacePath(appName, options) {
797
831
  if (isExistingAppDirectory(appPath)) {
798
832
  return { appPath, baseDir };
799
833
  }
800
- const altWorkspace =
801
- baseDir === 'enterprise-knowledge' ? 'integration' : 'enterprise-knowledge';
802
- const alt = resolveAppWorkspace(appName, { ...options, workspace: altWorkspace });
803
- if (!isExistingAppDirectory(alt.appPath)) {
804
- return null;
834
+ const alternates = ['integration', 'enterprise-knowledge', 'role-assistant']
835
+ .filter((workspaceName) => workspaceName !== baseDir);
836
+ for (const workspaceName of alternates) {
837
+ const alt = resolveAppWorkspace(appName, { ...options, workspace: workspaceName });
838
+ if (isExistingAppDirectory(alt.appPath)) {
839
+ return { appPath: alt.appPath, baseDir: alt.baseDir };
840
+ }
805
841
  }
806
- return { appPath: alt.appPath, baseDir: alt.baseDir };
842
+ return null;
807
843
  }
808
844
 
809
845
  function externalWorkspaceFolderFromConfig(appPath, baseDir, scopedWorkspace) {
@@ -926,7 +962,7 @@ async function detectAppType(appName, options = {}) {
926
962
  const builderAfterTemplate = checkBuilderFolder(appName);
927
963
  if (builderAfterTemplate) return builderAfterTemplate;
928
964
  throw new Error(
929
- `App '${appName}' not found in integration/${appName}, enterprise-knowledge/${appName}, or builder/${appName}`
965
+ `App '${appName}' not found in integration/${appName}, enterprise-knowledge/${appName}, role-assistant/${appName}, or builder/${appName}`
930
966
  );
931
967
  }
932
968
 
@@ -964,6 +1000,11 @@ async function getResolveAppPath(appName, options = {}) {
964
1000
  if (workspaceHit) {
965
1001
  return workspaceHit;
966
1002
  }
1003
+ const { getRoleAssistantPath } = require('./role-assistant-paths');
1004
+ const roleAssistantHit = tryResolveEnvOnlyPath(getRoleAssistantPath(appName));
1005
+ if (roleAssistantHit) {
1006
+ return roleAssistantHit;
1007
+ }
967
1008
  const integrationHit = tryResolveEnvOnlyPath(getIntegrationPath(appName));
968
1009
  if (integrationHit) {
969
1010
  return integrationHit;
@@ -981,23 +1022,17 @@ async function getResolveAppPath(appName, options = {}) {
981
1022
  return { appPath: result.appPath, envOnly: false };
982
1023
  }
983
1024
 
984
- /**
985
- * @param {string} walkDir - Candidate workspace root (walk upward from cwd)
986
- * @param {string} cwd - Resolved process.cwd()
987
- * @param {ReturnType<typeof nodeFs>} disk
988
- * @returns {string|null}
989
- */
990
- function tryIntegrationAppKeyAtWalkStep(walkDir, cwd, disk) {
991
- const integrationDir = path.join(walkDir, 'integration');
1025
+ function tryAuthoringAppKeyAtWalkStep(walkDir, cwd, disk, folderName) {
1026
+ const authoringDir = path.join(walkDir, folderName);
992
1027
  try {
993
- if (!disk.existsSync(integrationDir)) {
1028
+ if (!disk.existsSync(authoringDir)) {
994
1029
  return null;
995
1030
  }
996
- const intNorm = path.resolve(integrationDir);
997
- if (cwd !== intNorm && !cwd.startsWith(intNorm + path.sep)) {
1031
+ const rootNorm = path.resolve(authoringDir);
1032
+ if (cwd !== rootNorm && !cwd.startsWith(rootNorm + path.sep)) {
998
1033
  return null;
999
1034
  }
1000
- const rel = path.relative(intNorm, cwd);
1035
+ const rel = path.relative(rootNorm, cwd);
1001
1036
  if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
1002
1037
  return null;
1003
1038
  }
@@ -1008,16 +1043,20 @@ function tryIntegrationAppKeyAtWalkStep(walkDir, cwd, disk) {
1008
1043
  }
1009
1044
 
1010
1045
  /**
1011
- * Resolve app folder name when cwd is under some ancestor's `integration/<systemKey>/`.
1046
+ * Resolve app folder name when cwd is under integration/, role-assistant/, or
1047
+ * enterprise-knowledge/<systemKey>/.
1012
1048
  * Walks up from cwd (does not use {@link getIntegrationBuilderBaseDir}) so detection still works
1013
- * when the canonical base is config/home but the shell is inside a checkout's integration tree.
1049
+ * when the canonical base is config/home but the shell is inside a checkout's tree.
1014
1050
  */
1015
1051
  function resolveIntegrationAppKeyFromCwd() {
1016
1052
  const cwd = path.resolve(process.cwd());
1017
1053
  const disk = nodeFs();
1018
1054
  let p = cwd;
1019
1055
  for (let i = 0; i < 64; i += 1) {
1020
- const key = tryIntegrationAppKeyAtWalkStep(p, cwd, disk);
1056
+ const key =
1057
+ tryAuthoringAppKeyAtWalkStep(p, cwd, disk, 'integration') ||
1058
+ tryAuthoringAppKeyAtWalkStep(p, cwd, disk, 'role-assistant') ||
1059
+ tryAuthoringAppKeyAtWalkStep(p, cwd, disk, 'enterprise-knowledge');
1021
1060
  if (key) {
1022
1061
  return key;
1023
1062
  }
@@ -1073,6 +1112,9 @@ module.exports = {
1073
1112
  getEnterpriseKnowledgePath: require('./enterprise-knowledge-paths').getEnterpriseKnowledgePath,
1074
1113
  getEnterpriseKnowledgeRoot: require('./enterprise-knowledge-paths').getEnterpriseKnowledgeRoot,
1075
1114
  listEnterpriseKnowledgeAnchors: require('./enterprise-knowledge-paths').listEnterpriseKnowledgeAnchors,
1115
+ getRoleAssistantPath: require('./role-assistant-paths').getRoleAssistantPath,
1116
+ getRoleAssistantRoot: require('./role-assistant-paths').getRoleAssistantRoot,
1117
+ getCwdRoleAssistantRoot: require('./role-assistant-paths').getCwdRoleAssistantRoot,
1076
1118
  resolveAppWorkspace: require('./app-path-resolve').resolveAppWorkspace,
1077
1119
  resolveExternalSystemPath: require('./app-path-resolve').resolveExternalSystemPath
1078
1120
  };
@@ -57,10 +57,22 @@ async function resolveClientTokenAuthForPipelineRetry(systemKey) {
57
57
  * @param {Object} authConfig
58
58
  * @param {Object} payload
59
59
  * @param {string} systemKey - External system key for credential lookup on retry
60
+ * @param {Object} [options] - Forwarded to uploadApplicationViaPipeline (e.g. timeoutMs)
60
61
  * @returns {Promise<Object>}
61
62
  */
62
- async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfig, payload, systemKey) {
63
- const first = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
63
+ async function uploadApplicationViaPipelineWithAuthRetry(
64
+ dataplaneUrl,
65
+ authConfig,
66
+ payload,
67
+ systemKey,
68
+ options = {}
69
+ ) {
70
+ const first = await uploadApplicationViaPipeline(
71
+ dataplaneUrl,
72
+ authConfig,
73
+ payload,
74
+ options
75
+ );
64
76
  if (!isPipelineAuthFailure(first) || authConfig.type !== 'bearer') {
65
77
  return first;
66
78
  }
@@ -77,7 +89,7 @@ async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfi
77
89
  'Device login token was rejected for pipeline publish; retrying with application client token…'
78
90
  )
79
91
  );
80
- return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload);
92
+ return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload, options);
81
93
  }
82
94
 
83
95
  module.exports = {