@contentful/experience-design-system-types 2.42.1-dev-build-f69db6bf.0 → 2.43.0-alpha.0

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 (94) hide show
  1. package/README.md +15 -0
  2. package/dist/package.json +1 -1
  3. package/dist/src/agent-process-lifecycle.d.ts +26 -0
  4. package/dist/src/agent-process-lifecycle.js +171 -0
  5. package/dist/src/agent-prompt-artifact.d.ts +8 -0
  6. package/dist/src/agent-prompt-artifact.js +37 -0
  7. package/dist/src/agent-prompt-transport.d.ts +1 -0
  8. package/dist/src/agent-prompt-transport.js +4 -0
  9. package/dist/src/cdf/build.d.ts +9 -2
  10. package/dist/src/cdf/build.js +134 -17
  11. package/dist/src/cdf/finite-values.d.ts +8 -0
  12. package/dist/src/cdf/finite-values.js +21 -0
  13. package/dist/src/cdf/index.d.ts +2 -1
  14. package/dist/src/cdf/index.js +1 -0
  15. package/dist/src/cdf/property-validation.d.ts +16 -0
  16. package/dist/src/cdf/property-validation.js +211 -0
  17. package/dist/src/cdf/representation-validation.d.ts +6 -0
  18. package/dist/src/cdf/representation-validation.js +182 -0
  19. package/dist/src/cdf/schema.d.ts +778 -20
  20. package/dist/src/cdf/schema.js +362 -5
  21. package/dist/src/cdf/types.d.ts +57 -59
  22. package/dist/src/cdf/types.js +72 -15
  23. package/dist/src/cdf/validate.js +31 -1
  24. package/dist/src/cdf/vocabularies.d.ts +1 -1
  25. package/dist/src/cdf/vocabularies.js +9 -1
  26. package/dist/src/config/index.js +6 -3
  27. package/dist/src/exploration/constants.d.ts +10 -0
  28. package/dist/src/exploration/constants.js +18 -0
  29. package/dist/src/exploration/index.d.ts +3 -0
  30. package/dist/src/exploration/index.js +3 -0
  31. package/dist/src/exploration/schemas.d.ts +201 -0
  32. package/dist/src/exploration/schemas.js +126 -0
  33. package/dist/src/exploration/types.d.ts +393 -0
  34. package/dist/src/exploration/types.js +1 -0
  35. package/dist/src/handoff/index.d.ts +1 -1
  36. package/dist/src/index.d.ts +15 -1
  37. package/dist/src/index.js +12 -4
  38. package/dist/src/property-contract/analysis-representation-candidates.d.ts +19 -0
  39. package/dist/src/property-contract/analysis-representation-candidates.js +45 -0
  40. package/dist/src/property-contract/classification.d.ts +38 -0
  41. package/dist/src/property-contract/classification.js +84 -0
  42. package/dist/src/property-contract/compact-model-components.d.ts +16 -0
  43. package/dist/src/property-contract/compact-model-components.js +113 -0
  44. package/dist/src/property-contract/content-digest.d.ts +2 -0
  45. package/dist/src/property-contract/content-digest.js +69 -0
  46. package/dist/src/property-contract/decision-protocol.d.ts +45 -0
  47. package/dist/src/property-contract/decision-protocol.js +251 -0
  48. package/dist/src/property-contract/definition-import-rules.d.ts +10 -0
  49. package/dist/src/property-contract/definition-import-rules.js +105 -0
  50. package/dist/src/property-contract/definition-import.d.ts +13 -0
  51. package/dist/src/property-contract/definition-import.js +52 -0
  52. package/dist/src/property-contract/discriminated-union-source.d.ts +17 -0
  53. package/dist/src/property-contract/discriminated-union-source.js +180 -0
  54. package/dist/src/property-contract/import-prerequisites.d.ts +70 -0
  55. package/dist/src/property-contract/import-prerequisites.js +208 -0
  56. package/dist/src/property-contract/index.d.ts +18 -1
  57. package/dist/src/property-contract/index.js +18 -6
  58. package/dist/src/property-contract/logical-source-graph.d.ts +36 -0
  59. package/dist/src/property-contract/logical-source-graph.js +240 -0
  60. package/dist/src/property-contract/mapper-readiness.d.ts +6 -0
  61. package/dist/src/property-contract/mapper-readiness.js +34 -0
  62. package/dist/src/property-contract/model-exploration.d.ts +42 -0
  63. package/dist/src/property-contract/model-exploration.js +94 -0
  64. package/dist/src/property-contract/native-source-region.d.ts +5 -0
  65. package/dist/src/property-contract/native-source-region.js +125 -0
  66. package/dist/src/property-contract/null-handling.d.ts +10 -0
  67. package/dist/src/property-contract/null-handling.js +45 -0
  68. package/dist/src/property-contract/physical-property-import.d.ts +6 -0
  69. package/dist/src/property-contract/physical-property-import.js +55 -0
  70. package/dist/src/property-contract/property-decision.d.ts +14 -0
  71. package/dist/src/property-contract/property-decision.js +155 -0
  72. package/dist/src/property-contract/property-model.d.ts +197 -0
  73. package/dist/src/property-contract/property-model.js +833 -0
  74. package/dist/src/property-contract/representation-plan.d.ts +133 -0
  75. package/dist/src/property-contract/representation-plan.js +756 -0
  76. package/dist/src/property-contract/representation-proposal.d.ts +65 -0
  77. package/dist/src/property-contract/representation-proposal.js +204 -0
  78. package/dist/src/property-contract/resource-link-schema.d.ts +55 -0
  79. package/dist/src/property-contract/resource-link-schema.js +28 -0
  80. package/dist/src/property-contract/source-representation.d.ts +9 -0
  81. package/dist/src/property-contract/source-representation.js +79 -0
  82. package/dist/src/property-contract/target-definition.d.ts +17 -16
  83. package/dist/src/property-contract/target-definition.js +4 -26
  84. package/dist/src/schema-export.d.ts +30 -0
  85. package/dist/src/schema-export.js +285 -0
  86. package/dist/src/shared-source.d.ts +105 -0
  87. package/dist/src/shared-source.js +1 -0
  88. package/dist/src/source-input-association.d.ts +23 -0
  89. package/dist/src/source-input-association.js +52 -0
  90. package/dist/src/sources-api/preview/index.d.ts +37 -3
  91. package/dist/src/sources-api/preview/index.js +39 -1
  92. package/dist/src/sources-api/preview/schema.d.ts +2 -0
  93. package/dist/src/sources-api/preview/schema.js +74 -0
  94. package/package.json +1 -1
