@aifabrix/builder 2.44.5 → 2.45.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 (249) hide show
  1. package/.cursor/rules/cli-layout.mdc +8 -4
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/README.md +15 -23
  4. package/integration/hubspot-test/README.md +2 -0
  5. package/integration/hubspot-test/test.js +5 -3
  6. package/jest.projects.js +104 -2
  7. package/lib/api/controller-health.api.js +49 -0
  8. package/lib/api/dimension-values.api.js +82 -0
  9. package/lib/api/dimensions.api.js +114 -0
  10. package/lib/api/external-systems.api.js +1 -0
  11. package/lib/api/integration-clients.api.js +168 -0
  12. package/lib/api/types/dimension-values.types.js +28 -0
  13. package/lib/api/types/dimensions.types.js +31 -0
  14. package/lib/api/types/integration-clients.types.js +45 -0
  15. package/lib/api/validation-runner.js +46 -25
  16. package/lib/app/deploy-config.js +11 -1
  17. package/lib/app/deploy-status-display.js +3 -3
  18. package/lib/app/deploy.js +36 -14
  19. package/lib/app/display.js +15 -11
  20. package/lib/app/helpers.js +3 -3
  21. package/lib/app/index.js +3 -3
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +7 -6
  24. package/lib/app/restart-display.js +126 -0
  25. package/lib/app/rotate-secret.js +7 -6
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +58 -19
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +148 -74
  32. package/lib/app/show-display.js +7 -0
  33. package/lib/app/show.js +87 -5
  34. package/lib/build/index.js +83 -49
  35. package/lib/cli/doctor-check.js +117 -0
  36. package/lib/cli/index.js +8 -2
  37. package/lib/cli/infra-guided.js +460 -0
  38. package/lib/cli/installation-log-command.js +73 -0
  39. package/lib/cli/setup-app.js +31 -3
  40. package/lib/cli/setup-auth.js +98 -27
  41. package/lib/cli/setup-dev-path-commands.js +50 -3
  42. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  43. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  44. package/lib/cli/setup-infra.js +132 -118
  45. package/lib/cli/setup-integration-client.js +182 -0
  46. package/lib/cli/setup-parameters.js +21 -2
  47. package/lib/cli/setup-platform.js +102 -0
  48. package/lib/cli/setup-secrets.js +18 -6
  49. package/lib/cli/setup-utility-resolve.js +132 -0
  50. package/lib/cli/setup-utility.js +143 -84
  51. package/lib/commands/app-logs.js +81 -33
  52. package/lib/commands/auth-config.js +116 -18
  53. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  54. package/lib/commands/datasource-capability-output.js +29 -0
  55. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  56. package/lib/commands/datasource-capability.js +411 -0
  57. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  58. package/lib/commands/datasource.js +53 -13
  59. package/lib/commands/dev-down.js +3 -3
  60. package/lib/commands/dev-infra-gate.js +32 -0
  61. package/lib/commands/dev-init.js +13 -7
  62. package/lib/commands/dimension-value.js +179 -0
  63. package/lib/commands/dimension.js +330 -0
  64. package/lib/commands/integration-client.js +430 -0
  65. package/lib/commands/login-device.js +65 -30
  66. package/lib/commands/login.js +21 -10
  67. package/lib/commands/parameters-validate.js +78 -13
  68. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  69. package/lib/commands/repair-datasource-keys.js +10 -5
  70. package/lib/commands/repair-datasource.js +19 -7
  71. package/lib/commands/repair-env-template.js +4 -1
  72. package/lib/commands/repair-openapi-sync.js +172 -0
  73. package/lib/commands/repair-persist.js +102 -0
  74. package/lib/commands/repair-rbac-extract.js +27 -0
  75. package/lib/commands/repair-rbac-migrate.js +186 -0
  76. package/lib/commands/repair-rbac.js +214 -31
  77. package/lib/commands/repair-system-alignment.js +246 -0
  78. package/lib/commands/repair-system-permissions.js +168 -0
  79. package/lib/commands/repair.js +120 -338
  80. package/lib/commands/secure.js +1 -1
  81. package/lib/commands/setup-modes.js +468 -0
  82. package/lib/commands/setup-prompts.js +421 -0
  83. package/lib/commands/setup.js +254 -0
  84. package/lib/commands/teardown.js +277 -0
  85. package/lib/commands/up-common.js +113 -19
  86. package/lib/commands/up-dataplane.js +44 -19
  87. package/lib/commands/up-miso.js +18 -18
  88. package/lib/commands/upload.js +111 -23
  89. package/lib/commands/wizard-core-helpers.js +14 -11
  90. package/lib/commands/wizard-core.js +6 -5
  91. package/lib/commands/wizard-dataplane.js +2 -2
  92. package/lib/commands/wizard-entity-selection.js +4 -3
  93. package/lib/commands/wizard-headless.js +2 -1
  94. package/lib/commands/wizard.js +2 -1
  95. package/lib/constants/infra-compose-service-names.js +40 -0
  96. package/lib/core/audit-logger.js +1 -34
  97. package/lib/core/config-admin-email.js +56 -0
  98. package/lib/core/config-normalize.js +60 -0
  99. package/lib/core/config-registered-controller-urls.js +54 -0
  100. package/lib/core/config.js +33 -50
  101. package/lib/core/env-reader.js +16 -3
  102. package/lib/core/secrets-admin-env.js +101 -0
  103. package/lib/core/secrets-ensure-infra.js +34 -1
  104. package/lib/core/secrets-ensure.js +88 -66
  105. package/lib/core/secrets-env-content.js +428 -0
  106. package/lib/core/secrets-env-declarative-expand.js +170 -0
  107. package/lib/core/secrets-env-write.js +29 -1
  108. package/lib/core/secrets-load.js +252 -0
  109. package/lib/core/secrets-names.js +32 -0
  110. package/lib/core/secrets.js +17 -757
  111. package/lib/datasource/capability/basic-exposure.js +76 -0
  112. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  113. package/lib/datasource/capability/capability-key.js +34 -0
  114. package/lib/datasource/capability/capability-resolve.js +172 -0
  115. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  116. package/lib/datasource/capability/copy-operations.js +348 -0
  117. package/lib/datasource/capability/copy-test-payload.js +139 -0
  118. package/lib/datasource/capability/create-operations.js +235 -0
  119. package/lib/datasource/capability/dimension-operations.js +151 -0
  120. package/lib/datasource/capability/dimension-validate.js +219 -0
  121. package/lib/datasource/capability/json-pointer.js +31 -0
  122. package/lib/datasource/capability/reference-rewrite.js +51 -0
  123. package/lib/datasource/capability/relate-operations.js +325 -0
  124. package/lib/datasource/capability/relate-validate.js +219 -0
  125. package/lib/datasource/capability/remove-operations.js +275 -0
  126. package/lib/datasource/capability/run-capability-copy.js +152 -0
  127. package/lib/datasource/capability/run-capability-diff.js +135 -0
  128. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  129. package/lib/datasource/capability/run-capability-edit.js +377 -0
  130. package/lib/datasource/capability/run-capability-relate.js +193 -0
  131. package/lib/datasource/capability/run-capability-remove.js +105 -0
  132. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  133. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  134. package/lib/datasource/list.js +136 -23
  135. package/lib/datasource/log-viewer.js +2 -4
  136. package/lib/datasource/unified-validation-run.js +51 -16
  137. package/lib/datasource/validate.js +53 -1
  138. package/lib/deployment/deploy-poll-ui.js +60 -0
  139. package/lib/deployment/deployer-status.js +29 -3
  140. package/lib/deployment/deployer.js +48 -30
  141. package/lib/deployment/environment.js +7 -2
  142. package/lib/deployment/poll-interval.js +72 -0
  143. package/lib/deployment/push.js +11 -9
  144. package/lib/external-system/deploy.js +9 -2
  145. package/lib/external-system/download.js +61 -32
  146. package/lib/external-system/sync-deploy-manifest.js +33 -0
  147. package/lib/infrastructure/index.js +49 -19
  148. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  149. package/lib/internal/node-fs.js +2 -0
  150. package/lib/parameters/infra-kv-discovery.js +29 -4
  151. package/lib/parameters/infra-parameter-catalog.js +6 -3
  152. package/lib/parameters/infra-parameter-validate.js +67 -19
  153. package/lib/resolvers/datasource-resolver.js +53 -0
  154. package/lib/resolvers/dimension-file.js +52 -0
  155. package/lib/resolvers/manifest-resolver.js +133 -0
  156. package/lib/schema/application-schema.json +4 -0
  157. package/lib/schema/external-datasource.schema.json +183 -53
  158. package/lib/schema/external-system.schema.json +23 -10
  159. package/lib/schema/infra.parameter.yaml +26 -1
  160. package/lib/schema/wizard-config.schema.json +1 -1
  161. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  162. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  163. package/lib/utils/app-config-resolver.js +24 -1
  164. package/lib/utils/app-run-containers.js +2 -2
  165. package/lib/utils/applications-config-defaults.js +206 -0
  166. package/lib/utils/auth-config-validator.js +2 -12
  167. package/lib/utils/bash-secret-env.js +59 -0
  168. package/lib/utils/cli-secrets-error-format.js +78 -0
  169. package/lib/utils/cli-test-layout-chalk.js +31 -9
  170. package/lib/utils/cli-utils.js +4 -36
  171. package/lib/utils/compose-generate-docker-compose.js +111 -6
  172. package/lib/utils/compose-generator.js +17 -8
  173. package/lib/utils/controller-url.js +50 -7
  174. package/lib/utils/datasource-test-run-display.js +8 -0
  175. package/lib/utils/dev-hosts-helper.js +3 -2
  176. package/lib/utils/dev-init-ssh-merge.js +2 -1
  177. package/lib/utils/docker-build.js +17 -9
  178. package/lib/utils/docker-reload-mount.js +127 -0
  179. package/lib/utils/env-copy.js +99 -14
  180. package/lib/utils/env-template.js +5 -1
  181. package/lib/utils/external-readme.js +71 -2
  182. package/lib/utils/external-system-local-test-tty.js +3 -2
  183. package/lib/utils/external-system-readiness-core.js +45 -12
  184. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  185. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  186. package/lib/utils/external-system-readiness-display.js +10 -1
  187. package/lib/utils/file-upload.js +40 -3
  188. package/lib/utils/health-check-db-init.js +107 -0
  189. package/lib/utils/health-check-public-warn.js +69 -0
  190. package/lib/utils/health-check-url.js +28 -10
  191. package/lib/utils/health-check.js +139 -107
  192. package/lib/utils/help-builder.js +5 -1
  193. package/lib/utils/image-name.js +34 -7
  194. package/lib/utils/infra-optional-service-flags.js +69 -0
  195. package/lib/utils/installation-log-core.js +282 -0
  196. package/lib/utils/installation-log-record.js +237 -0
  197. package/lib/utils/installation-log.js +123 -0
  198. package/lib/utils/integration-file-backup.js +74 -0
  199. package/lib/utils/log-redaction.js +105 -0
  200. package/lib/utils/manifest-location.js +164 -0
  201. package/lib/utils/manifest-source-emit.js +162 -0
  202. package/lib/utils/mutagen-install.js +30 -3
  203. package/lib/utils/paths.js +308 -76
  204. package/lib/utils/postgres-wipe.js +212 -0
  205. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  206. package/lib/utils/remote-dev-auth.js +21 -5
  207. package/lib/utils/remote-docker-env.js +9 -1
  208. package/lib/utils/remote-secrets-loader.js +49 -4
  209. package/lib/utils/resolve-docker-image-ref.js +9 -3
  210. package/lib/utils/run-cli-flags.js +29 -0
  211. package/lib/utils/secrets-ancestor-paths.js +47 -0
  212. package/lib/utils/secrets-canonical.js +10 -3
  213. package/lib/utils/secrets-helpers.js +17 -10
  214. package/lib/utils/secrets-kv-refs.js +42 -0
  215. package/lib/utils/secrets-kv-scope.js +19 -2
  216. package/lib/utils/secrets-materialize-local.js +134 -0
  217. package/lib/utils/secrets-path.js +26 -13
  218. package/lib/utils/secrets-utils.js +20 -10
  219. package/lib/utils/system-builder-root.js +42 -0
  220. package/lib/utils/url-declarative-public-base.js +80 -12
  221. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  222. package/lib/utils/url-declarative-resolve-build.js +24 -388
  223. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  224. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  225. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  226. package/lib/utils/url-declarative-resolve.js +47 -7
  227. package/lib/utils/url-declarative-runtime-base-path.js +52 -0
  228. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  229. package/lib/utils/urls-local-registry-scan.js +103 -0
  230. package/lib/utils/urls-local-registry.js +158 -76
  231. package/lib/utils/validation-poll-ui.js +81 -0
  232. package/lib/utils/validation-run-poll.js +29 -5
  233. package/lib/utils/with-muted-logger.js +53 -0
  234. package/package.json +3 -1
  235. package/templates/applications/dataplane/application.yaml +5 -1
  236. package/templates/applications/dataplane/rbac.yaml +10 -10
  237. package/templates/applications/keycloak/env.template +8 -6
  238. package/templates/applications/miso-controller/application.yaml +9 -0
  239. package/templates/applications/miso-controller/env.template +27 -29
  240. package/templates/applications/miso-controller/rbac.yaml +9 -9
  241. package/templates/external-system/README.md.hbs +83 -123
  242. package/.npmrc.token +0 -1
  243. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  244. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  245. package/.nyc_output/processinfo/index.json +0 -1
  246. package/lib/api/service-users.api.js +0 -150
  247. package/lib/api/types/service-users.types.js +0 -65
  248. package/lib/cli/setup-service-user.js +0 -187
  249. package/lib/commands/service-user.js +0 -429
