@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
@@ -2,17 +2,17 @@
2
2
  "$schema":"https://json-schema.org/draft/2020-12/schema",
3
3
  "$id":"aifabrix://schema/external-datasource.schema.json",
4
4
  "title":"External Data Source",
5
- "description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, data dimensions, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior.",
5
+ "description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, data dimensions, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior. Root externalSpec is the datasource-level authoritative reference for where to obtain the vendor or tooling API specification used for import and validation; external-system.schema.json openapi/mcp are system-level connectivity hints only.",
6
6
  "metadata":{
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"2.4.6",
10
+ "version":"2.4.8",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2026-04-20T00:00:00Z",
15
+ "updatedAt":"2026-05-10T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
18
  "maxVersion":"3.0.0",
@@ -183,6 +183,27 @@
183
183
  "Added optional fetch.httpResponseNormalization for manifest-driven GET response shaping (302 Location follow, JSON redirect envelopes, nested JSON string URL follow-up)."
184
184
  ],
185
185
  "breaking":false
186
+ },
187
+ {
188
+ "version":"2.4.8",
189
+ "date":"2026-05-10T00:00:00Z",
190
+ "changes":[
191
+ "Documented roles: root externalSpec is the datasource-level authoritative vendor/API specification provenance (fetch, import, validation); config.openapi is the runtime connector (operations, documentKey) and is the behavioral source of truth; external-system openapi/mcp remain connectivity/bootstrap only.",
192
+ "Clarified externalSpec and openapi property descriptions to distinguish vendor spec location from generated or persisted AI-facing OpenAPI material managed by dataplane."
193
+ ],
194
+ "breaking":false
195
+ },
196
+ {
197
+ "version":"2.4.7",
198
+ "date":"2026-05-08T00:00:00Z",
199
+ "changes":[
200
+ "externalSpec: optional root object with required type (openapi | mcp) plus documentKey, url, relativePath for vendor or tooling provenance (wizard/import).",
201
+ "fieldMappings.attributes.<name>: added optional writePath (provider-payload target dot-path), direction (read|write|readwrite), and passthrough (boolean) to support bidirectional mapping authoring; compiler auto-derives writePath from invertible '{{raw.<dot.path>}}' expressions.",
202
+ "fieldMappings.attributes.<name>: short-form sugar accepted - a string equal to a {{...}} expression is normalized to { expression: '<value>' } at compile time.",
203
+ "Rejected body and rawBody as aliases for writePath (engine-neutral writePath is the only write-target identifier).",
204
+ "validate_raw_path: replaced provider-aware traversal with one generic JSON-Schema rule (descend via properties.<seg>; accept remaining path under additionalProperties: true; otherwise emit one actionable error)."
205
+ ],
206
+ "breaking":false
186
207
  }
187
208
  ]
188
209
  },
