atdd 0.1.0__py3-none-any.whl

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 (183) hide show
  1. atdd/__init__.py +0 -0
  2. atdd/cli.py +404 -0
  3. atdd/coach/__init__.py +0 -0
  4. atdd/coach/commands/__init__.py +0 -0
  5. atdd/coach/commands/add_persistence_metadata.py +215 -0
  6. atdd/coach/commands/analyze_migrations.py +188 -0
  7. atdd/coach/commands/consumers.py +720 -0
  8. atdd/coach/commands/infer_governance_status.py +149 -0
  9. atdd/coach/commands/initializer.py +177 -0
  10. atdd/coach/commands/interface.py +1078 -0
  11. atdd/coach/commands/inventory.py +565 -0
  12. atdd/coach/commands/migration.py +240 -0
  13. atdd/coach/commands/registry.py +1560 -0
  14. atdd/coach/commands/session.py +430 -0
  15. atdd/coach/commands/sync.py +405 -0
  16. atdd/coach/commands/test_interface.py +399 -0
  17. atdd/coach/commands/test_runner.py +141 -0
  18. atdd/coach/commands/tests/__init__.py +1 -0
  19. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  20. atdd/coach/commands/traceability.py +4264 -0
  21. atdd/coach/conventions/session.convention.yaml +754 -0
  22. atdd/coach/overlays/__init__.py +2 -0
  23. atdd/coach/overlays/claude.md +2 -0
  24. atdd/coach/schemas/config.schema.json +34 -0
  25. atdd/coach/schemas/manifest.schema.json +101 -0
  26. atdd/coach/templates/ATDD.md +282 -0
  27. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  28. atdd/coach/utils/__init__.py +0 -0
  29. atdd/coach/utils/graph/__init__.py +0 -0
  30. atdd/coach/utils/graph/urn.py +875 -0
  31. atdd/coach/validators/__init__.py +0 -0
  32. atdd/coach/validators/shared_fixtures.py +365 -0
  33. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  34. atdd/coach/validators/test_registry.py +575 -0
  35. atdd/coach/validators/test_session_validation.py +1183 -0
  36. atdd/coach/validators/test_traceability.py +448 -0
  37. atdd/coach/validators/test_update_feature_paths.py +108 -0
  38. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  39. atdd/coder/__init__.py +1 -0
  40. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  41. atdd/coder/conventions/backend.convention.yaml +460 -0
  42. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  43. atdd/coder/conventions/commons.convention.yaml +460 -0
  44. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  45. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  46. atdd/coder/conventions/design.convention.yaml +327 -0
  47. atdd/coder/conventions/design.recipe.yaml +273 -0
  48. atdd/coder/conventions/dto.convention.yaml +660 -0
  49. atdd/coder/conventions/frontend.convention.yaml +542 -0
  50. atdd/coder/conventions/green.convention.yaml +1012 -0
  51. atdd/coder/conventions/presentation.convention.yaml +587 -0
  52. atdd/coder/conventions/refactor.convention.yaml +535 -0
  53. atdd/coder/conventions/technology.convention.yaml +206 -0
  54. atdd/coder/conventions/tests/__init__.py +0 -0
  55. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  56. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  57. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  58. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  59. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  60. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  61. atdd/coder/conventions/train.convention.yaml +325 -0
  62. atdd/coder/conventions/verification.protocol.yaml +53 -0
  63. atdd/coder/schemas/design_system.schema.json +361 -0
  64. atdd/coder/validators/__init__.py +0 -0
  65. atdd/coder/validators/test_commons_structure.py +485 -0
  66. atdd/coder/validators/test_complexity.py +416 -0
  67. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  68. atdd/coder/validators/test_design_system_compliance.py +413 -0
  69. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  70. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  71. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  72. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  73. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  74. atdd/coder/validators/test_import_boundaries.py +396 -0
  75. atdd/coder/validators/test_init_file_urns.py +593 -0
  76. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  77. atdd/coder/validators/test_presentation_convention.py +260 -0
  78. atdd/coder/validators/test_python_architecture.py +674 -0
  79. atdd/coder/validators/test_quality_metrics.py +420 -0
  80. atdd/coder/validators/test_station_master_pattern.py +244 -0
  81. atdd/coder/validators/test_train_infrastructure.py +454 -0
  82. atdd/coder/validators/test_train_urns.py +293 -0
  83. atdd/coder/validators/test_typescript_architecture.py +616 -0
  84. atdd/coder/validators/test_usecase_structure.py +421 -0
  85. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  86. atdd/conftest.py +126 -0
  87. atdd/planner/__init__.py +1 -0
  88. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  89. atdd/planner/conventions/appendix.convention.yaml +187 -0
  90. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  91. atdd/planner/conventions/component.convention.yaml +670 -0
  92. atdd/planner/conventions/criteria.convention.yaml +141 -0
  93. atdd/planner/conventions/feature.convention.yaml +371 -0
  94. atdd/planner/conventions/interface.convention.yaml +382 -0
  95. atdd/planner/conventions/steps.convention.yaml +141 -0
  96. atdd/planner/conventions/train.convention.yaml +552 -0
  97. atdd/planner/conventions/wagon.convention.yaml +275 -0
  98. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  99. atdd/planner/schemas/acceptance.schema.json +336 -0
  100. atdd/planner/schemas/appendix.schema.json +78 -0
  101. atdd/planner/schemas/component.schema.json +114 -0
  102. atdd/planner/schemas/feature.schema.json +197 -0
  103. atdd/planner/schemas/train.schema.json +192 -0
  104. atdd/planner/schemas/wagon.schema.json +281 -0
  105. atdd/planner/schemas/wmbt.schema.json +59 -0
  106. atdd/planner/validators/__init__.py +0 -0
  107. atdd/planner/validators/conftest.py +5 -0
  108. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  109. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  110. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  111. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  112. atdd/planner/validators/test_plan_wagons.py +174 -0
  113. atdd/planner/validators/test_train_validation.py +514 -0
  114. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  115. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  116. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  117. atdd/tester/__init__.py +1 -0
  118. atdd/tester/conventions/artifact.convention.yaml +257 -0
  119. atdd/tester/conventions/contract.convention.yaml +1009 -0
  120. atdd/tester/conventions/filename.convention.yaml +555 -0
  121. atdd/tester/conventions/migration.convention.yaml +509 -0
  122. atdd/tester/conventions/red.convention.yaml +797 -0
  123. atdd/tester/conventions/routing.convention.yaml +51 -0
  124. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  125. atdd/tester/schemas/a11y.tmpl.json +17 -0
  126. atdd/tester/schemas/artifact.schema.json +189 -0
  127. atdd/tester/schemas/contract.schema.json +591 -0
  128. atdd/tester/schemas/contract.tmpl.json +95 -0
  129. atdd/tester/schemas/db.tmpl.json +20 -0
  130. atdd/tester/schemas/e2e.tmpl.json +17 -0
  131. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  132. atdd/tester/schemas/event.tmpl.json +17 -0
  133. atdd/tester/schemas/http.tmpl.json +19 -0
  134. atdd/tester/schemas/job.tmpl.json +18 -0
  135. atdd/tester/schemas/load.tmpl.json +21 -0
  136. atdd/tester/schemas/metric.tmpl.json +19 -0
  137. atdd/tester/schemas/pack.schema.json +139 -0
  138. atdd/tester/schemas/realtime.tmpl.json +20 -0
  139. atdd/tester/schemas/rls.tmpl.json +18 -0
  140. atdd/tester/schemas/script.tmpl.json +16 -0
  141. atdd/tester/schemas/sec.tmpl.json +18 -0
  142. atdd/tester/schemas/storage.tmpl.json +18 -0
  143. atdd/tester/schemas/telemetry.schema.json +128 -0
  144. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  145. atdd/tester/schemas/test_filename.schema.json +194 -0
  146. atdd/tester/schemas/test_intent.schema.json +179 -0
  147. atdd/tester/schemas/unit.tmpl.json +18 -0
  148. atdd/tester/schemas/visual.tmpl.json +18 -0
  149. atdd/tester/schemas/ws.tmpl.json +17 -0
  150. atdd/tester/utils/__init__.py +0 -0
  151. atdd/tester/utils/filename.py +300 -0
  152. atdd/tester/validators/__init__.py +0 -0
  153. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  154. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  155. atdd/tester/validators/conftest.py +5 -0
  156. atdd/tester/validators/coverage_gap_report.py +321 -0
  157. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  158. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  159. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  160. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  161. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  162. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  163. atdd/tester/validators/test_contracts_structure.py +200 -0
  164. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  165. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  166. atdd/tester/validators/test_fixture_validity.py +372 -0
  167. atdd/tester/validators/test_isolation.py +487 -0
  168. atdd/tester/validators/test_migration_coverage.py +204 -0
  169. atdd/tester/validators/test_migration_criteria.py +276 -0
  170. atdd/tester/validators/test_migration_generation.py +116 -0
  171. atdd/tester/validators/test_python_test_naming.py +410 -0
  172. atdd/tester/validators/test_red_layer_validation.py +95 -0
  173. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  174. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  175. atdd/tester/validators/test_telemetry_structure.py +634 -0
  176. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  177. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  178. atdd-0.1.0.dist-info/METADATA +191 -0
  179. atdd-0.1.0.dist-info/RECORD +183 -0
  180. atdd-0.1.0.dist-info/WHEEL +5 -0
  181. atdd-0.1.0.dist-info/entry_points.txt +2 -0
  182. atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
  183. atdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,307 @@
