@aifabrix/builder 2.46.0 → 2.47.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 (191) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +18 -4
  47. package/lib/cli/infra-guided-auth.js +36 -12
  48. package/lib/cli/setup-app.js +54 -3
  49. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  50. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  51. package/lib/cli/setup-npm.js +59 -0
  52. package/lib/cli/setup-platform.js +1 -1
  53. package/lib/cli/setup-secrets.js +1 -1
  54. package/lib/cli/setup-utility-help-after.js +4 -1
  55. package/lib/cli/setup-utility-resolve.js +8 -5
  56. package/lib/commands/app-logs.js +54 -26
  57. package/lib/commands/app.js +41 -0
  58. package/lib/commands/auth-status-dataplane-version.js +97 -61
  59. package/lib/commands/auth-status.js +2 -7
  60. package/lib/commands/channel.js +58 -0
  61. package/lib/commands/datasource.js +1 -1
  62. package/lib/commands/identity-apply-sync.js +49 -8
  63. package/lib/commands/identity-group-cli.js +66 -26
  64. package/lib/commands/identity-membership-role-cache.js +58 -37
  65. package/lib/commands/identity-shared.js +22 -1
  66. package/lib/commands/identity-user-cli.js +80 -34
  67. package/lib/commands/login-device.js +11 -3
  68. package/lib/commands/login.js +8 -3
  69. package/lib/commands/npm-config.js +103 -0
  70. package/lib/commands/repair-auth-apply.js +30 -4
  71. package/lib/commands/repair-auth-config.js +8 -13
  72. package/lib/commands/repair-datasource-com-v2.js +15 -0
  73. package/lib/commands/repair-datasource-service-contract.js +206 -0
  74. package/lib/commands/repair-datasource.js +53 -18
  75. package/lib/commands/repair-env-template.js +12 -12
  76. package/lib/commands/repair-openapi-sync.js +26 -4
  77. package/lib/commands/repair-steps.js +28 -13
  78. package/lib/commands/repair-system-alignment.js +7 -4
  79. package/lib/commands/resource-type.js +371 -0
  80. package/lib/commands/setup-modes.js +1 -1
  81. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  82. package/lib/commands/test-trust-external.js +39 -90
  83. package/lib/commands/up-common.js +33 -1
  84. package/lib/commands/up-dataplane.js +8 -4
  85. package/lib/commands/upload.js +24 -3
  86. package/lib/commands/verify-trust-command-action.js +4 -1
  87. package/lib/commands/verify-trust-external.js +12 -20
  88. package/lib/core/config-setup-platform-mode.js +3 -3
  89. package/lib/core/setup-platform-mode.js +41 -6
  90. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  91. package/lib/datasource/capability/capability-manifest.js +254 -0
  92. package/lib/datasource/capability/capability-resolve.js +3 -4
  93. package/lib/datasource/capability/copy-operations.js +5 -3
  94. package/lib/datasource/capability/remove-operations.js +2 -1
  95. package/lib/datasource/capability/run-capability-copy.js +32 -4
  96. package/lib/datasource/capability/run-capability-diff.js +3 -3
  97. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  98. package/lib/datasource/capability/run-capability-edit.js +4 -7
  99. package/lib/datasource/capability/run-capability-relate.js +2 -4
  100. package/lib/datasource/capability/run-capability-remove.js +2 -4
  101. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  102. package/lib/datasource/datasource-identifier.js +2 -1
  103. package/lib/datasource/datasource-validate-display.js +1 -1
  104. package/lib/datasource/field-reference-validator.js +24 -3
  105. package/lib/datasource/orphan-profiles-validator.js +98 -0
  106. package/lib/datasource/validate.js +41 -23
  107. package/lib/external-system/download-helpers.js +87 -0
  108. package/lib/external-system/download.js +61 -122
  109. package/lib/external-system/generator.js +1 -59
  110. package/lib/external-system/test.js +10 -15
  111. package/lib/generator/builders.js +11 -1
  112. package/lib/generator/external-controller-manifest.js +5 -2
  113. package/lib/generator/split.js +3 -1
  114. package/lib/generator/wizard.js +1 -28
  115. package/lib/identity/fk-reference.js +65 -0
  116. package/lib/identity/identity-apply-core.js +13 -58
  117. package/lib/identity/identity-apply-phases.js +5 -1
  118. package/lib/identity/identity-display.js +243 -0
  119. package/lib/identity/identity-response-helpers.js +52 -0
  120. package/lib/identity/identity-sync-errors.js +62 -0
  121. package/lib/identity/resolve-user-id.js +129 -0
  122. package/lib/identity/username-default.js +31 -0
  123. package/lib/lifecycle/certification-progress.js +2 -2
  124. package/lib/lifecycle/report-display-trust.js +70 -0
  125. package/lib/lifecycle/report-display.js +14 -13
  126. package/lib/resolvers/datasource-resolver.js +2 -3
  127. package/lib/resolvers/resource-type-file.js +159 -0
  128. package/lib/schema/application-schema.json +16 -0
  129. package/lib/schema/deployment-rules.yaml +2 -1
  130. package/lib/schema/external-datasource.schema.json +372 -58
  131. package/lib/schema/external-system.schema.json +331 -2
  132. package/lib/schema/type/document-storage.json +9 -5
  133. package/lib/utils/api.js +2 -1
  134. package/lib/utils/app-run-containers.js +25 -2
  135. package/lib/utils/authentication-shape.js +224 -0
  136. package/lib/utils/build-copy.js +36 -5
  137. package/lib/utils/build-resolve-image.js +66 -12
  138. package/lib/utils/celery-companion-apps.js +53 -0
  139. package/lib/utils/cli-utils.js +4 -0
  140. package/lib/utils/compose-celery-worker.js +23 -0
  141. package/lib/utils/compose-generate-docker-compose.js +9 -1
  142. package/lib/utils/configuration-env-resolver.js +36 -0
  143. package/lib/utils/controller-url.js +4 -1
  144. package/lib/utils/credential-secrets-env-kv.js +216 -0
  145. package/lib/utils/credential-secrets-env.js +52 -262
  146. package/lib/utils/dataplane-resolver.js +4 -115
  147. package/lib/utils/declarative-url-ports.js +24 -1
  148. package/lib/utils/dev-platform-tls.js +148 -0
  149. package/lib/utils/docker-build.js +1 -4
  150. package/lib/utils/external-env-template.js +10 -3
  151. package/lib/utils/external-system-validators.js +8 -2
  152. package/lib/utils/health-check-request.js +114 -0
  153. package/lib/utils/health-check-url.js +3 -1
  154. package/lib/utils/health-check.js +1 -113
  155. package/lib/utils/help-builder.js +3 -0
  156. package/lib/utils/keycloak-realm-ready.js +5 -1
  157. package/lib/utils/localhost-public-host.js +31 -0
  158. package/lib/utils/npm-user-config.js +145 -0
  159. package/lib/utils/paths.js +25 -0
  160. package/lib/utils/platform-controller-url.js +58 -4
  161. package/lib/utils/platform-kv-ref.js +57 -0
  162. package/lib/utils/runtime-configuration-download.js +189 -0
  163. package/lib/utils/runtime-configuration-values.js +119 -0
  164. package/lib/utils/schema-loader.js +19 -4
  165. package/lib/utils/url-declarative-public-base.js +33 -25
  166. package/lib/utils/worker-sidecars.js +142 -0
  167. package/lib/validation/external-manifest-validator.js +2 -0
  168. package/lib/validation/runtime-configuration-rules.js +98 -0
  169. package/lib/validation/validate-display-dimensions.js +154 -0
  170. package/lib/validation/validate-display-sections.js +276 -0
  171. package/lib/validation/validate-display-steps.js +67 -0
  172. package/lib/validation/validate-display.js +24 -402
  173. package/package.json +1 -1
  174. package/templates/applications/dataplane/application.yaml +5 -0
  175. package/templates/applications/dataplane/env.template +26 -13
  176. package/templates/applications/dataplane/rbac.yaml +108 -1
  177. package/templates/applications/miso-controller/application.yaml +2 -2
  178. package/templates/applications/miso-controller/env.template +37 -122
  179. package/templates/applications/miso-controller/rbac.yaml +28 -13
  180. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  181. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  182. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  183. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  184. package/templates/channels/slack/install.template.md.hbs +49 -0
  185. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  186. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  187. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  188. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  189. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  190. package/templates/python/docker-compose.hbs +56 -0
  191. package/templates/typescript/docker-compose.hbs +52 -0
