@adobe/design-data-spec 3.1.0 → 3.2.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 (58) hide show
  1. package/conformance/diff/cross-format/expected.json +2 -2
  2. package/conformance/diff/deprecated-new-token/new/tokens.tokens.json +1 -1
  3. package/conformance/diff/matched-gaining-deprecated/expected.json +1 -1
  4. package/conformance/diff/matched-gaining-deprecated/new/tokens.tokens.json +1 -1
  5. package/conformance/diff/replaced-by-pairing/expected.json +8 -8
  6. package/conformance/diff/replaced-by-pairing/old/tokens.tokens.json +5 -3
  7. package/conformance/diff/reverted-token/old/tokens.tokens.json +1 -1
  8. package/conformance/generation/ctr-legacy-key/expected/button.json +8 -0
  9. package/conformance/generation/ctr-legacy-key/expected/tokens.json +7 -0
  10. package/conformance/generation/ctr-legacy-key/input/tokens.tokens.json +8 -0
  11. package/conformance/generation/ctr-legacy-key/relationships/button.json +14 -0
  12. package/conformance/generation/deprecated-token/input/tokens.tokens.json +5 -3
  13. package/conformance/generation/mode-set-edit/input/tokens.tokens.json +8 -4
  14. package/conformance/generation/renamed-token/input/tokens.tokens.json +4 -2
  15. package/conformance/invalid/SPEC-010/dataset.json +4 -2
  16. package/conformance/invalid/SPEC-010/expected-errors.json +1 -1
  17. package/conformance/invalid/SPEC-010/tokens.tokens.json +4 -2
  18. package/conformance/invalid/SPEC-011/dataset.json +7 -5
  19. package/conformance/invalid/SPEC-011/expected-errors.json +1 -1
  20. package/conformance/invalid/SPEC-011/tokens.tokens.json +7 -5
  21. package/conformance/invalid/SPEC-012/dataset.json +1 -1
  22. package/conformance/invalid/SPEC-012/expected-errors.json +1 -1
  23. package/conformance/invalid/SPEC-012/tokens.tokens.json +1 -1
  24. package/conformance/invalid/SPEC-013/dataset.json +1 -1
  25. package/conformance/invalid/SPEC-013/tokens.tokens.json +1 -1
  26. package/conformance/invalid/SPEC-014/dataset.json +1 -1
  27. package/conformance/invalid/SPEC-014/tokens.tokens.json +1 -1
  28. package/conformance/invalid/SPEC-036/dataset.json +1 -1
  29. package/conformance/invalid/SPEC-037/dataset.json +3 -3
  30. package/conformance/invalid/SPEC-051/dataset.json +16 -0
  31. package/conformance/invalid/SPEC-051/expected-errors.json +10 -0
  32. package/conformance/invalid/SPEC-052/dataset.json +24 -0
  33. package/conformance/invalid/SPEC-052/expected-errors.json +10 -0
  34. package/conformance/invalid/SPEC-053/dataset.json +26 -0
  35. package/conformance/invalid/SPEC-053/expected-errors.json +10 -0
  36. package/conformance/invalid/SPEC-054/dataset.json +24 -0
  37. package/conformance/invalid/SPEC-054/expected-errors.json +10 -0
  38. package/conformance/invalid/SPEC-055/dataset.json +16 -0
  39. package/conformance/invalid/SPEC-055/expected-errors.json +10 -0
  40. package/conformance/invalid/SPEC-056/dataset.json +22 -0
  41. package/conformance/invalid/SPEC-056/expected-errors.json +10 -0
  42. package/conformance/invalid/SPEC-057/dataset.json +21 -0
  43. package/conformance/invalid/SPEC-057/expected-errors.json +10 -0
  44. package/conformance/valid/SPEC-014/dataset.json +1 -1
  45. package/conformance/valid/SPEC-036/dataset.json +2 -2
  46. package/conformance/valid/SPEC-037/dataset.json +6 -6
  47. package/conformance/valid/SPEC-051/dataset.json +42 -0
  48. package/conformance/valid/lifecycle-with-last-modified.json +1 -1
  49. package/package.json +1 -1
  50. package/rules/rules.yaml +98 -2
  51. package/schemas/component.schema.json +4 -4
  52. package/schemas/relationship.schema.json +228 -0
  53. package/schemas/token.schema.json +38 -48
  54. package/spec/authoring-workflow.md +19 -19
  55. package/spec/evolution.md +20 -20
  56. package/spec/index.md +2 -0
  57. package/spec/relationship-format.md +103 -0
  58. package/spec/token-format.md +39 -27
