@metaobjectsdev/metadata 0.9.0 → 0.11.0-rc.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 (392) hide show
  1. package/dist/attr-schema-validate.d.ts +1 -1
  2. package/dist/attr-schema-validate.d.ts.map +1 -1
  3. package/dist/attr-schema-validate.js +96 -11
  4. package/dist/attr-schema-validate.js.map +1 -1
  5. package/dist/constraint-merge.d.ts +18 -0
  6. package/dist/constraint-merge.d.ts.map +1 -0
  7. package/dist/constraint-merge.js +0 -0
  8. package/dist/constraint-merge.js.map +1 -0
  9. package/dist/constraint-validate.d.ts +6 -0
  10. package/dist/constraint-validate.d.ts.map +1 -0
  11. package/dist/constraint-validate.js +274 -0
  12. package/dist/constraint-validate.js.map +1 -0
  13. package/dist/core/attr/attr-constants.d.ts +13 -3
  14. package/dist/core/attr/attr-constants.d.ts.map +1 -1
  15. package/dist/core/attr/attr-constants.js +11 -2
  16. package/dist/core/attr/attr-constants.js.map +1 -1
  17. package/dist/core/attr/attr-definition.embedded.d.ts +3 -0
  18. package/dist/core/attr/attr-definition.embedded.d.ts.map +1 -0
  19. package/dist/core/attr/attr-definition.embedded.js +60 -0
  20. package/dist/core/attr/attr-definition.embedded.js.map +1 -0
  21. package/dist/core/documentation/doc-constants.d.ts +3 -2
  22. package/dist/core/documentation/doc-constants.d.ts.map +1 -1
  23. package/dist/core/documentation/doc-constants.js +3 -1
  24. package/dist/core/documentation/doc-constants.js.map +1 -1
  25. package/dist/core/documentation/doc-provider.d.ts.map +1 -1
  26. package/dist/core/documentation/doc-provider.js +6 -2
  27. package/dist/core/documentation/doc-provider.js.map +1 -1
  28. package/dist/core/documentation/documentation-definition.embedded.d.ts +3 -0
  29. package/dist/core/documentation/documentation-definition.embedded.d.ts.map +1 -0
  30. package/dist/core/documentation/documentation-definition.embedded.js +79 -0
  31. package/dist/core/documentation/documentation-definition.embedded.js.map +1 -0
  32. package/dist/core/field/field-constants.d.ts +7 -4
  33. package/dist/core/field/field-constants.d.ts.map +1 -1
  34. package/dist/core/field/field-constants.js +7 -7
  35. package/dist/core/field/field-constants.js.map +1 -1
  36. package/dist/core/field/field-definition.embedded.d.ts +3 -0
  37. package/dist/core/field/field-definition.embedded.d.ts.map +1 -0
  38. package/dist/core/field/field-definition.embedded.js +236 -0
  39. package/dist/core/field/field-definition.embedded.js.map +1 -0
  40. package/dist/core/field/meta-field.d.ts.map +1 -1
  41. package/dist/core/field/meta-field.js +7 -5
  42. package/dist/core/field/meta-field.js.map +1 -1
  43. package/dist/core/identity/identity-constants.d.ts.map +1 -1
  44. package/dist/core/identity/identity-constants.js +3 -0
  45. package/dist/core/identity/identity-constants.js.map +1 -1
  46. package/dist/core/identity/identity-definition.embedded.d.ts +3 -0
  47. package/dist/core/identity/identity-definition.embedded.d.ts.map +1 -0
  48. package/dist/core/identity/identity-definition.embedded.js +93 -0
  49. package/dist/core/identity/identity-definition.embedded.js.map +1 -0
  50. package/dist/core/identity/meta-identity.d.ts.map +1 -1
  51. package/dist/core/identity/meta-identity.js +8 -1
  52. package/dist/core/identity/meta-identity.js.map +1 -1
  53. package/dist/core/identity/validate-identity-passthrough.d.ts +42 -0
  54. package/dist/core/identity/validate-identity-passthrough.d.ts.map +1 -0
  55. package/dist/core/identity/validate-identity-passthrough.js +158 -0
  56. package/dist/core/identity/validate-identity-passthrough.js.map +1 -0
  57. package/dist/core/object/object-constants.d.ts +2 -1
  58. package/dist/core/object/object-constants.d.ts.map +1 -1
  59. package/dist/core/object/object-constants.js +3 -0
  60. package/dist/core/object/object-constants.js.map +1 -1
  61. package/dist/core/object/object-definition.embedded.d.ts +3 -0
  62. package/dist/core/object/object-definition.embedded.d.ts.map +1 -0
  63. package/dist/core/object/object-definition.embedded.js +110 -0
  64. package/dist/core/object/object-definition.embedded.js.map +1 -0
  65. package/dist/core/object/validate-discriminator.d.ts.map +1 -1
  66. package/dist/core/object/validate-discriminator.js +1 -3
  67. package/dist/core/object/validate-discriminator.js.map +1 -1
  68. package/dist/core/query/query-constants.d.ts.map +1 -1
  69. package/dist/core/query/query-constants.js +5 -3
  70. package/dist/core/query/query-constants.js.map +1 -1
  71. package/dist/core/relationship/derive-m2m-fields.d.ts +26 -0
  72. package/dist/core/relationship/derive-m2m-fields.d.ts.map +1 -0
  73. package/dist/core/relationship/derive-m2m-fields.js +102 -0
  74. package/dist/core/relationship/derive-m2m-fields.js.map +1 -0
  75. package/dist/core/relationship/meta-relationship.d.ts +6 -4
  76. package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
  77. package/dist/core/relationship/meta-relationship.js +12 -8
  78. package/dist/core/relationship/meta-relationship.js.map +1 -1
  79. package/dist/core/relationship/relationship-constants.d.ts +6 -2
  80. package/dist/core/relationship/relationship-constants.d.ts.map +1 -1
  81. package/dist/core/relationship/relationship-constants.js +6 -2
  82. package/dist/core/relationship/relationship-constants.js.map +1 -1
  83. package/dist/core/relationship/relationship-definition.embedded.d.ts +3 -0
  84. package/dist/core/relationship/relationship-definition.embedded.d.ts.map +1 -0
  85. package/dist/core/relationship/relationship-definition.embedded.js +310 -0
  86. package/dist/core/relationship/relationship-definition.embedded.js.map +1 -0
  87. package/dist/core/validator/validator-constants.d.ts +14 -1
  88. package/dist/core/validator/validator-constants.d.ts.map +1 -1
  89. package/dist/core/validator/validator-constants.js +20 -1
  90. package/dist/core/validator/validator-constants.js.map +1 -1
  91. package/dist/core/validator/validator-definition.embedded.d.ts +3 -0
  92. package/dist/core/validator/validator-definition.embedded.d.ts.map +1 -0
  93. package/dist/core/validator/validator-definition.embedded.js +255 -0
  94. package/dist/core/validator/validator-definition.embedded.js.map +1 -0
  95. package/dist/core/yaml-desugar.d.ts.map +1 -1
  96. package/dist/core/yaml-desugar.js +88 -10
  97. package/dist/core/yaml-desugar.js.map +1 -1
  98. package/dist/core-types.d.ts +5 -2
  99. package/dist/core-types.d.ts.map +1 -1
  100. package/dist/core-types.js +285 -116
  101. package/dist/core-types.js.map +1 -1
  102. package/dist/errors.d.ts +3 -3
  103. package/dist/errors.d.ts.map +1 -1
  104. package/dist/errors.js +87 -0
  105. package/dist/errors.js.map +1 -1
  106. package/dist/index.d.ts +17 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +26 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/library/embedded-library.generated.d.ts +2 -0
  111. package/dist/library/embedded-library.generated.d.ts.map +1 -0
  112. package/dist/library/embedded-library.generated.js +11 -0
  113. package/dist/library/embedded-library.generated.js.map +1 -0
  114. package/dist/library/library-sources.d.ts +12 -0
  115. package/dist/library/library-sources.d.ts.map +1 -0
  116. package/dist/library/library-sources.js +88 -0
  117. package/dist/library/library-sources.js.map +1 -0
  118. package/dist/loader/meta-data-loader.d.ts +10 -2
  119. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  120. package/dist/loader/meta-data-loader.js +57 -6
  121. package/dist/loader/meta-data-loader.js.map +1 -1
  122. package/dist/loader/shortcuts.d.ts +2 -5
  123. package/dist/loader/shortcuts.d.ts.map +1 -1
  124. package/dist/loader/shortcuts.js.map +1 -1
  125. package/dist/loader/validation-passes.d.ts +3 -0
  126. package/dist/loader/validation-passes.d.ts.map +1 -1
  127. package/dist/loader/validation-passes.js +519 -34
  128. package/dist/loader/validation-passes.js.map +1 -1
  129. package/dist/loader/validation-registry.d.ts +10 -0
  130. package/dist/loader/validation-registry.d.ts.map +1 -0
  131. package/dist/loader/validation-registry.js +84 -0
  132. package/dist/loader/validation-registry.js.map +1 -0
  133. package/dist/metamodel-docs/index.d.ts +19 -0
  134. package/dist/metamodel-docs/index.d.ts.map +1 -0
  135. package/dist/metamodel-docs/index.js +37 -0
  136. package/dist/metamodel-docs/index.js.map +1 -0
  137. package/dist/metamodel-docs/provenance.d.ts +42 -0
  138. package/dist/metamodel-docs/provenance.d.ts.map +1 -0
  139. package/dist/metamodel-docs/provenance.js +148 -0
  140. package/dist/metamodel-docs/provenance.js.map +1 -0
  141. package/dist/metamodel-docs/provider-definitions.d.ts +8 -0
  142. package/dist/metamodel-docs/provider-definitions.d.ts.map +1 -0
  143. package/dist/metamodel-docs/provider-definitions.js +48 -0
  144. package/dist/metamodel-docs/provider-definitions.js.map +1 -0
  145. package/dist/metamodel-docs/render.d.ts +12 -0
  146. package/dist/metamodel-docs/render.d.ts.map +1 -0
  147. package/dist/metamodel-docs/render.js +252 -0
  148. package/dist/metamodel-docs/render.js.map +1 -0
  149. package/dist/naming-refs.d.ts +41 -0
  150. package/dist/naming-refs.d.ts.map +1 -0
  151. package/dist/naming-refs.js +144 -0
  152. package/dist/naming-refs.js.map +1 -0
  153. package/dist/naming.d.ts.map +1 -1
  154. package/dist/naming.js +10 -2
  155. package/dist/naming.js.map +1 -1
  156. package/dist/parser-core.d.ts.map +1 -1
  157. package/dist/parser-core.js +84 -9
  158. package/dist/parser-core.js.map +1 -1
  159. package/dist/persistence/db/db-constants.d.ts +10 -0
  160. package/dist/persistence/db/db-constants.d.ts.map +1 -1
  161. package/dist/persistence/db/db-constants.js +14 -0
  162. package/dist/persistence/db/db-constants.js.map +1 -1
  163. package/dist/persistence/db/db-definition.embedded.d.ts +3 -0
  164. package/dist/persistence/db/db-definition.embedded.d.ts.map +1 -0
  165. package/dist/persistence/db/db-definition.embedded.js +227 -0
  166. package/dist/persistence/db/db-definition.embedded.js.map +1 -0
  167. package/dist/persistence/db/db-provider.d.ts.map +1 -1
  168. package/dist/persistence/db/db-provider.js +13 -18
  169. package/dist/persistence/db/db-provider.js.map +1 -1
  170. package/dist/persistence/origin/origin-definition.embedded.d.ts +3 -0
  171. package/dist/persistence/origin/origin-definition.embedded.d.ts.map +1 -0
  172. package/dist/persistence/origin/origin-definition.embedded.js +88 -0
  173. package/dist/persistence/origin/origin-definition.embedded.js.map +1 -0
  174. package/dist/persistence/source/source-definition.embedded.d.ts +3 -0
  175. package/dist/persistence/source/source-definition.embedded.d.ts.map +1 -0
  176. package/dist/persistence/source/source-definition.embedded.js +17 -0
  177. package/dist/persistence/source/source-definition.embedded.js.map +1 -0
  178. package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -1
  179. package/dist/persistence/source/validate-source-parameter-ref.js +7 -1
  180. package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -1
  181. package/dist/persistence/source/validate-source-roles.d.ts.map +1 -1
  182. package/dist/persistence/source/validate-source-roles.js +16 -1
  183. package/dist/persistence/source/validate-source-roles.js.map +1 -1
  184. package/dist/presentation/layout/layout-definition.embedded.d.ts +3 -0
  185. package/dist/presentation/layout/layout-definition.embedded.d.ts.map +1 -0
  186. package/dist/presentation/layout/layout-definition.embedded.js +16 -0
  187. package/dist/presentation/layout/layout-definition.embedded.js.map +1 -0
  188. package/dist/presentation/ui/ui-definition.embedded.d.ts +3 -0
  189. package/dist/presentation/ui/ui-definition.embedded.d.ts.map +1 -0
  190. package/dist/presentation/ui/ui-definition.embedded.js +114 -0
  191. package/dist/presentation/ui/ui-definition.embedded.js.map +1 -0
  192. package/dist/presentation/ui/ui-provider.d.ts +3 -0
  193. package/dist/presentation/ui/ui-provider.d.ts.map +1 -0
  194. package/dist/presentation/ui/ui-provider.js +21 -0
  195. package/dist/presentation/ui/ui-provider.js.map +1 -0
  196. package/dist/presentation/view/view-definition.embedded.d.ts +3 -0
  197. package/dist/presentation/view/view-definition.embedded.d.ts.map +1 -0
  198. package/dist/presentation/view/view-definition.embedded.js +76 -0
  199. package/dist/presentation/view/view-definition.embedded.js.map +1 -0
  200. package/dist/provider-data.d.ts +184 -0
  201. package/dist/provider-data.d.ts.map +1 -0
  202. package/dist/provider-data.js +271 -0
  203. package/dist/provider-data.js.map +1 -0
  204. package/dist/provider.d.ts +3 -1
  205. package/dist/provider.d.ts.map +1 -1
  206. package/dist/provider.js +15 -1
  207. package/dist/provider.js.map +1 -1
  208. package/dist/registry-coverage.d.ts +99 -0
  209. package/dist/registry-coverage.d.ts.map +1 -0
  210. package/dist/registry-coverage.js +294 -0
  211. package/dist/registry-coverage.js.map +1 -0
  212. package/dist/registry-manifest-exclusions.d.ts +62 -0
  213. package/dist/registry-manifest-exclusions.d.ts.map +1 -0
  214. package/dist/registry-manifest-exclusions.js +163 -0
  215. package/dist/registry-manifest-exclusions.js.map +1 -0
  216. package/dist/registry-manifest.d.ts +117 -0
  217. package/dist/registry-manifest.d.ts.map +1 -0
  218. package/dist/registry-manifest.js +242 -0
  219. package/dist/registry-manifest.js.map +1 -0
  220. package/dist/registry.d.ts +76 -2
  221. package/dist/registry.d.ts.map +1 -1
  222. package/dist/registry.js +37 -1
  223. package/dist/registry.js.map +1 -1
  224. package/dist/shared/structural.d.ts +7 -0
  225. package/dist/shared/structural.d.ts.map +1 -1
  226. package/dist/shared/structural.js +7 -0
  227. package/dist/shared/structural.js.map +1 -1
  228. package/dist/subtype-rules.d.ts.map +1 -1
  229. package/dist/subtype-rules.js +97 -13
  230. package/dist/subtype-rules.js.map +1 -1
  231. package/dist/super-resolve.d.ts +49 -2
  232. package/dist/super-resolve.d.ts.map +1 -1
  233. package/dist/super-resolve.js +128 -43
  234. package/dist/super-resolve.js.map +1 -1
  235. package/dist/template/meta-template.d.ts +3 -2
  236. package/dist/template/meta-template.d.ts.map +1 -1
  237. package/dist/template/meta-template.js +3 -2
  238. package/dist/template/meta-template.js.map +1 -1
  239. package/dist/template/prompt-definition.embedded.d.ts +3 -0
  240. package/dist/template/prompt-definition.embedded.d.ts.map +1 -0
  241. package/dist/template/prompt-definition.embedded.js +368 -0
  242. package/dist/template/prompt-definition.embedded.js.map +1 -0
  243. package/dist/template/prompt-provider.d.ts +3 -0
  244. package/dist/template/prompt-provider.d.ts.map +1 -0
  245. package/dist/template/prompt-provider.js +25 -0
  246. package/dist/template/prompt-provider.js.map +1 -0
  247. package/dist/template/template-constants.d.ts +2 -0
  248. package/dist/template/template-constants.d.ts.map +1 -1
  249. package/dist/template/template-constants.js +7 -0
  250. package/dist/template/template-constants.js.map +1 -1
  251. package/dist/template/template-definition.embedded.d.ts +3 -0
  252. package/dist/template/template-definition.embedded.d.ts.map +1 -0
  253. package/dist/template/template-definition.embedded.js +30 -0
  254. package/dist/template/template-definition.embedded.js.map +1 -0
  255. package/dist/validate-max-occurs.d.ts +5 -0
  256. package/dist/validate-max-occurs.d.ts.map +1 -0
  257. package/dist/validate-max-occurs.js +28 -0
  258. package/dist/validate-max-occurs.js.map +1 -0
  259. package/dist/validation-types.d.ts +36 -0
  260. package/dist/validation-types.d.ts.map +1 -0
  261. package/dist/validation-types.js +7 -0
  262. package/dist/validation-types.js.map +1 -0
  263. package/package.json +33 -22
  264. package/src/attr-schema-validate.ts +108 -8
  265. package/src/constraint-merge.ts +0 -0
  266. package/src/constraint-validate.ts +363 -0
  267. package/src/core/attr/attr-constants.ts +15 -3
  268. package/src/core/attr/attr-definition.embedded.ts +67 -0
  269. package/src/core/documentation/doc-constants.ts +3 -1
  270. package/src/core/documentation/doc-provider.ts +6 -2
  271. package/src/core/documentation/documentation-definition.embedded.ts +86 -0
  272. package/src/core/field/field-constants.ts +8 -7
  273. package/src/core/field/field-definition.embedded.ts +243 -0
  274. package/src/core/field/meta-field.ts +6 -7
  275. package/src/core/identity/identity-constants.ts +4 -0
  276. package/src/core/identity/identity-definition.embedded.ts +100 -0
  277. package/src/core/identity/meta-identity.ts +8 -1
  278. package/src/core/identity/validate-identity-passthrough.ts +194 -0
  279. package/src/core/object/object-constants.ts +3 -0
  280. package/src/core/object/object-definition.embedded.ts +117 -0
  281. package/src/core/object/validate-discriminator.ts +0 -4
  282. package/src/core/query/query-constants.ts +5 -3
  283. package/src/core/relationship/derive-m2m-fields.ts +145 -0
  284. package/src/core/relationship/meta-relationship.ts +15 -9
  285. package/src/core/relationship/relationship-constants.ts +6 -2
  286. package/src/core/relationship/relationship-definition.embedded.ts +317 -0
  287. package/src/core/validator/validator-constants.ts +22 -1
  288. package/src/core/validator/validator-definition.embedded.ts +262 -0
  289. package/src/core/yaml-desugar.ts +96 -7
  290. package/src/core-types.ts +314 -150
  291. package/src/errors.ts +89 -2
  292. package/src/index.ts +47 -2
  293. package/src/library/embedded-library.generated.ts +10 -0
  294. package/src/library/library-sources.ts +97 -0
  295. package/src/loader/meta-data-loader.ts +78 -7
  296. package/src/loader/shortcuts.ts +2 -2
  297. package/src/loader/validation-passes.ts +690 -34
  298. package/src/loader/validation-registry.ts +93 -0
  299. package/src/metamodel-docs/index.ts +41 -0
  300. package/src/metamodel-docs/provenance.ts +187 -0
  301. package/src/metamodel-docs/provider-definitions.ts +50 -0
  302. package/src/metamodel-docs/render.ts +309 -0
  303. package/src/naming-refs.ts +162 -0
  304. package/src/naming.ts +10 -2
  305. package/src/parser-core.ts +96 -9
  306. package/src/persistence/db/db-constants.ts +16 -0
  307. package/src/persistence/db/db-definition.embedded.ts +234 -0
  308. package/src/persistence/db/db-provider.ts +13 -18
  309. package/src/persistence/origin/origin-definition.embedded.ts +95 -0
  310. package/src/persistence/source/source-definition.embedded.ts +24 -0
  311. package/src/persistence/source/validate-source-parameter-ref.ts +7 -1
  312. package/src/persistence/source/validate-source-roles.ts +22 -1
  313. package/src/presentation/layout/layout-definition.embedded.ts +23 -0
  314. package/src/presentation/ui/ui-definition.embedded.ts +121 -0
  315. package/src/presentation/ui/ui-provider.ts +25 -0
  316. package/src/presentation/view/view-definition.embedded.ts +83 -0
  317. package/src/provider-data.ts +463 -0
  318. package/src/provider.ts +18 -0
  319. package/src/registry-coverage.ts +430 -0
  320. package/src/registry-manifest-exclusions.ts +176 -0
  321. package/src/registry-manifest.ts +334 -0
  322. package/src/registry.ts +106 -3
  323. package/src/shared/structural.ts +8 -0
  324. package/src/subtype-rules.ts +135 -18
  325. package/src/super-resolve.ts +153 -43
  326. package/src/template/meta-template.ts +3 -2
  327. package/src/template/prompt-definition.embedded.ts +375 -0
  328. package/src/template/prompt-provider.ts +29 -0
  329. package/src/template/template-constants.ts +8 -0
  330. package/src/template/template-definition.embedded.ts +37 -0
  331. package/src/validate-max-occurs.ts +39 -0
  332. package/src/validation-types.ts +57 -0
  333. package/dist/core/documentation/doc-schema.d.ts +0 -8
  334. package/dist/core/documentation/doc-schema.d.ts.map +0 -1
  335. package/dist/core/documentation/doc-schema.js +0 -53
  336. package/dist/core/documentation/doc-schema.js.map +0 -1
  337. package/dist/core/field/field-schema.d.ts +0 -22
  338. package/dist/core/field/field-schema.d.ts.map +0 -1
  339. package/dist/core/field/field-schema.js +0 -171
  340. package/dist/core/field/field-schema.js.map +0 -1
  341. package/dist/core/identity/identity-schema.d.ts +0 -6
  342. package/dist/core/identity/identity-schema.d.ts.map +0 -1
  343. package/dist/core/identity/identity-schema.js +0 -55
  344. package/dist/core/identity/identity-schema.js.map +0 -1
  345. package/dist/core/object/object-schema.d.ts +0 -4
  346. package/dist/core/object/object-schema.d.ts.map +0 -1
  347. package/dist/core/object/object-schema.js +0 -28
  348. package/dist/core/object/object-schema.js.map +0 -1
  349. package/dist/core/relationship/relationship-schema.d.ts +0 -4
  350. package/dist/core/relationship/relationship-schema.d.ts.map +0 -1
  351. package/dist/core/relationship/relationship-schema.js +0 -51
  352. package/dist/core/relationship/relationship-schema.js.map +0 -1
  353. package/dist/core/validator/validator-schema.d.ts +0 -4
  354. package/dist/core/validator/validator-schema.d.ts.map +0 -1
  355. package/dist/core/validator/validator-schema.js +0 -38
  356. package/dist/core/validator/validator-schema.js.map +0 -1
  357. package/dist/persistence/db/db-schema.d.ts +0 -14
  358. package/dist/persistence/db/db-schema.d.ts.map +0 -1
  359. package/dist/persistence/db/db-schema.js +0 -35
  360. package/dist/persistence/db/db-schema.js.map +0 -1
  361. package/dist/persistence/origin/origin-schema.d.ts +0 -4
  362. package/dist/persistence/origin/origin-schema.d.ts.map +0 -1
  363. package/dist/persistence/origin/origin-schema.js +0 -59
  364. package/dist/persistence/origin/origin-schema.js.map +0 -1
  365. package/dist/persistence/source/source-schema.d.ts +0 -4
  366. package/dist/persistence/source/source-schema.d.ts.map +0 -1
  367. package/dist/persistence/source/source-schema.js +0 -98
  368. package/dist/persistence/source/source-schema.js.map +0 -1
  369. package/dist/presentation/layout/layout-schema.d.ts +0 -4
  370. package/dist/presentation/layout/layout-schema.d.ts.map +0 -1
  371. package/dist/presentation/layout/layout-schema.js +0 -46
  372. package/dist/presentation/layout/layout-schema.js.map +0 -1
  373. package/dist/presentation/view/view-schema.d.ts +0 -4
  374. package/dist/presentation/view/view-schema.d.ts.map +0 -1
  375. package/dist/presentation/view/view-schema.js +0 -15
  376. package/dist/presentation/view/view-schema.js.map +0 -1
  377. package/dist/template/template-schema.d.ts +0 -3
  378. package/dist/template/template-schema.d.ts.map +0 -1
  379. package/dist/template/template-schema.js +0 -173
  380. package/dist/template/template-schema.js.map +0 -1
  381. package/src/core/documentation/doc-schema.ts +0 -64
  382. package/src/core/field/field-schema.ts +0 -228
  383. package/src/core/identity/identity-schema.ts +0 -80
  384. package/src/core/object/object-schema.ts +0 -35
  385. package/src/core/relationship/relationship-schema.ts +0 -67
  386. package/src/core/validator/validator-schema.ts +0 -50
  387. package/src/persistence/db/db-schema.ts +0 -50
  388. package/src/persistence/origin/origin-schema.ts +0 -80
  389. package/src/persistence/source/source-schema.ts +0 -129
  390. package/src/presentation/layout/layout-schema.ts +0 -62
  391. package/src/presentation/view/view-schema.ts +0 -21
  392. package/src/template/template-schema.ts +0 -211