@@ -315,6 +315,30 @@ function getDevDirectory(appName, developerId) {
315
315
  return baseDir;
316
316
  }
317
317
 
318
+ /**
319
+ * Target directory for `aifabrix create` — always under {@link process.cwd} (`builder/` or
320
+ * `integration/`) even when those folders do not exist yet. Platform system apps still
321
+ * materialize under {@link getAppsMaterializationParent}.
322
+ *
323
+ * Post-create lookup uses {@link getAppPath} / {@link getBuilderPath} / {@link getIntegrationPath}.
324
+ *
325
+ * @param {string} appName - Application name
326
+ * @param {string} [appType] - Application type (`external` or other)
327
+ * @returns {string} Absolute path to the new application directory
328
+ */
329
+ function getAppCreatePath(appName, appType) {
330
+ if (!appName || typeof appName !== 'string') {
331
+ throw new Error('App name is required and must be a string');
332
+ }
333
+ if (appType === 'external') {
334
+ return path.join(path.resolve(process.cwd()), 'integration', appName);
335
+ }
336
+ if (isSystemBuilderAppName(appName)) {
337
+ return path.join(getAppsMaterializationParent(), 'builder', appName);
338
+ }
339
+ return path.join(path.resolve(process.cwd()), 'builder', appName);
340
+ }
341
+
318
342
  /**
319
343
  * Gets the application path (builder or integration folder).
320
344
  * Matches {@link getBuilderPath} / {@link getIntegrationPath} (cwd `integration/` / `builder/`, then
@@ -881,6 +905,7 @@ module.exports = {
881
905
  getApplicationsBaseDir,
882
906
  getDevDirectory,
883
907
  getAppPath,
908
+ getAppCreatePath,
884
909
  getAppsMaterializationParent,
885
910
  getCwdIntegrationRoot,
886
911
  getCwdBuilderRoot,
@@ -18,6 +18,7 @@ const { loadConfigFile } = require('./config-format');
18
18
  const { computePublicUrlBaseString } = require('./url-declarative-public-base');
19
19
  const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('./health-check-url');
20
20
  const { getDevPorts } = require('./dev-config');
21
+ const { flagsForSetupPlatformMode } = require('../core/setup-platform-mode');
21
22
 
22
23
  const MISO_CONTROLLER_APP = 'miso-controller';
23
24
 
@@ -74,7 +75,7 @@ function loadAppVariables(appName) {
74
75
 
75
76
  function computeLocalFullPlatformFrontDoorUrl(params) {
76
77
  const { setupPlatformMode, remoteServer, developerIdNum, frontDoorRouting } = params;
77
- if (setupPlatformMode !== 'full') {
78
+ if (!flagsForSetupPlatformMode(setupPlatformMode).traefik) {
78
79
  return null;
79
80
  }
80
81
  if (remoteServer && String(remoteServer).trim()) {
@@ -102,12 +103,12 @@ async function computeAppBaseUrl(appName) {
102
103
  const userCfg = await config.getConfig();
103
104
  const remoteServer = await config.getRemoteServer();
104
105
  const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
106
+ const setupPlatformMode = await config.getSetupPlatformMode();
105
107
 
106
108
  const fd = variables.frontDoorRouting || null;
107
109
  const frontDoorEnabled = Boolean(fd && fd.enabled === true);
108
110
  const traefikOn = Boolean(userCfg && userCfg.traefik);
109
111
  const pathActive = Boolean(traefikOn && frontDoorEnabled);
110
- const setupPlatformMode = await config.getSetupPlatformMode();
111
112
 
112
113
  const publicBase = computePublicUrlBaseString({
113
114
  traefik: traefikOn,
@@ -119,7 +120,8 @@ async function computeAppBaseUrl(appName) {
119
120
  profile: 'docker',
120
121
  listenPort: basePort,
121
122
  developerIdNum,
122
- infraTlsEnabled
123
+ infraTlsEnabled,
124
+ setupPlatformMode
123
125
  });
124
126
 
125
127
  if (pathActive) {
@@ -147,10 +149,62 @@ async function resolvePlatformControllerUrl() {
147
149
  return computeAppBaseUrl(MISO_CONTROLLER_APP);
148
150
  }
149
151
 
152
+ /**
153
+ * Direct localhost controller URL (published Docker port + optional front-door mount).
154
+ * Used when the CLI runs on the builder host but the public Traefik URL is unreachable.
155
+ *
156
+ * @async
157
+ * @param {string} [appName]
158
+ * @returns {Promise<string>}
159
+ */
160
+ async function resolveLocalhostPlatformControllerUrl(appName = MISO_CONTROLLER_APP) {
161
+ const variables = loadAppVariables(appName);
162
+ const developerIdRaw = await config.getDeveloperId();
163
+ const developerIdNum =
164
+ typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
165
+ const basePort = Number(variables.port || 3000);
166
+ const hostPort = basePort + (Number.isFinite(developerIdNum) ? developerIdNum : 0) * 100;
167
+ const fd = variables.frontDoorRouting;
168
+ if (fd && fd.enabled === true && fd.pattern) {
169
+ const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
170
+ if (mount && mount !== '/') {
171
+ return joinUrlPath(`http://localhost:${hostPort}`, mount).replace(/\/+$/, '');
172
+ }
173
+ }
174
+ return `http://localhost:${hostPort}`;
175
+ }
176
+
177
+ /**
178
+ * Controller URL for CLI HTTP calls on a remote dev host (builder-server).
179
+ * Keeps the public Traefik URL when reachable; otherwise falls back to localhost.
180
+ *
181
+ * @async
182
+ * @returns {Promise<string>}
183
+ */
184
+ async function resolveCliPlatformControllerUrl() {
185
+ const publicUrl = await resolvePlatformControllerUrl();
186
+ const remoteServer = await config.getRemoteServer();
187
+ if (!remoteServer || !String(remoteServer).trim()) {
188
+ return publicUrl;
189
+ }
190
+ try {
191
+ const { checkHealthEndpoint } = require('./health-check-request');
192
+ const healthUrl = `${String(publicUrl).replace(/\/+$/, '')}/health`;
193
+ if (await checkHealthEndpoint(healthUrl)) {
194
+ return publicUrl;
195
+ }
196
+ } catch {
197
+ // fall through to localhost
198
+ }
199
+ return resolveLocalhostPlatformControllerUrl(MISO_CONTROLLER_APP);
200
+ }
201
+
150
202
  module.exports = {
151
203
  MISO_CONTROLLER_APP,
152
204
  loadAppVariables,
153
205
  loadBundledApplicationVariables,
154
206
  computeAppBaseUrl,
155
- resolvePlatformControllerUrl
207
+ resolvePlatformControllerUrl,
208
+ resolveLocalhostPlatformControllerUrl,
209
+ resolveCliPlatformControllerUrl
156
210
  };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Platform installation kv:// refs from infra.parameter.yaml (flat keys, no path segments).