@@ -1,9 +1,14 @@
1
1
  /**
2
- * urls.local.yaml beside effective config.yaml (same directory as secrets.local.yaml).
3
- * When AIFABRIX_HOME is POSIX $HOME but config lives in $HOME/.aifabrix/, the registry
4
- * is $HOME/.aifabrix/urls.local.yaml (not $HOME/urls.local.yaml).
2
+ * Primary `urls.local.yaml` beside `config.yaml` (same directory as {@link module:lib/utils/paths.getConfigDirForPaths}),
3
+ * aligned with `secrets.local.yaml`. When missing, {@link readUrlsLocalRegistrySync} may read a legacy
4
+ * file under {@link module:lib/utils/paths.getAifabrixHome} (older releases when `aifabrix-home` was `$HOME`).
5
5
  *
6
- * @fileoverview Read/write registry; scan each builder app folder for application.yaml
6
+ * Per-app keys (see {@link mergeDocIntoRegistry}): `appKey-port`, `appKey-pattern`, `appKey-containerPort`,
7
+ * optional `appKey-internalDockerUseOriginOnly` (boolean) — overrides `frontDoorRouting.internalDockerUseOriginOnly`
8
+ * for declarative `url://` resolution when set (e.g. Keycloak internal URL without `/auth` when true).
9
+ *
10
+ * @fileoverview Read/write registry; scan builder/package dirs in canonical order (plan 141 P3).
11
+ * Duplicate `app.key`: last scan directory wins — no cross-root mtime merge or `AIFABRIX_BUILDER_DIR` ordering.
7
12
  * @author AI Fabrix Team
8
13
  * @version 1.0.0
9
14
  */
