@metaobjectsdev/metadata 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) 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 +84 -7
  4. package/dist/attr-schema-validate.js.map +1 -1
  5. package/dist/constants.d.ts +208 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/constants.js +419 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/constraint-merge.d.ts +18 -0
  10. package/dist/constraint-merge.d.ts.map +1 -0
  11. package/dist/constraint-merge.js +0 -0
  12. package/dist/constraint-merge.js.map +1 -0
  13. package/dist/constraint-validate.d.ts +6 -0
  14. package/dist/constraint-validate.d.ts.map +1 -0
  15. package/dist/constraint-validate.js +274 -0
  16. package/dist/constraint-validate.js.map +1 -0
  17. package/dist/core/attr/attr-constants.d.ts +13 -3
  18. package/dist/core/attr/attr-constants.d.ts.map +1 -1
  19. package/dist/core/attr/attr-constants.js +11 -2
  20. package/dist/core/attr/attr-constants.js.map +1 -1
  21. package/dist/core/attr/attr-definition.embedded.d.ts +3 -0
  22. package/dist/core/attr/attr-definition.embedded.d.ts.map +1 -0
  23. package/dist/core/attr/attr-definition.embedded.js +60 -0
  24. package/dist/core/attr/attr-definition.embedded.js.map +1 -0
  25. package/dist/core/documentation/doc-constants.d.ts +3 -2
  26. package/dist/core/documentation/doc-constants.d.ts.map +1 -1
  27. package/dist/core/documentation/doc-constants.js +3 -1
  28. package/dist/core/documentation/doc-constants.js.map +1 -1
  29. package/dist/core/documentation/doc-provider.d.ts.map +1 -1
  30. package/dist/core/documentation/doc-provider.js +6 -2
  31. package/dist/core/documentation/doc-provider.js.map +1 -1
  32. package/dist/core/documentation/doc-schema.d.ts +1 -1
  33. package/dist/core/documentation/doc-schema.d.ts.map +1 -1
  34. package/dist/core/documentation/doc-schema.js +13 -5
  35. package/dist/core/documentation/doc-schema.js.map +1 -1
  36. package/dist/core/documentation/documentation-definition.embedded.d.ts +3 -0
  37. package/dist/core/documentation/documentation-definition.embedded.d.ts.map +1 -0
  38. package/dist/core/documentation/documentation-definition.embedded.js +79 -0
  39. package/dist/core/documentation/documentation-definition.embedded.js.map +1 -0
  40. package/dist/core/field/field-constants.d.ts +7 -4
  41. package/dist/core/field/field-constants.d.ts.map +1 -1
  42. package/dist/core/field/field-constants.js +7 -7
  43. package/dist/core/field/field-constants.js.map +1 -1
  44. package/dist/core/field/field-definition.embedded.d.ts +3 -0
  45. package/dist/core/field/field-definition.embedded.d.ts.map +1 -0
  46. package/dist/core/field/field-definition.embedded.js +236 -0
  47. package/dist/core/field/field-definition.embedded.js.map +1 -0
  48. package/dist/core/field/field-schema.d.ts +0 -16
  49. package/dist/core/field/field-schema.d.ts.map +1 -1
  50. package/dist/core/field/field-schema.js +10 -158
  51. package/dist/core/field/field-schema.js.map +1 -1
  52. package/dist/core/field/meta-field.d.ts.map +1 -1
  53. package/dist/core/field/meta-field.js +7 -5
  54. package/dist/core/field/meta-field.js.map +1 -1
  55. package/dist/core/file-meta-data-loader.d.ts +18 -0
  56. package/dist/core/file-meta-data-loader.d.ts.map +1 -0
  57. package/dist/core/file-meta-data-loader.js +81 -0
  58. package/dist/core/file-meta-data-loader.js.map +1 -0
  59. package/dist/core/file-source.d.ts +12 -0
  60. package/dist/core/file-source.d.ts.map +1 -0
  61. package/dist/core/file-source.js +46 -0
  62. package/dist/core/file-source.js.map +1 -0
  63. package/dist/core/identity/identity-definition.embedded.d.ts +3 -0
  64. package/dist/core/identity/identity-definition.embedded.d.ts.map +1 -0
  65. package/dist/core/identity/identity-definition.embedded.js +91 -0
  66. package/dist/core/identity/identity-definition.embedded.js.map +1 -0
  67. package/dist/core/identity/identity-schema.d.ts.map +1 -1
  68. package/dist/core/identity/identity-schema.js +3 -2
  69. package/dist/core/identity/identity-schema.js.map +1 -1
  70. package/dist/core/identity/validate-identity-passthrough.d.ts +42 -0
  71. package/dist/core/identity/validate-identity-passthrough.d.ts.map +1 -0
  72. package/dist/core/identity/validate-identity-passthrough.js +158 -0
  73. package/dist/core/identity/validate-identity-passthrough.js.map +1 -0
  74. package/dist/core/object/object-constants.d.ts +2 -1
  75. package/dist/core/object/object-constants.d.ts.map +1 -1
  76. package/dist/core/object/object-constants.js +3 -0
  77. package/dist/core/object/object-constants.js.map +1 -1
  78. package/dist/core/object/object-definition.embedded.d.ts +3 -0
  79. package/dist/core/object/object-definition.embedded.d.ts.map +1 -0
  80. package/dist/core/object/object-definition.embedded.js +110 -0
  81. package/dist/core/object/object-definition.embedded.js.map +1 -0
  82. package/dist/core/object/validate-discriminator.d.ts.map +1 -1
  83. package/dist/core/object/validate-discriminator.js +1 -3
  84. package/dist/core/object/validate-discriminator.js.map +1 -1
  85. package/dist/core/query/query-constants.d.ts.map +1 -1
  86. package/dist/core/query/query-constants.js +5 -3
  87. package/dist/core/query/query-constants.js.map +1 -1
  88. package/dist/core/relationship/derive-m2m-fields.d.ts +26 -0
  89. package/dist/core/relationship/derive-m2m-fields.d.ts.map +1 -0
  90. package/dist/core/relationship/derive-m2m-fields.js +102 -0
  91. package/dist/core/relationship/derive-m2m-fields.js.map +1 -0
  92. package/dist/core/relationship/meta-relationship.d.ts +6 -4
  93. package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
  94. package/dist/core/relationship/meta-relationship.js +12 -8
  95. package/dist/core/relationship/meta-relationship.js.map +1 -1
  96. package/dist/core/relationship/relationship-constants.d.ts +6 -2
  97. package/dist/core/relationship/relationship-constants.d.ts.map +1 -1
  98. package/dist/core/relationship/relationship-constants.js +6 -2
  99. package/dist/core/relationship/relationship-constants.js.map +1 -1
  100. package/dist/core/relationship/relationship-definition.embedded.d.ts +3 -0
  101. package/dist/core/relationship/relationship-definition.embedded.d.ts.map +1 -0
  102. package/dist/core/relationship/relationship-definition.embedded.js +310 -0
  103. package/dist/core/relationship/relationship-definition.embedded.js.map +1 -0
  104. package/dist/core/relationship/relationship-schema.d.ts.map +1 -1
  105. package/dist/core/relationship/relationship-schema.js +13 -7
  106. package/dist/core/relationship/relationship-schema.js.map +1 -1
  107. package/dist/core/validator/validator-definition.embedded.d.ts +3 -0
  108. package/dist/core/validator/validator-definition.embedded.d.ts.map +1 -0
  109. package/dist/core/validator/validator-definition.embedded.js +134 -0
  110. package/dist/core/validator/validator-definition.embedded.js.map +1 -0
  111. package/dist/core/yaml-desugar.d.ts.map +1 -1
  112. package/dist/core/yaml-desugar.js +88 -10
  113. package/dist/core/yaml-desugar.js.map +1 -1
  114. package/dist/core-attr-schemas.d.ts +22 -0
  115. package/dist/core-attr-schemas.d.ts.map +1 -0
  116. package/dist/core-attr-schemas.js +324 -0
  117. package/dist/core-attr-schemas.js.map +1 -0
  118. package/dist/core-types.d.ts +5 -2
  119. package/dist/core-types.d.ts.map +1 -1
  120. package/dist/core-types.js +261 -115
  121. package/dist/core-types.js.map +1 -1
  122. package/dist/db/db-attr-schemas.d.ts +8 -0
  123. package/dist/db/db-attr-schemas.d.ts.map +1 -0
  124. package/dist/db/db-attr-schemas.js +26 -0
  125. package/dist/db/db-attr-schemas.js.map +1 -0
  126. package/dist/db/db-provider.d.ts +3 -0
  127. package/dist/db/db-provider.d.ts.map +1 -0
  128. package/dist/db/db-provider.js +28 -0
  129. package/dist/db/db-provider.js.map +1 -0
  130. package/dist/errors.d.ts +1 -1
  131. package/dist/errors.d.ts.map +1 -1
  132. package/dist/errors.js +78 -0
  133. package/dist/errors.js.map +1 -1
  134. package/dist/index.d.ts +17 -2
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.js +26 -1
  137. package/dist/index.js.map +1 -1
  138. package/dist/library/embedded-library.generated.d.ts +2 -0
  139. package/dist/library/embedded-library.generated.d.ts.map +1 -0
  140. package/dist/library/embedded-library.generated.js +11 -0
  141. package/dist/library/embedded-library.generated.js.map +1 -0
  142. package/dist/library/library-sources.d.ts +12 -0
  143. package/dist/library/library-sources.d.ts.map +1 -0
  144. package/dist/library/library-sources.js +88 -0
  145. package/dist/library/library-sources.js.map +1 -0
  146. package/dist/loader/meta-data-loader.d.ts +10 -2
  147. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  148. package/dist/loader/meta-data-loader.js +47 -6
  149. package/dist/loader/meta-data-loader.js.map +1 -1
  150. package/dist/loader/shortcuts.d.ts +2 -5
  151. package/dist/loader/shortcuts.d.ts.map +1 -1
  152. package/dist/loader/shortcuts.js.map +1 -1
  153. package/dist/loader/validation-passes.d.ts +3 -0
  154. package/dist/loader/validation-passes.d.ts.map +1 -1
  155. package/dist/loader/validation-passes.js +513 -33
  156. package/dist/loader/validation-passes.js.map +1 -1
  157. package/dist/meta/find-reference.d.ts +22 -0
  158. package/dist/meta/find-reference.d.ts.map +1 -0
  159. package/dist/meta/find-reference.js +29 -0
  160. package/dist/meta/find-reference.js.map +1 -0
  161. package/dist/meta/meta-attr.d.ts +8 -0
  162. package/dist/meta/meta-attr.d.ts.map +1 -0
  163. package/dist/meta/meta-attr.js +17 -0
  164. package/dist/meta/meta-attr.js.map +1 -0
  165. package/dist/meta/meta-data.d.ts +107 -0
  166. package/dist/meta/meta-data.d.ts.map +1 -0
  167. package/dist/meta/meta-data.js +302 -0
  168. package/dist/meta/meta-data.js.map +1 -0
  169. package/dist/meta/meta-field.d.ts +48 -0
  170. package/dist/meta/meta-field.d.ts.map +1 -0
  171. package/dist/meta/meta-field.js +94 -0
  172. package/dist/meta/meta-field.js.map +1 -0
  173. package/dist/meta/meta-identity.d.ts +71 -0
  174. package/dist/meta/meta-identity.d.ts.map +1 -0
  175. package/dist/meta/meta-identity.js +129 -0
  176. package/dist/meta/meta-identity.js.map +1 -0
  177. package/dist/meta/meta-layout.d.ts +23 -0
  178. package/dist/meta/meta-layout.d.ts.map +1 -0
  179. package/dist/meta/meta-layout.js +45 -0
  180. package/dist/meta/meta-layout.js.map +1 -0
  181. package/dist/meta/meta-object.d.ts +40 -0
  182. package/dist/meta/meta-object.d.ts.map +1 -0
  183. package/dist/meta/meta-object.js +81 -0
  184. package/dist/meta/meta-object.js.map +1 -0
  185. package/dist/meta/meta-origin.d.ts +32 -0
  186. package/dist/meta/meta-origin.d.ts.map +1 -0
  187. package/dist/meta/meta-origin.js +55 -0
  188. package/dist/meta/meta-origin.js.map +1 -0
  189. package/dist/meta/meta-relationship.d.ts +11 -0
  190. package/dist/meta/meta-relationship.d.ts.map +1 -0
  191. package/dist/meta/meta-relationship.js +27 -0
  192. package/dist/meta/meta-relationship.js.map +1 -0
  193. package/dist/meta/meta-root.d.ts +12 -0
  194. package/dist/meta/meta-root.d.ts.map +1 -0
  195. package/dist/meta/meta-root.js +24 -0
  196. package/dist/meta/meta-root.js.map +1 -0
  197. package/dist/meta/meta-source.d.ts +18 -0
  198. package/dist/meta/meta-source.d.ts.map +1 -0
  199. package/dist/meta/meta-source.js +31 -0
  200. package/dist/meta/meta-source.js.map +1 -0
  201. package/dist/meta/meta-validator.d.ts +29 -0
  202. package/dist/meta/meta-validator.d.ts.map +1 -0
  203. package/dist/meta/meta-validator.js +49 -0
  204. package/dist/meta/meta-validator.js.map +1 -0
  205. package/dist/meta/meta-view.d.ts +4 -0
  206. package/dist/meta/meta-view.d.ts.map +1 -0
  207. package/dist/meta/meta-view.js +8 -0
  208. package/dist/meta/meta-view.js.map +1 -0
  209. package/dist/metamodel-docs/index.d.ts +19 -0
  210. package/dist/metamodel-docs/index.d.ts.map +1 -0
  211. package/dist/metamodel-docs/index.js +37 -0
  212. package/dist/metamodel-docs/index.js.map +1 -0
  213. package/dist/metamodel-docs/provenance.d.ts +42 -0
  214. package/dist/metamodel-docs/provenance.d.ts.map +1 -0
  215. package/dist/metamodel-docs/provenance.js +148 -0
  216. package/dist/metamodel-docs/provenance.js.map +1 -0
  217. package/dist/metamodel-docs/provider-definitions.d.ts +8 -0
  218. package/dist/metamodel-docs/provider-definitions.d.ts.map +1 -0
  219. package/dist/metamodel-docs/provider-definitions.js +48 -0
  220. package/dist/metamodel-docs/provider-definitions.js.map +1 -0
  221. package/dist/metamodel-docs/render.d.ts +12 -0
  222. package/dist/metamodel-docs/render.d.ts.map +1 -0
  223. package/dist/metamodel-docs/render.js +252 -0
  224. package/dist/metamodel-docs/render.js.map +1 -0
  225. package/dist/naming-refs.d.ts +41 -0
  226. package/dist/naming-refs.d.ts.map +1 -0
  227. package/dist/naming-refs.js +144 -0
  228. package/dist/naming-refs.js.map +1 -0
  229. package/dist/naming.d.ts.map +1 -1
  230. package/dist/naming.js +10 -2
  231. package/dist/naming.js.map +1 -1
  232. package/dist/parser-core.d.ts.map +1 -1
  233. package/dist/parser-core.js +74 -8
  234. package/dist/parser-core.js.map +1 -1
  235. package/dist/persistence/db/db-attr-schemas.d.ts +8 -0
  236. package/dist/persistence/db/db-attr-schemas.d.ts.map +1 -0
  237. package/dist/persistence/db/db-attr-schemas.js +28 -0
  238. package/dist/persistence/db/db-attr-schemas.js.map +1 -0
  239. package/dist/persistence/db/db-definition.embedded.d.ts +3 -0
  240. package/dist/persistence/db/db-definition.embedded.d.ts.map +1 -0
  241. package/dist/persistence/db/db-definition.embedded.js +170 -0
  242. package/dist/persistence/db/db-definition.embedded.js.map +1 -0
  243. package/dist/persistence/db/db-provider.d.ts.map +1 -1
  244. package/dist/persistence/db/db-provider.js +13 -18
  245. package/dist/persistence/db/db-provider.js.map +1 -1
  246. package/dist/persistence/db/db-schema.d.ts +14 -0
  247. package/dist/persistence/db/db-schema.d.ts.map +1 -1
  248. package/dist/persistence/db/db-schema.js +27 -0
  249. package/dist/persistence/db/db-schema.js.map +1 -1
  250. package/dist/persistence/origin/origin-definition.embedded.d.ts +3 -0
  251. package/dist/persistence/origin/origin-definition.embedded.d.ts.map +1 -0
  252. package/dist/persistence/origin/origin-definition.embedded.js +88 -0
  253. package/dist/persistence/origin/origin-definition.embedded.js.map +1 -0
  254. package/dist/persistence/origin/origin-schema.d.ts.map +1 -1
  255. package/dist/persistence/origin/origin-schema.js +7 -3
  256. package/dist/persistence/origin/origin-schema.js.map +1 -1
  257. package/dist/persistence/source/source-definition.embedded.d.ts +3 -0
  258. package/dist/persistence/source/source-definition.embedded.d.ts.map +1 -0
  259. package/dist/persistence/source/source-definition.embedded.js +17 -0
  260. package/dist/persistence/source/source-definition.embedded.js.map +1 -0
  261. package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -1
  262. package/dist/persistence/source/validate-source-parameter-ref.js +7 -1
  263. package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -1
  264. package/dist/persistence/source/validate-source-roles.d.ts.map +1 -1
  265. package/dist/persistence/source/validate-source-roles.js +16 -1
  266. package/dist/persistence/source/validate-source-roles.js.map +1 -1
  267. package/dist/presentation/layout/layout-definition.embedded.d.ts +3 -0
  268. package/dist/presentation/layout/layout-definition.embedded.d.ts.map +1 -0
  269. package/dist/presentation/layout/layout-definition.embedded.js +16 -0
  270. package/dist/presentation/layout/layout-definition.embedded.js.map +1 -0
  271. package/dist/presentation/layout/layout-schema.d.ts.map +1 -1
  272. package/dist/presentation/layout/layout-schema.js +3 -2
  273. package/dist/presentation/layout/layout-schema.js.map +1 -1
  274. package/dist/presentation/ui/ui-definition.embedded.d.ts +3 -0
  275. package/dist/presentation/ui/ui-definition.embedded.d.ts.map +1 -0
  276. package/dist/presentation/ui/ui-definition.embedded.js +114 -0
  277. package/dist/presentation/ui/ui-definition.embedded.js.map +1 -0
  278. package/dist/presentation/ui/ui-provider.d.ts +3 -0
  279. package/dist/presentation/ui/ui-provider.d.ts.map +1 -0
  280. package/dist/presentation/ui/ui-provider.js +21 -0
  281. package/dist/presentation/ui/ui-provider.js.map +1 -0
  282. package/dist/presentation/ui/ui-schema.d.ts +10 -0
  283. package/dist/presentation/ui/ui-schema.d.ts.map +1 -0
  284. package/dist/presentation/ui/ui-schema.js +41 -0
  285. package/dist/presentation/ui/ui-schema.js.map +1 -0
  286. package/dist/presentation/view/view-definition.embedded.d.ts +3 -0
  287. package/dist/presentation/view/view-definition.embedded.d.ts.map +1 -0
  288. package/dist/presentation/view/view-definition.embedded.js +76 -0
  289. package/dist/presentation/view/view-definition.embedded.js.map +1 -0
  290. package/dist/provider-data.d.ts +169 -0
  291. package/dist/provider-data.d.ts.map +1 -0
  292. package/dist/provider-data.js +269 -0
  293. package/dist/provider-data.js.map +1 -0
  294. package/dist/provider.d.ts +3 -1
  295. package/dist/provider.d.ts.map +1 -1
  296. package/dist/provider.js +15 -1
  297. package/dist/provider.js.map +1 -1
  298. package/dist/registry-coverage.d.ts +99 -0
  299. package/dist/registry-coverage.d.ts.map +1 -0
  300. package/dist/registry-coverage.js +294 -0
  301. package/dist/registry-coverage.js.map +1 -0
  302. package/dist/registry-manifest-exclusions.d.ts +62 -0
  303. package/dist/registry-manifest-exclusions.d.ts.map +1 -0
  304. package/dist/registry-manifest-exclusions.js +163 -0
  305. package/dist/registry-manifest-exclusions.js.map +1 -0
  306. package/dist/registry-manifest.d.ts +117 -0
  307. package/dist/registry-manifest.d.ts.map +1 -0
  308. package/dist/registry-manifest.js +242 -0
  309. package/dist/registry-manifest.js.map +1 -0
  310. package/dist/registry.d.ts +60 -2
  311. package/dist/registry.d.ts.map +1 -1
  312. package/dist/registry.js +37 -1
  313. package/dist/registry.js.map +1 -1
  314. package/dist/shared/structural.d.ts +7 -0
  315. package/dist/shared/structural.d.ts.map +1 -1
  316. package/dist/shared/structural.js +7 -0
  317. package/dist/shared/structural.js.map +1 -1
  318. package/dist/subtype-rules.d.ts.map +1 -1
  319. package/dist/subtype-rules.js +97 -13
  320. package/dist/subtype-rules.js.map +1 -1
  321. package/dist/super-resolve.d.ts +49 -2
  322. package/dist/super-resolve.d.ts.map +1 -1
  323. package/dist/super-resolve.js +128 -43
  324. package/dist/super-resolve.js.map +1 -1
  325. package/dist/template/meta-template.d.ts +3 -2
  326. package/dist/template/meta-template.d.ts.map +1 -1
  327. package/dist/template/meta-template.js +3 -2
  328. package/dist/template/meta-template.js.map +1 -1
  329. package/dist/template/prompt-definition.embedded.d.ts +3 -0
  330. package/dist/template/prompt-definition.embedded.d.ts.map +1 -0
  331. package/dist/template/prompt-definition.embedded.js +368 -0
  332. package/dist/template/prompt-definition.embedded.js.map +1 -0
  333. package/dist/template/prompt-provider.d.ts +3 -0
  334. package/dist/template/prompt-provider.d.ts.map +1 -0
  335. package/dist/template/prompt-provider.js +25 -0
  336. package/dist/template/prompt-provider.js.map +1 -0
  337. package/dist/template/prompt-schema.d.ts +20 -0
  338. package/dist/template/prompt-schema.d.ts.map +1 -0
  339. package/dist/template/prompt-schema.js +70 -0
  340. package/dist/template/prompt-schema.js.map +1 -0
  341. package/dist/template/template-constants.d.ts +2 -0
  342. package/dist/template/template-constants.d.ts.map +1 -1
  343. package/dist/template/template-constants.js +7 -0
  344. package/dist/template/template-constants.js.map +1 -1
  345. package/dist/template/template-definition.embedded.d.ts +3 -0
  346. package/dist/template/template-definition.embedded.d.ts.map +1 -0
  347. package/dist/template/template-definition.embedded.js +30 -0
  348. package/dist/template/template-definition.embedded.js.map +1 -0
  349. package/dist/template/template-schema.d.ts.map +1 -1
  350. package/dist/template/template-schema.js +12 -4
  351. package/dist/template/template-schema.js.map +1 -1
  352. package/package.json +33 -22
  353. package/src/attr-schema-validate.ts +96 -4
  354. package/src/constraint-merge.ts +0 -0
  355. package/src/constraint-validate.ts +363 -0
  356. package/src/core/attr/attr-constants.ts +15 -3
  357. package/src/core/attr/attr-definition.embedded.ts +67 -0
  358. package/src/core/documentation/doc-constants.ts +3 -1
  359. package/src/core/documentation/doc-provider.ts +6 -2
  360. package/src/core/documentation/documentation-definition.embedded.ts +86 -0
  361. package/src/core/field/field-constants.ts +8 -7
  362. package/src/core/field/field-definition.embedded.ts +243 -0
  363. package/src/core/field/meta-field.ts +6 -7
  364. package/src/core/identity/identity-definition.embedded.ts +98 -0
  365. package/src/core/identity/validate-identity-passthrough.ts +194 -0
  366. package/src/core/object/object-constants.ts +3 -0
  367. package/src/core/object/object-definition.embedded.ts +117 -0
  368. package/src/core/object/validate-discriminator.ts +0 -4
  369. package/src/core/query/query-constants.ts +5 -3
  370. package/src/core/relationship/derive-m2m-fields.ts +145 -0
  371. package/src/core/relationship/meta-relationship.ts +15 -9
  372. package/src/core/relationship/relationship-constants.ts +6 -2
  373. package/src/core/relationship/relationship-definition.embedded.ts +317 -0
  374. package/src/core/validator/validator-definition.embedded.ts +141 -0
  375. package/src/core/yaml-desugar.ts +96 -7
  376. package/src/core-types.ts +289 -150
  377. package/src/errors.ts +78 -0
  378. package/src/index.ts +47 -2
  379. package/src/library/embedded-library.generated.ts +10 -0
  380. package/src/library/library-sources.ts +97 -0
  381. package/src/loader/meta-data-loader.ts +66 -7
  382. package/src/loader/shortcuts.ts +2 -2
  383. package/src/loader/validation-passes.ts +679 -33
  384. package/src/metamodel-docs/index.ts +41 -0
  385. package/src/metamodel-docs/provenance.ts +187 -0
  386. package/src/metamodel-docs/provider-definitions.ts +50 -0
  387. package/src/metamodel-docs/render.ts +309 -0
  388. package/src/naming-refs.ts +162 -0
  389. package/src/naming.ts +10 -2
  390. package/src/parser-core.ts +86 -8
  391. package/src/persistence/db/db-definition.embedded.ts +177 -0
  392. package/src/persistence/db/db-provider.ts +13 -18
  393. package/src/persistence/origin/origin-definition.embedded.ts +95 -0
  394. package/src/persistence/source/source-definition.embedded.ts +24 -0
  395. package/src/persistence/source/validate-source-parameter-ref.ts +7 -1
  396. package/src/persistence/source/validate-source-roles.ts +22 -1
  397. package/src/presentation/layout/layout-definition.embedded.ts +23 -0
  398. package/src/presentation/ui/ui-definition.embedded.ts +121 -0
  399. package/src/presentation/ui/ui-provider.ts +25 -0
  400. package/src/presentation/view/view-definition.embedded.ts +83 -0
  401. package/src/provider-data.ts +446 -0
  402. package/src/provider.ts +18 -0
  403. package/src/registry-coverage.ts +430 -0
  404. package/src/registry-manifest-exclusions.ts +176 -0
  405. package/src/registry-manifest.ts +334 -0
  406. package/src/registry.ts +90 -3
  407. package/src/shared/structural.ts +8 -0
  408. package/src/subtype-rules.ts +135 -18
  409. package/src/super-resolve.ts +153 -43
  410. package/src/template/meta-template.ts +3 -2
  411. package/src/template/prompt-definition.embedded.ts +375 -0
  412. package/src/template/prompt-provider.ts +29 -0
  413. package/src/template/template-constants.ts +8 -0
  414. package/src/template/template-definition.embedded.ts +37 -0
  415. package/src/core/documentation/doc-schema.ts +0 -64
  416. package/src/core/field/field-schema.ts +0 -228
  417. package/src/core/identity/identity-schema.ts +0 -80
  418. package/src/core/object/object-schema.ts +0 -35
  419. package/src/core/relationship/relationship-schema.ts +0 -67
  420. package/src/core/validator/validator-schema.ts +0 -50
  421. package/src/persistence/db/db-schema.ts +0 -50
  422. package/src/persistence/origin/origin-schema.ts +0 -80
  423. package/src/persistence/source/source-schema.ts +0 -129
  424. package/src/presentation/layout/layout-schema.ts +0 -62
  425. package/src/presentation/view/view-schema.ts +0 -21
  426. package/src/template/template-schema.ts +0 -211
