metaobjects 0.15.2__tar.gz → 0.15.4__tar.gz

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 (380) hide show
  1. {metaobjects-0.15.2 → metaobjects-0.15.4}/PKG-INFO +1 -1
  2. {metaobjects-0.15.2 → metaobjects-0.15.4}/pyproject.toml +1 -1
  3. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/runtime/filter_parser.py +13 -3
  4. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/validation_passes.py +47 -12
  5. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/registry_manifest.py +11 -1
  6. metaobjects-0.15.4/tests/conformance/test_provider_composition_conformance.py +150 -0
  7. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/postgres_container.py +78 -9
  8. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_validation_origin_paths.py +64 -0
  9. {metaobjects-0.15.2 → metaobjects-0.15.4}/.gitignore +0 -0
  10. {metaobjects-0.15.2 → metaobjects-0.15.4}/LICENSE +0 -0
  11. {metaobjects-0.15.2 → metaobjects-0.15.4}/README.md +0 -0
  12. {metaobjects-0.15.2 → metaobjects-0.15.4}/hatch_build.py +0 -0
  13. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/__init__.py +0 -0
  14. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/agent_context/__init__.py +0 -0
  15. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/agent_context/scaffold.py +0 -0
  16. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/__init__.py +0 -0
  17. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/api_model.py +0 -0
  18. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/builder.py +0 -0
  19. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/naming.py +0 -0
  20. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/paths.py +0 -0
  21. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/apidocs/renderer.py +0 -0
  22. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/attr_class_map.py +0 -0
  23. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/cli.py +0 -0
  24. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
  25. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/__init__.py +0 -0
  26. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/config.py +0 -0
  27. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/constants.py +0 -0
  28. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
  29. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
  30. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/format.py +0 -0
  31. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
  32. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generator.py +0 -0
  33. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generator_registry.py +0 -0
  34. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/__init__.py +0 -0
  35. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/entity_model.py +0 -0
  36. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
  37. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
  38. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
  39. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
  40. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
  41. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
  42. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
  43. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
  44. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/router_generator.py +0 -0
  45. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/template_generator.py +0 -0
  46. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
  47. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
  48. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/instance_artifacts.py +0 -0
  49. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
  50. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/overwrite_policy.py +0 -0
  51. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/runner.py +0 -0
  52. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/runtime/__init__.py +0 -0
  53. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
  54. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
  55. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
  56. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
  57. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/codegen/type_map.py +0 -0
  58. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/core_types.py +0 -0
  59. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/datatype.py +0 -0
  60. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/documentation/__init__.py +0 -0
  61. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/documentation/doc_constants.py +0 -0
  62. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/documentation/doc_provider.py +0 -0
  63. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/documentation/doc_schema.py +0 -0
  64. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/errors.py +0 -0
  65. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/__init__.py +0 -0
  66. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/merge.py +0 -0
  67. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/meta_data_loader.py +0 -0
  68. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/registered_validation.py +0 -0
  69. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/sources/__init__.py +0 -0
  70. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/sources/directory_source.py +0 -0
  71. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/sources/file_source.py +0 -0
  72. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
  73. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/sources/uri_source.py +0 -0
  74. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/validate_discriminator.py +0 -0
  75. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/validate_field_readonly.py +0 -0
  76. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
  77. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
  78. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/__init__.py +0 -0
  79. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/__init__.py +0 -0
  80. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/attr/__init__.py +0 -0
  81. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
  82. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
  83. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/field/__init__.py +0 -0
  84. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/field/field_constants.py +0 -0
  85. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/field/meta_field.py +0 -0
  86. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/identity/__init__.py +0 -0
  87. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
  88. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
  89. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/index/__init__.py +0 -0
  90. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/index/index_constants.py +0 -0
  91. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/index/meta_index.py +0 -0
  92. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/__init__.py +0 -0
  93. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/meta_object.py +0 -0
  94. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
  95. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
  96. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/object_constants.py +0 -0
  97. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/object_extract.py +0 -0
  98. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/object/value_object.py +0 -0
  99. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
  100. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
  101. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
  102. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
  103. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/validator/__init__.py +0 -0
  104. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
  105. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/meta_data.py +0 -0
  106. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/meta_root.py +0 -0
  107. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/__init__.py +0 -0
  108. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
  109. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
  110. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
  111. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
  112. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
  113. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
  114. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
  115. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
  116. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
  117. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/__init__.py +0 -0
  118. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
  119. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
  120. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
  121. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
  122. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
  123. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
  124. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
  125. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
  126. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/provider_extends.py +0 -0
  127. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/template/__init__.py +0 -0
  128. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/template/meta_template.py +0 -0
  129. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/template/prompt_provider.py +0 -0
  130. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/meta/template/template_constants.py +0 -0
  131. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/naming_refs.py +0 -0
  132. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/parser.py +0 -0
  133. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/parser_yaml.py +0 -0
  134. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/provider.py +0 -0
  135. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/py.typed +0 -0
  136. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/registry.py +0 -0
  137. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/__init__.py +0 -0
  138. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/email_document.py +0 -0
  139. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/escapers.py +0 -0
  140. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
  141. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/__init__.py +0 -0
  142. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/coerce.py +0 -0
  143. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/extract.py +0 -0
  144. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/extract_map.py +0 -0
  145. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
  146. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/locate.py +0 -0
  147. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/normalize.py +0 -0
  148. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/strip.py +0 -0
  149. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/types.py +0 -0
  150. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
  151. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/filesystem_provider.py +0 -0
  152. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/__init__.py +0 -0
  153. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
  154. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
  155. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/prompt_field.py +0 -0
  156. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
  157. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/prompt/prompt_style.py +0 -0
  158. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/renderer.py +0 -0
  159. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/render/verify.py +0 -0
  160. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/runtime/__init__.py +0 -0
  161. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/runtime/llm_recorder.py +0 -0
  162. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/runtime/n2m_resolver.py +0 -0
  163. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/runtime/object_manager.py +0 -0
  164. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/runtime/tph.py +0 -0
  165. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/serializer_json.py +0 -0
  166. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/shared/__init__.py +0 -0
  167. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/shared/base_types.py +0 -0
  168. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/shared/separators.py +0 -0
  169. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/shared/structural.py +0 -0
  170. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/source/__init__.py +0 -0
  171. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/source/error_source.py +0 -0
  172. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/source/json_path.py +0 -0
  173. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/source/semantic_diff.py +0 -0
  174. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/source/yaml_positions.py +0 -0
  175. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/__init__.py +0 -0
  176. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/attr.json +0 -0
  177. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/db.json +0 -0
  178. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/documentation.json +0 -0
  179. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/field.json +0 -0
  180. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/identity.json +0 -0
  181. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/index.json +0 -0
  182. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/layout.json +0 -0
  183. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/object.json +0 -0
  184. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/origin.json +0 -0
  185. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/prompt.json +0 -0
  186. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/relationship.json +0 -0
  187. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/source.json +0 -0
  188. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/template.json +0 -0
  189. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/ui.json +0 -0
  190. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/validator.json +0 -0
  191. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/spec_metamodel/view.json +0 -0
  192. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/super_resolve.py +0 -0
  193. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/validation_types.py +0 -0
  194. {metaobjects-0.15.2 → metaobjects-0.15.4}/src/metaobjects/yaml_desugar.py +0 -0
  195. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/__init__.py +0 -0
  196. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/__init__.py +0 -0
  197. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
  198. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/extends/expected/Program.py +0 -0
  199. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/extends/meta.json +0 -0
  200. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
  201. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
  202. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/nested-array/meta.json +0 -0
  203. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
  204. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/scalars/expected/Report.py +0 -0
  205. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/scalars/meta.json +0 -0
  206. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
  207. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/golden/vanilla/meta.json +0 -0
  208. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_abstract_conformance.py +0 -0
  209. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_api_docs_builder.py +0 -0
  210. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_api_docs_paths.py +0 -0
  211. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_cli.py +0 -0
  212. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_cli_registry.py +0 -0
  213. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_cli_staleness_nudge.py +0 -0
  214. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_cli_verify_strict.py +0 -0
  215. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_cli_verify_subverbs.py +0 -0
  216. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_constants_config.py +0 -0
  217. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_entity_model.py +0 -0
  218. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_enum_conformance.py +0 -0
  219. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_extends_abstract_field_inheritance.py +0 -0
  220. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_extractor_generator.py +0 -0
  221. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_filter_allowlist_generator.py +0 -0
  222. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_format.py +0 -0
  223. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_fr010_nested_object_prompt.py +0 -0
  224. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_fr010_output_codegen.py +0 -0
  225. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
  226. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_generator.py +0 -0
  227. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_generator_extension_seams.py +0 -0
  228. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_golden.py +0 -0
  229. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
  230. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_inheritance_conformance.py +0 -0
  231. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_instance_artifacts.py +0 -0
  232. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_m2m_codegen.py +0 -0
  233. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_output_parser_generator.py +0 -0
  234. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_overwrite_policy.py +0 -0
  235. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_payload_vo_generator.py +0 -0
  236. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_projection_compile.py +0 -0
  237. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_render_helper_conformance.py +0 -0
  238. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_render_helper_generator.py +0 -0
  239. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_reverse_finders.py +0 -0
  240. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_router_generator.py +0 -0
  241. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_runner.py +0 -0
  242. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_data.py +0 -0
  243. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_generator.py +0 -0
  244. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_output_pattern.py +0 -0
  245. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_scope_helpers.py +0 -0
  246. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_scope_walk.py +0 -0
  247. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_template_spec.py +0 -0
  248. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_tph_codegen.py +0 -0
  249. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_trace_helper_generator.py +0 -0
  250. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_type_map.py +0 -0
  251. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/codegen/test_validation_conformance.py +0 -0
  252. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/__init__.py +0 -0
  253. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/capabilities.py +0 -0
  254. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/conformance-expected-failures.json +0 -0
  255. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/conformance_adapter.py +0 -0
  256. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/corpus.py +0 -0
  257. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/expected_failures.py +0 -0
  258. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/fixture_discovery.py +0 -0
  259. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/navigator.py +0 -0
  260. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
  261. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_conformance.py +0 -0
  262. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_extract_conformance.py +0 -0
  263. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_extract_object_verdict.py +0 -0
  264. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_fr010_loader_attrs.py +0 -0
  265. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_fr011_attrs.py +0 -0
  266. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_generator_registry_conformance.py +0 -0
  267. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_object_model_conformance.py +0 -0
  268. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_registry_conformance.py +0 -0
  269. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_runner_hardfail.py +0 -0
  270. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_spec_metamodel_embed.py +0 -0
  271. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_strict_attr_load.py +0 -0
  272. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_template_codegen_conformance.py +0 -0
  273. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_template_generator_conformance.py +0 -0
  274. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/test_yaml_conformance.py +0 -0
  275. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
  276. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/__init__.py +0 -0
  277. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/api_contract_assertions.py +0 -0
  278. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/api_contract_jsonb_server.py +0 -0
  279. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/api_contract_m2m_server.py +0 -0
  280. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/api_contract_server.py +0 -0
  281. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/generated_jsonb_app.py +0 -0
  282. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/generated_m2m_app.py +0 -0
  283. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/generated_router_app.py +0 -0
  284. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/generated_tph_app.py +0 -0
  285. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/meta_ai_trace.yaml +0 -0
  286. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/normalization.py +0 -0
  287. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/query_runner.py +0 -0
  288. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/scenarios.py +0 -0
  289. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract.py +0 -0
  290. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract_generated.py +0 -0
  291. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract_jsonb.py +0 -0
  292. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract_m2m.py +0 -0
  293. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract_m2m_generated.py +0 -0
  294. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_api_contract_tph_generated.py +0 -0
  295. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_llm_call_trace.py +0 -0
  296. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_normalization.py +0 -0
  297. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_query_scenarios.py +0 -0
  298. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/integration/test_runtime_return_types.py +0 -0
  299. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/open_closed_proof_test.py +0 -0
  300. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/__init__.py +0 -0
  301. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/__init__.py +0 -0
  302. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_coerce.py +0 -0
  303. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_extract.py +0 -0
  304. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_extract_map.py +0 -0
  305. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_json_forgiving_reader.py +0 -0
  306. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_locate.py +0 -0
  307. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_model.py +0 -0
  308. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_normalize.py +0 -0
  309. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_strip.py +0 -0
  310. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
  311. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/prompt/__init__.py +0 -0
  312. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/prompt/test_output_format_renderer.py +0 -0
  313. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_email_document.py +0 -0
  314. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_filesystem_provider.py +0 -0
  315. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_output_format_renderer_nested.py +0 -0
  316. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_output_prompt_conformance.py +0 -0
  317. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_render_conformance.py +0 -0
  318. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_render_max_chars.py +0 -0
  319. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_verify.py +0 -0
  320. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/render/test_verify_conformance.py +0 -0
  321. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/__init__.py +0 -0
  322. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_fr5c_merge_attribution.py +0 -0
  323. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_fr5d_reference_resolution.py +0 -0
  324. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_fr5e_database_source_shape.py +0 -0
  325. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_json_path.py +0 -0
  326. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_semantic_diff.py +0 -0
  327. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_source_on_node.py +0 -0
  328. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/source/test_yaml_positions.py +0 -0
  329. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/test_api_docs_accuracy.py +0 -0
  330. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/__init__.py +0 -0
  331. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_agent_context_staleness.py +0 -0
  332. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_capabilities.py +0 -0
  333. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_common_attrs.py +0 -0
  334. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_core_types.py +0 -0
  335. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_effective_package.py +0 -0
  336. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_errors.py +0 -0
  337. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_field_enum.py +0 -0
  338. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_field_map_validation.py +0 -0
  339. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
  340. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
  341. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_index_lookup.py +0 -0
  342. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_llm_recorder.py +0 -0
  343. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_loader.py +0 -0
  344. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_loader_bom.py +0 -0
  345. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_loader_class.py +0 -0
  346. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_merge.py +0 -0
  347. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_meta_attr.py +0 -0
  348. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_meta_data.py +0 -0
  349. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_meta_source.py +0 -0
  350. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_module_shortcuts.py +0 -0
  351. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_n2m_resolver.py +0 -0
  352. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_n2m_resolver_inherited.py +0 -0
  353. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
  354. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_one_primary_source.py +0 -0
  355. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_parser.py +0 -0
  356. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_parser_yaml.py +0 -0
  357. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_provider.py +0 -0
  358. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_provider_extension.py +0 -0
  359. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_registry.py +0 -0
  360. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_registry_completeness.py +0 -0
  361. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_registry_extend.py +0 -0
  362. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_registry_sealed.py +0 -0
  363. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_relationship_referential_actions.py +0 -0
  364. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_resolution_key.py +0 -0
  365. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
  366. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_serializer.py +0 -0
  367. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_shared_constants.py +0 -0
  368. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_smoke.py +0 -0
  369. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_sources.py +0 -0
  370. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_strict_child_placement.py +0 -0
  371. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_super_resolve.py +0 -0
  372. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_template_toolcall.py +0 -0
  373. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
  374. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_validation_attr_schema.py +0 -0
  375. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_validation_filter_values.py +0 -0
  376. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_validation_sort_field.py +0 -0
  377. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_validation_warnings.py +0 -0
  378. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_write_coercion_arrays.py +0 -0
  379. {metaobjects-0.15.2 → metaobjects-0.15.4}/tests/unit/test_yaml_desugar.py +0 -0
  380. {metaobjects-0.15.2 → metaobjects-0.15.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaobjects
3
- Version: 0.15.2
3
+ Version: 0.15.4
4
4
  Summary: Cross-language metadata standard: declare typed entities once, generate idiomatic drift-checked code across languages — Python port.
5
5
  Project-URL: Homepage, https://metaobjects.dev
6
6
  Project-URL: Repository, https://github.com/metaobjectsdev/metaobjects
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metaobjects"
3
- version = "0.15.2"
3
+ version = "0.15.4"
4
4
  description = "Cross-language metadata standard: declare typed entities once, generate idiomatic drift-checked code across languages — Python port."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -14,8 +14,9 @@ Returned ``FilterParseResult`` is either:
14
14
  * ``predicates`` populated + ``error_envelope`` ``None`` — success path; the
15
15
  generated router passes ``predicates`` into the repository.
16
16
  * ``error_envelope`` non-``None`` (one of ``invalid_filter_field`` /
17
- ``invalid_filter_op`` / ``invalid_filter_value``) — the generated router
18
- raises ``HTTPException(status_code=400, detail=result.error_envelope)``.
17
+ ``invalid_filter_op`` / ``invalid_filter_value`` / ``filter.in_too_large``) —
18
+ the generated router raises
19
+ ``HTTPException(status_code=400, detail=result.error_envelope)``.
19
20
 