@@ -4,21 +4,25 @@
4
4
  // warnings. No loader state is read or written — these are pure functions.
5
5
  //
6
6
  // Exported: validateDataGridSortFields, validateFilterableHasIndex,
7
- // validateOriginPaths, validateDataGridFilterValues,
7
+ // validateOriginPaths, validateDerivedFieldProvidability,
8
+ // validateDataGridFilterValues,
8
9
  // validateFieldObjectStorage (called by MetaDataLoader.load() in order).
9
10
  // Private: _findObject, _findField, _findRelationship,
10
11
  // _validateFromPath, _validateViaPath (helpers, not exported).
11
12
  import { ParseError } from "../errors.js";
13
+ import { refMatchesObject } from "../naming-refs.js";
12
14
  import { resolvedSource } from "../source.js";
13
- import { TYPE_OBJECT, TYPE_FIELD, TYPE_LAYOUT, TYPE_IDENTITY, TYPE_ORIGIN, TYPE_RELATIONSHIP, TYPE_TEMPLATE, } from "../shared/base-types.js";
14
- import { TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_KIND, TEMPLATE_KIND_EMAIL, TEMPLATE_ATTR_SUBJECT_REF, TEMPLATE_ATTR_HTML_BODY_REF, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_SUBTYPE_PROMPT, } from "../template/template-constants.js";
15
- import { OBJECT_SUBTYPE_VALUE } from "../core/object/object-constants.js";
15
+ import { TYPE_OBJECT, TYPE_FIELD, TYPE_LAYOUT, TYPE_IDENTITY, TYPE_ORIGIN, TYPE_RELATIONSHIP, TYPE_SOURCE, TYPE_TEMPLATE, } from "../shared/base-types.js";
16
+ import { TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_RESPONSE_REF, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_KIND, TEMPLATE_KIND_EMAIL, TEMPLATE_ATTR_SUBJECT_REF, TEMPLATE_ATTR_HTML_BODY_REF, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_SUBTYPE_PROMPT, } from "../template/template-constants.js";
17
+ import { OBJECT_SUBTYPE_ENTITY, OBJECT_SUBTYPE_VALUE, OBJECT_SUBTYPE_PROJECTION, } from "../core/object/object-constants.js";
18
+ import { MetaSource } from "../persistence/source/meta-source.js";
16
19
  import { LAYOUT_SUBTYPE_DATA_GRID, LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD, LAYOUT_DATA_GRID_ATTR_FILTER, } from "../presentation/layout/layout-constants.js";