@@ -4,13 +4,17 @@
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
 
12
13
  import type { MetaData } from "../shared/meta-data.js";
14
+ import type { MetaObject } from "../core/object/meta-object.js";
15
+ import type { MetaReferenceIdentity } from "../core/identity/meta-identity.js";
13
16
  import { ParseError } from "../errors.js";
17
+ import { refMatchesObject } from "../naming-refs.js";
14
18
  import { resolvedSource, type ErrorSource } from "../source.js";
15
19
  import {
16
20
  TYPE_OBJECT,
@@ -19,10 +23,12 @@ import {
19
23
  TYPE_IDENTITY,
20
24
  TYPE_ORIGIN,
21
25
  TYPE_RELATIONSHIP,
26
+ TYPE_SOURCE,
22
27
  TYPE_TEMPLATE,
23
28
  } from "../shared/base-types.js";
24
29
  import {
25
30
  TEMPLATE_ATTR_PAYLOAD_REF,
31
+ TEMPLATE_ATTR_RESPONSE_REF,
26
32
  TEMPLATE_ATTR_REQUIRED_SLOTS,
27
33
  TEMPLATE_ATTR_TEXT_REF,
28
34
  TEMPLATE_ATTR_KIND,
@@ -32,7 +38,12 @@ import {
32
38
  TEMPLATE_SUBTYPE_OUTPUT,
33
39
  TEMPLATE_SUBTYPE_PROMPT,
34
40
  } from "../template/template-constants.js";
35
- import { OBJECT_SUBTYPE_VALUE } from "../core/object/object-constants.js";
41
+ import {
42
+ OBJECT_SUBTYPE_ENTITY,
43
+ OBJECT_SUBTYPE_VALUE,
44
+ OBJECT_SUBTYPE_PROJECTION,
45
+ } from "../core/object/object-constants.js";
46
+ import { MetaSource } from "../persistence/source/meta-source.js";
36
47
  import {
37
48
  LAYOUT_SUBTYPE_DATA_GRID,
38
49
  LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD,
@@ -52,6 +63,7 @@ import {
52
63
  FIELD_SUBTYPE_DECIMAL,
53
64
  FIELD_SUBTYPE_BOOLEAN,
54
65
  FIELD_SUBTYPE_ENUM,
66
+ FIELD_SUBTYPE_OBJECT,
55
67
  } from "../core/field/field-constants.js";
56
68
  import { FIELD_ATTR_DB_INDEXED } from "../persistence/db/db-constants.js";
57
69
  import { IDENTITY_ATTR_FIELDS } from "../core/identity/identity-constants.js";
@@ -63,7 +75,16 @@ import {
63
75
  ORIGIN_AGGREGATE_ATTR_OF,
64
76
  ORIGIN_AGGREGATE_ATTR_VIA,
65
77
  } from "../persistence/origin/origin-constants.js";
66
- import { RELATIONSHIP_ATTR_OBJECT_REF } from "../core/relationship/relationship-constants.js";
78
+ import {
79
+ RELATIONSHIP_ATTR_OBJECT_REF,
80
+ RELATIONSHIP_ATTR_CARDINALITY,
81
+ RELATIONSHIP_ATTR_THROUGH,
82
+ RELATIONSHIP_ATTR_SOURCE_REF_FIELD,
83
+ RELATIONSHIP_ATTR_SYMMETRIC,
84
+ CARDINALITY_ONE,
85
+ CARDINALITY_MANY,
86
+ } from "../core/relationship/relationship-constants.js";
87
+ import { stripPackage } from "../naming.js";
67
88
  import {
68
89
  FILTER_COMPOSE_OR,
69
90
  FILTER_COMPOSE_AND,
@@ -110,9 +131,19 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
110
131
  // `meta verify` step, not here.
111
132
  // ---------------------------------------------------------------------------
112
133
 
134
+ /** Recursively collect all template.* nodes anywhere in the metadata tree. */
135
+ function allTemplates(node: MetaData): MetaData[] {
136
+ const out: MetaData[] = [];
137
+ for (const c of node.ownChildren()) {
138
+ if (c.type === TYPE_TEMPLATE) out.push(c);
139
+ out.push(...allTemplates(c));
140
+ }
141
+ return out;
142
+ }
143
+
113
144
  export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
114
145
  const errors: ParseError[] = [];
115
- for (const tmpl of root.ownChildren().filter((c) => c.type === TYPE_TEMPLATE)) {
146
+ for (const tmpl of allTemplates(root)) {
116
147
  // --- @kind / textRef / email part-ref cross-field rules ---
117
148
  // template.output is either a document (@kind absent/"document" → @textRef
118
149
  // required) or an email (@kind="email" → @subjectRef + @htmlBodyRef required,
@@ -165,7 +196,7 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
165
196
 
166
197
  const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
167
198
  if (typeof payloadRef !== "string") continue; // absence handled by the required-attr schema check
168
- const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === payloadRef);
199
+ const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
169
200
  if (!payload || payload.subType !== OBJECT_SUBTYPE_VALUE) {
170
201
  // FR5d — @payloadRef is a reference; emit format=resolved with
171
202
  // referrer=template FQN, target=the unresolved payloadRef string.
@@ -180,6 +211,18 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
180
211
  );
181
212
  continue;
182
213
  }
214
+ const responseRef = tmpl.ownAttr(TEMPLATE_ATTR_RESPONSE_REF);
215
+ if (typeof responseRef === "string") {
216
+ const resVo = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, responseRef));
217
+ if (!resVo || resVo.subType !== OBJECT_SUBTYPE_VALUE) {
218
+ errors.push(
219
+ new ParseError(
220
+ `template "${tmpl.name}" @responseRef "${responseRef}" does not resolve to an object.value at root`,
221
+ { code: "ERR_INVALID_TEMPLATE", source: resolvedSource(tmpl.source, tmpl.fqn(), responseRef) },
222
+ ),
223
+ );
224
+ }
225
+ }
183
226
  const fieldNames = new Set(
184
227
  payload.children().filter((c) => c.type === TYPE_FIELD).map((f) => f.name),
185
228
  );
@@ -242,6 +285,35 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
242
285
  return warnings;
243
286
  }
244
287
 
288
+ // ---------------------------------------------------------------------------
289
+ // @filterable on a subtype with no operator band (SP-H Unit9)
290
+ // ---------------------------------------------------------------------------
291
+ // A field marked @filterable: true whose subtype has NO entry in OPS_BY_SUBTYPE
292
+ // (e.g. field.object, or any extension subtype without a declared op band)
293
+ // would silently generate a filter type/allowlist with an empty op set — a
294
+ // filter route that rejects every request. Error early instead of shipping
295
+ // broken codegen. → ERR_FILTERABLE_UNSUPPORTED_SUBTYPE.
296
+
297
+ export function validateFilterableHasSupportedOps(root: MetaData): ParseError[] {
298
+ const errors: ParseError[] = [];
299
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
300
+ // children() — inherited @filterable fields (via extends:/super:) are visible.
301
+ for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
302
+ if (field.ownAttr(FIELD_ATTR_FILTERABLE) !== true) continue;
303
+ if (opsForSubType(field.subType).length > 0) continue;
304
+ errors.push(
305
+ new ParseError(
306
+ `Field "${obj.name}.${field.name}" has @filterable: true but its subtype ` +
307
+ `"${field.subType}" has no filter-operator band. Remove @filterable, or use a ` +
308
+ `field subtype that supports filtering (string/enum/uuid/number/currency/date/boolean).`,
309
+ { code: "ERR_FILTERABLE_UNSUPPORTED_SUBTYPE", source: field.source },
310
+ ),
311
+ );
312
+ }
313
+ }
314
+ return errors;
315
+ }
316
+
245
317
  // ---------------------------------------------------------------------------
