metaobjects 0.13.0__tar.gz → 0.14.0__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 (369) hide show
  1. {metaobjects-0.13.0 → metaobjects-0.14.0}/PKG-INFO +1 -1
  2. {metaobjects-0.13.0 → metaobjects-0.14.0}/pyproject.toml +1 -1
  3. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/cli.py +48 -7
  4. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/type_map.py +12 -0
  5. metaobjects-0.14.0/tests/codegen/test_cli_verify_strict.py +139 -0
  6. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_entity_model.py +14 -0
  7. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_type_map.py +14 -0
  8. metaobjects-0.14.0/tests/integration/api_contract_jsonb_server.py +160 -0
  9. metaobjects-0.14.0/tests/integration/generated_jsonb_app.py +135 -0
  10. metaobjects-0.14.0/tests/integration/test_api_contract_jsonb.py +153 -0
  11. {metaobjects-0.13.0 → metaobjects-0.14.0}/.gitignore +0 -0
  12. {metaobjects-0.13.0 → metaobjects-0.14.0}/LICENSE +0 -0
  13. {metaobjects-0.13.0 → metaobjects-0.14.0}/README.md +0 -0
  14. {metaobjects-0.13.0 → metaobjects-0.14.0}/hatch_build.py +0 -0
  15. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/__init__.py +0 -0
  16. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/agent_context/__init__.py +0 -0
  17. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/agent_context/scaffold.py +0 -0
  18. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/__init__.py +0 -0
  19. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/api_model.py +0 -0
  20. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/builder.py +0 -0
  21. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/naming.py +0 -0
  22. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/paths.py +0 -0
  23. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/renderer.py +0 -0
  24. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/attr_class_map.py +0 -0
  25. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
  26. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/__init__.py +0 -0
  27. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/config.py +0 -0
  28. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/constants.py +0 -0
  29. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
  30. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
  31. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/format.py +0 -0
  32. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
  33. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generator.py +0 -0
  34. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generator_registry.py +0 -0
  35. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/__init__.py +0 -0
  36. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/entity_model.py +0 -0
  37. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
  38. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
  39. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
  40. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
  41. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
  42. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
  43. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
  44. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
  45. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/router_generator.py +0 -0
  46. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/template_generator.py +0 -0
  47. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
  48. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
  49. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/instance_artifacts.py +0 -0
  50. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
  51. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/overwrite_policy.py +0 -0
  52. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runner.py +0 -0
  53. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runtime/__init__.py +0 -0
  54. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
  55. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
  56. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
  57. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
  58. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
  59. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/core_types.py +0 -0
  60. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/datatype.py +0 -0
  61. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/__init__.py +0 -0
  62. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_constants.py +0 -0
  63. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_provider.py +0 -0
  64. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_schema.py +0 -0
  65. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/errors.py +0 -0
  66. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/__init__.py +0 -0
  67. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/merge.py +0 -0
  68. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/meta_data_loader.py +0 -0
  69. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/registered_validation.py +0 -0
  70. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/__init__.py +0 -0
  71. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/directory_source.py +0 -0
  72. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/file_source.py +0 -0
  73. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
  74. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/uri_source.py +0 -0
  75. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_discriminator.py +0 -0
  76. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_field_readonly.py +0 -0
  77. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
  78. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
  79. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validation_passes.py +0 -0
  80. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/__init__.py +0 -0
  81. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/__init__.py +0 -0
  82. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/__init__.py +0 -0
  83. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
  84. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
  85. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/__init__.py +0 -0
  86. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/field_constants.py +0 -0
  87. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/meta_field.py +0 -0
  88. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/__init__.py +0 -0
  89. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
  90. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
  91. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/__init__.py +0 -0
  92. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/meta_object.py +0 -0
  93. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
  94. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
  95. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_constants.py +0 -0
  96. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_extract.py +0 -0
  97. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/value_object.py +0 -0
  98. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
  99. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
  100. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
  101. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
  102. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/validator/__init__.py +0 -0
  103. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
  104. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/meta_data.py +0 -0
  105. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/meta_root.py +0 -0
  106. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/__init__.py +0 -0
  107. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
  108. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
  109. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
  110. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
  111. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
  112. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
  113. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
  114. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
  115. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
  116. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/__init__.py +0 -0
  117. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
  118. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
  119. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
  120. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
  121. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
  122. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
  123. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
  124. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
  125. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/provider_extends.py +0 -0
  126. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/__init__.py +0 -0
  127. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/meta_template.py +0 -0
  128. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/prompt_provider.py +0 -0
  129. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/template_constants.py +0 -0
  130. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/naming_refs.py +0 -0
  131. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/parser.py +0 -0
  132. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/parser_yaml.py +0 -0
  133. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/provider.py +0 -0
  134. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/py.typed +0 -0
  135. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/registry.py +0 -0
  136. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/registry_manifest.py +0 -0
  137. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/__init__.py +0 -0
  138. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/email_document.py +0 -0
  139. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/escapers.py +0 -0
  140. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
  141. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/__init__.py +0 -0
  142. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/coerce.py +0 -0
  143. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/extract.py +0 -0
  144. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/extract_map.py +0 -0
  145. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
  146. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/locate.py +0 -0
  147. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/normalize.py +0 -0
  148. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/strip.py +0 -0
  149. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/types.py +0 -0
  150. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
  151. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/filesystem_provider.py +0 -0
  152. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/__init__.py +0 -0
  153. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
  154. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
  155. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_field.py +0 -0
  156. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
  157. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_style.py +0 -0
  158. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/renderer.py +0 -0
  159. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/verify.py +0 -0
  160. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/__init__.py +0 -0
  161. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/llm_recorder.py +0 -0
  162. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/n2m_resolver.py +0 -0
  163. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/object_manager.py +0 -0
  164. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/tph.py +0 -0
  165. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/serializer_json.py +0 -0
  166. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/__init__.py +0 -0
  167. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/base_types.py +0 -0
  168. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/separators.py +0 -0
  169. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/structural.py +0 -0
  170. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/__init__.py +0 -0
  171. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/error_source.py +0 -0
  172. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/json_path.py +0 -0
  173. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/semantic_diff.py +0 -0
  174. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/yaml_positions.py +0 -0
  175. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/__init__.py +0 -0
  176. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/attr.json +0 -0
  177. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/db.json +0 -0
  178. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/documentation.json +0 -0
  179. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/field.json +0 -0
  180. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/identity.json +0 -0
  181. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/layout.json +0 -0
  182. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/object.json +0 -0
  183. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/origin.json +0 -0
  184. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/prompt.json +0 -0
  185. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/relationship.json +0 -0
  186. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/source.json +0 -0
  187. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/template.json +0 -0
  188. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/ui.json +0 -0
  189. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/validator.json +0 -0
  190. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/view.json +0 -0
  191. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/super_resolve.py +0 -0
  192. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/validation_types.py +0 -0
  193. {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/yaml_desugar.py +0 -0
  194. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/__init__.py +0 -0
  195. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/__init__.py +0 -0
  196. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
  197. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/expected/Program.py +0 -0
  198. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/meta.json +0 -0
  199. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
  200. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
  201. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/meta.json +0 -0
  202. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
  203. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/expected/Report.py +0 -0
  204. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/meta.json +0 -0
  205. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
  206. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/vanilla/meta.json +0 -0
  207. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_abstract_conformance.py +0 -0
  208. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_api_docs_builder.py +0 -0
  209. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_api_docs_paths.py +0 -0
  210. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli.py +0 -0
  211. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_registry.py +0 -0
  212. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_staleness_nudge.py +0 -0
  213. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_verify_subverbs.py +0 -0
  214. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_constants_config.py +0 -0
  215. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_enum_conformance.py +0 -0
  216. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_extractor_generator.py +0 -0
  217. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_filter_allowlist_generator.py +0 -0
  218. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_format.py +0 -0
  219. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_fr010_output_codegen.py +0 -0
  220. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
  221. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_generator.py +0 -0
  222. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_generator_extension_seams.py +0 -0
  223. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_golden.py +0 -0
  224. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
  225. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_inheritance_conformance.py +0 -0
  226. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_instance_artifacts.py +0 -0
  227. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_m2m_codegen.py +0 -0
  228. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_output_parser_generator.py +0 -0
  229. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_overwrite_policy.py +0 -0
  230. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_payload_vo_generator.py +0 -0
  231. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_projection_compile.py +0 -0
  232. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_render_helper_conformance.py +0 -0
  233. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_render_helper_generator.py +0 -0
  234. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_router_generator.py +0 -0
  235. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_runner.py +0 -0
  236. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_data.py +0 -0
  237. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_generator.py +0 -0
  238. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_output_pattern.py +0 -0
  239. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_scope_helpers.py +0 -0
  240. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_scope_walk.py +0 -0
  241. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_spec.py +0 -0
  242. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_tph_codegen.py +0 -0
  243. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_trace_helper_generator.py +0 -0
  244. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_validation_conformance.py +0 -0
  245. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/__init__.py +0 -0
  246. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/capabilities.py +0 -0
  247. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/conformance-expected-failures.json +0 -0
  248. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/conformance_adapter.py +0 -0
  249. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/corpus.py +0 -0
  250. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/expected_failures.py +0 -0
  251. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/fixture_discovery.py +0 -0
  252. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/navigator.py +0 -0
  253. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
  254. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_conformance.py +0 -0
  255. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_extract_conformance.py +0 -0
  256. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_extract_object_verdict.py +0 -0
  257. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_fr010_loader_attrs.py +0 -0
  258. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_fr011_attrs.py +0 -0
  259. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_generator_registry_conformance.py +0 -0
  260. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_object_model_conformance.py +0 -0
  261. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_registry_conformance.py +0 -0
  262. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_runner_hardfail.py +0 -0
  263. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_spec_metamodel_embed.py +0 -0
  264. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_strict_attr_load.py +0 -0
  265. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_template_codegen_conformance.py +0 -0
  266. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_template_generator_conformance.py +0 -0
  267. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_yaml_conformance.py +0 -0
  268. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
  269. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/__init__.py +0 -0
  270. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_assertions.py +0 -0
  271. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_m2m_server.py +0 -0
  272. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_server.py +0 -0
  273. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_m2m_app.py +0 -0
  274. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_router_app.py +0 -0
  275. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_tph_app.py +0 -0
  276. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/meta_ai_trace.yaml +0 -0
  277. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/normalization.py +0 -0
  278. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/postgres_container.py +0 -0
  279. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/query_runner.py +0 -0
  280. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/scenarios.py +0 -0
  281. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract.py +0 -0
  282. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_generated.py +0 -0
  283. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_m2m.py +0 -0
  284. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_m2m_generated.py +0 -0
  285. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_tph_generated.py +0 -0
  286. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_llm_call_trace.py +0 -0
  287. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_normalization.py +0 -0
  288. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_query_scenarios.py +0 -0
  289. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_runtime_return_types.py +0 -0
  290. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/open_closed_proof_test.py +0 -0
  291. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/__init__.py +0 -0
  292. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/__init__.py +0 -0
  293. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_coerce.py +0 -0
  294. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_extract.py +0 -0
  295. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_extract_map.py +0 -0
  296. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_json_forgiving_reader.py +0 -0
  297. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_locate.py +0 -0
  298. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_model.py +0 -0
  299. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_normalize.py +0 -0
  300. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_strip.py +0 -0
  301. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
  302. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/prompt/__init__.py +0 -0
  303. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/prompt/test_output_format_renderer.py +0 -0
  304. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_email_document.py +0 -0
  305. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_filesystem_provider.py +0 -0
  306. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_output_format_renderer_nested.py +0 -0
  307. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_output_prompt_conformance.py +0 -0
  308. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_render_conformance.py +0 -0
  309. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_render_max_chars.py +0 -0
  310. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_verify.py +0 -0
  311. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_verify_conformance.py +0 -0
  312. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/__init__.py +0 -0
  313. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5c_merge_attribution.py +0 -0
  314. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5d_reference_resolution.py +0 -0
  315. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5e_database_source_shape.py +0 -0
  316. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_json_path.py +0 -0
  317. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_semantic_diff.py +0 -0
  318. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_source_on_node.py +0 -0
  319. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_yaml_positions.py +0 -0
  320. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/test_api_docs_accuracy.py +0 -0
  321. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/__init__.py +0 -0
  322. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_agent_context_staleness.py +0 -0
  323. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_capabilities.py +0 -0
  324. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_common_attrs.py +0 -0
  325. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_core_types.py +0 -0
  326. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_effective_package.py +0 -0
  327. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_errors.py +0 -0
  328. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_enum.py +0 -0
  329. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_map_validation.py +0 -0
  330. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
  331. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
  332. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_llm_recorder.py +0 -0
  333. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader.py +0 -0
  334. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader_bom.py +0 -0
  335. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader_class.py +0 -0
  336. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_merge.py +0 -0
  337. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_attr.py +0 -0
  338. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_data.py +0 -0
  339. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_source.py +0 -0
  340. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_module_shortcuts.py +0 -0
  341. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_n2m_resolver.py +0 -0
  342. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
  343. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_one_primary_source.py +0 -0
  344. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_parser.py +0 -0
  345. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_parser_yaml.py +0 -0
  346. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_provider.py +0 -0
  347. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_provider_extension.py +0 -0
  348. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry.py +0 -0
  349. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_completeness.py +0 -0
  350. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_extend.py +0 -0
  351. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_sealed.py +0 -0
  352. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_relationship_referential_actions.py +0 -0
  353. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_resolution_key.py +0 -0
  354. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
  355. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_serializer.py +0 -0
  356. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_shared_constants.py +0 -0
  357. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_smoke.py +0 -0
  358. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_sources.py +0 -0
  359. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_strict_child_placement.py +0 -0
  360. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_super_resolve.py +0 -0
  361. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_template_toolcall.py +0 -0
  362. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
  363. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_attr_schema.py +0 -0
  364. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_filter_values.py +0 -0
  365. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_origin_paths.py +0 -0
  366. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_sort_field.py +0 -0
  367. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_warnings.py +0 -0
  368. {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_yaml_desugar.py +0 -0
  369. {metaobjects-0.13.0 → metaobjects-0.14.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaobjects
3
- Version: 0.13.0
3
+ Version: 0.14.0
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.13.0"
3
+ version = "0.14.0"
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"
@@ -103,15 +103,33 @@ def _default_generators() -> list[Generator]:
103
103
  ]
104
104
 
105
105
 
106
- def _load_root(metadata_dir: str) -> tuple[MetaData | None, list[str]]:
107
- """Load metadata; return ``(root, error_messages)``. ``root`` is None on error."""
108
- result = MetaDataLoader.from_directory(metadata_dir)
106
+ def _load_root(
107
+ metadata_dir: str, strict: bool = False
108
+ ) -> tuple[MetaData | None, list[str]]:
109
+ """Load metadata; return ``(root, error_messages)``. ``root`` is None on error.
110
+
111
+ ``strict`` (ADR-0023, #96) — when True, an undeclared own ``@attr`` →
112
+ ``ERR_UNKNOWN_ATTR``. Defaults False so ``gen`` / ``docs`` keep the legacy
113
+ open-attr load; only ``verify`` opts in (strict-by-default with a ``--lax``
114
+ escape).
115
+ """
116
+ result = MetaDataLoader.from_directory(metadata_dir, strict=strict)
109
117
  if result.errors:
110
118
  msgs = [f"{e.code}: {e.message}" for e in result.errors]
111
119
  return None, msgs
112
120
  return result.root, []
113
121
 
114
122
 
123
+ def _strict_load_hint() -> str:
124
+ """Actionable next-steps when strict verify rejects an undeclared @attr."""
125
+ return (
126
+ "verify is strict (ADR-0023): every authored @attr must be declared. Fix: "
127
+ "register the attr on a metadata provider, OR move arbitrary author-supplied "
128
+ "properties into an `attr.properties` bag, OR re-run with `--lax` to keep the "
129
+ "legacy open-attr load."
130
+ )
131
+
132
+
115
133
  def _resolve_generators(names: str) -> tuple[list[Generator], list[str]]:
116
134
  """Resolve a comma-separated list of STABLE generator names via the registry.
117
135
 
@@ -174,6 +192,7 @@ def _generate(
174
192
  generators: list[Generator] | None = None,
175
193
  entity_filter: list[str] | None = None,
176
194
  emit_package_init: bool = True,
195
+ strict: bool = False,
177
196
  ) -> tuple[list[str], list[str]]:
178
197
  """Run the generator suite into ``out_dir``.
179
198
 
@@ -186,9 +205,10 @@ def _generate(
186
205
  output is text/markdown/csv/json/xml/html, never a Python package) so no
187
206
  spurious ``__init__.py`` is scattered through their output tree. Returns
188
207
  ``(written_paths, errors)``. On a load error, ``errors`` is non-empty and no
189
- files are written.
208
+ files are written. ``strict`` (ADR-0023, #96) is threaded to the load — the
209
+ ``verify --codegen`` caller passes True; ``gen`` keeps the default False.
190
210
  """
191
- root, errors = _load_root(metadata_dir)
211
+ root, errors = _load_root(metadata_dir, strict=strict)
192
212
  if root is None:
193
213
  return [], errors
194
214
  return _run_suite(root, out_dir, generators, entity_filter, emit_package_init), []
@@ -408,13 +428,20 @@ def _verify_codegen(args: argparse.Namespace) -> int:
408
428
  # Reuse the exact gen code path — regenerate into a throwaway temp dir. The
409
429
  # --entities filter must match the `gen` that produced --out, or the diff
410
430
  # reports the un-emitted entities as spurious drift.
431
+ # ADR-0023 strict-by-default (#96): verify loads strict unless --lax is given,
432
+ # so an undeclared/typo'd own @attr fails verify (matching Java's Maven goal).
433
+ strict = not getattr(args, "lax", False)
411
434
  with tempfile.TemporaryDirectory() as tmp:
412
435
  entities = _parse_entities(getattr(args, "entities", None))
413
- written, errors = _generate(args.metadata_dir, tmp, None, entities)
436
+ written, errors = _generate(
437
+ args.metadata_dir, tmp, None, entities, strict=strict
438
+ )
414
439
  if errors:
415
440
  print("error: failed to load metadata:", file=sys.stderr)
416
441
  for msg in errors:
417
442
  print(f" {msg}", file=sys.stderr)
443
+ if strict and any("ERR_UNKNOWN_ATTR" in m for m in errors):
444
+ print(_strict_load_hint(), file=sys.stderr)
418
445
  return 1
419
446
 
420
447
  expected = _relative_set(Path(tmp))
@@ -478,11 +505,16 @@ def _verify_templates(args: argparse.Namespace) -> int:
478
505
  )
479
506
  return 2
480
507
 
481
- root, errors = _load_root(args.metadata_dir)
508
+ # ADR-0023 strict-by-default (#96) — same as --codegen: an undeclared @attr
509
+ # fails verify unless --lax is passed.
510
+ strict = not getattr(args, "lax", False)
511
+ root, errors = _load_root(args.metadata_dir, strict=strict)
482
512
  if root is None:
483
513
  print("error: failed to load metadata:", file=sys.stderr)
484
514
  for msg in errors:
485
515
  print(f" {msg}", file=sys.stderr)
516
+ if strict and any("ERR_UNKNOWN_ATTR" in m for m in errors):
517
+ print(_strict_load_hint(), file=sys.stderr)
486
518
  return 1
487
519
 
488
520
  provider = FilesystemProvider(template_root)
@@ -760,6 +792,15 @@ def _build_parser() -> argparse.ArgumentParser:
760
792
  default=None,
761
793
  help="comma-separated entity allowlist for --codegen drift (match `gen --entities`)",
762
794
  )
795
+ verify.add_argument(
796
+ "--lax",
797
+ action="store_true",
798
+ help=(
799
+ "opt out of strict-attr load (ADR-0023, #96): verify is strict by "
800
+ "default — an undeclared/typo'd @attr fails. --lax restores the legacy "
801
+ "open-attr load."
802
+ ),
803
+ )
763
804
  verify.set_defaults(func=_cmd_verify)
764
805
 
765
806
  agent_docs = sub.add_parser(
@@ -5,6 +5,7 @@ from dataclasses import dataclass
5
5
 
6
6
  from metaobjects.meta.core.field.meta_field import MetaField
7
7
  from metaobjects.meta.core.field import field_constants as fc
8
+ from metaobjects.meta.persistence.db import db_constants as dbc
8
9
  from metaobjects.shared.structural import KEY_IS_ARRAY
9
10
 
10
11
 
@@ -94,6 +95,17 @@ def py_type_for(field: MetaField) -> PyType:
94
95
  base = PyType(f"Literal[{members}]", ("from typing import Literal",))
95
96
  else:
96
97
  base = PyType("str")
98
+ elif (
99
+ field.sub_type == fc.FIELD_SUBTYPE_STRING
100
+ and field.attrs().get(dbc.FIELD_ATTR_DB_COLUMN_TYPE) == dbc.DB_COLUMN_TYPE_JSONB
101
+ ):
102
+ # Issue #98 — a field.string carrying @dbColumnType:jsonb is the escape hatch
103
+ # for a genuinely-open JSON column (ADR-0013). pg8000 auto-decodes a jsonb
104
+ # column to a native Python object (dict/list/scalar) at read time, so a `str`
105
+ # annotation is a type lie. Bind ``Any`` — the Python analogue of TS's
106
+ # z.unknown() (#97) — since jsonb can hold any JSON value. (Other ports whose
107
+ # drivers return jsonb as raw text correctly keep their string type.)
108
+ base = PyType("Any", ("from typing import Any",))
97
109
  else:
98
110
  base = _SCALAR.get(field.sub_type, PyType("str"))
99
111
  if field_is_array(field):
@@ -0,0 +1,139 @@
1
+ """Issue #96 — `metaobjects verify` is strict-by-default (ADR-0023 cross-port).
2
+
3
+ Under strict load an authored own ``@attr`` that no provider declares is an
4
+ ``ERR_UNKNOWN_ATTR``. Java's Maven verify goal already forces strict; this brings
5
+ the Python (and TS) CLI verify in line: verify FAILS on an undeclared attr unless
6
+ ``--lax`` is passed. ``gen`` keeps loading lax (only verify defaults strict).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ from metaobjects.cli import main
14
+
15
+ # Shared cross-port fixture (also asserted by the TS CLI verify-strict test):
16
+ # a registered field.string carrying one undeclared own @attr.
17
+ _FIXTURE = (
18
+ Path(__file__).parents[4]
19
+ / "fixtures"
20
+ / "verify-strict-conformance"
21
+ / "unregistered-attr"
22
+ / "input"
23
+ / "meta.users.json"
24
+ )
25
+ _MADE_UP = _FIXTURE.read_text()
26
+
27
+ _CLEAN = """\
28
+ {
29
+ "metadata.root": {
30
+ "package": "acme::users",
31
+ "children": [
32
+ {
33
+ "object.entity": {
34
+ "name": "Account",
35
+ "children": [
36
+ { "field.long": { "name": "id" } },
37
+ { "field.string": { "name": "email", "@description": "the email" } },
38
+ { "identity.primary": { "name": "pk", "@fields": ["id"] } }
39
+ ]
40
+ }
41
+ }
42
+ ]
43
+ }
44
+ }
45
+ """
46
+
47
+
48
+ def _meta_dir(tmp_path: Path, body: str) -> str:
49
+ d = tmp_path / "meta"
50
+ d.mkdir()
51
+ (d / "meta.json").write_text(body)
52
+ return str(d)
53
+
54
+
55
+ # --- verify --codegen ------------------------------------------------------
56
+
57
+
58
+ def test_verify_codegen_fails_on_undeclared_attr_by_default(
59
+ tmp_path: Path, capsys
60
+ ) -> None:
61
+ meta_dir = _meta_dir(tmp_path, _MADE_UP)
62
+ out = tmp_path / "out"
63
+ rc = main(["verify", "--codegen", meta_dir, "--out", str(out)])
64
+ assert rc != 0
65
+ err = capsys.readouterr().err
66
+ assert "ERR_UNKNOWN_ATTR" in err
67
+ # Actionable hint: register a provider / attr.properties bag / --lax.
68
+ assert "--lax" in err
69
+ assert "attr.properties" in err
70
+
71
+
72
+ def test_verify_codegen_passes_with_lax(tmp_path: Path) -> None:
73
+ meta_dir = _meta_dir(tmp_path, _MADE_UP)
74
+ out = tmp_path / "out"
75
+ # Lax load tolerates the undeclared attr; gen-to-temp + diff (vs --out) is
76
+ # the codegen drift result, NOT a load failure. First gen (already lax),
77
+ # then verify --lax against the committed output.
78
+ assert main(["gen", meta_dir, "--out", str(out)]) == 0
79
+ assert main(["verify", "--lax", "--codegen", meta_dir, "--out", str(out)]) == 0
80
+
81
+
82
+ # --- verify --templates ----------------------------------------------------
83
+
84
+ _TEMPLATE_META = """\
85
+ {
86
+ "metadata.root": {
87
+ "package": "acme::ai",
88
+ "children": [
89
+ {
90
+ "object.value": {
91
+ "name": "Welcome",
92
+ "children": [
93
+ { "field.string": { "name": "name", "@madeUpAttr": "nope" } }
94
+ ]
95
+ }
96
+ },
97
+ {
98
+ "template.output": {
99
+ "name": "WelcomePage",
100
+ "@kind": "document",
101
+ "@payloadRef": "Welcome",
102
+ "@textRef": "pages/welcome",
103
+ "@format": "html"
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ }
109
+ """
110
+
111
+
112
+ def test_verify_templates_fails_on_undeclared_attr_by_default(
113
+ tmp_path: Path, capsys
114
+ ) -> None:
115
+ meta_dir = _meta_dir(tmp_path, _TEMPLATE_META)
116
+ troot = tmp_path / "templates"
117
+ (troot / "pages").mkdir(parents=True)
118
+ (troot / "pages" / "welcome.mustache").write_text("Hello {{name}}")
119
+ rc = main(["verify", "--templates", meta_dir, "--templates-root", str(troot)])
120
+ assert rc != 0
121
+ err = capsys.readouterr().err
122
+ assert "ERR_UNKNOWN_ATTR" in err
123
+
124
+
125
+ # --- gen stays lax (only verify defaults strict) ---------------------------
126
+
127
+
128
+ def test_gen_stays_lax_by_default(tmp_path: Path) -> None:
129
+ meta_dir = _meta_dir(tmp_path, _MADE_UP)
130
+ out = tmp_path / "out"
131
+ # gen tolerates the undeclared attr (no strict default for gen).
132
+ assert main(["gen", meta_dir, "--out", str(out)]) == 0
133
+
134
+
135
+ def test_verify_clean_metadata_passes_under_strict(tmp_path: Path) -> None:
136
+ meta_dir = _meta_dir(tmp_path, _CLEAN)
137
+ out = tmp_path / "out"
138
+ assert main(["gen", meta_dir, "--out", str(out)]) == 0
139
+ assert main(["verify", "--codegen", meta_dir, "--out", str(out)]) == 0
@@ -175,3 +175,17 @@ def test_field_default_is_emitted_as_literal() -> None:
175
175
  assert "n: int = 1" in out
176
176
  assert "c: float = 0.5" in out
177
177
  assert "s: str = 'approx'" in out
178
+
179
+
180
+ def test_dbcolumntype_jsonb_field_is_any_with_import() -> None:
181
+ """Issue #98 — a field.string @dbColumnType:jsonb stores genuinely-open JSON,
182
+ which pg8000 decodes to a native Python object at read time. The generated
183
+ Pydantic field is typed ``Any`` (not the lying ``str``) and the module threads
184
+ ``from typing import Any`` into its import block."""
185
+ from metaobjects.meta.persistence.db import db_constants as dbc
186
+
187
+ payload = MetaField(TYPE_FIELD, fc.FIELD_SUBTYPE_STRING, "payload")
188
+ payload.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_JSONB)
189
+ out = render_entity_model(_entity("Event", [payload], package="myapp::events"))
190
+ assert "from typing import Any" in out
191
+ assert "payload: Any | None = None" in out
@@ -102,3 +102,17 @@ def test_dbcolumntype_uuid_string_stays_str() -> None:
102
102
  f = _field(fc.FIELD_SUBTYPE_STRING)
103
103
  f.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_UUID)
104
104
  assert py_type_for(f).expr == "str"
105
+
106
+
107
+ def test_dbcolumntype_jsonb_string_becomes_any() -> None:
108
+ # Issue #98 — a field.string carrying @dbColumnType:jsonb stores genuinely-open
109
+ # JSON; pg8000 auto-decodes a jsonb column to a native Python object (dict/list/
110
+ # scalar), so `str` is a type lie. Emit `Any` (the Python analogue of TS's
111
+ # z.unknown(), #97), threading `from typing import Any` into the module imports.
112
+ from metaobjects.meta.persistence.db import db_constants as dbc
113
+
114
+ f = _field(fc.FIELD_SUBTYPE_STRING)
115
+ f.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_JSONB)
116
+ t = py_type_for(f)
117
+ assert t.expr == "Any"
118
+ assert "from typing import Any" in t.imports
@@ -0,0 +1,160 @@
1
+ """Hand-rolled reference FastAPI app for the ``Document`` entity from the
2
+ ``api-contract-conformance/jsonb/`` corpus.
3
+
4
+ The contract under test is the ``field.string @dbColumnType:jsonb`` open bag:
5
+ a posted JSON object must round-trip as an object (never a JSON-encoded
6
+ string). Pared down from ``api_contract_server.py`` (the Author reference) —
7
+ the jsonb scenario only POSTs + GETs by id, so this server implements just
8
+ those verbs.
9
+
10
+ Backend: pg8000 (pure-python DB-API; same driver as ``api_contract_server.py``).
11
+ ``payload`` is a bare ``JSONB`` column — the open bag holds any JSON value.
12
+ pg8000 auto-decodes jsonb to a native Python object on read; on write the
13
+ value is ``json.dumps``-serialized and bound with a ``::jsonb`` cast.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from contextlib import closing
19
+ from typing import Any
20
+
21
+ import pg8000.dbapi as pg8000
22
+ from fastapi import FastAPI, Request, status
23
+ from fastapi.responses import JSONResponse
24
+
25
+ from .postgres_container import PostgresInfo
26
+
27
+
28
+ class DocumentRepository:
29
+ """Direct-JDBC-equivalent repo against the test Postgres instance."""
30
+
31
+ def __init__(self, info: PostgresInfo) -> None:
32
+ self._info = info
33
+
34
+ def create_schema(self) -> None:
35
+ self._exec(
36
+ 'CREATE TABLE IF NOT EXISTS "documents" ('
37
+ " id BIGSERIAL PRIMARY KEY,"
38
+ " title VARCHAR(200) NOT NULL,"
39
+ " payload JSONB"
40
+ ")"
41
+ )
42
+
43
+ def truncate(self) -> None:
44
+ self._exec('TRUNCATE TABLE "documents" RESTART IDENTITY')
45
+
46
+ def apply_seed(self, rows: list[dict[str, Any]]) -> None:
47
+ """Insert seed rows with explicit ids, then bump the sequence so the
48
+ next implicit-id POST lands at max(id) + 1 (the GET-after-POST
49
+ contract relies on a deterministic id)."""
50
+ self.truncate()
51
+ with closing(self._connect()) as conn:
52
+ cur = conn.cursor()
53
+ try:
54
+ for r in rows:
55
+ cur.execute(
56
+ 'INSERT INTO "documents" (id, title, payload) '
57
+ "VALUES (%s, %s, %s::jsonb)",
58
+ (int(r["id"]), r["title"], _dump_payload(r.get("payload"))),
59
+ )
60
+ cur.execute(
61
+ "SELECT setval(pg_get_serial_sequence('documents', 'id'), "
62
+ "COALESCE((SELECT MAX(id) FROM documents), 1))"
63
+ )
64
+ conn.commit()
65
+ finally:
66
+ cur.close()
67
+
68
+ def find_by_id(self, ident: int) -> dict[str, Any] | None:
69
+ with closing(self._connect()) as conn:
70
+ cur = conn.cursor()
71
+ try:
72
+ cur.execute(
73
+ 'SELECT id, title, payload FROM "documents" WHERE id = %s',
74
+ (int(ident),),
75
+ )
76
+ row = cur.fetchone()
77
+ return _row_to_dict(row) if row is not None else None
78
+ finally:
79
+ cur.close()
80
+
81
+ def create(self, dto: dict[str, Any]) -> dict[str, Any]:
82
+ with closing(self._connect()) as conn:
83
+ cur = conn.cursor()
84
+ try:
85
+ cur.execute(
86
+ 'INSERT INTO "documents" (title, payload) '
87
+ "VALUES (%s, %s::jsonb) RETURNING id",
88
+ (dto.get("title"), _dump_payload(dto.get("payload"))),
89
+ )
90
+ new_id = int(cur.fetchone()[0])
91
+ conn.commit()
92
+ finally:
93
+ cur.close()
94
+ created = self.find_by_id(new_id)
95
+ if created is None:
96
+ raise RuntimeError("create: row vanished between INSERT and SELECT")
97
+ return created
98
+
99
+ def _connect(self) -> Any:
100
+ return pg8000.connect(
101
+ host=self._info.host,
102
+ port=self._info.port,
103
+ user=self._info.user,
104
+ password=self._info.password,
105
+ database=self._info.database,
106
+ )
107
+
108
+ def _exec(self, sql: str) -> None:
109
+ with closing(self._connect()) as conn:
110
+ cur = conn.cursor()
111
+ try:
112
+ cur.execute(sql)
113
+ conn.commit()
114
+ finally:
115
+ cur.close()
116
+
117
+
118
+ def make_jsonb_app(repo: DocumentRepository) -> FastAPI:
119
+ app = FastAPI()
120
+
121
+ @app.exception_handler(404)
122
+ async def _default_404(_req: Request, _exc: Any) -> JSONResponse:
123
+ return JSONResponse(status_code=404, content={"error": "not_found"})
124
+
125
+ @app.get("/api/documents/{document_id}")
126
+ def get_document(document_id: int) -> Any:
127
+ row = repo.find_by_id(document_id)
128
+ if row is None:
129
+ return JSONResponse(status_code=404, content={"error": "not_found"})
130
+ return row
131
+
132
+ @app.post("/api/documents", status_code=status.HTTP_201_CREATED)
133
+ def create_document(dto: dict[str, Any]) -> Any:
134
+ return repo.create(dto)
135
+
136
+ return app
137
+
138
+
139
+ def _dump_payload(value: Any) -> Any:
140
+ """jsonb bind: a dict/list is ``json.dumps``-serialized for the ``::jsonb``
141
+ cast; ``None`` stays ``None`` (SQL NULL); a pre-serialized string passes
142
+ through."""
143
+ if value is None:
144
+ return None
145
+ if isinstance(value, str):
146
+ return value
147
+ return json.dumps(value)
148
+
149
+
150
+ def _row_to_dict(row: Any) -> dict[str, Any]:
151
+ ident, title, payload = row
152
+ # pg8000 auto-decodes jsonb → native Python object; if a driver returned raw
153
+ # text we'd parse it, but the open-bag contract is "parsed value", so a str
154
+ # that is JSON is decoded here defensively.
155
+ if isinstance(payload, str):
156
+ try:
157
+ payload = json.loads(payload)
158
+ except json.JSONDecodeError:
159
+ pass
160
+ return {"id": int(ident), "title": title, "payload": payload}
@@ -0,0 +1,135 @@
1
+ """Boot the GENERATED FastAPI router for the ``Document`` entity over HTTP.
2
+
3
+ Peer to ``generated_router_app.py`` (the Author generated lane), for the
4
+ ``api-contract-conformance/jsonb/`` corpus. Runs the REAL ``render_router``
5
+ output for ``Document`` (whose ``payload`` field is ``field.string
6
+ @dbColumnType:jsonb`` → a Pydantic ``Any`` per #98), imports the emitted module
7
+ UNMODIFIED, and mounts it on a FastAPI app with a minimal in-memory repo behind
8
+ the generated consumer seam. The generated router is the artifact under test:
9
+ its DTO must accept a posted object and surface it back as an object.
10
+
11
+ No Testcontainers — the controller is the artifact, its persistence seam is
12
+ in-memory. Real DB behavior stays owned by persistence-conformance.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import importlib.util
17
+ import shutil
18
+ import sys
19
+ import tempfile
20
+ import uuid
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ from fastapi import FastAPI
25
+
26
+ from metaobjects import MetaDataLoader
27
+ from metaobjects.codegen.generators.filter_allowlist_generator import render_filter_allowlist
28
+ from metaobjects.codegen.generators.router_generator import render_router
29
+ from metaobjects.meta.core.object.meta_object import MetaObject
30
+ from metaobjects.shared.base_types import TYPE_OBJECT
31
+
32
+
33
+ def _find_document_entity(meta_json: Path) -> MetaObject:
34
+ tmp = Path(tempfile.mkdtemp(prefix="apic-jsonb-meta-"))
35
+ shutil.copy(meta_json, tmp / "meta.json")
36
+ result = MetaDataLoader.from_directory(str(tmp))
37
+ if result.errors:
38
+ msgs = "; ".join(f"{e.code}: {e.message}" for e in result.errors)
39
+ raise RuntimeError(f"corpus meta.json failed to load: {msgs}")
40
+ objects = [
41
+ c for c in result.root.children()
42
+ if c.type == TYPE_OBJECT and isinstance(c, MetaObject)
43
+ ]
44
+ for obj in objects:
45
+ if obj.name == "Document" or obj.name.endswith("::Document"):
46
+ return obj
47
+ raise RuntimeError(f"Document entity not found among {[o.name for o in objects]}")
48
+
49
+
50
+ def build_generated_jsonb_app(corpus_root: Path) -> tuple[FastAPI, "InMemoryDocumentRepository"]:
51
+ """Generate the Document router, import it, mount it, and wire the seam."""
52
+ document = _find_document_entity(corpus_root / "meta.json")
53
+
54
+ router_src = render_router(document)
55
+ allowlist_src = render_filter_allowlist(document)
56
+ if router_src is None or allowlist_src is None:
57
+ raise RuntimeError("router_generator / filter_allowlist_generator returned None for Document")
58
+
59
+ pkg_name = f"genjsonb_{uuid.uuid4().hex[:8]}"
60
+ tmp = Path(tempfile.mkdtemp(prefix="apic-jsonb-gen-"))
61
+ pkg_dir = tmp / pkg_name
62
+ pkg_dir.mkdir()
63
+ (pkg_dir / "__init__.py").write_text("")
64
+ (pkg_dir / "document_filter_allowlist.py").write_text(allowlist_src)
65
+ (pkg_dir / "document_router.py").write_text(router_src)
66
+
67
+ sys.path.insert(0, str(tmp))
68
+ pkg_spec = importlib.util.spec_from_file_location(
69
+ pkg_name, pkg_dir / "__init__.py", submodule_search_locations=[str(pkg_dir)]
70
+ )
71
+ pkg_mod = importlib.util.module_from_spec(pkg_spec)
72
+ sys.modules[pkg_name] = pkg_mod
73
+ pkg_spec.loader.exec_module(pkg_mod)
74
+ spec = importlib.util.spec_from_file_location(
75
+ f"{pkg_name}.document_router", pkg_dir / "document_router.py"
76
+ )
77
+ router_mod = importlib.util.module_from_spec(spec)
78
+ sys.modules[f"{pkg_name}.document_router"] = router_mod
79
+ spec.loader.exec_module(router_mod)
80
+
81
+ repo = InMemoryDocumentRepository()
82
+ app = FastAPI()
83
+ app.include_router(router_mod.router)
84
+ app.dependency_overrides[router_mod.get_repository] = lambda: repo
85
+ return app, repo
86
+
87
+
88
+ class InMemoryDocumentRepository:
89
+ """In-memory impl of the GENERATED ``DocumentRepository`` Protocol (test seam).
90
+
91
+ The jsonb scenario only POSTs + GETs by id, but the generated router's
92
+ Protocol declares the full CRUD surface, so all verbs are implemented (the
93
+ payload open bag is stored/echoed verbatim — never serialized to a string).
94
+ """
95
+
96
+ def __init__(self) -> None:
97
+ self._rows: list[dict[str, Any]] = []
98
+
99
+ def reset(self) -> None:
100
+ self._rows = []
101
+
102
+ def seed(self, rows: list[dict[str, Any]]) -> None:
103
+ self._rows = [dict(r) for r in rows]
104
+
105
+ def list(self, limit: int, offset: int, sort: Any, filters: list[Any]) -> list[Any]:
106
+ rows = sorted(self._rows, key=lambda r: r["id"])
107
+ return [dict(r) for r in rows[offset : offset + limit]]
108
+
109
+ def count(self, filters: list[Any]) -> int:
110
+ return len(self._rows)
111
+
112
+ def find_by_id(self, id: int) -> Any | None:
113
+ for r in self._rows:
114
+ if r["id"] == id:
115
+ return dict(r)
116
+ return None
117
+
118
+ def create(self, dto: Any) -> Any:
119
+ row = dict(dto)
120
+ if row.get("id") is None:
121
+ row["id"] = (max((r["id"] for r in self._rows), default=0)) + 1
122
+ self._rows.append(dict(row))
123
+ return row
124
+
125
+ def update(self, id: int, dto: Any) -> Any | None:
126
+ for r in self._rows:
127
+ if r["id"] == id:
128
+ r.update({k: v for k, v in dict(dto).items() if k != "id"})
129
+ return dict(r)
130
+ return None
131
+
132
+ def delete(self, id: int) -> bool:
133
+ before = len(self._rows)
134
+ self._rows = [r for r in self._rows if r["id"] != id]
135
+ return len(self._rows) != before