17
- import { FIELD_ATTR_FILTERABLE, FIELD_ATTR_OBJECT_REF, FIELD_ATTR_STORAGE, STORAGE_FLATTENED, FIELD_ATTR_DEFAULT, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_DECIMAL, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_ENUM, } from "../core/field/field-constants.js";
20
+ import { FIELD_ATTR_FILTERABLE, FIELD_ATTR_OBJECT_REF, FIELD_ATTR_STORAGE, STORAGE_FLATTENED, FIELD_ATTR_DEFAULT, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_DECIMAL, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_ENUM, FIELD_SUBTYPE_OBJECT, } from "../core/field/field-constants.js";
18
21
  import { FIELD_ATTR_DB_INDEXED } from "../persistence/db/db-constants.js";
19
- import { IDENTITY_ATTR_FIELDS } from "../core/identity/identity-constants.js";
22
+ import { IDENTITY_ATTR_FIELDS, IDENTITY_SUBTYPE_REFERENCE, } from "../core/identity/identity-constants.js";
20
23
  import { ORIGIN_SUBTYPE_PASSTHROUGH, ORIGIN_SUBTYPE_AGGREGATE, ORIGIN_PASSTHROUGH_ATTR_FROM, ORIGIN_PASSTHROUGH_ATTR_VIA, ORIGIN_AGGREGATE_ATTR_OF, ORIGIN_AGGREGATE_ATTR_VIA, } from "../persistence/origin/origin-constants.js";
