@adobe/design-data-spec 3.4.1 → 4.0.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 (86) hide show
  1. package/README.md +3 -0
  2. package/conformance/README.md +22 -0
  3. package/conformance/invalid/SPEC-058/dataset.json +38 -0
  4. package/conformance/invalid/SPEC-058/expected-errors.json +10 -0
  5. package/conformance/manifest-extensions/README.md +63 -0
  6. package/conformance/manifest-extensions/base/dataset.json +22 -0
  7. package/conformance/manifest-extensions/invalid/component-missing-name/expected-errors.json +7 -0
  8. package/conformance/manifest-extensions/invalid/component-missing-name/extensions/components/broken.json +1 -0
  9. package/conformance/manifest-extensions/invalid/component-missing-name/manifest.json +4 -0
  10. package/conformance/manifest-extensions/invalid/extensions-dir-absolute-path/expected-errors.json +1 -0
  11. package/conformance/manifest-extensions/invalid/extensions-dir-absolute-path/manifest.json +5 -0
  12. package/conformance/manifest-extensions/invalid/extensions-dir-parent-traversal/expected-errors.json +1 -0
  13. package/conformance/manifest-extensions/invalid/extensions-dir-parent-traversal/manifest.json +5 -0
  14. package/conformance/manifest-extensions/invalid/field-invalid-kind/expected-errors.json +6 -0
  15. package/conformance/manifest-extensions/invalid/field-invalid-kind/extensions/fields/broken.json +7 -0
  16. package/conformance/manifest-extensions/invalid/field-invalid-kind/manifest.json +4 -0
  17. package/conformance/manifest-extensions/invalid/guideline-invalid-category/expected-errors.json +6 -0
  18. package/conformance/manifest-extensions/invalid/guideline-invalid-category/extensions/guidelines/broken.json +7 -0
  19. package/conformance/manifest-extensions/invalid/guideline-invalid-category/manifest.json +4 -0
  20. package/conformance/manifest-extensions/invalid/override-missing-uuid/expected-errors.json +1 -0
  21. package/conformance/manifest-extensions/invalid/override-missing-uuid/extensions/relationships/bad-override.json +7 -0
  22. package/conformance/manifest-extensions/invalid/override-missing-uuid/manifest.json +4 -0
  23. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/expected-errors.json +6 -0
  24. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/extensions/platform-extensions/broken.json +4 -0
  25. package/conformance/manifest-extensions/invalid/platform-extension-missing-extends/manifest.json +4 -0
  26. package/conformance/manifest-extensions/invalid/relationship-missing-scope/expected-errors.json +6 -0
  27. package/conformance/manifest-extensions/invalid/relationship-missing-scope/extensions/relationships/broken.json +4 -0
  28. package/conformance/manifest-extensions/invalid/relationship-missing-scope/manifest.json +4 -0
  29. package/conformance/manifest-extensions/invalid/token-invalid-uuid/expected-errors.json +6 -0
  30. package/conformance/manifest-extensions/invalid/token-invalid-uuid/extensions/tokens/broken.tokens.json +7 -0
  31. package/conformance/manifest-extensions/invalid/token-invalid-uuid/manifest.json +4 -0
  32. package/conformance/manifest-extensions/invalid/unknown-subdir-name/expected-errors.json +6 -0
  33. package/conformance/manifest-extensions/invalid/unknown-subdir-name/extensions/bogus-subdir/whatever.json +1 -0
  34. package/conformance/manifest-extensions/invalid/unknown-subdir-name/manifest.json +4 -0
  35. package/conformance/manifest-extensions/invalid/unknown-term-id/expected-errors.json +1 -0
  36. package/conformance/manifest-extensions/invalid/unknown-term-id/extensions/platform-extensions/ios-states.json +5 -0
  37. package/conformance/manifest-extensions/invalid/unknown-term-id/manifest.json +4 -0
  38. package/conformance/manifest-extensions/valid/custom-extensions-dir/expected.json +1 -0
  39. package/conformance/manifest-extensions/valid/custom-extensions-dir/manifest.json +5 -0
  40. package/conformance/manifest-extensions/valid/custom-extensions-dir/platform-extras/components/tab-bar-ios.json +9 -0
  41. package/conformance/manifest-extensions/valid/injects-component/expected.json +1 -0
  42. package/conformance/manifest-extensions/valid/injects-component/extensions/components/tab-bar-ios.json +9 -0
  43. package/conformance/manifest-extensions/valid/injects-component/manifest.json +4 -0
  44. package/conformance/manifest-extensions/valid/injects-field/expected.json +1 -0
  45. package/conformance/manifest-extensions/valid/injects-field/extensions/fields/hapticStyle.json +8 -0
  46. package/conformance/manifest-extensions/valid/injects-field/manifest.json +4 -0
  47. package/conformance/manifest-extensions/valid/injects-guideline/expected.json +1 -0
  48. package/conformance/manifest-extensions/valid/injects-guideline/extensions/guidelines/ios-haptics.json +9 -0
  49. package/conformance/manifest-extensions/valid/injects-guideline/manifest.json +4 -0
  50. package/conformance/manifest-extensions/valid/injects-platform-extension/expected.json +12 -0
  51. package/conformance/manifest-extensions/valid/injects-platform-extension/extensions/platform-extensions/ios-states.json +5 -0
  52. package/conformance/manifest-extensions/valid/injects-platform-extension/manifest.json +4 -0
  53. package/conformance/manifest-extensions/valid/injects-relationship/expected.json +7 -0
  54. package/conformance/manifest-extensions/valid/injects-relationship/extensions/relationships/button.json +5 -0
  55. package/conformance/manifest-extensions/valid/injects-relationship/manifest.json +4 -0
  56. package/conformance/manifest-extensions/valid/later-file-wins/expected.json +1 -0
  57. package/conformance/manifest-extensions/valid/later-file-wins/extensions/components/a-tab-bar-ios.json +9 -0
  58. package/conformance/manifest-extensions/valid/later-file-wins/extensions/components/b-tab-bar-ios.json +9 -0
  59. package/conformance/manifest-extensions/valid/later-file-wins/manifest.json +4 -0
  60. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/expected.json +1 -0
  61. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/extensions/relationships/a-override.json +9 -0
  62. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/extensions/relationships/z-add.json +5 -0
  63. package/conformance/manifest-extensions/valid/override-entry-skips-schema-validation/manifest.json +4 -0
  64. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/expected.json +7 -0
  65. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/extensions/relationships/a-override.json +9 -0
  66. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/extensions/relationships/z-add.json +5 -0
  67. package/conformance/manifest-extensions/valid/override-relationship-by-uuid/manifest.json +4 -0
  68. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/expected.json +5 -0
  69. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/extensions/relationships/button.json +5 -0
  70. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/extensions/relationships/slider.json +5 -0
  71. package/conformance/manifest-extensions/valid/plain-add-uuid-collision-append/manifest.json +4 -0
  72. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/expected.json +1 -0
  73. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/extensions/relationships/button.json +5 -0
  74. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/extensions/relationships/remove-button.json +4 -0
  75. package/conformance/manifest-extensions/valid/remove-relationship-by-uuid/manifest.json +4 -0
  76. package/conformance/manifest-extensions/valid/tokens-sorted-order/expected.json +8 -0
  77. package/conformance/manifest-extensions/valid/tokens-sorted-order/extensions/tokens/a.tokens.json +7 -0
  78. package/conformance/manifest-extensions/valid/tokens-sorted-order/extensions/tokens/b.tokens.json +7 -0
  79. package/conformance/manifest-extensions/valid/tokens-sorted-order/manifest.json +4 -0
  80. package/package.json +1 -1
  81. package/rules/rules.yaml +17 -0
  82. package/schemas/manifest.schema.json +43 -101
  83. package/spec/manifest-schema-override-spike.md +6 -3
  84. package/spec/manifest.md +108 -66
  85. package/spec/mode-sets.md +5 -5
  86. package/spec/taxonomy.md +2 -2
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/design-data-spec",
3
- "version": "3.4.1",
3
+ "version": "4.0.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
@@ -690,3 +690,20 @@ rules:
690
690
  message: "Relationship '{relationship}' may need a legacyKey to round-trip through the legacy generator"