246
318
  // Origin path validation
247
319
  //
@@ -257,7 +329,9 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
257
329
  // ---------------------------------------------------------------------------
258
330
 
259
331
  function _findObject(root: MetaData, name: string): MetaData | undefined {
260
- return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
332
+ // FR-032 origin heads are FQN-qualified after the desugar/sweep; match on
333
+ // the effective FQN resolution key (with bare back-compat).
334
+ return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
261
335
  }
262
336
 
263
337
  function _findField(obj: MetaData, name: string): MetaData | undefined {
@@ -270,6 +344,21 @@ function _findRelationship(obj: MetaData, name: string): MetaData | undefined {
270
344
  return obj.children().find((c) => c.type === TYPE_RELATIONSHIP && c.name === name);
271
345
  }
272
346
 
347
+ /** Resolved `Entity.field` reference target: the entity AND the field node.
348
+ * FR-024 B5 inference needs the entity; the B6 extends/origin agreement
349
+ * check compares against the field node identity. */
350
+ interface ResolvedFromTarget {
351
+ readonly entity: MetaData;
352
+ readonly field: MetaData;
353
+ }
354
+
355
+ /**
356
+ * Validate a passthrough `@from` / aggregate `@of` "Entity.field" reference.
357
+ * Returns the resolved target entity + field on full success (FR-024 B5 —
358
+ * the inference/cardinality stage needs the entity; B6 agreement needs the
359
+ * field), or undefined when any error was pushed (malformed shape / unknown
360
+ * entity / unknown field).
361
+ */
273
362
  function _validateFromPath(
274
363
  fromAttr: string,
275
364
  root: MetaData,
@@ -278,7 +367,7 @@ function _validateFromPath(
278
367
  originSource: ErrorSource,
279
368
  errors: ParseError[],
280
369
  label: string = "origin.passthrough.@from",
281
- ): void {
370
+ ): ResolvedFromTarget | undefined {
282
371
  const projectionName = projection.name;
283
372
  // FR5d — referrer is `<projection-FQN>::<fieldName>` (the canonical
284
373
  // "where the broken reference lives" identifier).
@@ -297,7 +386,7 @@ function _validateFromPath(
297
386
  },
298
387
  ),
299
388
  );
300
- return;
389
+ return undefined;
301
390
  }