21
- import { RELATIONSHIP_ATTR_OBJECT_REF } from "../core/relationship/relationship-constants.js";
24
+ import { RELATIONSHIP_ATTR_OBJECT_REF, RELATIONSHIP_ATTR_CARDINALITY, RELATIONSHIP_ATTR_THROUGH, RELATIONSHIP_ATTR_SOURCE_REF_FIELD, RELATIONSHIP_ATTR_SYMMETRIC, CARDINALITY_ONE, CARDINALITY_MANY, } from "../core/relationship/relationship-constants.js";
25
+ import { stripPackage } from "../naming.js";
22
26
  import { FILTER_COMPOSE_OR, FILTER_COMPOSE_AND, opsForSubType, } from "../core/query/query-constants.js";
23
27
  // ---------------------------------------------------------------------------
24
28
  // Layout dataGrid @defaultSortField validation
@@ -50,9 +54,19 @@ export function validateDataGridSortFields(root) {
50
54
  // external template text via a provider, so it lives in the build-time
51
55
  // `meta verify` step, not here.
52
56
  // ---------------------------------------------------------------------------
57
+ /** Recursively collect all template.* nodes anywhere in the metadata tree. */
58
+ function allTemplates(node) {
59
+ const out = [];
60
+ for (const c of node.ownChildren()) {
61
+ if (c.type === TYPE_TEMPLATE)
62
+ out.push(c);
63
+ out.push(...allTemplates(c));
64
+ }
65
+ return out;
66
+ }
53
67
  export function validateTemplatePayloadRefs(root) {
54
68
  const errors = [];
55
- for (const tmpl of root.ownChildren().filter((c) => c.type === TYPE_TEMPLATE)) {
69
+ for (const tmpl of allTemplates(root)) {
56
70
  // --- @kind / textRef / email part-ref cross-field rules ---
57
71
  // template.output is either a document (@kind absent/"document" → @textRef
58
72
  // required) or an email (@kind="email" → @subjectRef + @htmlBodyRef required,
@@ -87,7 +101,7 @@ export function validateTemplatePayloadRefs(root) {
87
101
  const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
88
102
  if (typeof payloadRef !== "string")
89
103
  continue; // absence handled by the required-attr schema check
90
- const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === payloadRef);
104
+ const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
91
105
  if (!payload || payload.subType !== OBJECT_SUBTYPE_VALUE) {
92
106
  // FR5d — @payloadRef is a reference; emit format=resolved with
93
107
  // referrer=template FQN, target=the unresolved payloadRef string.
@@ -97,6 +111,13 @@ export function validateTemplatePayloadRefs(root) {
97
111
  }));
98
112
  continue;
99
113
  }
114
+ const responseRef = tmpl.ownAttr(TEMPLATE_ATTR_RESPONSE_REF);
115
+ if (typeof responseRef === "string") {
116
+ const resVo = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, responseRef));
117
+ if (!resVo || resVo.subType !== OBJECT_SUBTYPE_VALUE) {
118
+ errors.push(new ParseError(`template "${tmpl.name}" @responseRef "${responseRef}" does not resolve to an object.value at root`, { code: "ERR_INVALID_TEMPLATE", source: resolvedSource(tmpl.source, tmpl.fqn(), responseRef) }));
119
+ }
120
+ }
100
121
  const fieldNames = new Set(payload.children().filter((c) => c.type === TYPE_FIELD).map((f) => f.name));
101
122
  const slots = tmpl.ownAttr(TEMPLATE_ATTR_REQUIRED_SLOTS);
102
123
  const slotList = Array.isArray(slots) ? slots : typeof slots === "string" ? [slots] : [];
@@ -154,6 +175,30 @@ export function validateFilterableHasIndex(root) {
154
175
  return warnings;
155
176
  }
156
177
  // ---------------------------------------------------------------------------
178
+ // @filterable on a subtype with no operator band (SP-H Unit9)
179
+ // ---------------------------------------------------------------------------
180
+ // A field marked @filterable: true whose subtype has NO entry in OPS_BY_SUBTYPE
181
+ // (e.g. field.object, or any extension subtype without a declared op band)
182
+ // would silently generate a filter type/allowlist with an empty op set — a
183
+ // filter route that rejects every request. Error early instead of shipping
184
+ // broken codegen. → ERR_FILTERABLE_UNSUPPORTED_SUBTYPE.
185
+ export function validateFilterableHasSupportedOps(root) {
186
+ const errors = [];
187
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
188
+ // children() — inherited @filterable fields (via extends:/super:) are visible.
189
+ for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
190
+ if (field.ownAttr(FIELD_ATTR_FILTERABLE) !== true)
191
+ continue;
192
+ if (opsForSubType(field.subType).length > 0)
193
+ continue;
194
+ errors.push(new ParseError(`Field "${obj.name}.${field.name}" has @filterable: true but its subtype ` +
195
+ `"${field.subType}" has no filter-operator band. Remove @filterable, or use a ` +
196
+ `field subtype that supports filtering (string/enum/uuid/number/currency/date/boolean).`, { code: "ERR_FILTERABLE_UNSUPPORTED_SUBTYPE", source: field.source }));
197
+ }
198
+ }
199
+ return errors;
200
+ }
201
+ // ---------------------------------------------------------------------------
157
202
  // Origin path validation
158
203
  //
159
204
  // Walks every projection's fields, finds `origin` (TYPE_ORIGIN) children,
