Skip to content

Commit 43b186f

Browse files
dmealingclaude
andcommitted
feat(python): enforce ERR_RESERVED_ATTR in parser (ADR-0006 §D1)
Adds inline detection of @-prefixed reserved structural keys (name, package, extends, abstract, overlay, isArray, children, value) during parse. Any such key emits ERR_RESERVED_ATTR with the offending node and key in the message — matches the TS parser-core behaviour. The reserved-key tuple lives in parser.py and is built from the existing shared.structural KEY_* constants — single source of truth. Migrates two codegen golden fixtures (nested-array, scalars) from the deprecated @isarray attribute form to the bare isArray structural key per ADR-0006 §D1. Ledger: un-gates error-reserved-word-as-attr. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0e94f59 commit 43b186f

4 files changed

Lines changed: 27 additions & 3 deletions

File tree

server/python/src/metaobjects/parser.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@
2121
KEY_VALUE,
2222
)
2323

24+
# Reserved structural body keys — authoring any of these with the @-prefix is a
25+
# hard ERR_RESERVED_ATTR (ADR-0007). Detected inline as each @-key is processed.
26+
_RESERVED_STRUCTURAL_KEYS: frozenset[str] = frozenset({
27+
KEY_NAME,
28+
KEY_PACKAGE,
29+
KEY_EXTENDS,
30+
KEY_ABSTRACT,
31+
KEY_OVERLAY,
32+
KEY_IS_ARRAY,
33+
KEY_CHILDREN,
34+
KEY_VALUE,
35+
})
36+
2437

2538
@dataclass
2639
class ParseResult:
@@ -99,6 +112,18 @@ def _build(
99112
for key, value in body_dict.items():
100113
if key.startswith(ATTR_PREFIX):
101114
attr_name = key[len(ATTR_PREFIX):]
115+
# ADR-0007: @-prefixing a reserved structural body key is invalid.
116+
# Detected inline as each @-attr key is processed (matches TS parser-core).
117+
if attr_name in _RESERVED_STRUCTURAL_KEYS:
118+
result.errors.append(
119+
MetaError(
120+
f"node '{wrapper}' uses reserved structural key '{attr_name}' "
121+
f"with @-prefix; bare '{attr_name}' is the canonical form",
122+
ErrorCode.ERR_RESERVED_ATTR,
123+
source,
124+
)
125+
)
126+
continue
102127
schema = registry.attr_schema(type_, sub_type, attr_name)
103128
node.set_attr(attr_name, value, sub_type=schema.value_type if schema else None)
104129

server/python/tests/codegen/golden/nested-array/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
]}},
55
{ "object.value": { "name": "AuthorBrief", "children": [
66
{ "field.string": { "name": "displayName", "@required": true } },
7-
{ "field.object": { "name": "posts", "@objectRef": "PostBrief", "@isArray": true } }
7+
{ "field.object": { "name": "posts", "@objectRef": "PostBrief", "isArray": true } }
88
]}}
99
]}}

server/python/tests/codegen/golden/scalars/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
{ "field.timestamp": { "name": "generatedAt", "@required": true } },
77
{ "field.decimal": { "name": "total", "@required": true } },
88
{ "field.currency": { "name": "revenue", "@required": true } },
9-
{ "field.object": { "name": "metrics", "@objectRef": "Metric", "@isArray": true } }
9+
{ "field.object": { "name": "metrics", "@objectRef": "Metric", "isArray": true } }
1010
]}}
1111
]}}

server/python/tests/conformance/conformance-expected-failures.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"fixtures": [
33
"error-field-object-storage-flattened-array",
44
"error-field-object-storage-no-object-ref",
5-
"error-reserved-word-as-attr",
65
"error-template-payload-ref-unresolved",
76
"error-template-prompt-missing-payload-ref",
87
"error-template-required-slot-missing",

0 commit comments

Comments
 (0)