metaobjects 0.19.3__tar.gz → 0.19.5__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 (391) hide show
  1. {metaobjects-0.19.3 → metaobjects-0.19.5}/PKG-INFO +7 -3
  2. {metaobjects-0.19.3 → metaobjects-0.19.5}/README.md +6 -2
  3. {metaobjects-0.19.3 → metaobjects-0.19.5}/pyproject.toml +1 -1
  4. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/cli.py +26 -4
  5. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/router_generator.py +133 -1
  6. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli_verify_subverbs.py +110 -0
  7. metaobjects-0.19.5/tests/codegen/test_router_autoset.py +168 -0
  8. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/api_contract_assertions.py +42 -0
  9. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/api_contract_server.py +72 -17
  10. {metaobjects-0.19.3 → metaobjects-0.19.5}/.gitignore +0 -0
  11. {metaobjects-0.19.3 → metaobjects-0.19.5}/LICENSE +0 -0
  12. {metaobjects-0.19.3 → metaobjects-0.19.5}/hatch_build.py +0 -0
  13. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/__init__.py +0 -0
  14. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/agent_context/__init__.py +0 -0
  15. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/agent_context/scaffold.py +0 -0
  16. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/__init__.py +0 -0
  17. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/api_model.py +0 -0
  18. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/builder.py +0 -0
  19. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/naming.py +0 -0
  20. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/paths.py +0 -0
  21. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/apidocs/renderer.py +0 -0
  22. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/attr_class_map.py +0 -0
  23. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
  24. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/__init__.py +0 -0
  25. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/config.py +0 -0
  26. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/constants.py +0 -0
  27. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
  28. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
  29. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/format.py +0 -0
  30. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
  31. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generator.py +0 -0
  32. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generator_registry.py +0 -0
  33. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/__init__.py +0 -0
  34. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/entity_model.py +0 -0
  35. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
  36. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
  37. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
  38. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
  39. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
  40. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
  41. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
  42. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
  43. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/template_generator.py +0 -0
  44. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
  45. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
  46. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/instance_artifacts.py +0 -0
  47. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
  48. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/overwrite_policy.py +0 -0
  49. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/runner.py +0 -0
  50. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/runtime/__init__.py +0 -0
  51. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
  52. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
  53. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
  54. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
  55. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
  56. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/codegen/type_map.py +0 -0
  57. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/core_types.py +0 -0
  58. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/datatype.py +0 -0
  59. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/documentation/__init__.py +0 -0
  60. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/documentation/doc_constants.py +0 -0
  61. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/documentation/doc_provider.py +0 -0
  62. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/documentation/doc_schema.py +0 -0
  63. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/errors.py +0 -0
  64. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/__init__.py +0 -0
  65. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/merge.py +0 -0
  66. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/meta_data_loader.py +0 -0
  67. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/registered_validation.py +0 -0
  68. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/sources/__init__.py +0 -0
  69. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/sources/directory_source.py +0 -0
  70. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/sources/file_source.py +0 -0
  71. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
  72. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/sources/uri_source.py +0 -0
  73. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validate_discriminator.py +0 -0
  74. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validate_field_readonly.py +0 -0
  75. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validate_source_escapes.py +0 -0
  76. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
  77. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
  78. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/loader/validation_passes.py +0 -0
  79. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/__init__.py +0 -0
  80. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/__init__.py +0 -0
  81. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/attr/__init__.py +0 -0
  82. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
  83. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
  84. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/field/__init__.py +0 -0
  85. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/field/field_constants.py +0 -0
  86. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/field/meta_field.py +0 -0
  87. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/identity/__init__.py +0 -0
  88. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
  89. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
  90. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/index/__init__.py +0 -0
  91. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/index/index_constants.py +0 -0
  92. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/index/meta_index.py +0 -0
  93. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/__init__.py +0 -0
  94. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/meta_object.py +0 -0
  95. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
  96. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
  97. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/object_constants.py +0 -0
  98. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/object_extract.py +0 -0
  99. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/object/value_object.py +0 -0
  100. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
  101. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
  102. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
  103. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
  104. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/validator/__init__.py +0 -0
  105. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
  106. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/meta_data.py +0 -0
  107. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/meta_root.py +0 -0
  108. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/__init__.py +0 -0
  109. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
  110. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
  111. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
  112. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
  113. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
  114. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
  115. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
  116. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
  117. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
  118. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/__init__.py +0 -0
  119. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
  120. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
  121. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
  122. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
  123. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
  124. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
  125. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
  126. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
  127. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/provider_extends.py +0 -0
  128. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/template/__init__.py +0 -0
  129. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/template/meta_template.py +0 -0
  130. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/template/prompt_provider.py +0 -0
  131. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/meta/template/template_constants.py +0 -0
  132. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/naming_refs.py +0 -0
  133. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/parser.py +0 -0
  134. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/parser_yaml.py +0 -0
  135. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/provider.py +0 -0
  136. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/py.typed +0 -0
  137. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/registry.py +0 -0
  138. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/registry_manifest.py +0 -0
  139. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/__init__.py +0 -0
  140. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/email_document.py +0 -0
  141. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/escapers.py +0 -0
  142. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
  143. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/__init__.py +0 -0
  144. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/coerce.py +0 -0
  145. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/extract.py +0 -0
  146. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/extract_map.py +0 -0
  147. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
  148. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/locate.py +0 -0
  149. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/normalize.py +0 -0
  150. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/strip.py +0 -0
  151. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/types.py +0 -0
  152. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
  153. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/filesystem_provider.py +0 -0
  154. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/__init__.py +0 -0
  155. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
  156. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
  157. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/prompt_field.py +0 -0
  158. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
  159. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/prompt/prompt_style.py +0 -0
  160. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/renderer.py +0 -0
  161. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/render/verify.py +0 -0
  162. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/runtime/__init__.py +0 -0
  163. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/runtime/llm_recorder.py +0 -0
  164. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/runtime/n2m_resolver.py +0 -0
  165. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/runtime/object_manager.py +0 -0
  166. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/runtime/tph.py +0 -0
  167. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/serializer_json.py +0 -0
  168. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/shared/__init__.py +0 -0
  169. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/shared/base_types.py +0 -0
  170. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/shared/separators.py +0 -0
  171. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/shared/structural.py +0 -0
  172. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/source/__init__.py +0 -0
  173. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/source/error_source.py +0 -0
  174. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/source/json_path.py +0 -0
  175. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/source/semantic_diff.py +0 -0
  176. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/source/yaml_positions.py +0 -0
  177. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/__init__.py +0 -0
  178. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/attr.json +0 -0
  179. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/db.json +0 -0
  180. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/documentation.json +0 -0
  181. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/field.json +0 -0
  182. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/identity.json +0 -0
  183. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/index.json +0 -0
  184. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/layout.json +0 -0
  185. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/object.json +0 -0
  186. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/origin.json +0 -0
  187. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/prompt.json +0 -0
  188. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/relationship.json +0 -0
  189. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/source.json +0 -0
  190. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/template.json +0 -0
  191. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/ui-web.json +0 -0
  192. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/ui.json +0 -0
  193. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/validator.json +0 -0
  194. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/spec_metamodel/view.json +0 -0
  195. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/super_resolve.py +0 -0
  196. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/validation_types.py +0 -0
  197. {metaobjects-0.19.3 → metaobjects-0.19.5}/src/metaobjects/yaml_desugar.py +0 -0
  198. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/__init__.py +0 -0
  199. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/__init__.py +0 -0
  200. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
  201. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/extends/expected/Program.py +0 -0
  202. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/extends/meta.json +0 -0
  203. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
  204. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
  205. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/nested-array/meta.json +0 -0
  206. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
  207. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/scalars/expected/Report.py +0 -0
  208. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/scalars/meta.json +0 -0
  209. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
  210. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/golden/vanilla/meta.json +0 -0
  211. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_abstract_conformance.py +0 -0
  212. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_api_docs_builder.py +0 -0
  213. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_api_docs_paths.py +0 -0
  214. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli.py +0 -0
  215. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli_providers.py +0 -0
  216. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli_registry.py +0 -0
  217. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli_staleness_nudge.py +0 -0
  218. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_cli_verify_strict.py +0 -0
  219. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_constants_config.py +0 -0
  220. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_entity_model.py +0 -0
  221. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_enum_conformance.py +0 -0
  222. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_extends_abstract_field_inheritance.py +0 -0
  223. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_extractor_generator.py +0 -0
  224. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_filter_allowlist_generator.py +0 -0
  225. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_format.py +0 -0
  226. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_fr010_nested_object_prompt.py +0 -0
  227. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_fr010_output_codegen.py +0 -0
  228. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
  229. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_generator.py +0 -0
  230. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_generator_extension_seams.py +0 -0
  231. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_golden.py +0 -0
  232. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
  233. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_inheritance_conformance.py +0 -0
  234. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_instance_artifacts.py +0 -0
  235. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_m2m_codegen.py +0 -0
  236. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_origin_nullability.py +0 -0
  237. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_output_parser_generator.py +0 -0
  238. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_overwrite_policy.py +0 -0
  239. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_payload_vo_generator.py +0 -0
  240. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_projection_compile.py +0 -0
  241. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_render_helper_conformance.py +0 -0
  242. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_render_helper_generator.py +0 -0
  243. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_reverse_finders.py +0 -0
  244. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_router_generator.py +0 -0
  245. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_runner.py +0 -0
  246. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_data.py +0 -0
  247. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_generator.py +0 -0
  248. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_output_pattern.py +0 -0
  249. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_scope_helpers.py +0 -0
  250. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_scope_walk.py +0 -0
  251. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_template_spec.py +0 -0
  252. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_tph_codegen.py +0 -0
  253. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_trace_helper_generator.py +0 -0
  254. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_type_map.py +0 -0
  255. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_validation_conformance.py +0 -0
  256. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/codegen/test_write_through.py +0 -0
  257. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/__init__.py +0 -0
  258. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/capabilities.py +0 -0
  259. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/conformance-expected-failures.json +0 -0
  260. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/conformance_adapter.py +0 -0
  261. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/corpus.py +0 -0
  262. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/expected_failures.py +0 -0
  263. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/fixture_discovery.py +0 -0
  264. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/navigator.py +0 -0
  265. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
  266. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_conformance.py +0 -0
  267. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_extract_conformance.py +0 -0
  268. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_extract_object_verdict.py +0 -0
  269. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_fr010_loader_attrs.py +0 -0
  270. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_fr011_attrs.py +0 -0
  271. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_generator_registry_conformance.py +0 -0
  272. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_object_model_conformance.py +0 -0
  273. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_provider_composition_conformance.py +0 -0
  274. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_registry_conformance.py +0 -0
  275. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_runner_hardfail.py +0 -0
  276. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_spec_metamodel_embed.py +0 -0
  277. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_strict_attr_load.py +0 -0
  278. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_template_codegen_conformance.py +0 -0
  279. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_template_generator_conformance.py +0 -0
  280. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/test_yaml_conformance.py +0 -0
  281. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
  282. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/__init__.py +0 -0
  283. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/api_contract_jsonb_server.py +0 -0
  284. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/api_contract_m2m_server.py +0 -0
  285. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/generated_jsonb_app.py +0 -0
  286. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/generated_m2m_app.py +0 -0
  287. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/generated_router_app.py +0 -0
  288. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/generated_tph_app.py +0 -0
  289. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/meta_ai_trace.yaml +0 -0
  290. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/normalization.py +0 -0
  291. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/postgres_container.py +0 -0
  292. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/query_runner.py +0 -0
  293. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/scenarios.py +0 -0
  294. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract.py +0 -0
  295. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract_generated.py +0 -0
  296. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract_jsonb.py +0 -0
  297. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract_m2m.py +0 -0
  298. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract_m2m_generated.py +0 -0
  299. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_api_contract_tph_generated.py +0 -0
  300. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_generated_router_uuid_pk.py +0 -0
  301. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_llm_call_trace.py +0 -0
  302. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_normalization.py +0 -0
  303. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_query_scenarios.py +0 -0
  304. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/integration/test_runtime_return_types.py +0 -0
  305. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/open_closed_proof_test.py +0 -0
  306. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/__init__.py +0 -0
  307. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/__init__.py +0 -0
  308. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_coerce.py +0 -0
  309. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_extract.py +0 -0
  310. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_extract_map.py +0 -0
  311. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_json_forgiving_reader.py +0 -0
  312. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_locate.py +0 -0
  313. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_model.py +0 -0
  314. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_normalize.py +0 -0
  315. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_strip.py +0 -0
  316. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
  317. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/prompt/__init__.py +0 -0
  318. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/prompt/test_output_format_renderer.py +0 -0
  319. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_email_document.py +0 -0
  320. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_filesystem_provider.py +0 -0
  321. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_output_format_renderer_nested.py +0 -0
  322. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_output_prompt_conformance.py +0 -0
  323. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_render_conformance.py +0 -0
  324. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_render_max_chars.py +0 -0
  325. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_verify.py +0 -0
  326. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/render/test_verify_conformance.py +0 -0
  327. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/__init__.py +0 -0
  328. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_fr5c_merge_attribution.py +0 -0
  329. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_fr5d_reference_resolution.py +0 -0
  330. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_fr5e_database_source_shape.py +0 -0
  331. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_json_path.py +0 -0
  332. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_semantic_diff.py +0 -0
  333. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_source_on_node.py +0 -0
  334. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/source/test_yaml_positions.py +0 -0
  335. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/test_api_docs_accuracy.py +0 -0
  336. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/__init__.py +0 -0
  337. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_agent_context_staleness.py +0 -0
  338. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_capabilities.py +0 -0
  339. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_common_attrs.py +0 -0
  340. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_core_types.py +0 -0
  341. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_effective_package.py +0 -0
  342. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_errors.py +0 -0
  343. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_field_enum.py +0 -0
  344. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_field_map_validation.py +0 -0
  345. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
  346. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
  347. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_index_lookup.py +0 -0
  348. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_llm_recorder.py +0 -0
  349. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_loader.py +0 -0
  350. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_loader_bom.py +0 -0
  351. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_loader_class.py +0 -0
  352. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_merge.py +0 -0
  353. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_meta_attr.py +0 -0
  354. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_meta_data.py +0 -0
  355. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_meta_source.py +0 -0
  356. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_module_shortcuts.py +0 -0
  357. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_n2m_resolver.py +0 -0
  358. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_n2m_resolver_inherited.py +0 -0
  359. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_object_manager_autoset_stamping.py +0 -0
  360. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
  361. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_object_manager_write_through.py +0 -0
  362. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_one_primary_source.py +0 -0
  363. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_parser.py +0 -0
  364. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_parser_yaml.py +0 -0
  365. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_provider.py +0 -0
  366. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_provider_extension.py +0 -0
  367. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_registry.py +0 -0
  368. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_registry_completeness.py +0 -0
  369. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_registry_extend.py +0 -0
  370. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_registry_sealed.py +0 -0
  371. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_relationship_referential_actions.py +0 -0
  372. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_resolution_key.py +0 -0
  373. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
  374. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_serializer.py +0 -0
  375. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_shared_constants.py +0 -0
  376. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_smoke.py +0 -0
  377. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_source_escapes.py +0 -0
  378. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_sources.py +0 -0
  379. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_strict_child_placement.py +0 -0
  380. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_super_resolve.py +0 -0
  381. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_template_toolcall.py +0 -0
  382. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
  383. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_attr_schema.py +0 -0
  384. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_filter_values.py +0 -0
  385. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_origin_195.py +0 -0
  386. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_origin_paths.py +0 -0
  387. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_sort_field.py +0 -0
  388. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_validation_warnings.py +0 -0
  389. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_write_coercion_arrays.py +0 -0
  390. {metaobjects-0.19.3 → metaobjects-0.19.5}/tests/unit/test_yaml_desugar.py +0 -0
  391. {metaobjects-0.19.3 → metaobjects-0.19.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaobjects
3
- Version: 0.19.3
3
+ Version: 0.19.5
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
@@ -45,11 +45,13 @@ other language port.
45
45
 
46
46
  ## Install
47
47
 
48
+ Requires **Python 3.11+**.
49
+
48
50
  ```bash
49
51
  pip install metaobjects
50
52
  ```
51
53
 
52
- Requires Python 3.11+. The only runtime dependency is PyYAML.
54
+ The only runtime dependency is PyYAML.
53
55
 
54
56
  ## Quick start
55
57
 
@@ -77,7 +79,9 @@ The primary public API is the **loader** (`load_directory` / `load_uris` /
77
79
  `load_string`, `MetaDataLoader`, `LoadResult`, `ErrorCode`, `MetaError`). The
78
80
  distribution also ships the Python implementations of the other pillars used by the CLI
79
81
  and tooling: `codegen` (Pydantic + FastAPI emit), `render` (Mustache + payload-VO +
80
- verify), `runtime` (SQLAlchemy-Core object manager), and `migrate`.
82
+ verify), and `runtime` (a DB-API 2 `ObjectManager` pg8000 / psycopg — no SQLAlchemy).
83
+ Schema migrations are owned by the TypeScript `meta` CLI (ADR-0015); there is
84
+ **no `migrate` module** in this distribution.
81
85
 