@@ -167,7 +212,9 @@ export function validateFilterableHasIndex(root) {
167
212
  // allowedValues on the origin.aggregate @agg attr schema — not here.
168
213
  // ---------------------------------------------------------------------------
169
214
  function _findObject(root, name) {
170
- return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
215
+ // FR-032 origin heads are FQN-qualified after the desugar/sweep; match on
216
+ // the effective FQN resolution key (with bare back-compat).
217
+ return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
171
218
  }
172
219
  function _findField(obj, name) {
173
220
  // Use children() so inherited fields (via extends:/super:) are included.
@@ -177,6 +224,13 @@ function _findRelationship(obj, name) {
177
224
  // Use children() so inherited relationships (via extends:/super:) are included.
178
225
  return obj.children().find((c) => c.type === TYPE_RELATIONSHIP && c.name === name);
179
226
  }
227
+ /**
228
+ * Validate a passthrough `@from` / aggregate `@of` "Entity.field" reference.
229
+ * Returns the resolved target entity + field on full success (FR-024 B5 —
230
+ * the inference/cardinality stage needs the entity; B6 agreement needs the
231
+ * field), or undefined when any error was pushed (malformed shape / unknown
232
+ * entity / unknown field).
233
+ */
180
234
  function _validateFromPath(fromAttr, root, projection, fieldName, originSource, errors, label = "origin.passthrough.@from") {
181
235
  const projectionName = projection.name;
182
236
  // FR5d — referrer is `<projection-FQN>::<fieldName>` (the canonical
@@ -191,7 +245,7 @@ function _validateFromPath(fromAttr, root, projection, fieldName, originSource,
191
245
  code: "ERR_INVALID_ORIGIN",
192
246
  source: resolvedSource(originSource, referrer, fromAttr),
193
247
  }));
194
- return;
248
+ return undefined;
195
249
  }
196
250
  const entityName = fromAttr.slice(0, dotIdx);
197
251
  const targetFieldName = fromAttr.slice(dotIdx + 1);
@@ -202,7 +256,7 @@ function _validateFromPath(fromAttr, root, projection, fieldName, originSource,
202
256
  code: "ERR_INVALID_ORIGIN",
203
257
  source: resolvedSource(originSource, referrer, fromAttr),
204
258
  }));
205
- return;
259
+ return undefined;
206
260
  }
207
261
  const sourceField = _findField(sourceObj, targetFieldName);
208
262
  if (!sourceField) {
@@ -211,8 +265,15 @@ function _validateFromPath(fromAttr, root, projection, fieldName, originSource,
211
265
  code: "ERR_INVALID_ORIGIN",
212
266
  source: resolvedSource(originSource, referrer, fromAttr),
213
267
  }));
268
+ return undefined;
214
269
  }
270
+ return { entity: sourceObj, field: sourceField };
215
271
  }
272
+ /**
273
+ * Validate an explicit `@via` "Entity.rel[.rel...]" path. Returns the walked
274
+ * relationship hop nodes (in path order) on full success — FR-024 B5 runs the
275
+ * cardinality checks over them — or undefined when any error was pushed.
276
+ */
216
277
  function _validateViaPath(viaAttr, root, projection, fieldName, originSource, errors) {
217
278
  const projectionName = projection.name;
218
279
  // FR5d — referrer is `<projection-FQN>::<fieldName>`.
@@ -223,7 +284,7 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
223
284
  code: "ERR_INVALID_ORIGIN",
224
285
  source: resolvedSource(originSource, referrer, viaAttr),
225
286
  }));
226
- return;
287
+ return undefined;
227
288
  }
228
289
  const [entityName, ...relSegments] = segments;
229
290
  let currentObj = _findObject(root, entityName);
@@ -232,7 +293,7 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
232
293
  code: "ERR_INVALID_ORIGIN",
233
294
  source: resolvedSource(originSource, referrer, viaAttr),
234
295
  }));
235
- return;
296
+ return undefined;
236
297
  }
237
298
  // FR5d — track the deepest-valid-prefix as we walk. The prefix grows
238
299
  // segment-by-segment; on a hop failure the error message names the prefix
@@ -240,6 +301,7 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
240
301
  // After the entity lookup above, the deepest valid prefix is just the
241
302
  // entity name; each successful relationship hop appends a segment.
242
303
  const validSegments = [entityName];
304
+ const hops = [];
243
305
  for (const relName of relSegments) {
244
306
  const rel = _findRelationship(currentObj, relName);
245
307
  if (!rel) {
@@ -249,7 +311,7 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
249
311
  code: "ERR_INVALID_ORIGIN",
250
312
  source: resolvedSource(originSource, referrer, viaAttr),
251
313
  }));
252
- return;
314
+ return undefined;
253
315
  }
254
316
  const refTarget = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
255
317
  if (typeof refTarget !== "string" || refTarget === "") {
@@ -257,7 +319,7 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
257
319
  code: "ERR_INVALID_ORIGIN",
258
320
  source: resolvedSource(originSource, referrer, viaAttr),
259
321
  }));
260
- return;
322
+ return undefined;
261
323
  }
262
324
  const nextObj = _findObject(root, refTarget);
263
325
  if (!nextObj) {
@@ -269,15 +331,234 @@ function _validateViaPath(viaAttr, root, projection, fieldName, originSource, er
269
331
  code: "ERR_INVALID_ORIGIN",
270
332
  source: resolvedSource(originSource, referrer, refTarget),
271
333
  }));
272
- return;
334
+ return undefined;
273
335
  }
274
336
  validSegments.push(relName);
337
+ hops.push(rel);
275
338
  currentObj = nextObj;
276
339
  }