@@ -4,7 +4,7 @@
4
4
  "name": { "property": "accent-color" },
5
5
  "value": "#0d66d0",
6
6
  "uuid": "aaaaaaaa-0014-4000-8000-000000000001",
7
- "introduced": "1.0.0",
7
+ "lifecycle": { "introduced": "1.0.0" },
8
8
  "lastModified": "1.2.0"
9
9
  }
10
10
  ],
@@ -13,7 +13,7 @@
13
13
  "property": "color"
14
14
  },
15
15
  "value": "#000000",
16
- "deprecated": "1.0.0-draft"
16
+ "lifecycle": { "deprecatedIn": "1.0.0-draft" }
17
17
  }
18
18
  ],
19
19
  "components": [
@@ -35,7 +35,7 @@
35
35
  "documentationUrl": "https://spectrum.adobe.com/page/old-widget/"
36
36
  },
37
37
  "lifecycle": {
38
- "deprecated": "1.0.0-draft",
38
+ "deprecatedIn": "1.0.0-draft",
39
39
  "deprecatedComment": "Superseded by new-widget."
40
40
  }
41
41
  }
@@ -15,7 +15,7 @@
15
15
  "property": "background-color"
16
16
  },
17
17
  "value": "#0265dc",
18
- "deprecated": "1.0.0-draft"
18
+ "lifecycle": { "deprecatedIn": "1.0.0-draft" }
19
19
  },
20
20
  {
21
21
  "name": {
@@ -32,7 +32,7 @@
32
32
  "property": "background-color"
33
33
  },
34
34
  "value": "#cccccc",
35
- "deprecated": "1.0.0-draft"
35
+ "lifecycle": { "deprecatedIn": "1.0.0-draft" }
36
36
  },
37
37
  {
38
38
  "name": {
@@ -49,7 +49,7 @@
49
49
  "property": "background-color"
50
50
  },
51
51
  "value": "#0265dc",
52
- "deprecated": "1.0.0-draft"
52
+ "lifecycle": { "deprecatedIn": "1.0.0-draft" }
53
53
  }
54
54
  ],