82
86
  ## Authoring formats
83
87
 
@@ -10,11 +10,13 @@ other language port.
10
10
 
11
11
  ## Install
12
12
 
13
+ Requires **Python 3.11+**.
14
+
13
15
  ```bash
14
16
  pip install metaobjects
15
17
  ```
16
18
 
17
- Requires Python 3.11+. The only runtime dependency is PyYAML.
19
+ The only runtime dependency is PyYAML.
18
20
 
19
21
  ## Quick start
20
22
 
@@ -42,7 +44,9 @@ The primary public API is the **loader** (`load_directory` / `load_uris` /
42
44
  `load_string`, `MetaDataLoader`, `LoadResult`, `ErrorCode`, `MetaError`). The
43
45
  distribution also ships the Python implementations of the other pillars used by the CLI
44
46
  and tooling: `codegen` (Pydantic + FastAPI emit), `render` (Mustache + payload-VO +
45
- verify), `runtime` (SQLAlchemy-Core object manager), and `migrate`.
47
+ verify), and `runtime` (a DB-API 2 `ObjectManager` pg8000 / psycopg — no SQLAlchemy).
48
+ Schema migrations are owned by the TypeScript `meta` CLI (ADR-0015); there is
49
+ **no `migrate` module** in this distribution.
46
50
 