340
+ return hops;
341
+ }
342
+ // ---------------------------------------------------------------------------
343
+ // FR-024 B5 — base-entity derivation, single-hop-unique @via inference, and
344
+ // origin cardinality checks (spec §5–§6; ADR-0029 decisions 5–6).
345
+ // ---------------------------------------------------------------------------
346
+ /** A hop relationship's effective @cardinality, or undefined when not declared. */
347
+ function _hopCardinality(rel) {
348
+ const v = rel.attr(RELATIONSHIP_ATTR_CARDINALITY);
349
+ return typeof v === "string" ? v : undefined;
350
+ }
351
+ /**
352
+ * Derive the BASE entity a no-`@via` origin path anchors at (spec §5):
353
+ * - an entity (or any non-projection host) is its own base — derived fields
354
+ * on multi-source entities anchor at the entity itself;
355
+ * - a projection's base is the owner entity of its EXTENDED identity
356
+ * (`identity.primary { extends: "Customer.id" }` — declared structurally);
357
+ * - fallback (no identity): the single distinct entity targeted by the
358
+ * projection's plain field-`extends` refs; >1 distinct entity →
359
+ * ERR_AMBIGUOUS_PATH instructing the author to declare an extended
360
+ * identity; 0 → ERR_INVALID_ORIGIN (no base derivable, cannot infer).
361
+ *
362
+ * Returns undefined when no base is derivable (an error has been pushed).
363
+ */
364
+ /**
365
+ * FR-024: the entity NAMED by a node's dotted extends ref — the OWNER part of
366
+ * `<owner>.<child>...` resolved as an object. This differs from
367
+ * `superResolved.parent` when the resolved child is INHERITED: `Product.id`
368
+ * selecting BaseEntity's identity through Product's effective children must
369
+ * anchor `Product` (what the author wrote), never `BaseEntity` (where the
370
+ * child physically lives).
371
+ */
372
+ function _refNamedOwner(node, root) {
373
+ const ref = node.superRef;
374
+ if (ref === undefined)
375
+ return undefined;
376
+ const lastSep = ref.lastIndexOf("::");
377
+ const tail = lastSep === -1 ? ref : ref.slice(lastSep + 2);
378
+ const dot = tail.indexOf(".");
379
+ if (dot <= 0)
380
+ return undefined;
381
+ return _findObject(root, tail.slice(0, dot));
382
+ }
383
+ function _deriveBaseEntity(obj, root, fieldName, originSource, errors) {
384
+ if (obj.subType !== OBJECT_SUBTYPE_PROJECTION)
385
+ return obj;
386
+ // 1) The extended identity anchors the base entity (declared, not inferred).
387
+ // The anchor is the entity NAMED in the ref's owner part — see _refNamedOwner.
388
+ for (const identity of obj.ownChildren().filter((c) => c.type === TYPE_IDENTITY)) {
389
+ const extended = identity.superResolved;
390
+ if (extended !== undefined && extended.type === TYPE_IDENTITY) {
391
+ const named = _refNamedOwner(identity, root);
392
+ if (named !== undefined)
393
+ return named;
394
+ const owner = extended.parent;
395
+ if (owner !== undefined && owner.type === TYPE_OBJECT)
396
+ return owner;
397
+ }
398
+ }
399
+ // 2) Fallback: the single distinct entity targeted by plain field-extends —
400
+ // again preferring the ref-named owner over the physical declaring ancestor.
401
+ const targets = new Set();
402
+ for (const f of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
403
+ const sup = f.superResolved;
404
+ if (sup === undefined)
405
+ continue;
406
+ const named = _refNamedOwner(f, root);
407
+ const owner = named ?? sup.parent;
408
+ if (owner !== undefined &&
409
+ owner.type === TYPE_OBJECT &&
410
+ owner.subType !== OBJECT_SUBTYPE_VALUE &&
411
+ owner !== obj) {
412
+ targets.add(owner);
413
+ }
414
+ }
415
+ if (targets.size === 1)
416
+ return [...targets][0];
417
+ if (targets.size > 1) {
418
+ const names = [...targets].map((t) => `"${t.name}"`).join(", ");
419
+ errors.push(new ParseError(`origin on ${obj.name}.${fieldName}: cannot derive the base entity — the projection's fields extend ` +
420
+ `multiple entities (${names}) and no identity extends an entity identity. Declare an extended identity ` +
421
+ `(e.g. identity.primary { name: "id", extends: "<Entity>.<identity>" }) to anchor the base entity (FR-024).`, { code: "ERR_AMBIGUOUS_PATH", source: originSource }));
422
+ }
423
+ else {
424
+ errors.push(new ParseError(`origin on ${obj.name}.${fieldName}: cannot derive the base entity for @via inference — the projection ` +
425
+ `has no extended identity and no entity-targeted field extends. Declare an extended identity or an explicit @via (FR-024).`, { code: "ERR_INVALID_ORIGIN", source: originSource }));
426
+ }
427
+ return undefined;
428
+ }
429
+ /**
430
+ * True when the `@from`/`@of` target entity IS the host's base relation: the
431
+ * derived base entity itself, or an ancestor on the base's (or the host's)
432
+ * whole-object extends chain — the legacy `Summary extends Program` projection
433
+ * style inherits the base relation from its super, so `Program.title` on it is
434
+ * a base-relation column, not a join.
435
+ */
436
+ function _isBaseRelationTarget(target, base, host) {
437
+ for (let cur = base; cur !== undefined; cur = cur.superResolved) {
438
+ if (cur === target)
439
+ return true;
440
+ }
441
+ for (let cur = host; cur !== undefined; cur = cur.superResolved) {
442
+ if (cur === target)
443
+ return true;
444
+ }
445
+ return false;
446
+ }
447
+ /**
448
+ * Single-hop-unique `@via` inference (ADR-0029 decision 5): scan the base
449
+ * entity's EFFECTIVE relationship children for those whose @objectRef resolves
450
+ * to the `@from`/`@of` target entity. Exactly one → the inferred path (the
451
+ * caller proceeds exactly as if `@via` were declared with that relationship).
452
+ * Zero → ERR_INVALID_ORIGIN (cannot infer; multi-hop is always explicit).
453
+ * More than one → ERR_AMBIGUOUS_PATH naming the candidate relationships.
454
+ *
455
+ * Inference stops at single-hop-unique deliberately: the algorithm is part of
456
+ * the cross-port conformance contract; graph search is not trivially portable.
457
+ */
458
+ function _inferViaSingleHop(base, targetEntity, obj, fieldName, fromAttr, label, originSource, errors) {
459
+ const candidates = base
460
+ .children()
461
+ .filter((c) => c.type === TYPE_RELATIONSHIP)
462
+ .filter((rel) => {
463
+ const ref = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
464
+ return typeof ref === "string" && stripPackage(ref) === targetEntity.name;
465
+ });
466
+ // FR5d — referrer is `<host-FQN>::<fieldName>`, target is the from/of ref
467
+ // whose implicit path could not be resolved.
468
+ const referrer = `${obj.fqn()}::${fieldName}`;
469
+ if (candidates.length === 1)
470
+ return [candidates[0]];
471
+ if (candidates.length === 0) {
472
+ errors.push(new ParseError(`${label} "${fromAttr}" on ${obj.name}.${fieldName}: no @via and no single-hop relationship from base ` +
473
+ `entity "${base.name}" to "${targetEntity.name}" — cannot infer the path. Declare @via explicitly ` +
474
+ `(multi-hop paths are always explicit; ADR-0029).`, {
475
+ code: "ERR_INVALID_ORIGIN",
476
+ source: resolvedSource(originSource, referrer, fromAttr),
477
+ }));
478
+ return undefined;
479
+ }
480
+ const names = candidates.map((r) => `"${r.name}"`).join(", ");
481
+ errors.push(new ParseError(`${label} "${fromAttr}" on ${obj.name}.${fieldName}: no @via and ${candidates.length} relationships from ` +
482
+ `base entity "${base.name}" to "${targetEntity.name}" (${names}) — ambiguous. Declare @via naming one of them (ADR-0029).`, {
483
+ code: "ERR_AMBIGUOUS_PATH",
484
+ source: resolvedSource(originSource, referrer, fromAttr),
485
+ }));
486
+ return undefined;
487
+ }
488
+ /**
489
+ * ADR-0029 decision 6 — a passthrough via-path must be effectively to-one at
490
+ * EVERY hop. A hop is judged to-many only when it DECLARES `@cardinality:
491
+ * "many"`: @cardinality is an open string at the metamodel level (Java-
492
+ * canonical composite forms exist, and legacy fixtures omit it), so an
493
+ * absent/unknown cardinality is never misjudged.
494
+ */
495
+ function _checkPassthroughCardinality(hops, obj, fieldName, originSource, errors) {
496
+ for (const rel of hops) {
497
+ if (_hopCardinality(rel) === CARDINALITY_MANY) {
498
+ errors.push(new ParseError(`origin.passthrough on ${obj.name}.${fieldName}: @via hop "${rel.name}" is to-many ` +
499
+ `(@cardinality "${CARDINALITY_MANY}") — a row-multiplying passthrough — you meant aggregate (ADR-0029).`, { code: "ERR_ORIGIN_CARDINALITY", source: originSource }));
500
+ return;
501
+ }
502
+ }
503
+ }
504
+ /**
505
+ * ADR-0029 decision 6 — an aggregate via-path must contain at least one
506
+ * to-many hop. Conservative on the open @cardinality vocabulary: the error
507
+ * fires only when the path is PROVABLY to-one (every hop declares
508
+ * `@cardinality: "one"`); absent/composite cardinalities are not judged.
509
+ */
510
+ function _checkAggregateCardinality(hops, obj, fieldName, originSource, errors) {
511
+ if (hops.length === 0)
512
+ return;
513
+ const provablyToOne = hops.every((rel) => _hopCardinality(rel) === CARDINALITY_ONE);
514
+ if (provablyToOne) {
515
+ errors.push(new ParseError(`origin.aggregate on ${obj.name}.${fieldName}: every @via hop is to-one (@cardinality "${CARDINALITY_ONE}") — ` +
516
+ `aggregating over a to-one path — you meant passthrough (ADR-0029).`, { code: "ERR_ORIGIN_CARDINALITY", source: originSource }));
517
+ }
518
+ }
519
+ /**
520
+ * FR-024 B6 (spec §4; ADR-0029 decision 7) — extends/origin agreement.
521
+ *
522
+ * When a field declares BOTH an entity-nested `extends` (shape lineage) and
523
+ * an `origin.passthrough` @from (data lineage), the two are independent
524
+ * statements that must coincide: the resolved @from target must be THE SAME
525
+ * NODE as the field's resolved extends target — or appear on its extends
526
+ * chain (a projection field extending another projection's field that
527
+ * ultimately extends the entity field still agrees). Host-agnostic: applies
528
+ * on projections, entities, and values (FR-004 payloads may carry both).
529
+ *
530
+ * NOT judged:
531
+ * - `origin.aggregate` (it computes something new — no passthrough claim);
532
+ * - an extends whose target is a TOP-LEVEL abstract field (its parent is
533
+ * not an object) — shape-only reuse makes no lineage claim.
534
+ */
535
+ function _checkExtendsOriginAgreement(field, fromField, fromAttr, obj, originSource, errors) {
536
+ const sup = field.superResolved;
537
+ if (sup === undefined || sup.type !== TYPE_FIELD)
538
+ return;
539
+ const supOwner = sup.parent;
540
+ if (supOwner === undefined || supOwner.type !== TYPE_OBJECT)
541
+ return;
542
+ for (let cur = sup; cur !== undefined; cur = cur.superResolved) {
543
+ if (cur === fromField)
544
+ return; // shape lineage and data lineage agree
545
+ }
546
+ // FR5d resolved envelope: referrer = host::field, target = the @from ref.
547
+ errors.push(new ParseError(`origin.passthrough on ${obj.name}.${field.name}: @from "${fromAttr}" disagrees with the field's extends ` +
548
+ `target "${supOwner.name}.${sup.name}" — extends (shape lineage) and origin.passthrough (data lineage) ` +
549
+ `must point at the same entity field (FR-024).`, {
550
+ code: "ERR_EXTENDS_ORIGIN_MISMATCH",
551
+ source: resolvedSource(originSource, `${obj.fqn()}::${field.name}`, fromAttr),
552
+ }));
277
553
  }
