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
metaobjects/datatype.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Coarse value-type classification shared across nodes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DataType(str, Enum):
|
|
8
|
+
STRING = "string"
|
|
9
|
+
INT = "int"
|
|
10
|
+
LONG = "long"
|
|
11
|
+
DOUBLE = "double"
|
|
12
|
+
# DECIMAL is the exact, Decimal-preserving numeric type (NUMERIC columns).
|
|
13
|
+
# Distinct from DOUBLE so field.decimal surfaces a native ``Decimal`` (lossless),
|
|
14
|
+
# never a lossy float — see ADR-0019 + the SP-D runtime return-type contract.
|
|
15
|
+
DECIMAL = "decimal"
|
|
16
|
+
BOOLEAN = "boolean"
|
|
17
|
+
DATE = "date"
|
|
18
|
+
OBJECT = "object"
|
|
19
|
+
STRING_ARRAY = "stringArray"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Documentation provider — 7 universal common attrs (cross-language parity)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .doc_constants import (
|
|
5
|
+
DOC_ATTR_ALIASES,
|
|
6
|
+
DOC_ATTR_DEPRECATED,
|
|
7
|
+
DOC_ATTR_DESCRIPTION,
|
|
8
|
+
DOC_ATTR_NAMES,
|
|
9
|
+
DOC_ATTR_NOTES,
|
|
10
|
+
DOC_ATTR_REPLACED_BY,
|
|
11
|
+
DOC_ATTR_SEE_ALSO,
|
|
12
|
+
DOC_ATTR_TITLE,
|
|
13
|
+
)
|
|
14
|
+
from .doc_provider import doc_provider
|
|
15
|
+
from .doc_schema import common_doc_attrs
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DOC_ATTR_DESCRIPTION",
|
|
19
|
+
"DOC_ATTR_TITLE",
|
|
20
|
+
"DOC_ATTR_NOTES",
|
|
21
|
+
"DOC_ATTR_DEPRECATED",
|
|
22
|
+
"DOC_ATTR_REPLACED_BY",
|
|
23
|
+
"DOC_ATTR_SEE_ALSO",
|
|
24
|
+
"DOC_ATTR_ALIASES",
|
|
25
|
+
"DOC_ATTR_NAMES",
|
|
26
|
+
"common_doc_attrs",
|
|
27
|
+
"doc_provider",
|
|
28
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Cross-language doc-attr constants. Bare strings (identical to TS/C#/Java)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
DOC_ATTR_DESCRIPTION = "description"
|
|
5
|
+
DOC_ATTR_TITLE = "title"
|
|
6
|
+
DOC_ATTR_NOTES = "notes"
|
|
7
|
+
DOC_ATTR_DEPRECATED = "deprecated"
|
|
8
|
+
DOC_ATTR_REPLACED_BY = "replacedBy"
|
|
9
|
+
DOC_ATTR_SEE_ALSO = "seeAlso"
|
|
10
|
+
DOC_ATTR_ALIASES = "aliases"
|
|
11
|
+
|
|
12
|
+
DOC_ATTR_NAMES: tuple[str, ...] = (
|
|
13
|
+
DOC_ATTR_DESCRIPTION,
|
|
14
|
+
DOC_ATTR_TITLE,
|
|
15
|
+
DOC_ATTR_NOTES,
|
|
16
|
+
DOC_ATTR_DEPRECATED,
|
|
17
|
+
DOC_ATTR_REPLACED_BY,
|
|
18
|
+
DOC_ATTR_SEE_ALSO,
|
|
19
|
+
DOC_ATTR_ALIASES,
|
|
20
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The DocumentationProvider — registers the 7 common doc attrs on every metatype."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ..provider import Provider
|
|
5
|
+
from ..registry import TypeRegistry
|
|
6
|
+
from .doc_schema import common_doc_attrs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _DocProvider(Provider):
|
|
10
|
+
"""Subclass of Provider that also registers common doc attrs into the registry."""
|
|
11
|
+
|
|
12
|
+
def register_types(self, registry: TypeRegistry) -> None:
|
|
13
|
+
super().register_types(registry)
|
|
14
|
+
registry.register_common_attrs(common_doc_attrs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
doc_provider = _DocProvider(
|
|
18
|
+
"metaobjects-documentation",
|
|
19
|
+
dependencies=("metaobjects-core-types",),
|
|
20
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""The 7 universal doc common attrs as AttrSchema records."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ..meta.core.attr.attr_constants import ATTR_SUBTYPE_STRING
|
|
5
|
+
from ..registry import AttrSchema
|
|
6
|
+
from .doc_constants import (
|
|
7
|
+
DOC_ATTR_ALIASES,
|
|
8
|
+
DOC_ATTR_DEPRECATED,
|
|
9
|
+
DOC_ATTR_DESCRIPTION,
|
|
10
|
+
DOC_ATTR_NOTES,
|
|
11
|
+
DOC_ATTR_REPLACED_BY,
|
|
12
|
+
DOC_ATTR_SEE_ALSO,
|
|
13
|
+
DOC_ATTR_TITLE,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
common_doc_attrs: list[AttrSchema] = [
|
|
17
|
+
AttrSchema(name=DOC_ATTR_DESCRIPTION, value_type=ATTR_SUBTYPE_STRING, required=False),
|
|
18
|
+
AttrSchema(name=DOC_ATTR_TITLE, value_type=ATTR_SUBTYPE_STRING, required=False),
|
|
19
|
+
AttrSchema(name=DOC_ATTR_NOTES, value_type=ATTR_SUBTYPE_STRING, required=False),
|
|
20
|
+
AttrSchema(name=DOC_ATTR_DEPRECATED, value_type=ATTR_SUBTYPE_STRING, required=False),
|
|
21
|
+
AttrSchema(name=DOC_ATTR_REPLACED_BY, value_type=ATTR_SUBTYPE_STRING, required=False),
|
|
22
|
+
AttrSchema(name=DOC_ATTR_SEE_ALSO, value_type=ATTR_SUBTYPE_STRING, required=False, is_array=True),
|
|
23
|
+
AttrSchema(name=DOC_ATTR_ALIASES, value_type=ATTR_SUBTYPE_STRING, required=False, is_array=True),
|
|
24
|
+
]
|
metaobjects/errors.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Stable error/warning vocabulary. Codes (not messages) are the conformance contract."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # avoid runtime import cycles
|
|
8
|
+
from .source import ErrorSource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorCode(str, Enum):
|
|
12
|
+
ERR_MALFORMED_JSON = "ERR_MALFORMED_JSON"
|
|
13
|
+
ERR_TOP_LEVEL_NOT_OBJECT = "ERR_TOP_LEVEL_NOT_OBJECT"
|
|
14
|
+
ERR_UNKNOWN_TYPE = "ERR_UNKNOWN_TYPE"
|
|
15
|
+
ERR_UNKNOWN_SUBTYPE = "ERR_UNKNOWN_SUBTYPE"
|
|
16
|
+
ERR_MISSING_SUBTYPE = "ERR_MISSING_SUBTYPE"
|
|
17
|
+
ERR_DUPLICATE_NAME = "ERR_DUPLICATE_NAME"
|
|
18
|
+
ERR_UNRESOLVED_SUPER = "ERR_UNRESOLVED_SUPER"
|
|
19
|
+
ERR_INVALID_SUBTYPE_CHILD = "ERR_INVALID_SUBTYPE_CHILD"
|
|
20
|
+
ERR_UNKNOWN_ATTR = "ERR_UNKNOWN_ATTR"
|
|
21
|
+
ERR_MISSING_REQUIRED_ATTR = "ERR_MISSING_REQUIRED_ATTR"
|
|
22
|
+
ERR_BAD_ATTR_VALUE = "ERR_BAD_ATTR_VALUE"
|
|
23
|
+
ERR_BAD_DEFAULT_SORT_FIELD = "ERR_BAD_DEFAULT_SORT_FIELD"
|
|
24
|
+
ERR_PROVIDER_DEPENDENCY_CYCLE = "ERR_PROVIDER_DEPENDENCY_CYCLE"
|
|
25
|
+
ERR_PROVIDER_DUPLICATE_ID = "ERR_PROVIDER_DUPLICATE_ID"
|
|
26
|
+
ERR_PROVIDER_MISSING_DEPENDENCY = "ERR_PROVIDER_MISSING_DEPENDENCY"
|
|
27
|
+
ERR_PROVIDER_ATTR_CONFLICT = "ERR_PROVIDER_ATTR_CONFLICT"
|
|
28
|
+
ERR_SUBTYPE_RULE_VIOLATION = "ERR_SUBTYPE_RULE_VIOLATION"
|
|
29
|
+
ERR_OVERLAY_NO_TARGET = "ERR_OVERLAY_NO_TARGET"
|
|
30
|
+
# FR5c — two contributing files set the same @attr to different non-empty
|
|
31
|
+
# values on the same node. Carries a `MergedSource` envelope with both
|
|
32
|
+
# contributors listed (ADR-0009 §Overlay-merge).
|
|
33
|
+
ERR_MERGE_CONFLICT = "ERR_MERGE_CONFLICT"
|
|
34
|
+
ERR_MALFORMED_YAML = "ERR_MALFORMED_YAML"
|
|
35
|
+
# YAML 1.2 silently coerced an unquoted scalar to a type incompatible with the
|
|
36
|
+
# declared attr valueType (ADR-0006 D2). Authors should quote the value.
|
|
37
|
+
ERR_YAML_COERCION = "ERR_YAML_COERCION"
|
|
38
|
+
ERR_INVALID_ORIGIN = "ERR_INVALID_ORIGIN"
|
|
39
|
+
# FR-017 — M:N relationship slim-vocabulary validation (junction-missing-two-
|
|
40
|
+
# references / sourceRefField-not-matching / M:N-attr-on-1:N). The symmetric-
|
|
41
|
+
# on-hetero + symmetric+sourceRefField rules emit ERR_BAD_ATTR_VALUE instead.
|
|
42
|
+
ERR_INVALID_RELATIONSHIP = "ERR_INVALID_RELATIONSHIP"
|
|
43
|
+
ERR_BAD_ATTR_FILTER = "ERR_BAD_ATTR_FILTER"
|
|
44
|
+
# Reserved structural body key authored as an @-attr (source-v2 / ADR-0007).
|
|
45
|
+
ERR_RESERVED_ATTR = "ERR_RESERVED_ATTR"
|
|
46
|
+
# Source-v2 multi-source one-primary rule (ADR-0007).
|
|
47
|
+
ERR_SOURCE_NO_PRIMARY = "ERR_SOURCE_NO_PRIMARY"
|
|
48
|
+
ERR_SOURCE_MULTIPLE_PRIMARY = "ERR_SOURCE_MULTIPLE_PRIMARY"
|
|
49
|
+
# FR-016 / ADR-0018 — per-kind physical-name aliases on source.rdb.
|
|
50
|
+
ERR_PHYSICAL_NAME_KIND_MISMATCH = "ERR_PHYSICAL_NAME_KIND_MISMATCH"
|
|
51
|
+
ERR_PHYSICAL_NAME_MULTIPLE = "ERR_PHYSICAL_NAME_MULTIPLE"
|
|
52
|
+
# FR-013 — field-level @readOnly cross-attribute rules. Cross-language
|
|
53
|
+
# vocabulary; Python loader does not emit these yet (FR-013 Python fan-out
|
|
54
|
+
# is a separate workstream), but the enum tracks the shared corpus codes.
|
|
55
|
+
ERR_READONLY_ASSIGNED_PRIMARY = "ERR_READONLY_ASSIGNED_PRIMARY"
|
|
56
|
+
ERR_READONLY_DOWNGRADE = "ERR_READONLY_DOWNGRADE"
|
|
57
|
+
# FR-015 — source.rdb @parameterRef typed-input validation. Cross-language
|
|
58
|
+
# vocabulary; Python loader does not emit these yet, but the enum tracks
|
|
59
|
+
# the shared corpus codes.
|
|
60
|
+
ERR_PARAMETER_REF_UNRESOLVED = "ERR_PARAMETER_REF_UNRESOLVED"
|
|
61
|
+
ERR_PARAMETER_REF_NOT_VALUE_OBJECT = "ERR_PARAMETER_REF_NOT_VALUE_OBJECT"
|
|
62
|
+
ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND = "ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND"
|
|
63
|
+
ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH = "ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH"
|
|
64
|
+
# FR-014 — TPH discriminator cross-attribute validation. Cross-language
|
|
65
|
+
# vocabulary; Python loader does not emit these yet.
|
|
66
|
+
ERR_DISCRIMINATOR_FIELD_NOT_FOUND = "ERR_DISCRIMINATOR_FIELD_NOT_FOUND"
|
|
67
|
+
ERR_DISCRIMINATOR_VALUE_DUPLICATE = "ERR_DISCRIMINATOR_VALUE_DUPLICATE"
|
|
68
|
+
ERR_DISCRIMINATOR_VALUE_MISSING = "ERR_DISCRIMINATOR_VALUE_MISSING"
|
|
69
|
+
ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH = "ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH"
|
|
70
|
+
# Cross-language vocabulary for features other ports added (FR-003 storage, FR-004 template);
|
|
71
|
+
# the Python loader does not emit these yet, but the enum tracks the shared corpus codes.
|
|
72
|
+
ERR_INVALID_TEMPLATE = "ERR_INVALID_TEMPLATE"
|
|
73
|
+
ERR_STORAGE_FLATTENED_ARRAY = "ERR_STORAGE_FLATTENED_ARRAY"
|
|
74
|
+
ERR_STORAGE_WITHOUT_OBJECT_REF = "ERR_STORAGE_WITHOUT_OBJECT_REF"
|
|
75
|
+
# ADR-0013: a field.object REQUIRES @objectRef (open/untyped JSON uses the
|
|
76
|
+
# physical @dbColumnType: jsonb escape hatch on field.string).
|
|
77
|
+
ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF = "ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF"
|
|
78
|
+
ERR_PARTIAL_UNRESOLVED = "ERR_PARTIAL_UNRESOLVED"
|
|
79
|
+
ERR_REQUIRED_SLOT_UNUSED = "ERR_REQUIRED_SLOT_UNUSED"
|
|
80
|
+
ERR_VAR_NOT_ON_PAYLOAD = "ERR_VAR_NOT_ON_PAYLOAD"
|
|
81
|
+
ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING"
|
|
82
|
+
# SP-H Unit9 — @filterable: true on a field subtype with no filter-operator
|
|
83
|
+
# band (e.g. field.object). Would silently generate an empty-ops filter.
|
|
84
|
+
ERR_FILTERABLE_UNSUPPORTED_SUBTYPE = "ERR_FILTERABLE_UNSUPPORTED_SUBTYPE"
|
|
85
|
+
# ADR-0023 — a registration was attempted against a registry sealed after its
|
|
86
|
+
# agreed metamodel-provider bootstrap. Codegen cannot invent metamodel attrs.
|
|
87
|
+
ERR_REGISTRY_SEALED = "ERR_REGISTRY_SEALED"
|
|
88
|
+
ERR_UNKNOWN = "ERR_UNKNOWN"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MetaError:
|
|
92
|
+
"""A loader error. ``code`` is the conformance-compared value; ``message`` is human text.
|
|
93
|
+
|
|
94
|
+
FR5a / ADR-0009: ``envelope`` is the structured provenance envelope every
|
|
95
|
+
cross-language port emits — populated by the parser (JSON tree-walk) and by
|
|
96
|
+
validation passes that have access to a node's ``source``. Legacy ``source``
|
|
97
|
+
(the file path) / ``path`` remain for backward-compat (the conformance
|
|
98
|
+
adapter only inspects ``code``); new sites should pass ``envelope``.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
message: str,
|
|
104
|
+
code: ErrorCode = ErrorCode.ERR_UNKNOWN,
|
|
105
|
+
source: str | None = None,
|
|
106
|
+
path: str | None = None,
|
|
107
|
+
envelope: Optional[ErrorSource] = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
self.message = message
|
|
110
|
+
self.code = code
|
|
111
|
+
self.source = source
|
|
112
|
+
self.path = path
|
|
113
|
+
self.envelope = envelope
|
|
114
|
+
|
|
115
|
+
def __repr__(self) -> str:
|
|
116
|
+
return f"MetaError({self.code.name}: {self.message!r})"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ParseError(Exception):
|
|
120
|
+
"""Raised by the parser in strict mode; carries a code."""
|
|
121
|
+
|
|
122
|
+
def __init__(self, message: str, code: ErrorCode = ErrorCode.ERR_UNKNOWN) -> None:
|
|
123
|
+
super().__init__(message)
|
|
124
|
+
self.code = code
|
|
File without changes
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Multi-file / overlay merge: fold parsed roots into one (post-parse, pre-super-resolve).
|
|
2
|
+
|
|
3
|
+
FR5c — the merge phase is the cross-port attribution hub. Tracks which
|
|
4
|
+
files contributed to each post-merge node and, on each ``_merge_into``
|
|
5
|
+
call:
|
|
6
|
+
|
|
7
|
+
1. **ERR_MERGE_CONFLICT** — same ``@attr`` name set with different
|
|
8
|
+
non-empty values on both contributors → hard error with a
|
|
9
|
+
:class:`MergedSource` envelope listing both files and the conflicting
|
|
10
|
+
attr's jsonPath. Last-writer-wins still applies to the merged tree.
|
|
11
|
+
2. **MergedSource upgrade** — when the new contributor actually changed
|
|
12
|
+
the post-merge canonical (semantic_diff returns True), the target's
|
|
13
|
+
``source`` envelope is upgraded to :class:`MergedSource` with
|
|
14
|
+
alphabetically-sorted ``files``/``contributors`` (overlay-base for
|
|
15
|
+
the first file, overlay-extension for the rest).
|
|
16
|
+
3. **WARN_DUPLICATE_DECLARATION** — when the new contributor's content
|
|
17
|
+
produced no semantic change AND the new file isn't already a
|
|
18
|
+
contributor, emit a warning (string channel + envelope channel).
|
|
19
|
+
The node's source is NOT upgraded — the warning surfaces the
|
|
20
|
+
redundancy.
|
|
21
|
+
|
|
22
|
+
Cross-port contract (TS reference ``parser-core.ts``):
|
|
23
|
+
* Alphabetical file order across contributors[] (matches DirectorySource
|
|
24
|
+
sorting).
|
|
25
|
+
* Single error code ``ERR_MERGE_CONFLICT``; per-attr provenance deferred.
|
|
26
|
+
* "Conflict" = both sides set, both non-empty, values differ. Empty
|
|
27
|
+
string and absent are NOT conflicts.
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
from typing import Optional
|
|
33
|
+
|
|
34
|
+
from ..errors import ErrorCode, MetaError
|
|
35
|
+
from ..meta.meta_data import MetaData
|
|
36
|
+
from ..serializer_json import canonical_serialize
|
|
37
|
+
from ..source import (
|
|
38
|
+
Contributor,
|
|
39
|
+
ErrorSource,
|
|
40
|
+
JsonSource,
|
|
41
|
+
LoaderWarning,
|
|
42
|
+
MergedSource,
|
|
43
|
+
WARN_DUPLICATE_DECLARATION,
|
|
44
|
+
YamlSource,
|
|
45
|
+
)
|
|
46
|
+
from ..source.semantic_diff import semantic_diff
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def merge_roots(
|
|
50
|
+
roots: list[MetaData],
|
|
51
|
+
errors: list[MetaError],
|
|
52
|
+
warnings: Optional[list[str]] = None,
|
|
53
|
+
envelope_warnings: Optional[list[LoaderWarning]] = None,
|
|
54
|
+
) -> MetaData:
|
|
55
|
+
"""Merge all roots into the first. Returns the merged root (or raises if empty).
|
|
56
|
+
|
|
57
|
+
FR5c — *warnings* and *envelope_warnings*, when provided, receive
|
|
58
|
+
:data:`WARN_DUPLICATE_DECLARATION` messages for duplicate-with-no-change
|
|
59
|
+
contributions. They default to throwaway lists to preserve the prior
|
|
60
|
+
`merge_roots(roots, errors)` two-arg signature for any in-tree caller
|
|
61
|
+
that hasn't been updated yet.
|
|
62
|
+
"""
|
|
63
|
+
if not roots:
|
|
64
|
+
raise ValueError("merge_roots requires at least one root")
|
|
65
|
+
if warnings is None:
|
|
66
|
+
warnings = []
|
|
67
|
+
if envelope_warnings is None:
|
|
68
|
+
envelope_warnings = []
|
|
69
|
+
target = roots[0]
|
|
70
|
+
for src in roots[1:]:
|
|
71
|
+
_merge_into(target, src, errors, warnings, envelope_warnings)
|
|
72
|
+
return target
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _source_files(env: ErrorSource) -> tuple[str, ...]:
|
|
76
|
+
"""Extract the ``files`` tuple from any envelope that carries one.
|
|
77
|
+
|
|
78
|
+
Returns an empty tuple for code / database envelopes (no file context).
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(env, (JsonSource, YamlSource, MergedSource)):
|
|
81
|
+
return tuple(env.files)
|
|
82
|
+
return ()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _source_json_path(env: ErrorSource) -> Optional[str]:
|
|
86
|
+
"""Extract the ``json_path`` from any envelope that carries one."""
|
|
87
|
+
if isinstance(env, (JsonSource, YamlSource, MergedSource)):
|
|
88
|
+
return env.json_path
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_contributors(files: tuple[str, ...]) -> tuple[Contributor, ...]:
|
|
93
|
+
"""Build a contributors tuple — first file is overlay-base, rest are
|
|
94
|
+
overlay-extension. *files* must already be deduplicated + sorted.
|
|
95
|
+
"""
|
|
96
|
+
return tuple(
|
|
97
|
+
Contributor(
|
|
98
|
+
file=f,
|
|
99
|
+
role="overlay-base" if i == 0 else "overlay-extension",
|
|
100
|
+
)
|
|
101
|
+
for i, f in enumerate(files)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _is_empty_value(v: object) -> bool:
|
|
106
|
+
"""Mirror TS ``isEmptyValue``: ``None``, ``""``, and ``[]`` are empty;
|
|
107
|
+
everything else is set.
|
|
108
|
+
"""
|
|
109
|
+
if v is None:
|
|
110
|
+
return True
|
|
111
|
+
if isinstance(v, str) and v == "":
|
|
112
|
+
return True
|
|
113
|
+
if isinstance(v, list) and not v:
|
|
114
|
+
return True
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _attr_values_equal(a: object, b: object) -> bool:
|
|
119
|
+
"""Structural value equality matching TS ``attrValuesEqual``: scalars
|
|
120
|
+
compared by ``==``; lists/dicts compared key-order-independently via a
|
|
121
|
+
canonical-key-sorted JSON dump (the same substrate the canonical
|
|
122
|
+
serializer + semantic_diff use).
|
|
123
|
+
"""
|
|
124
|
+
if a == b:
|
|
125
|
+
return True
|
|
126
|
+
if isinstance(a, (list, dict)) and isinstance(b, (list, dict)):
|
|
127
|
+
try:
|
|
128
|
+
return json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
return False
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _detect_attr_merge_conflicts(
|
|
135
|
+
target: MetaData,
|
|
136
|
+
src: MetaData,
|
|
137
|
+
errors: list[MetaError],
|
|
138
|
+
) -> None:
|
|
139
|
+
"""FR5c — for every own @-attr on *src*, check whether *target* already
|
|
140
|
+
declares the same attr with a different non-empty value. If so, emit an
|
|
141
|
+
``ERR_MERGE_CONFLICT`` carrying a :class:`MergedSource` envelope naming
|
|
142
|
+
both contributors.
|
|
143
|
+
|
|
144
|
+
The merge itself proceeds (last-writer-wins) so the loader sees one
|
|
145
|
+
canonical tree; the error surfaces the conflict.
|
|
146
|
+
"""
|
|
147
|
+
target_files = _source_files(target.source)
|
|
148
|
+
src_files = _source_files(src.source)
|
|
149
|
+
target_json_path = _source_json_path(target.source)
|
|
150
|
+
|
|
151
|
+
pre_attrs: dict[str, object] = {
|
|
152
|
+
attr.name: getattr(attr, "value", None) for attr in target.own_meta_attrs()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for src_attr in src.own_meta_attrs():
|
|
156
|
+
attr_name = src_attr.name
|
|
157
|
+
if attr_name not in pre_attrs:
|
|
158
|
+
continue
|
|
159
|
+
existing_val = pre_attrs[attr_name]
|
|
160
|
+
new_val = getattr(src_attr, "value", None)
|
|
161
|
+
|
|
162
|
+
if _is_empty_value(new_val) or _is_empty_value(existing_val):
|
|
163
|
+
continue
|
|
164
|
+
if _attr_values_equal(existing_val, new_val):
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# Conflict — build the MergedSource envelope.
|
|
168
|
+
combined = sorted(set(target_files) | set(src_files))
|
|
169
|
+
conflict_files = tuple(combined)
|
|
170
|
+
attr_path = (
|
|
171
|
+
f"{target_json_path}.@{attr_name}"
|
|
172
|
+
if target_json_path
|
|
173
|
+
else f"@{attr_name}"
|
|
174
|
+
)
|
|
175
|
+
envelope = MergedSource(
|
|
176
|
+
files=conflict_files,
|
|
177
|
+
json_path=attr_path,
|
|
178
|
+
contributors=_build_contributors(conflict_files),
|
|
179
|
+
)
|
|
180
|
+
errors.append(
|
|
181
|
+
MetaError(
|
|
182
|
+
f"attr '@{attr_name}' conflicts: existing value "
|
|
183
|
+
f"{json.dumps(existing_val)} differs from new value "
|
|
184
|
+
f"{json.dumps(new_val)} on {target.fqn()}",
|
|
185
|
+
ErrorCode.ERR_MERGE_CONFLICT,
|
|
186
|
+
envelope=envelope,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _merge_into(
|
|
192
|
+
target: MetaData,
|
|
193
|
+
src: MetaData,
|
|
194
|
+
errors: list[MetaError],
|
|
195
|
+
warnings: list[str],
|
|
196
|
+
envelope_warnings: list[LoaderWarning],
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Merge *src*'s own attrs/children into *target* in place.
|
|
199
|
+
|
|
200
|
+
FR5c — runs three diagnostics around the merge:
|
|
201
|
+
1. ``ERR_MERGE_CONFLICT`` on conflicting @-attrs (before the write).
|
|
202
|
+
2. ``MergedSource`` upgrade when the merge produced semantic change.
|
|
203
|
+
3. ``WARN_DUPLICATE_DECLARATION`` when no semantic change occurred
|
|
204
|
+
AND the contributor file is new.
|
|
205
|
+
|
|
206
|
+
The root is intentionally excluded from FR5c diagnostics: it is a
|
|
207
|
+
synthetic accumulator (every file declares ``metadata.root``), not an
|
|
208
|
+
author-meaningful node. The merge attribution applies to
|
|
209
|
+
``object.entity`` / ``field.*`` / etc.
|
|
210
|
+
"""
|
|
211
|
+
is_root = target.parent is None # root has no parent
|
|
212
|
+
fr5c_active = not is_root and target.name != ""
|
|
213
|
+
|
|
214
|
+
pre_canonical: Optional[str] = None
|
|
215
|
+
if fr5c_active:
|
|
216
|
+
pre_canonical = canonical_serialize(target)
|
|
217
|
+
_detect_attr_merge_conflicts(target, src, errors)
|
|
218
|
+
|
|
219
|
+
# attrs: source overwrites target (last-writer-wins)
|
|
220
|
+
for attr in src.own_meta_attrs():
|
|
221
|
+
target.set_attr(attr.name, getattr(attr, "value", None), sub_type=attr.sub_type)
|
|
222
|
+
# children: merge by (type, name), else append
|
|
223
|
+
for sc in src.own_children():
|
|
224
|
+
tc = next(
|
|
225
|
+
(c for c in target.own_children() if c.type == sc.type and c.name == sc.name),
|
|
226
|
+
None,
|
|
227
|
+
)
|
|
228
|
+
if tc is not None:
|
|
229
|
+
_merge_into(tc, sc, errors, warnings, envelope_warnings)
|
|
230
|
+
else:
|
|
231
|
+
if getattr(sc, "is_overlay", False):
|
|
232
|
+
errors.append(
|
|
233
|
+
MetaError(
|
|
234
|
+
f"overlay node '{sc.fqn()}' has no merge target",
|
|
235
|
+
ErrorCode.ERR_OVERLAY_NO_TARGET,
|
|
236
|
+
path=sc.fqn(),
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
sc.parent = target
|
|
240
|
+
target.add_child(sc)
|
|
241
|
+
|
|
242
|
+
if not fr5c_active or pre_canonical is None:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Post-merge: compare shapes and upgrade source / emit warning.
|
|
246
|
+
post_canonical = canonical_serialize(target)
|
|
247
|
+
pre_parsed = json.loads(pre_canonical)
|
|
248
|
+
post_parsed = json.loads(post_canonical)
|
|
249
|
+
changed = semantic_diff(pre_parsed, post_parsed)
|
|
250
|
+
|
|
251
|
+
existing_env = target.source
|
|
252
|
+
existing_files = list(_source_files(existing_env))
|
|
253
|
+
src_files = list(_source_files(src.source))
|
|
254
|
+
json_path = _source_json_path(existing_env)
|
|
255
|
+
|
|
256
|
+
new_contributor_file = src_files[0] if src_files else "<unknown>"
|
|
257
|
+
|
|
258
|
+
if changed:
|
|
259
|
+
all_files = sorted(set(existing_files + src_files))
|
|
260
|
+
merged_files = tuple(all_files)
|
|
261
|
+
merged_env = MergedSource(
|
|
262
|
+
files=merged_files,
|
|
263
|
+
json_path=json_path if json_path is not None else "$",
|
|
264
|
+
contributors=_build_contributors(merged_files),
|
|
265
|
+
)
|
|
266
|
+
target.set_source(merged_env)
|
|
267
|
+
elif existing_files and new_contributor_file not in existing_files:
|
|
268
|
+
# Identical re-declaration from a different file → warn.
|
|
269
|
+
all_files = sorted(set(existing_files + [new_contributor_file]))
|
|
270
|
+
warn_files = tuple(all_files)
|
|
271
|
+
warn_env = MergedSource(
|
|
272
|
+
files=warn_files,
|
|
273
|
+
json_path=json_path if json_path is not None else "$",
|
|
274
|
+
contributors=_build_contributors(warn_files),
|
|
275
|
+
)
|
|
276
|
+
message = f"duplicate declaration of {target.fqn()} with no semantic change"
|
|
277
|
+
# Legacy string channel — what the conformance runner checks against
|
|
278
|
+
# expected-warnings.json (a list[str]).
|
|
279
|
+
warnings.append(message)
|
|
280
|
+
# Envelope channel — typed code + source for downstream tooling.
|
|
281
|
+
envelope_warnings.append(
|
|
282
|
+
LoaderWarning(
|
|
283
|
+
code=WARN_DUPLICATE_DECLARATION,
|
|
284
|
+
message=message,
|
|
285
|
+
source=warn_env,
|
|
286
|
+
)
|
|
287
|
+
)
|