691
691
  spec_ref: spec/relationship-format.md#legacy-fields-interim
692
692
  introduced_in: "1.0.0-draft"
693
+
694
+ - id: SPEC-058
695
+ name: ctr-ref-not-deprecated
696
+ severity: warning
697
+ layer: 2
698
+ category: naming-consistency
699
+ note: >
700
+ Warning, not error: some deprecated targets have no `replacedBy` (or an ambiguous
701
+ array-form one) and can't be auto-corrected without manual triage, so this can't be
702
+ a hard gate yet.
703
+ assert: >
704
+ A relationship-only CTR's `$ref` (no `uuid`/`legacyKey`) SHOULD NOT resolve to a
705
+ token whose `lifecycle.deprecatedIn` is set. A value-owning CTR (`uuid`/`legacyKey`
706
+ present) is exempt — it legitimately *is* the deprecated token record.
707
+ message: "Relationship $ref {ref} resolves to a token deprecated in {deprecatedIn}"
708
+ spec_ref: spec/relationship-format.md#legacy-fields-interim
709
+ 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
@@ -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
 
package/spec/manifest.md CHANGED
@@ -8,23 +8,23 @@ This document defines the **platform manifest**: how a platform implementation r
8
8
 
9
9
  The manifest supports a fixed, enumerated set of operations against the foundation — it does **not** allow overriding, aliasing, or removing arbitrary foundation artifacts. Support is concentrated on tokens; translations and schemas have no manifest-level override mechanism at all.
10
10
 
11
- | Operation | Supported? | Field | Applies to |
12
- | -------------------------------------------------- | ------------------------------------------------------------ | ------------------------------- | --------------------- |
13
- | Remove / exclude | Yes | `exclude` | Tokens only |
14
- | Include / whitelist | Yes | `include` | Tokens only |
15
- | Override value (type-preserving) | Yes | `overrides[].value` | Tokens only |
16
- | Override → re-alias | Yes | `overrides[].$ref` | Tokens only |
17
- | Add new tokens (may alias via `$ref`) | Yes | `extensions.tokens` | Tokens |
18
- | Add / replace components | Yes | `extensions.components` | Components |
19
- | Add / replace field declarations | Yes | `extensions.fields` | Fields |
20
- | Add / replace guideline documents | Yes | `extensions.guidelines` | Guidelines |
21
- | Add relationships/CTRs; override/remove by `uuid` | Yes | `extensions.relationships` | Relationships (CTRs) |
22
- | Add / remove naming exceptions | Yes | `extensions.namingExceptions` | Naming validation |
23
- | Annotate existing terminology (cannot add new ids) | Yes | `extensions.platformExtensions` | Existing registry ids |
24
- | Restrict allowed mode-set values | Yes | `modeSetRestrictions` | Mode sets |
25
- | Reformat name serialization | Schema-declared only, not yet applied by the reference SDK | `extensions.formatting` | Token name strings |
26
- | Override/remove/alias translations | No | — | — |
27
- | Override Layer-1 schemas | No (decided; see [spike](manifest-schema-override-spike.md)) | — | — |
11
+ | Operation | Supported? | Field | Applies to |
12
+ | -------------------------------------------------- | ------------------------------------------------------------ | --------------------------------- | --------------------- |
13
+ | Remove / exclude | Yes | `exclude` | Tokens only |
14
+ | Include / whitelist | Yes | `include` | Tokens only |
15
+ | Override value (type-preserving) | Yes | `overrides[].value` | Tokens only |
16
+ | Override → re-alias | Yes | `overrides[].$ref` | Tokens only |
17
+ | Add new tokens (may alias via `$ref`) | Yes | `extensions/tokens/` | Tokens |
18
+ | Add / replace components | Yes | `extensions/components/` | Components |
19
+ | Add / replace field declarations | Yes | `extensions/fields/` | Fields |
20
+ | Add / replace guideline documents | Yes | `extensions/guidelines/` | Guidelines |
21
+ | Add relationships/CTRs; override/remove by `uuid` | Yes | `extensions/relationships/` | Relationships (CTRs) |
22
+ | Add / remove naming exceptions | Yes | `namingExceptions` | Naming validation |
23
+ | Annotate existing terminology (cannot add new ids) | Yes | `extensions/platform-extensions/` | Existing registry ids |
24
+ | Restrict allowed mode-set values | Yes | `modeSetRestrictions` | Mode sets |
25
+ | Reformat name serialization | Schema-declared only, not yet applied by the reference SDK | `formatting` | Token name strings |
26
+ | Override/remove/alias translations | No | — | — |
27
+ | Override Layer-1 schemas | No (decided; see [spike](manifest-schema-override-spike.md)) | — | — |
28
28
 
