@aifabrix/builder 2.43.0 → 2.44.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 (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -0,0 +1,458 @@
1
+ /**
2
+ * @fileoverview Load and query infra.parameter.yaml (AJV + JSON Schema)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const fsRealSync = require('../internal/fs-real-sync');
8
+ const path = require('path');
9
+ const yaml = require('js-yaml');
10
+ const Ajv = require('ajv');
11
+
12
+ const DEFAULT_CATALOG_PATH = path.join(__dirname, '..', 'schema', 'infra.parameter.yaml');
13
+ const SCHEMA_PATH = path.join(__dirname, '..', 'schema', 'infra-parameter.schema.json');
14
+
15
+ let cachedCatalog = null;
16
+ let cachedPath = null;
17
+
18
+ /**
19
+ * Format AJV errors for a single message line.
20
+ * @param {object[]} errors - AJV errors array
21
+ * @returns {string}
22
+ */
23
+ function formatAjvErrors(errors) {
24
+ if (!Array.isArray(errors) || errors.length === 0) return 'Validation failed';
25
+ return errors
26
+ .map((e) => `${e.instancePath || '/'} ${e.message}`.trim())
27
+ .join('; ');
28
+ }
29
+
30
+ /**
31
+ * @param {string} resolved - Absolute path to YAML
32
+ * @returns {object}
33
+ */
34
+ function readCatalogYamlDocument(resolved) {
35
+ if (!fsRealSync.existsSync(resolved)) {
36
+ const err = new Error(`infra parameter catalog not found: ${resolved}`);
37
+ err.code = 'ENOENT';
38
+ throw err;
39
+ }
40
+ try {
41
+ return yaml.load(fsRealSync.readFileSync(resolved, 'utf8'));
42
+ } catch (e) {
43
+ const err = new Error(`Invalid infra parameter YAML (${resolved}): ${e.message}`);
44
+ err.cause = e;
45
+ throw err;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * @param {object} doc - Parsed root
51
+ */
52
+ function assertEachParameterHasKeyXorPattern(doc) {
53
+ const list = doc.parameters || [];
54
+ for (let i = 0; i < list.length; i++) {
55
+ const entry = list[i];
56
+ const hasKey = Boolean(entry.key && String(entry.key).length > 0);
57
+ const hasPat = Boolean(entry.keyPattern && String(entry.keyPattern).length > 0);
58
+ if (hasKey === hasPat) {
59
+ throw new Error(`infra.parameter.yaml parameters[${i}]: set exactly one of key or keyPattern`);
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @param {object} doc - Parsed catalog
66
+ */
67
+ function validateCatalogAgainstJsonSchema(doc) {
68
+ const schema = JSON.parse(fsRealSync.readFileSync(SCHEMA_PATH, 'utf8'));
69
+ const ajv = new Ajv({ allErrors: true, strict: false });
70
+ const validate = ajv.compile(schema);
71
+ if (!validate(doc)) {
72
+ throw new Error(`infra.parameter.yaml failed schema: ${formatAjvErrors(validate.errors || [])}`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @param {object} doc - Validated document
78
+ * @returns {{ exact: Map<string, object>, patterns: { regex: RegExp, entry: object }[] }}
79
+ */
80
+ function buildCatalogIndexes(doc) {
81
+ /** @type {Map<string, object>} */
82
+ const exact = new Map();
83
+ /** @type {{ regex: RegExp, entry: object }[]} */
84
+ const patterns = [];
85
+ for (const entry of doc.parameters || []) {
86
+ if (entry.key) {
87
+ exact.set(entry.key, entry);
88
+ } else if (entry.keyPattern) {
89
+ try {
90
+ patterns.push({ regex: new RegExp(entry.keyPattern), entry });
91
+ } catch (reErr) {
92
+ throw new Error(`Invalid keyPattern in catalog: ${entry.keyPattern}: ${reErr.message}`);
93
+ }
94
+ }
95
+ }
96
+ return { exact, patterns };
97
+ }
98
+
99
+ /**
100
+ * @param {object} doc - Root document
101
+ * @param {Map<string, object>} exact - Exact key map
102
+ * @param {{ regex: RegExp, entry: object }[]} patterns - Ordered patterns
103
+ * @returns {object} Catalog API
104
+ */
105
+ /**
106
+ * @param {object} doc - Parsed catalog root
107
+ * @returns {string[]}
108
+ */
109
+ function standardBootstrapKeysFromDoc(doc) {
110
+ const extra = doc && doc.standardUpInfraEnsureKeys;
111
+ if (!Array.isArray(extra)) return [];
112
+ return extra.map((k) => String(k).trim()).filter(Boolean);
113
+ }
114
+
115
+ function createCatalogApi(doc, exact, patterns) {
116
+ function findEntryForKey(key) {
117
+ if (!key || typeof key !== 'string') return null;
118
+ if (exact.has(key)) return exact.get(key);
119
+ for (const { regex, entry } of patterns) {
120
+ if (regex.test(key)) return entry;
121
+ }
122
+ return null;
123
+ }
124
+
125
+ function getEnsureOnKeys(hook) {
126
+ const out = new Set();
127
+ for (const entry of doc.parameters || []) {
128
+ if (!entry.key || !Array.isArray(entry.ensureOn)) continue;
129
+ if (entry.ensureOn.includes(hook)) out.add(entry.key);
130
+ }
131
+ return [...out];
132
+ }
133
+
134
+ function isKeyAllowedEmpty(key) {
135
+ const entry = findEntryForKey(key);
136
+ return Boolean(entry && entry.generator && entry.generator.type === 'emptyAllowed');
137
+ }
138
+
139
+ function keyMatchesEnsureHook(key, hook) {
140
+ const entry = findEntryForKey(key);
141
+ return Boolean(entry && Array.isArray(entry.ensureOn) && entry.ensureOn.includes(hook));
142
+ }
143
+
144
+ function getStandardUpInfraBootstrapKeys() {
145
+ return standardBootstrapKeysFromDoc(doc);
146
+ }
147
+
148
+ return {
149
+ data: doc,
150
+ findEntryForKey,
151
+ getEnsureOnKeys,
152
+ getStandardUpInfraBootstrapKeys,
153
+ isKeyAllowedEmpty,
154
+ keyMatchesEnsureHook
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Load and validate catalog from disk.
160
+ * @param {string} [catalogPath] - YAML path (default bundled schema copy)
161
+ * @returns {ReturnType<typeof createCatalogApi>}
162
+ * @throws {Error} On missing file, invalid YAML, or schema validation failure
163
+ */
164
+ function loadInfraParameterCatalog(catalogPath = DEFAULT_CATALOG_PATH) {
165
+ const resolved = path.resolve(catalogPath);
166
+ const doc = readCatalogYamlDocument(resolved);
167
+ validateCatalogAgainstJsonSchema(doc);
168
+ assertEachParameterHasKeyXorPattern(doc);
169
+ const { exact, patterns } = buildCatalogIndexes(doc);
170
+ return createCatalogApi(doc, exact, patterns);
171
+ }
172
+
173
+ /**
174
+ * Cached catalog (for CLI hot paths).
175
+ * @param {string} [catalogPath]
176
+ * @returns {ReturnType<typeof loadInfraParameterCatalog>}
177
+ */
178
+ function getInfraParameterCatalog(catalogPath) {
179
+ const resolved = path.resolve(catalogPath || DEFAULT_CATALOG_PATH);
180
+ if (cachedCatalog && cachedPath === resolved) return cachedCatalog;
181
+ cachedCatalog = loadInfraParameterCatalog(resolved);
182
+ cachedPath = resolved;
183
+ return cachedCatalog;
184
+ }
185
+
186
+ /**
187
+ * Test-only: clear memoized catalog.
188
+ */
189
+ function clearInfraParameterCatalogCache() {
190
+ cachedCatalog = null;
191
+ cachedPath = null;
192
+ }
193
+
194
+ /**
195
+ * @param {object} doc - Parsed YAML root
196
+ * @param {Set<string>} set - Target set
197
+ */
198
+ function addRelaxedUpInfraKeysFromDoc(doc, set) {
199
+ if (Array.isArray(doc.standardUpInfraEnsureKeys)) {
200
+ for (const k of doc.standardUpInfraEnsureKeys) {
201
+ if (typeof k === 'string' && k.trim()) set.add(k.trim());
202
+ }
203
+ }
204
+ for (const entry of doc.parameters || []) {
205
+ if (
206
+ entry &&
207
+ typeof entry.key === 'string' &&
208
+ entry.key.trim() &&
209
+ Array.isArray(entry.ensureOn) &&
210
+ entry.ensureOn.includes('upInfra')
211
+ ) {
212
+ set.add(entry.key.trim());
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * YAML-only parse (no AJV): `standardUpInfraEnsureKeys` plus exact `parameters[].key` where ensureOn includes upInfra.
219
+ * Used when full catalog validation fails so callers can still read key names from the shipped file.
220
+ *
221
+ * @param {string} [catalogPath] - Path to infra.parameter.yaml
222
+ * @returns {string[]|null} Sorted unique keys, or null if unreadable
223
+ */
224
+ function readRelaxedUpInfraEnsureKeyList(catalogPath = DEFAULT_CATALOG_PATH) {
225
+ try {
226
+ const resolved = path.resolve(catalogPath);
227
+ if (!fsRealSync.existsSync(resolved)) return null;
228
+ const doc = yaml.load(fsRealSync.readFileSync(resolved, 'utf8'));
229
+ if (!doc || typeof doc !== 'object') return null;
230
+ const set = new Set();
231
+ addRelaxedUpInfraKeysFromDoc(doc, set);
232
+ return [...set].sort();
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * YAML-only: keys whose generator type is emptyAllowed (for ensure backfill behavior).
240
+ *
241
+ * @param {string} [catalogPath]
242
+ * @returns {Set<string>|null}
243
+ */
244
+ function readRelaxedEmptyAllowedKeySet(catalogPath = DEFAULT_CATALOG_PATH) {
245
+ try {
246
+ const resolved = path.resolve(catalogPath);
247
+ if (!fsRealSync.existsSync(resolved)) return null;
248
+ const doc = yaml.load(fsRealSync.readFileSync(resolved, 'utf8'));
249
+ if (!doc || typeof doc !== 'object') return null;
250
+ const set = new Set();
251
+ for (const entry of doc.parameters || []) {
252
+ if (
253
+ entry &&
254
+ typeof entry.key === 'string' &&
255
+ entry.key.trim() &&
256
+ entry.generator &&
257
+ entry.generator.type === 'emptyAllowed'
258
+ ) {
259
+ set.add(entry.key.trim());
260
+ }
261
+ }
262
+ return set;
263
+ } catch {
264
+ return null;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Read `defaults` from infra.parameter.yaml without AJV (fallback when full catalog load fails).
270
+ * Same normalization as normalizeCatalogDefaults (adminEmail, adminPassword, userPassword only).
271
+ *
272
+ * @param {string} [catalogPath]
273
+ * @returns {Record<string, string>}
274
+ */
275
+ function readRelaxedCatalogDefaults(catalogPath = DEFAULT_CATALOG_PATH) {
276
+ try {
277
+ const resolved = path.resolve(catalogPath);
278
+ if (!fsRealSync.existsSync(resolved)) return {};
279
+ const doc = yaml.load(fsRealSync.readFileSync(resolved, 'utf8'));
280
+ if (!doc || typeof doc !== 'object') return {};
281
+ return normalizeCatalogDefaults(doc.defaults);
282
+ } catch {
283
+ return {};
284
+ }
285
+ }
286
+
287
+ /**
288
+ * List exact `kv://` keys whose catalog generator is `literal` and whose value contains `{{placeholderName}}`.
289
+ * Used to sync the secrets store when CLI overrides a shared placeholder (ensure only backfills missing keys).
290
+ *
291
+ * @param {{ data?: object }} catalogApi - Result of getInfraParameterCatalog()
292
+ * @param {string} placeholderName - e.g. adminPassword, userPassword, adminEmail
293
+ * @returns {string[]}
294
+ */
295
+ function listKvKeysWithLiteralPlaceholder(catalogApi, placeholderName) {
296
+ if (!catalogApi || !placeholderName || typeof placeholderName !== 'string') return [];
297
+ const needle = `{{${placeholderName}}}`;
298
+ const keys = [];
299
+ for (const entry of catalogApi.data?.parameters || []) {
300
+ if (!entry || typeof entry.key !== 'string' || !entry.key.trim()) continue;
301
+ const g = entry.generator;
302
+ if (!g || g.type !== 'literal') continue;
303
+ const v = g.value;
304
+ if (typeof v === 'string' && v.includes(needle)) {
305
+ keys.push(entry.key.trim());
306
+ }
307
+ }
308
+ return keys;
309
+ }
310
+
311
+ const ALPHANUMERIC_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
312
+
313
+ /**
314
+ * Cryptographic random string using only [a-zA-Z0-9] (no base64 punctuation).
315
+ * @param {object} gen - generator with optional length (1–512, default 32)
316
+ * @param {{ randomBytes: Function }} cryptoLike
317
+ * @returns {string}
318
+ */
319
+ function randomAlphanumericFromGenerator(gen, cryptoLike) {
320
+ const len = Math.min(512, Math.max(1, Number(gen.length) || 32));
321
+ const bytes = cryptoLike.randomBytes(len);
322
+ let out = '';
323
+ for (let i = 0; i < len; i++) {
324
+ out += ALPHANUMERIC_CHARSET[bytes[i] % 62];
325
+ }
326
+ return out;
327
+ }
328
+
329
+ /**
330
+ * Short alphanumeric secret (default 8 chars). Same charset as randomAlphanumeric.
331
+ * @param {object} gen - optional length (1–512, default 8)
332
+ * @param {{ randomBytes: Function }} cryptoLike
333
+ * @returns {string}
334
+ */
335
+ function passwordFromGenerator(gen, cryptoLike) {
336
+ const len = Math.min(512, Math.max(1, Number(gen.length) || 8));
337
+ return randomAlphanumericFromGenerator({ length: len }, cryptoLike);
338
+ }
339
+
340
+ function valueForGeneratorType(gen, cryptoLike) {
341
+ if (!gen || !gen.type) return '';
342
+ if (gen.type === 'randomBytes32') return cryptoLike.randomBytes(32).toString('base64');
343
+ if (gen.type === 'randomAlphanumeric') return randomAlphanumericFromGenerator(gen, cryptoLike);
344
+ if (gen.type === 'password') return passwordFromGenerator(gen, cryptoLike);
345
+ if (gen.type === 'emptyString' || gen.type === 'emptyAllowed') return '';
346
+ if (gen.type === 'literal') return gen.value !== undefined && gen.value !== null ? String(gen.value) : '';
347
+ if (gen.type === 'databaseUrl' || gen.type === 'databasePassword') return '';
348
+ return '';
349
+ }
350
+
351
+ const PLACEHOLDER_RE = /\{\{([a-zA-Z0-9_]+)\}\}/g;
352
+
353
+ /**
354
+ * Replace {{name}} in a string using a plain map (catalog defaults + CLI overrides).
355
+ * Unknown names stay as-is.
356
+ * @param {string} str
357
+ * @param {Record<string, string>} vars
358
+ * @returns {string}
359
+ */
360
+ function expandInfraCatalogPlaceholders(str, vars) {
361
+ if (str === undefined || str === null) return '';
362
+ const s = String(str);
363
+ if (!s.includes('{{')) return s;
364
+ const map = vars && typeof vars === 'object' ? vars : {};
365
+ return s.replace(PLACEHOLDER_RE, (full, name) => {
366
+ if (Object.prototype.hasOwnProperty.call(map, name) && map[name] !== undefined && map[name] !== null) {
367
+ return String(map[name]);
368
+ }
369
+ return full;
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Normalize catalog `defaults` block to known keys only.
375
+ * @param {object|undefined} raw
376
+ * @returns {Record<string, string>}
377
+ */
378
+ function normalizeCatalogDefaults(raw) {
379
+ if (!raw || typeof raw !== 'object') return {};
380
+ const out = {};
381
+ for (const k of ['adminEmail', 'adminPassword', 'userPassword']) {
382
+ if (raw[k] !== undefined && raw[k] !== null && String(raw[k]).trim() !== '') {
383
+ out[k] = String(raw[k]).trim();
384
+ }
385
+ }
386
+ return out;
387
+ }
388
+
389
+ /**
390
+ * Merge infra.parameter.yaml `defaults` with up-infra / caller CLI options (non-empty override wins).
391
+ * @param {object|undefined} catalogData - Parsed catalog root (`getInfraParameterCatalog().data`)
392
+ * @param {object} [cliOptions]
393
+ * @param {string} [cliOptions.adminPassword]
394
+ * @param {string} [cliOptions.adminPwd] - Alias for adminPassword
395
+ * @param {string} [cliOptions.adminEmail]
396
+ * @param {string} [cliOptions.userPassword]
397
+ * @param {boolean} [cliOptions.tlsEnabled] - When true, TLS_ENABLED is "true"; otherwise "false". HTTP_ENABLED is always the opposite (for {{HTTP_ENABLED}}).
398
+ * @returns {Record<string, string>}
399
+ */
400
+ function mergeInfraParameterDefaultsForCli(catalogData, cliOptions = {}) {
401
+ const base = normalizeCatalogDefaults(catalogData && catalogData.defaults);
402
+ const o = cliOptions || {};
403
+ const pwd = String(o.adminPassword || o.adminPwd || '').trim();
404
+ if (pwd) base.adminPassword = pwd;
405
+ const em = String(o.adminEmail || '').trim();
406
+ if (em) base.adminEmail = em;
407
+ const up = String(o.userPassword || '').trim();
408
+ if (up) base.userPassword = up;
409
+ base.TLS_ENABLED = o.tlsEnabled === true ? 'true' : 'false';
410
+ base.HTTP_ENABLED = base.TLS_ENABLED === 'true' ? 'false' : 'true';
411
+ return base;
412
+ }
413
+
414
+ /**
415
+ * Produce value from catalog generator (no database slot resolution — use secrets-generator for DB).
416
+ * @param {string} key - Secret key
417
+ * @param {object} entry - Catalog entry
418
+ * @param {{ randomBytes: Function }} cryptoLike - crypto module or stub
419
+ * @param {Record<string, string>|undefined} placeholderVars - Merged defaults + CLI; when omitted, uses catalog defaults only
420
+ * @returns {string}
421
+ */
422
+ function getCatalogDataForPlaceholders() {
423
+ try {
424
+ return getInfraParameterCatalog().data || {};
425
+ } catch {
426
+ return {};
427
+ }
428
+ }
429
+
430
+ function generateValueFromCatalogEntry(key, entry, cryptoLike = require('crypto'), placeholderVars) {
431
+ const raw = valueForGeneratorType(entry && entry.generator, cryptoLike);
432
+ let vars = placeholderVars;
433
+ if (vars === undefined) {
434
+ vars = mergeInfraParameterDefaultsForCli(getCatalogDataForPlaceholders(), {});
435
+ }
436
+ if (!vars || typeof vars !== 'object') vars = {};
437
+ return expandInfraCatalogPlaceholders(raw, vars);
438
+ }
439
+
440
+ module.exports = {
441
+ loadInfraParameterCatalog,
442
+ getInfraParameterCatalog,
443
+ clearInfraParameterCatalogCache,
444
+ generateValueFromCatalogEntry,
445
+ readRelaxedUpInfraEnsureKeyList,
446
+ readRelaxedEmptyAllowedKeySet,
447
+ readRelaxedCatalogDefaults,
448
+ listKvKeysWithLiteralPlaceholder,
449
+ standardBootstrapKeysFromDoc,
450
+ DEFAULT_CATALOG_PATH,
451
+ SCHEMA_PATH,
452
+ formatAjvErrors,
453
+ randomAlphanumericFromGenerator,
454
+ passwordFromGenerator,
455
+ expandInfraCatalogPlaceholders,
456
+ mergeInfraParameterDefaultsForCli,
457
+ normalizeCatalogDefaults
458
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @fileoverview Validate env.template kv:// keys against infra.parameter.yaml catalog
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { nodeFs } = require('../internal/node-fs');
8
+ const path = require('path');
9
+ const { listAppDirsForDiscovery, extractKvKeysFromEnvContent } = require('./infra-kv-discovery');
10
+
11
+ /**
12
+ * Validate that every kv:// key in workspace env.template files has catalog coverage.
13
+ * @param {{ findEntryForKey: Function }} catalog - Loaded catalog API
14
+ * @param {object} pathsUtil - paths module
15
+ * @returns {{ valid: boolean, errors: string[] }}
16
+ */
17
+ function validateWorkspaceKvRefsAgainstCatalog(catalog, pathsUtil) {
18
+ const errors = [];
19
+ const cwd = process.cwd();
20
+ const fs = nodeFs();
21
+
22
+ for (const { dir } of listAppDirsForDiscovery(pathsUtil)) {
23
+ const envPath = path.join(dir, 'env.template');
24
+ if (!fs.existsSync(envPath)) continue;
25
+ let content;
26
+ try {
27
+ content = fs.readFileSync(envPath, 'utf8');
28
+ } catch (e) {
29
+ errors.push(`Could not read ${envPath}: ${e.message}`);
30
+ continue;
31
+ }
32
+ const rel = path.relative(cwd, envPath) || envPath;
33
+ for (const k of extractKvKeysFromEnvContent(content)) {
34
+ if (!catalog.findEntryForKey(k)) {
35
+ errors.push(`Unknown kv:// key "${k}" in ${rel} — extend lib/schema/infra.parameter.yaml`);
36
+ }
37
+ }
38
+ }
39
+
40
+ return { valid: errors.length === 0, errors };
41
+ }
42
+
43
+ /**
44
+ * Validate catalog entries marked requiredForLocal have a non-empty generator type.
45
+ * @param {object} doc - Parsed catalog root
46
+ * @returns {{ valid: boolean, errors: string[] }}
47
+ */
48
+ function validateCatalogRequiredGenerators(doc) {
49
+ const errors = [];
50
+ const params = doc && Array.isArray(doc.parameters) ? doc.parameters : [];
51
+ params.forEach((entry, i) => {
52
+ if (!entry.requiredForLocal) return;
53
+ const t = entry.generator && entry.generator.type;
54
+ if (!t) {
55
+ errors.push(`parameters[${i}] requiredForLocal but generator.type is missing`);
56
+ }
57
+ });
58
+ return { valid: errors.length === 0, errors };
59
+ }
60
+
61
+ module.exports = {
62
+ validateWorkspaceKvRefsAgainstCatalog,
63
+ validateCatalogRequiredGenerators
64
+ };
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/application-schema.json",
3
+ "$id": "aifabrix://schema/application-schema.json",
4
4
  "title": "AI Fabrix Application Configuration Schema",
5
5
  "description": "Schema for AI Fabrix applications deployed via the API-Driven Deployment System",
6
6
  "metadata": {
7
7
  "key": "application-schema",
8
8
  "name": "Application Configuration Schema",
9
9
  "description": "JSON schema for validating AI Fabrix application configuration files",
10
- "version": "1.2.0",
10
+ "version": "1.4.0",
11
11
  "type": "schema",
12
12
  "category": "infrastructure",
13
13
  "author": "AI Fabrix Team",
14
14
  "createdAt": "2024-01-01T00:00:00Z",
15
- "updatedAt": "2025-12-01T00:00:00Z",
15
+ "updatedAt": "2026-04-06T00:00:00Z",
16
16
  "compatibility": {
17
17
  "minVersion": "1.0.0",
18
18
  "maxVersion": "2.0.0",
@@ -30,6 +30,23 @@
30
30
  "external-datasource.schema.json"
31
31
  ],
32
32
  "changelog": [
33
+ {
34
+ "version": "1.4.0",
35
+ "date": "2026-04-06T00:00:00Z",
36
+ "changes": [
37
+ "Removed build.localPort: local workstation PORT and localhost URL rewriting use the root-level manifest port only, with local host port = port + 10 + developerId*100 (developer id 0 → port+10). Docker published host port remains port + developerId*100.",
38
+ "Migration: delete build.localPort from application.yaml; set or keep port as the single listen/container port. Use url://public, url://internal, or url://<app>-public|internal in env.template for declarative URLs (resolved after kv://)."
39
+ ],
40
+ "breaking": true
41
+ },
42
+ {
43
+ "version": "1.3.0",
44
+ "date": "2026-04-05T00:00:00Z",
45
+ "changes": [
46
+ "Added optional environmentScopedResources (boolean): app declares support for env-prefixed resource names when the user activates useEnvironmentScopedResources in ~/.aifabrix/config.yaml and run env is dev or tst"
47
+ ],
48
+ "breaking": false
49
+ },
33
50
  {
34
51
  "version": "1.2.0",
35
52
  "date": "2025-12-01T00:00:00Z",
@@ -414,6 +431,10 @@
414
431
  "description": "Front Door probe interval in seconds (default 120)",
415
432
  "minimum": 60,
416
433
  "maximum": 600
434
+ },
435
+ "bashProbe": {
436
+ "type": "boolean",
437
+ "description": "When true, generated Docker Compose healthcheck uses a bash /dev/tcp HTTP probe instead of curl (for minimal images such as Keycloak that omit curl)."
417
438
  }
418
439
  },
419
440
  "additionalProperties": false,
@@ -504,12 +525,13 @@
504
525
  },
505
526
  "host": {
506
527
  "type": "string",
507
- "description": "Hostname for Traefik routing. Supports ${DEV_USERNAME} variable interpolation (e.g., '${DEV_USERNAME}.aifabrix.dev'). Required if enabled is true.",
508
- "pattern": "^[a-z0-9.$\\{\\}-]+$"
528
+ "description": "Hostname template for Traefik (no path). Placeholders: ${DEV_USERNAME} (from developer-id; empty when id is 0 or unset), ${REMOTE_HOST} (hostname from remote-server in config). For id 0, host is bare remote hostname. Non-zero: dev01, dev02, … Adjacent ${DEV_USERNAME}${REMOTE_HOST} is normalized to insert a dot. Required if enabled is true.",
529
+ "pattern": "^[a-zA-Z0-9.$\\{\\}_-]+$"
509
530
  },
510
531
  "tls": {
511
- "type": "boolean",
532
+ "type": "string",
512
533
  "description": "Enable TLS/HTTPS for Traefik (local development)",
534
+ "pattern": "^[a-zA-Z.$\\{\\}_-]+$",
513
535
  "default": true
514
536
  },
515
537
  "certStore": {
@@ -545,7 +567,7 @@
545
567
  "description": "Required roles for access",
546
568
  "items": {
547
569
  "type": "string",
548
- "pattern": "^[a-z-]+$"
570
+ "pattern": "^[a-z0-9-]+$"
549
571
  }
550
572
  },
551
573
  "endpoints": {
@@ -601,7 +623,7 @@
601
623
  "value": {
602
624
  "type": "string",
603
625
  "description": "Role identifier (used in JWT and ACL)",
604
- "pattern": "^[a-z-]+$"
626
+ "pattern": "^[a-z0-9-]+$"
605
627
  },
606
628
  "description": {
607
629
  "type": "string",
@@ -645,7 +667,7 @@
645
667
  "description": "Roles that have this permission",
646
668
  "items": {
647
669
  "type": "string",
648
- "pattern": "^[a-z-]+$",
670
+ "pattern": "^[a-z0-9-]+$",
649
671
  "minLength": 1,
650
672
  "maxLength": 50
651
673
  },
@@ -742,12 +764,6 @@
742
764
  "description": "Path where .env file is written for local development (relative to config dir); single .env for run",
743
765
  "pattern": "^[^/].*"
744
766
  },
745
- "localPort": {
746
- "type": "integer",
747
- "description": "Port for local development (host and .env PORT); when set and > 0, overrides port for run and local .env. Container/deployment still use port/containerPort.",
748
- "minimum": 1,
749
- "maximum": 65535
750
- },
751
767
  "containerPort": {
752
768
  "type": "integer",
753
769
  "description": "Container internal port (defaults to port if not specified)",
@@ -799,16 +815,20 @@
799
815
  "system": {
800
816
  "type": "object",
801
817
  "description": "Optional: Inline external system configuration for atomic deployment. Uses external-system.schema.json structure via $ref. Alternative to externalIntegration.systems file-based approach.",
802
- "$ref": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json"
818
+ "$ref": "aifabrix://schema/external-system.schema.json"
803
819
  },
804
820
  "dataSources": {
805
821
  "type": "array",
806
822
  "description": "Optional: Inline external data source configurations for atomic deployment. Uses external-datasource.schema.json structure via $ref. Alternative to externalIntegration.dataSources file-based approach.",
807
823
  "minItems": 0,
808
824
  "items": {
809
- "$ref": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json"
825
+ "$ref": "aifabrix://schema/external-datasource.schema.json"
810
826
  }
811
827
  },
828
+ "environmentScopedResources": {
829
+ "type": "boolean",
830
+ "description": "When true, this application supports environment-scoped resource names (Key Vault key prefixes, Redis DB index, local Docker container and Traefik paths) when useEnvironmentScopedResources is activated in ~/.aifabrix/config.yaml and the target environment is dev or tst. Does not apply to pro. Used to share Postgres/Docker across dev/tst without collisions."
831
+ },
812
832
  "externalIntegration": {
813
833
  "type": "object",
814
834
  "description": "Defines External Systems & External Data Sources shipped with this application. Pipeline registers these in Miso-Controller and publishes schemas to Dataplane. Supports both file-based references and inline definitions.",