278
554
  export function validateOriginPaths(root) {
279
555
  const errors = [];
280
556
  for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
557
+ // FR-024 B5: object.value hosts are EXEMPT from @via inference and
558
+ // cardinality checks — a value's origin.passthrough is FR-015 parameter
559
+ // lineage (values are constructed, never assembled; spec §7), not an
560
+ // assembly path. Their @from refs are still resolution-validated.
561
+ const isValueHost = obj.subType === OBJECT_SUBTYPE_VALUE;
281
562
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
282
563
  for (const origin of field.ownChildren().filter((c) => c.type === TYPE_ORIGIN)) {
283
564
  if (origin.subType === ORIGIN_SUBTYPE_PASSTHROUGH) {
@@ -288,10 +569,30 @@ export function validateOriginPaths(root) {
288
569
  errors.push(new ParseError(`origin.passthrough on ${obj.name}.${field.name}: missing @from.`, { code: "ERR_INVALID_ORIGIN", source: origin.source }));
289
570
  continue;
290
571
  }
291
- _validateFromPath(from, root, obj, field.name, origin.source, errors);
572
+ const fromTarget = _validateFromPath(from, root, obj, field.name, origin.source, errors);
573
+ // FR-024 B6 — extends/origin agreement (host-agnostic; runs whether
574
+ // @via is explicit, inferred, or a base-relation column).
575
+ if (fromTarget !== undefined) {
576
+ _checkExtendsOriginAgreement(field, fromTarget.field, from, obj, origin.source, errors);
577
+ }
292
578
  const via = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
293
579
  if (typeof via === "string" && via !== "") {
294
- _validateViaPath(via, root, obj, field.name, origin.source, errors);
580
+ const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
581
+ if (hops !== undefined) {
582
+ _checkPassthroughCardinality(hops, obj, field.name, origin.source, errors);
583
+ }
584
+ }
585
+ else if (fromTarget !== undefined && !isValueHost) {
586
+ // FR-024 §6 — no @via: derive the base entity; a @from targeting
587
+ // the base relation itself is a plain base column (no checks);
588
+ // otherwise infer the single-hop-unique path and gate cardinality.
589
+ const base = _deriveBaseEntity(obj, root, field.name, origin.source, errors);
590
+ if (base !== undefined && !_isBaseRelationTarget(fromTarget.entity, base, obj)) {
591
+ const hops = _inferViaSingleHop(base, fromTarget.entity, obj, field.name, from, "origin.passthrough.@from", origin.source, errors);
592
+ if (hops !== undefined) {
593
+ _checkPassthroughCardinality(hops, obj, field.name, origin.source, errors);
594
+ }
595
+ }
295
596
  }
296
597
  }
297
598
  else if (origin.subType === ORIGIN_SUBTYPE_AGGREGATE) {
@@ -300,13 +601,38 @@ export function validateOriginPaths(root) {
300
601
  errors.push(new ParseError(`origin.aggregate on ${obj.name}.${field.name}: missing @of.`, { code: "ERR_INVALID_ORIGIN", source: origin.source }));
301
602
  continue;
302
603
  }
303
- _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
604
+ // NOTE (FR-024 B6): NO extends/origin agreement check on aggregates —
605
+ // an aggregate computes something new (count/sum/…); spec §4 defines
606
+ // agreement for passthrough only.
607
+ const ofTarget = _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
304
608
  const via = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
305
- if (typeof via !== "string" || via === "") {
609
+ if (typeof via === "string" && via !== "") {
610
+ const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
611
+ if (hops !== undefined) {
612
+ _checkAggregateCardinality(hops, obj, field.name, origin.source, errors);
613
+ }
614
+ continue;
615
+ }
616
+ // FR-024 §6 — no @via on an aggregate: inference applies only when
617
+ // @of targets a non-base entity from a non-value host; an aggregate
618
+ // over the base relation itself still requires an explicit path.
619
+ if (ofTarget === undefined)
620
+ continue; // @of did not resolve — no inference to attempt
621
+ if (isValueHost) {
306
622
  errors.push(new ParseError(`origin.aggregate on ${obj.name}.${field.name}: missing @via (aggregates require a relationship path).`, { code: "ERR_INVALID_ORIGIN", source: origin.source }));
307
623
  continue;
308
624
  }
309
- _validateViaPath(via, root, obj, field.name, origin.source, errors);
625
+ const base = _deriveBaseEntity(obj, root, field.name, origin.source, errors);
626
+ if (base === undefined)
627
+ continue; // base underivable — error already pushed
628
+ if (_isBaseRelationTarget(ofTarget.entity, base, obj)) {
629
+ errors.push(new ParseError(`origin.aggregate on ${obj.name}.${field.name}: missing @via (aggregates require a relationship path).`, { code: "ERR_INVALID_ORIGIN", source: origin.source }));
630
+ continue;
631
+ }
632
+ const hops = _inferViaSingleHop(base, ofTarget.entity, obj, field.name, of_, "origin.aggregate.@of", origin.source, errors);
633
+ if (hops !== undefined) {
634
+ _checkAggregateCardinality(hops, obj, field.name, origin.source, errors);
635
+ }
310
636
  }
311
637
  }
312
638
  }
@@ -314,29 +640,82 @@ export function validateOriginPaths(root) {
314
640
  return errors;
315
641
  }
316
642
  // ---------------------------------------------------------------------------
317
- // @storage cross-attribute validation
643
+ // FR-024 B6 — derived-field providability (spec §7 population doctrine).
644
+ //
645
+ // An object.ENTITY field carrying any origin.* child is derived (read-only):
646
+ // it does not exist on the writable table — something must PROVIDE it on
647
+ // read. The spec §7 multi-source pattern is a writable table-primary source
648
+ // plus a read-only-kind source (view / materializedView / storedProc /
649
+ // tableFunction, e.g. @role "replica") that carries the derived fields.
650
+ // An entity whose only sources are writable kinds — or that has no source at
651
+ // all — cannot provide an origin-bearing field → ERR_DERIVED_FIELD_NO_READ_SOURCE
652
+ // on that field (plain node-source envelope).
318
653
  //
319
- // Rules:
320
- // 1. @storage requires @objectRef to be present (storage is meaningless
321
- // without a referenced object type).
654
+ // Exemptions:
655
+ // - object.projection the projection's own source/wire IS the provider;
656
+ // - object.value FR-015 lineage; values are constructed, never populated.
657
+ //
658
+ // The rule reads "at least one read-only-kind source, any role". Since the
659
+ // B4b hard cutover (ERR_ENTITY_PRIMARY_SOURCE_READONLY makes a read-only-kind
660
+ // PRIMARY illegal on entities), the only loadable satisfying shape is the
661
+ // strict one — a non-primary-role read-only source (the §7 multi-source
662
+ // pattern: table primary + view replica).
663
+ //
664
+ // Sources are scanned on the EFFECTIVE child view (children()) so an entity
665
+ // inheriting its sources from an abstract base is judged by what it actually
666
+ // has; fields + origins use the own view, mirroring validateOriginPaths.
667
+ // ---------------------------------------------------------------------------
668
+ export function validateDerivedFieldProvidability(root) {
669
+ const errors = [];
670
+ for (const obj of root
671
+ .ownChildren()
672
+ .filter((c) => c.type === TYPE_OBJECT && c.subType === OBJECT_SUBTYPE_ENTITY)) {
673
+ const hasReadCapableSource = obj
674
+ .children()
675
+ .filter((c) => c.type === TYPE_SOURCE)
676
+ .some((s) => s.isReadOnly());
677
+ if (hasReadCapableSource)
678
+ continue;
679
+ for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
680
+ if (!field.ownChildren().some((c) => c.type === TYPE_ORIGIN))
681
+ continue;
682
+ errors.push(new ParseError(`derived field "${obj.name}.${field.name}" carries an origin.* but entity "${obj.name}" declares no ` +
683
+ `read-capable source — derived fields do not exist on the writable table. Declare a read-only source ` +
684
+ `(e.g. source.rdb @kind "view" @role "replica") to provide it, or move the field to an object.projection (FR-024 §7).`, { code: "ERR_DERIVED_FIELD_NO_READ_SOURCE", source: field.source }));
685
+ }
686
+ }
687
+ return errors;
688
+ }
689
+ // ---------------------------------------------------------------------------
690
+ // field.object + @storage cross-attribute validation
691
+ //
692
+ // Rules (ADR-0013):
693
+ // 1. A field.object ALWAYS requires @objectRef. A field.object models a typed
694
+ // nested value; without @objectRef it is "an oxymoron at the logical layer".
695
+ // Genuinely open/untyped JSON uses the physical @dbColumnType: jsonb escape
696
+ // hatch on field.string, NOT a bare object. → ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF.
697
+ // (This rule subsumes the legacy @storage-without-@objectRef check —
698
+ // @storage is only meaningful on a field.object, so the missing-@objectRef
699
+ // situation now always reports this single, clearer error. One error per
700
+ // node: when @objectRef is absent we skip the flattened/array check below.)
322
701
  // 2. @storage "flattened" requires isArray to be absent or false (cannot
323
702
  // flatten a variable-length array into a fixed column set).