47
51
  ## Authoring formats
48
52
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metaobjects"
3
- version = "0.19.3"
3
+ version = "0.19.5"
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"
@@ -573,6 +573,16 @@ _TEMPLATE_TEXT_REF_ATTRS = (
573
573
  )
574
574
 
575
575
 
576
+ def _attr_str_list(raw: object) -> list[str] | None:
577
+ """Coerce a string-array attr (a list, or a single string) to ``list[str]``; ``None``
578
+ when absent — the shape :func:`render.verify` expects for required_slots/required_tags."""
579
+ if isinstance(raw, str):
580
+ return [raw]
581
+ if isinstance(raw, (list, tuple)):
582
+ return [str(x) for x in raw]
583
+ return None
584
+
585
+
576
586
  def _verify_templates(args: argparse.Namespace) -> int:
577
587
  """``verify --templates`` — the template/prompt ``{{field}}`` ↔ payload drift gate.
578
588
 
@@ -659,6 +669,16 @@ def _verify_templates(args: argparse.Namespace) -> int:
659
669
  error_count += 1
660
670
  continue
661
671
 
672
+ # #193/#230: @requiredSlots/@requiredTags are a template.prompt concept — enforced ONLY on
673
+ # the prompt path (the email/document output gate does not apply them per-part; the #193
674
+ # cross-port consensus). A prompt whose body omits a required output tag →
675
+ # ERR_OUTPUT_TAG_MISSING (drift, fails); an unused required slot → ERR_REQUIRED_SLOT_UNUSED
676
+ # (warning). This brings Python to parity with TS/Java/C# (previously it enforced neither).
677
+ is_prompt = tmpl.sub_type == tc.TEMPLATE_SUBTYPE_PROMPT
678
+ # ADR-0039: resolving read — an inherited @requiredSlots/@requiredTags is honored.
679
+ req_slots = _attr_str_list(tmpl.get_meta_attr(tc.TEMPLATE_ATTR_REQUIRED_SLOTS)) if is_prompt else None
680
+ req_tags = _attr_str_list(tmpl.get_meta_attr(tc.TEMPLATE_ATTR_REQUIRED_TAGS)) if is_prompt else None
681
+
662
682
  for ref in refs:
663
683
  text = provider.resolve(ref)
664
684
  if text is None:
@@ -670,12 +690,14 @@ def _verify_templates(args: argparse.Namespace) -> int:
670
690
  error_count += 1
671
691
  continue
672
692
  checked += 1
673
- for e in render_verify(text, fields, provider=provider):
693
+ for e in render_verify(
694
+ text, fields, provider=provider,
695
+ required_slots=req_slots, required_tags=req_tags,
696
+ ):
674
697
  if e.code == ERR_REQUIRED_SLOT_UNUSED:
675
- continue # warning, not drift
698
+ continue # an unused required slot is a warning, not drift
676
699
  print(
677
- f"error: [{tmpl.name}] ref '{ref}' — {e.code}: "
678
- f"{{{{{e.path}}}}} not on payload VO '{payload_ref}'.",
700
+ f"error: [{tmpl.name}] ref '{ref}' — {e.code}: {e.path}",
679
701
  file=sys.stderr,
680
702
  )
681
703
  error_count += 1
@@ -52,6 +52,7 @@ from metaobjects.codegen.instance_artifacts import emits_instance_artifacts
52
52
  from metaobjects.codegen.type_map import PyType, py_type_for
53
53
  from metaobjects.meta.core.field import field_constants as fc
54
54
  from metaobjects.meta.core.field.meta_field import MetaField
55
+ from metaobjects.meta.persistence.db import db_constants as dbc
55
56
  from metaobjects.meta.core.identity.identity_constants import (
56
57
  IDENTITY_ATTR_FIELDS,
57
58
  IDENTITY_REFERENCE_ATTR_REFERENCES,
@@ -95,7 +96,73 @@ def _required_field_names(entity: MetaObject) -> list[str]:
95
96
  _scalar_fields, which drops object fields for the sort allowlist). FR-035
96
97
  PATCH-2 guards present-null on any of these, and a @required jsonb column can
97
98
  be nulled just like a scalar one."""