29
29
  ## Manifest document
30
30
 
@@ -39,13 +39,15 @@ A manifest **MUST** conform to [`manifest.schema.json`](../schemas/manifest.sche
39
39
 
40
40
  ## Optional fields
41
41
 
42
- | Field | Type | Description |
43
- | --------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
- | `include` | array of string | Semantic **queries** selecting subsets of foundation tokens to materialize. |
45
- | `exclude` | array of string | Queries removing tokens from the included set. |
46
- | `overrides` | array of object | Typed overrides; each entry **MUST** preserve the target token’s **value type**. |
47
- | `extensions` | object | Platform-local additions layered on top of foundation — `tokens`, `components`, `fields`, `guidelines`, `relationships`, `namingExceptions`, `platformExtensions`, `formatting` (see `extensions` section below). |
48
- | `modeSetRestrictions` | object | Mode set restrictions for this platform; see [Mode Sets — Platform restrictions](mode-sets.md#platform-restrictions). |
42
+ | Field | Type | Description |
43
+ | --------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `include` | array of string | Semantic **queries** selecting subsets of foundation tokens to materialize. |
45
+ | `exclude` | array of string | Queries removing tokens from the included set. |
46
+ | `overrides` | array of object | Typed overrides; each entry **MUST** preserve the target token’s **value type**. |
47
+ | `extensionsDir` | string | Path (relative to the manifest) to the platform's `extensions/` directory. Default: `"extensions"`. See [`extensions/` directory](#extensions-directory) below. |
48
+ | `namingExceptions` | object | Platform-local overlay on the base naming-exceptions set: names to add and/or remove for this platform's naming validation. |
49
+ | `formatting` | object | Rules for serializing structured name objects into platform-specific token name strings. |
50
+ | `modeSetRestrictions` | object | Mode set restrictions for this platform; see [Mode Sets — Platform restrictions](mode-sets.md#platform-restrictions). |
49
51
 
50
52
  ### `include` / `exclude`
51
53
 
@@ -71,41 +73,85 @@ apply `Foundation < Platform < Product` precedence to select a single winner. To
71
73
  counts "tokens a platform ships" from `query` output should resolve first, or it will
72
74
  double-count overridden tokens.
73
75
 
74
- ### `extensions`
75
-
76
- **RECOMMENDED:** `extensions` follows the same structural conventions as foundation token files (tokens, mode sets) and **SHOULD** be validated with the same Layer 1 and Layer 2 rules.
77
-
78
- #### `extensions.tokens`
79
-
80
- Platform-local token definitions, in cascade-file format. Inserted at the platform layer alongside (not replacing) foundation tokens; entries **MAY** carry a `$ref` to alias an existing token instead of a literal value.
81
-
82
- #### `extensions.components`
83
-
84
- Platform-local component specs, injected into the component catalog. **NORMATIVE:** the reference SDK applies these add-or-replace by component `name` at the platform layer.
85
-
86
- #### `extensions.fields`
87
-
88
- Platform-local field declarations, injected into the field catalog. **NORMATIVE:** the reference SDK applies these add-or-replace by field `name` at the platform layer; each entry **MUST** validate against `field.schema.json`. Note: `extensions.formatting.conceptOrder` (if declared) references field names by string — a platform that renames or removes a field it also references there is self-inconsistent; that is a manifest-authoring concern, not enforced by the reference SDK.
89
-
90
- #### `extensions.guidelines`
91
-
92
- Platform-local guideline documents, injected into the guideline catalog. **NORMATIVE:** the reference SDK applies these add-or-replace by guideline `name` at the platform layer; each entry **MUST** validate against `guideline.schema.json`.
93
-
94
- #### `extensions.relationships`
95
-
96
- Platform-local Component/Token Relationship (CTR) entries. Relationships have no inherent
97
- stable key — only an optional `uuid` — so add and override/remove use different identity
98
- rules:
99
-
100
- * **Add:** an entry with no `op` field is a full relationship object (validated against
101
- `relationship.schema.json`), appended at the platform layer.
102
- * **Override / remove:** relationships have no other stable identity, so targeting one
103
- **MUST** carry a `uuid`. **NORMATIVE:** an `extensions.relationships` entry with `"op":
104
- "override"` or `"op": "remove"` that omits `uuid` is a manifest error — the reference SDK
105
- rejects it rather than silently skipping it. `"op": "override"` also carries a `value` (the
106
- replacement relationship object); `"op": "remove"` drops the matching record.
107
-
108
- #### `extensions.namingExceptions`
76
+ ### `extensions/` directory
77
+
78
+ **NORMATIVE:** Platform-local additions are declared as a sibling **directory** next to
79
+ `manifest.json` (default name `extensions/`; override with the top-level `extensionsDir`
80
+ field), not as an inline object in the manifest. This mirrors how the foundation dataset
81
+ (`packages/design-data/`) splits its own catalogs into one file per artifact, and lets a
82
+ platform's extension set grow without every addition colliding in one JSON object.
83
+
84
+ ```
85
+ extensions/
86
+ tokens/ *.tokens.json cascade-format token files
87
+ components/ <component>.json one component per file
88
+ fields/ <field>.json one field declaration per file
89
+ relationships/ <component>.json one CTR set per file
90
+ guidelines/ <topic>.json one guideline per file
91
+ platform-extensions/ <platform>-<registry>.json
92
+ ```
93
+
94
+ **NORMATIVE:** Each subdirectory is discovered by a recursive glob for `*.json` files (for
95
+ `tokens/`, `**/*.tokens.json`), with no index file — the same convention used by
96
+ `discover_json_files` in `sdk/core/src/discovery.rs` for the foundation dataset. Matched files
97
+ are processed in **sorted path order**; this is the sole basis for precedence below. A missing
98
+ or empty subdirectory contributes nothing and is not an error.
99
+
100
+ **NORMATIVE:** Merge semantics by subdirectory:
101
+
102
+ * **`tokens/`** — every `*.tokens.json` file is cascade-format (same shape as a foundation
103
+ cascade token file) and **MUST** validate against `cascade-file.schema.json`. Files are
104
+ **deep-merged** into one tokens object, in sorted path order. Entries **MAY** carry a `$ref`
105
+ to alias an existing token instead of a literal value.
106
+ * **`components/`, `fields/`, `guidelines/`, `platform-extensions/`** — one artifact per file.
107
+ Entries across all files in the subdirectory are concatenated, in sorted path order, and
108
+ injected into the corresponding catalog **add-or-replace by name** (for
109
+ `platform-extensions/`, by `termId`; see below). When two files declare the same name,
110
+ **the entry from the later file (in sorted path order) wins.**
111
+
112
+ **NORMATIVE:** Each fragment file **MUST** validate against its category's real JSON schema at
113
+ load time — this is enforced reference-SDK behavior, not an aspirational goal:
114
+
115
+ * **`components/`** → `component.schema.json`
116
+ * **`fields/`** → `field.schema.json` (see [`extensions/fields/`](#extensionsfields) below)
117
+ * **`guidelines/`** → `guideline.schema.json`
118
+ * **`relationships/`** → `relationship.schema.json` (see the Add/Override/remove rules above)
119
+ * **`platform-extensions/`** → `platform-extension.json` (see
120
+ [`extensions/platform-extensions/`](#extensionsplatform-extensions) below)
121
+ * **`tokens/`** → `cascade-file.schema.json` (see above)
122
+
123
+ A fragment that fails validation against its category schema is a manifest error — the
124
+ reference SDK rejects the load rather than silently skipping the malformed file.
125
+
126
+ * **`relationships/`** — Component/Token Relationship (CTR) entries. Relationships have no
127
+ inherent stable key — only an optional `uuid` — so add and override/remove use different
128
+ identity rules:
129
+ * **Add:** an entry with no `op` field is a full relationship object (validated against
130
+ `relationship.schema.json`), appended at the platform layer, in sorted path order.
131
+ * **Override / remove:** targeting an existing relationship **MUST** carry a `uuid`.
132
+ **NORMATIVE:** an entry with `"op": "override"` or `"op": "remove"` that omits `uuid` is a
133
+ manifest error — the reference SDK rejects it rather than silently skipping it. `"op":
134
+ "override"` also carries a `value` (the replacement relationship object); `"op": "remove"`
135
+ drops the matching record. Override/remove ops apply, in sorted path order, against the
136
+ accumulated set of added relationships (across all files, foundation and platform-local).
137
+
138
+ #### `extensions/fields/`
139
+
140
+ Platform-local field declarations, injected into the field catalog. **NORMATIVE:** each file
141
+ **MUST** validate against `field.schema.json`. Note: a top-level `formatting.conceptOrder` (if
142
+ declared) references field names by string — a platform that renames or removes a field it
143
+ also references there is self-inconsistent; that is a manifest-authoring concern, not enforced
144
+ by the reference SDK.
145
+
146
+ #### `extensions/platform-extensions/`
147
+
148
+ Platform terminology annotations layered onto **existing** foundation registry entries (for
149
+ example, platform-specific state names). **NORMATIVE:** each file **MUST** validate against
150
+ `platform-extension.json`, and every `termId` **MUST** already exist in the referenced
151
+ foundation registry — this mechanism annotates existing ids, it does **NOT** introduce new
152
+ ones.
153
+
154
+ ### `namingExceptions`
109
155
 
110
156
  Platform-local overlay on the base naming-exceptions set used by naming validation.
111
157
  **NORMATIVE:** the reference SDK applies `remove` before `add`, so a name listed in both
@@ -117,11 +163,7 @@ ends up present (add wins) rather than silently dropped. Absent this key, the ba
117
163
  | `add` | array of string | Names to add to the naming-exceptions set for this platform. |
118
164
  | `remove` | array of string | Names to remove from the base naming-exceptions set. |
119
165
 
120
- #### `extensions.platformExtensions`
121
-
122
- Platform terminology annotations layered onto **existing** foundation registry entries (for example, platform-specific state names). **NORMATIVE:** every `termId` **MUST** already exist in the referenced foundation registry — this mechanism annotates existing ids, it does **NOT** introduce new ones.
123
-
124
- #### `extensions.formatting`
166
+ ### `formatting`
125
167
 
126
168
  A platform **MAY** declare formatting rules that control how structured name objects are serialized into flat token name strings for that platform. See [Taxonomy — Platform formatting configuration](taxonomy.md#platform-formatting-configuration) for motivation and examples.
127
169
 
@@ -132,9 +174,9 @@ A platform **MAY** declare formatting rules that control how structured name obj
132
174
  | `delimiter` | string | Character(s) separating concepts in the serialized string (e.g. `-`, `_`, `.`, `/`). Default: `-`. |
133
175
  | `abbreviations` | object | Map of full term → abbreviated form (e.g. `{ "background": "bg" }`). Abbreviations are applied after concept ordering and before casing. |
134
176
 
135
- **NORMATIVE:** When `extensions.formatting` is absent, the default serialization defined in [Taxonomy](taxonomy.md#default-serialization-legacy-format) is used.
177
+ **NORMATIVE:** When `formatting` is absent, the default serialization defined in [Taxonomy](taxonomy.md#default-serialization-legacy-format) is used.
136
178
 
137
- **NORMATIVE:** A formatter applying `extensions.formatting` **MUST** produce deterministic output — the same name object and formatting configuration **MUST** always yield the same string.
179
+ **NORMATIVE:** A formatter applying `formatting` **MUST** produce deterministic output — the same name object and formatting configuration **MUST** always yield the same string.
138
180
 
139
181
  ## Validation
140
182
 
package/spec/mode-sets.md CHANGED
@@ -27,11 +27,11 @@ A **mode set declaration** is a JSON object describing one axis of variation. It
27
27
 
28
28
  These mode sets are declared in the `mode-sets/` catalog (see [Mode Set catalog](#mode-set-catalog)) and **SHOULD** be used consistently across Spectrum-compatible datasets:
29
29
 
30
- | `name` | `modes` | `default` | Notes |
31
- | ------------- | ---------------------------- | --------- | --------------------------------------------------------------------------------- |
32
- | `colorScheme` | `light`, `dark`, `wireframe` | `light` | Theme / appearance. |
33
- | `scale` | `desktop`, `mobile` | `desktop` | Density scale. Legacy names; desktop = medium, mobile = large in W3C terminology. |
34
- | `contrast` | `regular`, `high` | `regular` | Accessibility contrast level. |
30
+ | `name` | `modes` | `default` | Notes |
31
+ | ------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
32
+ | `colorScheme` | `light`, `dark`, `wireframe` | `light` | Theme / appearance. |
33
+ | `scale` | `desktop`, `mobile` | `desktop` | Density scale. Legacy names; desktop = medium, mobile = large in W3C terminology. |
34
+ | `contrast` | `regular`, `high` | `regular` | Accessibility contrast level. iOS platform data authors `increased` as a synonym for `high` — treat the two as the same crosswalk term, not distinct values. |
35
35
 
36
36
  ## Mode Set catalog
37
37
 
package/spec/taxonomy.md CHANGED
@@ -282,7 +282,7 @@ This ordering is preserved for backward compatibility with the current `@adobe/s
282
282
 
283
283
  ## Platform formatting configuration
284
284
 
285
- A platform [manifest](manifest.md) **MAY** declare formatting rules in its [`extensions.formatting`](manifest.md#extensionsformatting) section to control concept ordering, casing, delimiters, and abbreviations. The normative contract for these fields is defined in [Manifest — `extensions.formatting`](manifest.md#extensionsformatting).
285
+ A platform [manifest](manifest.md) **MAY** declare formatting rules in its top-level [`formatting`](manifest.md#formatting) field to control concept ordering, casing, delimiters, and abbreviations. The normative contract for these fields is defined in [Manifest — `formatting`](manifest.md#formatting).
286
286
 
287
287
  When no platform formatting is declared, the default serialization above is used.
288
288
 
@@ -338,5 +338,5 @@ node tools/token-corpus-migrate/src/cli.js \
338
338
  * [#806 — Token Taxonomy, Vocabulary, and Formatting](https://github.com/adobe/spectrum-design-data/discussions/806)
339
339
  * [#661 — Spectrum Design System Glossary](https://github.com/adobe/spectrum-design-data/discussions/661)
340
340
  * [#646 — Token Schema Structure and Validation System](https://github.com/adobe/spectrum-design-data/discussions/646)
341
- * [Manifest — `extensions.formatting`](manifest.md#extensionsformatting) — normative contract for platform formatting rules
341
+ * [Manifest — `formatting`](manifest.md#formatting) — normative contract for platform formatting rules
342
342
  * Nate Baldwin, "Naming conventions & shared taxonomy" — Design Data & Platforms onsite, April 1, 2026