@aifabrix/builder 2.43.0 → 2.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/.cursor/rules/cli-layout.mdc +75 -0
  3. package/.cursor/rules/project-rules.mdc +8 -0
  4. package/.npmrc.token +1 -0
  5. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  6. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  7. package/.nyc_output/processinfo/index.json +1 -0
  8. package/README.md +1 -1
  9. package/anchor-docs/README.md +10 -0
  10. package/anchor-docs/_TEMPLATE +24 -0
  11. package/bin/aifabrix.js +13 -4
  12. package/integration/hubspot-test/README.md +31 -0
  13. package/integration/hubspot-test/create-hubspot.js +5 -5
  14. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  15. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  16. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  17. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  18. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  19. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  20. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  21. package/integration/hubspot-test/test.js +35 -43
  22. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  23. package/integration/roundtrip-test-local/README.md +144 -0
  24. package/integration/roundtrip-test-local/application.yaml +13 -0
  25. package/integration/roundtrip-test-local/env.template +15 -0
  26. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  27. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  28. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  29. package/integration/roundtrip-test-local2/README.md +144 -0
  30. package/integration/roundtrip-test-local2/application.yaml +13 -0
  31. package/integration/roundtrip-test-local2/env.template +15 -0
  32. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  33. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  34. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  35. package/integration/test/wizard.yaml +8 -0
  36. package/jest.config.default.js +10 -0
  37. package/jest.config.integration.fixtures.js +22 -0
  38. package/jest.config.integration.js +21 -18
  39. package/jest.config.isolated.js +10 -0
  40. package/jest.projects.js +301 -0
  41. package/lib/api/certificates.api.js +62 -0
  42. package/lib/api/datasources-core.api.js +3 -3
  43. package/lib/api/dev-mtls-request.js +110 -0
  44. package/lib/api/dev-server-https.js +145 -0
  45. package/lib/api/dev.api.js +133 -144
  46. package/lib/api/index.js +11 -3
  47. package/lib/api/pipeline.api.js +67 -20
  48. package/lib/api/types/certificates.types.js +48 -0
  49. package/lib/api/types/dev.types.js +4 -3
  50. package/lib/api/types/pipeline.types.js +8 -5
  51. package/lib/api/types/validation-run.types.js +56 -0
  52. package/lib/api/validation-run.api.js +111 -0
  53. package/lib/api/validation-runner.js +109 -0
  54. package/lib/app/certification-show-enrich.js +129 -0
  55. package/lib/app/certification-verify-rows.js +60 -0
  56. package/lib/app/config.js +1 -1
  57. package/lib/app/deploy-status-display.js +2 -2
  58. package/lib/app/deploy.js +7 -6
  59. package/lib/app/display.js +2 -1
  60. package/lib/app/dockerfile.js +3 -2
  61. package/lib/app/down.js +2 -1
  62. package/lib/app/helpers.js +6 -5
  63. package/lib/app/index.js +27 -8
  64. package/lib/app/list.js +7 -6
  65. package/lib/app/push.js +4 -3
  66. package/lib/app/register.js +16 -7
  67. package/lib/app/rotate-secret.js +14 -13
  68. package/lib/app/run-container-start.js +184 -0
  69. package/lib/app/run-docker-fallback.js +108 -0
  70. package/lib/app/run-env-compose.js +30 -42
  71. package/lib/app/run-helpers.js +49 -126
  72. package/lib/app/run-infra-requirements.js +30 -0
  73. package/lib/app/run-resolve-image.js +21 -0
  74. package/lib/app/run.js +74 -21
  75. package/lib/app/show-display.js +44 -1
  76. package/lib/app/show.js +93 -9
  77. package/lib/build/index.js +13 -10
  78. package/lib/certification/cli-cert-sync-skip.js +21 -0
  79. package/lib/certification/merge-certification-from-artifact.js +185 -0
  80. package/lib/certification/post-unified-cert-sync.js +33 -0
  81. package/lib/certification/sync-after-external-command.js +52 -0
  82. package/lib/certification/sync-system-certification.js +197 -0
  83. package/lib/cli/index.js +2 -0
  84. package/lib/cli/setup-app.help.js +67 -0
  85. package/lib/cli/setup-app.js +61 -121
  86. package/lib/cli/setup-app.test-commands.js +195 -0
  87. package/lib/cli/setup-auth.js +19 -5
  88. package/lib/cli/setup-credential-deployment.js +22 -8
  89. package/lib/cli/setup-dev-path-commands.js +124 -0
  90. package/lib/cli/setup-dev.js +170 -113
  91. package/lib/cli/setup-environment.js +7 -1
  92. package/lib/cli/setup-external-system.js +84 -23
  93. package/lib/cli/setup-infra.js +126 -47
  94. package/lib/cli/setup-parameters.js +32 -0
  95. package/lib/cli/setup-secrets.js +137 -18
  96. package/lib/cli/setup-service-user.js +1 -1
  97. package/lib/cli/setup-utility.js +54 -22
  98. package/lib/commands/app-down.js +5 -7
  99. package/lib/commands/app-install.js +14 -7
  100. package/lib/commands/app-logs.js +13 -10
  101. package/lib/commands/app-shell.js +4 -1
  102. package/lib/commands/app-test.js +25 -19
  103. package/lib/commands/app.js +32 -11
  104. package/lib/commands/auth-config.js +6 -6
  105. package/lib/commands/auth-status.js +4 -3
  106. package/lib/commands/credential-env.js +4 -3
  107. package/lib/commands/credential-list.js +5 -4
  108. package/lib/commands/credential-push.js +4 -3
  109. package/lib/commands/datasource-unified-test-cli.js +428 -0
  110. package/lib/commands/datasource-unified-test-cli.options.js +191 -0
  111. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +106 -0
  112. package/lib/commands/datasource-validation-cli.js +143 -0
  113. package/lib/commands/datasource.js +125 -95
  114. package/lib/commands/deployment-list.js +6 -5
  115. package/lib/commands/dev-cli-handlers.js +122 -18
  116. package/lib/commands/dev-down.js +4 -3
  117. package/lib/commands/dev-init.js +231 -116
  118. package/lib/commands/dev-show-display.js +473 -0
  119. package/lib/commands/login-credentials.js +3 -2
  120. package/lib/commands/login-device.js +4 -3
  121. package/lib/commands/login.js +5 -4
  122. package/lib/commands/logout.js +8 -7
  123. package/lib/commands/parameters-validate.js +54 -0
  124. package/lib/commands/repair-datasource.js +314 -68
  125. package/lib/commands/repair-env-template.js +2 -2
  126. package/lib/commands/repair.js +21 -3
  127. package/lib/commands/secrets-list.js +23 -12
  128. package/lib/commands/secrets-remove-all.js +220 -0
  129. package/lib/commands/secrets-remove.js +21 -12
  130. package/lib/commands/secrets-set.js +21 -12
  131. package/lib/commands/secrets-validate.js +4 -4
  132. package/lib/commands/secure.js +10 -9
  133. package/lib/commands/service-user.js +26 -25
  134. package/lib/commands/test-e2e-external.js +27 -1
  135. package/lib/commands/up-common.js +3 -2
  136. package/lib/commands/up-dataplane.js +29 -16
  137. package/lib/commands/up-miso.js +19 -29
  138. package/lib/commands/upload.js +149 -39
  139. package/lib/commands/wizard-core-helpers.js +1 -1
  140. package/lib/commands/wizard-dataplane.js +4 -3
  141. package/lib/commands/wizard-helpers.js +3 -3
  142. package/lib/commands/wizard.js +2 -2
  143. package/lib/core/admin-secrets.js +14 -5
  144. package/lib/core/audit-logger.js +12 -4
  145. package/lib/core/config-attach-extensions.js +46 -0
  146. package/lib/core/config-runtime-paths.js +29 -0
  147. package/lib/core/config.js +55 -56
  148. package/lib/core/diff.js +3 -2
  149. package/lib/core/ensure-encryption-key.js +1 -1
  150. package/lib/core/secrets-ensure-infra.js +77 -0
  151. package/lib/core/secrets-ensure.js +120 -64
  152. package/lib/core/secrets-env-write.js +35 -7
  153. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  154. package/lib/core/secrets.js +200 -37
  155. package/lib/core/templates-env.js +4 -3
  156. package/lib/datasource/abac-validator.js +1 -10
  157. package/lib/datasource/deploy.js +75 -53
  158. package/lib/datasource/field-reference-validator.js +9 -6
  159. package/lib/datasource/integration-context.js +63 -0
  160. package/lib/datasource/list.js +8 -7
  161. package/lib/datasource/log-viewer.js +189 -67
  162. package/lib/datasource/resolve-app.js +4 -4
  163. package/lib/datasource/test-e2e.js +113 -146
  164. package/lib/datasource/test-integration.js +114 -122
  165. package/lib/datasource/unified-validation-run-body.js +68 -0
  166. package/lib/datasource/unified-validation-run-post.js +23 -0
  167. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  168. package/lib/datasource/unified-validation-run.js +93 -0
  169. package/lib/datasource/validate.js +157 -13
  170. package/lib/deployment/deployer.js +4 -3
  171. package/lib/deployment/environment.js +7 -6
  172. package/lib/deployment/push.js +17 -8
  173. package/lib/external-system/delete.js +4 -3
  174. package/lib/external-system/deploy.js +166 -53
  175. package/lib/external-system/download-helpers.js +1 -1
  176. package/lib/external-system/download.js +7 -6
  177. package/lib/external-system/generator.js +92 -6
  178. package/lib/external-system/integration-test-dispatch.js +26 -0
  179. package/lib/external-system/test-execution.js +5 -1
  180. package/lib/external-system/test-helpers.js +0 -4
  181. package/lib/external-system/test-system-level-helpers.js +110 -0
  182. package/lib/external-system/test-system-level.js +83 -44
  183. package/lib/external-system/test.js +59 -8
  184. package/lib/generator/builders.js +23 -11
  185. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  186. package/lib/generator/external.js +16 -4
  187. package/lib/generator/helpers.js +58 -3
  188. package/lib/generator/index.js +4 -0
  189. package/lib/generator/split-readme.js +12 -7
  190. package/lib/generator/split-variables.js +2 -1
  191. package/lib/generator/split.js +1 -1
  192. package/lib/generator/wizard-readme.js +3 -3
  193. package/lib/generator/wizard.js +8 -8
  194. package/lib/infrastructure/compose.js +70 -7
  195. package/lib/infrastructure/helpers-docker-check.js +67 -0
  196. package/lib/infrastructure/helpers.js +203 -42
  197. package/lib/infrastructure/index.js +31 -18
  198. package/lib/infrastructure/services.js +21 -67
  199. package/lib/internal/fs-real-sync.js +104 -0
  200. package/lib/internal/node-fs.js +98 -0
  201. package/lib/parameters/database-secret-values.js +173 -0
  202. package/lib/parameters/infra-kv-discovery.js +121 -0
  203. package/lib/parameters/infra-parameter-catalog.js +458 -0
  204. package/lib/parameters/infra-parameter-validate.js +64 -0
  205. package/lib/schema/application-schema.json +37 -17
  206. package/lib/schema/datasource-test-run.schema.json +493 -0
  207. package/lib/schema/deployment-rules.yaml +102 -63
  208. package/lib/schema/external-datasource.schema.json +1200 -442
  209. package/lib/schema/external-system.schema.json +203 -5
  210. package/lib/schema/flag-map-validation-run.json +31 -0
  211. package/lib/schema/infra-parameter.schema.json +106 -0
  212. package/lib/schema/infra.parameter.yaml +421 -0
  213. package/lib/schema/type/credential-auth-templates.json +40 -0
  214. package/lib/schema/type/document-storage.json +226 -0
  215. package/lib/schema/type/message-service.json +123 -0
  216. package/lib/schema/type/vector-store.json +88 -0
  217. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  218. package/lib/utils/api-error-handler.js +2 -2
  219. package/lib/utils/api.js +77 -17
  220. package/lib/utils/app-register-api.js +3 -2
  221. package/lib/utils/app-register-auth.js +1 -1
  222. package/lib/utils/app-register-config.js +4 -4
  223. package/lib/utils/app-register-display.js +3 -2
  224. package/lib/utils/app-register-validator.js +3 -2
  225. package/lib/utils/app-run-containers.js +26 -22
  226. package/lib/utils/app-scoped-config.js +31 -0
  227. package/lib/utils/app-service-env-from-builder.js +164 -0
  228. package/lib/utils/build-copy.js +1 -1
  229. package/lib/utils/build-helpers.js +20 -20
  230. package/lib/utils/build-resolve-image.js +165 -0
  231. package/lib/utils/cli-layout-chalk.js +8 -0
  232. package/lib/utils/cli-test-layout-chalk.js +267 -0
  233. package/lib/utils/cli-utils.js +88 -11
  234. package/lib/utils/compose-db-passwords.js +138 -0
  235. package/lib/utils/compose-generate-docker-compose.js +216 -0
  236. package/lib/utils/compose-generator.js +197 -291
  237. package/lib/utils/compose-miso-env.js +18 -0
  238. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  239. package/lib/utils/config-paths.js +166 -7
  240. package/lib/utils/config-scoped-resources-preference.js +41 -0
  241. package/lib/utils/configuration-env-resolver.js +11 -8
  242. package/lib/utils/controller-deployment-outcome.js +68 -0
  243. package/lib/utils/credential-display.js +2 -2
  244. package/lib/utils/credential-secrets-env.js +5 -5
  245. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  246. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  247. package/lib/utils/datasource-test-run-certificate-tty.js +82 -0
  248. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  249. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  250. package/lib/utils/datasource-test-run-display.js +459 -0
  251. package/lib/utils/datasource-test-run-exit.js +83 -0
  252. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  253. package/lib/utils/datasource-test-run-report-version.js +51 -0
  254. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  255. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  256. package/lib/utils/datasource-validation-watch.js +266 -0
  257. package/lib/utils/declarative-url-ports.js +47 -0
  258. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  259. package/lib/utils/dev-ca-install.js +185 -23
  260. package/lib/utils/dev-cert-helper.js +266 -17
  261. package/lib/utils/dev-hosts-helper.js +307 -0
  262. package/lib/utils/dev-init-cert-hints.js +37 -0
  263. package/lib/utils/dev-init-health-messages.js +52 -0
  264. package/lib/utils/dev-init-resolve.js +86 -0
  265. package/lib/utils/dev-init-ssh-merge.js +65 -0
  266. package/lib/utils/dev-ssh-config-helper.js +196 -0
  267. package/lib/utils/dev-user-groups.js +93 -0
  268. package/lib/utils/docker-build.js +42 -17
  269. package/lib/utils/docker-exec.js +28 -0
  270. package/lib/utils/docker-manifest-public-port.js +116 -0
  271. package/lib/utils/docker-not-running-hint.js +52 -0
  272. package/lib/utils/docker.js +98 -11
  273. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  274. package/lib/utils/env-config-loader.js +10 -91
  275. package/lib/utils/env-copy.js +19 -10
  276. package/lib/utils/env-map.js +35 -8
  277. package/lib/utils/env-template.js +2 -2
  278. package/lib/utils/environment-scoped-resources.js +144 -0
  279. package/lib/utils/error-formatter.js +92 -13
  280. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  281. package/lib/utils/error-formatters/network-errors.js +2 -1
  282. package/lib/utils/error-formatters/permission-errors.js +2 -1
  283. package/lib/utils/error-formatters/validation-errors.js +2 -1
  284. package/lib/utils/external-readme.js +8 -1
  285. package/lib/utils/external-system-display.js +242 -136
  286. package/lib/utils/external-system-local-test-tty.js +389 -0
  287. package/lib/utils/external-system-readiness-core.js +377 -0
  288. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  289. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  290. package/lib/utils/external-system-readiness-display.js +186 -0
  291. package/lib/utils/external-system-system-test-tty-overview.js +120 -0
  292. package/lib/utils/external-system-system-test-tty.js +417 -0
  293. package/lib/utils/external-system-test-helpers.js +24 -6
  294. package/lib/utils/external-system-validators.js +30 -12
  295. package/lib/utils/health-check-url.js +119 -0
  296. package/lib/utils/health-check.js +59 -25
  297. package/lib/utils/help-builder.js +11 -8
  298. package/lib/utils/image-version.js +4 -8
  299. package/lib/utils/infra-containers.js +4 -7
  300. package/lib/utils/infra-env-defaults.js +162 -0
  301. package/lib/utils/infra-status-display.js +167 -0
  302. package/lib/utils/infra-status.js +16 -8
  303. package/lib/utils/local-secrets.js +3 -4
  304. package/lib/utils/paths.js +148 -47
  305. package/lib/utils/port-resolver.js +10 -23
  306. package/lib/utils/redis-env-scope.js +62 -0
  307. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  308. package/lib/utils/remote-builder-validation.js +99 -0
  309. package/lib/utils/remote-dev-auth.js +117 -21
  310. package/lib/utils/remote-docker-env.js +67 -15
  311. package/lib/utils/remote-secrets-loader.js +13 -4
  312. package/lib/utils/resolve-docker-image-ref.js +124 -0
  313. package/lib/utils/schema-loader.js +22 -9
  314. package/lib/utils/secrets-bash-kv.js +25 -0
  315. package/lib/utils/secrets-generator.js +169 -49
  316. package/lib/utils/secrets-helpers.js +70 -59
  317. package/lib/utils/secrets-kv-scope.js +60 -0
  318. package/lib/utils/secrets-utils.js +32 -38
  319. package/lib/utils/secrets-validation.js +3 -1
  320. package/lib/utils/secrets-yaml-preserve.js +109 -0
  321. package/lib/utils/ssh-key-helper.js +4 -2
  322. package/lib/utils/template-helpers.js +2 -2
  323. package/lib/utils/test-log-writer.js +3 -3
  324. package/lib/utils/token-manager.js +1 -2
  325. package/lib/utils/url-declarative-public-base.js +188 -0
  326. package/lib/utils/url-declarative-resolve-build.js +493 -0
  327. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  328. package/lib/utils/url-declarative-resolve.js +220 -0
  329. package/lib/utils/url-declarative-token-parse.js +74 -0
  330. package/lib/utils/url-declarative-url-flags.js +50 -0
  331. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  332. package/lib/utils/url-public-path-prefix.js +34 -0
  333. package/lib/utils/urls-local-registry.js +220 -0
  334. package/lib/utils/validation-report-tty-kit.js +77 -0
  335. package/lib/utils/validation-run-poll.js +112 -0
  336. package/lib/utils/validation-run-post-retry.js +85 -0
  337. package/lib/utils/validation-run-request.js +116 -0
  338. package/lib/utils/variable-transformer.js +21 -4
  339. package/lib/utils/yaml-preserve.js +33 -14
  340. package/lib/validation/datasource-warnings.js +56 -0
  341. package/lib/validation/env-template-auth.js +1 -1
  342. package/lib/validation/external-manifest-validator.js +27 -7
  343. package/lib/validation/validate-display.js +37 -31
  344. package/lib/validation/validate-external-cert-sync.js +23 -0
  345. package/lib/validation/validate.js +8 -14
  346. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  347. package/lib/validation/validator.js +22 -65
  348. package/lib/validation/wizard-config-validator.js +2 -1
  349. package/package.json +9 -4
  350. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  351. package/scripts/diagnose-cli.js +150 -0
  352. package/scripts/install-local.js +307 -55
  353. package/scripts/pnpm-global-remove.js +48 -0
  354. package/templates/README.md +15 -2
  355. package/templates/applications/dataplane/application.yaml +52 -2
  356. package/templates/applications/dataplane/env.template +79 -17
  357. package/templates/applications/dataplane/rbac.yaml +8 -0
  358. package/templates/applications/keycloak/application.yaml +9 -1
  359. package/templates/applications/keycloak/env.template +15 -6
  360. package/templates/applications/miso-controller/application.yaml +10 -2
  361. package/templates/applications/miso-controller/env.template +42 -12
  362. package/templates/applications/miso-controller/rbac.yaml +5 -0
  363. package/templates/external-system/README.md.hbs +20 -7
  364. package/templates/external-system/deploy.js.hbs +5 -5
  365. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  366. package/templates/infra/compose.yaml.hbs +33 -16
  367. package/templates/infra/servers.json.hbs +3 -1
  368. package/templates/python/docker-compose.hbs +16 -0
  369. package/templates/typescript/docker-compose.hbs +16 -0
  370. package/lib/api/external-test.api.js +0 -111
  371. package/lib/schema/env-config.yaml +0 -60