302
391
  const entityName = fromAttr.slice(0, dotIdx);
303
392
  const targetFieldName = fromAttr.slice(dotIdx + 1);
@@ -313,7 +402,7 @@ function _validateFromPath(
313
402
  },
314
403
  ),
315
404
  );
316
- return;
405
+ return undefined;
317
406
  }
318
407
  const sourceField = _findField(sourceObj, targetFieldName);
319
408
  if (!sourceField) {
@@ -327,9 +416,16 @@ function _validateFromPath(
327
416
  },
328
417
  ),
329
418
  );
419
+ return undefined;
330
420
  }
421
+ return { entity: sourceObj, field: sourceField };
331
422
  }
332
423
 
424
+ /**
425
+ * Validate an explicit `@via` "Entity.rel[.rel...]" path. Returns the walked
426
+ * relationship hop nodes (in path order) on full success — FR-024 B5 runs the
427
+ * cardinality checks over them — or undefined when any error was pushed.
428
+ */
333
429
  function _validateViaPath(
334
430
  viaAttr: string,
335
431
  root: MetaData,
@@ -337,7 +433,7 @@ function _validateViaPath(
337
433
  fieldName: string,
338
434
  originSource: ErrorSource,
339
435
  errors: ParseError[],
340
- ): void {
436
+ ): MetaData[] | undefined {
341
437
  const projectionName = projection.name;
342
438
  // FR5d — referrer is `<projection-FQN>::<fieldName>`.
343
439
  const referrer = `${projection.fqn()}::${fieldName}`;
@@ -352,7 +448,7 @@ function _validateViaPath(
352
448
  },
353
449
  ),
354
450
  );