324
- //
325
- // Only field.object nodes carry @storage in practice, but the check is applied
326
- // to every field node that has @storage set — matching the permissive "check
327
- // what's there" model used by the other validation passes.
328
703
  // ---------------------------------------------------------------------------
329
704
  export function validateFieldObjectStorage(root) {
330
705
  const errors = [];
331
706
  for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
332
707
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
708
+ const objectRef = field.ownAttr(FIELD_ATTR_OBJECT_REF);
709
+ const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
710
+ if (field.subType === FIELD_SUBTYPE_OBJECT && !hasObjectRef) {
711
+ // A field.object with no @objectRef is rejected outright; reporting any
712
+ // further @storage error on the same node would be redundant.
713
+ errors.push(new ParseError(`field.object "${obj.name}.${field.name}" has no @objectRef; a field.object requires @objectRef. For an open/untyped JSON map use @dbColumnType: jsonb on a field.string instead of a bare object.`, { code: "ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF", source: field.source }));
714
+ continue;
715
+ }
333
716
  const storage = field.ownAttr(FIELD_ATTR_STORAGE);
334
717
  if (storage === undefined || storage === null)
335
718
  continue;
336
- const objectRef = field.ownAttr(FIELD_ATTR_OBJECT_REF);
337
- if (typeof objectRef !== "string" || objectRef.length === 0) {
338
- errors.push(new ParseError(`field "${obj.name}.${field.name}" sets @storage but has no @objectRef`, { code: "ERR_STORAGE_WITHOUT_OBJECT_REF", source: field.source }));
339
- }
340
719
  if (storage === STORAGE_FLATTENED && field.isArray === true) {
341
720
  errors.push(new ParseError(`field "${obj.name}.${field.name}" sets @storage "flattened" with isArray=true; flattened storage requires a single nested value`, { code: "ERR_STORAGE_FLATTENED_ARRAY", source: field.source }));
342
721
  }
@@ -461,6 +840,112 @@ export function validateDataGridFilterValues(root) {
461
840
  }
462
841
  return errors;
463
842
  }
843
+ // ---------------------------------------------------------------------------
844
+ // FR-017 — M:N relationship validation (slim vocabulary)
845
+ //
846
+ // Deferred-resolution validation (runs after all files load + extends:
847
+ // resolution, like origin paths), enforcing the cross-port M:N contract:
848
+ //
849
+ // (a) @symmetric:true is valid only on a self-join (@objectRef == declaring
850
+ // entity). Otherwise ERR_BAD_ATTR_VALUE.
851
+ // (b) @symmetric and @sourceRefField are mutually exclusive → ERR_BAD_ATTR_VALUE.
852
+ // (c) When @through is present: the named entity must exist and declare exactly
853
+ // two identity.reference children; @sourceRefField (if present) must match
854
+ // one of those references' FK fields → ERR_INVALID_RELATIONSHIP.
855
+ // (d) @through / @sourceRefField / @symmetric are invalid on a non-M:N
856
+ // relationship (@cardinality != "many", or no @through) → ERR_INVALID_RELATIONSHIP.
857
+ //
858
+ // Own-relationships only: a relationship is validated on the entity that declares
859
+ // it (matching the own-attrs policy of the other passes).
860
+ // ---------------------------------------------------------------------------
861
+ // The junction's reference view: the validator and the runtime/codegen FK
862
+ // derivation (deriveM2MFields) MUST agree on which references count. Both use
863
+ // the EFFECTIVE view (own + inherited via extends) via referenceIdentities(),
864
+ // so a junction defined through `extends` is treated identically here and at
865
+ // resolution time. (For a junction with no extends, effective == own.)
866
+ function _junctionReferences(junction) {
867
+ return junction.referenceIdentities();
868
+ }
869
+ /** FK field names declared by a junction's effective identity.reference children. */
870
+ function _junctionReferenceFkFields(junction) {
871
+ const out = [];
872
+ for (const ref of _junctionReferences(junction)) {
873
+ const first = ref.fields[0];
874
+ if (first)
875
+ out.push(first);
876
+ }
877
+ return out;
878
+ }
879
+ function _countJunctionReferences(junction) {
880
+ return _junctionReferences(junction).length;
881
+ }
882
+ export function validateRelationships(root) {
883
+ const errors = [];
884
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
885
+ for (const rel of obj.ownChildren().filter((c) => c.type === TYPE_RELATIONSHIP)) {
886
+ const through = rel.ownAttr(RELATIONSHIP_ATTR_THROUGH);
887
+ const sourceRefField = rel.ownAttr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
888
+ const symmetric = rel.ownAttr(RELATIONSHIP_ATTR_SYMMETRIC) === true;
889
+ const cardinality = rel.ownAttr(RELATIONSHIP_ATTR_CARDINALITY);
890
+ const objectRef = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
891
+ const hasThrough = typeof through === "string" && through !== "";
892
+ const hasSourceRefField = typeof sourceRefField === "string" && sourceRefField !== "";
893
+ const isMany = cardinality === CARDINALITY_MANY;
894
+ const isM2M = hasThrough && isMany;
895
+ // NOTE: @objectRef existence resolution moved to the validation registry
896
+ // (defaultValidationRegistry → a declarative reference descriptor). The M:N
897
+ // slim-vocabulary rules below stay here for now (Phase 3 migrates them too).
898
+ // Rule (d): M:N-only attrs on a non-M:N relationship.
899
+ if (!isM2M) {
900
+ if (hasThrough) {
901
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_THROUGH} but is not a M:N ` +
902
+ `relationship (requires @${RELATIONSHIP_ATTR_CARDINALITY}: "${CARDINALITY_MANY}").`, { code: "ERR_INVALID_RELATIONSHIP", source: rel.source }));
903
+ }
904
+ if (hasSourceRefField) {
905
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SOURCE_REF_FIELD} but is not a M:N relationship.`, { code: "ERR_INVALID_RELATIONSHIP", source: rel.source }));
906
+ }
907
+ if (symmetric) {
908
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SYMMETRIC} but is not a M:N relationship.`, { code: "ERR_INVALID_RELATIONSHIP", source: rel.source }));
909
+ }
910
+ continue;
911
+ }
912
+ // Rule (b): @symmetric and @sourceRefField are mutually exclusive.
913
+ if (symmetric && hasSourceRefField) {
914
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" sets both @${RELATIONSHIP_ATTR_SYMMETRIC} and ` +
915
+ `@${RELATIONSHIP_ATTR_SOURCE_REF_FIELD}; they are mutually exclusive.`, { code: "ERR_BAD_ATTR_VALUE", source: rel.source }));
916
+ }
917
+ // Rule (a): @symmetric is valid only on a self-join (@objectRef == declaring entity).
918
+ const isSelfJoin = typeof objectRef === "string" && stripPackage(objectRef) === obj.name;
919
+ if (symmetric && !isSelfJoin) {
920
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SYMMETRIC} but @${RELATIONSHIP_ATTR_OBJECT_REF} ` +
921
+ `"${String(objectRef)}" is not the declaring entity "${obj.name}"; @${RELATIONSHIP_ATTR_SYMMETRIC} is self-join-only.`, { code: "ERR_BAD_ATTR_VALUE", source: rel.source }));
922
+ }
923
+ // Rule (c): @through must name an entity declaring exactly two identity.reference children.
924
+ const junction = _findObject(root, through);
925
+ if (!junction) {
926
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_THROUGH} "${through}" does not resolve to an entity.`, { code: "ERR_INVALID_RELATIONSHIP", source: resolvedSource(rel.source, `${obj.fqn()}::${rel.name}`, String(through)) }));
927
+ continue;
928
+ }
929
+ const refCount = _countJunctionReferences(junction);
930
+ if (refCount !== 2) {
931
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_THROUGH} "${through}" must declare exactly two ` +
932
+ `identity.reference children (one per FK side); found ${refCount}.`, { code: "ERR_INVALID_RELATIONSHIP", source: rel.source }));
933
+ continue;
934
+ }
935
+ // @sourceRefField (if present) must match one of the junction's reference FK fields.
936
+ if (hasSourceRefField) {
937
+ const fkFields = _junctionReferenceFkFields(junction);
938
+ if (!fkFields.includes(sourceRefField)) {
939
+ errors.push(new ParseError(`relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_SOURCE_REF_FIELD} "${sourceRefField}" does not match ` +
940
+ `any identity.reference FK field on junction "${through}". Available: ${fkFields.join(", ") || "(none)"}.`, { code: "ERR_INVALID_RELATIONSHIP", source: rel.source }));
941
+ }
942
+ }
943
+ }
944
+ }
945
+ return errors;
946
+ }
947
+ // NOTE: identity.reference @references resolution moved to the validation registry
948
+ // (defaultValidationRegistry → a declarative reference descriptor with dottedFieldPath).
464
949
  function checkFilterClauses(filter, allow, entityName, layoutName, layoutSource, errors) {
465
950
  for (const [key, clause] of Object.entries(filter)) {
466
951
  if (key === FILTER_COMPOSE_OR || key === FILTER_COMPOSE_AND) {