20
21
  Mirror of the Java ``FilterParser`` / Kotlin ``KotlinFilterAllowlistGenerator`` /
21
22
  C# ``FilterParser`` ports — same wire grammar, same error envelopes, same
@@ -35,6 +36,13 @@ _INVALID_VALUE = object()
35
36
  _ERR_FIELD = "invalid_filter_field"
36
37
  _ERR_OP = "invalid_filter_op"
37
38
  _ERR_VALUE = "invalid_filter_value"
39
+ _ERR_IN_TOO_LARGE = "filter.in_too_large"
40
+
41
+ # Cross-port cap on the number of elements in an ``in``-list. A larger list is
42
+ # rejected with the ``filter.in_too_large`` envelope so a caller cannot force an
43
+ # unbounded ``IN (...)`` against the DB. Matches the TypeScript ``runtime-ts``
44
+ # filter parser's ``DEFAULT_MAX_IN_LIST``.
45
+ MAX_IN_LIST = 100
38
46
 
39
47
 
40
48
  @dataclass(frozen=True)
@@ -85,7 +93,7 @@ def parse_filter(
85
93
  validated + coerced filter list (preserving URL order); on failure,
86
94
  ``error_envelope`` is one of the cross-port envelopes
87
95
  (``invalid_filter_field`` / ``invalid_filter_op`` /
88
- ``invalid_filter_value``).
96
+ ``invalid_filter_value`` / ``filter.in_too_large``).
89
97
  """
90
98
  allowed_set = set(allowed_fields)
91
99
  ops_map: dict[str, frozenset[str]] = {
@@ -129,6 +137,8 @@ def parse_filter(
129
137
  coerced = _coerce_value(value, op)
130
138
  if coerced is _INVALID_VALUE:
131
139
  return FilterParseResult(error_envelope={"error": _ERR_VALUE})
140
+ if op == "in" and isinstance(coerced, list) and len(coerced) > MAX_IN_LIST:
141
+ return FilterParseResult(error_envelope={"error": _ERR_IN_TOO_LARGE})
132
142
  out.append(FilterPredicate(field=field_name, op=op, value=coerced))
133
143
 
134
144
  return FilterParseResult(predicates=out)
@@ -962,6 +962,34 @@ def _relationships_by_name(obj: MetaObject) -> dict[str, MetaData]:
962
962
  return result
963
963
 
964
964
 
965
+ def _find_reference(obj: MetaObject, name: str) -> MetaData | None:
966
+ """Find an ``identity.reference`` (a forward FK) by name — the "reference hop"
967
+ FR-024 allows in a ``@via`` path. The reference IS the FK (single source of truth
968
+ for direction + join column), so naming it navigates its many-to-one edge without
969
+ a redundant ``relationship.*``. Effective (inherited via extends:/super:)."""
970
+ for child in obj.children():
971
+ if (
972
+ child.type == TYPE_IDENTITY
973
+ and child.sub_type == IDENTITY_SUBTYPE_REFERENCE
974
+ and child.name == name
975
+ ):
976
+ return child
977
+ return None
978
+
979
+
980
+ def _is_reference_hop(hop: MetaData) -> bool:
981
+ """True for an ``identity.reference`` node (a ``@via`` reference hop)."""
982
+ return hop.type == TYPE_IDENTITY and hop.sub_type == IDENTITY_SUBTYPE_REFERENCE
983
+
984
+
985
+ def _hop_target_name(hop: MetaData) -> object:
986
+ """The target entity a ``@via`` hop points at: @objectRef (relationship) or
987
+ @references (reference hop)."""
988
+ if _is_reference_hop(hop):
989
+ return hop.get_meta_attr(IDENTITY_REFERENCE_ATTR_REFERENCES)
990
+ return hop.get_meta_attr(RELATIONSHIP_ATTR_OBJECT_REF)
991
+
992
+
965
993
  def _validate_entity_field_ref(
966
994
  ref: str,
967
995
  attr_name: str,
@@ -1078,13 +1106,15 @@ def _validate_via_path(
1078
1106
  hops: list[MetaData] = []
1079
1107
  for rel_name in segments[1:]:
1080
1108
  rels = _relationships_by_name(current_entity)
1081
- rel_node = rels.get(rel_name)
1109
+ # FR-024: a hop may name a relationship OR a reference-only FK
1110
+ # (identity.reference) — the reference IS a navigable many-to-one edge.
1111
+ rel_node = rels.get(rel_name) or _find_reference(current_entity, rel_name)
1082
1112
  if rel_node is None:
1083
1113
  prefix = ".".join(valid_segments)
1084
1114
  errors.append(
1085
1115
  MetaError(
1086
1116
  f"{context} @via='{via}' — entity '{current_entity.name}' has no "
1087
- f"relationship '{rel_name}' (known relationships: {sorted(rels)}). "
1117
+ f"relationship or reference '{rel_name}' (known relationships: {sorted(rels)}). "
1088
1118
  f'Deepest valid prefix was "{prefix}".',
1089
1119
  ErrorCode.ERR_INVALID_ORIGIN,
1090
1120
  envelope=resolved_source(origin_node.source, referrer, via),
@@ -1093,15 +1123,17 @@ def _validate_via_path(
1093
1123
  return None
1094
1124
 
1095
1125
  # Advance to the referenced entity.
1096
- # ADR-0039: resolving — a relationship may inherit @objectRef via extends
1097
- # (mirrors the TS _validateViaPath `rel.attr`, which resolves
1098
- # validation-passes.ts:522).
1099
- obj_ref = rel_node.get_meta_attr(RELATIONSHIP_ATTR_OBJECT_REF)
1126
+ # ADR-0039: resolving — a relationship/reference may inherit its target via
1127
+ # extends (mirrors the TS _validateViaPath which resolves). Target entity:
1128
+ # @objectRef (relationship) or @references (reference hop).
1129
+ obj_ref = _hop_target_name(rel_node)
1100
1130
  if not isinstance(obj_ref, str):
1131
+ missing = "@references" if _is_reference_hop(rel_node) else "@objectRef"
1132
+ kind = "reference" if _is_reference_hop(rel_node) else "relationship"
1101
1133
  errors.append(
1102
1134
  MetaError(
1103
- f"{context} @via='{via}' — relationship '{rel_name}' on entity "
1104
- f"'{current_entity.name}' has no @objectRef",
1135
+ f"{context} @via='{via}' — {kind} '{rel_name}' on entity "
1136
+ f"'{current_entity.name}' has no {missing}",
1105
1137
  ErrorCode.ERR_INVALID_ORIGIN,
1106
1138
  envelope=resolved_source(origin_node.source, referrer, via),
1107
1139
  )
@@ -1110,11 +1142,11 @@ def _validate_via_path(
1110
1142
 
1111
1143
  next_entity = object_index.get(obj_ref)
1112
1144
  if next_entity is None:
1113
- # FR5d — relationship's @objectRef points at a missing entity.
1114
- # target=the @objectRef value (mirrors TS validation-passes.ts L342-353).
1145
+ # FR5d — the hop's target points at a missing entity.
1146
+ kind = "reference" if _is_reference_hop(rel_node) else "relationship"
1115
1147
  errors.append(
1116
1148
  MetaError(
1117
- f"{context} @via='{via}' — relationship '{rel_name}' on entity "
1149
+ f"{context} @via='{via}' — {kind} '{rel_name}' on entity "
1118
1150
  f"'{current_entity.name}' references unknown entity '{obj_ref}'",
1119
1151
  ErrorCode.ERR_INVALID_ORIGIN,
1120
1152
  envelope=resolved_source(origin_node.source, referrer, obj_ref),
@@ -1137,7 +1169,10 @@ def _validate_via_path(
1137
1169
 
1138
1170
 
1139
1171
  def _hop_cardinality(rel: MetaData) -> str | None:
1140
- """A hop relationship's effective @cardinality, or None when not declared."""
1172
+ """A hop's effective @cardinality, or None when not declared. A reference hop
1173
+ (a forward FK) is inherently to-one — a child names the parent it points at."""
1174
+ if _is_reference_hop(rel):
1175
+ return CARDINALITY_ONE
1141
1176
  # ADR-0039: resolving (@cardinality may be inherited via extends) — mirrors the