@@ -0,0 +1,211 @@
1
+ import { CDF_PROPERTY_TYPES, CDF_PROPERTY_CATEGORIES } from './vocabularies.js';
2
+ import { childPath, cloneJson, jsonValueIssue, own } from '../property-contract/json-value.js';
3
+ import { validateTargetDefinition } from '../property-contract/target-definition.js';
4
+ import { SourceInputAssociationSchema } from '../source-input-association.js';
5
+ import { finiteValuesIssue } from './finite-values.js';
6
+ const targetTypes = {
7
+ string: 'String',
8
+ number: 'Number',
9
+ boolean: 'Boolean',
10
+ array: 'Array',
11
+ record: 'Record',
12
+ richtext: 'String',
13
+ media: 'String',
14
+ link: 'String',
15
+ token: 'String',
16
+ Integer: 'Integer',
17
+ DateTime: 'DateTime',
18
+ Literal: 'Literal',
19
+ RichText: 'RichText',
20
+ JSON: 'JSON',
21
+ ResourceLink: 'ResourceLink',
22
+ DiscriminatedUnion: 'DiscriminatedUnion',
23
+ };
24
+ export function cdfToTarget(d) {
25
+ const t = { type: targetTypes[d.$type] };
26
+ if (own(d, '$required'))
27
+ t.required = d.$required;
28
+ if (own(d, '$description'))
29
+ t.description = d.$description;
30
+ if (own(d, '$default'))
31
+ t.defaultValue = d.$default;
32
+ if (d.$values)
33
+ t.values = d.$values;
34
+ if (own(d, 'value'))
35
+ t.value = d.value;
36
+ if (d.valueType)
37
+ t.valueType = cdfToTarget(d.valueType);
38
+ if (d.discriminator !== undefined)
39
+ t.discriminator = d.discriminator;
40
+ if (d.members)
41
+ t.members = d.members.map(cdfToTarget);
42
+ if (d.allowedResources)
43
+ t.allowedResources = d.allowedResources;
44
+ if (d.$items)
45
+ t.items = cdfToTarget(d.$items);
46
+ if (d.$fields)
47
+ t.fields = Object.fromEntries(Object.entries(d.$fields).map(([key, field]) => [key, cdfToTarget(field)]));
48
+ return t;
49
+ }
50
+ /** Decode complete target metadata without narrowing it to a type name. */
51
+ export function targetToCDF(d) {
52
+ const types = {
53
+ String: 'string',
54
+ Number: 'number',
55
+ Boolean: 'boolean',
56
+ Array: 'array',
57
+ Record: 'record',
58
+ Integer: 'Integer',
59
+ DateTime: 'DateTime',
60
+ Literal: 'Literal',
61
+ RichText: 'RichText',
62
+ JSON: 'JSON',
63
+ ResourceLink: 'ResourceLink',
64
+ DiscriminatedUnion: 'DiscriminatedUnion',
65
+ };
66
+ const field = { $type: types[d.type] };
67
+ if (own(d, 'required'))
68
+ field.$required = d.required;
69
+ if (own(d, 'description'))
70
+ field.$description = d.description;
71
+ if (own(d, 'defaultValue'))
72
+ field.$default = cloneJson(d.defaultValue);
73
+ if (d.values)
74
+ field.$values = cloneJson(d.values);
75
+ if (d.items)
76
+ field.$items = targetToCDF(d.items);
77
+ if (d.fields)
78
+ field.$fields = Object.fromEntries(Object.entries(d.fields).map(([key, child]) => [key, targetToCDF(child)]));
79
+ if (own(d, 'value'))
80
+ field.value = cloneJson(d.value);
81
+ if (d.valueType)
82
+ field.valueType = targetToCDF(d.valueType);
83
+ if (d.discriminator !== undefined)
84
+ field.discriminator = d.discriminator;
85
+ if (d.members)
86
+ field.members = d.members.map(targetToCDF);
87
+ if (d.allowedResources)
88
+ field.allowedResources = cloneJson(d.allowedResources);
89
+ return field;
90
+ }
91
+ /** All recursive grammar children retain their exact paths, including exclusive members. */
92
+ export function cdfDefinitionChildren(definition) {
93
+ return [
94
+ ...(definition.$items ? [{ definition: definition.$items, path: '/$items' }] : []),
95
+ ...(definition.valueType ? [{ definition: definition.valueType, path: '/valueType' }] : []),
96
+ ...Object.entries(definition.$fields ?? {}).map(([key, field]) => ({
97
+ definition: field,
98
+ path: childPath('/$fields', key),
99
+ })),
100
+ ...(definition.members ?? []).map((member, index) => ({ definition: member, path: '/members/' + index })),
101
+ ];
102
+ }
103
+ export function containsCDFDefinitionType(definition, type) {
104
+ return (definition.$type === type ||
105
+ cdfDefinitionChildren(definition).some((child) => containsCDFDefinitionType(child.definition, type)));
106
+ }
107
+ export function validateCDFProperty(input, requireCategory = true) {
108
+ const unsafe = jsonValueIssue(input);
109
+ if (unsafe)
110
+ return { valid: false, issues: [unsafe] };
111
+ const issues = [];
112
+ const walk = (value, path, root, depth) => {
113
+ if (depth > 64 || !value || typeof value !== 'object' || Array.isArray(value)) {
114
+ issues.push({ path, message: 'Expected bounded property definition' });
115
+ return;
116
+ }
117
+ const d = value;
118
+ const add = (key, message) => issues.push({ path: childPath(path, key), message });
119
+ if (!CDF_PROPERTY_TYPES.includes(d.$type)) {
120
+ add('$type', 'Unknown CDF type');
121
+ return;
122
+ }
123
+ if ((root && requireCategory) || own(d, '$category'))
124
+ if (!CDF_PROPERTY_CATEGORIES.includes(d.$category))
125
+ add('$category', 'Unknown category');
126
+ const allowed = new Set(['$type', '$category', '$description', '$required', '$default', '$values']);
127
+ if (root)
128
+ allowed.add('$sourceInput');
129
+ if (own(d, '$sourceInput') &&
130
+ !SourceInputAssociationSchema.safeParse(d.$sourceInput).success)
131
+ add('$sourceInput', 'Invalid source input association');
132
+ if (d.$type === 'array')
133
+ allowed.add('$items');
134
+ if (d.$type === 'record')
135
+ allowed.add('$fields');
136
+ if (['Integer', 'DateTime', 'Literal', 'RichText', 'JSON', 'ResourceLink', 'DiscriminatedUnion'].includes(d.$type) &&
137
+ d.$category !== undefined &&
138
+ d.$category !== 'content')
139
+ add('$category', 'Richer representations require content category');
140
+ if (d.$type === 'Literal') {
141
+ allowed.add('value');
142
+ allowed.add('valueType');
143
+ walk(d.valueType, childPath(path, 'valueType'), false, depth + 1);
144
+ }
145
+ if (d.$type === 'ResourceLink')
146
+ allowed.add('allowedResources');
147
+ if (d.$type === 'DiscriminatedUnion') {
148
+ allowed.add('discriminator');
149
+ allowed.add('members');
150
+ if (!Array.isArray(d.members))
151
+ add('members', 'Expected complete union members');
152
+ else
153
+ d.members.forEach((member, index) => walk(member, childPath(childPath(path, 'members'), index), false, depth + 1));
154
+ }
155
+ if (d.$type === 'token') {
156
+ allowed.add('$token.kind');
157
+ allowed.add('$token.allowed');
158
+ }
159
+ for (const key of Object.keys(d))
160
+ if (!allowed.has(key))
161
+ add(key, 'Unexpected property metadata');
162
+ if (own(d, '$description') && typeof d.$description !== 'string')
163
+ add('$description', 'Expected string');
164
+ if (own(d, '$values')) {
165
+ if (!Array.isArray(d.$values))
166
+ add('$values', 'Expected array');
167
+ else {
168
+ const issue = finiteValuesIssue(d.$type, d.$values);
169
+ if (issue)
170
+ issues.push({
171
+ path: issue.index === undefined
172
+ ? childPath(path, '$values')
173
+ : childPath(childPath(path, '$values'), issue.index),
174
+ message: issue.message,
175
+ });
176
+ }
177
+ }
178
+ if (own(d, '$required') && typeof d.$required !== 'boolean')
179
+ add('$required', 'Expected boolean');
180
+ if (own(d, '$token.kind') && typeof d['$token.kind'] !== 'string')
181
+ add('$token.kind', 'Expected string');
182
+ if (own(d, '$token.allowed') &&
183
+ (!Array.isArray(d['$token.allowed']) || !d['$token.allowed'].every((v) => typeof v === 'string')))
184
+ add('$token.allowed', 'Expected string paths');
185
+ if (d.$type === 'array')
186
+ walk(d.$items, childPath(path, '$items'), false, depth + 1);
187
+ if (d.$type === 'record') {
188
+ if (!d.$fields ||
189
+ typeof d.$fields !== 'object' ||
190
+ Array.isArray(d.$fields) ||
191
+ Object.keys(d.$fields).length === 0)
192
+ add('$fields', 'Expected nonempty field dictionary');
193
+ else
194
+ for (const [key, field] of Object.entries(d.$fields))
195
+ walk(field, childPath(childPath(path, '$fields'), key), false, depth + 1);
196
+ }
197
+ };
198
+ walk(input, '', true, 0);
199
+ if (!issues.length) {
200
+ for (const issue of validateTargetDefinition(cdfToTarget(input)).issues)
201
+ issues.push({
202
+ path: issue.path
203
+ .replace(/\/defaultValue(?=\/|$)/g, '/$default')
204
+ .replace(/\/items(?=\/|$)/g, '/$items')
205
+ .replace(/\/fields(?=\/|$)/g, '/$fields')
206
+ .replace(/\/values(?=\/|$)/g, '/$values'),
207
+ message: issue.message,
208
+ });
209
+ }
210
+ return { valid: !issues.length, issues };
211
+ }
@@ -0,0 +1,6 @@
1
+ import type { CDFComponentEntry, CDFValidationError } from './types.js';
2
+ /** Check saved plan integrity and exact physical closure independently of mapper availability. */
3
+ export declare function validateRepresentationClosure(components: Array<{
4
+ key: string;
5
+ entry: CDFComponentEntry;
6
+ }>): CDFValidationError[];
@@ -0,0 +1,182 @@
1
+ import { contentDigest } from '../property-contract/content-digest.js';
2
+ import { childPath, cloneJson, jsonEqual, own } from '../property-contract/json-value.js';
3
+ import { validateRepresentationPlan } from '../property-contract/representation-plan.js';
4
+ /** Check saved plan integrity and exact physical closure independently of mapper availability. */
5
+ export function validateRepresentationClosure(components) {
6
+ const errors = [];
7
+ const entries = new Map(components.map((component) => [component.key, component.entry]));
8
+ const claimed = new Set();
9
+ const owners = new Set();
10
+ if (entries.size !== components.length)
11
+ errors.push({ path: '/', message: 'Duplicate complete component identity' });
12
+ for (const { key, entry } of components) {
13
+ for (const [property, plan] of Object.entries(entry.$representationPlans ?? {})) {
14
+ const path = childPath(childPath(childPath('', key), '$representationPlans'), property);
15
+ const fail = (message) => {
16
+ errors.push({ path, message });
17
+ };
18
+ try {
19
+ for (const issue of validateRepresentationPlan(plan, plan.sourceModel, plan.contextSnapshot))
20
+ fail(issue.message);
21
+ if (owners.has(plan.identity.propertyId))
22
+ fail('Duplicate representation source owner');
23
+ owners.add(plan.identity.propertyId);
24
+ const input = cloneJson(plan);
25
+ const { id: _id, ...unsigned } = input;
26
+ for (const generated of unsigned.models) {
27
+ delete generated.entry.$model;
28
+ for (const slot of Object.values(generated.entry.$slots ?? {}))
29
+ if (slot.$modelBinding)
30
+ slot.$modelBinding.planId = '';
31
+ }
32
+ if (unsigned.attachment?.definition.$modelBinding)
33
+ unsigned.attachment.definition.$modelBinding.planId = '';
34
+ if (contentDigest(unsigned) !== plan.id)
35
+ fail('Stale or altered representation plan identity');
36
+ if (plan.identity.key !== property ||
37
+ plan.identity.propertyId !== JSON.stringify([plan.identity.componentId, property]))
38
+ fail('Representation property owner disagrees');
39
+ const graph = plan.logicalGraph;
40
+ const nodes = new Map(graph.nodes.map((node) => [node.id, node]));
41
+ const mappings = new Map(plan.mappings.map((mapping) => [mapping.nodeId, mapping]));
42
+ if (graph.authority !== 'compiler' ||
43
+ graph.rootId !== plan.identity.nodeId ||
44
+ !nodes.has(graph.rootId) ||
45
+ nodes.size !== graph.nodes.length ||
46
+ mappings.size !== plan.mappings.length ||
47
+ mappings.size !== nodes.size)
48
+ fail('Incomplete or duplicate logical source accounting');
49
+ const reached = new Set();
50
+ const pending = [graph.rootId];
51
+ while (pending.length) {
52
+ const nodeId = pending.pop();
53
+ if (reached.has(nodeId))
54
+ continue;
55
+ reached.add(nodeId);
56
+ const node = nodes.get(nodeId);
57
+ if (!node) {
58
+ fail('Dangling logical source relationship');
59
+ continue;
60
+ }
61
+ const mapping = mappings.get(nodeId);
62
+ if (!mapping ||
63
+ mapping.sourceNodeId !== node.sourceNodeId ||
64
+ !jsonEqual(mapping.path, node.path) ||
65
+ mapping.required !== node.required ||
66
+ mapping.readonly !== node.readonly ||
67
+ mapping.occurrenceReadonly !== node.occurrenceReadonly ||
68
+ mapping.intrinsicReadonly !== node.intrinsicReadonly ||
69
+ mapping.nullable !== (node.nullable ?? null) ||
70
+ mapping.undefinable !== (node.undefinable ?? null) ||
71
+ !jsonEqual(mapping.defaultIntent, node.defaultIntent))
72
+ fail('Mapping changes retained source occurrence evidence');
73
+ if (mapping?.targetNodeId && !nodes.has(mapping.targetNodeId))
74
+ fail('Dangling source mapping target');
75
+ if (mapping?.disposition === 'reconstruction') {
76
+ const operation = mapping.reconstruction;
77
+ if (!operation)
78
+ fail('Missing source reconstruction operation');
79
+ else if (operation.kind === 'fixed-value') {
80
+ if (!node.required ||
81
+ node.nullable !== false ||
82
+ node.undefinable !== false ||
83
+ node.domain.provenance !== 'compiler' ||
84
+ node.domain.openness !== 'closed' ||
85
+ node.domain.declaredValues.length !== 1 ||
86
+ !jsonEqual(node.domain.declaredValues[0], operation.value))
87
+ fail('Fixed reconstruction changes the intrinsic source value or presence');
88
+ }
89
+ else if (operation.kind === 'object-envelope') {
90
+ if (node.kind !== 'record' ||
91
+ node.fields?.length !== 1 ||
92
+ node.fields[0].key !== operation.field ||
93
+ node.fields[0].nodeId !== operation.valueNodeId)
94
+ fail('Object reconstruction changes source shape');
95
+ }
96
+ else if (operation.kind === 'ordered-array') {
97
+ if (node.kind !== 'array' || node.items?.length !== 1 || node.items[0] !== operation.itemNodeId)
98
+ fail('Array reconstruction changes source shape');
99
+ }
100
+ else if (operation.kind === 'optional-value') {
101
+ const values = (node.alternatives ?? []).filter((id) => !['null', 'undefined', 'never'].includes(nodes.get(id)?.kind ?? ''));
102
+ if (node.kind !== 'union' || values.length !== 1 || values[0] !== operation.valueNodeId)
103
+ fail('Optional reconstruction changes source alternatives');
104
+ }
105
+ else if (operation.kind === 'empty-value' && operation.state !== node.kind)
106
+ fail('Empty reconstruction changes source state');
107
+ }
108
+ else if (mapping?.reconstruction)
109
+ fail('Reconstruction operation has no reconstruction disposition');
110
+ pending.push(...(node.fields?.map((field) => field.nodeId) ?? []), ...(node.items ?? []), ...(node.alternatives ?? []), ...(node.indexSignatures?.map((signature) => signature.valueNodeId) ?? []));
111
+ }
112
+ if (reached.size !== nodes.size)
113
+ fail('Disconnected logical source accounting');
114
+ if (plan.sdkValid &&
115
+ (plan.issues.length ||
116
+ plan.mappings.some((mapping) => mapping.disposition === 'held') ||
117
+ graph.coverage !== 'complete-within-declared-bound'))
118
+ fail('Representation validity contradicts held source evidence');
119
+ if (plan.kind === 'native') {
120
+ if (plan.models.length || plan.attachment || !jsonEqual(entry.$properties[property], plan.nativeDefinition))
121
+ fail('Native representation disagrees with the complete component');
122
+ }
123
+ else if (!plan.attachment ||
124
+ own(entry.$properties, property) ||
125
+ !jsonEqual(entry.$slots?.[plan.attachment.slot], plan.attachment.definition))
126
+ fail('Model attachment disagrees with the complete component');
127
+ const models = new Map(plan.models.map((generated) => [generated.key, generated]));
128
+ if (models.size !== plan.models.length)
129
+ fail('Duplicate representation model identity');
130
+ for (const generated of plan.models) {
131
+ const binding = generated.entry.$model;
132
+ if (!binding ||
133
+ binding.planId !== plan.id ||
134
+ binding.componentId !== plan.identity.componentId ||
135
+ binding.propertyId !== plan.identity.propertyId ||
136
+ binding.nodeId !== generated.nodeId ||
137
+ binding.sourceNodeId !== nodes.get(generated.nodeId)?.sourceNodeId)
138
+ fail('Model source owner disagrees');
139
+ if (!jsonEqual(entries.get(generated.key), generated.entry))
140
+ fail('Missing or altered representation model definition');
141
+ if (claimed.has(generated.key))
142
+ fail('Duplicate representation model owner');
143
+ claimed.add(generated.key);
144
+ for (const slot of Object.values(generated.entry.$slots ?? {})) {
145
+ const owner = slot.$modelBinding;
146
+ if (!owner ||
147
+ owner.planId !== plan.id ||
148
+ owner.componentId !== plan.identity.componentId ||
149
+ owner.propertyId !== plan.identity.propertyId ||
150
+ owner.sourceNodeId !== nodes.get(owner.nodeId)?.sourceNodeId ||
151
+ (slot.$allowedComponents ?? []).some((allowed) => !models.has(allowed)))
152
+ fail('Model relationship changes source ownership or closure');
153
+ }
154
+ }
155
+ const physical = new Set();
156
+ const dependencies = [...(plan.attachment?.definition.$allowedComponents ?? [])];
157
+ while (dependencies.length) {
158
+ const modelKey = dependencies.pop();
159
+ if (physical.has(modelKey))
160
+ continue;
161
+ physical.add(modelKey);
162
+ const generated = models.get(modelKey);
163
+ if (!generated) {
164
+ fail('Missing representation dependency');
165
+ continue;
166
+ }
167
+ for (const slot of Object.values(generated.entry.$slots ?? {}))
168
+ dependencies.push(...(slot.$allowedComponents ?? []));
169
+ }
170
+ if (physical.size !== models.size)
171
+ fail('Orphan representation dependency');
172
+ }
173
+ catch {
174
+ fail('Malformed complete representation metadata');
175
+ }
176
+ }
177
+ }
178
+ for (const { key, entry } of components)
179
+ if (entry.$model && !claimed.has(key))
180
+ errors.push({ path: childPath('', key), message: 'Model definition has no complete source owner' });
181
+ return errors;
182
+ }