55
55
  "components": [
@@ -68,7 +68,7 @@
68
68
  {
69
69
  "name": "handle",
70
70
  "lifecycle": {
71
- "deprecated": "1.0.0-draft",
71
+ "deprecatedIn": "1.0.0-draft",
72
72
  "deprecatedComment": "Renamed to thumb."
73
73
  }
74
74
  }
@@ -89,7 +89,7 @@
89
89
  {
90
90
  "name": "pressed",
91
91
  "lifecycle": {
92
- "deprecated": "1.0.0-draft",
92
+ "deprecatedIn": "1.0.0-draft",
93
93
  "deprecatedComment": "Use active instead."
94
94
  }
95
95
  }
@@ -107,7 +107,7 @@
107
107
  {
108
108
  "value": "cta",
109
109
  "lifecycle": {
110
- "deprecated": "1.0.0-draft",
110
+ "deprecatedIn": "1.0.0-draft",
111
111
  "deprecatedComment": "Use primary instead."
112
112
  }
113
113
  }
@@ -0,0 +1,42 @@
1
+ {
2
+ "relationships": [
3
+ {
4
+ "scope": {
5
+ "component": "button",
6
+ "part": "label",
7
+ "property": "background-color",
8
+ "options": { "style": "fill", "state": ["hover"] }
9
+ },
10
+ "value": "#ff0000",
11
+ "uuid": "33333333-3333-3333-3333-333333333333",
12
+ "legacyKey": "button-label-background-color-fill-hover"
13
+ },
14
+ {
15
+ "scope": { "component": "button", "property": "border-color" },
16
+ "context": "informative border color",
17
+ "$ref": "33333333-3333-3333-3333-333333333333"
18
+ }
19
+ ],
20
+ "components": [
21
+ {
22
+ "$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/components/button.json",
23
+ "name": "button",
24
+ "displayName": "Button",
25
+ "meta": { "category": "actions" },
26
+ "anatomy": [
27
+ { "name": "icon", "description": "Leading icon." },
28
+ { "name": "label", "description": "Button text.", "required": true }
29
+ ],
30
+ "options": {
31
+ "style": {
32
+ "type": "enum",
33
+ "values": [{ "value": "fill" }, { "value": "outline" }]
34
+ }
35
+ },
36
+ "states": [
37
+ { "name": "hover", "trigger": "interaction", "precedence": 50 },
38
+ { "name": "disabled", "trigger": "prop", "precedence": 100 }
39
+ ]
40
+ }
41
+ ]
42
+ }
@@ -7,6 +7,6 @@
7
7
  },
8
8
  "value": "#0265dc",
9
9
  "uuid": "aaaaaaaa-0001-4000-8000-000000000001",
10
- "introduced": "1.0.0",
10
+ "lifecycle": { "introduced": "1.0.0" },
11
11
  "lastModified": "2.1.0"
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/design-data-spec",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Design Data Specification — prose, JSON Schemas, rule catalog, and conformance fixtures for the design data format",
5
5
  "type": "module",