1142
1177
  # TS _hopCardinality which reads `rel.attr()` (resolving), NOT ownAttr.
1143
1178
  v = rel.get_meta_attr(RELATIONSHIP_ATTR_CARDINALITY)
@@ -255,6 +255,15 @@ def _sorted_children(definition: TypeDefinition) -> list[dict[str, object]]:
255
255
  return children
256
256
 
257
257
 
258
+ METAMODEL_VERSION = "0.9"
259
+ """Rolled-up spec-version for the cross-port registry manifest.
260
+
261
+ ``"0"`` = pre-1.0 / unstable (semver major-0). Flips to ``"1.0"`` at the
262
+ 1.0 vocabulary cut. Emitted as the first top-level key in the canonical
263
+ manifest so every port's byte-comparison gate sees it at position 0.
264
+ """
265
+
266
+
258
267
  def build_registry_manifest(registry: TypeRegistry) -> dict[str, object]:
259
268
  """Build the canonical registry-manifest object from an assembled registry.
260
269
 
@@ -306,8 +315,9 @@ def build_registry_manifest(registry: TypeRegistry) -> dict[str, object]:
306
315
  if default_sub is not None:
307
316
  default_sub_types[type_name] = default_sub
308
317
 
309
- # Fixed top-level key order: types, commonAttrs, defaultSubTypes.
318
+ # Fixed top-level key order: metamodelVersion (C4), types, commonAttrs, defaultSubTypes.
310
319
  return {
320
+ "metamodelVersion": METAMODEL_VERSION,
311
321
  "types": types,
312
322
  "commonAttrs": common_attrs,
313
323
  "defaultSubTypes": default_sub_types,
@@ -0,0 +1,150 @@
1
+ """Provider-composition conformance runner (Python port).
2
+
3
+ Five registry/provider error codes are Tier-1 cross-port invariants that the
4
+ metadata-input -> error corpus cannot reach: they are triggered by HOW providers
5
+ are composed and sealed, not by any metadata document. This runner gates them
6
+ from the shared corpus at fixtures/provider-composition-conformance/.
7
+
8
+ Each port supplies the SAME canonical named-provider set (see the corpus
9
+ README). A manifest names providers by id; the runner maps names -> provider
10
+ objects, composes, and asserts the surfaced .code. The registry-sealed scenario
11
+ composes, seals, then runs a probe provider's register_types against the sealed
12
+ registry.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from pathlib import Path
18
+
19
+ import pytest
20
+
21
+ from metaobjects.errors import ErrorCode, ParseError
22
+ from metaobjects.provider import Provider, compose_registry
23
+ from metaobjects.registry import AttrSchema, ChildRule, TypeDefinition, TypeRegistry
24
+ from metaobjects.meta.template.meta_template import MetaTemplate
25
+ from metaobjects.shared.base_types import TYPE_ATTR
26
+
27
+ # Fresh, otherwise-unused template subtype the attr-conflict / seal providers use.
28
+ _CONFLICT_SUBTYPE = "compositionprobe"
29
+ _CONFLICT_ATTR = "conflictAttr"
30
+
31
+
32
+ def _corpus_root() -> Path:
33
+ here = Path(__file__).resolve()
34
+ for parent in [here, *here.parents]:
35
+ candidate = parent / "fixtures" / "provider-composition-conformance"
36
+ if candidate.is_dir():
37
+ return candidate
38
+ raise RuntimeError("could not locate fixtures/provider-composition-conformance from " + str(here))
39
+
40
+
41
+ def _noop_provider(provider_id: str, *deps: str) -> Provider:
42
+ return Provider(provider_id, dependencies=tuple(deps))
43
+
44
+
45
+ def _attr_conflict_base_provider() -> Provider:
46
+ """Registers a fresh test-only type carrying a single string attr."""
47
+ p = Provider("attr-conflict-base")
48
+ p.add(
49
+ TypeDefinition(
50
+ type="template",
51
+ sub_type=_CONFLICT_SUBTYPE,
52
+ factory=lambda t, s, n: MetaTemplate(t, s, n),
53
+ attrs=[AttrSchema(_CONFLICT_ATTR, "string")],
54
+ child_rules=[ChildRule(TYPE_ATTR, "*")],
55
+ )
56
+ )
57
+ return p
58
+
59
+
60
+ class _AttrConflictClashProvider(Provider):
61
+ """Extends the base's type, redefining the same attr name -> attr conflict."""
62
+
63
+ def __init__(self) -> None:
64
+ super().__init__("attr-conflict-clash", dependencies=("attr-conflict-base",))
65
+
66
+ def register_types(self, registry: TypeRegistry) -> None: # noqa: D401
67
+ registry.extend(
68
+ "template",
69
+ _CONFLICT_SUBTYPE,
70
+ attributes=[AttrSchema(_CONFLICT_ATTR, "string")],
71
+ )
72
+
73
+
74
+ class _SealProbeProvider(Provider):
75
+ """Attempts a mutating registration — throws against a sealed registry."""
76
+
77
+ def __init__(self) -> None:
78
+ super().__init__("seal-probe")
79
+
80
+ def register_types(self, registry: TypeRegistry) -> None: # noqa: D401
81
+ registry.register(
82
+ TypeDefinition(
83
+ type="template",
84
+ sub_type="sealprobe",
85
+ factory=lambda t, s, n: MetaTemplate(t, s, n),
86
+ attrs=[],
87
+ child_rules=[],
88
+ )
89
+ )
90
+
91
+
92
+ def _providers() -> dict[str, Provider]:
93
+ return {
94
+ "duplicate-x": _noop_provider("duplicate-x"),
95
+ # Same `.id` as duplicate-x — surfaces ERR_PROVIDER_DUPLICATE_ID at compose time.
96
+ "duplicate-x-clone": _noop_provider("duplicate-x"),
97
+ "depends-on-missing": _noop_provider("depends-on-missing", "does-not-exist"),
98
+ "cycle-a": _noop_provider("cycle-a", "cycle-b"),
99
+ "cycle-b": _noop_provider("cycle-b", "cycle-a"),
100
+ "attr-conflict-base": _attr_conflict_base_provider(),
101
+ "attr-conflict-clash": _AttrConflictClashProvider(),
102
+ "seal-probe": _SealProbeProvider(),
103
+ }
104
+
105
+
106
+ def _resolve(providers: dict[str, Provider], pid: str) -> Provider:
107
+ p = providers.get(pid)
108
+ if p is None:
109
+ raise ValueError(f'Unknown named provider "{pid}" in provider-composition corpus')
110
+ return p
111
+
112
+
113
+ def _manifest_files() -> list[Path]:
114
+ return sorted(_corpus_root().glob("*.json"))
115
+
116
+
117
+ def _error_code(exc: BaseException) -> str:
118
+ code = getattr(exc, "code", None)
119
+ if isinstance(code, ErrorCode):
120
+ return code.value
121
+ if isinstance(code, str):
122
+ return code
123
+ return "ERR_UNKNOWN"
124
+
125
+
126
+ def test_corpus_non_empty() -> None:
127
+ assert len(_manifest_files()) > 0
128
+
129
+
130
+ @pytest.mark.parametrize("manifest_path", _manifest_files(), ids=lambda p: p.name)
131
+ def test_provider_composition(manifest_path: Path) -> None:
132
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
133
+ providers = _providers()
134
+ expected = manifest["expectedError"]
135
+ resolved = [_resolve(providers, pid) for pid in manifest["providers"]]
136
+
137
+ if "sealThenRegister" in manifest:
138
+ # Compose (must succeed), seal, then run the probe against the sealed registry.
139
+ registry = compose_registry(resolved)
140
+ registry.seal()
141
+ probe = _resolve(providers, manifest["sealThenRegister"])
142
+ with pytest.raises(ParseError) as exc_info:
143
+ probe.register_types(registry)
144
+ assert _error_code(exc_info.value) == expected
145
+ return
146
+
147
+ # Ordinary scenario: the compose call itself throws.
148
+ with pytest.raises(ParseError) as exc_info:
149
+ compose_registry(resolved)
150
+ assert _error_code(exc_info.value) == expected
@@ -1,17 +1,31 @@
1
- """Spin up a fresh Postgres container per scenario by shelling out to docker.
2
-
3
- Mirrors the TS/Java/C# integration suites. Avoids the python-testcontainers
4
- package + its docker API negotiation churn (the same kind of mismatch
5
- testcontainers-java hits against Docker Engine 29+).
1
+ """Obtain a fresh, isolated Postgres database per scenario.
2
+
3
+ Two modes, mirroring the TS/Java/Kotlin/C# integration suites:
4
+
5
+ 1. Shared sidecar (CI): if ``METAOBJECTS_TEST_PG_URL`` is set, connect to that
6
+ already-running Postgres and CREATE a uniquely-named database per instance
7
+ (dropping it on stop). No container boot / image pull on the hot path — the
8
+ point of the shared ``services: postgres`` CI sidecar. Each scenario still
9
+ gets a pristine empty database, so isolation is identical to the per-
10
+ container path.
11
+ 2. Per-container (local dev): with no env var, ``docker run`` a fresh container,
12
+ exactly as before. Avoids the python-testcontainers package + its docker API
13
+ negotiation churn (the same kind of mismatch testcontainers-java hits
14
+ against Docker Engine 29+).
6
15
  """
7
16
  from __future__ import annotations
8
17
 
18
+ import os
9
19
  import socket
10
20
  import subprocess
11
21
  import time
12
22
  import uuid
13
23
  from contextlib import closing
14
24
  from dataclasses import dataclass
25
+ from urllib.parse import urlsplit
26
+
27
+ # Env var naming the shared CI Postgres sidecar (admin URL). Unset = per-container.
28
+ SHARED_PG_URL_ENV = "METAOBJECTS_TEST_PG_URL"
15
29
 
16
30
 
17
31
  @dataclass(frozen=True)
@@ -33,8 +47,21 @@ class PostgresContainer:
33
47
  DATABASE = "postgres"
34
48
 
35
49
  def __init__(self) -> None:
50
+ shared = os.environ.get(SHARED_PG_URL_ENV)
51
+ if shared:
52
+ self._shared = True
53
+ self._name = None
54
+ self._db_on_shared(shared)
55
+ return
56
+
57
+ self._shared = False
58
+ self._admin_url = None
36
59
  self._name = f"metaobjects-test-{uuid.uuid4().hex[:8]}"
37
60
  self._port = _pick_free_port()
61
+ self._info = PostgresInfo(
62
+ host="localhost", port=self._port, database=self.DATABASE,
63
+ user=self.USER, password=self.PASSWORD,
64
+ )
38
65
  _docker(
39
66
  "run", "-d", "--rm",
40
67
  "--name", self._name,
@@ -44,13 +71,55 @@ class PostgresContainer:
44
71
  )
45
72
  self._wait_ready()
46
73
 
47
- def info(self) -> PostgresInfo:
48
- return PostgresInfo(
49
- host="localhost", port=self._port, database=self.DATABASE,
50
- user=self.USER, password=self.PASSWORD,
74
+ # -- Shared-sidecar mode -------------------------------------------------
75
+ # CREATE a fresh, uniquely-named database on the already-running Postgres
76
+ # named by the admin URL; a dedicated database per scenario preserves the
77
+ # per-container path's "pristine empty DB" isolation.
78
+ def _db_on_shared(self, admin_url: str) -> None:
79
+ import pg8000.dbapi # local import — pg8000 lives in the `integration` extra
80
+
81
+ parts = urlsplit(admin_url)
82
+ self._admin_url = admin_url
83
+ db_name = f"mo_test_{uuid.uuid4().hex}"
84
+ admin_db = parts.path.lstrip("/") or "postgres"
85
+ conn = pg8000.dbapi.connect(
86
+ host=parts.hostname, port=parts.port or 5432,
87
+ user=parts.username, password=parts.password, database=admin_db,
51
88
  )
89
+ try:
90
+ conn.autocommit = True # CREATE DATABASE cannot run in a transaction
91
+ cur = conn.cursor()
92
+ cur.execute(f'CREATE DATABASE "{db_name}"') # generated name — no user input
93
+ finally:
94
+ conn.close()
95
+ self._info = PostgresInfo(
96
+ host=parts.hostname, port=parts.port or 5432, database=db_name,
97
+ user=parts.username, password=parts.password,
98
+ )
99
+
100
+ def info(self) -> PostgresInfo:
101
+ return self._info
52
102
 
53
103
  def stop(self) -> None:
104
+ if self._shared:
105
+ import pg8000.dbapi
106
+
107
+ parts = urlsplit(self._admin_url)
108
+ admin_db = parts.path.lstrip("/") or "postgres"
109
+ try:
110
+ conn = pg8000.dbapi.connect(
111
+ host=parts.hostname, port=parts.port or 5432,
112
+ user=parts.username, password=parts.password, database=admin_db,
113
+ )
114
+ conn.autocommit = True
115
+ cur = conn.cursor()
116
+ cur.execute(
117
+ f'DROP DATABASE IF EXISTS "{self._info.database}" WITH (FORCE)'
118
+ )
119
+ conn.close()
120
+ except Exception: # noqa: BLE001 — best-effort cleanup
121
+ pass
122
+ return
54
123
  try:
55
124
  _docker("rm", "-f", self._name)
56
125
  except subprocess.CalledProcessError:
@@ -12,6 +12,7 @@ from metaobjects.registry import TypeRegistry
12
12
  from metaobjects.shared.base_types import (
13
13
  SUBTYPE_ROOT,
14
14
  TYPE_FIELD,
15
+ TYPE_IDENTITY,
15
16
  TYPE_METADATA,
16
17
  TYPE_OBJECT,
17
18
  TYPE_ORIGIN,
@@ -119,6 +120,69 @@ def _build_passthrough_tree(from_ref: str) -> MetaData:
119
120
  return root
120
121
 
121
122
 
123
+ def _build_reference_via_tree(via: str) -> MetaData:
124
+ """Build a tree whose projection passthrough joins via an identity.reference
125
+ (a reference-only FK, no relationship) — FR-024 @via-over-reference.
126
+ - Program (id, title)
127
+ - Enrollment (id, programId, identity.reference ref_program → Program)
128
+ - EnrollmentView (extends Enrollment) passthrough Program.title @via *via*
129
+ """
130
+ from metaobjects.meta.core.field.meta_field import MetaField
131
+ from metaobjects.meta.core.object.meta_object import MetaObject
132
+
133
+ root = MetaRoot(TYPE_METADATA, SUBTYPE_ROOT, "")
134
+
135
+ program = MetaObject(TYPE_OBJECT, "entity", "Program")
136
+ program.add_child(MetaField(TYPE_FIELD, "long", "id"))
137
+ program.add_child(MetaField(TYPE_FIELD, "string", "title"))
138
+ root.add_child(program)
139
+
140
+ enrollment = MetaObject(TYPE_OBJECT, "entity", "Enrollment")
141
+ enrollment.add_child(MetaField(TYPE_FIELD, "long", "id"))
142
+ enrollment.add_child(MetaField(TYPE_FIELD, "long", "programId"))
143
+ ref = MetaData(TYPE_IDENTITY, "reference", "ref_program")
144
+ ref.set_attr("fields", "programId", ATTR_SUBTYPE_STRING)
145
+ ref.set_attr("references", "Program", ATTR_SUBTYPE_STRING)
146
+ enrollment.add_child(ref)
147
+ root.add_child(enrollment)
148
+
149
+ view = MetaObject(TYPE_OBJECT, "entity", "EnrollmentView")
150
+ view.super_ref = "Enrollment"
151
+ view.super_data = enrollment
152
+ field = MetaField(TYPE_FIELD, "string", "program_title")
153
+ origin = MetaData(TYPE_ORIGIN, "passthrough", "")
154
+ origin.set_attr("from", "Program.title", ATTR_SUBTYPE_STRING)
155
+ origin.set_attr("via", via, ATTR_SUBTYPE_STRING)
156
+ field.add_child(origin)
157
+ view.add_child(field)
158
+ root.add_child(view)
159
+
160
+ return root
161
+
162
+
163
+ def _via_errors(root: MetaData) -> list[str]:
164
+ """ERR_INVALID_ORIGIN messages that mention the @via path (ignores unrelated passes)."""
165
+ errors, _ = _errors_and_warnings(root)
166
+ return [
167
+ e.message
168
+ for e in errors
169
+ if e.code == ErrorCode.ERR_INVALID_ORIGIN and "@via" in e.message
170
+ ]
171
+
172
+
173
+ def test_valid_passthrough_via_reference_no_error() -> None:
174
+ """@via naming an identity.reference (reference-only FK) resolves — no origin error."""
175
+ root = _build_reference_via_tree(via="Enrollment.ref_program")
176
+ assert _via_errors(root) == []
177
+
178
+
179
+ def test_invalid_passthrough_via_bad_reference() -> None:
180
+ """@via naming neither a relationship nor a reference → ERR_INVALID_ORIGIN."""
181
+ root = _build_reference_via_tree(via="Enrollment.bogus")
182
+ msgs = _via_errors(root)
183
+ assert any("bogus" in m and "no relationship or reference" in m for m in msgs), msgs
184
+
185
+
122
186
  # ---------------------------------------------------------------------------
123
187
  # Tests: origin.aggregate @via validation
124
188
  # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes