@adobe/design-data-spec 3.5.0 → 4.1.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 (92) hide show
  1. package/README.md +3 -0
  2. package/conformance/README.md +24 -2
  3. package/conformance/generation/README.md +6 -6
  4. package/conformance/generation/mode-set-edit/input/tokens.tokens.json +3 -3
  5. package/conformance/generation/mode-set-token/input/tokens.tokens.json +2 -2
  6. package/conformance/manifest-extensions/README.md +63 -0
  7. package/conformance/manifest-extensions/base/dataset.json +22 -0
  8. package/conformance/manifest-extensions/invalid/component-missing-name/expected-errors.json +7 -0
  9. package/conformance/manifest-extensions/invalid/component-missing-name/extensions/components/broken.json +1 -0
  10. package/conformance/manifest-extensions/invalid/component-missing-name/manifest.json +4 -0
  11. package/conformance/manifest-extensions/invalid/extensions-dir-absolute-path/expected-errors.json +1 -0
  12. package/conformance/manifest-extensions/invalid/extensions-dir-absolute-path/manifest.json +5 -0
  13. package/conformance/manifest-extensions/invalid/extensions-dir-parent-traversal/expected-errors.json +1 -0
  14. package/conformance/manifest-extensions/invalid/extensions-dir-parent-traversal/manifest.json +5 -0
  15. package/conformance/manifest-extensions/invalid/field-invalid-kind/expected-errors.json +6 -0
  16. package/conformance/manifest-extensions/invalid/field-invalid-kind/extensions/fields/broken.json +7 -0
  17. package/conformance/manifest-extensions/invalid/field-invalid-kind/manifest.json +4 -0
  18. package/conformance/manifest-extensions/invalid/guideline-invalid-category/expected-errors.json +6 -0
  19. package/conformance/manifest-extensions/invalid/guideline-invalid-category/extensions/guidelines/broken.json +7 -0
  20. package/conformance/manifest-extensions/invalid/guideline-invalid-category/manifest.json +4 -0
  21. package/conformance/manifest-extensions/invalid/override-missing-uuid/expected-errors.json +1 -0
  22. package/conformance/manifest-extensions/invalid/override-missing-uuid/extensions/relationships/bad-override.json +7 -0
  23. package/conformance/manifest-extensions/invalid/override-missing-uuid/manifest.json +4 -0
  24. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/expected-errors.json +6 -0
  25. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/extensions/platform-extensions/broken.json +4 -0
  26. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/manifest.json +4 -0
  27. package/conformance/manifest-extensions/invalid/relationship-missing-scope/expected-errors.json +6 -0
  28. package/conformance/manifest-extensions/invalid/relationship-missing-scope/extensions/relationships/broken.json +4 -0
  29. package/conformance/manifest-extensions/invalid/relationship-missing-scope/manifest.json +4 -0
  30. package/conformance/manifest-extensions/invalid/token-invalid-uuid/expected-errors.json +6 -0
  31. package/conformance/manifest-extensions/invalid/token-invalid-uuid/extensions/tokens/broken.tokens.json +7 -0
  32. package/conformance/manifest-extensions/invalid/token-invalid-uuid/manifest.json +4 -0
  33. package/conformance/manifest-extensions/invalid/unknown-subdir-name/expected-errors.json +6 -0
  34. package/conformance/manifest-extensions/invalid/unknown-subdir-name/extensions/bogus-subdir/whatever.json +1 -0
  35. package/conformance/manifest-extensions/invalid/unknown-subdir-name/manifest.json +4 -0
  36. package/conformance/manifest-extensions/invalid/unknown-term-id/expected-errors.json +1 -0
  37. package/conformance/manifest-extensions/invalid/unknown-term-id/extensions/platform-extensions/ios-states.json +5 -0
  38. package/conformance/manifest-extensions/invalid/unknown-term-id/manifest.json +4 -0
  39. package/conformance/manifest-extensions/valid/custom-extensions-dir/expected.json +1 -0
  40. package/conformance/manifest-extensions/valid/custom-extensions-dir/manifest.json +5 -0
  41. package/conformance/manifest-extensions/valid/custom-extensions-dir/platform-extras/components/tab-bar-ios.json +9 -0
  42. package/conformance/manifest-extensions/valid/injects-component/expected.json +1 -0
  43. package/conformance/manifest-extensions/valid/injects-component/extensions/components/tab-bar-ios.json +9 -0
  44. package/conformance/manifest-extensions/valid/injects-component/manifest.json +4 -0
  45. package/conformance/manifest-extensions/valid/injects-field/expected.json +1 -0
  46. package/conformance/manifest-extensions/valid/injects-field/extensions/fields/hapticStyle.json +8 -0
  47. package/conformance/manifest-extensions/valid/injects-field/manifest.json +4 -0
  48. package/conformance/manifest-extensions/valid/injects-guideline/expected.json +1 -0
  49. package/conformance/manifest-extensions/valid/injects-guideline/extensions/guidelines/ios-haptics.json +9 -0
  50. package/conformance/manifest-extensions/valid/injects-guideline/manifest.json +4 -0
  51. package/conformance/manifest-extensions/valid/injects-platform-extension/expected.json +12 -0
  52. package/conformance/manifest-extensions/valid/injects-platform-extension/extensions/platform-extensions/ios-states.json +5 -0
  53. package/conformance/manifest-extensions/valid/injects-platform-extension/manifest.json +4 -0
  54. package/conformance/manifest-extensions/valid/injects-relationship/expected.json +7 -0
  55. package/conformance/manifest-extensions/valid/injects-relationship/extensions/relationships/button.json +5 -0
  56. package/conformance/manifest-extensions/valid/injects-relationship/manifest.json +4 -0
  57. package/conformance/manifest-extensions/valid/later-file-wins/expected.json +1 -0
  58. package/conformance/manifest-extensions/valid/later-file-wins/extensions/components/a-tab-bar-ios.json +9 -0
  59. package/conformance/manifest-extensions/valid/later-file-wins/extensions/components/b-tab-bar-ios.json +9 -0
  60. package/conformance/manifest-extensions/valid/later-file-wins/manifest.json +4 -0
  61. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/expected.json +1 -0
  62. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/extensions/relationships/a-override.json +9 -0
  63. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/extensions/relationships/z-add.json +5 -0
  64. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/manifest.json +4 -0
  65. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/expected.json +7 -0
  66. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/extensions/relationships/a-override.json +9 -0
  67. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/extensions/relationships/z-add.json +5 -0
  68. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/manifest.json +4 -0
  69. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/expected.json +5 -0
  70. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/extensions/relationships/button.json +5 -0
  71. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/extensions/relationships/slider.json +5 -0
  72. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/manifest.json +4 -0
  73. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/expected.json +1 -0
  74. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/extensions/relationships/button.json +5 -0
  75. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/extensions/relationships/remove-button.json +4 -0
  76. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/manifest.json +4 -0
  77. package/conformance/manifest-extensions/valid/tokens-sorted-order/expected.json +8 -0
  78. package/conformance/manifest-extensions/valid/tokens-sorted-order/extensions/tokens/a.tokens.json +7 -0
  79. package/conformance/manifest-extensions/valid/tokens-sorted-order/extensions/tokens/b.tokens.json +7 -0
  80. package/conformance/manifest-extensions/valid/tokens-sorted-order/manifest.json +4 -0
  81. package/conformance/reference/set-alias-dark/expected.json +1 -1
  82. package/conformance/reference/set-alias-dark/input/tokens.tokens.json +2 -2
  83. package/conformance/reference/set-alias-light/expected.json +1 -1
  84. package/conformance/reference/set-alias-light/input/tokens.tokens.json +2 -2
  85. package/package.json +1 -1
  86. package/rules/rules.yaml +17 -0
  87. package/schemas/manifest.schema.json +43 -101
  88. package/spec/cascade.md +3 -1
  89. package/spec/manifest-schema-override-spike.md +6 -3
  90. package/spec/manifest.md +108 -66
  91. package/spec/taxonomy.md +2 -2
  92. package/spec/token-format.md +28 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "relationships": {
3
+ "byUuid": {
4
+ "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a": { "count": 1, "value": "8px" }
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "op": "override",
3
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a",
4
+ "value": {
5
+ "scope": { "component": "button", "property": "corner-radius" },
6
+ "value": "8px",
7
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "scope": { "component": "button", "property": "corner-radius" },
3
+ "value": "4px",
4
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
5
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "specVersion": "1.0.0-draft",
3
+ "foundationVersion": "1.0.0"
4
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "relationships": {
3
+ "byUuid": { "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a": { "count": 2 } }
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "scope": { "component": "button", "property": "corner-radius" },
3
+ "value": "4px",
4
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "scope": { "component": "slider", "property": "corner-radius" },
3
+ "value": "2px",
4
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
5
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "specVersion": "1.0.0-draft",
3
+ "foundationVersion": "1.0.0"
4
+ }
@@ -0,0 +1 @@
1
+ { "relationships": { "absent": ["9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"] } }
@@ -0,0 +1,5 @@
1
+ {
2
+ "scope": { "component": "button", "property": "corner-radius" },
3
+ "value": "4px",
4
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
5
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "op": "remove",
3
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f1a"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "specVersion": "1.0.0-draft",
3
+ "foundationVersion": "1.0.0"
4
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tokens": {
3
+ "orderByUuid": [
4
+ "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f01",
5
+ "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f02"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "name": { "property": "elevation", "component": "card" },
4
+ "value": "4dp",
5
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f01"
6
+ }
7
+ ]
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "name": { "property": "elevation", "component": "sheet" },
4
+ "value": "8dp",
5
+ "uuid": "9c858f9c-1d90-4f1a-8c1a-1f1a1f1a1f02"
6
+ }
7
+ ]
@@ -0,0 +1,4 @@
1
+ {
2
+ "specVersion": "1.0.0-draft",
3
+ "foundationVersion": "1.0.0"
4
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "A set-level alias ($ref targeting a set_uuid) MUST resolve to the mode-appropriate child. Under colorScheme=dark, accent-color-900 → blue-900 dark (different value from light).",
2
+ "description": "A set-level alias ($ref targeting a conceptId) MUST resolve to the mode-appropriate child. Under colorScheme=dark, accent-color-900 → blue-900 dark (different value from light).",
3
3
  "resolved": true,
4
4
  "expected_value": "rgb(75, 117, 255)",
5
5
  "expected_chain_length": 3,
@@ -9,7 +9,7 @@
9
9
  "$schema": "https://opensource.adobe.com/spectrum-design-data/schemas/token-types/color.json",
10
10
  "value": "rgb(59, 99, 251)",
11
11
  "uuid": "ref-blue900-light-0000-000000000002",
12
- "set_uuid": "ref-blue900-set-000-0000-000000000002"
12
+ "conceptId": "ref-blue900-set-000-0000-000000000002"
13
13
  },
14
14
  {
15
15
  "name": {
@@ -21,7 +21,7 @@
21
21
  "$schema": "https://opensource.adobe.com/spectrum-design-data/schemas/token-types/color.json",
22
22
  "value": "rgb(75, 117, 255)",
23
23
  "uuid": "ref-blue900-dark-0000-000000000002",
24
- "set_uuid": "ref-blue900-set-000-0000-000000000002"
24
+ "conceptId": "ref-blue900-set-000-0000-000000000002"
25
25
  },
26
26
  {
27
27
  "name": { "property": "accent-color-900" },
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "A set-level alias ($ref targeting a set_uuid) MUST resolve to the mode-appropriate child. Under colorScheme=light, accent-color-900 → blue-900 light.",
2
+ "description": "A set-level alias ($ref targeting a conceptId) MUST resolve to the mode-appropriate child. Under colorScheme=light, accent-color-900 → blue-900 light.",
3
3
  "resolved": true,
4
4
  "expected_value": "rgb(59, 99, 251)",
5
5
  "expected_chain_length": 3,
@@ -9,7 +9,7 @@
9
9
  "$schema": "https://opensource.adobe.com/spectrum-design-data/schemas/token-types/color.json",
10
10
  "value": "rgb(59, 99, 251)",
11
11
  "uuid": "ref-blue900-light-0000-000000000001",
12
- "set_uuid": "ref-blue900-set-000-0000-000000000001"
12
+ "conceptId": "ref-blue900-set-000-0000-000000000001"
13
13
  },
14
14
  {
15
15
  "name": {
@@ -21,7 +21,7 @@
21
21
  "$schema": "https://opensource.adobe.com/spectrum-design-data/schemas/token-types/color.json",
22
22
  "value": "rgb(75, 117, 255)",
23
23
  "uuid": "ref-blue900-dark-0000-000000000001",
24
- "set_uuid": "ref-blue900-set-000-0000-000000000001"
24
+ "conceptId": "ref-blue900-set-000-0000-000000000001"
25
25
  },
26
26
  {
27
27
  "name": { "property": "accent-color-900" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/design-data-spec",
3
- "version": "3.5.0",
3
+ "version": "4.1.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
@@ -707,3 +707,20 @@ rules:
707
707
  message: "Relationship $ref {ref} resolves to a token deprecated in {deprecatedIn}"
708
708
  spec_ref: spec/relationship-format.md#legacy-fields-interim
709
709
  introduced_in: "1.0.0-draft"
710
+
711
+ - id: SPEC-059
712
+ name: alias-mode-context-compatibility
713
+ severity: warning
714
+ layer: 2
715
+ category: reference-integrity
716
+ note: >
717
+ Mode analogue of SPEC-002. A `$ref` target that is a `conceptId` (see
718
+ token-format.md#concept-identity-conceptid) is context-resolved elsewhere and exempt
719
+ here; only a direct per-value target is checked.
720
+ assert: >
721
+ An alias whose name object sets a mode-set field (e.g. `colorScheme: dark`) SHOULD NOT
722
+ resolve, via its `$ref` chain, to a per-value token that sets the same field to a
723
+ different value. Tokens whose name object omits the field (mode-agnostic) are exempt.
724
+ message: "Alias {token} sets {field}={ownValue} but its $ref chain resolves to a token with {field}={leafValue}"
725
+ spec_ref: spec/cascade.md#alias-resolution
726
+ introduced_in: "1.0.0-draft"
@@ -83,122 +83,64 @@
83
83
  "additionalProperties": false
84
84
  }
85
85
  },
86
- "extensions": {
86
+ "extensionsDir": {
87
+ "type": "string",
88
+ "minLength": 1,
89
+ "description": "Path (relative to the manifest) to the platform's extensions directory, holding platform-local tokens/components/fields/relationships/guidelines/platform-extensions as one file per artifact (see spec/manifest.md#extensions-directory). Default: \"extensions\"."
90
+ },
91
+ "namingExceptions": {
87
92
  "type": "object",
88
- "description": "Platform-local tokens, mode sets, and formatting rules.",
93
+ "description": "Platform-local overlay on the base naming-exceptions set: names to add and/or remove for this platform's naming validation.",
89
94
  "properties": {
90
- "tokens": {
91
- "$ref": "cascade-file.schema.json",
92
- "description": "Platform-local token definitions, in cascade-format (same shape as a cascade token file)."
93
- },
94
- "components": {
95
+ "add": {
95
96
  "type": "array",
96
- "items": { "$ref": "component.schema.json" },
97
- "description": "Platform-local component specs, injected into the component catalog (add-or-replace by name)."
97
+ "items": { "type": "string", "minLength": 1 },
98
+ "description": "Token names to add to the naming-exceptions set for this platform."
98
99
  },
99
- "fields": {
100
+ "remove": {
100
101
  "type": "array",
101
- "items": { "$ref": "field.schema.json" },
102
- "description": "Platform-local field declarations, injected into the field catalog (add-or-replace by name)."
103
- },
104
- "relationships": {
105
- "type": "array",
106
- "items": {
107
- "oneOf": [
108
- { "$ref": "relationship.schema.json#/$defs/relationship" },
109
- {
110
- "type": "object",
111
- "required": ["op", "uuid", "value"],
112
- "properties": {
113
- "op": { "const": "override" },
114
- "uuid": { "type": "string", "format": "uuid" },
115
- "value": {
116
- "$ref": "relationship.schema.json#/$defs/relationship"
117
- }
118
- },
119
- "additionalProperties": false
120
- },
121
- {
122
- "type": "object",
123
- "required": ["op", "uuid"],
124
- "properties": {
125
- "op": { "const": "remove" },
126
- "uuid": { "type": "string", "format": "uuid" }
127
- },
128
- "additionalProperties": false
129
- }
130
- ]
131
- },
132
- "description": "Platform-local Component/Token Relationship (CTR) add/override/remove. Entries without \"op\" are added positionally; \"override\"/\"remove\" require a \"uuid\" to target an existing relationship."
133
- },
134
- "platformExtensions": {
102
+ "items": { "type": "string", "minLength": 1 },
103
+ "description": "Token names to remove from the base naming-exceptions set for this platform."
104
+ }
105
+ },
106
+ "additionalProperties": false
107
+ },
108
+ "formatting": {
109
+ "type": "object",
110
+ "description": "Rules for serializing structured name objects into platform-specific token name strings.",
111
+ "properties": {
112
+ "conceptOrder": {
135
113
  "type": "array",
136
114
  "items": {
137
- "$ref": "https://opensource.adobe.com/spectrum-design-data/schemas/platform-extension.json"
115
+ "type": "string"
138
116
  },
139
- "description": "Platform-local terminology extensions (e.g. iOS state vocabulary), annotating ids in an existing base registry."
117
+ "uniqueItems": true,
118
+ "description": "Ordered list of field names for serialization. Values must match declared field names from the design system's field catalog (field.schema.json declarations). Replaces the default serialization.position order from each field declaration."
140
119
  },
141
- "guidelines": {
142
- "type": "array",
143
- "items": { "$ref": "guideline.schema.json" },
144
- "description": "Platform-local guideline docs, injected into the guideline catalog (add-or-replace by name)."
120
+ "casing": {
121
+ "type": "string",
122
+ "enum": [
123
+ "kebab-case",
124
+ "camelCase",
125
+ "PascalCase",
126
+ "SCREAMING_SNAKE_CASE"
127
+ ],
128
+ "description": "Casing style for serialized token names. Default: kebab-case."
145
129
  },
146
- "namingExceptions": {
147
- "type": "object",
148
- "description": "Platform-local overlay on the base naming-exceptions set: names to add and/or remove for this platform's naming validation.",
149
- "properties": {
150
- "add": {
151
- "type": "array",
152
- "items": { "type": "string", "minLength": 1 },
153
- "description": "Token names to add to the naming-exceptions set for this platform."
154
- },
155
- "remove": {
156
- "type": "array",
157
- "items": { "type": "string", "minLength": 1 },
158
- "description": "Token names to remove from the base naming-exceptions set for this platform."
159
- }
160
- },
161
- "additionalProperties": false
130
+ "delimiter": {
131
+ "type": "string",
132
+ "minLength": 1,
133
+ "description": "Character(s) separating concepts in the serialized string. Default: -."
162
134
  },
163
- "formatting": {
135
+ "abbreviations": {
164
136
  "type": "object",
165
- "description": "Rules for serializing structured name objects into platform-specific token name strings.",
166
- "properties": {
167
- "conceptOrder": {
168
- "type": "array",
169
- "items": {
170
- "type": "string"
171
- },
172
- "uniqueItems": true,
173
- "description": "Ordered list of field names for serialization. Values must match declared field names from the design system's field catalog (field.schema.json declarations). Replaces the default serialization.position order from each field declaration."
174
- },
175
- "casing": {
176
- "type": "string",
177
- "enum": [
178
- "kebab-case",
179
- "camelCase",
180
- "PascalCase",
181
- "SCREAMING_SNAKE_CASE"
182
- ],
183
- "description": "Casing style for serialized token names. Default: kebab-case."
184
- },
185
- "delimiter": {
186
- "type": "string",
187
- "minLength": 1,
188
- "description": "Character(s) separating concepts in the serialized string. Default: -."
189
- },
190
- "abbreviations": {
191
- "type": "object",
192
- "additionalProperties": {
193
- "type": "string"
194
- },
195
- "description": "Map of full term to abbreviated form, applied after ordering and before casing."
196
- }
137
+ "additionalProperties": {
138
+ "type": "string"
197
139
  },
198
- "additionalProperties": false
140
+ "description": "Map of full term to abbreviated form, applied after ordering and before casing."
199
141
  }
200
142
  },
201
- "additionalProperties": true
143
+ "additionalProperties": false
202
144
  }
203
145
  },
204
146
  "additionalProperties": false
package/spec/cascade.md CHANGED
@@ -57,7 +57,9 @@ Exact matching rules for omitted mode sets are defined alongside mode set declar
57
57
 
58
58
  **NORMATIVE:** If the winning candidate is an alias, its `$ref` chain **MUST** be resolved to a literal value using the standard alias-resolution rules (SPEC-001 through SPEC-003 in `rules/rules.yaml`). The alias target is resolved within the same dataset (i.e. the same merged layer stack), not relative to any single layer. In cascade format, `$ref` holds the **UUID** of the target token (see [`token-format.md` §Alias](token-format.md#alias-ref)); resolution proceeds UUID-first, then direct graph key (legacy slug), then serialised name.
59
59
 
60
- **RATIONALE:** Aliases participate in cascade as opaque references — their target values are not examined during specificity calculation or layer comparison. This keeps resolution deterministic and allows aliases to be valid candidates at any specificity level.
60
+ **NORMATIVE:** When a `$ref` target is a **`conceptId`** (see [`token-format.md` §Concept identity](token-format.md#concept-identity-conceptid)) rather than a per-row `uuid`, resolution **MUST** pick the mode row registered under that `conceptId` whose name-object fields best match the referencing token's own resolution context (greatest count of matching mode-set key/value pairs). Ties **MUST** be broken by the lexicographically smallest `uuid` among equally-scoring rows, so repeated resolution of the same alias always returns the same token. This context-aware step runs in place of, not in addition to, the UUID-first/direct-key/name fallback above — a `conceptId` target either resolves to its best-matching row or (when no rows are registered under it) falls through to the standard chain.
61
+
62
+ **RATIONALE:** Aliases participate in cascade as opaque references — their target values are not examined during specificity calculation or layer comparison. This keeps resolution deterministic and allows aliases to be valid candidates at any specificity level. Resolving a `conceptId` target against the *referencing* token's own context (rather than always the first or an arbitrary row) is what lets one alias correctly track a multi-mode concept (e.g. a semantic color that must stay light/dark-aligned with whatever aliases to it) without one alias per mode.
61
63
 
62
64
  ## Cross-mode-set overrides
63
65
 
@@ -24,9 +24,12 @@ schemas for its own data?
24
24
  3. **No concrete need surfaced.** Nothing in the h890.22/h890.23 work (iOS
25
25
  externalization, or the guidelines/fields/relationships/naming-exceptions cascade
26
26
  additions) needed schema relaxation — every platform-local addition validates fine
27
- against the existing schemas via `extensions.*`. The `extensions` object is already
28
- `additionalProperties: true`, which covers "the platform wants to attach data the
29
- schema doesn't know about" without touching the schema itself.
27
+ against the existing per-artifact schemas via the `extensions/` directory (see
28
+ [Manifest — `extensions/` directory](manifest.md#extensions-directory)). Each fragment
29
+ file there is checked against the same `component.schema.json` /
30
+ `field.schema.json` / `guideline.schema.json` / etc. as the foundation dataset, which
31
+ already covers "the platform wants to attach data the schema knows how to validate"
32
+ without touching the schema itself.
30
33
 
31
34
  ## Decision
32
35