6
6
  "repository": {
package/rules/rules.yaml CHANGED
@@ -96,7 +96,7 @@ rules:
96
96
  name: replaced-by-array-requires-comment
97
97
  severity: error
98
98
  category: completeness
99
- assert: When replaced_by is an array, deprecated_comment MUST be present to explain which replacement applies in which context.
99
+ assert: When replaced_by is an array, deprecated_comment MUST be present to explain which replacement applies in which scope.
100
100
  message: "replaced_by is an array but deprecated_comment is missing on token {token}"
101
101
  spec_ref: spec/token-format.md#lifecycle-and-metadata
102
102
  introduced_in: "1.0.0-draft"
@@ -322,7 +322,7 @@ rules:
322
322
  assert: >
323
323
  A validator MUST NOT flag a registry ID as invalid solely because that same ID appears in
324
324
  a different registry. Registry IDs are scoped to their registry; cross-registry overlap is
325
- intentional when the same word is a valid term in each registry's distinct validation context.
325
+ intentional when the same word is a valid term in each registry's distinct validation scope.
326
326
  message: "Registry ID '{id}' appears in multiple registries — this is permitted; do not reject on cross-registry overlap alone"
327
327
  spec_ref: spec/registry.md#id-scoping
328
328
  introduced_in: "1.0.0-draft"
@@ -594,3 +594,99 @@ rules:
594
594
  message: "Token '{token}' fuses '{field}' concept '{value}' into name.property; decompose into the structured field"
595
595
  spec_ref: spec/token-format.md#name-object
596
596
  introduced_in: "1.0.0-draft"
597
+
598
+ - id: SPEC-051
599
+ name: ctr-component-exists
600
+ severity: error
601
+ layer: 2
602
+ category: reference-integrity
603
+ assert: >
604
+ A Component/Token Relationship (CTR)'s `scope.component` value MUST match the
605
+ `name` of a declared component in the dataset. Mirrors SPEC-018
606
+ (component-name-exists) for the CTR scope object.
607
+ message: "Relationship '{relationship}' references undeclared component '{component}'"
608
+ spec_ref: spec/relationship-format.md#scope-fields
609
+ introduced_in: "1.0.0-draft"
610
+
611
+ - id: SPEC-052
612
+ name: ctr-part-valid
613
+ severity: error
614
+ layer: 2
615
+ category: reference-integrity
616
+ assert: >
617
+ A CTR's `scope.part` value, when present, MUST match the `name` of a declared
618
+ anatomy part on the referenced component. Mirrors SPEC-020
619
+ (component-anatomy-valid) for the CTR scope object.
620
+ message: "Relationship '{relationship}' references undeclared anatomy part '{part}' on component '{component}'"
621
+ spec_ref: spec/relationship-format.md#scope-fields
622
+ introduced_in: "1.0.0-draft"
623
+
624
+ - id: SPEC-053
625
+ name: ctr-option-valid
626
+ severity: error
627
+ layer: 2
628
+ category: reference-integrity
629
+ assert: >
630
+ A CTR's `scope.options.<key>` value MUST match a value in the declared
631
+ `options.<key>.values` list for the referenced component, when that list exists.
632
+ Combines the intent of SPEC-019 (component-variant-valid, error) and SPEC-040
633
+ (component-option-field-valid, warning) into a single error-severity rule for
634
+ CTRs, since a CTR's options are the whole of its scope rather than one field
635
+ among many name-object keys.
636
+ message: "Relationship '{relationship}' has {key} '{value}' which is not declared on component '{component}'"
637
+ spec_ref: spec/relationship-format.md#scope-fields
638
+ introduced_in: "1.0.0-draft"
639
+
640
+ - id: SPEC-054
641
+ name: ctr-state-valid
642
+ severity: error
643
+ layer: 2
644
+ category: reference-integrity
645
+ assert: >
646
+ Each element of a CTR's `scope.options.state` array MUST match the `name` of a
647
+ declared state on the referenced component, when state declarations are present.
648
+ Mirrors SPEC-022 (component-state-valid) for the CTR scope object.
649
+ message: "Relationship '{relationship}' references undeclared state '{state}' on component '{component}'"
650
+ spec_ref: spec/relationship-format.md#scope-fields
651
+ introduced_in: "1.0.0-draft"
652
+
653
+ - id: SPEC-055
654
+ name: ctr-ref-resolves
655
+ severity: error
656
+ layer: 2
657
+ category: reference-integrity
658
+ assert: >
659
+ A CTR's `$ref` value MUST resolve to a declared token or relationship `uuid` in
660
+ the dataset. Mirrors SPEC-001 (alias-target-exists) and SPEC-027
661
+ (token-binding-token-exists) for the CTR alias direction.
662
+ message: "Relationship alias target not found for $ref: {target}"
663
+ spec_ref: spec/relationship-format.md#value-owning-vs-relationship-only
664
+ introduced_in: "1.0.0-draft"
665
+
666
+ - id: SPEC-056
667
+ name: ctr-uuid-unique
668
+ severity: error
669
+ layer: 2
670
+ category: reference-integrity
671
+ assert: >
672
+ CTR `uuid` values MUST be unique across all relationship entries within the same
673
+ cascade layer. Mirrors SPEC-004 (uuid-global-uniqueness) for relationships.
674
+ message: "Duplicate relationship uuid {uuid}"
675
+ spec_ref: spec/relationship-format.md#identity
676
+ introduced_in: "1.0.0-draft"
677
+
678
+ - id: SPEC-057
679
+ name: ctr-legacykey-present-if-legacy
680
+ severity: warning
681
+ layer: 2
682
+ category: naming-consistency
683
+ note: >
684
+ Advisory placeholder. Detecting "this CTR should have a legacy counterpart" is a
685
+ generator-side determination deferred to the legacy-migration phase; this rule
686
+ documents intent rather than enforcing it structurally.
687
+ assert: >
688
+ A CTR that round-trips through the legacy generator (packages/tokens/src) SHOULD
689
+ carry `legacyKey`. Not structurally enforced in this foundation.
690
+ message: "Relationship '{relationship}' may need a legacyKey to round-trip through the legacy generator"
691
+ spec_ref: spec/relationship-format.md#legacy-fields-interim
692
+ introduced_in: "1.0.0-draft"
@@ -179,7 +179,7 @@
179
179
  },
180
180
  "lifecycle": {
181
181
  "$ref": "#/$defs/lifecycle",
182
- "description": "Version lifecycle metadata for this value. Set lifecycle.deprecated to signal that tokens referencing this value should migrate (SPEC-037)."
182
+ "description": "Version lifecycle metadata for this value. Set lifecycle.deprecatedIn to signal that tokens referencing this value should migrate (SPEC-037)."
183
183
  }
184
184
  },
185
185
  "additionalProperties": false
@@ -234,7 +234,7 @@
234
234
  },
235
235
  "lifecycle": {
236
236
  "$ref": "#/$defs/lifecycle",
237
- "description": "Version lifecycle metadata for this anatomy part. Set lifecycle.deprecated to signal that tokens referencing this part should migrate."
237
+ "description": "Version lifecycle metadata for this anatomy part. Set lifecycle.deprecatedIn to signal that tokens referencing this part should migrate."
238
238
  }
239
239
  },
240
240
  "additionalProperties": false
@@ -284,7 +284,7 @@
284
284
  },
285
285
  "lifecycle": {
286
286
  "$ref": "#/$defs/lifecycle",
287
- "description": "Version lifecycle metadata for this state. Set lifecycle.deprecated to signal that tokens referencing this state should migrate."
287
+ "description": "Version lifecycle metadata for this state. Set lifecycle.deprecatedIn to signal that tokens referencing this state should migrate."
288
288
  }
289
289
  },
290
290
  "additionalProperties": false
@@ -314,7 +314,7 @@
314
314
  "type": "string",
315
315
  "description": "Spec version when this component declaration was added (e.g. '1.0.0-draft')."
316
316
  },