355
- return;
451
+ return undefined;
356
452
  }
357
453
  const [entityName, ...relSegments] = segments as [string, ...string[]];
358
454
  let currentObj = _findObject(root, entityName);
@@ -366,7 +462,7 @@ function _validateViaPath(
366
462
  },
367
463
  ),
368
464
  );
369
- return;
465
+ return undefined;
370
466
  }
371
467
  // FR5d — track the deepest-valid-prefix as we walk. The prefix grows
372
468
  // segment-by-segment; on a hop failure the error message names the prefix
@@ -374,6 +470,7 @@ function _validateViaPath(
374
470
  // After the entity lookup above, the deepest valid prefix is just the
375
471
  // entity name; each successful relationship hop appends a segment.
376
472
  const validSegments: string[] = [entityName];
473
+ const hops: MetaData[] = [];
377
474
  for (const relName of relSegments) {
378
475
  const rel = _findRelationship(currentObj, relName);
379
476
  if (!rel) {
@@ -388,7 +485,7 @@ function _validateViaPath(
388
485
  },
389
486
  ),
390
487
  );
391
- return;
488
+ return undefined;
392
489
  }
393
490
  const refTarget = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
394
491
  if (typeof refTarget !== "string" || refTarget === "") {
@@ -401,7 +498,7 @@ function _validateViaPath(
401
498
  },
402
499
  ),
403
500
  );
404
- return;
501
+ return undefined;
405
502
  }
406
503
  const nextObj = _findObject(root, refTarget);
407
504
  if (!nextObj) {
@@ -418,16 +515,302 @@ function _validateViaPath(
418
515
  },
419
516
  ),
420
517
  );
421
- return;
518
+ return undefined;
422
519
  }
423
520
  validSegments.push(relName);
521
+ hops.push(rel);
424
522
  currentObj = nextObj;
425
523
  }