1
+ """
2
+ RED Tests for Artifact Naming Convention with Optional Category Facet
3
+
4
+ SPEC: SPEC-TESTER-CONV-0059 through SPEC-TESTER-CONV-0067
5
+ Feature: Artifact naming supports optional category facet
6
+ Background:
7
+ - Colon separator denotes hierarchy (domain:resource)
8
+ - Dot separator denotes facet (resource.category)
9
+ - Category is optional third segment for nested resources
10
+ - Examples use ux:foundations instead of legacy design:tokens
11
+ """
12
+
13
+ import pytest
14
+ import yaml
15
+ from pathlib import Path
16
+
17
+
18
+ @pytest.fixture
19
+ def artifact_convention():
20
+ """Load artifact.convention.yaml"""
21
+ # File is at atdd/tester/audits/test_*.py, convention is at atdd/tester/conventions/
22
+ convention_path = Path(__file__).parent.parent / "conventions" / "artifact.convention.yaml"
23
+ assert convention_path.exists(), f"Convention file not found: {convention_path}"
24
+
25
+ with open(convention_path) as f:
26
+ return yaml.safe_load(f)
27
+
28
+
29
+ # SPEC-TESTER-CONV-0059
30
+ def test_logical_pattern_has_category(artifact_convention):
31
+ """Logical naming pattern supports optional category"""
32
+ naming = artifact_convention.get("naming", {})
33
+ logical_pattern = naming.get("logical_pattern")
34
+
35
+ assert logical_pattern == "{domain}:{resource}[.{category}]", \
36
+ f"Expected logical_pattern to be '{{domain}}:{{resource}}[.{{category}}]', got: {logical_pattern}"
37
+
38
+ # Verify rationale explains hierarchy vs facet
39
+ rationale = naming.get("rationale", "")
40
+ assert "colon" in rationale.lower() or "hierarchy" in rationale.lower(), \
41
+ "Rationale should explain colon separator for hierarchy"
42
+
43
+
44
+ # SPEC-TESTER-CONV-0060
45
+ def test_physical_pattern_has_category_directory(artifact_convention):
46
+ """Physical path pattern supports nested category directory"""
47
+ naming = artifact_convention.get("naming", {})
48
+ physical_pattern = naming.get("physical_pattern")
49
+
50
+ assert physical_pattern == "contracts/{domain}/{resource}[/{category}].json", \
51
+ f"Expected physical_pattern with optional category directory, got: {physical_pattern}"
52
+
53
+ # Check examples demonstrate both patterns
54
+ examples = naming.get("examples", [])
55
+ assert len(examples) >= 2, "Should have examples for both base and category resources"
56
+
57
+
58
+ # SPEC-TESTER-CONV-0061
59
+ def test_examples_use_ux_foundations(artifact_convention):
60
+ """Examples use ux:foundations instead of design:tokens"""
61
+ naming = artifact_convention.get("naming", {})
62
+ examples = naming.get("examples", [])
63
+
64
+ # Check for ux:foundations base example
65
+ ux_base = None
66
+ ux_category = None
67
+
68
+ for example in examples:
69
+ if example.get("logical") == "ux:foundations":
70
+ ux_base = example
71
+ if example.get("logical") == "ux:foundations.colors":
72
+ ux_category = example
73
+
74
+ assert ux_base is not None, "Missing ux:foundations base example"
75
+ assert ux_base.get("physical") == "contracts/commons/ux/foundations.json", \
76
+ f"ux:foundations should map to contracts/commons/ux/foundations.json"
77
+
78
+ assert ux_category is not None, "Missing ux:foundations.colors category example"
79
+ assert ux_category.get("physical") == "contracts/commons/ux/foundations/colors.json", \
80
+ f"ux:foundations.colors should map to contracts/commons/ux/foundations/colors.json"
81
+
82
+
83
+ # SPEC-TESTER-CONV-0061 (part 2)
84
+ def test_no_design_tokens_examples(artifact_convention):
85
+ """No design:tokens examples remain"""
86
+ naming = artifact_convention.get("naming", {})
87
+ examples = naming.get("examples", [])
88
+
89
+ for example in examples:
90
+ logical = example.get("logical", "")
91
+ assert not logical.startswith("design:"), \
92
+ f"Found legacy design:tokens example: {logical}"
93
+
94
+
95
+ # SPEC-TESTER-CONV-0062
96
+ def test_api_pattern_has_category_segment(artifact_convention):
97
+ """API mapping supports optional category path segment"""
98
+ api_mapping = artifact_convention.get("api_mapping", {})
99
+ pattern = api_mapping.get("pattern")
100
+
101
+ assert pattern == "/{domain}s/{id}/{resource}[/{category}]", \
102
+ f"Expected API pattern with optional category segment, got: {pattern}"
103
+
104
+
105
+ # SPEC-TESTER-CONV-0062 (part 2)
106
+ def test_api_examples_include_ux_foundations(artifact_convention):
107
+ """API examples include ux:foundations"""
108
+ api_mapping = artifact_convention.get("api_mapping", {})
109
+ examples = api_mapping.get("examples", [])
110
+
111
+ # Check for ux:foundations base example
112
+ ux_base = None
113
+ ux_category = None
114
+
115
+ for example in examples:
116
+ if example.get("artifact") == "ux:foundations":
117
+ ux_base = example
118
+ if example.get("artifact") == "ux:foundations.colors":
119
+ ux_category = example
120
+
121
+ assert ux_base is not None, "Missing ux:foundations API example"
122
+ assert ux_base.get("endpoint") == "GET /uxs/{id}/foundations", \
123
+ f"Expected 'GET /uxs/{{id}}/foundations', got: {ux_base.get('endpoint')}"
124
+
125
+ assert ux_category is not None, "Missing ux:foundations.colors API example"
126
+ assert ux_category.get("endpoint") == "GET /uxs/{id}/foundations/colors", \
127
+ f"Expected 'GET /uxs/{{id}}/foundations/colors', got: {ux_category.get('endpoint')}"
128
+
129
+
130
+ # SPEC-TESTER-CONV-0063
131
+ def test_urn_pattern_preserves_category_facet(artifact_convention):
132
+ """URN pattern preserves category as dot facet"""
133
+ # Check if artifact_urns section exists
134
+ artifact_urns = artifact_convention.get("artifact_urns", {})
135
+ urn_pattern = artifact_urns.get("urn_pattern", {})
136
+
137
+ format_str = urn_pattern.get("format")
138
+ assert format_str == "contract:{domain}:{resource}[.{category}]", \
139
+ f"Expected URN format 'contract:{{domain}}:{{resource}}[.{{category}}]', got: {format_str}"
140
+
141
+ # Check conversion rule
142
+ conversion_rule = urn_pattern.get("conversion_rule", "")
143
+ assert "colon" in conversion_rule.lower() and "hierarchy" in conversion_rule.lower(), \
144
+ "Conversion rule should explain colon for hierarchy"
145
+ assert "dot" in conversion_rule.lower() and "facet" in conversion_rule.lower(), \
146
+ "Conversion rule should explain dot for facet"
147
+
148
+
149
+ # SPEC-TESTER-CONV-0063 (part 2)
150
+ def test_urn_examples_use_ux_foundations(artifact_convention):
151
+ """URN examples use ux:foundations"""
152
+ artifact_urns = artifact_convention.get("artifact_urns", {})
153
+ examples = artifact_urns.get("examples", {})
154
+ artifact_to_urn = examples.get("artifact_to_urn", [])
155
+
156
+ # Check for ux:foundations examples
157
+ ux_base = None
158
+ ux_category = None
159
+
160
+ for example in artifact_to_urn:
161
+ if example.get("artifact_name") == "ux:foundations":
162
+ ux_base = example
163
+ if example.get("artifact_name") == "ux:foundations.colors":
164
+ ux_category = example
165
+
166
+ assert ux_base is not None, "Missing ux:foundations URN example"
167
+ assert ux_base.get("urn") == "contract:ux:foundations", \
168
+ f"Expected 'contract:ux:foundations', got: {ux_base.get('urn')}"
169
+
170
+ assert ux_category is not None, "Missing ux:foundations.colors URN example"
171
+ assert ux_category.get("urn") == "contract:ux:foundations.colors", \
172
+ f"Expected 'contract:ux:foundations.colors', got: {ux_category.get('urn')}"
173
+
174
+
175
+ # SPEC-TESTER-CONV-0064
176
+ def test_contract_id_supports_category(artifact_convention):
177
+ """Contract ID field supports optional category"""
178
+ contract_artifacts = artifact_convention.get("contract_artifacts", {})
179
+ id_field = contract_artifacts.get("id_field")
180
+
181
+ assert id_field == "id: {domain}:{resource}[.{category}]:v{version}", \
182
+ f"Expected ID field pattern with optional category, got: {id_field}"
183
+
184
+ # Check URN mapping mentions category
185
+ urn_mapping = contract_artifacts.get("urn_mapping", "")
186
+ assert ".{category}" in urn_mapping or "category" in urn_mapping.lower(), \
187
+ "URN mapping should mention category facet"
188
+
189
+
190
+ # SPEC-TESTER-CONV-0064 (part 2)
191
+ def test_contract_examples_use_ux_foundations(artifact_convention):
192
+ """Contract examples use ux:foundations"""
193
+ contract_artifacts = artifact_convention.get("contract_artifacts", {})
194
+ examples = contract_artifacts.get("example", [])
195
+
196
+ # Check for ux:foundations examples
197
+ ux_base = None
198
+ ux_category = None
199
+
200
+ for example in examples:
201
+ if example.get("id") == "ux:foundations:v1":
202
+ ux_base = example
203
+ if example.get("id") == "ux:foundations.colors:v1":
204
+ ux_category = example
205
+
206
+ assert ux_base is not None, "Missing ux:foundations contract example"
207
+ assert ux_base.get("path") == "ux/foundations.json", \
208
+ f"Expected 'ux/foundations.json', got: {ux_base.get('path')}"
209
+ assert ux_base.get("producer") == "wagon:maintain-ux", \
210
+ f"Expected producer 'wagon:maintain-ux', got: {ux_base.get('producer')}"
211
+
212
+ assert ux_category is not None, "Missing ux:foundations.colors contract example"
213
+ assert ux_category.get("path") == "ux/foundations/colors.json", \
214
+ f"Expected 'ux/foundations/colors.json', got: {ux_category.get('path')}"
215
+
216
+
217
+ # SPEC-TESTER-CONV-0065
218
+ def test_wagon_examples_use_maintain_ux(artifact_convention):
219
+ """Wagon artifacts examples updated to maintain-ux"""
220
+ wagon_artifacts = artifact_convention.get("wagon_artifacts", {})
221
+ produce_example = wagon_artifacts.get("produce_example", {})
222
+
223
+ wagon_name = produce_example.get("wagon")
224
+ assert wagon_name == "maintain-ux", \
225
+ f"Expected producer wagon 'maintain-ux', got: {wagon_name}"
226
+
227
+
228
+ # SPEC-TESTER-CONV-0065 (part 2)
229
+ def test_wagon_produces_ux_foundations(artifact_convention):
230
+ """Wagon produces ux:foundations artifacts"""
231
+ wagon_artifacts = artifact_convention.get("wagon_artifacts", {})
232
+ produce_example = wagon_artifacts.get("produce_example", {})
233
+ produce = produce_example.get("produce", [])
234
+
235
+ # Check for ux:foundations and ux:foundations.colors
236
+ ux_base = None
237
+ ux_category = None
238
+
239
+ for item in produce:
240
+ if item.get("name") == "ux:foundations":
241
+ ux_base = item
242
+ if item.get("name") == "ux:foundations.colors":
243
+ ux_category = item
244
+
245
+ assert ux_base is not None, "Missing ux:foundations in produce"
246
+ assert ux_base.get("urn") == "contract:ux:foundations", \
247
+ f"Expected URN 'contract:ux:foundations', got: {ux_base.get('urn')}"
248
+ assert ux_base.get("to") == "external", \
249
+ f"Expected to='external', got: {ux_base.get('to')}"
250
+
251
+ assert ux_category is not None, "Missing ux:foundations.colors in produce"
252
+ assert ux_category.get("urn") == "contract:ux:foundations.colors", \
253
+ f"Expected URN 'contract:ux:foundations.colors', got: {ux_category.get('urn')}"
254
+
255
+
256
+ # SPEC-TESTER-CONV-0066
257
+ def test_validation_regex_allows_category(artifact_convention):
258
+ """Validation regex allows optional category facet"""
259
+ import re
260
+
261
+ validation = artifact_convention.get("validation", {})
262
+ id_pattern = validation.get("id_pattern")
263
+
264
+ assert id_pattern is not None, "Missing id_pattern in validation section"
265
+
266
+ # Test the regex against valid patterns
267
+ test_cases = [
268
+ ("ux:foundations:v1", True),
269
+ ("ux:foundations.colors:v1", True),
270
+ ("mechanic:decision.choice:v1", True),
271
+ ("match:result:v2", True),
272
+ ("invalid", False),
273
+ ("no:version", False),
274
+ ]
275
+
276
+ for test_input, should_match in test_cases:
277
+ match = re.match(id_pattern, test_input)
278
+ if should_match:
279
+ assert match is not None, \
280
+ f"Pattern '{id_pattern}' should match '{test_input}'"
281
+ else:
282
+ assert match is None, \
283
+ f"Pattern '{id_pattern}' should NOT match '{test_input}'"
284
+
285
+
286
+ # SPEC-TESTER-CONV-0067
287
+ def test_migration_note_documents_refactoring(artifact_convention):
288
+ """Migration note documents legacy URN refactoring"""
289
+ artifact_urns = artifact_convention.get("artifact_urns", {})
290
+ migration_strategy = artifact_urns.get("migration_strategy", {})
291
+ refactor_note = migration_strategy.get("refactor_note", "")
292
+
293
+ assert refactor_note != "", "Missing refactor_note in migration_strategy"
294
+
295
+ # Check for legacy pattern documentation
296
+ assert "contract:{domain}.{resource}" in refactor_note or \
297
+ "domain.resource" in refactor_note.lower(), \
298
+ "Should document legacy pattern with dot separator"
299
+
300
+ # Check for new pattern documentation
301
+ assert "contract:{domain}:{resource}" in refactor_note or \
302
+ "domain:resource" in refactor_note.lower(), \
303
+ "Should document new pattern with colon separator"
304
+
305
+ # Check for category preservation
306
+ assert "category" in refactor_note.lower() and "dot" in refactor_note.lower(), \
307
+ "Should explain category as dot facet"