@adobe/design-data-spec 4.8.0 → 4.9.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 (88) hide show
  1. package/conformance/README.md +32 -9
  2. package/conformance/invalid/SPEC-051/dataset.json +9 -3
  3. package/conformance/invalid/SPEC-052/dataset.json +14 -4
  4. package/conformance/invalid/SPEC-053/dataset.json +16 -4
  5. package/conformance/invalid/SPEC-054/dataset.json +18 -5
  6. package/conformance/invalid/SPEC-055/dataset.json +9 -3
  7. package/conformance/invalid/SPEC-057/dataset.json +5 -2
  8. package/conformance/invalid/SPEC-058/dataset.json +8 -3
  9. package/conformance/invalid/SPEC-059/dataset.json +28 -0
  10. package/conformance/invalid/SPEC-059/expected-errors.json +10 -0
  11. package/conformance/invalid/SPEC-060/dataset.json +44 -0
  12. package/conformance/invalid/SPEC-060/expected-errors.json +10 -0
  13. package/conformance/invalid/SPEC-061/dataset.json +63 -0
  14. package/conformance/invalid/SPEC-061/expected-errors.json +15 -0
  15. package/conformance/invalid/SPEC-062/dataset.json +50 -0
  16. package/conformance/invalid/SPEC-062/expected-errors.json +10 -0
  17. package/conformance/invalid/SPEC-063/dataset.json +57 -0
  18. package/conformance/invalid/SPEC-063/expected-errors.json +10 -0
  19. package/conformance/invalid/SPEC-064/dataset.json +53 -0
  20. package/conformance/invalid/SPEC-064/expected-errors.json +15 -0
  21. package/conformance/invalid/SPEC-065/dataset.json +78 -0
  22. package/conformance/invalid/SPEC-065/expected-errors.json +10 -0
  23. package/conformance/invalid/SPEC-066/dataset.json +37 -0
  24. package/conformance/invalid/SPEC-066/expected-errors.json +10 -0
  25. package/conformance/invalid/SPEC-067/dataset.json +60 -0
  26. package/conformance/invalid/SPEC-067/expected-errors.json +10 -0
  27. package/conformance/invalid/SPEC-068/dataset.json +37 -0
  28. package/conformance/invalid/SPEC-068/expected-errors.json +10 -0
  29. package/conformance/invalid/SPEC-069/dataset.json +63 -0
  30. package/conformance/invalid/SPEC-069/expected-errors.json +10 -0
  31. package/conformance/invalid/SPEC-070/dataset.json +71 -0
  32. package/conformance/invalid/SPEC-070/expected-errors.json +15 -0
  33. package/conformance/invalid/SPEC-071/dataset.json +77 -0
  34. package/conformance/invalid/SPEC-071/expected-errors.json +10 -0
  35. package/conformance/invalid/SPEC-072/dataset.json +52 -0
  36. package/conformance/invalid/SPEC-072/expected-errors.json +10 -0
  37. package/conformance/invalid/SPEC-073/dataset.json +59 -0
  38. package/conformance/invalid/SPEC-073/expected-errors.json +10 -0
  39. package/conformance/invalid/SPEC-074/dataset.json +36 -0
  40. package/conformance/invalid/SPEC-074/expected-errors.json +10 -0
  41. package/conformance/invalid/SPEC-075/dataset.json +53 -0
  42. package/conformance/invalid/SPEC-075/expected-errors.json +10 -0
  43. package/conformance/invalid/SPEC-076/dataset.json +62 -0
  44. package/conformance/invalid/SPEC-076/expected-errors.json +10 -0
  45. package/conformance/invalid/SPEC-076/manifest.json +6 -0
  46. package/conformance/invalid/SPEC-079/dataset.json +62 -0
  47. package/conformance/invalid/SPEC-079/expected-errors.json +10 -0
  48. package/conformance/invalid/SPEC-079/manifest.json +58 -0
  49. package/conformance/invalid/SPEC-080/dataset.json +62 -0
  50. package/conformance/invalid/SPEC-080/expected-errors.json +10 -0
  51. package/conformance/invalid/SPEC-080/manifest.json +32 -0
  52. package/conformance/invalid/SPEC-081/dataset.json +62 -0
  53. package/conformance/invalid/SPEC-081/expected-errors.json +10 -0
  54. package/conformance/invalid/SPEC-081/manifest.json +7 -0
  55. package/conformance/invalid/SPEC-082/dataset.json +62 -0
  56. package/conformance/invalid/SPEC-082/expected-errors.json +10 -0
  57. package/conformance/invalid/SPEC-082/manifest.json +14 -0
  58. package/conformance/invalid/SPEC-083/dataset.json +47 -0
  59. package/conformance/invalid/SPEC-083/expected-errors.json +10 -0
  60. package/conformance/invalid/SPEC-084/dataset.json +16 -0
  61. package/conformance/invalid/SPEC-084/expected-errors.json +10 -0
  62. package/conformance/valid/SPEC-025/dataset.json +1 -1
  63. package/conformance/valid/SPEC-049/dataset.json +1 -1
  64. package/conformance/valid/SPEC-051/dataset.json +40 -9
  65. package/conformance/valid/SPEC-064/dataset.json +64 -0
  66. package/conformance/valid/SPEC-064/expected-errors.json +4 -0
  67. package/conformance/valid/SPEC-065/dataset.json +89 -0
  68. package/conformance/valid/SPEC-065/expected-errors.json +4 -0
  69. package/conformance/valid/SPEC-079/dataset.json +62 -0
  70. package/conformance/valid/SPEC-079/manifest.json +58 -0
  71. package/package.json +1 -1
  72. package/rules/rules.yaml +373 -0
  73. package/schemas/anatomy-part.schema.json +11 -0
  74. package/schemas/component-changelog.schema.json +306 -0
  75. package/schemas/component-spec-sheet.schema.json +215 -0
  76. package/schemas/component-visual.schema.json +287 -0
  77. package/schemas/component.schema.json +80 -0
  78. package/schemas/manifest.schema.json +5 -0
  79. package/schemas/relationship.schema.json +8 -6
  80. package/spec/anatomy-format.md +97 -10
  81. package/spec/artifact-moves.md +157 -0
  82. package/spec/component-changelog.md +49 -0
  83. package/spec/component-format.md +4 -0
  84. package/spec/component-visuals.md +210 -0
  85. package/spec/index.md +5 -0
  86. package/spec/manifest.md +46 -29
  87. package/spec/option-constraints.md +87 -0
  88. package/spec/relationship-format.md +14 -11
package/rules/rules.yaml CHANGED
@@ -724,3 +724,376 @@ rules:
724
724
  message: "Alias {token} sets {field}={ownValue} but its $ref chain resolves to a token with {field}={leafValue}"
725
725
  spec_ref: spec/cascade.md#alias-resolution
726
726
  introduced_in: "1.0.0-draft"
727
+
728
+ - id: SPEC-060
729
+ name: visual-anatomy-valid
730
+ severity: error
731
+ layer: 2
732
+ category: reference-integrity
733
+ note: >
734
+ Cross-file sibling of SPEC-020 for visual recipes (`visuals/*.json`). A JSON Schema
735
+ cannot read the component file, so this is not a Layer 1 check. Skipped when no
736
+ component catalog is loaded.
737
+ assert: >
738
+ A visual recipe's `component` MUST name a declared component. Every key of `parts` MUST
739
+ be a declared anatomy part. `root` MUST be a recipe part and a top-level anatomy part
740
+ (listed in no other part's `contains`). Each part's `children` MUST be recipe parts and
741
+ appear in that anatomy part's `contains`. `around` MUST name a declared anatomy part;
742
+ `pressTarget` MUST name a declared anatomy part that is also a recipe part.
743
+ message: "Recipe part '{part}' is not a declared anatomy part of '{component}'"
744
+ spec_ref: spec/component-visuals.md#validation
745
+ introduced_in: "1.0.0-draft"
746
+
747
+ - id: SPEC-061
748
+ name: visual-conditions-valid
749
+ severity: error
750
+ layer: 2
751
+ category: reference-integrity
752
+ assert: >
753
+ Every `when.options` key (and `visibleWhen.options` key) in a visual recipe MUST be an
754
+ option of the component, with a value in that option's `values` (a boolean option
755
+ requires a boolean). Every `when.states` / `visibleWhen.states` entry MUST be the `name`
756
+ of a state declared by the component. Every `{name}` placeholder in a `token` or
757
+ `relationship` selector MUST be `size`, `sizeIndex` or an option of the component that
758
+ declares `values`.
759
+ message: "Option '{option}' of '{component}' has no value {value}"
760
+ spec_ref: spec/component-visuals.md#validation
761
+ introduced_in: "1.0.0-draft"
762
+
763
+ - id: SPEC-062
764
+ name: visual-selectors-resolve
765
+ severity: error
766
+ layer: 2
767
+ category: reference-integrity
768
+ note: >
769
+ `{sizeIndex}` expands over the `sizeScale` values, `{size}` over its keys and `{option}`
770
+ over the option's declared values before resolving. Relationship `context` is compared trimmed. Token selectors match name-object
771
+ fields the way the visual runtime does, including the property + `scaleIndex` fallback.
772
+ assert: >
773
+ Every `{relationship: {property, context}}` selector in a visual recipe MUST resolve to
774
+ a relationship record whose `scope.component` is the recipe's component and whose
775
+ `scope.property` and `context` match. Every `{token: {...}}` selector MUST match at
776
+ least one token.
777
+ message: "No relationship for '{component}' with property '{property}' and context '{context}'"
778
+ spec_ref: spec/component-visuals.md#validation
779
+ introduced_in: "1.0.0-draft"
780
+
781
+ - id: SPEC-063
782
+ name: visual-size-scale-valid
783
+ severity: error
784
+ layer: 2
785
+ category: reference-integrity
786
+ assert: >
787
+ The keys of a visual recipe's `sizeScale` MUST equal the values of the component's
788
+ `size` option. A component without a `size` option MAY declare a one-entry `sizeScale`,
789
+ the single size renderers draw (for example the fixed-size iOS switch).
790
+ message: "sizeScale key '{size}' is not a 'size' value of '{component}'"
791
+ spec_ref: spec/component-visuals.md#validation
792
+ introduced_in: "1.0.0-draft"
793
+ - id: SPEC-064
794
+ name: constraint-options-declared
795
+ severity: error
796
+ layer: 2
797
+ category: component
798
+ assert: >
799
+ Every option name used in a component constraint's `when` or `require` MUST be declared in
800
+ the component's `options`. Every value used there MUST appear in that option's `values`
801
+ list. Value checks are silent when the option declares no `values` list.
802
+ message: "Constraint option '{option}' is not declared on component '{component}'"
803
+ spec_ref: spec/option-constraints.md#rules
804
+ introduced_in: "1.0.0-draft"
805
+
806
+ - id: SPEC-065
807
+ name: recipe-targets-excluded-combination
808
+ severity: warning
809
+ layer: 2
810
+ category: component
811
+ note: >
812
+ Only `error` constraints count. Unpinned options range over their declared values (booleans
813
+ over true and false); an option with no known domain is never proven excluded. A rule that
814
+ also matches a valid combination is not reported.
815
+ assert: >
816
+ A visual recipe rule (`when.options`) or relationship `scope.options` SHOULD NOT match only
817
+ combinations excluded by an `error` constraint on the component.
818
+ message: "Recipe rule for '{component}' matches only combinations excluded by an error constraint"
819
+ spec_ref: spec/option-constraints.md#rules
820
+ introduced_in: "1.0.0-draft"
821
+
822
+ - id: SPEC-066
823
+ name: visual-covers-component
824
+ severity: warning
825
+ layer: 2
826
+ category: component
827
+ note: >
828
+ A name counts as used when a `when` or `visibleWhen` condition names it, when a token the
829
+ recipe binds carries the state, or when it is implied: `size` by `sizeScale`, `label` by a
830
+ text part, and `isDisabled` or `isSelected` by the `disabled` or `selected` states (and the
831
+ reverse). New recipes MUST be free of this warning; existing recipes are held at a baseline.
832
+ assert: >
833
+ Every option and state a component declares SHOULD either affect its visual recipe or be
834
+ listed in the recipe's `notVisual` array. A `notVisual` name MUST be declared by the
835
+ component and SHOULD NOT also be used by the recipe.
836
+ message: "Option '{option}' of '{component}' does not affect the recipe; use it in a condition or list it in notVisual"
837
+ spec_ref: spec/component-visuals.md#validation
838
+ introduced_in: "1.0.0-draft"
839
+
840
+ - id: SPEC-067
841
+ name: visual-prefers-relationship
842
+ severity: warning
843
+ layer: 2
844
+ category: reference-integrity
845
+ note: >
846
+ Relationships carry the component scope and the design-token group label, so a recipe
847
+ binds through them by default. Use a `token` selector only when no relationship covers the
848
+ value.
849
+ assert: >
850
+ A `token` selector in a visual recipe SHOULD NOT bind a token that one of the component's
851
+ relationships already binds.
852
+ message: "Token selector {selector} is also bound by relationship {relationship}; bind it through the relationship"
853
+ spec_ref: spec/component-visuals.md#validation
854
+ introduced_in: "1.0.0-draft"
855
+
856
+ - id: SPEC-068
857
+ name: visual-literal-discouraged
858
+ severity: warning
859
+ layer: 2
860
+ category: tech-debt
861
+ note: >
862
+ Layout keywords (`direction`, `align`, `justify`), `transparent` and `none` colors, zero
863
+ widths and ratio radii (`relativeTo`) are fine as literals.
864
+ assert: >
865
+ A literal (`{"value": ...}`) in a visual recipe SHOULD NOT supply a color or a non-zero
866
+ size; a token or relationship describes it, or one should be added.
867
+ message: "Literal {value} for '{prop}' on part '{part}'; bind a token or relationship instead"
868
+ spec_ref: spec/component-visuals.md#validation
869
+ introduced_in: "1.0.0-draft"
870
+
871
+ - id: SPEC-069
872
+ name: visual-token-not-deprecated
873
+ severity: warning
874
+ layer: 2
875
+ category: reference-integrity
876
+ note: >
877
+ Extends SPEC-058, which checks relationship `$ref` values, to the bindings a recipe draws
878
+ with.
879
+ assert: >
880
+ A `token` or `relationship` selector in a visual recipe SHOULD NOT resolve only to
881
+ deprecated tokens (those with `lifecycle.deprecatedIn`).
882
+ message: "{selector} resolves only to deprecated tokens (for example '{token}', deprecated in {version})"
883
+ spec_ref: spec/component-visuals.md#validation
884
+ introduced_in: "1.0.0-draft"
885
+
886
+ - id: SPEC-070
887
+ name: visual-ring-names-agree
888
+ severity: warning
889
+ layer: 2
890
+ category: naming-consistency
891
+ note: >
892
+ A shared name lets tools, relationships and docs treat the focus ring alike across
893
+ components. The rule reports the disagreement, not which name wins.
894
+ assert: >
895
+ Visual recipes SHOULD give the focus ring part (`kind: "ring"`) the same name.
896
+ message: "Focus ring part is named '{name}' here but '{other}' in other recipes; recipes should name it alike"
897
+ spec_ref: spec/component-visuals.md#validation
898
+ introduced_in: "1.0.0-draft"
899
+
900
+ - id: SPEC-071
901
+ name: visual-relationship-contexts-used
902
+ severity: info
903
+ layer: 2
904
+ category: component
905
+ note: >
906
+ Unused contexts are not errors: relationships describe the component beyond its recipe.
907
+ The count shows how much of the component's token data a recipe still has to cover.
908
+ assert: >
909
+ A visual recipe reports how many of its component's relationship contexts the recipe
910
+ never binds.
911
+ message: "{n} of {total} relationship contexts of '{component}' have no binding in this recipe, for example {sample}"
912
+ spec_ref: spec/component-visuals.md#validation
913
+ introduced_in: "1.0.0-draft"
914
+
915
+ - id: SPEC-072
916
+ name: relationship-duplicate-ref-scale-counterpart
917
+ severity: warning
918
+ layer: 2
919
+ category: relationship
920
+ note: >
921
+ Typically a desktop and a mobile record where the second was copied without repointing it,
922
+ so the other scale's token is never reachable through the relationship. A repeated record
923
+ on a token with no scale counterpart is not reported.
924
+ assert: >
925
+ Two relationship records with the same scope, context and `$ref` SHOULD NOT both point at
926
+ a token that has a scale counterpart (the same name with another `scale` value).
927
+ message: "Relationship {scope} references {ref} twice, but the token has a {scale} counterpart ({counterpart}); point one record at it"
928
+ spec_ref: spec/relationship-format.md
929
+ introduced_in: "1.0.0-draft"
930
+
931
+ - id: SPEC-073
932
+ name: component-anatomy-reviewed
933
+ severity: info
934
+ layer: 2
935
+ category: component
936
+ note: >
937
+ One diagnostic for the whole dataset, so the gap stays visible without a line per
938
+ component. Never an error: anatomy is authored as recipes are built. A component with
939
+ anatomy and no `anatomyReview` counts as draft.
940
+ assert: >
941
+ A component that has relationship records SHOULD declare anatomy, and a component's
942
+ anatomy SHOULD have `anatomyReview.status` of `reviewed`.
943
+ message: "Components without reviewed anatomy: {n} with relationships but no anatomy ({names}); {m} with draft anatomy ({names})"
944
+ spec_ref: spec/anatomy-format.md#review-checklist
945
+ introduced_in: "1.0.0-draft"
946
+
947
+ - id: SPEC-074
948
+ name: anatomy-present-in-declared
949
+ severity: error
950
+ layer: 2
951
+ category: reference-integrity
952
+ note: >
953
+ The anatomy version of SPEC-054. State names are compared after normalization, so
954
+ `focus`, `key-focus` and `keyboard-focus` are one state. A part that lists `presentIn`
955
+ on a component that declares no states is also reported.
956
+ assert: >
957
+ Every entry of an anatomy part's `presentIn` MUST match a state declared on the same
958
+ component.
959
+ message: "Anatomy part '{part}' on component '{component}' is present in state '{state}', but it is not a declared state"
960
+ spec_ref: spec/anatomy-format.md#presentin
961
+ introduced_in: "1.0.0-draft"
962
+
963
+ - id: SPEC-075
964
+ name: visual-present-in-matches-anatomy
965
+ severity: error
966
+ layer: 2
967
+ category: component
968
+ note: >
969
+ Keeps a recipe's `visibleWhen.states` and the anatomy's `presentIn` from drifting. States
970
+ are compared after normalization. `visibleWhen.states` means every listed state, and
971
+ `presentIn` means any listed state, so a difference is an error only when neither side
972
+ lists more than one state; otherwise it is reported at info level.
973
+ assert: >
974
+ For a recipe part that is also an anatomy part, the recipe's `visibleWhen.states` MUST
975
+ equal the anatomy part's `presentIn`.
976
+ message: "Recipe part '{part}' of '{component}' is visible in {recipeStates}, but its anatomy presentIn lists {presentIn}"
977
+ spec_ref: spec/anatomy-format.md#presentin
978
+ introduced_in: "1.0.0-draft"
979
+
980
+ - id: SPEC-076
981
+ name: visual-bindings-resolve-on-platform
982
+ severity: warning
983
+ layer: 2
984
+ category: component
985
+ note: >
986
+ Runs only when a platform manifest is in scope (for example `design-data validate
987
+ --platform ios --visuals-path ...`). Renders each recipe on the foundation and with the
988
+ manifest's cascade applied, over every size, light and dark, desktop and mobile, each
989
+ interaction state and each option value, and reports the unresolved bindings that appear
990
+ only under the platform. A gap the foundation also has is not reported.
991
+ assert: >
992
+ Every token or relationship binding a visual recipe resolves on the foundation SHOULD also
993
+ resolve once the platform manifest's include, exclude, overrides and extensions are applied.
994
+ message: "Recipe for '{component}' binds something that resolves on the foundation but not under platform '{platform}': {binding}"
995
+ spec_ref: spec/manifest.md
996
+ introduced_in: "1.0.0-draft"
997
+
998
+ # SPEC-077 (artifact-single-owner) and SPEC-078 (promoted-mode-fallback) are reserved by
999
+ # spec/artifact-moves.md and are not in this catalog until they are implemented.
1000
+
1001
+ - id: SPEC-079
1002
+ name: platform-recipe-resolves
1003
+ severity: warning
1004
+ layer: 2
1005
+ category: component
1006
+ note: >
1007
+ Runs only when a platform manifest with `extensions/visuals/` is in scope. A warning for
1008
+ now; a follow-up promotes it to an error once this release is published (needs a changeset).
1009
+ Renders each platform-owned recipe over the request grid with no foundation comparison.
1010
+ The request grid is `light`/`dark` by `desktop`/`mobile`, as in SPEC-076. The CI guard
1011
+ `.github/scripts/validate-platform-recipes.mjs` fails on it even while it is a warning.
1012
+ assert: >
1013
+ A platform-owned visual recipe MUST render over the request grid without a scene
1014
+ diagnostic once the platform manifest is applied.
1015
+ message: "Platform recipe for '{component}' does not render under platform '{platform}': {diagnostic}"
1016
+ spec_ref: spec/component-visuals.md#platform-owned-recipes
1017
+ introduced_in: "1.0.0-draft"
1018
+
1019
+ - id: SPEC-080
1020
+ name: platform-recipe-replaces-foundation
1021
+ severity: info
1022
+ layer: 2
1023
+ category: component
1024
+ note: >
1025
+ Runs only when a platform manifest with `extensions/visuals/` is in scope. Shows a reviewer
1026
+ which foundation recipe a platform recipe replaced, so a copy that may drift is visible.
1027
+ assert: >
1028
+ A platform recipe that has the same component name as a foundation recipe replaces it
1029
+ for that platform; validation reports the pair.
1030
+ message: "Recipe for '{component}' in platform '{platform}' replaces the foundation recipe"
1031
+ spec_ref: spec/component-visuals.md#platform-owned-recipes
1032
+ introduced_in: "1.0.0-draft"
1033
+
1034
+ - id: SPEC-081
1035
+ name: component-alias-valid
1036
+ severity: error
1037
+ layer: 2
1038
+ category: reference-integrity
1039
+ note: >
1040
+ Runs only when a platform manifest is in scope. Reads the manifest's merged
1041
+ `extensions.components` names and the foundation component catalog.
1042
+ assert: >
1043
+ Each `componentAliases` key MUST be a foundation component, its target MUST be a component
1044
+ the platform declares in `extensions/components/`, aliases MUST NOT chain or form a cycle,
1045
+ and a key MUST NOT also be replaced by `extensions/components/<key>.json`.
1046
+ message: "Invalid component alias '{alias}' in platform '{platform}': {reason}"
1047
+ spec_ref: spec/manifest.md#componentaliases
1048
+ introduced_in: "1.0.0-draft"
1049
+
1050
+ - id: SPEC-082
1051
+ name: platform-manifest-applies
1052
+ severity: error
1053
+ layer: 2
1054
+ category: reference-integrity
1055
+ note: >
1056
+ Runs only when a platform manifest is in scope. SPEC-076, SPEC-079 and the platform recipe
1057
+ checks apply the manifest themselves and report nothing when it fails, so this rule is what
1058
+ makes a manifest that cannot apply (for example one that extends a mode set that was not
1059
+ loaded, as when `validate --platform` runs without `--mode-sets-path`) fail instead of look
1060
+ like a pass. The CI guard `.github/scripts/validate-platform-recipes.mjs` fails on it.
1061
+ assert: >
1062
+ A platform manifest MUST apply to the foundation graph.
1063
+ message: "Platform '{platform}' failed to apply, so the platform rules (SPEC-076, SPEC-079 and the platform recipe checks) did not run: {error}"
1064
+ spec_ref: spec/manifest.md#validation
1065
+ introduced_in: "1.0.0-draft"
1066
+
1067
+ - id: SPEC-083
1068
+ name: visual-props-read-by-renderer
1069
+ severity: warning
1070
+ layer: 2
1071
+ category: component
1072
+ note: >
1073
+ The recipe schema accepts props the renderer does not read (`direction`,
1074
+ `align`, `minWidth`, and others on a part kind that has no use for them). The renderer
1075
+ ignores them without a diagnostic. The list of read props is the renderer's own table,
1076
+ not a copy kept in the rule. A recipe binding an ignored prop looks complete and draws
1077
+ differently from what it says.
1078
+ assert: >
1079
+ Every prop in a recipe part's `props` MUST be one the renderer reads for that part: the
1080
+ root part, a part of its `kind`, or a part with a `placement`.
1081
+ message: "Part '{part}' ({role}) of '{component}' binds '{prop}', which the renderer does not read for that part"
1082
+ spec_ref: spec/component-visuals.md#validation
1083
+ introduced_in: "1.0.0-draft"
1084
+
1085
+ - id: SPEC-084
1086
+ name: ctr-uuid-required
1087
+ severity: error
1088
+ layer: 2
1089
+ category: reference-integrity
1090
+ note: >
1091
+ Changelogs, platform overrides and `$ref` all need a stable identity for a
1092
+ relationship. The schema requires `uuid` in `relationships/*.json`; this rule also
1093
+ covers records the schema does not see (a platform manifest's inline relationship
1094
+ adds). Uniqueness is SPEC-056.
1095
+ assert: >
1096
+ Every relationship record MUST have a `uuid`.
1097
+ message: "Relationship #{index} has no uuid (component '{component}', property '{property}')"
1098
+ spec_ref: spec/relationship-format.md#identity
1099
+ introduced_in: "1.0.0-draft"
@@ -21,6 +21,12 @@
21
21
  "default": false,
22
22
  "description": "Whether this part is always rendered regardless of component configuration."
23
23
  },
24
+ "presentIn": {
25
+ "type": "array",
26
+ "items": { "type": "string" },
27
+ "uniqueItems": true,
28
+ "description": "Declared component states in which this part is present (for example focus for a focus indicator); drawn when any is active. Absent means the part can appear in any state. Not for state-dependent styling. Each entry should match a `states[].name` declared on the component (SPEC-074)."
29
+ },
24
30
  "contains": {
25
31
  "type": "array",
26
32
  "items": {
@@ -41,5 +47,10 @@
41
47
  "description": "Version lifecycle metadata for this anatomy part. Set lifecycle.deprecated to signal that tokens referencing this part should migrate."
42
48
  }
43
49
  },
50
+ "if": {
51
+ "required": ["required"],
52
+ "properties": { "required": { "const": true } }
53
+ },
54
+ "then": { "not": { "required": ["presentIn"] } },
44
55
  "additionalProperties": false
45
56
  }