524
+ return hops;
525
+ }
526
+
527
+ // ---------------------------------------------------------------------------
528
+ // FR-024 B5 — base-entity derivation, single-hop-unique @via inference, and
529
+ // origin cardinality checks (spec §5–§6; ADR-0029 decisions 5–6).
530
+ // ---------------------------------------------------------------------------
531
+
532
+ /** A hop relationship's effective @cardinality, or undefined when not declared. */
533
+ function _hopCardinality(rel: MetaData): string | undefined {
534
+ const v = rel.attr(RELATIONSHIP_ATTR_CARDINALITY);
535
+ return typeof v === "string" ? v : undefined;
536
+ }
537
+
538
+ /**
539
+ * Derive the BASE entity a no-`@via` origin path anchors at (spec §5):
540
+ * - an entity (or any non-projection host) is its own base — derived fields
541
+ * on multi-source entities anchor at the entity itself;
542
+ * - a projection's base is the owner entity of its EXTENDED identity
543
+ * (`identity.primary { extends: "Customer.id" }` — declared structurally);
544
+ * - fallback (no identity): the single distinct entity targeted by the
545
+ * projection's plain field-`extends` refs; >1 distinct entity →
546
+ * ERR_AMBIGUOUS_PATH instructing the author to declare an extended
547
+ * identity; 0 → ERR_INVALID_ORIGIN (no base derivable, cannot infer).
548
+ *
549
+ * Returns undefined when no base is derivable (an error has been pushed).
550
+ */
551
+ /**
552
+ * FR-024: the entity NAMED by a node's dotted extends ref — the OWNER part of
553
+ * `<owner>.<child>...` resolved as an object. This differs from
554
+ * `superResolved.parent` when the resolved child is INHERITED: `Product.id`
555
+ * selecting BaseEntity's identity through Product's effective children must
556
+ * anchor `Product` (what the author wrote), never `BaseEntity` (where the
557
+ * child physically lives).
558
+ */
559
+ function _refNamedOwner(node: MetaData, root: MetaData): MetaData | undefined {
560
+ const ref = node.superRef;
561
+ if (ref === undefined) return undefined;
562
+ const lastSep = ref.lastIndexOf("::");
563
+ const tail = lastSep === -1 ? ref : ref.slice(lastSep + 2);
564
+ const dot = tail.indexOf(".");
565
+ if (dot <= 0) return undefined;
566
+ return _findObject(root, tail.slice(0, dot));
567
+ }
568
+
569
+ function _deriveBaseEntity(
570
+ obj: MetaData,
571
+ root: MetaData,
572
+ fieldName: string,
573
+ originSource: ErrorSource,
574
+ errors: ParseError[],
575
+ ): MetaData | undefined {
576
+ if (obj.subType !== OBJECT_SUBTYPE_PROJECTION) return obj;
577
+
578
+ // 1) The extended identity anchors the base entity (declared, not inferred).
579
+ // The anchor is the entity NAMED in the ref's owner part — see _refNamedOwner.
580
+ for (const identity of obj.ownChildren().filter((c) => c.type === TYPE_IDENTITY)) {
581
+ const extended = identity.superResolved;
582
+ if (extended !== undefined && extended.type === TYPE_IDENTITY) {
583
+ const named = _refNamedOwner(identity, root);
584
+ if (named !== undefined) return named;
585
+ const owner = extended.parent;
586
+ if (owner !== undefined && owner.type === TYPE_OBJECT) return owner;
587
+ }
588
+ }
589
+
590
+ // 2) Fallback: the single distinct entity targeted by plain field-extends —
591
+ // again preferring the ref-named owner over the physical declaring ancestor.
592
+ const targets = new Set<MetaData>();
593
+ for (const f of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
594
+ const sup = f.superResolved;
595
+ if (sup === undefined) continue;
596
+ const named = _refNamedOwner(f, root);
597
+ const owner = named ?? sup.parent;
598
+ if (
599
+ owner !== undefined &&
600
+ owner.type === TYPE_OBJECT &&
601
+ owner.subType !== OBJECT_SUBTYPE_VALUE &&
602
+ owner !== obj
603
+ ) {
604
+ targets.add(owner);
605
+ }
606
+ }
607
+ if (targets.size === 1) return [...targets][0];
608
+ if (targets.size > 1) {
609
+ const names = [...targets].map((t) => `"${t.name}"`).join(", ");
610
+ errors.push(
611
+ new ParseError(
612
+ `origin on ${obj.name}.${fieldName}: cannot derive the base entity — the projection's fields extend ` +
613
+ `multiple entities (${names}) and no identity extends an entity identity. Declare an extended identity ` +
614
+ `(e.g. identity.primary { name: "id", extends: "<Entity>.<identity>" }) to anchor the base entity (FR-024).`,
615
+ { code: "ERR_AMBIGUOUS_PATH", source: originSource },
616
+ ),
617
+ );
618
+ } else {
619
+ errors.push(
620
+ new ParseError(
621
+ `origin on ${obj.name}.${fieldName}: cannot derive the base entity for @via inference — the projection ` +
622
+ `has no extended identity and no entity-targeted field extends. Declare an extended identity or an explicit @via (FR-024).`,
623
+ { code: "ERR_INVALID_ORIGIN", source: originSource },
624
+ ),
625
+ );
626
+ }
627
+ return undefined;
628
+ }
629
+
630
+ /**
631
+ * True when the `@from`/`@of` target entity IS the host's base relation: the
632
+ * derived base entity itself, or an ancestor on the base's (or the host's)
633
+ * whole-object extends chain — the legacy `Summary extends Program` projection
634
+ * style inherits the base relation from its super, so `Program.title` on it is
635
+ * a base-relation column, not a join.
636
+ */
637
+ function _isBaseRelationTarget(target: MetaData, base: MetaData, host: MetaData): boolean {
638
+ for (let cur: MetaData | undefined = base; cur !== undefined; cur = cur.superResolved) {
639
+ if (cur === target) return true;
640
+ }
641
+ for (let cur: MetaData | undefined = host; cur !== undefined; cur = cur.superResolved) {
642
+ if (cur === target) return true;
643
+ }
644
+ return false;
645
+ }
646
+
647
+ /**
648
+ * Single-hop-unique `@via` inference (ADR-0029 decision 5): scan the base
649
+ * entity's EFFECTIVE relationship children for those whose @objectRef resolves
650
+ * to the `@from`/`@of` target entity. Exactly one → the inferred path (the
651
+ * caller proceeds exactly as if `@via` were declared with that relationship).
652
+ * Zero → ERR_INVALID_ORIGIN (cannot infer; multi-hop is always explicit).
653
+ * More than one → ERR_AMBIGUOUS_PATH naming the candidate relationships.
654
+ *
655
+ * Inference stops at single-hop-unique deliberately: the algorithm is part of
656
+ * the cross-port conformance contract; graph search is not trivially portable.
657
+ */
658
+ function _inferViaSingleHop(
659
+ base: MetaData,
660
+ targetEntity: MetaData,
661
+ obj: MetaData,
662
+ fieldName: string,
663
+ fromAttr: string,
664
+ label: string,
665
+ originSource: ErrorSource,
666
+ errors: ParseError[],
667
+ ): MetaData[] | undefined {
668
+ const candidates = base
669
+ .children()
670
+ .filter((c) => c.type === TYPE_RELATIONSHIP)
671
+ .filter((rel) => {
672
+ const ref = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
673
+ return typeof ref === "string" && stripPackage(ref) === targetEntity.name;
674
+ });
675
+ // FR5d — referrer is `<host-FQN>::<fieldName>`, target is the from/of ref
676
+ // whose implicit path could not be resolved.
677
+ const referrer = `${obj.fqn()}::${fieldName}`;
678
+ if (candidates.length === 1) return [candidates[0] as MetaData];
679
+ if (candidates.length === 0) {
680
+ errors.push(
681
+ new ParseError(
682
+ `${label} "${fromAttr}" on ${obj.name}.${fieldName}: no @via and no single-hop relationship from base ` +
683
+ `entity "${base.name}" to "${targetEntity.name}" — cannot infer the path. Declare @via explicitly ` +
684
+ `(multi-hop paths are always explicit; ADR-0029).`,
685
+ {
686
+ code: "ERR_INVALID_ORIGIN",
687
+ source: resolvedSource(originSource, referrer, fromAttr),
688
+ },
689
+ ),
690
+ );
691
+ return undefined;
692
+ }
693
+ const names = candidates.map((r) => `"${r.name}"`).join(", ");
694
+ errors.push(
695
+ new ParseError(
696
+ `${label} "${fromAttr}" on ${obj.name}.${fieldName}: no @via and ${candidates.length} relationships from ` +
697
+ `base entity "${base.name}" to "${targetEntity.name}" (${names}) — ambiguous. Declare @via naming one of them (ADR-0029).`,
698
+ {
699
+ code: "ERR_AMBIGUOUS_PATH",
700
+ source: resolvedSource(originSource, referrer, fromAttr),
701
+ },
702
+ ),
703
+ );
704
+ return undefined;
705
+ }
706
+
707
+ /**
708
+ * ADR-0029 decision 6 — a passthrough via-path must be effectively to-one at
709
+ * EVERY hop. A hop is judged to-many only when it DECLARES `@cardinality:
710
+ * "many"`: @cardinality is an open string at the metamodel level (Java-
711
+ * canonical composite forms exist, and legacy fixtures omit it), so an
712
+ * absent/unknown cardinality is never misjudged.
713
+ */
714
+ function _checkPassthroughCardinality(
715
+ hops: readonly MetaData[],
716
+ obj: MetaData,
717
+ fieldName: string,
718
+ originSource: ErrorSource,
719
+ errors: ParseError[],
720
+ ): void {
721
+ for (const rel of hops) {
722
+ if (_hopCardinality(rel) === CARDINALITY_MANY) {
723
+ errors.push(
724
+ new ParseError(
725
+ `origin.passthrough on ${obj.name}.${fieldName}: @via hop "${rel.name}" is to-many ` +
726
+ `(@cardinality "${CARDINALITY_MANY}") — a row-multiplying passthrough — you meant aggregate (ADR-0029).`,
727
+ { code: "ERR_ORIGIN_CARDINALITY", source: originSource },
728
+ ),
729
+ );
730
+ return;
731
+ }
732
+ }
733
+ }
734
+
735
+ /**
736
+ * ADR-0029 decision 6 — an aggregate via-path must contain at least one
737
+ * to-many hop. Conservative on the open @cardinality vocabulary: the error
738
+ * fires only when the path is PROVABLY to-one (every hop declares
739
+ * `@cardinality: "one"`); absent/composite cardinalities are not judged.
740
+ */
741
+ function _checkAggregateCardinality(
742
+ hops: readonly MetaData[],
743
+ obj: MetaData,
744
+ fieldName: string,
745
+ originSource: ErrorSource,
746
+ errors: ParseError[],
747
+ ): void {
748
+ if (hops.length === 0) return;
749
+ const provablyToOne = hops.every((rel) => _hopCardinality(rel) === CARDINALITY_ONE);
750
+ if (provablyToOne) {
751
+ errors.push(
752
+ new ParseError(
753
+ `origin.aggregate on ${obj.name}.${fieldName}: every @via hop is to-one (@cardinality "${CARDINALITY_ONE}") — ` +
754
+ `aggregating over a to-one path — you meant passthrough (ADR-0029).`,
755
+ { code: "ERR_ORIGIN_CARDINALITY", source: originSource },
756
+ ),
757
+ );
758
+ }
759
+ }
760
+
761
+ /**
762
+ * FR-024 B6 (spec §4; ADR-0029 decision 7) — extends/origin agreement.
763
+ *
764
+ * When a field declares BOTH an entity-nested `extends` (shape lineage) and
765
+ * an `origin.passthrough` @from (data lineage), the two are independent
766
+ * statements that must coincide: the resolved @from target must be THE SAME
767
+ * NODE as the field's resolved extends target — or appear on its extends
768
+ * chain (a projection field extending another projection's field that
769
+ * ultimately extends the entity field still agrees). Host-agnostic: applies
770
+ * on projections, entities, and values (FR-004 payloads may carry both).
771
+ *
772
+ * NOT judged:
773
+ * - `origin.aggregate` (it computes something new — no passthrough claim);
774
+ * - an extends whose target is a TOP-LEVEL abstract field (its parent is
775
+ * not an object) — shape-only reuse makes no lineage claim.
776
+ */
777
+ function _checkExtendsOriginAgreement(
778
+ field: MetaData,
779
+ fromField: MetaData,
780
+ fromAttr: string,
781
+ obj: MetaData,
782
+ originSource: ErrorSource,
783
+ errors: ParseError[],
784
+ ): void {
785
+ const sup = field.superResolved;
786
+ if (sup === undefined || sup.type !== TYPE_FIELD) return;
787
+ const supOwner = sup.parent;
788
+ if (supOwner === undefined || supOwner.type !== TYPE_OBJECT) return;
789
+ for (let cur: MetaData | undefined = sup; cur !== undefined; cur = cur.superResolved) {
790
+ if (cur === fromField) return; // shape lineage and data lineage agree
791
+ }
792
+ // FR5d resolved envelope: referrer = host::field, target = the @from ref.
793
+ errors.push(
794
+ new ParseError(
795
+ `origin.passthrough on ${obj.name}.${field.name}: @from "${fromAttr}" disagrees with the field's extends ` +
796
+ `target "${supOwner.name}.${sup.name}" — extends (shape lineage) and origin.passthrough (data lineage) ` +
797
+ `must point at the same entity field (FR-024).`,
798
+ {
799
+ code: "ERR_EXTENDS_ORIGIN_MISMATCH",
800
+ source: resolvedSource(originSource, `${obj.fqn()}::${field.name}`, fromAttr),
801
+ },
802
+ ),
803
+ );
426
804
  }
427
805
 
