@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Primary `urls.local.yaml` under {@link module:lib/utils/paths.getAifabrixHome} (same as
3
- * `secrets.local.yaml`). When that file is missing, falls back to the config directory for migration.
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`).
4
5
  *
5
- * @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.
6
12
  * @author AI Fabrix Team
7
13
  * @version 1.0.0
8
14
  */
@@ -14,17 +20,18 @@ const path = require('path');
14
20
  const yaml = require('js-yaml');
15
21
  const { DECLARATIVE_URL_INFRA_DEFAULTS } = require('./infra-env-defaults');
16
22
  const pathsUtil = require('./paths');
23
+ const { collectLatestApplicationYamlEntriesPerApp } = require('./urls-local-registry-scan');
17
24
 
18
25
  /**
19
- * @returns {string} Absolute path to urls.local.yaml (primary; under resolved AI Fabrix home)
26
+ * @returns {string} Absolute path to urls.local.yaml (beside config.yaml)
20
27
  */
21
28
  function getUrlsLocalYamlPath() {
22
- return path.join(pathsUtil.getAifabrixHome(), 'urls.local.yaml');
29
+ return path.join(pathsUtil.getConfigDirForPaths(), 'urls.local.yaml');
23
30
  }
24
31
 
25
- /** @returns {string} Path beside config.yaml (migration / older layouts) */
26
- function getConfigDirUrlsLocalYamlPath() {
27
- return path.join(pathsUtil.getConfigDirForPaths(), 'urls.local.yaml');
32
+ /** @returns {string} Legacy primary path from older CLI (under resolved AI Fabrix home) */
33
+ function getLegacyUrlsLocalYamlPathAtAifabrixHome() {
34
+ return path.join(pathsUtil.getAifabrixHome(), 'urls.local.yaml');
28
35
  }
29
36
 
30
37
  function loadRegistryYamlFile(filePath) {
@@ -44,9 +51,9 @@ function readUrlsLocalRegistrySync() {
44
51
  if (fsRealSync.existsSync(primary)) {
45
52
  return loadRegistryYamlFile(primary);
46
53
  }
47
- const atConfig = getConfigDirUrlsLocalYamlPath();
48
- if (path.resolve(atConfig) !== path.resolve(primary) && fsRealSync.existsSync(atConfig)) {
49
- return loadRegistryYamlFile(atConfig);
54
+ const legacy = getLegacyUrlsLocalYamlPathAtAifabrixHome();
55
+ if (path.resolve(legacy) !== path.resolve(primary) && fsRealSync.existsSync(legacy)) {
56
+ return loadRegistryYamlFile(legacy);
50
57
  }
51
58
  return {};
52
59
  }
@@ -90,22 +97,6 @@ function writeMergedRegistry(merged) {
90
97
  return merged;
91
98
  }
92
99
 
93
- /**
94
- * @param {string} cfgPath
95
- * @returns {object|null}
96
- */
97
- function tryLoadApplicationYaml(cfgPath) {
98
- if (!fsRealSync.existsSync(cfgPath)) {
99
- return null;
100
- }
101
- try {
102
- const doc = yaml.load(fsRealSync.readFileSync(cfgPath, 'utf8'));
103
- return doc && typeof doc === 'object' ? doc : null;
104
- } catch {
105
- return null;
106
- }
107
- }
108
-
109
100
  /**
110
101
  * @param {object} doc
111
102
  * @returns {number|null}
@@ -152,114 +143,193 @@ function mergeDocIntoRegistry(merged, doc, folderName) {
152
143
  } else {
153
144
  delete merged[ckey];
154
145
  }
146
+
147
+ mergeInternalDockerOriginOnlyRegistryKeyFromDoc(merged, appKey, doc);
155
148
  }
156
149
 
157
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
+ *
158
155
  * @param {Object} merged
159
- * @param {string} builderDir
156
+ * @param {string} appKey
157
+ * @param {object} doc
160
158
  */
161
- function mergeBuilderDirIntoRegistry(merged, builderDir) {
162
- 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') {
163
163
  return;
164
164
  }
165
- for (const ent of fsRealSync.readdirSync(builderDir, { withFileTypes: true })) {
166
- if (!ent.isDirectory()) {
167
- continue;
168
- }
169
- const doc = tryLoadApplicationYaml(path.join(builderDir, ent.name, 'application.yaml'));
170
- if (!doc) {
171
- continue;
172
- }
173
- 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];
174
175
  }
175
176
  }
176
177
 
177
178
  /**
178
- * True when getBuilderRoot() resolves to the same path as AIFABRIX_BUILDER_DIR (authoritative override).
179
- * @param {string|null} resolvedEffective
180
- * @param {string|null} envResolved
181
- * @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}
182
185
  */
183
- function effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved) {
184
- 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);
185
213
  }
186
214
 
187
215
  /**
188
- * Builder dirs to scan in order; later merges overwrite registry keys from earlier dirs.
189
- * When `AIFABRIX_BUILDER_DIR` is set and differs from projectRoot/builder, env builder root is merged last.
190
- * Otherwise projectRoot/builder is merged last so explicit refresh roots override getBuilderRoot().
191
- *
192
- * @param {string} root - Resolved project root passed to refresh
193
- * @param {string|null} effectiveBuilderDir - pathsUtil.getBuilderRoot()
194
216
  * @returns {string[]}
195
217
  */
196
- function getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir) {
197
- const legacyBuilderDir = path.join(root, 'builder');
198
- let resolvedLegacy;
199
- let resolvedEffective;
218
+ function getPackageBuilderLikeDirsForRegistryScan() {
219
+ const out = [];
200
220
  try {
201
- resolvedLegacy = path.resolve(legacyBuilderDir);
202
- resolvedEffective = effectiveBuilderDir ? path.resolve(effectiveBuilderDir) : null;
221
+ out.push(path.join(pathsUtil.getIntegrationBuilderBaseDir(), 'packages'));
203
222
  } catch {
204
- return [legacyBuilderDir];
223
+ // ignore
224
+ }
225
+ try {
226
+ const pr = pathsUtil.getProjectRoot();
227
+ if (pr) {
228
+ out.push(path.join(pr, 'packages'));
229
+ }
230
+ } catch {
231
+ // ignore
205
232
  }
206
- const envRaw = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
207
- const envResolved = envRaw ? path.resolve(envRaw) : null;
208
- // Only treat env as authoritative when getBuilderRoot() is actually that path. Otherwise a stray
209
- // AIFABRIX_BUILDER_DIR on CI (or Jest mocking getBuilderRoot to a temp dir) must not force
210
- // [legacy, effective] — that order lets the real checkout builder overwrite the mocked root last.
211
- const effectiveMatchesEnvVar = effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved);
233
+ return out;
234
+ }
212
235
 
213
- if (effectiveBuilderDir && resolvedEffective && resolvedEffective === resolvedLegacy) {
214
- 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'));
215
251
  }
216
- if (effectiveMatchesEnvVar && effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
217
- return [legacyBuilderDir, effectiveBuilderDir];
252
+ for (const pkg of getPackageBuilderLikeDirsForRegistryScan()) {
253
+ pushUniqueScanDir(scanDirs, pkg);
218
254
  }
219
- if (effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
220
- 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
+ }
221
265
  }
222
- return [legacyBuilderDir];
266
+ return scanDirs;
223
267
  }
224
268
 
225
269
  /**
226
270
  * Merge scan results into registry (does not remove stale keys).
227
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/`).
228
274
  * @returns {Object} Updated registry