@@ -283,69 +304,131 @@
283
304
  "properties":{
284
305
  "attributes":{
285
306
  "type":"object",
286
- "description":"Key = normalized attribute name. Value = transformation configuration.",
307
+ "description":"Key = normalized attribute name. Value = transformation configuration. Short-form: a string equal to a {{...}} expression is normalized to { expression: '...' } by the compiler.",
287
308
  "propertyNames":{
288
309
  "pattern":"^[a-z][a-zA-Z0-9]*$"
289
310
  },
290
311
  "additionalProperties":{
291
- "type":"object",
292
- "properties":{
293
- "expression":{
312
+ "oneOf":[
313
+ {
294
314
  "type":"string",
295
- "description":"Pipe-based DSL expression. Allowed roots: raw.<path>, fk.<fk>.metadata.<field>, fk.<fk>.dimension.<dimension>[.label], dimension.<dimension>[.label]. FK traversal in DSL is one-hop only.",
315
+ "description":"Short-form sugar: an expression string is normalized by the compiler to { expression: '<value>' }.",
296
316
  "maxLength":512,
297
317
  "pattern":"^\\s*\\{\\{(raw\\.[a-zA-Z0-9_.]+|fk\\.[a-z][a-zA-Z0-9]*\\.metadata\\.[a-z][a-zA-Z0-9]*|fk\\.[a-z][a-zA-Z0-9]*\\.dimension\\.[a-zA-Z0-9_]+(\\.label)?|dimension\\.[a-zA-Z0-9_]+(\\.label)?)\\}\\}(\\s*\\|\\s*[a-zA-Z0-9_]+(\\([^)]*\\))?)*\\s*$"
298
318
  },
299
- "description":{
300
- "type":"string",
301
- "description":"Technical description of the normalized field."
302
- },
303
- "semantic":{
319
+ {
304
320
  "type":"object",
305
- "description":"Semantic metadata for AI agents and schema generation.",
306
321
  "properties":{
307
- "title":{
322
+ "expression":{
308
323
  "type":"string",
309
- "description":"Human-friendly label for this field."
324
+ "description":"Pipe-based DSL expression. Allowed roots: raw.<path>, fk.<fk>.metadata.<field>, fk.<fk>.dimension.<dimension>[.label], dimension.<dimension>[.label]. FK traversal in DSL is one-hop only.",
325
+ "maxLength":512,
326
+ "pattern":"^\\s*\\{\\{(raw\\.[a-zA-Z0-9_.]+|fk\\.[a-z][a-zA-Z0-9]*\\.metadata\\.[a-z][a-zA-Z0-9]*|fk\\.[a-z][a-zA-Z0-9]*\\.dimension\\.[a-zA-Z0-9_]+(\\.label)?|dimension\\.[a-zA-Z0-9_]+(\\.label)?)\\}\\}(\\s*\\|\\s*[a-zA-Z0-9_]+(\\([^)]*\\))?)*\\s*$"
310
327
  },
311
328
  "description":{
312
329
  "type":"string",
313
- "description":"Business-level description of the field and how it is used."
330
+ "description":"Technical description of the normalized field."
314
331
  },
315
- "example":{
316
- "description":"Example value for this field.",
317
- "type":["string","number","boolean","object","array","null"]
332
+ "writePath":{
333
+ "type":"string",
334
+ "description":"Optional. Provider-payload target path for write operations. Dot-separated identifiers (e.g. 'properties.name'). When omitted and the expression is a single, function-free '{{raw.<dot.path>}}', the compiler derives writePath from raw.<dot.path>. When the expression is non-invertible (function chain, fk., dimension., literal, multiple references), writePath is null unless explicitly set.",
335
+ "pattern":"^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$",
336
+ "maxLength":256
318
337
  },
319
- "categories":{
320
- "type":"array",
321
- "description":"Logical categories/tags (e.g. ['sales', 'revenue']).",
322
- "items":{
323
- "type":"string"
324
- }
338
+ "direction":{
339
+ "type":"string",
340
+ "description":"Optional. Mapping direction. 'readwrite' (default for invertible mappings, including those with explicit writePath), 'read' (default for non-invertible expressions or fields listed in exposed.readonly), 'write' (write-only).",
341
+ "enum":["read","write","readwrite"]
325
342
  },
326
- "synonyms":{
327
- "type":"array",
328
- "description":"Optional list of synonyms used in natural language (e.g. 'opportunity', 'sales-case').",
329
- "items":{
330
- "type":"string"
331
- }
343
+ "passthrough":{
344
+ "type":"boolean",
345
+ "description":"Optional. When true, declares an explicit dynamic-keys passthrough into the provider payload. The mapper writes the exposed value verbatim at writePath. Requires writePath. There is no implicit deep-merge of unknown keys; passthrough is the only mechanism for forwarding arbitrary provider-bag keys.",
346
+ "default":false
347
+ },
348
+ "semantic":{
349
+ "type":"object",
350
+ "description":"Semantic metadata for AI agents and schema generation.",
351
+ "properties":{
352
+ "title":{
353
+ "type":"string",
354
+ "description":"Human-friendly label for this field."
355
+ },
356
+ "description":{
357
+ "type":"string",
358
+ "description":"Business-level description of the field and how it is used."
359
+ },
360
+ "example":{
361
+ "description":"Example value for this field.",
362
+ "type":["string","number","boolean","object","array","null"]
363
+ },
364
+ "categories":{
365
+ "type":"array",
366
+ "description":"Logical categories/tags (e.g. ['sales', 'revenue']).",
367
+ "items":{
368
+ "type":"string"
369
+ }
370
+ },
371
+ "synonyms":{
372
+ "type":"array",
373
+ "description":"Optional list of synonyms used in natural language (e.g. 'opportunity', 'sales-case').",
374
+ "items":{
375
+ "type":"string"
376
+ }
377
+ }
378
+ },
379
+ "additionalProperties":false
380
+ },
381
+ "lineage":{
382
+ "$ref":"#/$defs/fieldMappingLineage"
332
383
  }
333
384
  },
385
+ "required":[
386
+ "expression"
387
+ ],
388
+ "not":{
389
+ "anyOf":[
390
+ {"required":["body"]},
391
+ {"required":["rawBody"]}
392
+ ]
393
+ },
334
394
  "additionalProperties":false
335
- },
336
- "lineage":{
337
- "$ref":"#/$defs/fieldMappingLineage"
338
395
  }
339
- },
340
- "required":[
341
- "expression"
342
- ],
343
- "additionalProperties":false
396
+ ]
344
397
  }
345
398
  }
346
399
  },
347
400
  "additionalProperties":false
348
401
  },
402
+ "externalSpec":{
403
+ "type":"object",
404
+ "description":"Optional datasource-level provenance for the authoritative vendor or tooling API specification: where to fetch or resolve the external OpenAPI document or MCP surface (type, url, relativePath, optional documentKey). Used for import paths and lightweight validation against declared operations—not system-level connectivity (see external-system openapi/mcp). The generated or materialized AI-facing OpenAPI derived from config.openapi.operations is a separate dataplane-managed artifact keyed by openapi.documentKey.",
405
+ "properties":{
406
+ "type":{
407
+ "type":"string",
408
+ "enum":[
409
+ "openapi",
410
+ "mcp"
411
+ ],
412
+ "description":"Whether this provenance describes an OpenAPI document (openapi) or an MCP server/tooling surface (mcp)."
413
+ },
414
+ "documentKey":{
415
+ "type":"string",
416
+ "description":"Optional key when external source aligns with openapi.documentKey."
417
+ },
418
+ "url":{
419
+ "type":"string",
420
+ "description":"Optional absolute URL of the external OpenAPI document or MCP descriptor, depending on type."
421
+ },
422
+ "relativePath":{
423
+ "type":"string",
424
+ "description":"Optional path relative to the integration bundle root for an external OpenAPI JSON file or MCP artifact, depending on type."
425
+ }
426
+ },
427
+ "required":[
428
+ "type"
429
+ ],
430
+ "additionalProperties":false
431
+ },
349
432
  "exposed":{
350
433
  "type":"object",
351
434
  "description":"Defines public API exposure contract. v2.4.0 freeze requires exposed.schema as canonical response shape.",
@@ -500,7 +583,7 @@
500
583
  },
501
584
  "openapi":{
502
585
  "type":"object",
503
- "description":"OpenAPI-driven connector configuration. Only includes endpoints selected during wizard onboarding. To add more endpoints, run the wizard again.",
586
+ "description":"OpenAPI-driven connector configuration: authoritative runtime operation bindings and AI-facing documentKey. Operations are selected during wizard onboarding; dataplane may generate or refresh the persisted OpenAPI artifact for that documentKey from these operations when needed. Where the vendor OpenAPI file is obtained from is described by root externalSpec, not this block.",
504
587
  "properties":{
505
588
  "enabled":{
506
589
  "type":"boolean",
@@ -551,8 +634,13 @@
551
634
  "type":"boolean",
552
635
  "default":false,
553
636
  "description":"When true and no explicit operation security scopes are configured, the runtime resolves required RBAC permission as '<resourceType>:<operation>' for each operation (resourceType from the datasource config, default document), matching builder repair RBAC permission names. Explicit operation-level scopes (openapi.operations.*.security/permissions) take precedence and disable autoRbac for that operation."
637
+ },
638
+ "operationRef":{
639
+ "type":"string",
640
+ "description":"Optional OpenAPI JSON Pointer to the primary operation (e.g. list) used by wizard-generated configs for document resolution."
554
641
  }
555
- }
642
+ },
643
+ "additionalProperties":false
556
644
  },
557
645
  "validation":{
558
646
  "type":"object",
@@ -694,12 +782,17 @@
694
782
  },
695
783
  "primaryKey":{
696
784
  "type":"object",
697
- "description":"Optional key/value payload for primary-key focused operation tests.",
785
+ "description":"Concrete primary-key field values for list/get/mutations, keyed by the same names as the datasource manifest primaryKey array. Authors may supply a subset of PK fields. Optional primaryKey.search (string legacy filter or JSON filter object) resolves a row via list when implemented for the engine.",
698
786
  "additionalProperties":true
699
787
  },
788
+ "useCopyForMutations":{
789
+ "type":"boolean",
790
+ "default":true,
791
+ "description":"When true (default if scenarios include create), after create the executor merges returned identifiers into the effective primary key for later get/update/delete in that run. When false, mutations use the static manifest primaryKey (destructive on shared fixtures). Ignored when no create scenario exists."
792
+ },
700
793
  "scenarios":{
701
794
  "type":"array",
702
- "description":"Optional operation-specific test scenarios.",
795
+ "description":"Ordered scenario steps for capacity E2E; execution order is array order (optional order field is a diagnostics hint only).",
703
796
  "items":{
704
797
  "type":"object",
705
798
  "required":[
@@ -710,6 +803,11 @@
710
803
  "type":"string",
711
804
  "pattern":"^[a-z][a-zA-Z0-9]*$"
712
805
  },
806
+ "enabled":{
807
+ "type":"boolean",
808
+ "default":true,
809
+ "description":"When false, the executor skips this scenario and records an explicit skip (scenario_disabled) in evidence."
810
+ },
713
811
  "input":{
714
812
  "type":"object",
715
813
  "additionalProperties":true
@@ -756,15 +854,15 @@
756
854
  },
757
855
  "additionalProperties":false
758
856
  },
759
- "capabilities":{
760
- "type":"array",
761
- "description":"Supported operations list.",
762
- "items":{
763
- "type":"string",
764
- "pattern":"^[a-z][a-zA-Z0-9]*$"
765
- },
766
- "uniqueItems":true
767
- },
857
+ "capabilities":{
858
+ "description":"Supported operations list.",
859
+ "type":"array",
860
+ "items":{
861
+ "type":"string",
862
+ "pattern":"^[a-z][a-zA-Z0-9_]*$"
863
+ },
864
+ "uniqueItems":true
865
+ },
768
866
  "execution":{
769
867
  "type":"object",
770
868
  "description":"Execution engine configuration for this datasource (CIP, Python, or datasource-chaining). CIP is the native declarative mode.",
@@ -1524,6 +1622,29 @@
1524
1622
  }
1525
1623
  ]
1526
1624
  },
1625
+ "restRuntime":{
1626
+ "type":"object",
1627
+ "description":"Canonical REST path shape for manifest-defined (non-core) operations. Core CRUD keys ignore this block.",
1628
+ "additionalProperties":false,
1629
+ "properties":{
1630
+ "pathKind":{
1631
+ "type":"string",
1632
+ "enum":[
1633
+ "collection",
1634
+ "collectionSubresource",
1635
+ "item",
1636
+ "itemSubresource"
1637
+ ]
1638
+ },
1639
+ "subpathSegments":{
1640
+ "type":"array",
1641
+ "items":{
1642
+ "type":"string"
1643
+ },
1644
+ "description":"Trailing path segments after the resource segment or record identifier (e.g. basic → /{resource}/basic)."
1645
+ }
1646
+ }
1647
+ },
1527
1648
  "security":{
1528
1649
  "description":"OpenAPI security requirements. Supports object/list/string forms consumed by RBAC coverage validation.",
1529
1650
  "oneOf":[
@@ -1755,6 +1876,15 @@
1755
1876
  "identity":{
1756
1877
  "$ref":"#/$defs/identitySpec"
1757
1878
  },
1879
+ "shape":{
1880
+ "type":"string",
1881
+ "enum":[
1882
+ "create",
1883
+ "update",
1884
+ "delete"
1885
+ ],
1886
+ "description":"Optional semantic classification for this operation when automatic inference from fetch steps (method/path) is insufficient."
1887
+ },
1758
1888
  "steps":{
1759
1889
  "type":"array",
1760
1890
  "minItems":1,
@@ -2,17 +2,17 @@
2
2
  "$schema":"http://json-schema.org/draft-07/schema#",
3
3
  "$id":"aifabrix://schema/external-system.schema.json",
4
4
  "title":"AI Fabrix External System Configuration Schema",
5
- "description":"Schema for configuring an external system connected to the AI Fabrix Dataplane. This defines authentication, OpenAPI/MCP bindings, field mappings defaults, metadata handling and portal inputs.",
5
+ "description":"Schema for configuring an external system connected to the AI Fabrix Dataplane. This defines authentication, system-level OpenAPI/MCP connectivity hints (not the datasource-grade vendor contract; see external-datasource.schema.json root externalSpec), field mappings defaults, metadata handling and portal inputs.",
6
6
  "metadata":{
7
7
  "key":"external-system-schema",
8
8
  "name":"External System Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalSystem configuration files",
10
- "version":"1.6.2",
10
+ "version":"1.6.3",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2026-04-23T00:00:00Z",
15
+ "updatedAt":"2026-05-10T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.4.0",
18
18
  "maxVersion":"2.0.0",
@@ -29,6 +29,15 @@
29
29
 
30
30
  ],
31
31
  "changelog":[
32
+ {
33
+ "version":"1.6.3",
34
+ "date":"2026-05-10T00:00:00Z",
35
+ "changes":[
36
+ "Documented split: system openapi/mcp objects are connectivity and bootstrap hints (specUrl, serverUrl, documentKey); datasource-level vendor/API provenance belongs in external-datasource externalSpec.",
37
+ "Declared optional openapi.autoDiscoverEntities (wizard/tooling); tightened openapi and mcp to additionalProperties: false."
38
+ ],
39
+ "breaking":false
40
+ },
32
41
  {
33
42
  "version":"1.6.2",
34
43
  "date":"2026-04-23T00:00:00Z",
@@ -154,7 +163,7 @@
154
163
  "mcp",
155
164
  "custom"
156
165
  ],
157
- "description":"Integration type: OpenAPI-driven, MCP-driven, or custom Python connector."
166
+ "description":"Integration type: openapi and mcp indicate how the system is wired at a high level; per-datasource vendor specifications and import provenance are authored on each ExternalDataSource (external-datasource.schema.json externalSpec), not duplicated as the full contract here."
158
167
  },
159
168
  "enabled":{
160
169
  "type":"boolean",
@@ -235,23 +244,27 @@
235
244
  },
236
245
  "openapi":{
237
246
  "type":"object",
238
- "description":"OpenAPI integration configuration",
247
+ "description":"System-level connectivity and bootstrap hints for OpenAPI-backed systems (e.g. default specUrl for discovery, documentKey alignment with shared registry entries). Not the authoritative per-datasource vendor OpenAPI contract; that is declared on the datasource via externalSpec and validated against imported material.",
239
248
  "properties":{
240
249
  "documentKey":{
241
250
  "type":"string",
242
- "description":"Key of the OpenAPI document in the registry"
251
+ "description":"Optional registry-oriented key associated with this system's default OpenAPI document (wizard/bootstrap)."
243
252
  },
244
253
  "specUrl":{
245
254
  "type":"string",
246
- "description":"URL to the OpenAPI specification",
255
+ "description":"Optional absolute URL to a representative OpenAPI specification for onboarding or tooling (not a substitute for datasource externalSpec).",
247
256
  "pattern":"^(http|https)://.*$"
257
+ },
258
+ "autoDiscoverEntities":{
259
+ "type":"boolean",
260
+ "description":"Optional wizard or tooling hint to discover entities from the system default spec. Not part of the per-datasource vendor contract (see external-datasource externalSpec)."
248
261
  }
249
262
  },
250
- "additionalProperties":true
263
+ "additionalProperties":false
251
264
  },
252
265
  "mcp":{
253
266
  "type":"object",
254
- "description":"Model Context Protocol integration configuration",
267
+ "description":"System-level MCP connectivity hints (server URL, tool prefix). Not the datasource-level MCP security or operation contract; those live on ExternalDataSource configuration.",
255
268
  "properties":{
256
269
  "serverUrl":{
257
270
  "type":"string",
@@ -264,7 +277,7 @@
264
277
  "pattern":"^[a-zA-Z0-9_-]+$"
265
278
  }
266
279
  },
267
- "additionalProperties":true
280
+ "additionalProperties":false
268
281
  },
269
282
  "dataSources":{
270
283
  "type":"array",
@@ -11,6 +11,10 @@ defaults:
11
11
  userPassword: user123
12
12
  # Always ensured on up-infra even when no workspace env.template references these kv:// keys (bootstrap defaults).
13
13
  standardUpInfraEnsureKeys:
14
+ # Docker Postgres admin + Redis — required for admin-secrets / infra compose; not guaranteed in every app env.template
15
+ - postgres-passwordKeyVault
16
+ - redis-url
17
+ - redis-passwordKeyVault
14
18
  - databases-miso-controller-0-urlKeyVault
15
19
  - databases-miso-controller-0-passwordKeyVault
16
20
  - databases-miso-controller-1-urlKeyVault
@@ -113,6 +117,17 @@ parameters:
113
117
  vaultSecretName: keycloak-client-secretKeyVault
114
118
  notes: Per-app OAuth client secret from Keycloak registration; generated on first ensure.
115
119
 
120
+ # Keycloak events: webhook signature secret used by miso-controller (KEYCLOAK_EVENTS_SECRET).
121
+ # This must exist on first boot because up-platform runs containers from templates (no prior resolve).
122
+ - key: keycloak-events-secretKeyVault
123
+ scope: shared-service
124
+ generator:
125
+ type: randomBytes32
126
+ ensureOn: [upInfra, resolveApp]
127
+ azure:
128
+ notes: >-
129
+ Shared secret for Keycloak event signature verification. Generated on first ensure for local bootstrap.
130
+
116
131
  - key: keycloak-default-passwordKeyVault
117
132
  scope: shared-service
118
133
  generator:
@@ -236,6 +251,16 @@ parameters:
236
251
  notes: >-
237
252
  Per-app OAuth client id from controller registration; literal default matches local Keycloak client naming.
238
253
 
254
+ - key: miso-controller-client-secretKeyVault
255
+ scope: app
256
+ generator:
257
+ type: randomBytes32
258
+ ensureOn: [upInfra, resolveApp]
259
+ azure:
260
+ notes: >-
261
+ Per-app OAuth client secret used by miso-controller for service-to-service auth (MISO_CLIENTSECRET).
262
+ Generated on first ensure for local bootstrap.
263
+
239
264
  # Dataplane ↔ controller OAuth (builder/dataplane env.template MISO_CLIENTID / MISO_CLIENTSECRET).
240
265
  - key: dataplane-client-idKeyVault
241
266
  scope: app
@@ -332,7 +357,7 @@ parameters:
332
357
  scope: app
333
358
  generator:
334
359
  type: randomBytes32
335
- ensureOn: [resolveApp]
360
+ ensureOn: [upInfra, resolveApp]
336
361
  azure:
337
362
  notes: >-
338
363
  Prefer {appKey}-secrets-apiKeyVault locally; dataplane shares miso-controller's entry for pipeline Bearer bypass.
@@ -214,7 +214,7 @@
214
214
  },
215
215
  "debug": {
216
216
  "type": "boolean",
217
- "description": "When true, capture detailed generation steps and save to debug.log (dataplane returns debugLog)",
217
+ "description": "When true, capture detailed generation steps and save to integration/<app>/logs/debug.log (dataplane returns debugLog)",
218
218
  "default": false
219
219
  }
220
220
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Walk ancestors of `startDir` for `<dir>/.aifabrix/config.yaml`.
3
+ *
4
+ * @fileoverview Config directory discovery from cwd
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+
13
+ /**
14
+ * @param {string} startDir
15
+ * @param {(p: string) => boolean} existsSyncFn
16
+ * @returns {string|null} Absolute path to `.aifabrix` directory containing `config.yaml`
17
+ */
18
+ function findAifabrixConfigDirFromAncestors(startDir, existsSyncFn) {
19
+ if (!startDir || typeof startDir !== 'string') {
20
+ return null;
21
+ }
22
+ let dir = path.resolve(startDir);
23
+ const maxSteps = 64;
24
+ for (let i = 0; i < maxSteps; i += 1) {
25
+ const candidate = path.join(dir, '.aifabrix', 'config.yaml');
26
+ if (existsSyncFn(candidate)) {
27
+ return path.join(dir, '.aifabrix');
28
+ }
29
+ const parent = path.dirname(dir);
30
+ if (parent === dir) {
31
+ break;
32
+ }
33
+ dir = parent;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ module.exports = {
39
+ findAifabrixConfigDirFromAncestors
40
+ };
@@ -3,8 +3,8 @@
3
3
  * Shared by `paths.getConfigDirForPaths` and `config.getConfigDir` (no circular imports).
4
4
  *
5
5
  * When `AIFABRIX_HOME` is set to the POSIX home (builder-server pattern) but the real
6
- * config file is under `~/.aifabrix/config.yaml`, use that nested directory so
7
- * `secrets.local.yaml` and auth config stay beside `config.yaml`.
6
+ * config file is under `~/.aifabrix/config.yaml`, use that nested directory.
7
+ * With no env override, walks up from cwd for `<ancestor>/.aifabrix/config.yaml`, else `~/.aifabrix`.
8
8
  *
9
9
  * Relative `AIFABRIX_HOME` / `AIFABRIX_CONFIG` values are anchored to the user home
10
10
  * directory (not `process.cwd()`), so a mistaken `aifabrix-training` does not become
@@ -21,6 +21,7 @@
21
21
  const { existsSync } = require('../internal/fs-real-sync');
22
22
  const path = require('path');
23
23
  const os = require('os');
24
+ const { findAifabrixConfigDirFromAncestors } = require('./aifabrix-config-dir-walk');
24
25
 
25
26
  /**
26
27
  * @returns {string}
@@ -102,6 +103,23 @@ function resolveAifabrixConfigEnvPath(raw) {
102
103
  return resolveAifabrixHomeLikePath(raw);
103
104
  }
104
105
 
106
+ /**
107
+ * When neither `AIFABRIX_CONFIG` nor `AIFABRIX_HOME` is set, look for `<ancestor>/.aifabrix/config.yaml`
108
+ * walking up from `process.cwd()`. Skipped under Jest so suites do not pick up the workspace config.
109
+ *
110
+ * @returns {string|null} Directory containing `config.yaml`, or null
111
+ */
112
+ function findAifabrixConfigDirWalkingUpFromCwd() {
113
+ if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
114
+ return null;
115
+ }
116
+ try {
117
+ return findAifabrixConfigDirFromAncestors(process.cwd(), existsSync);
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
105
123
  /**
106
124
  * @returns {string} Absolute directory containing `config.yaml`
107
125
  */
@@ -122,11 +140,16 @@ function getAifabrixRuntimeConfigDir() {
122
140
  }
123
141
  return homeDir;
124
142
  }
143
+ const fromCwd = findAifabrixConfigDirWalkingUpFromCwd();
144
+ if (fromCwd) {
145
+ return fromCwd;
146
+ }
125
147
  return path.join(safeHomedir(), '.aifabrix');
126
148
  }
127
149
 
128
150
  module.exports = {
129
151
  getAifabrixRuntimeConfigDir,
130
152
  resolveAifabrixHomeLikePath,
131
- resolveAifabrixConfigEnvPath
153
+ resolveAifabrixConfigEnvPath,
154
+ findAifabrixConfigDirWalkingUpFromCwd
132
155
  };
@@ -12,7 +12,28 @@
12
12
  'use strict';
13
13
 
14
14
  const path = require('path');
15
- const fs = require('fs');
15
+ const defaultFs = require('fs');
16
+ const { nodeFs } = require('../internal/node-fs');
17
+
18
+ const realFs = nodeFs();
19
+
20
+ /**
21
+ * Prefer real disk when `fs` is not mocked (avoids `jest.mock('fs')` bleed in other suites).
22
+ * Use `require('fs')` when Jest replaced it so tests with virtual file stores still work.
23
+ *
24
+ * @returns {Pick<import('fs'), 'existsSync'|'renameSync'>}
25
+ */
26
+ function getResolverFs() {
27
+ if (
28
+ typeof jest !== 'undefined' &&
29
+ defaultFs.existsSync &&
30
+ typeof jest.isMockFunction === 'function' &&
31
+ jest.isMockFunction(defaultFs.existsSync)
32
+ ) {
33
+ return defaultFs;
34
+ }
35
+ return realFs;
36
+ }
16
37
 
17
38
  /**
18
39
  * Resolves path to application config file (application.yaml, application.json, or legacy variables.yaml).
@@ -23,6 +44,7 @@ const fs = require('fs');
23
44
  * @throws {Error} If no config file found
24
45
  */
25
46
  function resolveApplicationConfigPath(appPath) {
47
+ const fs = getResolverFs();
26
48
  if (!appPath || typeof appPath !== 'string') {
27
49
  throw new Error('App path is required and must be a string');
28
50
  }
@@ -59,6 +81,7 @@ const RBAC_NAMES = ['rbac.yaml', 'rbac.yml', 'rbac.json'];
59
81
  * @returns {string|null} Absolute path to RBAC file, or null if none exist
60
82
  */
61
83
  function resolveRbacPath(appPath) {
84
+ const fs = getResolverFs();
62
85
  if (!appPath || typeof appPath !== 'string') {
63
86
  throw new Error('App path is required and must be a string');
64
87
  }