Rule-6 check (c) reports a correctly shaped relation property as "placed off a property" whenever it is nested inside an array-of-objects.
Measured
Full-tree run against ConductionNL/larpingapp@development:
lib/Settings/larpingapp_register.json: x-relation-filter is placed off a property
(inside items / an x-* block / non-property node) — it rides only on the relation
property itself (ADR-062 rule 6)
The flagged node is character.skillOverrides.items.properties.skill:
{
"type": "string",
"format": "uuid",
"$ref": "skill",
"x-relation-filter": { "setting": "@object.setting" },
"description": "The assigned skill whose requirements are waived",
"title": "Skill"
}
That is the canonical dialect exactly — type + format: uuid + $ref + the filter riding on the same property. It is byte-for-byte the same shape as character.skills twenty lines above, which the gate accepts. The only difference is nesting depth.
Cause — call path
scripts/lib/check_relation_dialect.py:
property_ids is populated in a single, non-recursive loop (~line 372-388):
property_ids = set()
for sname, schema in schemas.items():
props = schema.get("properties")
if not isinstance(props, dict):
continue
...
for pname, prop in props.items():
...
property_ids.add(id(prop))
It only ever walks schema.properties.*. Nothing descends into properties.<p>.items.properties.*.
_raw_walk() (~line 470) recurses into everything:
def _raw_walk(node, path, property_ids, findings):
if isinstance(node, dict):
...
if "x-relation-filter" in node:
if id(node) not in property_ids:
findings.append((path, "... placed off a property (inside items / an x-* block / non-property node) ..."))
So any nested relation property is structurally unrepresentable to check (c): _raw_walk finds it, property_ids can never contain it, and the finding is unconditional.
Why this one bites
The finding is unfixable in the app. The three ways to clear it are all wrong:
- move the filter off the property — that is the actual rule-6 violation;
- flatten
skillOverrides from an array-of-objects to something else — a schema redesign to satisfy a linter;
- delete the filter — loses the setting-scoped picker.
So it sits as permanent red. I have left it unfixed in ConductionNL/larpingapp#289 and documented it there rather than mangling correct schema.
Suggested fix
Collect property_ids recursively through items (and items.properties), mirroring what _raw_walk already traverses. Roughly: after adding id(prop), if prop.get("items") is a dict with a properties dict, add each of those too — and recurse, since arrays of objects can nest.
The other checks in the helper ((b) relation-shape, (f) $ref resolution) have the same blind spot for nested properties, so a nested relation missing its $ref is currently not reported — the gate is simultaneously false-positive on shape and false-negative on the thing that matters. Worth fixing both with one recursive collector.
Rule-6 check (c) reports a correctly shaped relation property as "placed off a property" whenever it is nested inside an array-of-objects.
Measured
Full-tree run against
ConductionNL/larpingapp@development:The flagged node is
character.skillOverrides.items.properties.skill:{ "type": "string", "format": "uuid", "$ref": "skill", "x-relation-filter": { "setting": "@object.setting" }, "description": "The assigned skill whose requirements are waived", "title": "Skill" }That is the canonical dialect exactly —
type+format: uuid+$ref+ the filter riding on the same property. It is byte-for-byte the same shape ascharacter.skillstwenty lines above, which the gate accepts. The only difference is nesting depth.Cause — call path
scripts/lib/check_relation_dialect.py:property_idsis populated in a single, non-recursive loop (~line 372-388):It only ever walks
schema.properties.*. Nothing descends intoproperties.<p>.items.properties.*._raw_walk()(~line 470) recurses into everything:So any nested relation property is structurally unrepresentable to check (c):
_raw_walkfinds it,property_idscan never contain it, and the finding is unconditional.Why this one bites
The finding is unfixable in the app. The three ways to clear it are all wrong:
skillOverridesfrom an array-of-objects to something else — a schema redesign to satisfy a linter;So it sits as permanent red. I have left it unfixed in ConductionNL/larpingapp#289 and documented it there rather than mangling correct schema.
Suggested fix
Collect
property_idsrecursively throughitems(anditems.properties), mirroring what_raw_walkalready traverses. Roughly: after addingid(prop), ifprop.get("items")is a dict with apropertiesdict, add each of those too — and recurse, since arrays of objects can nest.The other checks in the helper ((b) relation-shape, (f)
$refresolution) have the same blind spot for nested properties, so a nested relation missing its$refis currently not reported — the gate is simultaneously false-positive on shape and false-negative on the thing that matters. Worth fixing both with one recursive collector.