3
+ *
4
+ * @fileoverview Distinguish platform catalog secrets from per-integration path-style kv refs
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getInfraParameterCatalog } = require('../parameters/infra-parameter-catalog');
12
+
13
+ /**
14
+ * Returns the single-segment path after kv:// when the ref has no slashes.
15
+ *
16
+ * @param {string} val - kv:// reference
17
+ * @returns {string|null}
18
+ */
19
+ function kvRefSegment(val) {
20
+ if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return null;
21
+ const after = val.trim().slice(5);
22
+ if (!after || after.includes('/')) return null;
23
+ return after;
24
+ }
25
+
26
+ /**
27
+ * True when val is kv://<key> and key exists in shipped infra.parameter.yaml.
28
+ *
29
+ * @param {string} val - kv:// reference
30
+ * @returns {boolean}
31
+ */
32
+ function isPlatformCatalogKvRef(val) {
33
+ const segment = kvRefSegment(val);
34
+ if (!segment) return false;
35
+ try {
36
+ const catalog = getInfraParameterCatalog();
37
+ return Boolean(catalog.findEntryForKey(segment));
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * True when repair should normalize a kv value to path-style kv://systemKey/variable.
45
+ * Platform catalog refs (e.g. kv://secrets-openaiApiKeyVault) are never legacy.
46
+ *
47
+ * @param {string} val - Value from authentication.security or configuration
48
+ * @returns {boolean}
49
+ */
50
+ function isLegacyKvValue(val) {
51
+ if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
52
+ if (isPlatformCatalogKvRef(val)) return false;
53
+ const after = val.trim().slice(5);
54
+ return after.includes('KeyVault') || !after.includes('/');
55
+ }
56
+
57
+ module.exports = { kvRefSegment, isPlatformCatalogKvRef, isLegacyKvValue };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Sync runtime configuration_values from dataplane on download (values → env.template; manifest → placeholders).
3
+ *
4
+ * @fileoverview Download-time runtime configuration sync
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const fsPromises = require('fs').promises;
11
+ const path = require('path');
12
+ const yaml = require('js-yaml');
13
+
14
+ const {
15
+ getExternalSystemConfigurationResolved,
16
+ getExternalDataSourceConfigurationResolved
17
+ } = require('../api/runtime-configuration.api');
18
+ const {
19
+ retemplateAllVariableDefinitions,
20
+ retemplateDatasourceConfigurationOverrides
21
+ } = require('./configuration-env-resolver');
22
+ const { mergeEnvTemplateWithExisting } = require('../generator/split');
23
+
24
+ /**
25
+ * @param {Object} response - Dataplane API client response
26
+ * @returns {Record<string, string>}
27
+ */
28
+ function extractResolvedVariableValues(response) {
29
+ if (!response || !response.success) {
30
+ return {};
31
+ }
32
+ const body = response.data && typeof response.data === 'object' ? response.data : {};
33
+ const variables = body.variables && typeof body.variables === 'object' ? body.variables : {};
34
+ const out = {};
35
+ for (const [name, entry] of Object.entries(variables)) {
36
+ if (!entry || typeof entry.value !== 'string') {
37
+ continue;
38
+ }
39
+ const value = entry.value.trim();
40
+ if (!value || value.startsWith('kv://')) {
41
+ continue;
42
+ }
43
+ out[name] = value;
44
+ }
45
+ return out;
46
+ }
47
+
48
+ /**
49
+ * @param {string} dataplaneUrl
50
+ * @param {Object} authConfig
51
+ * @param {string} systemKey
52
+ * @param {Array<{ key?: string }>} dataSources
53
+ * @returns {Promise<Record<string, string>>}
54
+ */
55
+ async function collectResolvedConfigurationEnvUpdates(
56
+ dataplaneUrl,
57
+ authConfig,
58
+ systemKey,
59
+ dataSources
60
+ ) {
61
+ const updates = {};
62
+ const systemResponse = await getExternalSystemConfigurationResolved(
63
+ dataplaneUrl,
64
+ systemKey,
65
+ authConfig
66
+ );
67
+ Object.assign(updates, extractResolvedVariableValues(systemResponse));
68
+
69
+ for (const ds of dataSources || []) {
70
+ if (!ds?.key) {
71
+ continue;
72
+ }
73
+ const dsResponse = await getExternalDataSourceConfigurationResolved(
74
+ dataplaneUrl,
75
+ ds.key,
76
+ authConfig
77
+ );
78
+ Object.assign(updates, extractResolvedVariableValues(dsResponse));
79
+ }
80
+ return updates;
81
+ }
82
+
83
+ /**
84
+ * @param {string} integrationPath
85
+ * @param {Record<string, string>} updates
86
+ * @returns {Promise<boolean>}
87
+ */
88
+ async function mergeConfigurationUpdatesIntoEnvTemplate(integrationPath, updates) {
89
+ const keys = Object.keys(updates || {});
90
+ if (!keys.length) {
91
+ return false;
92
+ }
93
+ const envTemplatePath = path.join(integrationPath, 'env.template');
94
+ const expectedByKey = new Map();
95
+ for (const key of keys) {
96
+ expectedByKey.set(key, `${key}=${updates[key]}`);
97
+ }
98
+ const existingContent = fs.existsSync(envTemplatePath)
99
+ ? await fsPromises.readFile(envTemplatePath, 'utf8')
100
+ : '';
101
+ const merged = mergeEnvTemplateWithExisting(existingContent, expectedByKey);
102
+ await fsPromises.writeFile(envTemplatePath, merged, { mode: 0o644, encoding: 'utf8' });
103
+ return true;
104
+ }
105
+
106
+ /**
107
+ * @param {string} filePath
108
+ * @returns {Promise<boolean>}
109
+ */
110
+ async function retemplateConfigurationFile(filePath, { system = false } = {}) {
111
+ if (!filePath || !fs.existsSync(filePath)) {
112
+ return false;
113
+ }
114
+ const raw = await fsPromises.readFile(filePath, 'utf8');
115
+ const isJson = filePath.endsWith('.json');
116
+ const parsed = isJson ? JSON.parse(raw) : yaml.load(raw);
117
+ if (!parsed || typeof parsed !== 'object') {
118
+ return false;
119
+ }
120
+ if (system) {
121
+ retemplateAllVariableDefinitions(parsed.configuration);
122
+ } else {
123
+ retemplateDatasourceConfigurationOverrides(parsed.configuration);
124
+ }
125
+ const next = isJson ? `${JSON.stringify(parsed, null, 2)}\n` : yaml.dump(parsed, { indent: 2, lineWidth: -1 });
126
+ await fsPromises.writeFile(filePath, next, { mode: 0o644, encoding: 'utf8' });
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * @param {string} integrationPath
132
+ * @param {string} systemKey
133
+ * @returns {Promise<void>}
134
+ */
135
+ async function retemplateDownloadedConfigurationFiles(integrationPath, systemKey) {
136
+ for (const ext of ['yaml', 'json']) {
137
+ const systemPath = path.join(integrationPath, `${systemKey}-system.${ext}`);
138
+ if (fs.existsSync(systemPath)) {
139
+ await retemplateConfigurationFile(systemPath, { system: true });
140
+ }
141
+ }
142
+ const entries = await fsPromises.readdir(integrationPath);
143
+ for (const name of entries) {
144
+ if (!name.includes('-datasource-') || (!name.endsWith('.yaml') && !name.endsWith('.json'))) {
145
+ continue;
146
+ }
147
+ await retemplateConfigurationFile(path.join(integrationPath, name), { system: false });
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Pulls configuration_values from dataplane, merges env.template, and re-templates manifests.
153
+ *
154
+ * @param {Object} params
155
+ * @param {string} params.dataplaneUrl
156
+ * @param {Object} params.authConfig
157
+ * @param {string} params.systemKey
158
+ * @param {string} params.integrationPath
159
+ * @param {Array<{ key?: string }>} params.dataSources
160
+ * @returns {Promise<{ merged: number, names: string[] }>}
161
+ */
162
+ async function syncRuntimeConfigurationFromDataplane({
163
+ dataplaneUrl,
164
+ authConfig,
165
+ systemKey,
166
+ integrationPath,
167
+ dataSources
168
+ }) {
169
+ const updates = await collectResolvedConfigurationEnvUpdates(
170
+ dataplaneUrl,
171
+ authConfig,
172
+ systemKey,
173
+ dataSources
174
+ );
175
+ const names = Object.keys(updates);
176
+ if (names.length) {
177
+ await mergeConfigurationUpdatesIntoEnvTemplate(integrationPath, updates);
178
+ }
179
+ await retemplateDownloadedConfigurationFiles(integrationPath, systemKey);
180
+ return { merged: names.length, names };
181
+ }
182
+
183
+ module.exports = {
184
+ extractResolvedVariableValues,
185
+ collectResolvedConfigurationEnvUpdates,
186
+ mergeConfigurationUpdatesIntoEnvTemplate,
187
+ retemplateDownloadedConfigurationFiles,
188
+ syncRuntimeConfigurationFromDataplane
189
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Push runtime configuration_values to dataplane after publish (never logs values).
3
+ *
4
+ * @fileoverview Post-publish configuration PUT helpers
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const {
10
+ putExternalSystemConfiguration,
11
+ putExternalDataSourceConfiguration
12
+ } = require('../api/runtime-configuration.api');
13
+ const { buildResolvedEnvMapForIntegration } = require('./configuration-env-resolver');
14
+
15
+ /**
16
+ * @param {Array<{ name?: string, location?: string }>|undefined} systemConfiguration
17
+ * @param {Object.<string, string>} envMap
18
+ * @returns {Array<{ name: string, value: string }>}
19
+ */
20
+ function collectSystemVariablePutRequests(systemConfiguration, envMap) {
21
+ if (!Array.isArray(systemConfiguration)) {
22
+ return [];
23
+ }
24
+ const requests = [];
25
+ for (const item of systemConfiguration) {
26
+ if (!item || !item.name) {
27
+ continue;
28
+ }
29
+ const location = String(item.location || 'variable').toLowerCase();
30
+ if (location !== 'variable') {
31
+ continue;
32
+ }
33
+ const name = String(item.name).trim();
34
+ const raw = envMap[name];
35
+ if (raw === undefined || raw === null) {
36
+ continue;
37
+ }
38
+ const value = String(raw).trim();
39
+ if (!value || value.startsWith('kv://') || value.includes('{{')) {
40
+ continue;
41
+ }
42
+ requests.push({ name, value });
43
+ }
44
+ return requests;
45
+ }
46
+
47
+ /**
48
+ * Datasource overrides are name+value only; values may already be resolved in upload payload.
49
+ *
50
+ * @param {Array<{ name?: string, value?: string }>|undefined} datasourceConfiguration
51
+ * @returns {Array<{ name: string, value: string }>}
52
+ */
53
+ function collectDatasourceOverridePutRequests(datasourceConfiguration) {
54
+ if (!Array.isArray(datasourceConfiguration)) {
55
+ return [];
56
+ }
57
+ const requests = [];
58
+ for (const item of datasourceConfiguration) {
59
+ if (!item || !item.name) {
60
+ continue;
61
+ }
62
+ const name = String(item.name).trim();
63
+ const raw = item.value;
64
+ if (typeof raw !== 'string') {
65
+ continue;
66
+ }
67
+ const value = raw.trim();
68
+ if (!value || value.startsWith('kv://') || value.includes('{{')) {
69
+ continue;
70
+ }
71
+ requests.push({ name, value });
72
+ }
73
+ return requests;
74
+ }
75
+
76
+ /**
77
+ * Upserts system-scoped and datasource-scoped configuration values after publish.
78
+ *
79
+ * @param {string} dataplaneUrl
80
+ * @param {Object} authConfig
81
+ * @param {string} systemKey
82
+ * @param {{ application?: { configuration?: unknown[] }, dataSources?: Array<{ key?: string, configuration?: unknown[] }> }} payload
83
+ * @returns {Promise<{ pushed: number, names: string[] }>}
84
+ */
85
+ async function pushRuntimeConfigurationValues(dataplaneUrl, authConfig, systemKey, payload) {
86
+ const { envMap } = await buildResolvedEnvMapForIntegration(systemKey);
87
+ const names = [];
88
+ let pushed = 0;
89
+
90
+ const systemRequests = collectSystemVariablePutRequests(
91
+ payload?.application?.configuration,
92
+ envMap
93
+ );
94
+ for (const body of systemRequests) {
95
+ await putExternalSystemConfiguration(dataplaneUrl, systemKey, authConfig, body);
96
+ names.push(body.name);
97
+ pushed += 1;
98
+ }
99
+
100
+ for (const ds of payload?.dataSources || []) {
101
+ if (!ds?.key) {
102
+ continue;
103
+ }
104
+ const dsRequests = collectDatasourceOverridePutRequests(ds.configuration);
105
+ for (const body of dsRequests) {
106
+ await putExternalDataSourceConfiguration(dataplaneUrl, ds.key, authConfig, body);
107
+ names.push(`${ds.key}:${body.name}`);
108
+ pushed += 1;
109
+ }
110
+ }
111
+
112
+ return { pushed, names };
113
+ }
114
+
115
+ module.exports = {
116
+ collectSystemVariablePutRequests,
117
+ collectDatasourceOverridePutRequests,
118
+ pushRuntimeConfigurationValues
119
+ };
@@ -28,6 +28,23 @@ function resetValidators() {
28
28
  externalDataSourceValidator = null;
29
29
  }
30
30
 
31
+ /**
32
+ * Register AJV schemas required to compile or validate external-datasource manifests.
33
+ * Includes type schemas and external-system (for $defs/subscriptionFilter cross-ref).
34
+ *
35
+ * @function registerExternalDatasourceAjvDependencies
36
+ * @param {import('ajv').default} ajv - AJV instance
37
+ */
38
+ function registerExternalDatasourceAjvDependencies(ajv) {
39
+ ajv.addSchema(require('../schema/type/document-storage.json'));
40
+ ajv.addSchema(require('../schema/type/message-service.json'));
41
+ ajv.addSchema(require('../schema/type/vector-store.json'));
42
+ const externalSystemSchema = require('../schema/external-system.schema.json');
43
+ if (!ajv.getSchema(externalSystemSchema.$id)) {
44
+ ajv.addSchema(externalSystemSchema, externalSystemSchema.$id);
45
+ }
46
+ }
47
+
31
48
  /**
32
49
  * Loads and compiles external-system schema
33
50
  * Caches the compiled validator for performance
@@ -115,10 +132,7 @@ function loadExternalDataSourceSchema() {
115
132
 
116
133
  const ajv = new Ajv({ allErrors: true, strict: false, strictSchema: false });
117
134
  addFormats(ajv);
118
- // external-datasource.schema.json references these by $id (aifabrix://schema/type/*)
119
- ajv.addSchema(require('../schema/type/document-storage.json'));
120
- ajv.addSchema(require('../schema/type/message-service.json'));
121
- ajv.addSchema(require('../schema/type/vector-store.json'));
135
+ registerExternalDatasourceAjvDependencies(ajv);
122
136
  externalDataSourceValidator = ajv.compile(schemaToCompile);
123
137
 
124
138
  return externalDataSourceValidator;
@@ -333,6 +347,7 @@ function detectSchemaType(filePath, content) {
333
347
  module.exports = {
334
348
  loadExternalSystemSchema,
335
349
  loadExternalDataSourceSchema,
350
+ registerExternalDatasourceAjvDependencies,
336
351
  detectSchemaType,
337
352
  detectSchemaTypeFromParsed,
338
353
  resetValidators
@@ -10,26 +10,11 @@
10
10
 
11
11
  const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
12
12
  const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
13
-
14
- /**
15
- * Public URL authorities that must use `http://` (browsers hit loopback without TLS in practice).
16
- *
17
- * @param {string} hostname - Parsed URL hostname
18
- * @returns {boolean}
19
- */
20
- function isLocalhostPublicSchemeHostname(hostname) {
21
- const h = String(hostname || '').trim().toLowerCase();
22
- if (!h) {
23
- return false;
24
- }
25
- if (h === 'localhost') {
26
- return true;
27
- }
28
- if (h === '127.0.0.1') {
29
- return true;
30
- }
31
- return h === '::1';
32
- }
13
+ const {
14
+ normalizeSetupPlatformMode,
15
+ SETUP_PLATFORM_MODE
16
+ } = require('../core/setup-platform-mode');
17
+ const { isLocalhostPublicSchemeHostname } = require('./localhost-public-host');
33
18
 
34
19
  /**
35
20
  * Expand frontDoorRouting.host placeholders for url:// (same rules as Traefik labels in compose-generator).
@@ -156,6 +141,28 @@ function templateUsesRemoteHostPlaceholder(hostTemplate) {
156
141
  return /\$\{REMOTE_HOST\}/.test(String(hostTemplate || ''));
157
142
  }
158
143
 
144
+ /**
145
+ * HTTPS for Traefik front-door public host when infra TLS is on, remote-server is https,
146
+ * or setup mode is single-traefik/full (builder-server nginx edge TLS).
147
+ *
148
+ * @param {Object} opts
149
+ * @param {boolean} [opts.infraTlsEnabled]
150
+ * @param {string|null|undefined} [opts.remoteServer]
151
+ * @param {string|null|undefined} [opts.setupPlatformMode]
152
+ * @returns {boolean}
153
+ */
154
+ function traefikFrontDoorUsesHttps(opts = {}) {
155
+ if (opts.infraTlsEnabled) {
156
+ return true;
157
+ }
158
+ const remote = String(opts.remoteServer || '').trim();
159
+ if (remote && /^https:\/\//i.test(remote)) {
160
+ return true;
161
+ }
162
+ const mode = normalizeSetupPlatformMode(opts.setupPlatformMode);
163
+ return mode === SETUP_PLATFORM_MODE.SINGLE_TRAEFIK || mode === SETUP_PLATFORM_MODE.FULL;
164
+ }
165
+
159
166
  /**
160
167
  * @param {Object} opts - same shape as computePublicUrlBaseString
161
168
  * @returns {string|null}
@@ -167,7 +174,6 @@ function buildTraefikPublicBaseIfApplicable(opts) {
167
174
  hostTemplate,
168
175
  developerIdRaw,
169
176
  remoteServer,
170
- infraTlsEnabled,
171
177
  profile,
172
178
  developerIdNum
173
179
  } = opts;
@@ -189,8 +195,7 @@ function buildTraefikPublicBaseIfApplicable(opts) {
189
195
  if (!expanded) {
190
196
  return null;
191
197
  }
192
- // Scheme follows global `tlsEnabled` (`up-infra --tls`) only — not `frontDoorRouting.tls` when infra TLS is off.
193
- const useHttps = Boolean(infraTlsEnabled);
198
+ const useHttps = traefikFrontDoorUsesHttps(opts);
194
199
  let scheme = useHttps ? 'https' : 'http';
195
200
  if (isLocalhostPublicSchemeHostname(expanded)) {
196
201
  scheme = 'http';
@@ -210,7 +215,8 @@ function buildTraefikPublicBaseIfApplicable(opts) {
210
215
  * @param {'docker'|'local'} opts.profile
211
216
  * @param {number} opts.listenPort
212
217
  * @param {number} opts.developerIdNum
213
- * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); Traefik front-door public bases use **`https`** only when this is **true** (even if `frontDoorRouting.tls` is **false**). When **false**, front-door bases use **`http`** regardless of `frontDoorRouting.tls`. Loopback hostnames stay **http** always.
218
+ * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`). Traefik front-door public bases also use **`https`** when `remote-server` is **`https://`** or `setupPlatformMode` is **`single-traefik`** / **`full`** (builder-server nginx edge TLS). Loopback hostnames stay **http** always.
219
+ * @param {string|null|undefined} [opts.setupPlatformMode] - From config; used with remote-server for edge TLS scheme
214
220
  * @param {boolean} [opts.pathActive] - traefik ∧ frontDoorRouting.enabled; required for Traefik host branch (plan 124)
215
221
  * @param {boolean|undefined} [opts.declarativePublicUrlsUseLocalhost] - **`true`**: force localhost authority (+ port rules below). **`false`**: allow `remote-server` as authority when Traefik is on, or when global `traefik` is not `false` and per-app `proxy` opts in without Traefik. **`undefined`**: default — omit `remote-server` when Traefik is off so published services use `http://localhost:<port>` (e.g. `af setup` / device login URLs). When user config has **`traefik: false`**, expansion forces localhost for public bases regardless of per-app `proxy`.
216
222
  * @returns {string}
@@ -270,5 +276,7 @@ function computePublicUrlBaseString(opts) {
270
276
  module.exports = {
271
277
  expandFrontDoorHostTemplateForUrls,
272
278
  computePublicUrlBaseString,
273
- resolveHostPortForDeclarativePublic
279
+ resolveHostPortForDeclarativePublic,
280
+ isLocalhostPublicSchemeHostname,
281
+ traefikFrontDoorUsesHttps
274
282
  };