@verndale/accessibility-standards 1.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.
- package/OPERATING.md +5 -0
- package/README.md +22 -0
- package/bin/accessibility-standards.mjs +32 -0
- package/dist/manifest.json +202 -0
- package/dist/profiles/ai-orchestration/coverage-manifest.json +202 -0
- package/dist/profiles/ai-orchestration/implementation/accessibility.md +329 -0
- package/dist/profiles/ai-orchestration/pattern-contracts.json +697 -0
- package/dist/profiles/ai-orchestration/patterns/accordion.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/alert.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/carousel.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/combobox.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/consequential-submission.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/dialog.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/disclosure.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/dynamic-status.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/field.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/image.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/listbox.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/media-player.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/menu-button.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/pagination.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/popover.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/progress.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/tabs.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/tooltip.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/validation.md +11 -0
- package/dist/profiles/ai-orchestration/review/accessibility.md +67 -0
- package/dist/profiles/ai-orchestration/semantics/accessible-name.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/alert.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/consequential-action.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/data-table.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/expanded-state.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.initial.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.return.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.visible.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-error.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-label.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-required.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/headings.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/keyboard.escape.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/keyboard.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/landmarks.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/live-status.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/media-alternatives.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/motion-control.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/name-role-value.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/native-dialog.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/native-elements.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/non-text-alternative.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/roles-states-properties.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/selected-state.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/target-size.md +8 -0
- package/dist/profiles/ai-orchestration/semantics.json +289 -0
- package/dist/profiles/ai-orchestration/testing/accessibility.md +138 -0
- package/dist/profiles/conductor/a11y-semantics.md +152 -0
- package/dist/profiles/conductor/accessibility-quality.md +179 -0
- package/dist/profiles/conductor/applicability-matrix.json +428 -0
- package/dist/profiles/conductor/coverage-manifest.json +202 -0
- package/dist/profiles/conductor/pattern-contracts.json +697 -0
- package/dist/profiles/conductor/patterns/accordion.json +44 -0
- package/dist/profiles/conductor/patterns/alert.json +34 -0
- package/dist/profiles/conductor/patterns/carousel.json +45 -0
- package/dist/profiles/conductor/patterns/combobox.json +44 -0
- package/dist/profiles/conductor/patterns/consequential-submission.json +38 -0
- package/dist/profiles/conductor/patterns/dialog.json +47 -0
- package/dist/profiles/conductor/patterns/disclosure.json +39 -0
- package/dist/profiles/conductor/patterns/dynamic-status.json +36 -0
- package/dist/profiles/conductor/patterns/field.json +39 -0
- package/dist/profiles/conductor/patterns/image.json +35 -0
- package/dist/profiles/conductor/patterns/listbox.json +43 -0
- package/dist/profiles/conductor/patterns/media-player.json +45 -0
- package/dist/profiles/conductor/patterns/menu-button.json +41 -0
- package/dist/profiles/conductor/patterns/pagination.json +39 -0
- package/dist/profiles/conductor/patterns/popover.json +40 -0
- package/dist/profiles/conductor/patterns/progress.json +37 -0
- package/dist/profiles/conductor/patterns/tabs.json +42 -0
- package/dist/profiles/conductor/patterns/tooltip.json +37 -0
- package/dist/profiles/conductor/patterns/validation.json +42 -0
- package/dist/profiles/conductor/semantics.json +289 -0
- package/dist/projection-manifest.json +90 -0
- package/dist/requirements-to-implementation.md +21 -0
- package/lib/build-manifest.mjs +20 -0
- package/lib/build.mjs +31 -0
- package/lib/compile-applicability.mjs +56 -0
- package/lib/digest.mjs +11 -0
- package/lib/index.mjs +4 -0
- package/lib/load.mjs +40 -0
- package/lib/render.mjs +71 -0
- package/lib/sync.mjs +94 -0
- package/lib/validate.mjs +31 -0
- package/package.json +46 -0
- package/profiles/ai-orchestration/profile.json +1 -0
- package/profiles/ai-orchestration/templates/implementation/README.md +3 -0
- package/profiles/ai-orchestration/templates/patterns/README.md +3 -0
- package/profiles/ai-orchestration/templates/review/README.md +3 -0
- package/profiles/ai-orchestration/templates/semantics/README.md +3 -0
- package/profiles/ai-orchestration/templates/testing/README.md +3 -0
- package/profiles/conductor/profile.json +1 -0
- package/profiles/conductor/templates/applicability/README.md +3 -0
- package/profiles/conductor/templates/evidence/README.md +3 -0
- package/profiles/conductor/templates/patterns/README.md +3 -0
- package/profiles/conductor/templates/semantics/README.md +3 -0
- package/schemas/applicability-matrix.schema.json +1 -0
- package/schemas/consumer-config.schema.json +1 -0
- package/schemas/consumer-routes.schema.json +1 -0
- package/schemas/manifest.schema.json +1 -0
- package/schemas/pattern.schema.json +1 -0
- package/schemas/profile.schema.json +1 -0
- package/schemas/projection-manifest.schema.json +1 -0
- package/schemas/record.schema.json +1 -0
- package/schemas/semantic.schema.json +1 -0
- package/src/applicability/facts.yml +1 -0
- package/src/applicability/matrix.yml +20 -0
- package/src/evidence/proof-routing.yml +1 -0
- package/src/foundations/conformance-boundary.yml +1 -0
- package/src/foundations/standards-hierarchy.yml +1 -0
- package/src/foundations/wcag-2.2.yml +1 -0
- package/src/patterns/disclosure/accordion.yml +1 -0
- package/src/patterns/disclosure/disclosure.yml +1 -0
- package/src/patterns/feedback/alert.yml +1 -0
- package/src/patterns/feedback/dynamic-status.yml +1 -0
- package/src/patterns/feedback/progress.yml +1 -0
- package/src/patterns/forms/consequential-submission.yml +1 -0
- package/src/patterns/forms/field.yml +1 -0
- package/src/patterns/forms/validation.yml +1 -0
- package/src/patterns/media/carousel.yml +1 -0
- package/src/patterns/media/image.yml +1 -0
- package/src/patterns/media/media-player.yml +1 -0
- package/src/patterns/navigation/menu-button.yml +1 -0
- package/src/patterns/navigation/pagination.yml +1 -0
- package/src/patterns/navigation/tabs.yml +1 -0
- package/src/patterns/overlays/dialog.yml +1 -0
- package/src/patterns/overlays/popover.yml +1 -0
- package/src/patterns/overlays/tooltip.yml +1 -0
- package/src/patterns/selection/combobox.yml +1 -0
- package/src/patterns/selection/listbox.yml +1 -0
- package/src/policies/enhanced-quality.yml +1 -0
- package/src/policies/target-size.yml +1 -0
- package/src/semantics/accessible-names.yml +1 -0
- package/src/semantics/forms.yml +1 -0
- package/src/semantics/headings-landmarks.yml +1 -0
- package/src/semantics/keyboard-focus.yml +1 -0
- package/src/semantics/live-regions.yml +1 -0
- package/src/semantics/media.yml +1 -0
- package/src/semantics/native-elements.yml +1 -0
- package/src/semantics/roles-states-properties.yml +1 -0
- package/src/semantics/tables.yml +1 -0
- package/src/sources.yml +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_generated": "GENERATED by @verndale/accessibility-standards; DO NOT EDIT",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"pattern": {
|
|
5
|
+
"id": "pattern.tabs",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"title": "Tabs",
|
|
8
|
+
"kind": "pattern",
|
|
9
|
+
"requires": [
|
|
10
|
+
"semantics.accessible-name",
|
|
11
|
+
"semantics.selected-state",
|
|
12
|
+
"semantics.keyboard",
|
|
13
|
+
"semantics.focus.visible"
|
|
14
|
+
],
|
|
15
|
+
"activation": {
|
|
16
|
+
"fact": "component.pattern",
|
|
17
|
+
"equals": "tabs"
|
|
18
|
+
},
|
|
19
|
+
"scope": "Tabbed interfaces",
|
|
20
|
+
"behavior": [
|
|
21
|
+
"Arrow keys move between tabs",
|
|
22
|
+
"Selection and activation follow the documented model"
|
|
23
|
+
],
|
|
24
|
+
"product_decisions": [
|
|
25
|
+
"automatic_or_manual_activation"
|
|
26
|
+
],
|
|
27
|
+
"functional_spec_bindings": [
|
|
28
|
+
"behavior",
|
|
29
|
+
"states",
|
|
30
|
+
"keyboard"
|
|
31
|
+
],
|
|
32
|
+
"implementation_outcomes": [
|
|
33
|
+
"tabs-keyboard-model"
|
|
34
|
+
],
|
|
35
|
+
"evidence_routes": [
|
|
36
|
+
"unit",
|
|
37
|
+
"axe",
|
|
38
|
+
"e2e",
|
|
39
|
+
"human"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_generated": "GENERATED by @verndale/accessibility-standards; DO NOT EDIT",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"pattern": {
|
|
5
|
+
"id": "pattern.tooltip",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"title": "Tooltip",
|
|
8
|
+
"kind": "pattern",
|
|
9
|
+
"requires": [
|
|
10
|
+
"semantics.accessible-name",
|
|
11
|
+
"semantics.keyboard.escape"
|
|
12
|
+
],
|
|
13
|
+
"activation": {
|
|
14
|
+
"fact": "component.pattern",
|
|
15
|
+
"equals": "tooltip"
|
|
16
|
+
},
|
|
17
|
+
"scope": "Supplementary description",
|
|
18
|
+
"behavior": [
|
|
19
|
+
"Available on hover and focus",
|
|
20
|
+
"Never contains interactive content"
|
|
21
|
+
],
|
|
22
|
+
"product_decisions": [
|
|
23
|
+
"delay"
|
|
24
|
+
],
|
|
25
|
+
"functional_spec_bindings": [
|
|
26
|
+
"behavior"
|
|
27
|
+
],
|
|
28
|
+
"implementation_outcomes": [
|
|
29
|
+
"tooltip-description"
|
|
30
|
+
],
|
|
31
|
+
"evidence_routes": [
|
|
32
|
+
"unit",
|
|
33
|
+
"e2e",
|
|
34
|
+
"human"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_generated": "GENERATED by @verndale/accessibility-standards; DO NOT EDIT",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"pattern": {
|
|
5
|
+
"id": "pattern.validation",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"title": "Form validation",
|
|
8
|
+
"kind": "pattern",
|
|
9
|
+
"requires": [
|
|
10
|
+
"semantics.form-error",
|
|
11
|
+
"semantics.live-status",
|
|
12
|
+
"semantics.focus.initial"
|
|
13
|
+
],
|
|
14
|
+
"activation": {
|
|
15
|
+
"fact": "component.has_validation",
|
|
16
|
+
"equals": true
|
|
17
|
+
},
|
|
18
|
+
"scope": "Client or server validation",
|
|
19
|
+
"behavior": [
|
|
20
|
+
"Identify, associate, summarize, and focus errors deterministically"
|
|
21
|
+
],
|
|
22
|
+
"product_decisions": [
|
|
23
|
+
"validation_timing",
|
|
24
|
+
"error_summary"
|
|
25
|
+
],
|
|
26
|
+
"functional_spec_bindings": [
|
|
27
|
+
"validation",
|
|
28
|
+
"messaging",
|
|
29
|
+
"focus"
|
|
30
|
+
],
|
|
31
|
+
"implementation_outcomes": [
|
|
32
|
+
"validation-associations",
|
|
33
|
+
"error-summary"
|
|
34
|
+
],
|
|
35
|
+
"evidence_routes": [
|
|
36
|
+
"unit",
|
|
37
|
+
"axe",
|
|
38
|
+
"e2e",
|
|
39
|
+
"human"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_generated": "GENERATED by @verndale/accessibility-standards; DO NOT EDIT",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"semantics": [
|
|
5
|
+
{
|
|
6
|
+
"id": "semantics.accessible-name",
|
|
7
|
+
"version": "1.0.0",
|
|
8
|
+
"title": "Accessible name",
|
|
9
|
+
"kind": "semantic",
|
|
10
|
+
"requirement": "Every interactive or meaning-bearing control has a concise programmatic name derived from visible text when available.",
|
|
11
|
+
"proof": [
|
|
12
|
+
"unit",
|
|
13
|
+
"axe",
|
|
14
|
+
"human"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "semantics.alert",
|
|
19
|
+
"version": "1.0.0",
|
|
20
|
+
"title": "Urgent alert",
|
|
21
|
+
"kind": "semantic",
|
|
22
|
+
"requirement": "Use alert semantics only for urgent, time-sensitive messages that require immediate announcement.",
|
|
23
|
+
"proof": [
|
|
24
|
+
"unit",
|
|
25
|
+
"human"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "semantics.consequential-action",
|
|
30
|
+
"version": "1.0.0",
|
|
31
|
+
"title": "Consequential action",
|
|
32
|
+
"kind": "semantic",
|
|
33
|
+
"requirement": "Legal, financial, destructive, or data-submitting actions support review, confirmation, or reversal as required.",
|
|
34
|
+
"proof": [
|
|
35
|
+
"e2e",
|
|
36
|
+
"human"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "semantics.data-table",
|
|
41
|
+
"version": "1.0.0",
|
|
42
|
+
"title": "Data table",
|
|
43
|
+
"kind": "semantic",
|
|
44
|
+
"requirement": "Tabular relationships use table markup with associated headers; interactive grids implement their full keyboard model.",
|
|
45
|
+
"proof": [
|
|
46
|
+
"axe",
|
|
47
|
+
"e2e",
|
|
48
|
+
"human"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "semantics.expanded-state",
|
|
53
|
+
"version": "1.0.0",
|
|
54
|
+
"title": "Expanded state",
|
|
55
|
+
"kind": "semantic",
|
|
56
|
+
"requirement": "Expandable controls expose aria-expanded and identify the controlled region when needed.",
|
|
57
|
+
"proof": [
|
|
58
|
+
"unit",
|
|
59
|
+
"axe",
|
|
60
|
+
"e2e"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "semantics.focus.initial",
|
|
65
|
+
"version": "1.0.0",
|
|
66
|
+
"title": "Initial focus",
|
|
67
|
+
"kind": "semantic",
|
|
68
|
+
"requirement": "On activation, focus moves to the deterministic target appropriate to the pattern.",
|
|
69
|
+
"proof": [
|
|
70
|
+
"unit",
|
|
71
|
+
"e2e"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "semantics.focus.return",
|
|
76
|
+
"version": "1.0.0",
|
|
77
|
+
"title": "Focus return",
|
|
78
|
+
"kind": "semantic",
|
|
79
|
+
"requirement": "On dismissal, focus returns to the invoking control or a documented logical successor.",
|
|
80
|
+
"proof": [
|
|
81
|
+
"unit",
|
|
82
|
+
"e2e"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "semantics.focus.visible",
|
|
87
|
+
"version": "1.0.0",
|
|
88
|
+
"title": "Visible focus",
|
|
89
|
+
"kind": "semantic",
|
|
90
|
+
"requirement": "Keyboard focus is visible, sufficiently contrasted, and not fully obscured.",
|
|
91
|
+
"proof": [
|
|
92
|
+
"e2e",
|
|
93
|
+
"human"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "semantics.form-error",
|
|
98
|
+
"version": "1.0.0",
|
|
99
|
+
"title": "Form error",
|
|
100
|
+
"kind": "semantic",
|
|
101
|
+
"requirement": "Identify errors in text, associate them with fields, and provide correction guidance.",
|
|
102
|
+
"proof": [
|
|
103
|
+
"unit",
|
|
104
|
+
"axe",
|
|
105
|
+
"e2e",
|
|
106
|
+
"human"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "semantics.form-label",
|
|
111
|
+
"version": "1.0.0",
|
|
112
|
+
"title": "Form label",
|
|
113
|
+
"kind": "semantic",
|
|
114
|
+
"requirement": "Every form control has a persistent programmatically associated label.",
|
|
115
|
+
"proof": [
|
|
116
|
+
"unit",
|
|
117
|
+
"axe"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "semantics.form-required",
|
|
122
|
+
"version": "1.0.0",
|
|
123
|
+
"title": "Required field",
|
|
124
|
+
"kind": "semantic",
|
|
125
|
+
"requirement": "Requiredness is communicated visually and programmatically before submission.",
|
|
126
|
+
"proof": [
|
|
127
|
+
"unit",
|
|
128
|
+
"axe"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "semantics.headings",
|
|
133
|
+
"version": "1.0.0",
|
|
134
|
+
"title": "Heading structure",
|
|
135
|
+
"kind": "semantic",
|
|
136
|
+
"requirement": "Headings describe document structure without choosing levels for visual appearance.",
|
|
137
|
+
"proof": [
|
|
138
|
+
"axe",
|
|
139
|
+
"human"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "semantics.keyboard",
|
|
144
|
+
"version": "1.0.0",
|
|
145
|
+
"title": "Keyboard operation",
|
|
146
|
+
"kind": "semantic",
|
|
147
|
+
"requirement": "All functionality is operable with a keyboard without traps.",
|
|
148
|
+
"proof": [
|
|
149
|
+
"e2e",
|
|
150
|
+
"human"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "semantics.keyboard.escape",
|
|
155
|
+
"version": "1.0.0",
|
|
156
|
+
"title": "Escape dismissal",
|
|
157
|
+
"kind": "semantic",
|
|
158
|
+
"requirement": "Escape closes the topmost dismissible overlay without activating its trigger.",
|
|
159
|
+
"proof": [
|
|
160
|
+
"unit",
|
|
161
|
+
"e2e"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "semantics.landmarks",
|
|
166
|
+
"version": "1.0.0",
|
|
167
|
+
"title": "Landmarks",
|
|
168
|
+
"kind": "semantic",
|
|
169
|
+
"requirement": "Use native landmarks and name repeated landmarks only when a name is required to distinguish them.",
|
|
170
|
+
"proof": [
|
|
171
|
+
"axe",
|
|
172
|
+
"human"
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "semantics.live-status",
|
|
177
|
+
"version": "1.0.0",
|
|
178
|
+
"title": "Status announcements",
|
|
179
|
+
"kind": "semantic",
|
|
180
|
+
"requirement": "Important asynchronous status updates are exposed through a correctly scoped, non-duplicative live region.",
|
|
181
|
+
"proof": [
|
|
182
|
+
"unit",
|
|
183
|
+
"e2e",
|
|
184
|
+
"human"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "semantics.media-alternatives",
|
|
189
|
+
"version": "1.0.0",
|
|
190
|
+
"title": "Media alternatives",
|
|
191
|
+
"kind": "semantic",
|
|
192
|
+
"requirement": "Prerecorded media provides applicable captions, transcripts, and audio description.",
|
|
193
|
+
"proof": [
|
|
194
|
+
"human"
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "semantics.motion-control",
|
|
199
|
+
"version": "1.0.0",
|
|
200
|
+
"title": "Motion control",
|
|
201
|
+
"kind": "semantic",
|
|
202
|
+
"requirement": "Respect reduced-motion preferences and provide controls for moving or time-limited content where required.",
|
|
203
|
+
"proof": [
|
|
204
|
+
"e2e",
|
|
205
|
+
"human"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"id": "semantics.name-role-value",
|
|
210
|
+
"version": "1.0.0",
|
|
211
|
+
"title": "Name role value",
|
|
212
|
+
"kind": "semantic",
|
|
213
|
+
"requirement": "Expose the correct accessible name, role, value, and state through the platform accessibility API.",
|
|
214
|
+
"proof": [
|
|
215
|
+
"unit",
|
|
216
|
+
"axe",
|
|
217
|
+
"human"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"id": "semantics.native-dialog",
|
|
222
|
+
"version": "1.0.0",
|
|
223
|
+
"title": "Native dialog semantics",
|
|
224
|
+
"kind": "semantic",
|
|
225
|
+
"requirement": "Use dialog semantics with a programmatic name and modal state only when interaction is modal.",
|
|
226
|
+
"proof": [
|
|
227
|
+
"axe",
|
|
228
|
+
"e2e",
|
|
229
|
+
"human"
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"id": "semantics.native-elements",
|
|
234
|
+
"version": "1.0.0",
|
|
235
|
+
"title": "Native elements",
|
|
236
|
+
"kind": "semantic",
|
|
237
|
+
"requirement": "Use the native HTML element whose semantics and behavior match the control before adding ARIA.",
|
|
238
|
+
"proof": [
|
|
239
|
+
"axe",
|
|
240
|
+
"human"
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"id": "semantics.non-text-alternative",
|
|
245
|
+
"version": "1.0.0",
|
|
246
|
+
"title": "Non-text alternative",
|
|
247
|
+
"kind": "semantic",
|
|
248
|
+
"requirement": "Meaningful non-text content has an equivalent alternative and decorative content is ignored by assistive technology.",
|
|
249
|
+
"proof": [
|
|
250
|
+
"axe",
|
|
251
|
+
"human"
|
|
252
|
+
]
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"id": "semantics.roles-states-properties",
|
|
256
|
+
"version": "1.0.0",
|
|
257
|
+
"title": "Roles states and properties",
|
|
258
|
+
"kind": "semantic",
|
|
259
|
+
"requirement": "Apply only valid ARIA roles, states, and properties and keep exposed state synchronized with rendered behavior.",
|
|
260
|
+
"proof": [
|
|
261
|
+
"unit",
|
|
262
|
+
"axe",
|
|
263
|
+
"e2e"
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "semantics.selected-state",
|
|
268
|
+
"version": "1.0.0",
|
|
269
|
+
"title": "Selected state",
|
|
270
|
+
"kind": "semantic",
|
|
271
|
+
"requirement": "Selection widgets expose current selection with the state required by their role.",
|
|
272
|
+
"proof": [
|
|
273
|
+
"unit",
|
|
274
|
+
"e2e"
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"id": "semantics.target-size",
|
|
279
|
+
"version": "1.0.0",
|
|
280
|
+
"title": "Target size",
|
|
281
|
+
"kind": "semantic",
|
|
282
|
+
"requirement": "Pointer targets meet 24 by 24 CSS pixels or a WCAG 2.5.8 exception; 44 by 44 is an enhanced recommendation.",
|
|
283
|
+
"proof": [
|
|
284
|
+
"e2e",
|
|
285
|
+
"human"
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "@verndale/accessibility-standards@1.0.0",
|
|
3
|
+
"profiles": {
|
|
4
|
+
"conductor": {
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"outputs": {
|
|
7
|
+
"semanticsJson": "42611a6f9f7152ee8a9d2fe39dc59e80e7088835f7d4f11ee67d8876d33af3a0",
|
|
8
|
+
"patternContracts": "2944b4161b4072550916e0cad7ab0daca8704f92a0db5928980a3f3fbd4077a7",
|
|
9
|
+
"coverageManifest": "dd422be52e03889dd1d88d3aeb84de7d95173f9dc14c0a65117dec5c0747cca4",
|
|
10
|
+
"semanticsMarkdown": "5a4745e6e318cca8ff7300447bb04ef8963416c5f0a1c4d69037a59208e8555f",
|
|
11
|
+
"qualityMarkdown": "bb0facb2ea463432481b5ee1b6a96fe5aaf11880c5ae0668c554b43117cf8284",
|
|
12
|
+
"applicabilityMatrix": "cc7bd706a9f73a38ed7e101d9a6d58901d55cdc5a4551c52df53dcbb1fc30225",
|
|
13
|
+
"pattern:pattern.accordion": "ec254a0aaf01372c6d326df9e2747a6fdadeb505801921069e19f9a76f7224d9",
|
|
14
|
+
"pattern:pattern.alert": "743f7059d8c4c74344c1774962aa7ae111ac251e1688ae34b312d398e4ded68c",
|
|
15
|
+
"pattern:pattern.carousel": "0072641c7995fc74952061afc3b4e20ce8205db92304b9708cd3ad5314b750cb",
|
|
16
|
+
"pattern:pattern.combobox": "427024d4c9e5fde9d72c101d480bb24f6c4fa29d2beed087d4e8e3bb93a463d9",
|
|
17
|
+
"pattern:pattern.consequential-submission": "4f32ab0f6a86c168d28d5653c6d40444d0f9d0c1285661282c97f93aac86b7ab",
|
|
18
|
+
"pattern:pattern.dialog": "3f7b778be6cc0cc368d6a9feae73876f5c1f0739c411c1978eac186c6a8478bf",
|
|
19
|
+
"pattern:pattern.disclosure": "12ea9bc2b9d037314c4477aa40f8d8ad176482238162da1a04864d16fd2d19f1",
|
|
20
|
+
"pattern:pattern.dynamic-status": "3f8ac01ba98b1a056b82d03e39e1cc735cf4a89ba14ef7cc00a8886950e9a771",
|
|
21
|
+
"pattern:pattern.field": "e3eafe382fc919420d9f75ec37a3b99ab9be4d1ed8c154efe2e1a5499cc28d19",
|
|
22
|
+
"pattern:pattern.image": "0faf65bbbaaee23387dfe7f1de973e1f4da24fba50248e19fdbfac6bdefe904b",
|
|
23
|
+
"pattern:pattern.listbox": "6a7f3a99752a2c2684671d4db746b5bccf90d45b37bb2de2f4488829e339743b",
|
|
24
|
+
"pattern:pattern.media-player": "7eb5372c93adbb649845e79a9a7437472dc3b2bf75f3ddc4da725136faa907ea",
|
|
25
|
+
"pattern:pattern.menu-button": "9615bcb307f5aa35dbec8161e5fcdedb52c3125df8fb967e6360c04d5a15231b",
|
|
26
|
+
"pattern:pattern.pagination": "bf3c9ebae1ec36d91d7d9e9cb6c2ccc8d95daf07bc4a323cf2bbaef8823986c7",
|
|
27
|
+
"pattern:pattern.popover": "766ea35bff5fd3ca8a89c62cafc25c319a50bfdacc9cdcd39950b58ef010f756",
|
|
28
|
+
"pattern:pattern.progress": "3fd8e767744cb778aac8a86e0e320144fc7ddd17f46b5ec3bf607ec573f96042",
|
|
29
|
+
"pattern:pattern.tabs": "5fb87a74409e85044a76a236af1019b793d9ee5eafd8d34d9628b5bd7e864bf6",
|
|
30
|
+
"pattern:pattern.tooltip": "b56513f93ee194eb38536cb82712817d98c55a6268dd5bd4ae962336e5e66b64",
|
|
31
|
+
"pattern:pattern.validation": "ba8fd3aced80af3e5e3a28d062f8f8e58f5586c00ce2e2b82b3aa155faf6f2ff"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"ai-orchestration": {
|
|
35
|
+
"version": "1.0.0",
|
|
36
|
+
"outputs": {
|
|
37
|
+
"semanticsJson": "42611a6f9f7152ee8a9d2fe39dc59e80e7088835f7d4f11ee67d8876d33af3a0",
|
|
38
|
+
"patternContracts": "2944b4161b4072550916e0cad7ab0daca8704f92a0db5928980a3f3fbd4077a7",
|
|
39
|
+
"coverageManifest": "dd422be52e03889dd1d88d3aeb84de7d95173f9dc14c0a65117dec5c0747cca4",
|
|
40
|
+
"semantic:semantics.accessible-name": "0c39e1d1fafabdf30d14ed94cd4e06161fcc1d0ac07ad3554c5c795d4bbac762",
|
|
41
|
+
"semantic:semantics.alert": "d079bb0ea1612ba3828eebc344a9c0d49655166212e98453c0eb7b8059d01f8c",
|
|
42
|
+
"semantic:semantics.consequential-action": "79d1ec9c2119cee11be28ffde458217e8666c7804c392a61bbc220fc2121cef5",
|
|
43
|
+
"semantic:semantics.data-table": "16ed84e97d8d6b1bc9704f874927225c3a3992045ac8aaf3b9704d4fb45fe788",
|
|
44
|
+
"semantic:semantics.expanded-state": "d2a3e60714bd36234a874ad32c6334152ea924983fb16a847689f71353725717",
|
|
45
|
+
"semantic:semantics.focus.initial": "a8b7b3a59da2eac1516f7315b3e6876fa4462b650065f85ba38db942edc7c296",
|
|
46
|
+
"semantic:semantics.focus.return": "ce71ad35ce7d65b1bec1a010baefae49e058e6b310e4204e1eaf3df4255474e2",
|
|
47
|
+
"semantic:semantics.focus.visible": "0e8a9d6d8820756b3693521d98057fe07f77c0233320f9e06d72b5ce21f0fbaf",
|
|
48
|
+
"semantic:semantics.form-error": "ffd1b4d19d696cb5f93faca86641834742f57d1fe12b8c7e3741b2ee505196a0",
|
|
49
|
+
"semantic:semantics.form-label": "16e5410b9911dcd4ba205c7082402a0115881650b4fcb31a4b95bb2ee98b9938",
|
|
50
|
+
"semantic:semantics.form-required": "3bdb58565de8b36f313f2610c8bae700f01ec3c2e9084ab0c6e080eacc6a17f1",
|
|
51
|
+
"semantic:semantics.headings": "bda060974cb9b2c60a2cc725564eff6998592647bc0149374501caa456bd39bb",
|
|
52
|
+
"semantic:semantics.keyboard": "702e79e65248ee4147194ab5325de90d6197bd54c3c832d2d36977d5c1b37223",
|
|
53
|
+
"semantic:semantics.keyboard.escape": "6318353a5ed5dbd179199b50590c48db9408f92b9546fa4e3f28656964a8bb4f",
|
|
54
|
+
"semantic:semantics.landmarks": "7f7ef886b9b66286870b29bfef92665488a8016a161ae8c1b306f9fb0700047c",
|
|
55
|
+
"semantic:semantics.live-status": "f174ca78b509e9dfe070610ca174f2f7893f9098d97beb96fa34673c1a026b80",
|
|
56
|
+
"semantic:semantics.media-alternatives": "3b396a45778e19073cf25651a79b1be2eac84a9b67c822d961547e5121439b67",
|
|
57
|
+
"semantic:semantics.motion-control": "dc944ee6ae6d948a543a33dc86f62277b337211544c1981332a67867ca2180eb",
|
|
58
|
+
"semantic:semantics.name-role-value": "1018827e63d16427e6a42b40908737d433235999a96b73d8842d0937e6855aac",
|
|
59
|
+
"semantic:semantics.native-dialog": "3b8326bc9e7c0e10a8382c1baf8d0ca0b3afb81a6c9f5521339bb9615bc9c9c4",
|
|
60
|
+
"semantic:semantics.native-elements": "852c7d44d101e84fb05ccd6e9d2eb7107f7f083712382d8b960ac3fa2434090d",
|
|
61
|
+
"semantic:semantics.non-text-alternative": "4e45ef4c751502936b4f00453f048717e9ad551fefac71c798984aee89c7ecb3",
|
|
62
|
+
"semantic:semantics.roles-states-properties": "a970228c27d39fce6395d786db60efd5081bf5373530b7b6bfec46c2ae47c394",
|
|
63
|
+
"semantic:semantics.selected-state": "044b6902dd98327069301b066d5cc88e71767a698f3f9b32f56f6016d4d72935",
|
|
64
|
+
"semantic:semantics.target-size": "4cd61e2e7b3ae90bc63271e7959c4af983049b189019a3681c59610176d1adc5",
|
|
65
|
+
"pattern:pattern.accordion": "d551bf7a2258cca79f306d9edf6a24e2955d2d025f8314dfedc3e5389db5ae44",
|
|
66
|
+
"pattern:pattern.alert": "a16c54025010343e93bf4fc0dd18286f53e4be9cd07d61be70d6b06a18df0ffc",
|
|
67
|
+
"pattern:pattern.carousel": "259d27e48064979080ebe333b673300425cf884ecce8b02eca6c2c2a56a1127c",
|
|
68
|
+
"pattern:pattern.combobox": "df92de73df4847ec283f8776dc2fdd85fd15bd0b3c8d05df416e40cede9f26af",
|
|
69
|
+
"pattern:pattern.consequential-submission": "21e49bb0e290b7dea77553df6d29bcfdb34950d07ba1ef4e7cf1a8b79fe03098",
|
|
70
|
+
"pattern:pattern.dialog": "aaca16e959dcaf603ee2abbb6b18d4df9594821ce8a96f821f4e5cf0f7db2cac",
|
|
71
|
+
"pattern:pattern.disclosure": "0477a611b9afbfca8e78d55c78812f5014375022a9c272886fbff2c026699804",
|
|
72
|
+
"pattern:pattern.dynamic-status": "cc009834aca59e79c940339b5a2eefb4bc8d501f45c7d54c7441d92740ae0d62",
|
|
73
|
+
"pattern:pattern.field": "18796141e47668682ffb430d2bd39b043fbb98bce80958e5c4761f066b058701",
|
|
74
|
+
"pattern:pattern.image": "eb3d93a2d889f0603002cf4d8f79e467be08687d6e4bc2b7a9229452c74eb3f2",
|
|
75
|
+
"pattern:pattern.listbox": "764b873185a1c4b3d16c1940057b440a8f5e47a8329312c43ec60470a009d759",
|
|
76
|
+
"pattern:pattern.media-player": "813a828908f79eb0703a5a150bcea29ee41cceca93e1f66a106abc9a78fb1eb9",
|
|
77
|
+
"pattern:pattern.menu-button": "a4fb849f4c73c6b0f57f5c0fb2f7384fc4860514cf599313142196b6a57488f8",
|
|
78
|
+
"pattern:pattern.pagination": "2e3f8e6cc49ed52e5c60bde08acbae8da2e0964a6c71b4d5914e165f7e6dfdd9",
|
|
79
|
+
"pattern:pattern.popover": "5fcace9fa64700c72296dad857ee2e3025f774b915c4a6694e99772b296ade2f",
|
|
80
|
+
"pattern:pattern.progress": "7a417350fcabfb2ec9bed8b51c3e7440412d2922ac1c7e005c61dd6ebcaf2fef",
|
|
81
|
+
"pattern:pattern.tabs": "d944fea00a8b169de2b68772f7a61085c8adbbbe6976689b3b984fc74d2d9919",
|
|
82
|
+
"pattern:pattern.tooltip": "94986056a2ad50dc3c7128c8667f8252b4f5a2b4452ef370b902f85a25bce7bd",
|
|
83
|
+
"pattern:pattern.validation": "f5445a62766a133dbdc2ec505f9a8d8f35f23deb3ba4c41b76d80c87875966fc",
|
|
84
|
+
"implementation": "9dc92bd2c2f625a4e1c15cdf3b194e344bf3e1a4b0f166a47a0241a534d6bfb0",
|
|
85
|
+
"testing": "238ee503005595fb0beec9e512b04ab1edba337c399d83cd5e3f391577d188be",
|
|
86
|
+
"review": "a0e3ec20d8ade44bef4fc2fa9fcb6925617c105d0b674a3af284044f1e2fe2a8"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Requirements to implementation
|
|
2
|
+
|
|
3
|
+
- pattern.accordion → semantics.native-elements, semantics.accessible-name, semantics.expanded-state, semantics.keyboard, semantics.focus.visible → accordion-control, accordion-panel
|
|
4
|
+
- pattern.alert → semantics.alert → alert-announcement
|
|
5
|
+
- pattern.carousel → semantics.accessible-name, semantics.keyboard, semantics.focus.visible, semantics.motion-control, semantics.live-status → carousel-controls, carousel-rotation
|
|
6
|
+
- pattern.combobox → semantics.accessible-name, semantics.expanded-state, semantics.selected-state, semantics.keyboard, semantics.live-status → combobox-state-machine
|
|
7
|
+
- pattern.consequential-submission → semantics.consequential-action, semantics.form-error, semantics.keyboard → submission-safeguard
|
|
8
|
+
- pattern.dialog → semantics.native-dialog, semantics.accessible-name, semantics.keyboard.escape, semantics.focus.initial, semantics.focus.return → dialog-semantics, dialog-focus-lifecycle
|
|
9
|
+
- pattern.disclosure → semantics.native-elements, semantics.accessible-name, semantics.expanded-state, semantics.keyboard → disclosure-control
|
|
10
|
+
- pattern.dynamic-status → semantics.live-status → status-live-region
|
|
11
|
+
- pattern.field → semantics.form-label, semantics.form-required, semantics.focus.visible → field-associations
|
|
12
|
+
- pattern.image → semantics.non-text-alternative → image-alternative
|
|
13
|
+
- pattern.listbox → semantics.accessible-name, semantics.selected-state, semantics.keyboard, semantics.focus.visible → listbox-keyboard-model
|
|
14
|
+
- pattern.media-player → semantics.media-alternatives, semantics.accessible-name, semantics.keyboard, semantics.focus.visible, semantics.motion-control → media-alternatives, media-controls
|
|
15
|
+
- pattern.menu-button → semantics.accessible-name, semantics.expanded-state, semantics.keyboard, semantics.focus.initial, semantics.focus.return → menu-button-keyboard
|
|
16
|
+
- pattern.pagination → semantics.native-elements, semantics.accessible-name, semantics.landmarks, semantics.focus.visible → pagination-current-page
|
|
17
|
+
- pattern.popover → semantics.native-elements, semantics.accessible-name, semantics.keyboard.escape, semantics.focus.return → popover-dismissal
|
|
18
|
+
- pattern.progress → semantics.name-role-value, semantics.live-status → progress-value
|
|
19
|
+
- pattern.tabs → semantics.accessible-name, semantics.selected-state, semantics.keyboard, semantics.focus.visible → tabs-keyboard-model
|
|
20
|
+
- pattern.tooltip → semantics.accessible-name, semantics.keyboard.escape → tooltip-description
|
|
21
|
+
- pattern.validation → semantics.form-error, semantics.live-status, semantics.focus.initial → validation-associations, error-summary
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { digest } from './digest.mjs';
|
|
2
|
+
|
|
3
|
+
export function buildProjectionManifest({ data, profile, config, routes, artifacts }) {
|
|
4
|
+
const records = [...data.foundations, ...data.semantics, ...data.patterns, ...data.policies];
|
|
5
|
+
return {
|
|
6
|
+
_generated: 'GENERATED by @verndale/accessibility-standards; DO NOT EDIT',
|
|
7
|
+
package: `@verndale/accessibility-standards@${data.package.version}`,
|
|
8
|
+
profile,
|
|
9
|
+
profile_version: '1.0.0',
|
|
10
|
+
schema_version: 1,
|
|
11
|
+
digests: {
|
|
12
|
+
source: digest({ records, matrix: data.matrix, evidence: data.evidence }),
|
|
13
|
+
config: digest(config),
|
|
14
|
+
routes: digest(routes)
|
|
15
|
+
},
|
|
16
|
+
projected_ids: [...new Set([...data.semantics, ...data.patterns, ...data.matrix.rows].map(({ id }) => id))].sort(),
|
|
17
|
+
lane_coverage: Object.fromEntries(['unit', 'axe', 'e2e', 'human'].map((lane) => [lane, records.filter((item) => item.proof?.includes(lane) || item.evidence_routes?.includes(lane)).map(({ id }) => id).sort()])),
|
|
18
|
+
output_digests: Object.fromEntries([...artifacts].map(([key, content]) => [key, digest(content)]).sort(([a], [b]) => a.localeCompare(b)))
|
|
19
|
+
};
|
|
20
|
+
}
|
package/lib/build.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { loadStandards, packageRoot } from './load.mjs';
|
|
4
|
+
import { buildArtifacts, defaultPath } from './render.mjs';
|
|
5
|
+
import { digest } from './digest.mjs';
|
|
6
|
+
import { validateStandards } from './validate.mjs';
|
|
7
|
+
|
|
8
|
+
export async function buildDistribution(root = packageRoot) {
|
|
9
|
+
const data = await loadStandards(root);
|
|
10
|
+
validateStandards(data);
|
|
11
|
+
const dist = join(root, 'dist');
|
|
12
|
+
await rm(dist, { recursive: true, force: true });
|
|
13
|
+
const profileManifests = {};
|
|
14
|
+
for (const profile of ['conductor', 'ai-orchestration']) {
|
|
15
|
+
const artifacts = buildArtifacts(data, profile);
|
|
16
|
+
const outputDigests = {};
|
|
17
|
+
for (const [key, content] of artifacts) {
|
|
18
|
+
const path = join(dist, 'profiles', profile, defaultPath(key, profile));
|
|
19
|
+
await mkdir(dirname(path), { recursive: true });
|
|
20
|
+
await writeFile(path, content);
|
|
21
|
+
outputDigests[key] = digest(content);
|
|
22
|
+
}
|
|
23
|
+
profileManifests[profile] = { version: '1.0.0', outputs: outputDigests };
|
|
24
|
+
}
|
|
25
|
+
const records = [...data.foundations, ...data.semantics, ...data.patterns, ...data.policies].sort((a, b) => a.id.localeCompare(b.id));
|
|
26
|
+
const manifest = { package: `@verndale/accessibility-standards@${data.package.version}`, schema_version: 1, records: records.map((record) => ({ id: record.id, digest: digest(record) })) };
|
|
27
|
+
await writeFile(join(dist, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
28
|
+
await writeFile(join(dist, 'projection-manifest.json'), `${JSON.stringify({ package: manifest.package, profiles: profileManifests }, null, 2)}\n`);
|
|
29
|
+
await writeFile(join(dist, 'requirements-to-implementation.md'), `# Requirements to implementation\n\n${data.patterns.sort((a, b) => a.id.localeCompare(b.id)).map((pattern) => `- ${pattern.id} → ${pattern.requires.join(', ')} → ${pattern.implementation_outcomes.join(', ')}`).join('\n')}\n`);
|
|
30
|
+
return { records: records.length, profiles: 2 };
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const OPS = new Set(['all', 'any', 'not', 'equals', 'contains', 'exists']);
|
|
2
|
+
|
|
3
|
+
export function validateExpression(expression, facts) {
|
|
4
|
+
const keys = Object.keys(expression ?? {});
|
|
5
|
+
if (keys.length !== 1 || !OPS.has(keys[0])) throw new Error(`Unsupported applicability expression: ${keys.join(',') || '<empty>'}`);
|
|
6
|
+
const [op] = keys;
|
|
7
|
+
const operand = expression[op];
|
|
8
|
+
if (op === 'all' || op === 'any') {
|
|
9
|
+
if (!Array.isArray(operand) || !operand.length) throw new Error(`${op} requires a non-empty array`);
|
|
10
|
+
operand.forEach((item) => validateExpression(item, facts));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (op === 'not') return validateExpression(operand, facts);
|
|
14
|
+
const fact = operand?.fact;
|
|
15
|
+
if (!fact || !facts[fact]) throw new Error(`Unknown applicability fact: ${fact ?? '<missing>'}`);
|
|
16
|
+
if (op !== 'exists' && !Object.hasOwn(operand, 'value')) throw new Error(`${op} requires value`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function evaluate(expression, values) {
|
|
20
|
+
const [op, operand] = Object.entries(expression)[0];
|
|
21
|
+
if (op === 'all') return operand.every((item) => evaluate(item, values));
|
|
22
|
+
if (op === 'any') return operand.some((item) => evaluate(item, values));
|
|
23
|
+
if (op === 'not') return !evaluate(operand, values);
|
|
24
|
+
const observed = values[operand.fact];
|
|
25
|
+
if (op === 'exists') return observed !== undefined && observed !== null;
|
|
26
|
+
if (op === 'equals') return observed === operand.value;
|
|
27
|
+
if (op === 'contains') {
|
|
28
|
+
if (Array.isArray(operand.value)) return operand.value.includes(observed);
|
|
29
|
+
if (Array.isArray(observed)) return observed.includes(operand.value);
|
|
30
|
+
return typeof observed === 'string' && observed.includes(String(operand.value));
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Unsupported operator: ${op}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function compileApplicability(matrix, facts, values = {}) {
|
|
36
|
+
return matrix.rows.map((row, index) => {
|
|
37
|
+
validateExpression(row.when, facts.facts);
|
|
38
|
+
const observed = referencedFacts(row.when).every((fact) => Object.hasOwn(values, fact));
|
|
39
|
+
return {
|
|
40
|
+
...row,
|
|
41
|
+
order: index,
|
|
42
|
+
trigger_state: observed ? (evaluate(row.when, values) ? 'applicable' : 'not_applicable') : 'unobserved',
|
|
43
|
+
resolution_state: observed ? (evaluate(row.when, values) ? 'needs_confirmation' : 'skipped') : 'needs_input'
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function referencedFacts(expression) {
|
|
49
|
+
const [op, operand] = Object.entries(expression)[0];
|
|
50
|
+
if (op === 'all' || op === 'any') return [...new Set(operand.flatMap(referencedFacts))];
|
|
51
|
+
if (op === 'not') return referencedFacts(operand);
|
|
52
|
+
return [operand.fact];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const triggerStates = ['unobserved', 'not_applicable', 'candidate', 'applicable'];
|
|
56
|
+
export const resolutionStates = ['skipped', 'needs_confirmation', 'needs_input', 'resolved', 'deferred', 'conflict'];
|
package/lib/digest.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function stable(value) {
|
|
4
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(',')}]`;
|
|
5
|
+
if (value && typeof value === 'object') return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stable(value[key])}`).join(',')}}`;
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function digest(value) {
|
|
10
|
+
return createHash('sha256').update(typeof value === 'string' ? value : stable(value)).digest('hex');
|
|
11
|
+
}
|
package/lib/index.mjs
ADDED