428
806
  export function validateOriginPaths(root: MetaData): ParseError[] {
429
807
  const errors: ParseError[] = [];
430
808
  for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
809
+ // FR-024 B5: object.value hosts are EXEMPT from @via inference and
810
+ // cardinality checks — a value's origin.passthrough is FR-015 parameter
811
+ // lineage (values are constructed, never assembled; spec §7), not an
812
+ // assembly path. Their @from refs are still resolution-validated.
813
+ const isValueHost = obj.subType === OBJECT_SUBTYPE_VALUE;
431
814
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
432
815
  for (const origin of field.ownChildren().filter((c) => c.type === TYPE_ORIGIN)) {
433
816
  if (origin.subType === ORIGIN_SUBTYPE_PASSTHROUGH) {
@@ -443,10 +826,32 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
443
826
  );
444
827
  continue;
445
828
  }
446
- _validateFromPath(from, root, obj, field.name, origin.source, errors);
829
+ const fromTarget = _validateFromPath(from, root, obj, field.name, origin.source, errors);
830
+ // FR-024 B6 — extends/origin agreement (host-agnostic; runs whether
831
+ // @via is explicit, inferred, or a base-relation column).
832
+ if (fromTarget !== undefined) {
833
+ _checkExtendsOriginAgreement(field, fromTarget.field, from, obj, origin.source, errors);
834
+ }
447
835
  const via = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
448
836
  if (typeof via === "string" && via !== "") {
449
- _validateViaPath(via, root, obj, field.name, origin.source, errors);
837
+ const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
838
+ if (hops !== undefined) {
839
+ _checkPassthroughCardinality(hops, obj, field.name, origin.source, errors);
840
+ }
841
+ } else if (fromTarget !== undefined && !isValueHost) {
842
+ // FR-024 §6 — no @via: derive the base entity; a @from targeting
843
+ // the base relation itself is a plain base column (no checks);
844
+ // otherwise infer the single-hop-unique path and gate cardinality.
845
+ const base = _deriveBaseEntity(obj, root, field.name, origin.source, errors);
846
+ if (base !== undefined && !_isBaseRelationTarget(fromTarget.entity, base, obj)) {
847
+ const hops = _inferViaSingleHop(
848
+ base, fromTarget.entity, obj, field.name, from,
849
+ "origin.passthrough.@from", origin.source, errors,
850
+ );
851
+ if (hops !== undefined) {
852
+ _checkPassthroughCardinality(hops, obj, field.name, origin.source, errors);
853
+ }
854
+ }
450
855
  }
451
856
  } else if (origin.subType === ORIGIN_SUBTYPE_AGGREGATE) {
452
857
  const of_ = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
@@ -459,9 +864,34 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
459
864
  );
460
865
  continue;
461
866
  }
462
- _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
867
+ // NOTE (FR-024 B6): NO extends/origin agreement check on aggregates —
868
+ // an aggregate computes something new (count/sum/…); spec §4 defines
869
+ // agreement for passthrough only.
870
+ const ofTarget = _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
463
871
  const via = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
464
- if (typeof via !== "string" || via === "") {
872
+ if (typeof via === "string" && via !== "") {
873
+ const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
874
+ if (hops !== undefined) {
875
+ _checkAggregateCardinality(hops, obj, field.name, origin.source, errors);
876
+ }
877
+ continue;
878
+ }
879
+ // FR-024 §6 — no @via on an aggregate: inference applies only when
880
+ // @of targets a non-base entity from a non-value host; an aggregate
881
+ // over the base relation itself still requires an explicit path.
882
+ if (ofTarget === undefined) continue; // @of did not resolve — no inference to attempt
883
+ if (isValueHost) {
884
+ errors.push(
885
+ new ParseError(
886
+ `origin.aggregate on ${obj.name}.${field.name}: missing @via (aggregates require a relationship path).`,
887
+ { code: "ERR_INVALID_ORIGIN", source: origin.source },
888
+ ),
889
+ );
890
+ continue;
891
+ }
892
+ const base = _deriveBaseEntity(obj, root, field.name, origin.source, errors);
893
+ if (base === undefined) continue; // base underivable — error already pushed
894
+ if (_isBaseRelationTarget(ofTarget.entity, base, obj)) {
465
895
  errors.push(
466
896
  new ParseError(
467
897
  `origin.aggregate on ${obj.name}.${field.name}: missing @via (aggregates require a relationship path).`,
@@ -470,7 +900,13 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
470
900
  );
471
901
  continue;
472
902
  }
473
- _validateViaPath(via, root, obj, field.name, origin.source, errors);
903
+ const hops = _inferViaSingleHop(
904
+ base, ofTarget.entity, obj, field.name, of_,
905
+ "origin.aggregate.@of", origin.source, errors,
906
+ );
907
+ if (hops !== undefined) {
908
+ _checkAggregateCardinality(hops, obj, field.name, origin.source, errors);
909
+ }
474
910
  }
475
911
  }
476
912
  }
@@ -479,34 +915,94 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
479
915
  }
480
916
 
481
917
  // ---------------------------------------------------------------------------
482
- // @storage cross-attribute validation
918
+ // FR-024 B6 — derived-field providability (spec §7 population doctrine).
919
+ //
920
+ // An object.ENTITY field carrying any origin.* child is derived (read-only):
921
+ // it does not exist on the writable table — something must PROVIDE it on
922
+ // read. The spec §7 multi-source pattern is a writable table-primary source
923
+ // plus a read-only-kind source (view / materializedView / storedProc /
924
+ // tableFunction, e.g. @role "replica") that carries the derived fields.
925
+ // An entity whose only sources are writable kinds — or that has no source at
926
+ // all — cannot provide an origin-bearing field → ERR_DERIVED_FIELD_NO_READ_SOURCE
927
+ // on that field (plain node-source envelope).
928
+ //
929
+ // Exemptions:
930
+ // - object.projection — the projection's own source/wire IS the provider;
931
+ // - object.value — FR-015 lineage; values are constructed, never populated.
932
+ //
933
+ // The rule reads "at least one read-only-kind source, any role". Since the
934
+ // B4b hard cutover (ERR_ENTITY_PRIMARY_SOURCE_READONLY makes a read-only-kind
935
+ // PRIMARY illegal on entities), the only loadable satisfying shape is the
936
+ // strict one — a non-primary-role read-only source (the §7 multi-source
937
+ // pattern: table primary + view replica).
938
+ //
939
+ // Sources are scanned on the EFFECTIVE child view (children()) so an entity
940
+ // inheriting its sources from an abstract base is judged by what it actually
941
+ // has; fields + origins use the own view, mirroring validateOriginPaths.
942
+ // ---------------------------------------------------------------------------
943
+
944
+ export function validateDerivedFieldProvidability(root: MetaData): ParseError[] {
945
+ const errors: ParseError[] = [];
946
+ for (const obj of root
947
+ .ownChildren()
948
+ .filter((c) => c.type === TYPE_OBJECT && c.subType === OBJECT_SUBTYPE_ENTITY)) {
949
+ const hasReadCapableSource = obj
950
+ .children()
951
+ .filter((c) => c.type === TYPE_SOURCE)
952
+ .some((s) => (s as MetaSource).isReadOnly());
953
+ if (hasReadCapableSource) continue;
954
+ for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
955
+ if (!field.ownChildren().some((c) => c.type === TYPE_ORIGIN)) continue;
956
+ errors.push(
957
+ new ParseError(
958
+ `derived field "${obj.name}.${field.name}" carries an origin.* but entity "${obj.name}" declares no ` +
959
+ `read-capable source — derived fields do not exist on the writable table. Declare a read-only source ` +
960
+ `(e.g. source.rdb @kind "view" @role "replica") to provide it, or move the field to an object.projection (FR-024 §7).`,
961
+ { code: "ERR_DERIVED_FIELD_NO_READ_SOURCE", source: field.source },
962
+ ),
963
+ );
964
+ }
965
+ }
966
+ return errors;
967
+ }
968
+
969
+ // ---------------------------------------------------------------------------
970
+ // field.object + @storage cross-attribute validation
483
971
  //
484
- // Rules:
485
- // 1. @storage requires @objectRef to be present (storage is meaningless
486
- // without a referenced object type).
972
+ // Rules (ADR-0013):
973
+ // 1. A field.object ALWAYS requires @objectRef. A field.object models a typed
974
+ // nested value; without @objectRef it is "an oxymoron at the logical layer".
975
+ // Genuinely open/untyped JSON uses the physical @dbColumnType: jsonb escape
976
+ // hatch on field.string, NOT a bare object. → ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF.
977
+ // (This rule subsumes the legacy @storage-without-@objectRef check —
978
+ // @storage is only meaningful on a field.object, so the missing-@objectRef
979
+ // situation now always reports this single, clearer error. One error per
980
+ // node: when @objectRef is absent we skip the flattened/array check below.)
487
981
  // 2. @storage "flattened" requires isArray to be absent or false (cannot
488
982
  // flatten a variable-length array into a fixed column set).
489
- //
490
- // Only field.object nodes carry @storage in practice, but the check is applied
491
- // to every field node that has @storage set — matching the permissive "check
492
- // what's there" model used by the other validation passes.
493
983
  // ---------------------------------------------------------------------------
494
984
 
