@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.
- package/README.md +15 -0
- package/dist/package.json +1 -1
- package/dist/src/agent-process-lifecycle.d.ts +26 -0
- package/dist/src/agent-process-lifecycle.js +171 -0
- package/dist/src/agent-prompt-artifact.d.ts +8 -0
- package/dist/src/agent-prompt-artifact.js +37 -0
- package/dist/src/agent-prompt-transport.d.ts +1 -0
- package/dist/src/agent-prompt-transport.js +4 -0
- package/dist/src/cdf/build.d.ts +9 -2
- package/dist/src/cdf/build.js +134 -17
- package/dist/src/cdf/finite-values.d.ts +8 -0
- package/dist/src/cdf/finite-values.js +21 -0
- package/dist/src/cdf/index.d.ts +2 -1
- package/dist/src/cdf/index.js +1 -0
- package/dist/src/cdf/property-validation.d.ts +16 -0
- package/dist/src/cdf/property-validation.js +211 -0
- package/dist/src/cdf/representation-validation.d.ts +6 -0
- package/dist/src/cdf/representation-validation.js +182 -0
- package/dist/src/cdf/schema.d.ts +778 -20
- package/dist/src/cdf/schema.js +362 -5
- package/dist/src/cdf/types.d.ts +57 -59
- package/dist/src/cdf/types.js +72 -15
- package/dist/src/cdf/validate.js +31 -1
- package/dist/src/cdf/vocabularies.d.ts +1 -1
- package/dist/src/cdf/vocabularies.js +9 -1
- package/dist/src/config/index.js +6 -3
- package/dist/src/exploration/constants.d.ts +10 -0
- package/dist/src/exploration/constants.js +18 -0
- package/dist/src/exploration/index.d.ts +3 -0
- package/dist/src/exploration/index.js +3 -0
- package/dist/src/exploration/schemas.d.ts +201 -0
- package/dist/src/exploration/schemas.js +126 -0
- package/dist/src/exploration/types.d.ts +393 -0
- package/dist/src/exploration/types.js +1 -0
- package/dist/src/handoff/index.d.ts +1 -1
- package/dist/src/index.d.ts +15 -1
- package/dist/src/index.js +12 -4
- package/dist/src/property-contract/analysis-representation-candidates.d.ts +19 -0
- package/dist/src/property-contract/analysis-representation-candidates.js +45 -0
- package/dist/src/property-contract/classification.d.ts +38 -0
- package/dist/src/property-contract/classification.js +84 -0
- package/dist/src/property-contract/compact-model-components.d.ts +16 -0
- package/dist/src/property-contract/compact-model-components.js +113 -0
- package/dist/src/property-contract/content-digest.d.ts +2 -0
- package/dist/src/property-contract/content-digest.js +69 -0
- package/dist/src/property-contract/decision-protocol.d.ts +45 -0
- package/dist/src/property-contract/decision-protocol.js +251 -0
- package/dist/src/property-contract/definition-import-rules.d.ts +10 -0
- package/dist/src/property-contract/definition-import-rules.js +105 -0
- package/dist/src/property-contract/definition-import.d.ts +13 -0
- package/dist/src/property-contract/definition-import.js +52 -0
- package/dist/src/property-contract/discriminated-union-source.d.ts +17 -0
- package/dist/src/property-contract/discriminated-union-source.js +180 -0
- package/dist/src/property-contract/import-prerequisites.d.ts +70 -0
- package/dist/src/property-contract/import-prerequisites.js +208 -0
- package/dist/src/property-contract/index.d.ts +18 -1
- package/dist/src/property-contract/index.js +18 -6
- package/dist/src/property-contract/logical-source-graph.d.ts +36 -0
- package/dist/src/property-contract/logical-source-graph.js +240 -0
- package/dist/src/property-contract/mapper-readiness.d.ts +6 -0
- package/dist/src/property-contract/mapper-readiness.js +34 -0
- package/dist/src/property-contract/model-exploration.d.ts +42 -0
- package/dist/src/property-contract/model-exploration.js +94 -0
- package/dist/src/property-contract/native-source-region.d.ts +5 -0
- package/dist/src/property-contract/native-source-region.js +125 -0
- package/dist/src/property-contract/null-handling.d.ts +10 -0
- package/dist/src/property-contract/null-handling.js +45 -0
- package/dist/src/property-contract/physical-property-import.d.ts +6 -0
- package/dist/src/property-contract/physical-property-import.js +55 -0
- package/dist/src/property-contract/property-decision.d.ts +14 -0
- package/dist/src/property-contract/property-decision.js +155 -0
- package/dist/src/property-contract/property-model.d.ts +197 -0
- package/dist/src/property-contract/property-model.js +833 -0
- package/dist/src/property-contract/representation-plan.d.ts +133 -0
- package/dist/src/property-contract/representation-plan.js +756 -0
- package/dist/src/property-contract/representation-proposal.d.ts +65 -0
- package/dist/src/property-contract/representation-proposal.js +204 -0
- package/dist/src/property-contract/resource-link-schema.d.ts +55 -0
- package/dist/src/property-contract/resource-link-schema.js +28 -0
- package/dist/src/property-contract/source-representation.d.ts +9 -0
- package/dist/src/property-contract/source-representation.js +79 -0
- package/dist/src/property-contract/target-definition.d.ts +17 -16
- package/dist/src/property-contract/target-definition.js +4 -26
- package/dist/src/schema-export.d.ts +30 -0
- package/dist/src/schema-export.js +285 -0
- package/dist/src/shared-source.d.ts +105 -0
- package/dist/src/shared-source.js +1 -0
- package/dist/src/source-input-association.d.ts +23 -0
- package/dist/src/source-input-association.js +52 -0
- package/dist/src/sources-api/preview/index.d.ts +37 -3
- package/dist/src/sources-api/preview/index.js +39 -1
- package/dist/src/sources-api/preview/schema.d.ts +2 -0
- package/dist/src/sources-api/preview/schema.js +74 -0
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { cloneJson } from './json-value.js';
|
|
2
|
+
/** Compact physical authoring scaffolding while retaining every logical source node. */
|
|
3
|
+
export function compactModelComponents({ nodes, models, mappings, attachment, }) {
|
|
4
|
+
const wrappers = new Map();
|
|
5
|
+
for (const model of models.values()) {
|
|
6
|
+
const node = nodes.get(model.nodeId);
|
|
7
|
+
const slots = Object.entries(model.entry.$slots ?? {});
|
|
8
|
+
if (Object.keys(model.entry.$properties).length || slots.length !== 1 || node.branchConditions?.length)
|
|
9
|
+
continue;
|
|
10
|
+
const [name, slot] = slots[0];
|
|
11
|
+
if (slot.$allowedComponents?.length !== 1 || !slot.$cardinality)
|
|
12
|
+
continue;
|
|
13
|
+
const childKey = slot.$allowedComponents[0];
|
|
14
|
+
const childNodeId = slot.$modelBinding?.nodeId;
|
|
15
|
+
if (!childNodeId)
|
|
16
|
+
continue;
|
|
17
|
+
let reconstruction;
|
|
18
|
+
if (node.kind === 'union' && !node.unionBranches && name === 'value') {
|
|
19
|
+
const alternatives = node.alternatives ?? [];
|
|
20
|
+
const values = alternatives.filter((id) => !['null', 'undefined', 'never'].includes(nodes.get(id).kind));
|
|
21
|
+
if (values.length === 1 && values[0] === childNodeId)
|
|
22
|
+
reconstruction = { kind: 'optional-value', valueNodeId: childNodeId };
|
|
23
|
+
}
|
|
24
|
+
else if (node.kind === 'record' &&
|
|
25
|
+
node.fields?.length === 1 &&
|
|
26
|
+
!node.indexSignatures?.length &&
|
|
27
|
+
!node.callSignatures?.length &&
|
|
28
|
+
node.fields[0].key === name &&
|
|
29
|
+
node.fields[0].nodeId === childNodeId)
|
|
30
|
+
reconstruction = { kind: 'object-envelope', field: name, valueNodeId: childNodeId };
|
|
31
|
+
else if (node.kind === 'array' && node.items?.length === 1 && name === 'items' && node.items[0] === childNodeId)
|
|
32
|
+
reconstruction = { kind: 'ordered-array', itemNodeId: childNodeId };
|
|
33
|
+
if (reconstruction)
|
|
34
|
+
wrappers.set(model.key, { childKey, slot, reconstruction });
|
|
35
|
+
}
|
|
36
|
+
const repeated = new Set();
|
|
37
|
+
for (const model of models.values())
|
|
38
|
+
for (const slot of Object.values(model.entry.$slots ?? {}))
|
|
39
|
+
if (slot.$cardinality?.max !== 1)
|
|
40
|
+
for (const key of slot.$allowedComponents ?? [])
|
|
41
|
+
repeated.add(key);
|
|
42
|
+
// Transparent envelopes inherit their incoming element boundary; nested collections retain theirs.
|
|
43
|
+
let changed = true;
|
|
44
|
+
while (changed) {
|
|
45
|
+
changed = false;
|
|
46
|
+
for (const [key, wrapper] of wrappers)
|
|
47
|
+
if (repeated.has(key) && wrapper.reconstruction.kind !== 'ordered-array' && !repeated.has(wrapper.childKey)) {
|
|
48
|
+
repeated.add(wrapper.childKey);
|
|
49
|
+
changed = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const [key, wrapper] of wrappers)
|
|
53
|
+
if (repeated.has(key) &&
|
|
54
|
+
(wrapper.reconstruction.kind !== 'object-envelope' || wrapper.slot.$cardinality.min === 0))
|
|
55
|
+
wrappers.delete(key);
|
|
56
|
+
// A recursive chain of envelopes describes actual nesting, so it needs an instance boundary.
|
|
57
|
+
for (const key of [...wrappers.keys()]) {
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
let current = key;
|
|
60
|
+
while (wrappers.has(current)) {
|
|
61
|
+
if (visited.has(current)) {
|
|
62
|
+
for (const member of visited)
|
|
63
|
+
wrappers.delete(member);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
visited.add(current);
|
|
67
|
+
current = wrappers.get(current).childKey;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const compactSlot = (slot) => {
|
|
71
|
+
if (slot.$allowedComponents?.length !== 1 || !slot.$cardinality)
|
|
72
|
+
return;
|
|
73
|
+
let key = slot.$allowedComponents[0];
|
|
74
|
+
let cardinality = cloneJson(slot.$cardinality);
|
|
75
|
+
while (wrappers.has(key)) {
|
|
76
|
+
const wrapper = wrappers.get(key);
|
|
77
|
+
const child = wrapper.slot.$cardinality;
|
|
78
|
+
if (wrapper.reconstruction.kind === 'ordered-array')
|
|
79
|
+
cardinality = { min: child.min, max: child.max, ordered: true };
|
|
80
|
+
else if (!cardinality.ordered)
|
|
81
|
+
cardinality = { ...cardinality, min: cardinality.min === 0 || child.min === 0 ? 0 : cardinality.min };
|
|
82
|
+
key = wrapper.childKey;
|
|
83
|
+
}
|
|
84
|
+
slot.$allowedComponents = [key];
|
|
85
|
+
slot.$cardinality = cardinality;
|
|
86
|
+
slot.$required = cardinality.min > 0;
|
|
87
|
+
};
|
|
88
|
+
compactSlot(attachment);
|
|
89
|
+
for (const model of models.values())
|
|
90
|
+
if (!wrappers.has(model.key))
|
|
91
|
+
for (const slot of Object.values(model.entry.$slots ?? {}))
|
|
92
|
+
compactSlot(slot);
|
|
93
|
+
for (const [key, wrapper] of wrappers) {
|
|
94
|
+
const model = models.get(key);
|
|
95
|
+
const mapping = mappings.get(model.nodeId);
|
|
96
|
+
delete mapping.componentKey;
|
|
97
|
+
delete mapping.slot;
|
|
98
|
+
mapping.disposition = 'reconstruction';
|
|
99
|
+
mapping.reconstruction = cloneJson(wrapper.reconstruction);
|
|
100
|
+
if (wrapper.reconstruction.kind === 'optional-value')
|
|
101
|
+
for (const id of nodes.get(model.nodeId).alternatives ?? []) {
|
|
102
|
+
const node = nodes.get(id);
|
|
103
|
+
if (!['null', 'undefined', 'never'].includes(node.kind))
|
|
104
|
+
continue;
|
|
105
|
+
const empty = mappings.get(id);
|
|
106
|
+
delete empty.componentKey;
|
|
107
|
+
delete empty.slot;
|
|
108
|
+
empty.disposition = 'reconstruction';
|
|
109
|
+
empty.reconstruction = { kind: 'empty-value', state: node.kind };
|
|
110
|
+
}
|
|
111
|
+
models.delete(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsonValueIssue } from './json-value.js';
|
|
2
|
+
/** Stable SHA-256 identity for plain source/plan data in Node and browser runtimes. */
|
|
3
|
+
export function contentDigest(value) {
|
|
4
|
+
const issue = jsonValueIssue(value);
|
|
5
|
+
if (issue)
|
|
6
|
+
throw new Error(issue.message);
|
|
7
|
+
const canonical = (input) => {
|
|
8
|
+
if (Array.isArray(input))
|
|
9
|
+
return '[' + input.map(canonical).join(',') + ']';
|
|
10
|
+
if (input && typeof input === 'object')
|
|
11
|
+
return ('{' +
|
|
12
|
+
Object.keys(input)
|
|
13
|
+
.sort()
|
|
14
|
+
.map((key) => JSON.stringify(key) + ':' + canonical(input[key]))
|
|
15
|
+
.join(',') +
|
|
16
|
+
'}');
|
|
17
|
+
return JSON.stringify(input);
|
|
18
|
+
};
|
|
19
|
+
const bytes = new TextEncoder().encode(canonical(value));
|
|
20
|
+
const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
|
|
21
|
+
padded.set(bytes);
|
|
22
|
+
padded[bytes.length] = 128;
|
|
23
|
+
const view = new DataView(padded.buffer);
|
|
24
|
+
view.setUint32(padded.length - 8, Math.floor(bytes.length / 0x20000000));
|
|
25
|
+
view.setUint32(padded.length - 4, bytes.length * 8);
|
|
26
|
+
const primes = [];
|
|
27
|
+
for (let candidate = 2; primes.length < 64; candidate++)
|
|
28
|
+
if (!primes.some((prime) => prime * prime <= candidate && candidate % prime === 0))
|
|
29
|
+
primes.push(candidate);
|
|
30
|
+
const initial = primes.slice(0, 8).map((prime) => ((Math.sqrt(prime) % 1) * 0x100000000) >>> 0);
|
|
31
|
+
const constants = primes.map((prime) => ((Math.cbrt(prime) % 1) * 0x100000000) >>> 0);
|
|
32
|
+
const rotate = (word, bits) => (word >>> bits) | (word << (32 - bits));
|
|
33
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
34
|
+
const words = new Uint32Array(64);
|
|
35
|
+
for (let i = 0; i < 16; i++)
|
|
36
|
+
words[i] = view.getUint32(offset + i * 4);
|
|
37
|
+
for (let i = 16; i < 64; i++) {
|
|
38
|
+
const x = words[i - 15], y = words[i - 2];
|
|
39
|
+
words[i] =
|
|
40
|
+
(words[i - 16] +
|
|
41
|
+
(rotate(x, 7) ^ rotate(x, 18) ^ (x >>> 3)) +
|
|
42
|
+
words[i - 7] +
|
|
43
|
+
(rotate(y, 17) ^ rotate(y, 19) ^ (y >>> 10))) >>>
|
|
44
|
+
0;
|
|
45
|
+
}
|
|
46
|
+
let [a, b, c, d, e, f, g, h] = initial;
|
|
47
|
+
for (let i = 0; i < 64; i++) {
|
|
48
|
+
const first = (h +
|
|
49
|
+
(rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) +
|
|
50
|
+
((e & f) ^ (~e & g)) +
|
|
51
|
+
constants[i] +
|
|
52
|
+
words[i]) >>>
|
|
53
|
+
0;
|
|
54
|
+
const second = ((rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
|
55
|
+
h = g;
|
|
56
|
+
g = f;
|
|
57
|
+
f = e;
|
|
58
|
+
e = (d + first) >>> 0;
|
|
59
|
+
d = c;
|
|
60
|
+
c = b;
|
|
61
|
+
b = a;
|
|
62
|
+
a = (first + second) >>> 0;
|
|
63
|
+
}
|
|
64
|
+
[a, b, c, d, e, f, g, h].forEach((word, i) => {
|
|
65
|
+
initial[i] = (initial[i] + word) >>> 0;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return initial.map((word) => word.toString(16).padStart(8, '0')).join('');
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ClassifiedPropertyCall } from './classification.js';
|
|
2
|
+
export interface ComponentClassificationCall {
|
|
3
|
+
tool: 'classify_component';
|
|
4
|
+
description?: string;
|
|
5
|
+
rationale?: {
|
|
6
|
+
description?: string;
|
|
7
|
+
props?: string;
|
|
8
|
+
slots?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface SlotClassificationCall {
|
|
12
|
+
tool: 'classify_slot';
|
|
13
|
+
slot: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
allowed_components?: string[];
|
|
16
|
+
description?: string;
|
|
17
|
+
rationale?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PropertyExclusionCall {
|
|
20
|
+
tool: 'exclude_prop';
|
|
21
|
+
prop: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
export type ClassificationToolCall = ClassifiedPropertyCall | ComponentClassificationCall | SlotClassificationCall | PropertyExclusionCall;
|
|
25
|
+
export interface NormalizedClassificationCalls {
|
|
26
|
+
calls: ClassificationToolCall[];
|
|
27
|
+
warnings: string[];
|
|
28
|
+
}
|
|
29
|
+
/** Wire and direct calls use the same descriptor-checked normalization. */
|
|
30
|
+
export declare function normalizeClassificationCalls(values: readonly unknown[]): NormalizedClassificationCalls;
|
|
31
|
+
export declare function parseClassificationToolLines(stdout: string): NormalizedClassificationCalls;
|
|
32
|
+
export interface DecisionInventory {
|
|
33
|
+
properties: readonly string[];
|
|
34
|
+
slots: readonly {
|
|
35
|
+
name: string;
|
|
36
|
+
sourceInput?: string;
|
|
37
|
+
}[];
|
|
38
|
+
}
|
|
39
|
+
export interface DecisionAccounting {
|
|
40
|
+
complete: boolean;
|
|
41
|
+
accepted: ClassificationToolCall[];
|
|
42
|
+
missing: string[];
|
|
43
|
+
issues: string[];
|
|
44
|
+
}
|
|
45
|
+
export declare function accountClassificationCalls(inventory: DecisionInventory, calls: readonly ClassificationToolCall[]): DecisionAccounting;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { CDF_PROPERTY_TYPES } from '../cdf/vocabularies.js';
|
|
2
|
+
import { parseClassificationMetadata, parseClassificationDefaultMetadata, } from './classification.js';
|
|
3
|
+
import { cloneJson, jsonValueIssue, parseStrictJson, own } from './json-value.js';
|
|
4
|
+
import { validateProposalDraft } from './representation-proposal.js';
|
|
5
|
+
import { isSourceBoundRepresentation } from './source-representation.js';
|
|
6
|
+
import { validateNullHandling } from './null-handling.js';
|
|
7
|
+
const fields = {
|
|
8
|
+
classify_component: ['description', 'rationale'],
|
|
9
|
+
classify_slot: ['slot', 'required', 'allowed_components', 'description', 'rationale'],
|
|
10
|
+
exclude_prop: ['prop', 'reason'],
|
|
11
|
+
classify_prop: [
|
|
12
|
+
'prop',
|
|
13
|
+
'cdf_type',
|
|
14
|
+
'cdf_category',
|
|
15
|
+
'values',
|
|
16
|
+
'token_kind',
|
|
17
|
+
'required',
|
|
18
|
+
'description',
|
|
19
|
+
'default',
|
|
20
|
+
'default_intent',
|
|
21
|
+
'items',
|
|
22
|
+
'fields',
|
|
23
|
+
'source_revision',
|
|
24
|
+
'source_node_id',
|
|
25
|
+
'reason',
|
|
26
|
+
'value',
|
|
27
|
+
'valueType',
|
|
28
|
+
'discriminator',
|
|
29
|
+
'members',
|
|
30
|
+
'allowedResources',
|
|
31
|
+
'representation_proposal',
|
|
32
|
+
'representation_selection',
|
|
33
|
+
'null_handling',
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
/** Wire and direct calls use the same descriptor-checked normalization. */
|
|
37
|
+
export function normalizeClassificationCalls(values) {
|
|
38
|
+
const outerIssue = jsonValueIssue(values);
|
|
39
|
+
if (outerIssue)
|
|
40
|
+
return { calls: [], warnings: [`${outerIssue.path}: ${outerIssue.message}`] };
|
|
41
|
+
const calls = [];
|
|
42
|
+
const warnings = [];
|
|
43
|
+
for (const [index, value] of values.entries()) {
|
|
44
|
+
let subject = '';
|
|
45
|
+
try {
|
|
46
|
+
const unsafe = jsonValueIssue(value);
|
|
47
|
+
if (unsafe)
|
|
48
|
+
throw new Error(`${unsafe.path}: ${unsafe.message}`);
|
|
49
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
50
|
+
throw new Error('Expected tool object');
|
|
51
|
+
const rec = value;
|
|
52
|
+
if (typeof rec.tool !== 'string' || !own(fields, rec.tool))
|
|
53
|
+
throw new Error('Unknown tool');
|
|
54
|
+
const tool = rec.tool;
|
|
55
|
+
if ((tool === 'classify_prop' || tool === 'exclude_prop') && typeof rec.prop === 'string')
|
|
56
|
+
subject = ` property:${rec.prop}:`;
|
|
57
|
+
else if (tool === 'classify_slot' && typeof rec.slot === 'string')
|
|
58
|
+
subject = ` slot:${rec.slot}:`;
|
|
59
|
+
const allowed = fields[tool];
|
|
60
|
+
if (Object.keys(rec).some((key) => key !== 'tool' && !allowed.includes(key)))
|
|
61
|
+
throw new Error('Unknown decision field');
|
|
62
|
+
for (const key of ['prop', 'slot', 'description', 'reason', 'token_kind', 'source_revision', 'source_node_id'])
|
|
63
|
+
if (own(rec, key) && typeof rec[key] !== 'string')
|
|
64
|
+
throw new Error(`${key} must be a string`);
|
|
65
|
+
if (own(rec, 'required') && typeof rec.required !== 'boolean')
|
|
66
|
+
throw new Error('required must be Boolean');
|
|
67
|
+
if (tool === 'classify_prop' || tool === 'exclude_prop')
|
|
68
|
+
if (typeof rec.prop !== 'string' || !rec.prop)
|
|
69
|
+
throw new Error('Missing exact property name');
|
|
70
|
+
if (tool === 'exclude_prop' && (typeof rec.reason !== 'string' || !rec.reason.trim()))
|
|
71
|
+
throw new Error('Exclusion requires nonblank reason');
|
|
72
|
+
if (tool === 'classify_slot') {
|
|
73
|
+
if (typeof rec.slot !== 'string' || !rec.slot)
|
|
74
|
+
throw new Error('Missing exact slot name');
|
|
75
|
+
if (own(rec, 'rationale') && typeof rec.rationale !== 'string')
|
|
76
|
+
throw new Error('rationale must be a string');
|
|
77
|
+
if (own(rec, 'allowed_components') &&
|
|
78
|
+
(!Array.isArray(rec.allowed_components) ||
|
|
79
|
+
!rec.allowed_components.every((entry) => typeof entry === 'string') ||
|
|
80
|
+
new Set(rec.allowed_components).size !== rec.allowed_components.length))
|
|
81
|
+
throw new Error('Invalid allowed components');
|
|
82
|
+
}
|
|
83
|
+
if (tool === 'classify_component' && own(rec, 'rationale')) {
|
|
84
|
+
const rationale = rec.rationale;
|
|
85
|
+
if (!rationale ||
|
|
86
|
+
typeof rationale !== 'object' ||
|
|
87
|
+
Array.isArray(rationale) ||
|
|
88
|
+
Object.entries(rationale).some(([key, entry]) => !['description', 'props', 'slots'].includes(key) || typeof entry !== 'string'))
|
|
89
|
+
throw new Error('Invalid component rationale');
|
|
90
|
+
}
|
|
91
|
+
const call = cloneJson(rec);
|
|
92
|
+
if (call.tool === 'classify_prop') {
|
|
93
|
+
if (!['content', 'design', 'state'].includes(call.cdf_category))
|
|
94
|
+
throw new Error('Invalid semantic category');
|
|
95
|
+
if (own(rec, 'null_handling')) {
|
|
96
|
+
const issues = validateNullHandling(rec.null_handling);
|
|
97
|
+
if (issues.length)
|
|
98
|
+
throw new Error(issues.map((issue) => `${issue.path}: ${issue.message}`).join('; '));
|
|
99
|
+
if (!call.source_revision?.trim() || !call.source_node_id?.trim())
|
|
100
|
+
throw new Error('Null handling requires the current source revision and property root node');
|
|
101
|
+
}
|
|
102
|
+
if (own(rec, 'representation_selection')) {
|
|
103
|
+
const selection = call.representation_selection;
|
|
104
|
+
if (!selection ||
|
|
105
|
+
typeof selection !== 'object' ||
|
|
106
|
+
Array.isArray(selection) ||
|
|
107
|
+
selection.strategy !== 'component-model' ||
|
|
108
|
+
typeof selection.candidateId !== 'string' ||
|
|
109
|
+
!selection.candidateId.trim() ||
|
|
110
|
+
Object.keys(selection).some((key) => !['strategy', 'candidateId'].includes(key)))
|
|
111
|
+
throw new Error('Invalid model representation selection');
|
|
112
|
+
if (!call.source_revision?.trim() || !call.source_node_id?.trim())
|
|
113
|
+
throw new Error('Model selection requires the current source revision and property root node');
|
|
114
|
+
if (!call.reason?.trim())
|
|
115
|
+
throw new Error('Model selection requires a source-grounded reason');
|
|
116
|
+
if ([
|
|
117
|
+
'cdf_type',
|
|
118
|
+
'values',
|
|
119
|
+
'token_kind',
|
|
120
|
+
'items',
|
|
121
|
+
'fields',
|
|
122
|
+
'value',
|
|
123
|
+
'valueType',
|
|
124
|
+
'discriminator',
|
|
125
|
+
'members',
|
|
126
|
+
'allowedResources',
|
|
127
|
+
'representation_proposal',
|
|
128
|
+
].some((key) => own(rec, key)))
|
|
129
|
+
throw new Error('Model selection cannot also select a native property definition');
|
|
130
|
+
const issues = parseClassificationDefaultMetadata(rec, call);
|
|
131
|
+
if (issues.length)
|
|
132
|
+
throw new Error(issues.join('; '));
|
|
133
|
+
}
|
|
134
|
+
else if (own(rec, 'cdf_type')) {
|
|
135
|
+
if (typeof rec.cdf_type !== 'string' || !CDF_PROPERTY_TYPES.includes(rec.cdf_type))
|
|
136
|
+
throw new Error('Invalid representation type');
|
|
137
|
+
if (own(rec, 'token_kind') && rec.cdf_type !== 'token')
|
|
138
|
+
throw new Error('Token kind requires token representation');
|
|
139
|
+
if (own(rec, 'values') && rec.cdf_type === 'token')
|
|
140
|
+
throw new Error('Token options require a token mapping decision');
|
|
141
|
+
const issues = parseClassificationMetadata(rec, call);
|
|
142
|
+
if (issues.length)
|
|
143
|
+
throw new Error(issues.join('; '));
|
|
144
|
+
if (own(rec, 'representation_proposal')) {
|
|
145
|
+
const issues = validateProposalDraft(call.representation_proposal);
|
|
146
|
+
if (issues.length)
|
|
147
|
+
throw new Error(`property:${call.prop}: ` +
|
|
148
|
+
issues.map((issue) => `/representation_proposal${issue.path}: ${issue.message}`).join('; '));
|
|
149
|
+
if (!call.representation_proposal ||
|
|
150
|
+
call.representation_proposal.desiredType !== call.cdf_type ||
|
|
151
|
+
call.cdf_category !== 'content' ||
|
|
152
|
+
call.representation_proposal.key !== call.prop ||
|
|
153
|
+
call.representation_proposal.name !== call.prop)
|
|
154
|
+
throw new Error('Representation proposal and property decision disagree');
|
|
155
|
+
}
|
|
156
|
+
if (isSourceBoundRepresentation(call.cdf_type) && !call.representation_proposal)
|
|
157
|
+
throw new Error('Source-bound representation requires a complete proposal');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if (typeof call.reason !== 'string' || !call.reason.trim())
|
|
161
|
+
throw new Error('Absent representation requires nonblank reason');
|
|
162
|
+
const companions = [
|
|
163
|
+
'values',
|
|
164
|
+
'token_kind',
|
|
165
|
+
'default',
|
|
166
|
+
'default_intent',
|
|
167
|
+
'items',
|
|
168
|
+
'fields',
|
|
169
|
+
'required',
|
|
170
|
+
'value',
|
|
171
|
+
'valueType',
|
|
172
|
+
'discriminator',
|
|
173
|
+
'members',
|
|
174
|
+
'allowedResources',
|
|
175
|
+
'representation_proposal',
|
|
176
|
+
'null_handling',
|
|
177
|
+
].filter((key) => own(rec, key));
|
|
178
|
+
if (companions.length)
|
|
179
|
+
throw new Error(`Representation companions require a representation: ${companions.join(', ')} require cdf_type or representation_selection; omit these fields while unresolved`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
calls.push(call);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
warnings.push(`decision ${index + 1}:${subject} ${error instanceof Error ? error.message : 'Invalid decision'}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const keys = calls.map((call) => call.tool === 'classify_component'
|
|
189
|
+
? 'component'
|
|
190
|
+
: call.tool === 'classify_slot'
|
|
191
|
+
? `slot:${call.slot}`
|
|
192
|
+
: `property:${call.prop}`);
|
|
193
|
+
const duplicates = new Set(keys.filter((key, index) => keys.indexOf(key) !== index));
|
|
194
|
+
for (const key of duplicates)
|
|
195
|
+
warnings.push(`Duplicate or conflicting subject ${key}`);
|
|
196
|
+
return { calls: calls.filter((_, index) => !duplicates.has(keys[index])), warnings };
|
|
197
|
+
}
|
|
198
|
+
export function parseClassificationToolLines(stdout) {
|
|
199
|
+
const values = [];
|
|
200
|
+
const warnings = [];
|
|
201
|
+
for (const [index, raw] of stdout.split('\n').entries()) {
|
|
202
|
+
const line = raw.trim();
|
|
203
|
+
if (!line.startsWith('{'))
|
|
204
|
+
continue;
|
|
205
|
+
try {
|
|
206
|
+
values.push(parseStrictJson(line));
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
warnings.push(`line ${index + 1}: malformed JSON or trailing content`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const normalized = normalizeClassificationCalls(values);
|
|
213
|
+
return { calls: normalized.calls, warnings: [...warnings, ...normalized.warnings] };
|
|
214
|
+
}
|
|
215
|
+
export function accountClassificationCalls(inventory, calls) {
|
|
216
|
+
const issues = [];
|
|
217
|
+
const subjects = [
|
|
218
|
+
'component',
|
|
219
|
+
...inventory.properties.map((key) => `property:${key}`),
|
|
220
|
+
...inventory.slots.map((slot) => `slot:${slot.name}`),
|
|
221
|
+
];
|
|
222
|
+
if (new Set(subjects).size !== subjects.length)
|
|
223
|
+
issues.push('Duplicate retained source subject');
|
|
224
|
+
const owners = inventory.slots.flatMap((slot) => (slot.sourceInput === undefined ? [] : [slot.sourceInput]));
|
|
225
|
+
if (new Set(owners).size !== owners.length)
|
|
226
|
+
issues.push('Competing accepted slot owners');
|
|
227
|
+
if (owners.some((key) => inventory.properties.includes(key)))
|
|
228
|
+
issues.push('Property and slot compete for source owner');
|
|
229
|
+
const grouped = new Map();
|
|
230
|
+
for (const call of calls) {
|
|
231
|
+
const key = call.tool === 'classify_component'
|
|
232
|
+
? 'component'
|
|
233
|
+
: call.tool === 'classify_slot'
|
|
234
|
+
? `slot:${call.slot}`
|
|
235
|
+
: `property:${call.prop}`;
|
|
236
|
+
const entries = grouped.get(key) ?? [];
|
|
237
|
+
entries.push(call);
|
|
238
|
+
grouped.set(key, entries);
|
|
239
|
+
}
|
|
240
|
+
const accepted = [];
|
|
241
|
+
for (const [key, entries] of grouped) {
|
|
242
|
+
if (!subjects.includes(key))
|
|
243
|
+
issues.push(`Unknown subject ${key}`);
|
|
244
|
+
else if (entries.length !== 1)
|
|
245
|
+
issues.push(`Duplicate or conflicting subject ${key}`);
|
|
246
|
+
else
|
|
247
|
+
accepted.push(entries[0]);
|
|
248
|
+
}
|
|
249
|
+
const missing = subjects.filter((key) => grouped.get(key)?.length !== 1);
|
|
250
|
+
return { complete: !issues.length && !missing.length, accepted, missing, issues };
|
|
251
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CDFComponentEntry, type CDFSlotDefinition } from '../cdf/types.js';
|
|
2
|
+
export * from './physical-property-import.js';
|
|
3
|
+
import { type PropertyIssue } from './json-value.js';
|
|
4
|
+
import { type ModelBinding, type RepresentationPlan } from './representation-plan.js';
|
|
5
|
+
/** Importing a definition does not establish delivery or rendering readiness. */
|
|
6
|
+
export declare function definitionImportCapabilityStatus(capability: string): 'supported' | 'deferred' | 'unavailable';
|
|
7
|
+
export declare function slotDefinitionImportIssues(slot: CDFSlotDefinition, modelOwner?: ModelBinding): string[];
|
|
8
|
+
export declare function representationDefinitionImportIssues(plan: RepresentationPlan): string[];
|
|
9
|
+
/** Check source ownership and physical import constraints before mapping. */
|
|
10
|
+
export declare function componentDefinitionImportIssues(component: CDFComponentEntry): PropertyIssue[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { CDFComponentSchema } from '../cdf/types.js';
|
|
2
|
+
import { propertyDefinitionImportIssues } from './physical-property-import.js';
|
|
3
|
+
export * from './physical-property-import.js';
|
|
4
|
+
import { childPath, jsonEqual } from './json-value.js';
|
|
5
|
+
import { validateRepresentationPlan } from './representation-plan.js';
|
|
6
|
+
const DEFINITION_CAPABILITIES = new Set([
|
|
7
|
+
'deployed-mapper',
|
|
8
|
+
'component-model-mapper',
|
|
9
|
+
'singleton-slot-cardinality',
|
|
10
|
+
'ordered-slot-cardinality',
|
|
11
|
+
'cyclic-model-dependencies',
|
|
12
|
+
]);
|
|
13
|
+
const DEFERRED_RUNTIME_CAPABILITIES = new Set([
|
|
14
|
+
'source-reconstruction',
|
|
15
|
+
'source-default-evaluation',
|
|
16
|
+
'source-structural-default-materialization',
|
|
17
|
+
'empty-source-value-materialization',
|
|
18
|
+
'presence-null-state',
|
|
19
|
+
'exclusive-union-branch-selection',
|
|
20
|
+
'intrinsic-field-reconstruction',
|
|
21
|
+
'index-key-preservation:string',
|
|
22
|
+
'index-key-preservation:number',
|
|
23
|
+
]);
|
|
24
|
+
/** Importing a definition does not establish delivery or rendering readiness. */
|
|
25
|
+
export function definitionImportCapabilityStatus(capability) {
|
|
26
|
+
if (DEFINITION_CAPABILITIES.has(capability))
|
|
27
|
+
return 'supported';
|
|
28
|
+
return DEFERRED_RUNTIME_CAPABILITIES.has(capability) ? 'deferred' : 'unavailable';
|
|
29
|
+
}
|
|
30
|
+
export function slotDefinitionImportIssues(slot, modelOwner) {
|
|
31
|
+
const cardinality = slot.$cardinality;
|
|
32
|
+
if (!cardinality)
|
|
33
|
+
return [];
|
|
34
|
+
const binding = slot.$modelBinding;
|
|
35
|
+
const ownedModelSlot = modelOwner !== undefined &&
|
|
36
|
+
binding !== undefined &&
|
|
37
|
+
binding.planId === modelOwner.planId &&
|
|
38
|
+
binding.componentId === modelOwner.componentId &&
|
|
39
|
+
binding.propertyId === modelOwner.propertyId;
|
|
40
|
+
// A source-owned model uses one typed slot per position; its family is checked separately.
|
|
41
|
+
if ((cardinality.tuplePosition !== undefined || cardinality.rest !== undefined) && !ownedModelSlot)
|
|
42
|
+
return ['Slot cardinality mapping is not available for positional model semantics'];
|
|
43
|
+
if (cardinality.max === 0)
|
|
44
|
+
return ['Slot cardinality mapping is not available for empty-only slots'];
|
|
45
|
+
if (!Number.isSafeInteger(cardinality.min) || (cardinality.max !== null && !Number.isSafeInteger(cardinality.max)))
|
|
46
|
+
return ['Slot cardinality bounds must be safe integers'];
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
export function representationDefinitionImportIssues(plan) {
|
|
50
|
+
if (!['native', 'component-model'].includes(plan.kind) || !plan.sdkValid || !Array.isArray(plan.requirements))
|
|
51
|
+
return ['Representation mapping prerequisites are not available'];
|
|
52
|
+
const issues = plan.requirements.flatMap((requirement) => {
|
|
53
|
+
const allowed = plan.kind === 'native'
|
|
54
|
+
? requirement.capability === 'deployed-mapper'
|
|
55
|
+
: definitionImportCapabilityStatus(requirement.capability) !== 'unavailable';
|
|
56
|
+
return allowed ? [] : [`Required capability ${requirement.capability} remains unverified at ${requirement.path}`];
|
|
57
|
+
});
|
|
58
|
+
if (plan.kind === 'native' && plan.nativeDefinition)
|
|
59
|
+
issues.push(...propertyDefinitionImportIssues(plan.nativeDefinition).map(({ path, message }) => `${path}: ${message}`));
|
|
60
|
+
for (const model of plan.models)
|
|
61
|
+
issues.push(...componentDefinitionImportIssues(model.entry).map(({ path, message }) => `${model.key}${path}: ${message}`));
|
|
62
|
+
return issues;
|
|
63
|
+
}
|
|
64
|
+
/** Check source ownership and physical import constraints before mapping. */
|
|
65
|
+
export function componentDefinitionImportIssues(component) {
|
|
66
|
+
const issues = [];
|
|
67
|
+
if (component.$model &&
|
|
68
|
+
!CDFComponentSchema.safeParse(Object.fromEntries(Object.entries(component).filter(([key]) => key !== 'key')))
|
|
69
|
+
.success)
|
|
70
|
+
issues.push({ path: '/$model', message: 'Invalid model component source binding' });
|
|
71
|
+
for (const [id, slot] of Object.entries(component.$slots ?? {})) {
|
|
72
|
+
if (!id.trim() || !(id.split('.').at(-1) ?? id).split(/[-_]/g).filter(Boolean).join(' ').length)
|
|
73
|
+
issues.push({ path: childPath('/$slots', id), message: 'Slot id must produce a non-empty display name' });
|
|
74
|
+
for (const message of slotDefinitionImportIssues(slot, component.$model))
|
|
75
|
+
issues.push({ path: childPath('/$slots', id) + '/$cardinality', message });
|
|
76
|
+
}
|
|
77
|
+
for (const [id, definition] of Object.entries(component.$properties))
|
|
78
|
+
for (const issue of propertyDefinitionImportIssues(definition))
|
|
79
|
+
issues.push({ path: childPath('/$properties', id) + issue.path, message: issue.message });
|
|
80
|
+
for (const [id, plan] of Object.entries(component.$representationPlans ?? {})) {
|
|
81
|
+
const path = childPath('/$representationPlans', id);
|
|
82
|
+
const prerequisites = representationDefinitionImportIssues(plan);
|
|
83
|
+
if (prerequisites.length) {
|
|
84
|
+
issues.push(...prerequisites.map((message) => ({ path, message })));
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
issues.push(...validateRepresentationPlan(plan, plan.sourceModel, plan.contextSnapshot).map(({ message }) => ({
|
|
89
|
+
path,
|
|
90
|
+
message,
|
|
91
|
+
})));
|
|
92
|
+
if (plan.identity.key !== id ||
|
|
93
|
+
(plan.kind === 'native'
|
|
94
|
+
? !Object.hasOwn(component.$properties, id) || !jsonEqual(plan.nativeDefinition, component.$properties[id])
|
|
95
|
+
: !plan.attachment ||
|
|
96
|
+
Object.hasOwn(component.$properties, id) ||
|
|
97
|
+
!jsonEqual(component.$slots?.[plan.attachment.slot], plan.attachment.definition)))
|
|
98
|
+
issues.push({ path, message: 'Representation does not match its physical definition' });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
issues.push({ path, message: 'Invalid representation plan' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return issues;
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CDFComponentEntry } from '../cdf/types.js';
|
|
2
|
+
import { type PropertyIssue } from './json-value.js';
|
|
3
|
+
export * from './definition-import-rules.js';
|
|
4
|
+
/** Model slots require an exact plan owner even on an otherwise ordinary component. */
|
|
5
|
+
export declare function componentModelFamilyIssues(components: readonly {
|
|
6
|
+
key: string;
|
|
7
|
+
entry: CDFComponentEntry;
|
|
8
|
+
}[]): PropertyIssue[];
|
|
9
|
+
/** Data-model relationships do not instantiate a composition tree. Keep ordinary edges intact. */
|
|
10
|
+
export declare function componentCompositionDefinitions<T extends {
|
|
11
|
+
key: string;
|
|
12
|
+
entry: CDFComponentEntry;
|
|
13
|
+
}>(components: readonly T[]): T[];
|