317
- "deprecated": {
317
+ "deprecatedIn": {
318
318
  "type": "string",
319
319
  "description": "Spec version when this component was deprecated. Truthy = deprecated."
320
320
  },
@@ -0,0 +1,228 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/relationship.schema.json",
4
+ "title": "Component/Token Relationship (CTR) file",
5
+ "description": "Layer 1 — structural shape of a relationships/*.json file: a JSON array of Component/Token Relationship (CTR) entries. Graph rules (option-value validity, component/part/state existence, $ref resolution) are Layer 2. See spec/relationship-format.md.",
6
+ "type": "array",
7
+ "items": {
8
+ "$ref": "#/$defs/relationship"
9
+ },
10
+ "$defs": {
11
+ "lifecycle": {
12
+ "type": "object",
13
+ "description": "Version lifecycle metadata, mirroring the token/component lifecycle pattern (see token.schema.json).",
14
+ "properties": {
15
+ "introduced": {
16
+ "type": "string",
17
+ "description": "Spec version when this relationship was added (e.g. \"1.0.0-draft\")."
18
+ },
19
+ "deprecatedIn": {
20
+ "type": "string",
21
+ "description": "Spec version when the relationship was deprecated (e.g. \"3.2.0\"). Truthy = deprecated."
22
+ },
23
+ "deprecatedComment": {
24
+ "type": "string",
25
+ "description": "Human-readable deprecation explanation and migration guidance."
26
+ },
27
+ "replacedBy": {
28
+ "oneOf": [
29
+ { "type": "string", "format": "uuid" },
30
+ {
31
+ "type": "array",
32
+ "items": { "type": "string", "format": "uuid" },
33
+ "minItems": 1
34
+ }
35
+ ],
36
+ "description": "UUID(s) of the replacement relationship(s). Array form requires deprecatedComment."
37
+ },
38
+ "plannedRemoval": {
39
+ "type": "string",
40
+ "description": "Spec version when the relationship will be removed."
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "scope": {
46
+ "type": "object",
47
+ "description": "Explicit relationship context: which component, part, and property this CTR describes, plus any option-scoped narrowing. Layer 1 only enforces shape here — that `component.<part>` exists, that `options.<key>` values validate against the referenced component's declared option values, and that `property` matches a registered property term are Layer 2 rules (see spec/relationship-format.md).",
48
+ "required": ["component", "property"],
49
+ "properties": {
50
+ "component": {
51
+ "type": "string",
52
+ "minLength": 1,
53
+ "description": "Component name (kebab-case). MUST match a declared component name (SPEC-051)."
54
+ },
55
+ "part": {
56
+ "type": "string",
57
+ "minLength": 1,
58
+ "description": "Anatomy part name within the component. MUST match a declared anatomy part when present (SPEC-052)."
59
+ },
60
+ "property": {
61
+ "type": "string",
62
+ "minLength": 1,
63
+ "description": "The stylistic attribute this relationship describes (e.g. color, width, padding)."
64
+ },
65
+ "options": {
66
+ "type": "object",
67
+ "description": "Option-scoped narrowing (e.g. variant, size, style, arbitrary component option keys). Each key's value MUST validate against the referenced component's options.<key>.values (SPEC-053), except `state`, which is an ordered array of atomic state ids validated against the component's declared states (SPEC-054).",
68
+ "properties": {
69
+ "state": {
70
+ "type": "array",
71
+ "items": {
72
+ "type": "string",
73
+ "pattern": "^[a-z][a-z0-9-]*$"
74
+ },
75
+ "minItems": 1,
76
+ "description": "Ordered array of atomic state ids (e.g. [\"hover\"], [\"selected\", \"hover\"])."
77
+ }
78
+ },
79
+ "additionalProperties": {
80
+ "type": "string"
81
+ }
82
+ }
83
+ },
84
+ "additionalProperties": false
85
+ },
86
+ "ctrWithValue": {
87
+ "type": "object",
88
+ "required": ["scope", "value"],
89
+ "not": {
90
+ "required": ["$ref"]
91
+ },
92
+ "dependentRequired": {
93
+ "setUuid": ["legacyKey"],
94
+ "setSchema": ["legacyKey"]
95
+ },
96
+ "properties": {
97
+ "specVersion": {
98
+ "const": "1.0.0-draft"
99
+ },
100
+ "scope": {
101
+ "$ref": "#/$defs/scope"
102
+ },
103
+ "context": {
104
+ "type": "string",
105
+ "description": "Human-readable label for how this CTR is used (mirrors tokenBindings[].context; maps to a Figma Token Group label). Purely descriptive — not validated."
106
+ },
107
+ "$valueType": {
108
+ "type": "string",
109
+ "format": "uri-reference",
110
+ "description": "URI reference to a value-type schema under schemas/value-types/. When present, value MUST validate against the referenced schema."
111
+ },
112
+ "value": {
113
+ "description": "Literal value owned by this CTR; type narrowed by value-type schemas where applicable.",
114
+ "anyOf": [
115
+ { "$ref": "value-types/color.schema.json" },
116
+ { "type": "string" },
117
+ { "type": "number" },
118
+ { "type": "boolean" },
119
+ { "type": "object" },
120
+ { "type": "array" }
121
+ ]
122
+ },
123
+ "uuid": {
124
+ "type": "string",
125
+ "format": "uuid",
126
+ "description": "MUST be globally unique across all relationship entries in the same layer (SPEC-056)."
127
+ },
128
+ "lifecycle": {
129
+ "$ref": "#/$defs/lifecycle"
130
+ },
131
+ "private": {
132
+ "type": "boolean"
133
+ },
134
+ "description": {
135
+ "type": "string",
136
+ "description": "Plain text describing the relationship's purpose."
137
+ },
138
+ "legacyKey": {
139
+ "type": "string",
140
+ "minLength": 1,
141
+ "description": "Interim/transitional: pins the exact flat key written to legacy output (packages/tokens/src) for CTRs that must round-trip through the legacy generator. Not required for CTRs with no legacy counterpart. See spec/relationship-format.md#legacy-fields."
142
+ },
143
+ "setUuid": {
144
+ "type": "string",
145
+ "format": "uuid",
146
+ "description": "Interim/transitional: identifies the legacy color-set entry this CTR belongs to, for color-set legacy output reconstruction. Only present alongside legacyKey."
147
+ },
148
+ "setSchema": {
149
+ "type": "string",
150
+ "minLength": 1,
151
+ "description": "Interim/transitional: names the legacy color-set schema this CTR's set entry conforms to. Only present alongside setUuid."
152
+ }
153
+ },
154
+ "additionalProperties": false
155
+ },
156
+ "ctrWithRef": {
157
+ "type": "object",
158
+ "required": ["scope", "$ref"],
159
+ "not": {
160
+ "required": ["value"]
161
+ },
162
+ "dependentRequired": {
163
+ "setUuid": ["legacyKey"],
164
+ "setSchema": ["legacyKey"]
165
+ },
166
+ "properties": {
167
+ "specVersion": {
168
+ "const": "1.0.0-draft"
169
+ },
170
+ "scope": {
171
+ "$ref": "#/$defs/scope"
172
+ },
173
+ "context": {
174
+ "type": "string",
175
+ "description": "Human-readable label for how this CTR is used (mirrors tokenBindings[].context; maps to a Figma Token Group label). Purely descriptive — not validated."
176
+ },
177
+ "$valueType": {
178
+ "type": "string",
179
+ "format": "uri-reference",
180
+ "description": "URI reference to a value-type schema under schemas/value-types/. When present, the alias resolution chain MUST terminate at a value validating against this schema."
181
+ },
182
+ "$ref": {
183
+ "type": "string",
184
+ "minLength": 1,
185
+ "description": "Alias target. MUST resolve to a token or relationship UUID in the dataset (SPEC-055)."
186
+ },
187
+ "uuid": {
188
+ "type": "string",
189
+ "format": "uuid",
190
+ "description": "MUST be globally unique across all relationship entries in the same layer (SPEC-056)."
191
+ },
192
+ "lifecycle": {
193
+ "$ref": "#/$defs/lifecycle"
194
+ },
195
+ "private": {
196
+ "type": "boolean"
197
+ },
198
+ "description": {
199
+ "type": "string",
200
+ "description": "Plain text describing the relationship's purpose."
201
+ },
202
+ "legacyKey": {
203
+ "type": "string",
204
+ "minLength": 1,
205
+ "description": "Interim/transitional: pins the exact flat key written to legacy output (packages/tokens/src) for CTRs that must round-trip through the legacy generator. Not required for CTRs with no legacy counterpart. See spec/relationship-format.md#legacy-fields."
206
+ },
207
+ "setUuid": {
208
+ "type": "string",
209
+ "format": "uuid",
210
+ "description": "Interim/transitional: identifies the legacy color-set entry this CTR belongs to, for color-set legacy output reconstruction. Only present alongside legacyKey."
211
+ },
212
+ "setSchema": {
213
+ "type": "string",
214
+ "minLength": 1,
215
+ "description": "Interim/transitional: names the legacy color-set schema this CTR's set entry conforms to. Only present alongside setUuid."
216
+ }
217
+ },
218
+ "additionalProperties": false
219
+ },
220
+ "relationship": {
221
+ "type": "object",
222
+ "oneOf": [
223
+ { "$ref": "#/$defs/ctrWithValue" },
224
+ { "$ref": "#/$defs/ctrWithRef" }
225
+ ]
226
+ }
227
+ }
228
+ }
@@ -114,6 +114,40 @@
114
114
  "type": "string"
115
115
  }
116
116
  },
117
+ "lifecycle": {
118
+ "type": "object",
119
+ "description": "Version lifecycle metadata, mirroring the component lifecycle pattern (see component.schema.json).",
120
+ "properties": {
121
+ "introduced": {
122
+ "type": "string",
123
+ "description": "Spec version when this token was added (e.g. \"1.0.0-draft\")."
124
+ },
125
+ "deprecatedIn": {
126
+ "type": "string",
127
+ "description": "Spec version when the token was deprecated (e.g. \"3.2.0\"). Truthy = deprecated."
128
+ },
129
+ "deprecatedComment": {
130
+ "type": "string",
131
+ "description": "Human-readable deprecation explanation and migration guidance."
132
+ },
133
+ "replacedBy": {
134
+ "oneOf": [
135
+ { "type": "string", "format": "uuid" },
136
+ {
137
+ "type": "array",
138
+ "items": { "type": "string", "format": "uuid" },
139
+ "minItems": 1
140
+ }
141
+ ],
142
+ "description": "UUID(s) of the replacement token(s). Array form requires deprecatedComment."
143
+ },
144
+ "plannedRemoval": {
145
+ "type": "string",
146
+ "description": "Spec version when token will be removed."
147
+ }
148
+ },
149
+ "additionalProperties": false
150
+ },
117
151
  "tokenWithValue": {
118
152
  "type": "object",
119
153
  "required": ["name", "value"],
@@ -166,30 +200,8 @@
166
200
  "type": "string",
167
201
  "format": "uuid"
168
202
  },
169
- "introduced": {
170
- "type": "string"
171
- },
172
- "deprecated": {
173
- "type": "string",
174
- "description": "Spec version when token was deprecated (e.g. \"3.2.0\"). Truthy = deprecated."
175
- },
176
- "deprecated_comment": {
177
- "type": "string"
178
- },
179
- "replaced_by": {
180
- "oneOf": [
181
- { "type": "string", "format": "uuid" },
182
- {
183
- "type": "array",
184
- "items": { "type": "string", "format": "uuid" },
185
- "minItems": 1
186
- }
187
- ],
188
- "description": "UUID(s) of the replacement token(s). Array form requires deprecated_comment."
189
- },
190
- "plannedRemoval": {
191
- "type": "string",
192
- "description": "Spec version when token will be removed."
203
+ "lifecycle": {
204
+ "$ref": "#/$defs/lifecycle"
193
205
  },
194
206
  "private": {
195
207
  "type": "boolean"
@@ -250,30 +262,8 @@
250
262
  "type": "string",
251
263
  "format": "uuid"
252
264
  },
253
- "introduced": {
254
- "type": "string"
255
- },
256
- "deprecated": {
257
- "type": "string",
258
- "description": "Spec version when token was deprecated (e.g. \"3.2.0\"). Truthy = deprecated."
259
- },
260
- "deprecated_comment": {
261
- "type": "string"
262
- },
263
- "replaced_by": {
264
- "oneOf": [
265
- { "type": "string", "format": "uuid" },
266
- {
267
- "type": "array",
268
- "items": { "type": "string", "format": "uuid" },
269
- "minItems": 1
270
- }
271
- ],
272
- "description": "UUID(s) of the replacement token(s). Array form requires deprecated_comment."
273
- },
274
- "plannedRemoval": {
275
- "type": "string",
276
- "description": "Spec version when token will be removed."
265
+ "lifecycle": {
266
+ "$ref": "#/$defs/lifecycle"
277
267
  },
278
268
  "private": {
279
269
  "type": "boolean"