229
275
  */
230
- function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
276
+ function refreshUrlsLocalRegistryFromBuilder(projectRoot, opts) {
277
+ const o = opts && typeof opts === 'object' ? opts : {};
231
278
  const root = projectRoot || pathsUtil.getProjectRoot();
232
279
  const merged = { ...readUrlsLocalRegistrySync() };
233
280
  if (!root) {
234
281
  return writeMergedRegistry(merged);
235
282
  }
236
- // Published npm tarball omits builder/ under the package root (.npmignore). Global installs must
237
- // 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).
238
285
  let effectiveBuilderDir = null;
239
286
  try {
240
287
  effectiveBuilderDir = pathsUtil.getBuilderRoot();
241
288
  } catch {
242
289
  effectiveBuilderDir = null;
243
290
  }
244
- let builderDirs = getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir);
245
- try {
246
- const sysRoot = pathsUtil.getSystemBuilderRoot();
247
- const resolvedSys = path.resolve(sysRoot);
248
- if (fsRealSync.existsSync(sysRoot)) {
249
- const resolvedList = builderDirs.map((d) => path.resolve(d));
250
- if (!resolvedList.includes(resolvedSys)) {
251
- builderDirs = [sysRoot, ...builderDirs];
252
- }
253
- }
254
- } catch {
255
- // ignore
256
- }
257
- for (const builderDir of builderDirs) {
258
- 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);
259
295
  }