@@ -11,16 +11,19 @@
11
11
  */
12
12
 
13
13
  /**
14
- * Set of attribute names plus dimension keys valid for field references.
15
- * Used for primaryKey (schema allows dimensions or attributes) and other paths (attributes only).
14
+ * Set of attribute names plus root dimension keys valid for field references.
15
+ * Used for primaryKey (schema allows dimension keys or attributes) and other paths (attributes only).
16
16
  *
17
17
  * @param {Object} parsed - Parsed datasource object
18
18
  * @returns {{ attributes: string[], attributesAndDimensions: Set<string> }}
19
19
  */
20
20
  function getNormalizedSets(parsed) {
21
21
  const attributes = Object.keys(parsed?.fieldMappings?.attributes ?? {});
22
- const dimensions = Object.keys(parsed?.fieldMappings?.dimensions ?? {});
23
- const attributesAndDimensions = new Set([...attributes, ...dimensions]);
22
+ const rootDims = Object.keys(parsed?.dimensions ?? {}).filter(k => {
23
+ const b = parsed.dimensions[k];
24
+ return b && typeof b === 'object';
25
+ });
26
+ const attributesAndDimensions = new Set([...attributes, ...rootDims]);
24
27
  return { attributes, attributesAndDimensions };
25
28
  }