98
- return [f.name for f in entity.fields() if is_required(f)]
99
+ # #203/ADR-0045: an @autoSet field is server-owned — never a caller-facing PATCH field, so
100
+ # it is not present-null-checked (a caller-sent null is ignored/stamped, not a 400). Mirrors
101
+ # the Kotlin controller (which excludes @autoSet from its patch-settable set).
102
+ return [f.name for f in entity.fields() if is_required(f) and not _is_auto_set(f)]
103
+
104
+
105
+ # --- #203 / ADR-0045: @autoSet stamping in the generated router (above the repo seam) ----------
106
+
107
+ _AUTO_SET_TEMPORAL_SUBTYPES = (
108
+ fc.FIELD_SUBTYPE_DATE,
109
+ fc.FIELD_SUBTYPE_TIME,
110
+ fc.FIELD_SUBTYPE_TIMESTAMP,
111
+ )
112
+
113
+
114
+ def _is_auto_set(field: MetaField) -> bool:
115
+ """True iff *field* is a server-owned ``@autoSet`` column (ADR-0039 resolving read)."""
116
+ return field.get_meta_attr(fc.FIELD_ATTR_AUTO_SET) in (
117
+ fc.AUTO_SET_ON_CREATE,
118
+ fc.AUTO_SET_ON_UPDATE,
119
+ )
120
+
121
+
122
+ def _auto_set_split(entity: MetaObject) -> tuple[list[MetaField], list[MetaField]]:
123
+ """The entity's ``@autoSet`` temporal fields split into ``(onCreate, onUpdate)``.
124
+
125
+ Reads the RESOLVING ``@autoSet`` (ADR-0039) so an inherited
126
+ ``BaseEntity.createdAt/updatedAt`` is honored; a non-temporal field is skipped.
127
+ Both lists are empty for an entity that declares none, keeping its router
128
+ byte-identical to the pre-#203 output."""
129
+ on_create: list[MetaField] = []
130
+ on_update: list[MetaField] = []
131
+ for f in entity.fields():
132
+ if f.sub_type not in _AUTO_SET_TEMPORAL_SUBTYPES:
133
+ continue
134
+ mode = f.get_meta_attr(fc.FIELD_ATTR_AUTO_SET) # ADR-0039 resolving: may be inherited via extends
135
+ if mode == fc.AUTO_SET_ON_CREATE:
136
+ on_create.append(f)
137
+ elif mode == fc.AUTO_SET_ON_UPDATE:
138
+ on_update.append(f)
139
+ return on_create, on_update
140
+
141
+
142
+ def _auto_set_stamp_expr(field: MetaField, base_var: str) -> str:
143
+ """The inline now()-expression for an ``@autoSet`` *field*, derived from one shared
144
+ tz-aware *base_var* datetime so every column stamped in one op is equal (a fresh
145
+ row's created == updated). Keyed off the COLUMN's temporal type: ``field.date`` →
146
+ ``.date()``; ``field.time`` → ``.time()``; ``field.timestamp`` → the tz-aware base
147
+ (``@localTime`` → a naive wall-clock value). Mirrors ObjectManager._auto_set_stamp."""
148
+ sub = field.sub_type
149
+ if sub == fc.FIELD_SUBTYPE_DATE:
150
+ return f"{base_var}.date()"
151
+ if sub == fc.FIELD_SUBTYPE_TIME:
152
+ return f"{base_var}.time()"
153
+ is_tz = field.get_meta_attr(dbc.FIELD_ATTR_LOCAL_TIME) is not True # ADR-0039 resolving
154
+ return base_var if is_tz else f"{base_var}.replace(tzinfo=None)"
155
+
156
+
157
+ def _auto_set_stamp_lines(fields: list[MetaField], comment: str) -> list[str]:
158
+ """Router-handler body lines stamping *fields* into ``dto`` from ONE captured base
159
+ instant (so all same-op columns are equal), led by *comment*. Empty for no fields
160
+ (so the call site spreads to nothing — a non-@autoSet router stays byte-identical)."""
161
+ if not fields:
162
+ return []
163
+ lines = [f" # {comment}", " _asnow = _dt.datetime.now(_dt.timezone.utc)"]
164
+ lines += [f' dto["{f.name}"] = {_auto_set_stamp_expr(f, "_asnow")}' for f in fields]
165
+ return lines
99
166
 