@@ -15,16 +20,17 @@ const path = require('path');
15
20
  const yaml = require('js-yaml');
16
21
  const { DECLARATIVE_URL_INFRA_DEFAULTS } = require('./infra-env-defaults');
17
22
  const pathsUtil = require('./paths');
23
+ const { collectLatestApplicationYamlEntriesPerApp } = require('./urls-local-registry-scan');
18
24
 
19
25
  /**
20
- * @returns {string} Absolute path to urls.local.yaml (primary; beside config.yaml)
26
+ * @returns {string} Absolute path to urls.local.yaml (beside config.yaml)
21
27
  */
22
28
  function getUrlsLocalYamlPath() {
23
29
  return path.join(pathsUtil.getConfigDirForPaths(), 'urls.local.yaml');
24
30
  }
25
31
 
26
- /** @returns {string} Legacy path when registry was stored under getAifabrixHome() only */
27
- function getLegacyUrlsLocalYamlPath() {
32
+ /** @returns {string} Legacy primary path from older CLI (under resolved AI Fabrix home) */
33
+ function getLegacyUrlsLocalYamlPathAtAifabrixHome() {
28
34
  return path.join(pathsUtil.getAifabrixHome(), 'urls.local.yaml');
29
35
  }
30
36
 
@@ -45,8 +51,8 @@ function readUrlsLocalRegistrySync() {
45
51
  if (fsRealSync.existsSync(primary)) {
46
52
  return loadRegistryYamlFile(primary);
47
53
  }
48
- const legacy = getLegacyUrlsLocalYamlPath();
49
- if (legacy !== primary && fsRealSync.existsSync(legacy)) {
54
+ const legacy = getLegacyUrlsLocalYamlPathAtAifabrixHome();
55
+ if (path.resolve(legacy) !== path.resolve(primary) && fsRealSync.existsSync(legacy)) {
50
56
  return loadRegistryYamlFile(legacy);
51
57
  }
52
58
  return {};
@@ -91,22 +97,6 @@ function writeMergedRegistry(merged) {
91
97
  return merged;
92
98
  }
93
99
 
94
- /**
95
- * @param {string} cfgPath
96
- * @returns {object|null}
97
- */
98
- function tryLoadApplicationYaml(cfgPath) {
99
- if (!fsRealSync.existsSync(cfgPath)) {
100
- return null;
101
- }
102
- try {
103
- const doc = yaml.load(fsRealSync.readFileSync(cfgPath, 'utf8'));
104
- return doc && typeof doc === 'object' ? doc : null;
105
- } catch {
106
- return null;
107
- }
108
- }
109
-
110
100
  /**
111
101
  * @param {object} doc
112
102
  * @returns {number|null}
@@ -153,102 +143,193 @@ function mergeDocIntoRegistry(merged, doc, folderName) {
153
143
  } else {
154
144
  delete merged[ckey];
155
145
  }
146
+
147
+ mergeInternalDockerOriginOnlyRegistryKeyFromDoc(merged, appKey, doc);
156
148
  }
157
149
 
158
150
  /**
151
+ * When `application.yaml` explicitly sets `frontDoorRouting.internalDockerUseOriginOnly`, mirror it into
152
+ * `urls.local.yaml` on refresh. If the property is absent, leave any existing registry value unchanged
153
+ * (supports hand-edited `appKey-internalDockerUseOriginOnly` without a YAML field).
154
+ *
159
155
  * @param {Object} merged
160
- * @param {string} builderDir
156
+ * @param {string} appKey
157
+ * @param {object} doc
161
158
  */
162
- function mergeBuilderDirIntoRegistry(merged, builderDir) {
163
- if (!builderDir || !fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
159
+ function mergeInternalDockerOriginOnlyRegistryKeyFromDoc(merged, appKey, doc) {
160
+ const key = `${appKey}-internalDockerUseOriginOnly`;
161
+ const fd = doc && doc.frontDoorRouting;
162
+ if (!fd || typeof fd !== 'object') {
164
163
  return;
165
164
  }
166
- for (const ent of fsRealSync.readdirSync(builderDir, { withFileTypes: true })) {
167
- if (!ent.isDirectory()) {
168
- continue;
169
- }
170
- const doc = tryLoadApplicationYaml(path.join(builderDir, ent.name, 'application.yaml'));
171
- if (!doc) {
172
- continue;
173
- }
174
- mergeDocIntoRegistry(merged, doc, ent.name);
165
+ if (!Object.prototype.hasOwnProperty.call(fd, 'internalDockerUseOriginOnly')) {
166
+ return;
167
+ }
168
+ const v = fd.internalDockerUseOriginOnly;
169
+ if (v === true) {
170
+ merged[key] = true;
171
+ } else if (v === false) {
172
+ merged[key] = false;
173
+ } else {
174
+ delete merged[key];
175
175
  }
176
176
  }
177
177
 
178
178
  /**
179
- * True when getBuilderRoot() resolves to the same path as AIFABRIX_BUILDER_DIR (authoritative override).
180
- * @param {string|null} resolvedEffective
181
- * @param {string|null} envResolved
182
- * @returns {boolean}
179
+ * Append a directory to the scan list when it exists, is a directory, and is not already included (resolved).
180
+ * Later entries win duplicate `app.key` merges (see {@link collectLatestApplicationYamlEntriesPerApp}).
181
+ *
182
+ * @param {string[]} scanDirs
183
+ * @param {string|null|undefined} dirPath
184
+ * @returns {void}
183
185
  */
184
- function effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved) {
185
- return Boolean(envResolved && resolvedEffective && resolvedEffective === envResolved);
186
+ function pushUniqueScanDir(scanDirs, dirPath) {
187
+ if (!dirPath || typeof dirPath !== 'string') {
188
+ return;
189
+ }
190
+ let resolved;
191
+ try {
192
+ resolved = path.resolve(dirPath);
193
+ } catch {
194
+ return;
195
+ }
196
+ try {
197
+ if (!fsRealSync.existsSync(resolved) || !fsRealSync.statSync(resolved).isDirectory()) {
198
+ return;
199
+ }
200
+ } catch {
201
+ return;
202
+ }
203
+ for (const existing of scanDirs) {
204
+ try {
205
+ if (path.resolve(existing) === resolved) {
206
+ return;
207
+ }
208
+ } catch {
209
+ // ignore
210
+ }
211
+ }
212
+ scanDirs.push(resolved);
186
213
  }
187
214
 
188
215
  /**
189
- * Builder dirs to scan in order; later merges overwrite registry keys from earlier dirs.
190
- * When `AIFABRIX_BUILDER_DIR` is set and differs from projectRoot/builder, env builder root is merged last.
191
- * Otherwise projectRoot/builder is merged last so explicit refresh roots override getBuilderRoot().
192
- *
193
- * @param {string} root - Resolved project root passed to refresh
194
- * @param {string|null} effectiveBuilderDir - pathsUtil.getBuilderRoot()
195
216
  * @returns {string[]}
196
217
  */
197
- function getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir) {
198
- const legacyBuilderDir = path.join(root, 'builder');
199
- let resolvedLegacy;
200
- let resolvedEffective;
218
+ function getPackageBuilderLikeDirsForRegistryScan() {
219
+ const out = [];
201
220
  try {
202
- resolvedLegacy = path.resolve(legacyBuilderDir);
203
- resolvedEffective = effectiveBuilderDir ? path.resolve(effectiveBuilderDir) : null;
221
+ out.push(path.join(pathsUtil.getIntegrationBuilderBaseDir(), 'packages'));
204
222
  } catch {
205
- return [legacyBuilderDir];
223
+ // ignore
206
224
  }
207
- const envRaw = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
208
- const envResolved = envRaw ? path.resolve(envRaw) : null;
209
- // Only treat env as authoritative when getBuilderRoot() is actually that path. Otherwise a stray
210
- // AIFABRIX_BUILDER_DIR on CI (or Jest mocking getBuilderRoot to a temp dir) must not force
211
- // [legacy, effective] — that order lets the real checkout builder overwrite the mocked root last.
212
- const effectiveMatchesEnvVar = effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved);
225
+ try {
226
+ const pr = pathsUtil.getProjectRoot();
227
+ if (pr) {
228
+ out.push(path.join(pr, 'packages'));
229
+ }
230
+ } catch {
231
+ // ignore
232
+ }
233
+ return out;
234
+ }
213
235
 
214
- if (effectiveBuilderDir && resolvedEffective && resolvedEffective === resolvedLegacy) {
215
- return [legacyBuilderDir];
236
+ /**
237
+ * Canonical builder/package scan roots (low → high priority). Plan 141 P3: fixed order replaces
238
+ * mtime merge and `AIFABRIX_BUILDER_DIR` scan ordering; `findProjectRootFromCwd` builder is last when enabled.
239
+ *
240
+ * @param {string|null} root
241
+ * @param {string|null} effectiveBuilderDir
242
+ * @param {{ excludeCwdBuilderScan?: boolean }} opts
243
+ * @returns {string[]}
244
+ */
245
+ function buildCanonicalRegistryScanDirs(root, effectiveBuilderDir, opts = {}) {
246
+ const scanDirs = [];
247
+ pushUniqueScanDir(scanDirs, pathsUtil.getSystemBuilderRoot());
248
+ pushUniqueScanDir(scanDirs, effectiveBuilderDir);
249
+ if (root) {
250
+ pushUniqueScanDir(scanDirs, path.join(root, 'builder'));
216
251
  }
217
- if (effectiveMatchesEnvVar && effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
218
- return [legacyBuilderDir, effectiveBuilderDir];
252
+ for (const pkg of getPackageBuilderLikeDirsForRegistryScan()) {
253
+ pushUniqueScanDir(scanDirs, pkg);
219
254
  }
220
- if (effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
221
- return [effectiveBuilderDir, legacyBuilderDir];
255
+ if (!opts.excludeCwdBuilderScan) {
256
+ let cwdRoot = null;
257
+ try {
258
+ cwdRoot = pathsUtil.findProjectRootFromCwd();
259
+ } catch {
260
+ cwdRoot = null;
261
+ }
262
+ if (cwdRoot && typeof cwdRoot === 'string') {
263
+ pushUniqueScanDir(scanDirs, path.join(cwdRoot, 'builder'));
264
+ }
222
265
  }
223
- return [legacyBuilderDir];
266
+ return scanDirs;
224
267
  }
225
268
 
226
269
  /**
227
270
  * Merge scan results into registry (does not remove stale keys).
228
271
  * @param {string|null} projectRoot - getProjectRoot() or null (same semantics as projectRoot || getProjectRoot())
272
+ * @param {{ excludeCwdBuilderScan?: boolean }} [opts] - When `excludeCwdBuilderScan` is true, omit cwd
273
+ * checkout `builder/` (tests / `ctx.projectRoot` isolation — avoids merging ambient repo `builder/`).
229
274
  * @returns {Object} Updated registry
230
275
  */
231
- function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
276
+ function refreshUrlsLocalRegistryFromBuilder(projectRoot, opts) {
277
+ const o = opts && typeof opts === 'object' ? opts : {};
232
278
  const root = projectRoot || pathsUtil.getProjectRoot();
233
279
  const merged = { ...readUrlsLocalRegistrySync() };
234
280
  if (!root) {
235
281
  return writeMergedRegistry(merged);
236
282
  }
237
- // Published npm tarball omits builder/ under the package root (.npmignore). Global installs must
238
- // still refresh from the real builder tree (AIFABRIX_BUILDER_DIR or integration base + builder).
283
+ // Published npm tarball omits builder/ under the package root (.npmignore). Global installs still
284
+ // resolve materialized apps via getBuilderRoot() (same parent as getSystemBuilderRoot when aligned).
239
285
  let effectiveBuilderDir = null;
240
286
  try {
241
287
  effectiveBuilderDir = pathsUtil.getBuilderRoot();
242
288
  } catch {
243
289
  effectiveBuilderDir = null;
244
290
  }
245
- const builderDirs = getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir);
246
- for (const builderDir of builderDirs) {
247
- mergeBuilderDirIntoRegistry(merged, builderDir);
291
+ const scanDirs = buildCanonicalRegistryScanDirs(root, effectiveBuilderDir, o);
292
+ const entries = collectLatestApplicationYamlEntriesPerApp(scanDirs);
293
+ for (const { folderName, doc } of entries) {
294
+ mergeDocIntoRegistry(merged, doc, folderName);
248
295
  }
249
296
  return writeMergedRegistry(merged);
250
297
  }
251
298
 
299
+ const REGISTRY_BOOL_TRUE = new Set([true, 'true', 'yes', 'on', 1, '1']);
300
+ const REGISTRY_BOOL_FALSE = new Set([false, 'false', 'no', 'off', 0, '0']);
301
+
302
+ /**
303
+ * @param {unknown} raw
304
+ * @returns {boolean|undefined}
305
+ */
306
+ function coerceRegistryBool(raw) {
307
+ if (REGISTRY_BOOL_TRUE.has(raw)) {
308
+ return true;
309
+ }
310
+ if (REGISTRY_BOOL_FALSE.has(raw)) {
311
+ return false;
312
+ }
313
+ return undefined;
314
+ }
315
+
316
+ /**
317
+ * Optional `appKey-internalDockerUseOriginOnly` in `urls.local.yaml` (boolean or common string coercions).
318
+ * When set, declarative URL resolution uses this value instead of (or in addition to) `application.yaml`.
319
+ *
320
+ * @param {string} appKey
321
+ * @param {Object|null|undefined} registry
322
+ * @returns {boolean|undefined} undefined when the key is absent or not coercible to boolean
323
+ */
324
+ function readRegistryInternalDockerUseOriginOnly(appKey, registry) {
325
+ const r = registry && typeof registry === 'object' ? registry : {};
326
+ const key = `${appKey}-internalDockerUseOriginOnly`;
327
+ if (!Object.prototype.hasOwnProperty.call(r, key)) {
328
+ return undefined;
329
+ }
330
+ return coerceRegistryBool(r[key]);
331
+ }
332
+
252
333
  /**
253
334
  * @param {string} appKey
254
335
  * @param {Object} registry
@@ -282,5 +363,6 @@ module.exports = {
282
363
  writeUrlsLocalRegistrySync,
283
364
  refreshUrlsLocalRegistryFromBuilder,
284
365
  normalizePatternForUrl,
285
- getRegistryEntryForApp
366
+ getRegistryEntryForApp,
367
+ readRegistryInternalDockerUseOriginOnly
286
368
  };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * TTY ora spinner + throttled non-TTY lines for unified validation run polling (aligned with deploy-poll-ui).
3
+ *
4
+ * @fileoverview Validation run poll presentation helpers
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+
14
+ function shouldUseValidationPollSpinner() {
15
+ return Boolean(process && process.stdout && process.stdout.isTTY);
16
+ }
17
+
18
+ /**
19
+ * Single-line ora text / log line for validation polling.
20
+ *
21
+ * @param {Object|null|undefined} envelope - Latest GET envelope (optional before first poll tick)
22
+ * @param {number} attemptIndex - Zero-based poll index after initial POST
23
+ * @param {number} deadlineMs - Wall-clock deadline (Date.now() + remaining budget)
24
+ * @returns {string}
25
+ */
26
+ function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs) {
27
+ const remainingSec = Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1000));
28
+ if (!envelope || typeof envelope !== 'object') {
29
+ return `Waiting for validation run… starting (budget ~${remainingSec}s)`;
30
+ }
31
+ const c =
32
+ envelope.reportCompleteness !== undefined && envelope.reportCompleteness !== null
33
+ ? String(envelope.reportCompleteness)
34
+ : '?';
35
+ const st =
36
+ envelope.status !== undefined && envelope.status !== null ? String(envelope.status) : '?';
37
+ return `Waiting for validation run… completeness=${c} status=${st} (poll ${attemptIndex + 1} · ~${remainingSec}s left)`;
38
+ }
39
+
40
+ const NON_TTY_THROTTLE_MS = 5000;
41
+
42
+ /**
43
+ * @param {number} deadlineMs - Absolute deadline for poll budget display
44
+ * @returns {{ usesSpinner: boolean, onPollProgress: Function, finish: () => void }}
45
+ */
46
+ function createValidationPollHandlers(deadlineMs) {
47
+ if (shouldUseValidationPollSpinner()) {
48
+ const ora = require('ora');
49
+ const spinner = ora({
50
+ text: buildValidationPollSpinnerText(null, 0, deadlineMs),
51
+ spinner: 'dots'
52
+ }).start();
53
+ return {
54
+ usesSpinner: true,
55
+ onPollProgress: (envelope, attemptIndex, _meta) => {
56
+ spinner.text = buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs);
57
+ },
58
+ finish: () => {
59
+ spinner.stop();
60
+ }
61
+ };
62
+ }
63
+
64
+ let lastNonTtyLogAt = 0;
65
+ return {
66
+ usesSpinner: false,
67
+ onPollProgress: (envelope, attemptIndex, _meta) => {
68
+ const now = Date.now();
69
+ if (now - lastNonTtyLogAt < NON_TTY_THROTTLE_MS) return;
70
+ lastNonTtyLogAt = now;
71
+ logger.log(chalk.gray(buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs)));
72
+ },
73
+ finish: () => {}
74
+ };
75
+ }
76
+
77
+ module.exports = {
78
+ createValidationPollHandlers,
79
+ shouldUseValidationPollSpinner,
80
+ buildValidationPollSpinnerText
81
+ };
@@ -34,8 +34,8 @@ function sleep(ms) {
34
34
  return new Promise(resolve => setTimeout(resolve, ms));
35
35
  }
36
36
 
37
- function maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef) {
38
- if (!verbosePoll || !envelope || typeof envelope !== 'object') return;
37
+ function maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef, skipBecauseUi) {
38
+ if (skipBecauseUi || !verbosePoll || !envelope || typeof envelope !== 'object') return;
39
39
  const now = Date.now();
40
40
  if (now - lastProgressLogAtRef[0] < 5000) return;
41
41
  lastProgressLogAtRef[0] = now;
@@ -57,6 +57,21 @@ function isTerminalReportCompleteness(envelope) {
57
57
  return envelope.reportCompleteness === 'full';
58
58
  }
59
59
 
60
+ function emitPollProgressLine(
61
+ envelope,
62
+ verbosePoll,
63
+ lastProgressLogAtRef,
64
+ onPollProgress,
65
+ attempt,
66
+ deadline
67
+ ) {
68
+ const hasPollUi = typeof onPollProgress === 'function';
69
+ maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef, hasPollUi);
70
+ if (hasPollUi) {
71
+ onPollProgress(envelope, attempt, { deadlineMs: deadline });
72
+ }
73
+ }
74
+
60
75
  /**
61
76
  * Poll until reportCompleteness === 'full' or budget exhausted.
62
77
  * @async
@@ -66,7 +81,8 @@ function isTerminalReportCompleteness(envelope) {
66
81
  * @param {string} opts.testRunId
67
82
  * @param {number} opts.budgetMs - Remaining wall-clock budget for polls only (POST excluded)
68
83
  * @param {typeof getValidationRunWithTransportRetry} [opts.fetchRun] - Inject for tests (default: GET with transport retry)
69
- * @param {boolean} [opts.verbosePoll] - Log throttled progress (plan §3.13)
84
+ * @param {boolean} [opts.verbosePoll] - Log throttled progress (plan §3.13); skipped when `onPollProgress` is set
85
+ * @param {Function|null} [opts.onPollProgress] - `(envelope, attemptIndex, { deadlineMs })` each non-terminal poll
70
86
  * @param {number} [opts.pollRequestTimeoutMs] - Per-GET HTTP timeout (match validation aggregate budget)
71
87
  * @returns {Promise<{ envelope: Object|null, timedOut: boolean, lastApiResult: Object|null }>}
72
88
  */
@@ -78,7 +94,8 @@ async function pollValidationRunUntilComplete(opts) {
78
94
  budgetMs,
79
95
  fetchRun = getValidationRunWithTransportRetry,
80
96
  verbosePoll = false,
81
- pollRequestTimeoutMs
97
+ pollRequestTimeoutMs,
98
+ onPollProgress = null
82
99
  } = opts;
83
100
  const pollTransportOpts =
84
101
  Number.isFinite(pollRequestTimeoutMs) && pollRequestTimeoutMs > 0
@@ -100,7 +117,14 @@ async function pollValidationRunUntilComplete(opts) {
100
117
  return { envelope, timedOut: false, lastApiResult };
101
118
  }
102
119
 
103
- maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef);
120
+ emitPollProgressLine(
121
+ envelope,
122
+ verbosePoll,
123
+ lastProgressLogAtRef,
124
+ onPollProgress,
125
+ attempt,
126
+ deadline
127
+ );
104
128
 
105
129
  const delay = Math.min(nextPollDelayMs(attempt), Math.max(0, deadline - Date.now()));
106
130
  attempt += 1;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @fileoverview Temporarily mute logger.log output for guided UX flows.
3
+ *
4
+ * Used by guided installer-style commands (e.g. up-platform default mode) to avoid
5
+ * streaming orchestration mechanics while preserving errors and warnings.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const logger = require('./logger');
11
+
12
+ /**
13
+ * Run a function while muting logger.log/info.
14
+ *
15
+ * - logger.error and logger.warn are preserved.
16
+ * - An optional allowlist can let specific messages through (rare).
17
+ *
18
+ * @template T
19
+ * @param {() => Promise<T>} fn
20
+ * @param {{ allow?: ((...args: any[]) => boolean) }} [opts]
21
+ * @returns {Promise<T>}
22
+ */
23
+ async function withMutedLogger(fn, opts = {}) {
24
+ const original = {
25
+ log: logger.log,
26
+ info: logger.info
27
+ };
28
+
29
+ const allow = typeof opts.allow === 'function' ? opts.allow : null;
30
+
31
+ const muted = (...args) => {
32
+ try {
33
+ if (allow && allow(...args)) {
34
+ return original.log(...args);
35
+ }
36
+ } catch {
37
+ // ignore allow errors; treat as muted
38
+ }
39
+ return undefined;
40
+ };
41
+
42
+ logger.log = muted;
43
+ logger.info = muted;
44
+ try {
45
+ return await fn();
46
+ } finally {
47
+ logger.log = original.log;
48
+ logger.info = original.info;
49
+ }
50
+ }
51
+
52
+ module.exports = { withMutedLogger };
53
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.44.5",
3
+ "version": "2.45.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -36,6 +36,8 @@
36
36
  "precommit": "npm run lint:fix && npm run test",
37
37
  "install:local": "node scripts/install-local.js && which aifabrix && which af",
38
38
  "uninstall:local": "node scripts/install-local.js uninstall && which aifabrix && which af",
39
+ "install:global": "npm install -g . --force && which aifabrix && which af && aifabrix --version",
40
+ "uninstall:global": "npm uninstall -g @aifabrix/builder",
39
41
  "diagnose:cli": "node scripts/diagnose-cli.js",
40
42
  "check:schema-sync": "node scripts/check-datasource-test-run-schema-sync.js",
41
43
  "check:flags": "jest tests/lib/schema/flag-map-validation-run.test.js --runInBand --config jest.config.default.js"
@@ -8,7 +8,7 @@ app:
8
8
  version: 1.9.5
9
9
 
10
10
  # Image Configuration
11
- # Set tag to match your build (e.g. aifabrix build dataplane -t v1.0.0 then tag: v1.0.0)
11
+ # Set tag to match your build (e.g. aifabrix build dataplane -t 1.0.0 then tag: 1.0.0)
12
12
  # Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
13
13
  image:
14
14
  name: aifabrix/dataplane
@@ -26,6 +26,8 @@ environmentScopedResources: true
26
26
  frontDoorRouting:
27
27
  pattern: /data/*
28
28
  enabled: true
29
+ # Docker url://internal full URLs: http://dataplane:PORT only (ingress /data is not on in-container routes).
30
+ internalDockerUseOriginOnly: false
29
31
  host: ${DEV_USERNAME}.${REMOTE_HOST}
30
32
  tls: ${TLS_ENABLED}
31
33
 
@@ -63,6 +65,8 @@ build:
63
65
  envOutputPath: ../../.env # Copy to repo root for local dev
64
66
  language: python # Runtime language for template selection (typescript or python)
65
67
  reloadStart: uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-3001} --reload # PORT set from port above at run time; default 3001 must match port
68
+ # Pulled-image local compose (no bind-mount): drives `command` in generated docker-compose. Override for non-standard images (e.g. stub ACR tags).
69
+ imageRun: exec python -m uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-3001}
66
70
 
67
71
  # =============================================================================
68
72
  # Portal Input Configuration (Deployment Wizard)
@@ -232,19 +232,19 @@ permissions:
232
232
  roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-compliance-admin", "aifabrix-observer"]
233
233
  description: "Read group information"
234
234
 
235
- # OpenAPI file management
236
- - name: "openapi-file:read"
235
+ # OpenAPI / MCP spec bundle (mounted specs under /api/v1/specs)
236
+ - name: "spec:read"
237
237
  roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-deployment-admin", "aifabrix-compliance-admin", "aifabrix-developer", "aifabrix-observer"]
238
- description: "Read OpenAPI files"
239
-
240
- - name: "openapi-file:update"
238
+ description: "Read OpenAPI/MCP spec bundles"
239
+
240
+ - name: "spec:update"
241
241
  roles: ["aifabrix-platform-admin", "aifabrix-developer"]
242
- description: "Update OpenAPI files"
243
-
244
- - name: "openapi-file:delete"
242
+ description: "Update OpenAPI/MCP spec bundles (uploaded or user-owned)"
243
+
244
+ - name: "spec:delete"
245
245
  roles: ["aifabrix-platform-admin", "aifabrix-developer"]
246
- description: "Delete OpenAPI files"
247
-
246
+ description: "Delete OpenAPI/MCP spec bundles (user-owned only; internal specs are not deletable via API)"
247
+
248
248
  # External data source write operations
249
249
  - name: "external-data-source:write"
250
250
  roles: ["aifabrix-platform-admin", "aifabrix-developer"]
@@ -23,15 +23,17 @@ KC_HTTP_RELATIVE_PATH=url://vdir-public
23
23
  # must use the PUBLIC URL as issuer in all tokens so they match what the
24
24
  # controller expects (KEYCLOAK_SERVER_URL).
25
25
  # - Users log in via http://localhost:${KEYCLOAK_PUBLIC_PORT} (browser/CLI)
26
- # - Server calls Keycloak at http://keycloak:8080 for token exchange and refresh
26
+ # - Server calls Keycloak at url://keycloak-internal for token exchange and refresh
27
27
  # - Controller sends Host: localhost:${KEYCLOAK_PUBLIC_PORT} so Keycloak validates issuer
28
28
  # against public URL (requires KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true)
29
29
  # When KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true, hostname must be a full URL.
30
- # Use host-only origin (no /auth); KC_HTTP_RELATIVE_PATH carries the front-door path (url://vdir-public).
31
- # Hostname v2: port belongs in KC_HOSTNAME (url://host-public expands to e.g. http://localhost:8182 or
32
- # https://devNN.example.com). Do not set KC_HOSTNAME_PORT (deprecated v1; triggers Quarkus warnings).
33
- # KEYCLOAK_PUBLIC_PORT = application.yaml `port` (host-published) + dev×100; used by other apps / docs.
34
- KC_HOSTNAME=url://host-public
30
+ # KC_HTTP_RELATIVE_PATH carries the runtime base path (url://vdir-public), and application.yaml
31
+ # frontDoorRouting exposes the same path for internal server-to-server URLs when enabled.
32
+ # Hostname v2: use a full public URL so Keycloak generates redirects that preserve the /auth base path.
33
+ # `url://public` expands to the full front-door URL (including /auth when Traefik + frontDoorRouting.enabled are on).
34
+ # NOTE: Prefer KC_HOSTNAME (not KC_HOSTNAME_URL). hostname-url triggers legacy hostname v1 warnings and, depending on
35
+ # runtime, may not be treated as an active hostname for hostname-backchannel-dynamic.
36
+ KC_HOSTNAME=url://public
35
37
  # nginx / Traefik send X-Forwarded-*; required when using an edge proxy (Keycloak 26+).
36
38
  KC_PROXY_HEADERS=xforwarded
37
39
  # Required for Host header to work: Keycloak resolves backchannel URL from request headers
@@ -7,6 +7,8 @@ app:
7
7
  version: '1.9.5'
8
8
 
9
9
  # Image Configuration
10
+ # Set tag to match your build (e.g. aifabrix build miso-controller -t 1.0.0 then tag: 1.0.0)
11
+ # Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
10
12
  image:
11
13
  name: aifabrix/miso-controller
12
14
  tag: latest
@@ -21,6 +23,8 @@ port: 3000
21
23
  frontDoorRouting:
22
24
  pattern: /miso/*
23
25
  enabled: true
26
+ # Docker url://internal full URLs: http://miso-controller:PORT only (ingress /miso is not on in-container routes).
27
+ internalDockerUseOriginOnly: false
24
28
  host: ${DEV_USERNAME}.${REMOTE_HOST}
25
29
  tls: ${TLS_ENABLED}
26
30
 
@@ -57,6 +61,11 @@ build:
57
61
  language: typescript # Runtime language for template selection (typescript or python)
58
62
  reloadStart: pnpm run start:reload # When running with --reload
59
63
 
64
+ # Repository Configuration (pipeline validate: REPOSITORY_URL_MISMATCH)
65
+ repository:
66
+ enabled: true
67
+ repositoryUrl: https://github.com/aifabrix/aifabrix-miso
68
+
60
69
  # =============================================================================
61
70
  # Portal Input Configuration (Deployment Wizard)
62
71
  # =============================================================================