260
296
  return writeMergedRegistry(merged);
261
297
  }
262
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
+
263
333
  /**
264
334
  * @param {string} appKey
265
335
  * @param {Object} registry
@@ -293,5 +363,6 @@ module.exports = {
293
363
  writeUrlsLocalRegistrySync,
294
364
  refreshUrlsLocalRegistryFromBuilder,
295
365
  normalizePatternForUrl,
296
- getRegistryEntryForApp
366
+ getRegistryEntryForApp,
367
+ readRegistryInternalDockerUseOriginOnly
297
368
  };
@@ -23,10 +23,12 @@ function shouldUseValidationPollSpinner() {
23
23
  * @param {number} deadlineMs - Wall-clock deadline (Date.now() + remaining budget)
24
24
  * @returns {string}
25
25
  */
26
- function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs) {
26
+ function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject) {
27
27
  const remainingSec = Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1000));
28
+ const label =
29
+ subject && String(subject).trim() ? String(subject).trim() : 'validation run';
28
30
  if (!envelope || typeof envelope !== 'object') {
29
- return `Waiting for validation run… starting (budget ~${remainingSec}s)`;
31
+ return `Waiting for ${label}… starting (budget ~${remainingSec}s)`;
30
32
  }
31
33
  const c =
32
34
  envelope.reportCompleteness !== undefined && envelope.reportCompleteness !== null
