metaobjects 0.9.0__py3-none-any.whl

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 (181) hide show
  1. metaobjects/__init__.py +75 -0
  2. metaobjects/agent_context/__init__.py +55 -0
  3. metaobjects/agent_context/_content/README.md +14 -0
  4. metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
  5. metaobjects/agent_context/_content/servers/java.meta.json +5 -0
  6. metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
  7. metaobjects/agent_context/_content/servers/python.meta.json +5 -0
  8. metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
  9. metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
  10. metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
  11. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
  12. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
  13. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
  14. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
  15. metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
  16. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
  17. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
  18. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
  19. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
  20. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
  21. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
  22. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
  23. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
  24. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
  25. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
  26. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
  27. metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
  28. metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
  29. metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
  30. metaobjects/agent_context/assemble.py +133 -0
  31. metaobjects/agent_context/content_root.py +54 -0
  32. metaobjects/agent_context/scaffold.py +191 -0
  33. metaobjects/agent_context/types.py +44 -0
  34. metaobjects/attr_class_map.py +23 -0
  35. metaobjects/cli.py +696 -0
  36. metaobjects/codegen/__init__.py +0 -0
  37. metaobjects/codegen/config.py +11 -0
  38. metaobjects/codegen/constants.py +13 -0
  39. metaobjects/codegen/extract_delegate_emitter.py +384 -0
  40. metaobjects/codegen/extract_schema_emitter.py +139 -0
  41. metaobjects/codegen/format.py +31 -0
  42. metaobjects/codegen/fr010_field_mapping.py +220 -0
  43. metaobjects/codegen/generator.py +62 -0
  44. metaobjects/codegen/generator_registry.py +163 -0
  45. metaobjects/codegen/generators/__init__.py +0 -0
  46. metaobjects/codegen/generators/entity_model.py +263 -0
  47. metaobjects/codegen/generators/extractor_generator.py +317 -0
  48. metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
  49. metaobjects/codegen/generators/m2m_codegen.py +192 -0
  50. metaobjects/codegen/generators/output_parser_generator.py +272 -0
  51. metaobjects/codegen/generators/output_prompt_generator.py +192 -0
  52. metaobjects/codegen/generators/payload_vo_generator.py +672 -0
  53. metaobjects/codegen/generators/render_helper_generator.py +451 -0
  54. metaobjects/codegen/generators/router_generator.py +635 -0
  55. metaobjects/codegen/generators/template_generator.py +70 -0
  56. metaobjects/codegen/generators/tph_plan.py +120 -0
  57. metaobjects/codegen/generators/trace_helper_generator.py +336 -0
  58. metaobjects/codegen/instance_artifacts.py +15 -0
  59. metaobjects/codegen/output_format_spec_emitter.py +79 -0
  60. metaobjects/codegen/overwrite_policy.py +27 -0
  61. metaobjects/codegen/runner.py +110 -0
  62. metaobjects/codegen/runtime/__init__.py +6 -0
  63. metaobjects/codegen/runtime/filter_parser.py +193 -0
  64. metaobjects/codegen/type_map.py +84 -0
  65. metaobjects/core_types.py +809 -0
  66. metaobjects/datatype.py +19 -0
  67. metaobjects/documentation/__init__.py +28 -0
  68. metaobjects/documentation/doc_constants.py +20 -0
  69. metaobjects/documentation/doc_provider.py +20 -0
  70. metaobjects/documentation/doc_schema.py +24 -0
  71. metaobjects/errors.py +124 -0
  72. metaobjects/loader/__init__.py +0 -0
  73. metaobjects/loader/merge.py +287 -0
  74. metaobjects/loader/meta_data_loader.py +245 -0
  75. metaobjects/loader/sources/__init__.py +24 -0
  76. metaobjects/loader/sources/directory_source.py +50 -0
  77. metaobjects/loader/sources/file_source.py +41 -0
  78. metaobjects/loader/sources/meta_data_source.py +67 -0
  79. metaobjects/loader/sources/uri_source.py +56 -0
  80. metaobjects/loader/validate_discriminator.py +181 -0
  81. metaobjects/loader/validate_field_readonly.py +146 -0
  82. metaobjects/loader/validate_source_parameter_ref.py +159 -0
  83. metaobjects/loader/validate_source_physical_names.py +140 -0
  84. metaobjects/loader/validation_passes.py +1513 -0
  85. metaobjects/meta/__init__.py +1 -0
  86. metaobjects/meta/core/__init__.py +0 -0
  87. metaobjects/meta/core/attr/__init__.py +0 -0
  88. metaobjects/meta/core/attr/attr_constants.py +31 -0
  89. metaobjects/meta/core/attr/meta_attr.py +136 -0
  90. metaobjects/meta/core/field/__init__.py +0 -0
  91. metaobjects/meta/core/field/field_constants.py +105 -0
  92. metaobjects/meta/core/field/meta_field.py +76 -0
  93. metaobjects/meta/core/identity/__init__.py +0 -0
  94. metaobjects/meta/core/identity/identity_constants.py +19 -0
  95. metaobjects/meta/core/identity/meta_identity.py +8 -0
  96. metaobjects/meta/core/object/__init__.py +0 -0
  97. metaobjects/meta/core/object/meta_object.py +65 -0
  98. metaobjects/meta/core/object/meta_object_aware.py +43 -0
  99. metaobjects/meta/core/object/object_class_registry.py +56 -0
  100. metaobjects/meta/core/object/object_constants.py +13 -0
  101. metaobjects/meta/core/object/object_extract.py +400 -0
  102. metaobjects/meta/core/object/value_object.py +70 -0
  103. metaobjects/meta/core/relationship/__init__.py +0 -0
  104. metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
  105. metaobjects/meta/core/relationship/meta_relationship.py +54 -0
  106. metaobjects/meta/core/relationship/relationship_constants.py +51 -0
  107. metaobjects/meta/core/validator/__init__.py +0 -0
  108. metaobjects/meta/core/validator/validator_constants.py +18 -0
  109. metaobjects/meta/meta_data.py +206 -0
  110. metaobjects/meta/meta_root.py +8 -0
  111. metaobjects/meta/persistence/__init__.py +0 -0
  112. metaobjects/meta/persistence/db/__init__.py +1 -0
  113. metaobjects/meta/persistence/db/db_constants.py +41 -0
  114. metaobjects/meta/persistence/db/db_provider.py +60 -0
  115. metaobjects/meta/persistence/origin/__init__.py +0 -0
  116. metaobjects/meta/persistence/origin/meta_origin.py +8 -0
  117. metaobjects/meta/persistence/origin/origin_constants.py +20 -0
  118. metaobjects/meta/persistence/source/__init__.py +0 -0
  119. metaobjects/meta/persistence/source/meta_source.py +137 -0
  120. metaobjects/meta/persistence/source/source_constants.py +115 -0
  121. metaobjects/meta/presentation/__init__.py +0 -0
  122. metaobjects/meta/presentation/layout/__init__.py +0 -0
  123. metaobjects/meta/presentation/layout/layout_constants.py +13 -0
  124. metaobjects/meta/presentation/layout/meta_layout.py +8 -0
  125. metaobjects/meta/presentation/view/__init__.py +0 -0
  126. metaobjects/meta/presentation/view/meta_view.py +8 -0
  127. metaobjects/meta/presentation/view/view_constants.py +22 -0
  128. metaobjects/meta/template/__init__.py +0 -0
  129. metaobjects/meta/template/meta_template.py +46 -0
  130. metaobjects/meta/template/template_constants.py +112 -0
  131. metaobjects/meta/template/template_provider.py +43 -0
  132. metaobjects/parser.py +380 -0
  133. metaobjects/parser_yaml.py +82 -0
  134. metaobjects/provider.py +111 -0
  135. metaobjects/py.typed +0 -0
  136. metaobjects/registry.py +210 -0
  137. metaobjects/registry_manifest.py +223 -0
  138. metaobjects/render/__init__.py +74 -0
  139. metaobjects/render/email_document.py +14 -0
  140. metaobjects/render/escapers.py +109 -0
  141. metaobjects/render/extract/__init__.py +59 -0
  142. metaobjects/render/extract/coerce.py +279 -0
  143. metaobjects/render/extract/extract.py +211 -0
  144. metaobjects/render/extract/extract_map.py +61 -0
  145. metaobjects/render/extract/json_forgiving_reader.py +203 -0
  146. metaobjects/render/extract/locate.py +65 -0
  147. metaobjects/render/extract/normalize.py +96 -0
  148. metaobjects/render/extract/strip.py +20 -0
  149. metaobjects/render/extract/types.py +332 -0
  150. metaobjects/render/extract/xml_forgiving_reader.py +162 -0
  151. metaobjects/render/filesystem_provider.py +51 -0
  152. metaobjects/render/prompt/__init__.py +32 -0
  153. metaobjects/render/prompt/output_format_renderer.py +340 -0
  154. metaobjects/render/prompt/output_format_spec.py +28 -0
  155. metaobjects/render/prompt/prompt_field.py +29 -0
  156. metaobjects/render/prompt/prompt_overrides.py +29 -0
  157. metaobjects/render/prompt/prompt_style.py +38 -0
  158. metaobjects/render/renderer.py +358 -0
  159. metaobjects/render/verify.py +266 -0
  160. metaobjects/runtime/__init__.py +39 -0
  161. metaobjects/runtime/llm_recorder.py +210 -0
  162. metaobjects/runtime/n2m_resolver.py +155 -0
  163. metaobjects/runtime/object_manager.py +715 -0
  164. metaobjects/runtime/tph.py +50 -0
  165. metaobjects/serializer_json.py +172 -0
  166. metaobjects/shared/__init__.py +0 -0
  167. metaobjects/shared/base_types.py +16 -0
  168. metaobjects/shared/separators.py +4 -0
  169. metaobjects/shared/structural.py +9 -0
  170. metaobjects/source/__init__.py +79 -0
  171. metaobjects/source/error_source.py +266 -0
  172. metaobjects/source/json_path.py +106 -0
  173. metaobjects/source/semantic_diff.py +98 -0
  174. metaobjects/source/yaml_positions.py +174 -0
  175. metaobjects/super_resolve.py +128 -0
  176. metaobjects/yaml_desugar.py +481 -0
  177. metaobjects-0.9.0.dist-info/METADATA +97 -0
  178. metaobjects-0.9.0.dist-info/RECORD +181 -0
  179. metaobjects-0.9.0.dist-info/WHEEL +4 -0
  180. metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
  181. metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