100
167
 
101
168
  def _py_set_literal(names: list[str], *, frozen: bool = False) -> str:
@@ -259,6 +326,8 @@ class RouterGenerator:
259
326
  ops_const: str,
260
327
  model_name: str,
261
328
  patch_model: str,
329
+ create_autoset: list[str] = (),
330
+ update_autoset: list[str] = (),
262
331
  ) -> list[str]:
263
332
  """One CRUD route handler block, dispatched by *name*
264
333
  (``list`` / ``get`` / ``create`` / ``update`` / ``delete``). Override to
@@ -318,6 +387,10 @@ class RouterGenerator:
318
387
  f" {model_name}(**dto)",
319
388
  " except ValidationError:",
320
389
  ' return JSONResponse(status_code=400, content={"error": "validation"})',
390
+ # #203/ADR-0045: the generated router (the API surface) stamps @autoSet columns
391
+ # ABOVE the consumer repo seam — onCreate + onUpdate both stamped from one instant
392
+ # (a fresh row's createdAt == updatedAt), ignoring any caller-supplied value.
393
+ *create_autoset,
321
394
  " return repo.create(dto)",
322
395
  ]
323
396
  if name == "update":
@@ -341,6 +414,10 @@ class RouterGenerator:
341
414
  f" {patch_model}(**dto)",
342
415
  " except ValidationError:",
343
416
  ' return JSONResponse(status_code=400, content={"error": "validation"})',
417
+ # #203/ADR-0045: bump onUpdate @autoSet columns (server-owned) on EVERY patch,
418
+ # above the repo seam — a server-inserted present key (compatible with the PATCH
419
+ # present-key tristate); onCreate columns are never touched here.
420
+ *update_autoset,
344
421
  f" saved = repo.update({pk_param}, dto)",
345
422
  " if saved is None:",
346
423
  ' return JSONResponse(status_code=404, content={"error": "not_found"})',
@@ -417,6 +494,24 @@ class RouterGenerator:
417
494
  ops_const = f"{upper}_FILTER_OPS_BY_FIELD"
418
495
  allowlist_module = f"{snake}_filter_allowlist"
419
496
 
497
+ # #203/ADR-0045: @autoSet columns live on the BASE entity and are shared by every
498
+ # subtype (RESOLVING _auto_set_split(entity) picks up an inherited BaseEntity
499
+ # column too) — computed once here and threaded into each per-subtype handler
500
+ # below (empty for a non-@autoSet hierarchy → byte-identical output).
501
+ on_create_auto, on_update_auto = _auto_set_split(entity)
502
+ has_autoset = bool(on_create_auto or on_update_auto)
503
+ create_autoset = _auto_set_stamp_lines(
504
+ on_create_auto + on_update_auto,
505
+ "#203/ADR-0045: stamp @autoSet columns (server-owned; caller ignored).",
506
+ )
507
+ update_autoset = [
508
+ f' dto.pop("{f.name}", None) # onCreate @autoSet is write-once (server-owned)'
509
+ for f in on_create_auto
510
+ ] + _auto_set_stamp_lines(
511
+ on_update_auto,
512
+ "#203/ADR-0045: bump onUpdate @autoSet column(s) (server-owned).",
513
+ )
514
+
420
515
  # Sort allowlist = base scalar fields ∪ every subtype's own scalar fields, so a
421
516
  # per-subtype list can sort on a subtype-only column too. Stable order.
422
517
  sort_fields: list[str] = [f.name for f in _scalar_fields(entity)]
@@ -448,6 +543,10 @@ class RouterGenerator:
448
543
  )
449
544
  parts.append("from __future__ import annotations")
450
545
  parts.append("")
546
+ if has_autoset:
547
+ # #203/ADR-0045: the router stamps @autoSet columns inline (no runtime dependency).
548
+ parts.append("import datetime as _dt")
549
+ parts.append("")
451
550
  for import_line in sorted(pk.imports):
452
551
  parts.append(import_line)
453
552
  if pk.imports:
@@ -555,6 +654,9 @@ class RouterGenerator:
555
654
  parts.append(f" {sub}Create(**dto)")
556
655
  parts.append(" except ValidationError:")
557
656
  parts.append(' return JSONResponse(status_code=400, content={"error": "validation"})')
657
+ # #203/ADR-0045: stamp @autoSet columns ABOVE the repo seam (empty for a
658
+ # non-@autoSet hierarchy — byte-identical output).
659
+ parts.extend(create_autoset)
558
660
  parts.append(f' return repo.create("{val}", dto)')
559
661
  parts.append("")
560
662
  parts.append("")
@@ -585,6 +687,9 @@ class RouterGenerator:
585
687
  parts.append(f" {sub}Patch(**dto)")
586
688
  parts.append(" except ValidationError:")
587
689
  parts.append(' return JSONResponse(status_code=400, content={"error": "validation"})')
690
+ # #203/ADR-0045: pop the write-once onCreate column(s), bump onUpdate — ABOVE
691
+ # the repo seam (empty for a non-@autoSet hierarchy — byte-identical output).
692
+ parts.extend(update_autoset)
588
693
  parts.append(f' saved = repo.update("{val}", {pk_param}, dto)')
589
694
  parts.append(" if saved is None:")
590
695
  parts.append(' return JSONResponse(status_code=404, content={"error": "not_found"})')
@@ -684,6 +789,27 @@ class RouterGenerator:
684
789
  # is a 400 — the update handler guards these before the repo call.
685
790
  required_set_body = _py_set_literal(_required_field_names(entity), frozen=True)
686
791
 
792
+ # #203/ADR-0045: @autoSet stamp lines for the create/update handlers (empty for a
793
+ # non-@autoSet entity → byte-identical output). insert stamps onCreate+onUpdate from one
794
+ # instant; a patch bumps onUpdate only (createdAt stays immutable).
795
+ _on_create_auto, _on_update_auto = _auto_set_split(entity)
796
+ create_autoset = _auto_set_stamp_lines(
797
+ _on_create_auto + _on_update_auto,
798
+ "#203/ADR-0045: stamp @autoSet columns (server-owned; caller ignored).",
799
+ )
800
+ # #203/ADR-0045 (integrity): onCreate @autoSet columns are write-once — strip any
801
+ # caller-supplied value on PATCH before the repo call (a caller cannot mutate createdAt via
802
+ # the deployed API). Mirrors ObjectManager.update (`data.pop(...)`) + the Kotlin controller
803
+ # (which excludes @autoSet from its patch-settable set).
804
+ update_autoset = [
805
+ f' dto.pop("{f.name}", None) # onCreate @autoSet is write-once (server-owned)'
806
+ for f in _on_create_auto
807
+ ] + _auto_set_stamp_lines(
808
+ _on_update_auto,
809
+ "#203/ADR-0045: bump onUpdate @autoSet column(s) (server-owned).",
810
+ )
811
+ has_autoset = bool(_on_create_auto or _on_update_auto)
812
+
687
813
  parts: list[str] = []
688
814
  parts.append(
689
815
  generated_header(short_name, _effective_fqn(entity)).rstrip() + "\n"
@@ -691,6 +817,10 @@ class RouterGenerator:
691
817
  )
692
818
  parts.append("from __future__ import annotations")
693
819
  parts.append("")
820
+ if has_autoset:
821
+ # #203/ADR-0045: the router stamps @autoSet columns inline (no runtime dependency).
822
+ parts.append("import datetime as _dt")
823
+ parts.append("")
694
824
  for import_line in sorted(pk.imports):
695
825
  parts.append(import_line)
696
826
  if pk.imports:
@@ -762,6 +892,8 @@ class RouterGenerator:
762
892
  ops_const=ops_const,
763
893
  model_name=f"{short_name}Create",
764
894
  patch_model=f"{short_name}Patch",
895
+ create_autoset=create_autoset,
896
+ update_autoset=update_autoset,
765
897
  )
766
898
  for i, hname in enumerate(("list", "get", "create", "update", "delete")):
767
899
  if i > 0:
@@ -79,6 +79,46 @@ def _templates_dir(tmp_path: Path, body: str) -> str:
79
79
  return str(root)
80
80
 
81
81
 
82
+ # An email `template.output` (@kind=email) carries subject + html body part-refs
83
+ # (NO @textRef). Each part's mustache must be drift-checked against the payload VO,
84
+ # parity with a document output and template.prompt (#193).
85
+ _META_EMAIL = """\
86
+ {
87
+ "metadata.root": {
88
+ "package": "acme::ai",
89
+ "children": [
90
+ {
91
+ "object.value": {
92
+ "name": "Welcome",
93
+ "children": [
94
+ { "field.string": { "name": "name", "@required": true } }
95
+ ]
96
+ }
97
+ },
98
+ {
99
+ "template.output": {
100
+ "name": "WelcomeEmail",
101
+ "@kind": "email",
102
+ "@payloadRef": "Welcome",
103
+ "@subjectRef": "emails/subject",
104
+ "@htmlBodyRef": "emails/html"
105
+ }
106
+ }
107
+ ]
108
+ }
109
+ }
110
+ """
111
+
112
+
113
+ def _email_templates_dir(tmp_path: Path, subject: str, html: str) -> str:
114
+ """Write a templates root with the email subject + html body parts."""
115
+ root = tmp_path / "email_templates"
116
+ (root / "emails").mkdir(parents=True)
117
+ (root / "emails" / "subject.mustache").write_text(subject)
118
+ (root / "emails" / "html.mustache").write_text(html)
119
+ return str(root)
120
+
121
+
82
122
  def _meta_dir_with(tmp_path: Path, meta_json: str) -> str:
83
123
  d = tmp_path / "tmeta"
84
124
  d.mkdir()
@@ -135,6 +175,76 @@ def test_templates_unresolvable_ref_is_drift(tmp_path: Path) -> None:
135
175
  assert rc != 0
136
176
 
137
177
 
178
+ def test_templates_email_clean_returns_zero(tmp_path: Path) -> None:
179
+ # An @kind=email output whose subject + html parts reference only payload
180
+ # fields is clean (#193 — email parts drift-checked like a document body).
181
+ meta_dir = _meta_dir_with(tmp_path, _META_EMAIL)
182
+ troot = _email_templates_dir(tmp_path, "Hello {{name}}", "<p>Hi {{name}}</p>")
183
+ rc = main(["verify", "--templates", meta_dir, "--templates-root", troot])
184
+ assert rc == 0
185
+
186
+
187
+ def test_templates_email_body_drift_is_caught(tmp_path: Path, capsys) -> None:
188
+ # A {{missing}} in the email HTML body part must be reported (#193): the
189
+ # per-port bug was skipping @kind=email templates entirely.
190
+ meta_dir = _meta_dir_with(tmp_path, _META_EMAIL)
191
+ troot = _email_templates_dir(tmp_path, "Hello {{name}}", "<p>Hi {{missing}}</p>")
192
+ rc = main(["verify", "--templates", meta_dir, "--templates-root", troot])
193
+ assert rc != 0
194
+ err = capsys.readouterr().err
195
+ assert "missing" in err
196
+ assert "WelcomeEmail" in err
197
+
198
+
199
+ # --- #230: template.prompt @requiredTags / @requiredSlots enforcement (parity w/ TS/Java/C#) ---
200
+
201
+ _META_PROMPT_TAGS = """\
202
+ {
203
+ "metadata.root": {
204
+ "package": "acme::ai",
205
+ "children": [
206
+ {
207
+ "object.value": {
208
+ "name": "Welcome",
209
+ "children": [
210
+ { "field.string": { "name": "name", "@required": true } }
211
+ ]
212
+ }
213
+ },
214
+ {
215
+ "template.prompt": {
216
+ "name": "TaggedPrompt",
217
+ "@payloadRef": "Welcome",
218
+ "@textRef": "pages/welcome",
219
+ "@requiredTags": ["answer"]
220
+ }
221
+ }
222
+ ]
223
+ }
224
+ }
225
+ """
226
+
227
+
228
+ def test_templates_prompt_required_tag_missing_is_drift(tmp_path: Path, capsys) -> None:
229
+ # #230: a template.prompt @requiredTags whose rendered body omits the <answer> tag must FAIL
230
+ # verify. Python previously passed required_slots/required_tags to render_verify NOWHERE, so it
231
+ # enforced neither — a divergence from TS/Java/C#.
232
+ meta_dir = _meta_dir_with(tmp_path, _META_PROMPT_TAGS)
233
+ troot = _templates_dir(tmp_path, "Hello {{name}}") # no <answer>…</answer> tag
234
+ rc = main(["verify", "--templates", meta_dir, "--templates-root", troot])
235
+ assert rc != 0
236
+ err = capsys.readouterr().err
237
+ assert "ERR_OUTPUT_TAG_MISSING" in err
238
+ assert "answer" in err
239
+
240
+
241
+ def test_templates_prompt_required_tag_present_passes(tmp_path: Path) -> None:
242
+ meta_dir = _meta_dir_with(tmp_path, _META_PROMPT_TAGS)
243
+ troot = _templates_dir(tmp_path, "Hello {{name}} <answer>{{name}}</answer>")
244
+ rc = main(["verify", "--templates", meta_dir, "--templates-root", troot])
245
+ assert rc == 0
246
+
247
+
138
248
  # --- 3. bare verify = codegen (back-compat) + the subverb note --------------
139
249
 
140
250
 
@@ -0,0 +1,168 @@
1
+ """#203 / ADR-0045 — the generated FastAPI router (the deployed API surface) stamps
2
+ ``@autoSet`` timestamps ABOVE the consumer repository seam, so an adopter who wires
3
+ their own persistence into the generated router still gets the shipped stamping
4
+ semantics. (The ObjectManager keeps stamping for non-HTTP writes — defense-in-depth,
5
+ not the wire-tier guarantee.) A non-``@autoSet`` entity's router stays byte-identical."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import shutil
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+ from metaobjects import MetaDataLoader
14
+ from metaobjects.codegen.generators.m2m_codegen import build_object_index
15
+ from metaobjects.codegen.generators.router_generator import render_router
16
+ from metaobjects.meta.core.object.meta_object import MetaObject
17
+ from metaobjects.shared.base_types import TYPE_OBJECT
18
+
19
+ # Event declares both @autoSet policies on field.timestamp columns; Note declares none
20
+ # (the byte-identical baseline).
21
+ _AUTOSET_FIXTURE = """{
22
+ "metadata.root": { "package": "acme::events", "children": [
23
+ { "object.entity": { "name": "Event", "children": [
24
+ { "source.rdb": { "@table": "events" } },
25
+ { "field.long": { "name": "id" } },
26
+ { "field.string": { "name": "name", "@required": true, "@maxLength": 120 } },
27
+ { "field.timestamp": { "name": "createdAt", "@autoSet": "onCreate" } },
28
+ { "field.timestamp": { "name": "updatedAt", "@autoSet": "onUpdate" } },
29
+ { "identity.primary": { "@fields": "id", "@generation": "increment" } }
30
+ ] } },
31
+ { "object.entity": { "name": "Note", "children": [
32
+ { "source.rdb": { "@table": "notes" } },
33
+ { "field.long": { "name": "id" } },
34
+ { "field.string": { "name": "title", "@required": true, "@maxLength": 200 } },
35
+ { "identity.primary": { "@fields": "id", "@generation": "increment" } }
36
+ ] } }
37
+ ] }
38
+ }"""
39
+
40
+
41
+ def _load(fixture: str) -> dict[str, MetaObject]:
42
+ tmp = Path(tempfile.mkdtemp(prefix="router-autoset-"))
43
+ try:
44
+ (tmp / "meta.events.json").write_text(fixture)
45
+ result = MetaDataLoader.from_directory(str(tmp))
46
+ assert not result.errors, [f"{e.code}: {e.message}" for e in result.errors]
47
+ return {
48
+ c.name: c
49
+ for c in result.root.children()
50
+ if c.type == TYPE_OBJECT and isinstance(c, MetaObject)
51
+ }
52
+ finally:
53
+ shutil.rmtree(tmp, ignore_errors=True)
54
+
55
+
56
+ _ENTITIES = _load(_AUTOSET_FIXTURE)
57
+ _INDEX = build_object_index(list(_ENTITIES.values()))
58
+
59
+
60
+ def test_router_create_stamps_both_autoset_columns_from_one_instant() -> None:
61
+ src = render_router(_ENTITIES["Event"], _INDEX)
62
+ assert src is not None
63
+ assert "import datetime as _dt" in src
64
+ # ONE captured base instant, assigned to BOTH columns → a fresh row's createdAt == updatedAt.
65
+ assert "_asnow = _dt.datetime.now(_dt.timezone.utc)" in src
66
+ assert 'dto["createdAt"] = _asnow' in src
67
+ assert 'dto["updatedAt"] = _asnow' in src
68
+
69
+
70
+ def test_router_update_bumps_only_onupdate() -> None:
71
+ src = render_router(_ENTITIES["Event"], _INDEX)
72
+ assert src is not None
73
+ update_body = src.split("def update_")[1].split("def delete_")[0]
74
+ # the update handler bumps updatedAt; createdAt is never stamped on update (immutable).
75
+ assert 'dto["updatedAt"] = _asnow' in update_body
76
+ assert 'dto["createdAt"] = ' not in update_body
77
+ # #203/ADR-0045 integrity: a caller-supplied onCreate @autoSet value is STRIPPED on PATCH
78
+ # (write-once — cannot be mutated via the deployed API), so it never reaches repo.update.
79
+ assert 'dto.pop("createdAt", None)' in update_body
80
+
81
+
82
+ def test_non_autoset_router_is_byte_identical() -> None:
83
+ src = render_router(_ENTITIES["Note"], _INDEX)
84
+ assert src is not None
85
+ assert "import datetime as _dt" not in src
86
+ assert "_asnow" not in src
87
+ assert "_dt.datetime.now" not in src
88
+
89
+
90
+ # ---- FR-017 TPH: the per-subtype router also stamps @autoSet (#203/#229) ----------------
91
+ #
92
+ # The base `Auth` declares both @autoSet policies on field.timestamp columns (shared by
93
+ # every subtype via `extends`); `PlainAuth` declares none (the byte-identical baseline).
94
+
95
+ TPH_AUTOSET = """
96
+ { "metadata.root": { "package": "acme::auth", "children": [
97
+ { "object.entity": { "name": "Auth", "@discriminator": "type", "children": [
98
+ { "source.rdb": { "@table": "auths" } },
99
+ { "field.long": { "name": "id" } },
100
+ { "field.enum": { "name": "type", "@values": ["Bridge"] } },
101
+ { "field.string": { "name": "reference", "@required": true, "@maxLength": 80 } },
102
+ { "field.timestamp": { "name": "autoCreatedAt", "@autoSet": "onCreate" } },
103
+ { "field.timestamp": { "name": "autoUpdatedAt", "@autoSet": "onUpdate" } },
104
+ { "identity.primary": { "name": "id", "@fields": "id", "@generation": "increment" } }
105
+ ]}},
106
+ { "object.entity": { "name": "BridgeAuth", "extends": "Auth", "@discriminatorValue": "Bridge", "children": [
107
+ { "field.int": { "name": "quantity", "@required": true } }
108
+ ]}}
109
+ ]}}
110
+ """
111
+
112
+ TPH_PLAIN = """
113
+ { "metadata.root": { "package": "acme::auth", "children": [
114
+ { "object.entity": { "name": "Auth", "@discriminator": "type", "children": [
115
+ { "source.rdb": { "@table": "auths" } },
116
+ { "field.long": { "name": "id" } },
117
+ { "field.enum": { "name": "type", "@values": ["Bridge"] } },
118
+ { "field.string": { "name": "reference", "@required": true, "@maxLength": 80 } },
119
+ { "identity.primary": { "name": "id", "@fields": "id", "@generation": "increment" } }
120
+ ]}},
121
+ { "object.entity": { "name": "BridgeAuth", "extends": "Auth", "@discriminatorValue": "Bridge", "children": [
122
+ { "field.int": { "name": "quantity", "@required": true } }
123
+ ]}}
124
+ ]}}
125
+ """
126
+
127
+
128
+ def _render_tph(fixture: str, name: str) -> str:
129
+ """Load *fixture*, build the object index, and render *name*'s (the TPH base's)
130
+ router — dispatched to ``_render_tph_router`` inside ``render_router`` once a
131
+ discriminator plan resolves for it."""
132
+ entities = _load(fixture)
133
+ index = build_object_index(list(entities.values()))
134
+ src = render_router(entities[name], index)
135
+ assert src is not None
136
+ return src
137
+
138
+
139
+ def _handler_body(src: str, def_line: str) -> str:
140
+ """Slice out one handler's source, from *def_line* up to (not including) the next
141
+ top-level ``def `` (the next handler in the module)."""
142
+ after = src.split(def_line, 1)[1]
143
+ return after.split("\ndef ", 1)[0]
144
+
145
+
146
+ def test_tph_router_imports_datetime_when_autoset_present() -> None:
147
+ src = _render_tph(TPH_AUTOSET, "Auth")
148
+ assert "import datetime as _dt" in src
149
+
150
+
151
+ def test_tph_per_subtype_create_stamps_both_autoset_columns() -> None:
152
+ body = _handler_body(_render_tph(TPH_AUTOSET, "Auth"), "def create_auths_bridge")
153
+ assert "_asnow = _dt.datetime.now(_dt.timezone.utc)" in body
154
+ assert 'dto["autoCreatedAt"] = _asnow' in body
155
+ assert 'dto["autoUpdatedAt"] = _asnow' in body
156
+
157
+
158
+ def test_tph_per_subtype_update_pops_oncreate_and_bumps_onupdate() -> None:
159
+ body = _handler_body(_render_tph(TPH_AUTOSET, "Auth"), "def update_auths_bridge")
160
+ assert 'dto.pop("autoCreatedAt", None)' in body
161
+ assert 'dto["autoUpdatedAt"] = _asnow' in body
162
+ assert 'dto["autoCreatedAt"] = ' not in body
163
+
164
+
165
+ def test_tph_router_without_autoset_is_byte_identical() -> None:
166
+ src = _render_tph(TPH_PLAIN, "Auth")
167
+ assert "import datetime as _dt" not in src
168
+ assert "_asnow" not in src