@@ -34,26 +36,29 @@ function buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs) {
34
36
  : '?';
35
37
  const st =
36
38
  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)`;
39
+ return `Waiting for ${label}… completeness=${c} status=${st} (poll ${attemptIndex + 1} · ~${remainingSec}s left)`;
38
40
  }
39
41
 
40
42
  const NON_TTY_THROTTLE_MS = 5000;
41
43
 
42
44
  /**
43
45
  * @param {number} deadlineMs - Absolute deadline for poll budget display
46
+ * @param {Object} [opts]
47
+ * @param {string} [opts.subject] - Datasource key or label shown in poll text
44
48
  * @returns {{ usesSpinner: boolean, onPollProgress: Function, finish: () => void }}
45
49
  */
46
- function createValidationPollHandlers(deadlineMs) {
50
+ function createValidationPollHandlers(deadlineMs, opts = {}) {
51
+ const subject = opts.subject;
47
52
  if (shouldUseValidationPollSpinner()) {
48
53
  const ora = require('ora');
49
54
  const spinner = ora({
50
- text: buildValidationPollSpinnerText(null, 0, deadlineMs),
55
+ text: buildValidationPollSpinnerText(null, 0, deadlineMs, subject),
51
56
  spinner: 'dots'
52
57
  }).start();
53
58
  return {
54
59
  usesSpinner: true,
55
60
  onPollProgress: (envelope, attemptIndex, _meta) => {
56
- spinner.text = buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs);
61
+ spinner.text = buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject);
57
62
  },
58
63
  finish: () => {
59
64
  spinner.stop();
@@ -68,7 +73,9 @@ function createValidationPollHandlers(deadlineMs) {
68
73
  const now = Date.now();
69
74
  if (now - lastNonTtyLogAt < NON_TTY_THROTTLE_MS) return;
70
75
  lastNonTtyLogAt = now;
71
- logger.log(chalk.gray(buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs)));
76
+ logger.log(
77
+ chalk.gray(buildValidationPollSpinnerText(envelope, attemptIndex, deadlineMs, subject))
78
+ );
72
79
  },
73
80
  finish: () => {}
74
81
  };
@@ -164,36 +164,73 @@ function validateWizardConfigSchema(config) {
164
164
  };
165
165
  }
166
166
 
167
+ /**
168
+ * Candidate bases for relative openapi-file paths (first existing wins).
169
+ * @param {string} configPath - Path to wizard.yaml
170
+ * @returns {string[]}
171
+ */
172
+ function wizardOpenApiFilePathCandidates(filePath, configPath) {
173
+ const raw = String(filePath || '').trim();
174
+ if (!raw) {
175
+ return [];
176
+ }
177
+ if (path.isAbsolute(raw)) {
178
+ return [path.resolve(raw)];
179
+ }
180
+ const configDir = path.dirname(path.resolve(configPath));
181
+ return [...new Set([
182
+ path.resolve(process.cwd(), raw),
183
+ path.resolve(configDir, raw)
184
+ ])];
185
+ }
186
+
187
+ /**
188
+ * Resolve openapi-file path for wizard config (absolute path).
189
+ * Relative paths: process.cwd() first, then wizard.yaml directory.
190
+ * @param {string} filePath
191
+ * @param {string} configPath
192
+ * @returns {Promise<string>}
193
+ */
194
+ async function resolveWizardOpenApiFilePath(filePath, configPath) {
195
+ const candidates = wizardOpenApiFilePathCandidates(filePath, configPath);
196
+ if (candidates.length === 0) {
197
+ return '';
198
+ }
199
+ for (const candidate of candidates) {
200
+ try {
201
+ await fs.access(candidate);
202
+ return candidate;
203
+ } catch (error) {
204
+ if (error.code !== 'ENOENT') {
205
+ throw error;
206
+ }
207
+ }
208
+ }
209
+ return candidates[0];
210
+ }
211
+
167
212
  /**
168
213
  * Validate file path exists (for openapi-file source type)
169
214
  * @function validateFilePath
170
215
  * @param {string} filePath - Path to validate
171
- * @param {string} basePath - Base path for relative paths
172
- * @returns {Promise<Object>} Validation result with valid flag and errors
216
+ * @param {string} configPath - Path to wizard.yaml (relative paths resolve from cwd, then config dir)
217
+ * @returns {Promise<Object>} Validation result with valid flag, errors, and resolvedPath
173
218
  */
174
- async function validateFilePath(filePath, basePath) {
175
- const baseDir = path.resolve(basePath);
176
- const resolvedPath = path.isAbsolute(filePath)
177
- ? path.resolve(filePath)
178
- : path.resolve(baseDir, filePath);
179
- if (!path.isAbsolute(filePath)) {
180
- const baseWithSep = baseDir.endsWith(path.sep) ? baseDir : `${baseDir}${path.sep}`;
181
- if (!resolvedPath.startsWith(baseWithSep)) {
182
- return {
183
- valid: false,
184
- errors: [`OpenAPI file path must be within: ${baseDir}`]
185
- };
186
- }
219
+ async function validateFilePath(filePath, configPath) {
220
+ const resolvedPath = await resolveWizardOpenApiFilePath(filePath, configPath);
221
+ if (!resolvedPath) {
222
+ return { valid: false, errors: ['OpenAPI file path is required'], resolvedPath: '' };
187
223
  }
188
224
  try {
189
225
  await fs.access(resolvedPath);
190
226
  } catch (error) {
191
227
  return {
192
228
  valid: false,
193
- errors: [`OpenAPI file not found: ${resolvedPath}`]
229
+ errors: [`OpenAPI file not found: ${resolvedPath}`],
230
+ resolvedPath
194
231
  };
195
232
  }
196
- return { valid: true, errors: [] };
233
+ return { valid: true, errors: [], resolvedPath };
197
234
  }
198
235
 
199
236
  /**
@@ -229,10 +266,11 @@ function validateSchemaAndResolveEnvVars(config, shouldResolveEnvVars) {
229
266
  async function performSemanticValidations(config, configPath, validateFilePaths) {
230
267
  const errors = [];
231
268
  if (validateFilePaths && config.source?.type === 'openapi-file' && config.source?.filePath) {
232
- const basePath = path.dirname(path.resolve(configPath));
233
- const fileResult = await validateFilePath(config.source.filePath, basePath);
269
+ const fileResult = await validateFilePath(config.source.filePath, configPath);
234
270
  if (!fileResult.valid) {
235
271
  errors.push(...fileResult.errors);
272
+ } else if (fileResult.resolvedPath) {
273
+ config.source.filePath = fileResult.resolvedPath;
236
274
  }
237
275
  }
238
276
  if (config.mode === 'add-datasource' && !config.systemIdOrKey) {
@@ -296,6 +334,7 @@ module.exports = {
296
334
  wizardConfigExists,
297
335
  validateWizardConfig,
298
336
  validateWizardConfigSchema,
337
+ resolveWizardOpenApiFilePath,
299
338
  resolveEnvVar,
300
339
  resolveEnvVarsInObject,
301
340
  formatValidationErrors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.44.6",
3
+ "version": "2.45.5",
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"
@@ -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)
@@ -21,7 +21,7 @@ LOG_FORMAT=json
21
21
  # Production with persistent logs: use /mnt/data/logs/app.log and mount a volume at /mnt/data.
22
22
  LOG_FILE_PATH=/mnt/data/logs/app.log
23
23
  # If true, run without Redis/Celery (single process)
24
- LOCAL_MODE=false
24
+ LOCAL_MODE=true
25
25
 
26
26
  # When API_KEY is set, a matching Bearer token bypasses OAuth2 validation
27
27
  # Same kv:// as miso-controller API_KEY so pipeline Bearer bypass uses one secrets.local entry
@@ -30,6 +30,9 @@ API_KEY=kv://miso-controller-secrets-apiKeyVault
30
30
  # API Configuration
31
31
  API_V1_STR=/api/v1
32
32
  VERSION=1.9.5
33
+ # Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
34
+ # MIN_BUILDER_CLI_VERSION=2.45.0
35
+ MIN_BUILDER_CLI_VERSION=2.45.0
33
36
  # Base URL for the dataplane web server (used for default OAuth2 callback URL when redirectUri is omitted)
34
37
  # url:// resolves after kv://; includes front-door path from application.yaml (e.g. /data)
35
38
  DATAPLANE_WEB_SERVER_URL=url://public
@@ -165,6 +168,15 @@ RBAC_EXPLAIN_MODE_ENABLED=false
165
168
  # validates only (no second DB write). Default false.
166
169
  AUDIT_ASYNC_SHADOW_ENQUEUE=false
167
170
 
171
+ # Policy Library management HTTP API gate. When false (default), management
172
+ # routes are not registered (404): /api/v1/policies/*, /api/v1/external/{key}/policies,
173
+ # /api/v1/users/{id}/policies, /api/v1/groups/{id}/policies, IDE /validate/policy and
174
+ # /simulate/policy-impact/{datasourceKey}. ABAC runtime evaluation is unaffected.
175
+ # Set to true for local dev when iterating on policy CRUD APIs.
176
+ # Policy Library management HTTP APIs (default false → routes not registered, 404).
177
+ # Set true for local dev policy CRUD; pair with VITE_POLICY_MANAGEMENT_ENABLED=true in app-ui.
178
+ POLICY_MANAGEMENT_API_ENABLED=false
179
+
168
180
  # =============================================================================
169
181
  # OBSERVABILITY CONFIGURATION
170
182
  # =============================================================================
@@ -280,6 +280,10 @@ permissions:
280
280
  - name: "record:search"
281
281
  roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-deployment-admin", "aifabrix-compliance-admin", "aifabrix-developer", "aifabrix-observer"]
282
282
  description: "Search records"
283
+
284
+ - name: "governance:evaluate"
285
+ roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-developer"]
286
+ description: "Run governance scenario acceptance (subject impersonation for ABAC proof)"
283
287
 
284
288
  # IDE simulation operations
285
289
  - name: "dataplane:abac-simulate"
@@ -23,6 +23,8 @@ port: 3000
23
23
  frontDoorRouting:
24
24
  pattern: /miso/*
25
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
26
28
  host: ${DEV_USERNAME}.${REMOTE_HOST}
27
29
  tls: ${TLS_ENABLED}
28
30