495
985
  export function validateFieldObjectStorage(root: MetaData): ParseError[] {
496
986
  const errors: ParseError[] = [];
497
987
  for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
498
988
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
499
- const storage = field.ownAttr(FIELD_ATTR_STORAGE);
500
- if (storage === undefined || storage === null) continue;
501
989
  const objectRef = field.ownAttr(FIELD_ATTR_OBJECT_REF);
502
- if (typeof objectRef !== "string" || objectRef.length === 0) {
990
+ const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
991
+
992
+ if (field.subType === FIELD_SUBTYPE_OBJECT && !hasObjectRef) {
993
+ // A field.object with no @objectRef is rejected outright; reporting any
994
+ // further @storage error on the same node would be redundant.
503
995
  errors.push(
504
996
  new ParseError(
505
- `field "${obj.name}.${field.name}" sets @storage but has no @objectRef`,
506
- { code: "ERR_STORAGE_WITHOUT_OBJECT_REF", source: field.source },
997
+ `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.`,
998
+ { code: "ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF", source: field.source },
507
999
  ),
508
1000
  );
1001
+ continue;
509
1002
  }
1003
+
1004
+ const storage = field.ownAttr(FIELD_ATTR_STORAGE);
1005
+ if (storage === undefined || storage === null) continue;
510
1006
  if (storage === STORAGE_FLATTENED && field.isArray === true) {
511
1007
  errors.push(
512
1008
  new ParseError(
@@ -646,6 +1142,156 @@ export function validateDataGridFilterValues(root: MetaData): ParseError[] {
646
1142
  return errors;
647
1143
  }
648
1144
 
1145
+ // ---------------------------------------------------------------------------
1146
+ // FR-017 — M:N relationship validation (slim vocabulary)
1147
+ //
1148
+ // Deferred-resolution validation (runs after all files load + extends:
1149
+ // resolution, like origin paths), enforcing the cross-port M:N contract:
1150
+ //
1151
+ // (a) @symmetric:true is valid only on a self-join (@objectRef == declaring
1152
+ // entity). Otherwise ERR_BAD_ATTR_VALUE.
1153
+ // (b) @symmetric and @sourceRefField are mutually exclusive → ERR_BAD_ATTR_VALUE.
1154
+ // (c) When @through is present: the named entity must exist and declare exactly
1155
+ // two identity.reference children; @sourceRefField (if present) must match
1156
+ // one of those references' FK fields → ERR_INVALID_RELATIONSHIP.
1157
+ // (d) @through / @sourceRefField / @symmetric are invalid on a non-M:N
1158
+ // relationship (@cardinality != "many", or no @through) → ERR_INVALID_RELATIONSHIP.
1159
+ //
1160
+ // Own-relationships only: a relationship is validated on the entity that declares
1161
+ // it (matching the own-attrs policy of the other passes).
1162
+ // ---------------------------------------------------------------------------
1163
+
1164
+ // The junction's reference view: the validator and the runtime/codegen FK
1165
+ // derivation (deriveM2MFields) MUST agree on which references count. Both use
1166
+ // the EFFECTIVE view (own + inherited via extends) via referenceIdentities(),
1167
+ // so a junction defined through `extends` is treated identically here and at
1168
+ // resolution time. (For a junction with no extends, effective == own.)
1169
+ function _junctionReferences(junction: MetaData): MetaReferenceIdentity[] {
1170
+ return (junction as MetaObject).referenceIdentities();
1171
+ }
1172
+
1173
+ /** FK field names declared by a junction's effective identity.reference children. */
1174
+ function _junctionReferenceFkFields(junction: MetaData): string[] {
1175
+ const out: string[] = [];
1176
+ for (const ref of _junctionReferences(junction)) {
1177
+ const first = ref.fields[0];
1178
+ if (first) out.push(first);
1179
+ }
1180
+ return out;
1181
+ }
1182
+
1183
+ function _countJunctionReferences(junction: MetaData): number {
1184
+ return _junctionReferences(junction).length;
1185
+ }
1186
+
1187
+ export function validateRelationships(root: MetaData): ParseError[] {
1188
+ const errors: ParseError[] = [];
1189
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
1190
+ for (const rel of obj.ownChildren().filter((c) => c.type === TYPE_RELATIONSHIP)) {
1191
+ const through = rel.ownAttr(RELATIONSHIP_ATTR_THROUGH);
1192
+ const sourceRefField = rel.ownAttr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
1193
+ const symmetric = rel.ownAttr(RELATIONSHIP_ATTR_SYMMETRIC) === true;
1194
+ const cardinality = rel.ownAttr(RELATIONSHIP_ATTR_CARDINALITY);
1195
+ const objectRef = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
1196
+
1197
+ const hasThrough = typeof through === "string" && through !== "";
1198
+ const hasSourceRefField = typeof sourceRefField === "string" && sourceRefField !== "";
1199
+ const isMany = cardinality === CARDINALITY_MANY;
1200
+ const isM2M = hasThrough && isMany;
1201
+
1202
+ // Rule (d): M:N-only attrs on a non-M:N relationship.
1203
+ if (!isM2M) {
1204
+ if (hasThrough) {
1205
+ errors.push(
1206
+ new ParseError(
1207
+ `relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_THROUGH} but is not a M:N ` +
1208
+ `relationship (requires @${RELATIONSHIP_ATTR_CARDINALITY}: "${CARDINALITY_MANY}").`,
1209
+ { code: "ERR_INVALID_RELATIONSHIP", source: rel.source },
1210
+ ),
1211
+ );
1212
+ }
1213
+ if (hasSourceRefField) {
1214
+ errors.push(
1215
+ new ParseError(
1216
+ `relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SOURCE_REF_FIELD} but is not a M:N relationship.`,
1217
+ { code: "ERR_INVALID_RELATIONSHIP", source: rel.source },
1218
+ ),
1219
+ );
1220
+ }
1221
+ if (symmetric) {
1222
+ errors.push(
1223
+ new ParseError(
1224
+ `relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SYMMETRIC} but is not a M:N relationship.`,
1225
+ { code: "ERR_INVALID_RELATIONSHIP", source: rel.source },
1226
+ ),
1227
+ );
1228
+ }
1229
+ continue;
1230
+ }
1231
+
1232
+ // Rule (b): @symmetric and @sourceRefField are mutually exclusive.
1233
+ if (symmetric && hasSourceRefField) {
1234
+ errors.push(
1235
+ new ParseError(
1236
+ `relationship "${obj.name}.${rel.name}" sets both @${RELATIONSHIP_ATTR_SYMMETRIC} and ` +
1237
+ `@${RELATIONSHIP_ATTR_SOURCE_REF_FIELD}; they are mutually exclusive.`,
1238
+ { code: "ERR_BAD_ATTR_VALUE", source: rel.source },
1239
+ ),
1240
+ );
1241
+ }
1242
+
1243
+ // Rule (a): @symmetric is valid only on a self-join (@objectRef == declaring entity).
1244
+ const isSelfJoin = typeof objectRef === "string" && stripPackage(objectRef) === obj.name;
1245
+ if (symmetric && !isSelfJoin) {
1246
+ errors.push(
1247
+ new ParseError(
1248
+ `relationship "${obj.name}.${rel.name}" sets @${RELATIONSHIP_ATTR_SYMMETRIC} but @${RELATIONSHIP_ATTR_OBJECT_REF} ` +
1249
+ `"${String(objectRef)}" is not the declaring entity "${obj.name}"; @${RELATIONSHIP_ATTR_SYMMETRIC} is self-join-only.`,
1250
+ { code: "ERR_BAD_ATTR_VALUE", source: rel.source },
1251
+ ),
1252
+ );
1253
+ }
1254
+
1255
+ // Rule (c): @through must name an entity declaring exactly two identity.reference children.
1256
+ const junction = _findObject(root, through as string);
1257
+ if (!junction) {
1258
+ errors.push(
1259
+ new ParseError(
1260
+ `relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_THROUGH} "${through}" does not resolve to an entity.`,
1261
+ { code: "ERR_INVALID_RELATIONSHIP", source: resolvedSource(rel.source, `${obj.fqn()}::${rel.name}`, String(through)) },
1262
+ ),
1263
+ );
1264
+ continue;
1265
+ }
1266
+ const refCount = _countJunctionReferences(junction);
1267
+ if (refCount !== 2) {
1268
+ errors.push(
1269
+ new ParseError(
1270
+ `relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_THROUGH} "${through}" must declare exactly two ` +
1271
+ `identity.reference children (one per FK side); found ${refCount}.`,
1272
+ { code: "ERR_INVALID_RELATIONSHIP", source: rel.source },
1273
+ ),
1274
+ );
1275
+ continue;
1276
+ }
1277
+ // @sourceRefField (if present) must match one of the junction's reference FK fields.
1278
+ if (hasSourceRefField) {
1279
+ const fkFields = _junctionReferenceFkFields(junction);
1280
+ if (!fkFields.includes(sourceRefField as string)) {
1281
+ errors.push(
1282
+ new ParseError(
1283
+ `relationship "${obj.name}.${rel.name}" @${RELATIONSHIP_ATTR_SOURCE_REF_FIELD} "${sourceRefField}" does not match ` +
1284
+ `any identity.reference FK field on junction "${through}". Available: ${fkFields.join(", ") || "(none)"}.`,
1285
+ { code: "ERR_INVALID_RELATIONSHIP", source: rel.source },
1286
+ ),
1287
+ );
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+ return errors;
1293
+ }
1294
+
649
1295
  function checkFilterClauses(
650
1296
  filter: Record<string, unknown>,
651
1297
  allow: Map<string, readonly string[]>,