26
29
 
@@ -31,7 +34,7 @@ function checkPrimaryKey(parsed, attributesAndDimensions, errors) {
31
34
  primaryKey.forEach((field, i) => {
32
35
  if (typeof field === 'string' && field !== '' && !attributesAndDimensions.has(field)) {
33
36
  errors.push(
34
- `primaryKey[${i}]: field '${field}' does not exist in fieldMappings.attributes or fieldMappings.dimensions. Each primaryKey value must reference an attribute or dimension name.`
37
+ `primaryKey[${i}]: field '${field}' does not exist in fieldMappings.attributes or root dimensions. Each primaryKey value must reference an attribute or dimension key.`
35
38
  );
36
39
  }
37
40
  });
@@ -98,7 +101,7 @@ function checkIndexingAndValidation(parsed, normalizedAttributes, errors) {
98
101
  /**
99
102
  * Validates that all field references in indexing, validation, quality,
100
103
  * primaryKey, and exposed.profiles exist in fieldMappings.attributes (or
101
- * dimensions for primaryKey per schema). When fieldMappings.attributes is
104
+ * root dimension keys for primaryKey per schema). When fieldMappings.attributes is
102
105
  * missing or empty, returns no errors (skip check, matching dataplane behavior).
103
106
  *
104
107
  * @function validateFieldReferences
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @fileoverview Shared integration app resolution (system key, datasource file lookup).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs').promises;
9
+ const { getIntegrationPath } = require('../utils/paths');
10
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
11
+ const { loadConfigFile } = require('../utils/config-format');
12
+
13
+ /**
14
+ * @param {string} appKey - Integration app key
15
+ * @returns {Promise<string>} systemKey
16
+ */
17
+ async function getSystemKeyFromAppKey(appKey) {
18
+ const appPath = getIntegrationPath(appKey);
19
+ const configPath = resolveApplicationConfigPath(appPath);
20
+ const config = loadConfigFile(configPath);
21
+ if (!config.externalIntegration || !config.externalIntegration.systems || config.externalIntegration.systems.length === 0) {
22
+ throw new Error(`No externalIntegration.systems found in ${configPath}`);
23
+ }
24
+ const systemFile = config.externalIntegration.systems[0];
25
+ const systemPath = path.isAbsolute(systemFile)
26
+ ? systemFile
27
+ : path.join(appPath, systemFile);
28
+ const systemContent = await fs.readFile(systemPath, 'utf8');
29
+ const yaml = require('js-yaml');
30
+ const systemConfig = yaml.load(systemContent);
31
+ return systemConfig?.key || path.basename(systemFile, '-system.yaml').replace('-system', '');
32
+ }
33
+
34
+ /**
35
+ * Find a datasource filename by matching the key inside the file.
36
+ * @param {string} appPath
37
+ * @param {string} schemaBasePath
38
+ * @param {string[]} datasourceFiles
39
+ * @param {string} datasourceKey
40
+ * @returns {string|null}
41
+ */
42
+ function findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey) {
43
+ const fsSync = require('fs');
44
+ for (const f of datasourceFiles) {
45
+ if (!f || typeof f !== 'string') continue;
46
+ const fullPath = path.isAbsolute(schemaBasePath)
47
+ ? path.join(schemaBasePath, f)
48
+ : path.join(appPath, schemaBasePath, f);
49
+ if (!fsSync.existsSync(fullPath)) continue;
50
+ try {
51
+ const parsed = loadConfigFile(fullPath);
52
+ if (parsed && parsed.key === datasourceKey) return f;
53
+ } catch {
54
+ // skip unreadable or invalid files
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+
60
+ module.exports = {
61
+ getSystemKeyFromAppKey,
62
+ findDatasourceFileByKey
63
+ };
@@ -1,7 +1,8 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Datasource List Command
3
4
  *
4
- * Lists datasources from the dataplane (GET /api/v1/external/).
5
+ * Lists datasources from the dataplane (GET /api/v1/external).
5
6
  * Resolves dataplane URL from the controller, then calls the dataplane list API.
6
7
  *
7
8
  * @fileoverview Datasource listing for AI Fabrix Builder
@@ -74,7 +75,7 @@ function extractFromPaginatedFormat(apiResponse) {
74
75
  * @param {Object} apiResponse - API response object
75
76
  */
76
77
  function logInvalidResponseError(apiResponse) {
77
- logger.error(chalk.red('Invalid response: expected data array or items array'));
78
+ logger.error(formatBlockingError('Invalid response: expected data array or items array'));
78
79
  logger.error(chalk.gray('\nAPI response type:'), typeof apiResponse);
79
80
  logger.error(chalk.gray('API response:'), JSON.stringify(apiResponse, null, 2));
80
81
  }
@@ -177,7 +178,7 @@ async function getDeviceTokenFromConfig(config) {
177
178
  */
178
179
  function validateDatasourceListingAuth(token, controllerUrl) {
179
180
  if (!token || !controllerUrl || (typeof controllerUrl === 'string' && !controllerUrl.trim())) {
180
- logger.error(chalk.red('Not logged in. Run: aifabrix login'));
181
+ logger.error(formatBlockingError('Not logged in. Run: aifabrix login'));
181
182
  logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
182
183
  process.exit(1);
183
184
  }
@@ -215,7 +216,7 @@ function handleDatasourceApiError(response, dataplaneUrl = null) {
215
216
  function validateControllerUrl(controllerUrl) {
216
217
  const trimmed = controllerUrl.trim();
217
218
  if (!trimmed) {
218
- logger.error(chalk.red('Controller URL is empty.'));
219
+ logger.error(formatBlockingError('Controller URL is empty.'));
219
220
  logger.error(chalk.gray(` Controller URL from config: ${JSON.stringify(controllerUrl)}`));
220
221
  logger.error(chalk.gray(' Run: aifabrix login --method device --controller <url>'));
221
222
  process.exit(1);
@@ -238,7 +239,7 @@ async function resolveAndValidateDataplaneUrl(controllerUrl, environment, authCo
238
239
  // discoverDataplaneUrl already logs progress and success messages
239
240
  dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
240
241
  } catch (error) {
241
- logger.error(chalk.red('Failed to resolve dataplane URL:'), error.message);
242
+ logger.error(formatBlockingError('Failed to resolve dataplane URL:'), error.message);
242
243
  logger.error(chalk.gray('\nThe dataplane URL is automatically discovered from the controller.'));
243
244
  logger.error(chalk.gray('If discovery fails, ensure you are logged in and the controller is accessible:'));
244
245
  logger.error(chalk.gray(' aifabrix login'));
@@ -248,7 +249,7 @@ async function resolveAndValidateDataplaneUrl(controllerUrl, environment, authCo
248
249
  }
249
250
 
250
251
  if (!dataplaneUrl || typeof dataplaneUrl !== 'string' || !dataplaneUrl.trim()) {
251
- logger.error(chalk.red('Dataplane URL is empty.'));
252
+ logger.error(formatBlockingError('Dataplane URL is empty.'));
252
253
  logger.error(chalk.gray('The dataplane URL could not be discovered from the controller.'));
253
254
  logger.error(chalk.gray('Ensure the dataplane service is registered in the controller.'));
254
255
  process.exit(1);
@@ -295,7 +296,7 @@ async function listDatasources(_options) {
295
296
  // Resolve dataplane URL first (required for list call)
296
297
  const dataplaneUrl = await resolveAndValidateDataplaneUrl(controllerUrl, environment, authConfig);
297
298
 
298
- // List datasources from dataplane (GET /api/v1/external/)
299
+ // List datasources from dataplane (GET /api/v1/external)
299
300
  const response = await listDatasourcesFromDataplane(dataplaneUrl, authConfig);
300
301
 
301
302
  if (!response.success || !response.data) {
@@ -1,17 +1,19 @@
1
1
  /**
2
- * Log viewer for E2E and integration test logs - format and display JSON logs.
3
- * @fileoverview Read and format test-e2e / test-integration debug logs for terminal
2
+ * Log viewer for E2E, integration, and structural (`datasource test`) debug logs.
3
+ * @fileoverview Read and format saved JSON logs under integration/<app>/logs/
4
4
  * @author AI Fabrix Team
5
5
  * @version 2.0.0
6
6
  */
7
7
  /* eslint-disable max-statements, complexity, max-depth -- Formatter functions; display branches by design */
8
8
 
9
9
  const path = require('path');
10
- const fs = require('fs').promises;
10
+ // Use node:fs so suites that jest.mock('fs') do not break real-disk log resolution (structural tests, CI).
11
+ const fsp = require('node:fs').promises;
11
12
  const chalk = require('chalk');
12
13
  const logger = require('../utils/logger');
13
14
  const { resolveAppKeyForDatasource } = require('./resolve-app');
14
15
  const { getIntegrationPath } = require('../utils/paths');
16
+ const { sectionTitle, headerKeyValue, formatBlockingError, successGlyph, failureGlyph } = require('../utils/cli-test-layout-chalk');
15
17
 
16
18
  /**
17
19
  * Get the path to the latest log file in a directory matching a glob-like pattern
@@ -22,7 +24,7 @@ const { getIntegrationPath } = require('../utils/paths');
22
24
  async function getLatestLogPath(logsDir, pattern) {
23
25
  let entries;
24
26
  try {
25
- entries = await fs.readdir(logsDir, { withFileTypes: true });
27
+ entries = await fsp.readdir(logsDir, { withFileTypes: true });
26
28
  } catch (err) {
27
29
  if (err.code === 'ENOENT') return null;
28
30
  throw err;
@@ -33,12 +35,46 @@ async function getLatestLogPath(logsDir, pattern) {
33
35
  .map(e => path.join(logsDir, e.name));
34
36
  if (files.length === 0) return null;
35
37
  const withStats = await Promise.all(
36
- files.map(async f => ({ path: f, mtime: (await fs.stat(f)).mtimeMs }))
38
+ files.map(async f => ({ path: f, mtime: (await fsp.stat(f)).mtimeMs }))
37
39
  );
38
40
  withStats.sort((a, b) => b.mtime - a.mtime);
39
41
  return withStats[0].path;
40
42
  }
41
43
 
44
+ /**
45
+ * Structural validation logs use prefix `test-` but must not pick up `test-e2e-` or `test-integration-`.
46
+ * @param {string} name - File name only
47
+ * @returns {boolean}
48
+ */
49
+ function isStructuralTestLogFileName(name) {
50
+ if (!name || typeof name !== 'string' || !name.endsWith('.json')) return false;
51
+ if (name.startsWith('test-e2e-') || name.startsWith('test-integration-')) return false;
52
+ return name.startsWith('test-');
53
+ }
54
+
55
+ /**
56
+ * Latest structural `datasource test --debug` log in logsDir.
57
+ * @param {string} logsDir
58
+ * @returns {Promise<string|null>}
59
+ */
60
+ async function getLatestStructuralTestLogPath(logsDir) {
61
+ let entries;
62
+ try {
63
+ entries = await fsp.readdir(logsDir, { withFileTypes: true });
64
+ } catch (err) {
65
+ if (err.code === 'ENOENT') return null;
66
+ throw err;
67
+ }
68
+ const names = entries
69
+ .filter(e => e.isFile() && isStructuralTestLogFileName(e.name))
70
+ .map(e => e.name);
71
+ if (names.length === 0) return null;
72
+ // Structural logs embed a sortable timestamp in the filename (`test-YYYY-...Z.json`).
73
+ // Prefer lexicographic ordering to avoid filesystem timestamp resolution issues in CI.
74
+ names.sort((a, b) => b.localeCompare(a));
75
+ return path.join(logsDir, names[0]);
76
+ }
77
+
42
78
  /**
43
79
  * Truncate string for display
44
80
  * @param {string} s - String
@@ -55,62 +91,88 @@ function truncate(s, maxLen = 60) {
55
91
  * @param {Object} data - Parsed log JSON (request, response, error)
56
92
  * @param {string} [fileName] - Log file name for header
57
93
  */
58
- function formatE2ELog(data, fileName) {
59
- logger.log(chalk.blue('\n——— E2E Log') + (fileName ? chalk.gray(` ${fileName}`) : ''));
94
+ function logE2ERequestSection(data) {
60
95
  const req = data.request || {};
61
- logger.log(chalk.cyan('Request:'));
62
- logger.log(chalk.gray(` sourceIdOrKey: ${req.sourceIdOrKey ?? '—'}`));
96
+ logger.log('');
97
+ logger.log(sectionTitle('Request:'));
98
+ logger.log(headerKeyValue('sourceIdOrKey:', String(req.sourceIdOrKey ?? '—')));
63
99
  if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
64
100
  if (req.cleanup !== undefined) logger.log(chalk.gray(` cleanup: ${req.cleanup}`));
65
- if (req.primaryKeyValue !== undefined) logger.log(chalk.gray(` primaryKeyValue: ${truncate(JSON.stringify(req.primaryKeyValue))}`));
101
+ if (req.primaryKeyValue !== undefined) {
102
+ logger.log(chalk.gray(` primaryKeyValue: ${truncate(JSON.stringify(req.primaryKeyValue))}`));
103
+ }
104
+ return req;
105
+ }
106
+
107
+ function logE2EStepsSection(steps) {
108
+ if (!Array.isArray(steps) || steps.length === 0) return;
109
+ logger.log(sectionTitle('Steps:'));
110
+ for (const step of steps) {
111
+ const name = step.name || step.step || 'unknown';
112
+ const ok = step.success !== false && !step.error;
113
+ logger.log(` ${ok ? successGlyph() : failureGlyph()} ${chalk.white(name)}`);
114
+ if (step.error) logger.log(chalk.red(` ${step.error}`));
115
+ if (step.message && ok) logger.log(chalk.gray(` ${step.message}`));
116
+ if ((name === 'sync' || step.step === 'sync') && step.evidence && step.evidence.jobs) {
117
+ for (const job of step.evidence.jobs) {
118
+ const audit = job.audit || {};
119
+ const parts = [];
120
+ if (job.recordsProcessed !== undefined && job.recordsProcessed !== null) parts.push(`${job.recordsProcessed} processed`);
121
+ if (job.totalRecords !== undefined && job.totalRecords !== null) parts.push(`total: ${job.totalRecords}`);
122
+ if (
123
+ (audit.inserted !== undefined && audit.inserted !== null) ||
124
+ (audit.updated !== undefined && audit.updated !== null) ||
125
+ (audit.deleted !== undefined && audit.deleted !== null)
126
+ ) {
127
+ parts.push(`(inserted: ${audit.inserted ?? 0}, updated: ${audit.updated ?? 0}, deleted: ${audit.deleted ?? 0})`);
128
+ }
129
+ if (parts.length) logger.log(chalk.gray(` Job: ${parts.join(' ')}`));
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ function logE2EAuditTraceSection(req, res) {
136
+ if (!res.auditLog || !Array.isArray(res.auditLog) || res.auditLog.length === 0) return;
137
+ logger.log('');
138
+ logger.log(sectionTitle('CIP execution trace(s):'));
139
+ logger.log(chalk.gray(`${res.auditLog.length}`));
140
+ const baseUrl = (req.dataplaneUrl || '').toString().replace(/\/$/, '');
141
+ const sourceIdOrKey = req.sourceIdOrKey || '';
142
+ res.auditLog.slice(0, 3).forEach((trace, i) => {
143
+ const id = trace.executionId || trace.id || trace.traceId;
144
+ if (!id) return;
145
+ const idStr = String(id);
146
+ logger.log(chalk.gray(` ${i + 1}. executionId: ${idStr}`));
147
+ if (baseUrl && sourceIdOrKey) {
148
+ const executionUrl = `${baseUrl}/api/v1/external/${sourceIdOrKey}/executions/${idStr}`;
149
+ logger.log(chalk.gray(` Link: ${executionUrl}`));
150
+ }
151
+ });
152
+ }
153
+
154
+ function logE2EResponseSection(req, data) {
66
155
  if (data.error) {
67
- logger.log(chalk.red('Error: ') + data.error);
156
+ logger.log(formatBlockingError(`Error: ${data.error}`));
68
157
  return;
69
158
  }
70
159
  const res = data.response || {};
71
- logger.log(chalk.cyan('Response:'));
160
+ logger.log('');
161
+ logger.log(sectionTitle('Response:'));
72
162
  logger.log(chalk.gray(` success: ${res.success}`));
73
163
  if (res.status) logger.log(chalk.gray(` status: ${res.status}`));
74
164
  if (res.error) logger.log(chalk.red(` error: ${res.error}`));
75
165
  const steps = res.steps || res.completedActions || [];
76
- if (steps.length > 0) {
77
- logger.log(chalk.cyan('Steps:'));
78
- for (const step of steps) {
79
- const name = step.name || step.step || 'unknown';
80
- const ok = step.success !== false && !step.error;
81
- logger.log(` ${ok ? chalk.green('') : chalk.red('✗')} ${name}`);
82
- if (step.error) logger.log(chalk.red(` ${step.error}`));
83
- if (step.message && ok) logger.log(chalk.gray(` ${step.message}`));
84
- if ((name === 'sync' || step.step === 'sync') && step.evidence && step.evidence.jobs) {
85
- for (const job of step.evidence.jobs) {
86
- const audit = job.audit || {};
87
- const parts = [];
88
- if (job.recordsProcessed !== undefined && job.recordsProcessed !== null) parts.push(`${job.recordsProcessed} processed`);
89
- if (job.totalRecords !== undefined && job.totalRecords !== null) parts.push(`total: ${job.totalRecords}`);
90
- if (audit.inserted !== undefined && audit.inserted !== null || audit.updated !== undefined && audit.updated !== null || audit.deleted !== undefined && audit.deleted !== null) {
91
- parts.push(`(inserted: ${audit.inserted ?? 0}, updated: ${audit.updated ?? 0}, deleted: ${audit.deleted ?? 0})`);
92
- }
93
- if (parts.length) logger.log(chalk.gray(` Job: ${parts.join(' ')}`));
94
- }
95
- }
96
- }
97
- }
98
- if (res.auditLog && Array.isArray(res.auditLog) && res.auditLog.length > 0) {
99
- logger.log(chalk.cyan('CIP execution trace(s): ') + chalk.gray(`${res.auditLog.length}`));
100
- const baseUrl = (req.dataplaneUrl || '').toString().replace(/\/$/, '');
101
- const sourceIdOrKey = req.sourceIdOrKey || '';
102
- res.auditLog.slice(0, 3).forEach((trace, i) => {
103
- const id = trace.executionId || trace.id || trace.traceId;
104
- if (id) {
105
- const idStr = String(id);
106
- logger.log(chalk.gray(` ${i + 1}. executionId: ${idStr}`));
107
- if (baseUrl && sourceIdOrKey) {
108
- const executionUrl = `${baseUrl}/api/v1/external/${sourceIdOrKey}/executions/${idStr}`;
109
- logger.log(chalk.gray(` Link: ${executionUrl}`));
110
- }
111
- }
112
- });
113
- }
166
+ logE2EStepsSection(steps);
167
+ logE2EAuditTraceSection(req, res);
168
+ }
169
+
170
+ function formatE2ELog(data, fileName) {
171
+ logger.log('');
172
+ logger.log(sectionTitle('E2E log'));
173
+ if (fileName) logger.log(chalk.gray(fileName));
174
+ const req = logE2ERequestSection(data);
175
+ logE2EResponseSection(req, data);
114
176
  }
115
177
 
116
178
  /**
@@ -119,48 +181,96 @@ function formatE2ELog(data, fileName) {
119
181
  * @param {string} [fileName] - Log file name for header
120
182
  */
121
183
  function formatIntegrationLog(data, fileName) {
122
- logger.log(chalk.blue('\n——— Integration Log') + (fileName ? chalk.gray(` ${fileName}`) : ''));
184
+ logger.log('');
185
+ logger.log(sectionTitle('Integration log'));
186
+ if (fileName) logger.log(chalk.gray(fileName));
123
187
  const req = data.request || {};
124
- logger.log(chalk.cyan('Request:'));
188
+ logger.log('');
189
+ logger.log(sectionTitle('Request:'));
125
190
  logger.log(chalk.gray(` systemKey: ${req.systemKey ?? '—'}, datasourceKey: ${req.datasourceKey ?? '—'}`));
126
191
  if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
127
192
  if (data.error) {
128
- logger.log(chalk.red('Error: ') + data.error);
193
+ logger.log(formatBlockingError(`Error: ${data.error}`));
129
194
  return;
130
195
  }
131
196
  const res = data.response || {};
132
- logger.log(chalk.cyan('Response:'));
197
+ logger.log('');
198
+ logger.log(sectionTitle('Response:'));
133
199
  logger.log(chalk.gray(` success: ${res.success}`));
134
200
  if (res.error) logger.log(chalk.red(` error: ${res.error}`));
135
201
  const vr = res.validationResults || {};
136
- logger.log(chalk.cyan('Validation:'));
202
+ logger.log(sectionTitle('Validation:'));
137
203
  logger.log(chalk.gray(` isValid: ${vr.isValid}`));
138
204
  if (vr.errors && vr.errors.length) vr.errors.forEach(e => logger.log(chalk.red(` - ${e}`)));
139
205
  const fmr = res.fieldMappingResults || {};
140
206
  if (Object.keys(fmr).length) {
141
- logger.log(chalk.cyan('Field mapping:'));
207
+ logger.log(sectionTitle('Field mapping:'));
142
208
  logger.log(chalk.gray(` mappingCount: ${fmr.mappingCount ?? '—'}`));
143
209
  if (fmr.dimensions) logger.log(chalk.gray(` dimensions: ${Object.keys(fmr.dimensions).join(', ')}`));
144
210
  }
145
211
  const etr = res.endpointTestResults || {};
146
212
  if (Object.keys(etr).length) {
147
- logger.log(chalk.cyan('Endpoint:'));
213
+ logger.log(sectionTitle('Endpoint:'));
148
214
  logger.log(chalk.gray(` endpointConfigured: ${etr.endpointConfigured}`));
149
215
  }
150
216
  if (res.normalizedOutput || res.normalizedMetadata) {
151
217
  const out = res.normalizedOutput || res.normalizedMetadata;
152
218
  const keys = typeof out === 'object' && out !== null ? Object.keys(out) : [];
153
- logger.log(chalk.cyan('Normalized output: ') + chalk.gray(keys.length ? `${keys.length} fields` : '—'));
219
+ logger.log(sectionTitle('Normalized output:') + ' ' + chalk.gray(keys.length ? `${keys.length} fields` : '—'));
154
220
  }
155
221
  }
156
222
 
223
+ function structuralEnvelopeStatusLine(status) {
224
+ if (status === 'ok' || status === 'skipped') return `${successGlyph()} ${chalk.gray('status:')} ${status}`;
225
+ if (status === 'warn') return `${chalk.yellow('⚠')} ${chalk.gray('status:')} ${status}`;
226
+ if (status === 'fail') return `${failureGlyph()} ${chalk.gray('status:')} ${status}`;
227
+ return `${chalk.gray('?')} ${chalk.gray('status:')} ${status ?? '—'}`;
228
+ }
229
+
230
+ /**
231
+ * Format structural validation log (`datasource test --debug` → `test-*.json`).
232
+ * @param {Object} data - Parsed JSON { request, response?, error? }
233
+ * @param {string} [fileName] - Log file name for header
234
+ */
235
+ function formatStructuralTestLog(data, fileName) {
236
+ logger.log('');
237
+ logger.log(sectionTitle('Structural validation log'));
238
+ if (fileName) logger.log(chalk.gray(fileName));
239
+ const req = data.request || {};
240
+ logger.log('');
241
+ logger.log(sectionTitle('Request:'));
242
+ logger.log(headerKeyValue('datasourceKey:', String(req.datasourceKey ?? '—')));
243
+ if (req.runType) logger.log(headerKeyValue('runType:', String(req.runType)));
244
+ if (req.includeDebug !== undefined) {
245
+ logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
246
+ }
247
+ if (data.error) {
248
+ logger.log('');
249
+ logger.log(formatBlockingError(`Error: ${data.error}`));
250
+ return;
251
+ }
252
+ const res = data.response || {};
253
+ logger.log('');
254
+ logger.log(sectionTitle('Response (envelope):'));
255
+ logger.log(` ${structuralEnvelopeStatusLine(res.status)}`);
256
+ if (res.reportCompleteness) {
257
+ logger.log(chalk.gray(` reportCompleteness: ${res.reportCompleteness}`));
258
+ }
259
+ if (res.runId) logger.log(chalk.gray(` runId: ${res.runId}`));
260
+ if (res.systemKey) logger.log(chalk.gray(` systemKey: ${res.systemKey}`));
261
+ }
262
+
157
263
  /**
158
264
  * Format log content by type
159
265
  * @param {Object} parsed - Parsed JSON log
160
- * @param {'test-e2e'|'test-integration'} logType - Log type
266
+ * @param {'test'|'test-e2e'|'test-integration'} logType - Log type
161
267
  * @param {string} [fileName] - File name for header
162
268
  */
163
269
  function formatLogContent(parsed, logType, fileName) {
270
+ if (logType === 'test') {
271
+ formatStructuralTestLog(parsed, fileName);
272
+ return;
273
+ }
164
274
  if (logType === 'test-e2e') {
165
275
  formatE2ELog(parsed, fileName);
166
276
  } else {
@@ -175,7 +285,7 @@ function formatLogContent(parsed, logType, fileName) {
175
285
  * @param {Object} options - Options
176
286
  * @param {string} [options.app] - App key (optional, resolved from key if omitted)
177
287
  * @param {string} [options.file] - Path to log file (overrides app resolution)
178
- * @param {'test-e2e'|'test-integration'} options.logType - Log type
288
+ * @param {'test'|'test-e2e'|'test-integration'} options.logType - Log type
179
289
  * @throws {Error} When file not found or invalid JSON
180
290
  */
181
291
  /* eslint-disable-next-line max-statements -- Resolve path, read, parse, format */
@@ -193,16 +303,25 @@ async function runLogViewer(datasourceKey, options) {
193
303
  const { appKey } = await resolveAppKeyForDatasource(datasourceKey.trim(), app);
194
304
  const appPath = getIntegrationPath(appKey);
195
305
  const logsDir = path.join(appPath, 'logs');
196
- const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
197
- logPath = await getLatestLogPath(logsDir, pattern);
198
- if (!logPath) {
199
- throw new Error(
200
- `No ${logType} log found in ${logsDir}. Run the test with --debug first.`
201
- );
306
+ if (logType === 'test') {
307
+ logPath = await getLatestStructuralTestLogPath(logsDir);
308
+ if (!logPath) {
309
+ throw new Error(
310
+ `No structural validation log found in ${logsDir}. Run: aifabrix datasource test <key> --debug`
311
+ );
312
+ }
313
+ } else {
314
+ const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
315
+ logPath = await getLatestLogPath(logsDir, pattern);
316
+ if (!logPath) {
317
+ throw new Error(
318
+ `No ${logType} log found in ${logsDir}. Run the test with --debug first.`
319
+ );
320
+ }
202
321
  }
203
322
  fileName = path.basename(logPath);
204
323
  }
205
- const content = await fs.readFile(logPath, 'utf8');
324
+ const content = await fsp.readFile(logPath, 'utf8');
206
325
  let parsed;
207
326
  try {
208
327
  parsed = JSON.parse(content);
@@ -214,8 +333,11 @@ async function runLogViewer(datasourceKey, options) {
214
333
 
215
334
  module.exports = {
216
335
  getLatestLogPath,
336
+ getLatestStructuralTestLogPath,
337
+ isStructuralTestLogFileName,
217
338
  formatLogContent,
218
339
  formatE2ELog,
219
340
  formatIntegrationLog,
341
+ formatStructuralTestLog,
220
342
  runLogViewer
221
343
  };
@@ -17,7 +17,7 @@ const { loadConfigFile } = require('../utils/config-format');
17
17
 
18
18
  /**
19
19
  * For one app, check if any of its datasource files has the given key
20
- * @param {string} appKey - Integration app key
20
+ * @param {string} appKey - Integration folder name (system key context)
21
21
  * @param {string} datasourceKey - Datasource key to match
22
22
  * @returns {boolean} True if this app has a datasource with that key
23
23
  */
@@ -52,7 +52,7 @@ function appHasDatasourceKey(appKey, datasourceKey) {
52
52
  * Resolve app key for a datasource: explicit --app, cwd, scan by key, or parse key convention.
53
53
  * @async
54
54
  * @param {string} datasourceKey - Datasource key (e.g. hubspot-test-company)
55
- * @param {string} [explicitApp] - Explicit app key from --app
55
+ * @param {string} [explicitApp] - Explicit integration folder from --app
56
56
  * @returns {Promise<{appKey: string}>} Resolved app key
57
57
  * @throws {Error} When app cannot be determined or multiple apps match
58
58
  */
@@ -85,7 +85,7 @@ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
85
85
  }
86
86
  if (matches.length > 1) {
87
87
  throw new Error(
88
- `More than one app has this datasource; add --app <appKey>. Apps: ${matches.join(', ')}`
88
+ `More than one app has this datasource; add --app <app>. Apps: ${matches.join(', ')}`
89
89
  );
90
90
  }
91
91
 
@@ -99,7 +99,7 @@ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
99
99
  }
100
100
 
101
101
  throw new Error(
102
- 'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
102
+ 'Could not determine app context. Use --app <app> or run from integration/<systemKey>/ directory.'
103
103
  );
104
104
  }
105
105