@@ -0,0 +1,146 @@
1
+ """FR-013 — field-level ``@readOnly`` cross-attribute rules.
2
+
3
+ Codes:
4
+ * ``ERR_READONLY_ASSIGNED_PRIMARY`` — ``@readOnly: true`` on a field that is
5
+ the target of an ``identity.primary`` with ``@generation: "assigned"``.
6
+ The application has no path to populate the identity value.
7
+ * ``ERR_READONLY_DOWNGRADE`` — a concrete subtype declares ``@readOnly:
8
+ false`` on a field whose extends-chain parent declares ``@readOnly: true``.
9
+ Read-only-ness can only be upgraded, never downgraded.
10
+ * ``WARN_READONLY_VALUE_OBJECT`` — ``@readOnly: true`` on a field child of an
11
+ ``object.value``. No persistence semantics apply; advisory only.
12
+
13
+ Mirrors the TS reference
14
+ ``packages/metadata/src/core/field/validate-field-readonly.ts``.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from ..errors import ErrorCode, MetaError
19
+ from ..meta.meta_data import MetaData
20
+ from ..meta.core.field.field_constants import FIELD_ATTR_READ_ONLY
21
+ from ..meta.core.identity.identity_constants import (
22
+ GENERATION_ASSIGNED,
23
+ IDENTITY_ATTR_FIELDS,
24
+ IDENTITY_ATTR_GENERATION,
25
+ IDENTITY_SUBTYPE_PRIMARY,
26
+ )
27
+ from ..meta.core.object.object_constants import OBJECT_SUBTYPE_VALUE
28
+ from ..shared.base_types import TYPE_FIELD, TYPE_IDENTITY, TYPE_OBJECT
29
+ from ..source.error_source import LoaderWarning
30
+
31
+ WARN_READONLY_VALUE_OBJECT = "WARN_READONLY_VALUE_OBJECT"
32
+
33
+
34
+ def _read_only_flag(field: MetaData) -> bool | None:
35
+ """The explicit ``@readOnly`` value (True/False) or None when absent."""
36
+ v = field.attr(FIELD_ATTR_READ_ONLY)
37
+ return v if isinstance(v, bool) else None
38
+
39
+
40
+ def _inherited_field(obj: MetaData, name: str) -> MetaData | None:
41
+ """Walk the extends chain for a field with ``name``; return its declaring
42
+ node (own attrs intact) if found."""
43
+ cursor = obj.super_data
44
+ while cursor is not None:
45
+ for c in cursor.own_children():
46
+ if c.type == TYPE_FIELD and c.name == name:
47
+ return c
48
+ cursor = cursor.super_data
49
+ return None
50
+
51
+
52
+ def _primary_assigned_field_names(obj: MetaData) -> set[str]:
53
+ """Names of fields participating in any ``identity.primary`` with
54
+ ``@generation: "assigned"`` on ``obj`` or its extends chain (effective)."""
55
+ out: set[str] = set()
56
+ for ident in obj.children():
57
+ if ident.type != TYPE_IDENTITY:
58
+ continue
59
+ if ident.sub_type != IDENTITY_SUBTYPE_PRIMARY:
60
+ continue
61
+ if ident.attr(IDENTITY_ATTR_GENERATION) != GENERATION_ASSIGNED:
62
+ continue
63
+ fields = ident.attr(IDENTITY_ATTR_FIELDS)
64
+ if isinstance(fields, (list, tuple)):
65
+ for f_name in fields:
66
+ if isinstance(f_name, str):
67
+ out.add(f_name)
68
+ elif isinstance(fields, str):
69
+ out.add(fields)
70
+ return out
71
+
72
+
73
+ def validate_field_readonly(
74
+ root: MetaData,
75
+ errors: list[MetaError],
76
+ envelope_warnings: list[LoaderWarning] | None = None,
77
+ legacy_warnings: list[str] | None = None,
78
+ ) -> None:
79
+ for obj in root.own_children():
80
+ if obj.type != TYPE_OBJECT:
81
+ continue
82
+ is_value_object = obj.sub_type == OBJECT_SUBTYPE_VALUE
83
+
84
+ # 1) WARN_READONLY_VALUE_OBJECT — any @readOnly field child of object.value.
85
+ if is_value_object:
86
+ for child in obj.own_children():
87
+ if child.type == TYPE_FIELD and _read_only_flag(child) is True:
88
+ msg = (
89
+ f'field "{child.name}" on object.value "{obj.name}" '
90
+ "declares @readOnly: true; value-objects have no "
91
+ "persistence semantics so the read-only contract is "
92
+ "advisory (codegen may use it for record/struct treatment)."
93
+ )
94
+ if envelope_warnings is not None:
95
+ envelope_warnings.append(
96
+ LoaderWarning(
97
+ code=WARN_READONLY_VALUE_OBJECT,
98
+ message=msg,
99
+ source=child.source,
100
+ )
101
+ )
102
+ if legacy_warnings is not None:
103
+ legacy_warnings.append(WARN_READONLY_VALUE_OBJECT)
104
+
105
+ # 2) ERR_READONLY_DOWNGRADE — read-only-ness can only be upgraded across
106
+ # extends. Only the explicit own @readOnly: false case matters.
107
+ for own_field in obj.own_children():
108
+ if own_field.type != TYPE_FIELD:
109
+ continue
110
+ if _read_only_flag(own_field) is not False:
111
+ continue
112
+ inherited = _inherited_field(obj, own_field.name)
113
+ if inherited is not None and _read_only_flag(inherited) is True:
114
+ errors.append(
115
+ MetaError(
116
+ f'field "{own_field.name}" on "{obj.name}" sets @readOnly: '
117
+ "false, but the extends-chain parent declares @readOnly: "
118
+ "true. Read-only-ness can only be upgraded, not downgraded "
119
+ "(FR-013).",
120
+ ErrorCode.ERR_READONLY_DOWNGRADE,
121
+ envelope=own_field.source,
122
+ )
123
+ )
124
+
125
+ # 3) ERR_READONLY_ASSIGNED_PRIMARY — @readOnly: true on a field used in an
126
+ # identity.primary whose @generation is "assigned" (effective tree).
127
+ if not is_value_object:
128
+ assigned = _primary_assigned_field_names(obj)
129
+ if assigned:
130
+ for field in obj.children():
131
+ if field.type != TYPE_FIELD:
132
+ continue
133
+ if field.name not in assigned:
134
+ continue
135
+ if _read_only_flag(field) is not True:
136
+ continue
137
+ errors.append(
138
+ MetaError(
139
+ f'field "{field.name}" on "{obj.name}" is @readOnly: '
140
+ "true AND the target of identity.primary with "
141
+ '@generation: "assigned"; the application has no path '
142
+ "to populate the identity value (FR-013).",
143
+ ErrorCode.ERR_READONLY_ASSIGNED_PRIMARY,
144
+ envelope=field.source,
145
+ )
146
+ )
@@ -0,0 +1,159 @@
1
+ """FR-015 — ``source.rdb`` ``@parameterRef`` typed-input rules.
2
+
3
+ Codes:
4
+ * ``ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND`` — ``@parameterRef`` set with a
5
+ non-callable ``@kind`` (only ``storedProc`` / ``tableFunction`` accept
6
+ parameters). Checked before resolution so authoring mistakes surface first.
7
+ * ``ERR_PARAMETER_REF_UNRESOLVED`` — ``@parameterRef`` names a non-existent
8
+ object.
9
+ * ``ERR_PARAMETER_REF_NOT_VALUE_OBJECT`` — ``@parameterRef`` points at an
10
+ object that is not an ``object.value``.
11
+ * ``ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH`` — a parameter field uses
12
+ ``origin.passthrough @from: "Entity.field"`` but its subtype does not match
13
+ the referenced field's subtype.
14
+
15
+ Mirrors the TS reference
16
+ ``packages/metadata/src/persistence/source/validate-source-parameter-ref.ts``.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from ..errors import ErrorCode, MetaError
21
+ from ..meta.meta_data import MetaData
22
+ from ..meta.persistence.source.meta_source import MetaSource
23
+ from ..meta.persistence.source.source_constants import (
24
+ SOURCE_ATTR_PARAMETER_REF,
25
+ SOURCE_KIND_STORED_PROC,
26
+ SOURCE_KIND_TABLE_FUNCTION,
27
+ SOURCE_SUBTYPE_RDB,
28
+ )
29
+ from ..meta.core.object.object_constants import (
30
+ OBJECT_SUBTYPE_ENTITY,
31
+ OBJECT_SUBTYPE_VALUE,
32
+ )
33
+ from ..meta.persistence.origin.origin_constants import (
34
+ ORIGIN_ATTR_FROM,
35
+ ORIGIN_SUBTYPE_PASSTHROUGH,
36
+ )
37
+ from ..shared.base_types import TYPE_FIELD, TYPE_OBJECT, TYPE_ORIGIN, TYPE_SOURCE
38
+ from ..shared.separators import PACKAGE_SEP
39
+
40
+ _CALLABLE_KINDS = frozenset({SOURCE_KIND_STORED_PROC, SOURCE_KIND_TABLE_FUNCTION})
41
+
42
+
43
+ def validate_source_parameter_ref(root: MetaData, errors: list[MetaError]) -> None:
44
+ # Pre-index every object by name AND fqn so resolution costs O(1) per source.
45
+ object_index: dict[str, MetaData] = {}
46
+ for obj in root.own_children():
47
+ if obj.type != TYPE_OBJECT:
48
+ continue
49
+ object_index[obj.name] = obj
50
+ fqn = (
51
+ f"{obj.package}{PACKAGE_SEP}{obj.name}"
52
+ if obj.package
53
+ else obj.name
54
+ )
55
+ object_index[fqn] = obj
56
+
57
+ for obj in root.own_children():
58
+ if obj.type != TYPE_OBJECT:
59
+ continue
60
+ for source in obj.own_children():
61
+ if source.type != TYPE_SOURCE or source.sub_type != SOURCE_SUBTYPE_RDB:
62
+ continue
63
+ if not isinstance(source, MetaSource):
64
+ continue
65
+ ref = source.attr(SOURCE_ATTR_PARAMETER_REF)
66
+ if not isinstance(ref, str) or ref == "":
67
+ continue
68
+
69
+ # ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND — before resolution.
70
+ if source.effective_kind() not in _CALLABLE_KINDS:
71
+ errors.append(
72
+ MetaError(
73
+ f'source.rdb on object "{obj.name}" has @parameterRef but '
74
+ f'@kind is "{source.effective_kind()}"; only "storedProc" '
75
+ 'or "tableFunction" accept parameters',
76
+ ErrorCode.ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND,
77
+ envelope=source.source,
78
+ )
79
+ )
80
+ continue
81
+
82
+ target = object_index.get(ref)
83
+ if target is None:
84
+ errors.append(
85
+ MetaError(
86
+ f'source.rdb on object "{obj.name}" @parameterRef = "{ref}" '
87
+ "does not resolve to any known object",
88
+ ErrorCode.ERR_PARAMETER_REF_UNRESOLVED,
89
+ envelope=source.source,
90
+ )
91
+ )
92
+ continue
93
+
94
+ if target.sub_type != OBJECT_SUBTYPE_VALUE:
95
+ reason = (
96
+ "an object.entity (entities have identity; parameter shapes "
97
+ "are value-objects)"
98
+ if target.sub_type == OBJECT_SUBTYPE_ENTITY
99
+ else f"an object.{target.sub_type}"
100
+ )
101
+ errors.append(
102
+ MetaError(
103
+ f'source.rdb on object "{obj.name}" @parameterRef = "{ref}" '
104
+ f"resolves to {reason}; use an object.value",
105
+ ErrorCode.ERR_PARAMETER_REF_NOT_VALUE_OBJECT,
106
+ envelope=source.source,
107
+ )
108
+ )
109
+ continue
110
+
111
+ # ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH — each parameter field
112
+ # with origin.passthrough must match the referenced field's subtype.
113
+ for param_field in target.own_children():
114
+ if param_field.type != TYPE_FIELD:
115
+ continue
116
+ passthrough = next(
117
+ (
118
+ c
119
+ for c in param_field.own_children()
120
+ if c.type == TYPE_ORIGIN
121
+ and c.sub_type == ORIGIN_SUBTYPE_PASSTHROUGH
122
+ ),
123
+ None,
124
+ )
125
+ if passthrough is None:
126
+ continue
127
+ frm = passthrough.attr(ORIGIN_ATTR_FROM)
128
+ if not isinstance(frm, str) or frm == "":
129
+ continue
130
+ dot = frm.find(".")
131
+ if dot < 0:
132
+ continue
133
+ target_entity_name = frm[:dot]
134
+ target_field_name = frm[dot + 1:]
135
+ target_entity = object_index.get(target_entity_name)
136
+ if target_entity is None:
137
+ continue # origin-paths pass surfaces this
138
+ target_field = next(
139
+ (
140
+ c
141
+ for c in target_entity.own_children()
142
+ if c.type == TYPE_FIELD and c.name == target_field_name
143
+ ),
144
+ None,
145
+ )
146
+ if target_field is None:
147
+ continue
148
+ if param_field.sub_type != target_field.sub_type:
149
+ errors.append(
150
+ MetaError(
151
+ f'parameter field "{param_field.name}" '
152
+ f"(field.{param_field.sub_type}) on @parameterRef "
153
+ f'"{ref}" uses origin.passthrough @from: "{frm}", but '
154
+ f"{target_entity.name}.{target_field_name} is "
155
+ f"field.{target_field.sub_type}; types must match",
156
+ ErrorCode.ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH,
157
+ envelope=param_field.source,
158
+ )
159
+ )
@@ -0,0 +1,140 @@
1
+ """FR-016 / ADR-0018 — per-kind physical-name aliases on source.rdb.
2
+
3
+ Each ``source.rdb`` may declare at most one of
4
+ ``@table`` / ``@view`` / ``@materializedView`` / ``@proc`` / ``@function``. The
5
+ chosen alias must match the source's ``@kind``, with one pre-1.0 legacy
6
+ exception: ``@table`` is also accepted for non-table kinds (e.g.
7
+ ``@kind: "storedProc"`` + ``@table: "fn_x"``), which emits a
8
+ ``WARN_LEGACY_PHYSICAL_NAME_ALIAS``.
9
+
10
+ Codes emitted by this pass:
11
+ * ``ERR_BAD_ATTR_VALUE`` — any kind-aware alias set to ``""``.
12
+ * ``ERR_PHYSICAL_NAME_MULTIPLE`` — two or more kind-aware aliases on one source.
13
+ * ``ERR_PHYSICAL_NAME_KIND_MISMATCH`` — alias other than ``@table`` set with
14
+ a non-matching ``@kind``.
15
+ * ``WARN_LEGACY_PHYSICAL_NAME_ALIAS`` — ``@table`` set with a non-table
16
+ ``@kind`` (legacy spelling). Loader accepts.
17
+
18
+ Mirrors the TS reference
19
+ ``packages/metadata/src/persistence/source/validate-source-physical-names.ts``.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from ..errors import ErrorCode, MetaError
24
+ from ..meta.meta_data import MetaData
25
+ from ..meta.persistence.source.meta_source import MetaSource
26
+ from ..meta.persistence.source.source_constants import (
27
+ ALL_PHYSICAL_NAME_ALIASES,
28
+ PHYSICAL_NAME_ATTR_BY_KIND,
29
+ SOURCE_ATTR_TABLE,
30
+ SOURCE_SUBTYPE_RDB,
31
+ )
32
+ from ..shared.base_types import TYPE_OBJECT, TYPE_SOURCE
33
+ from ..source.error_source import (
34
+ LoaderWarning,
35
+ WARN_LEGACY_PHYSICAL_NAME_ALIAS,
36
+ )
37
+
38
+
39
+ def _kind_for_alias(alias: str) -> str:
40
+ for kind, attr in PHYSICAL_NAME_ATTR_BY_KIND.items():
41
+ if attr == alias:
42
+ return kind
43
+ return "(unknown)"
44
+
45
+
46
+ def validate_source_physical_names(
47
+ root: MetaData,
48
+ errors: list[MetaError],
49
+ envelope_warnings: list[LoaderWarning] | None = None,
50
+ legacy_warnings: list[str] | None = None,
51
+ ) -> None:
52
+ """Run the FR-016 / ADR-0018 per-kind physical-name validation.
53
+
54
+ ``envelope_warnings`` collects the FR5c-style envelope warnings (today only
55
+ ``WARN_LEGACY_PHYSICAL_NAME_ALIAS``). ``legacy_warnings`` mirrors the
56
+ cross-port string warnings channel — populated with the same code string
57
+ so the legacy ``warnings: list[str]`` consumers see something too.
58
+ """
59
+ for obj in root.own_children():
60
+ if obj.type != TYPE_OBJECT:
61
+ continue
62
+ for source in obj.own_children():
63
+ if source.type != TYPE_SOURCE:
64
+ continue
65
+ if source.sub_type != SOURCE_SUBTYPE_RDB:
66
+ continue
67
+ if not isinstance(source, MetaSource):
68
+ continue
69
+
70
+ # Empty-string check first — explicit "" is meaningless and an
71
+ # authoring error regardless of which alias was used. Run before
72
+ # the multi/mismatch checks so an empty value can't slip through.
73
+ for attr in ALL_PHYSICAL_NAME_ALIASES:
74
+ v = source.attr(attr)
75
+ if isinstance(v, str) and v == "":
76
+ errors.append(
77
+ MetaError(
78
+ f'source.rdb on object "{obj.name}" sets @{attr} '
79
+ "to an empty string; physical name attrs require a "
80
+ "non-empty value",
81
+ ErrorCode.ERR_BAD_ATTR_VALUE,
82
+ envelope=source.source,
83
+ )
84
+ )
85
+
86
+ set_aliases = [
87
+ a for a in ALL_PHYSICAL_NAME_ALIASES
88
+ if isinstance(source.attr(a), str) and source.attr(a)
89
+ ]
90
+
91
+ if len(set_aliases) > 1:
92
+ joined = ", ".join(f"@{a}" for a in set_aliases)
93
+ errors.append(
94
+ MetaError(
95
+ f'source.rdb on object "{obj.name}" declares multiple '
96
+ f"physical-name aliases ({joined}); set exactly one",
97
+ ErrorCode.ERR_PHYSICAL_NAME_MULTIPLE,
98
+ envelope=source.source,
99
+ )
100
+ )
101
+ continue
102
+
103
+ if not set_aliases:
104
+ continue
105
+
106
+ chosen = set_aliases[0]
107
+ expected = PHYSICAL_NAME_ATTR_BY_KIND.get(source.effective_kind())
108
+
109
+ if chosen == expected:
110
+ continue
111
+
112
+ # Legacy: @table is permitted for non-table kinds with a warning.
113
+ if chosen == SOURCE_ATTR_TABLE:
114
+ msg = (
115
+ f'source.rdb on object "{obj.name}" uses @table with '
116
+ f'@kind: "{source.effective_kind()}"; prefer the '
117
+ f"kind-matching alias @{expected} (ADR-0018)"
118
+ )
119
+ if envelope_warnings is not None:
120
+ envelope_warnings.append(
121
+ LoaderWarning(
122
+ code=WARN_LEGACY_PHYSICAL_NAME_ALIAS,
123
+ message=msg,
124
+ source=source.source,
125
+ )
126
+ )
127
+ if legacy_warnings is not None:
128
+ legacy_warnings.append(WARN_LEGACY_PHYSICAL_NAME_ALIAS)
129
+ continue
130
+
131
+ # Any other mismatch is a hard error.
132
+ errors.append(
133
+ MetaError(
134
+ f'source.rdb on object "{obj.name}" uses @{chosen} with '
135
+ f'@kind: "{source.effective_kind()}"; @{chosen} is only '
136
+ f'valid for @kind: "{_kind_for_alias(chosen)}"',
137
+ ErrorCode.ERR_PHYSICAL_NAME_KIND_MISMATCH,
138
+ envelope=source.source,
139
+ )
140
+ )