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.
- metaobjects/__init__.py +75 -0
- metaobjects/agent_context/__init__.py +55 -0
- metaobjects/agent_context/_content/README.md +14 -0
- metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
- metaobjects/agent_context/_content/servers/java.meta.json +5 -0
- metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
- metaobjects/agent_context/_content/servers/python.meta.json +5 -0
- metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
- metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
- metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
- metaobjects/agent_context/assemble.py +133 -0
- metaobjects/agent_context/content_root.py +54 -0
- metaobjects/agent_context/scaffold.py +191 -0
- metaobjects/agent_context/types.py +44 -0
- metaobjects/attr_class_map.py +23 -0
- metaobjects/cli.py +696 -0
- metaobjects/codegen/__init__.py +0 -0
- metaobjects/codegen/config.py +11 -0
- metaobjects/codegen/constants.py +13 -0
- metaobjects/codegen/extract_delegate_emitter.py +384 -0
- metaobjects/codegen/extract_schema_emitter.py +139 -0
- metaobjects/codegen/format.py +31 -0
- metaobjects/codegen/fr010_field_mapping.py +220 -0
- metaobjects/codegen/generator.py +62 -0
- metaobjects/codegen/generator_registry.py +163 -0
- metaobjects/codegen/generators/__init__.py +0 -0
- metaobjects/codegen/generators/entity_model.py +263 -0
- metaobjects/codegen/generators/extractor_generator.py +317 -0
- metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
- metaobjects/codegen/generators/m2m_codegen.py +192 -0
- metaobjects/codegen/generators/output_parser_generator.py +272 -0
- metaobjects/codegen/generators/output_prompt_generator.py +192 -0
- metaobjects/codegen/generators/payload_vo_generator.py +672 -0
- metaobjects/codegen/generators/render_helper_generator.py +451 -0
- metaobjects/codegen/generators/router_generator.py +635 -0
- metaobjects/codegen/generators/template_generator.py +70 -0
- metaobjects/codegen/generators/tph_plan.py +120 -0
- metaobjects/codegen/generators/trace_helper_generator.py +336 -0
- metaobjects/codegen/instance_artifacts.py +15 -0
- metaobjects/codegen/output_format_spec_emitter.py +79 -0
- metaobjects/codegen/overwrite_policy.py +27 -0
- metaobjects/codegen/runner.py +110 -0
- metaobjects/codegen/runtime/__init__.py +6 -0
- metaobjects/codegen/runtime/filter_parser.py +193 -0
- metaobjects/codegen/type_map.py +84 -0
- metaobjects/core_types.py +809 -0
- metaobjects/datatype.py +19 -0
- metaobjects/documentation/__init__.py +28 -0
- metaobjects/documentation/doc_constants.py +20 -0
- metaobjects/documentation/doc_provider.py +20 -0
- metaobjects/documentation/doc_schema.py +24 -0
- metaobjects/errors.py +124 -0
- metaobjects/loader/__init__.py +0 -0
- metaobjects/loader/merge.py +287 -0
- metaobjects/loader/meta_data_loader.py +245 -0
- metaobjects/loader/sources/__init__.py +24 -0
- metaobjects/loader/sources/directory_source.py +50 -0
- metaobjects/loader/sources/file_source.py +41 -0
- metaobjects/loader/sources/meta_data_source.py +67 -0
- metaobjects/loader/sources/uri_source.py +56 -0
- metaobjects/loader/validate_discriminator.py +181 -0
- metaobjects/loader/validate_field_readonly.py +146 -0
- metaobjects/loader/validate_source_parameter_ref.py +159 -0
- metaobjects/loader/validate_source_physical_names.py +140 -0
- metaobjects/loader/validation_passes.py +1513 -0
- metaobjects/meta/__init__.py +1 -0
- metaobjects/meta/core/__init__.py +0 -0
- metaobjects/meta/core/attr/__init__.py +0 -0
- metaobjects/meta/core/attr/attr_constants.py +31 -0
- metaobjects/meta/core/attr/meta_attr.py +136 -0
- metaobjects/meta/core/field/__init__.py +0 -0
- metaobjects/meta/core/field/field_constants.py +105 -0
- metaobjects/meta/core/field/meta_field.py +76 -0
- metaobjects/meta/core/identity/__init__.py +0 -0
- metaobjects/meta/core/identity/identity_constants.py +19 -0
- metaobjects/meta/core/identity/meta_identity.py +8 -0
- metaobjects/meta/core/object/__init__.py +0 -0
- metaobjects/meta/core/object/meta_object.py +65 -0
- metaobjects/meta/core/object/meta_object_aware.py +43 -0
- metaobjects/meta/core/object/object_class_registry.py +56 -0
- metaobjects/meta/core/object/object_constants.py +13 -0
- metaobjects/meta/core/object/object_extract.py +400 -0
- metaobjects/meta/core/object/value_object.py +70 -0
- metaobjects/meta/core/relationship/__init__.py +0 -0
- metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
- metaobjects/meta/core/relationship/meta_relationship.py +54 -0
- metaobjects/meta/core/relationship/relationship_constants.py +51 -0
- metaobjects/meta/core/validator/__init__.py +0 -0
- metaobjects/meta/core/validator/validator_constants.py +18 -0
- metaobjects/meta/meta_data.py +206 -0
- metaobjects/meta/meta_root.py +8 -0
- metaobjects/meta/persistence/__init__.py +0 -0
- metaobjects/meta/persistence/db/__init__.py +1 -0
- metaobjects/meta/persistence/db/db_constants.py +41 -0
- metaobjects/meta/persistence/db/db_provider.py +60 -0
- metaobjects/meta/persistence/origin/__init__.py +0 -0
- metaobjects/meta/persistence/origin/meta_origin.py +8 -0
- metaobjects/meta/persistence/origin/origin_constants.py +20 -0
- metaobjects/meta/persistence/source/__init__.py +0 -0
- metaobjects/meta/persistence/source/meta_source.py +137 -0
- metaobjects/meta/persistence/source/source_constants.py +115 -0
- metaobjects/meta/presentation/__init__.py +0 -0
- metaobjects/meta/presentation/layout/__init__.py +0 -0
- metaobjects/meta/presentation/layout/layout_constants.py +13 -0
- metaobjects/meta/presentation/layout/meta_layout.py +8 -0
- metaobjects/meta/presentation/view/__init__.py +0 -0
- metaobjects/meta/presentation/view/meta_view.py +8 -0
- metaobjects/meta/presentation/view/view_constants.py +22 -0
- metaobjects/meta/template/__init__.py +0 -0
- metaobjects/meta/template/meta_template.py +46 -0
- metaobjects/meta/template/template_constants.py +112 -0
- metaobjects/meta/template/template_provider.py +43 -0
- metaobjects/parser.py +380 -0
- metaobjects/parser_yaml.py +82 -0
- metaobjects/provider.py +111 -0
- metaobjects/py.typed +0 -0
- metaobjects/registry.py +210 -0
- metaobjects/registry_manifest.py +223 -0
- metaobjects/render/__init__.py +74 -0
- metaobjects/render/email_document.py +14 -0
- metaobjects/render/escapers.py +109 -0
- metaobjects/render/extract/__init__.py +59 -0
- metaobjects/render/extract/coerce.py +279 -0
- metaobjects/render/extract/extract.py +211 -0
- metaobjects/render/extract/extract_map.py +61 -0
- metaobjects/render/extract/json_forgiving_reader.py +203 -0
- metaobjects/render/extract/locate.py +65 -0
- metaobjects/render/extract/normalize.py +96 -0
- metaobjects/render/extract/strip.py +20 -0
- metaobjects/render/extract/types.py +332 -0
- metaobjects/render/extract/xml_forgiving_reader.py +162 -0
- metaobjects/render/filesystem_provider.py +51 -0
- metaobjects/render/prompt/__init__.py +32 -0
- metaobjects/render/prompt/output_format_renderer.py +340 -0
- metaobjects/render/prompt/output_format_spec.py +28 -0
- metaobjects/render/prompt/prompt_field.py +29 -0
- metaobjects/render/prompt/prompt_overrides.py +29 -0
- metaobjects/render/prompt/prompt_style.py +38 -0
- metaobjects/render/renderer.py +358 -0
- metaobjects/render/verify.py +266 -0
- metaobjects/runtime/__init__.py +39 -0
- metaobjects/runtime/llm_recorder.py +210 -0
- metaobjects/runtime/n2m_resolver.py +155 -0
- metaobjects/runtime/object_manager.py +715 -0
- metaobjects/runtime/tph.py +50 -0
- metaobjects/serializer_json.py +172 -0
- metaobjects/shared/__init__.py +0 -0
- metaobjects/shared/base_types.py +16 -0
- metaobjects/shared/separators.py +4 -0
- metaobjects/shared/structural.py +9 -0
- metaobjects/source/__init__.py +79 -0
- metaobjects/source/error_source.py +266 -0
- metaobjects/source/json_path.py +106 -0
- metaobjects/source/semantic_diff.py +98 -0
- metaobjects/source/yaml_positions.py +174 -0
- metaobjects/super_resolve.py +128 -0
- metaobjects/yaml_desugar.py +481 -0
- metaobjects-0.9.0.dist-info/METADATA +97 -0
- metaobjects-0.9.0.dist-info/RECORD +181 -0
- metaobjects-0.9.0.dist-info/WHEEL +4 -0
- metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|