atdd 0.4.7__py3-none-any.whl → 0.6.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.
@@ -0,0 +1,433 @@
1
+ """
2
+ Planner hierarchy coverage validation.
3
+
4
+ ATDD Hierarchy Coverage Spec v0.1 - Section 2: Planner Coverage Rules
5
+
6
+ Validates bidirectional coverage between:
7
+ - Trains <-> Wagons (COVERAGE-PLAN-2.1)
8
+ - Wagons <-> Features (COVERAGE-PLAN-2.2)
9
+ - Features <-> WMBTs (COVERAGE-PLAN-2.3)
10
+ - WMBTs <-> Acceptances (COVERAGE-PLAN-2.4)
11
+
12
+ Architecture:
13
+ - Uses shared fixtures from atdd.coach.validators.shared_fixtures
14
+ - Phased rollout via atdd.coach.utils.coverage_phase
15
+ - Exception handling via .atdd/config.yaml coverage.exceptions
16
+ """
17
+
18
+ import pytest
19
+ import yaml
20
+ from pathlib import Path
21
+ from typing import Dict, List, Set, Tuple, Any
22
+
23
+ from atdd.coach.utils.repo import find_repo_root
24
+ from atdd.coach.utils.coverage_phase import (
25
+ CoveragePhase,
26
+ should_enforce,
27
+ emit_coverage_warning
28
+ )
29
+
30
+
31
+ # Path constants
32
+ REPO_ROOT = find_repo_root()
33
+ PLAN_DIR = REPO_ROOT / "plan"
34
+
35
+
36
+ # ============================================================================
37
+ # COVERAGE-PLAN-2.1: Train <-> Wagon Coverage
38
+ # ============================================================================
39
+
40
+
41
+ @pytest.mark.planner
42
+ def test_all_wagons_in_at_least_one_train(
43
+ wagon_manifests,
44
+ wagon_to_train_mapping,
45
+ coverage_exceptions
46
+ ):
47
+ """
48
+ COVERAGE-PLAN-2.1a: Every wagon appears in at least one train.
49
+
50
+ Given: All wagon manifests in plan/
51
+ When: Checking train participant references
52
+ Then: Every wagon slug appears in at least one train's participants
53
+ (unless in wagons_not_in_train allow-list or status:draft)
54
+ """
55
+ allowed_wagons = set(coverage_exceptions.get("wagons_not_in_train", []))
56
+ violations = []
57
+
58
+ for path, manifest in wagon_manifests:
59
+ wagon_slug = manifest.get("wagon", "")
60
+ status = manifest.get("status", "")
61
+
62
+ # Skip draft wagons
63
+ if status == "draft":
64
+ continue
65
+
66
+ # Skip allowed exceptions
67
+ if wagon_slug in allowed_wagons:
68
+ continue
69
+
70
+ # Check if wagon is in any train
71
+ if wagon_slug not in wagon_to_train_mapping:
72
+ violations.append(
73
+ f"{wagon_slug}: not referenced by any train (path: {path.relative_to(REPO_ROOT)})"
74
+ )
75
+
76
+ if violations:
77
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
78
+ pytest.fail(
79
+ f"COVERAGE-PLAN-2.1a: Wagons not in any train:\n " +
80
+ "\n ".join(violations) +
81
+ "\n\nAdd to train participants or coverage.exceptions.wagons_not_in_train"
82
+ )
83
+ else:
84
+ for violation in violations:
85
+ emit_coverage_warning(
86
+ "COVERAGE-PLAN-2.1a",
87
+ violation,
88
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
89
+ )
90
+
91
+
92
+ @pytest.mark.planner
93
+ def test_all_train_wagon_refs_exist(train_files, wagon_manifests):
94
+ """
95
+ COVERAGE-PLAN-2.1b: Every train wagon participant has manifest.
96
+
97
+ Given: Train YAML files with wagon: participants
98
+ When: Checking wagon references
99
+ Then: Every referenced wagon has a manifest in plan/
100
+ """
101
+ # Build set of existing wagon slugs
102
+ wagon_slugs = {manifest.get("wagon", "") for _, manifest in wagon_manifests}
103
+
104
+ violations = []
105
+
106
+ for train_path, train_data in train_files:
107
+ train_id = train_data.get("train_id", train_path.stem)
108
+ participants = train_data.get("participants", [])
109
+
110
+ for participant in participants:
111
+ if isinstance(participant, str) and participant.startswith("wagon:"):
112
+ wagon_slug = participant.replace("wagon:", "")
113
+ if wagon_slug not in wagon_slugs:
114
+ violations.append(
115
+ f"{train_id}: references non-existent wagon '{wagon_slug}'"
116
+ )
117
+
118
+ if violations:
119
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
120
+ pytest.fail(
121
+ f"COVERAGE-PLAN-2.1b: Train wagon references to non-existent wagons:\n " +
122
+ "\n ".join(violations)
123
+ )
124
+ else:
125
+ for violation in violations:
126
+ emit_coverage_warning(
127
+ "COVERAGE-PLAN-2.1b",
128
+ violation,
129
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
130
+ )
131
+
132
+
133
+ # ============================================================================
134
+ # COVERAGE-PLAN-2.2: Wagon <-> Feature Coverage
135
+ # ============================================================================
136
+
137
+
138
+ @pytest.mark.planner
139
+ def test_all_features_in_wagon_manifest(feature_files, wagon_manifests, coverage_exceptions):
140
+ """
141
+ COVERAGE-PLAN-2.2a: Every feature file referenced in wagon manifest.
142
+
143
+ Given: Feature files in plan/*/features/
144
+ When: Checking wagon manifest features[] references
145
+ Then: Every feature file is referenced by its wagon's manifest
146
+ """
147
+ allowed_features = set(coverage_exceptions.get("features_orphaned", []))
148
+
149
+ # Build mapping of wagon_dir -> manifest features
150
+ wagon_features: Dict[str, Set[str]] = {}
151
+ for path, manifest in wagon_manifests:
152
+ wagon_dir = path.parent.name
153
+ features_list = manifest.get("features", [])
154
+
155
+ feature_slugs = set()
156
+ for feature in features_list:
157
+ if isinstance(feature, dict) and "urn" in feature:
158
+ # URN format: feature:wagon-slug:feature-slug
159
+ urn = feature["urn"]
160
+ parts = urn.split(":")
161
+ if len(parts) >= 3:
162
+ feature_slugs.add(parts[2])
163
+ elif isinstance(feature, str):
164
+ # Direct slug or URN
165
+ if feature.startswith("feature:"):
166
+ parts = feature.split(":")
167
+ if len(parts) >= 3:
168
+ feature_slugs.add(parts[2])
169
+ else:
170
+ feature_slugs.add(feature)
171
+
172
+ wagon_features[wagon_dir] = feature_slugs
173
+
174
+ violations = []
175
+
176
+ for path, feature_data in feature_files:
177
+ wagon_dir = path.parent.parent.name
178
+ # Feature filename (snake_case) -> slug (kebab-case)
179
+ feature_filename = path.stem
180
+ feature_slug = feature_filename.replace("_", "-")
181
+
182
+ # Also check for URN in feature data
183
+ feature_urn = feature_data.get("urn", "")
184
+
185
+ # Skip allowed exceptions
186
+ if feature_urn in allowed_features or feature_slug in allowed_features:
187
+ continue
188
+
189
+ # Check if feature is in wagon manifest
190
+ manifest_features = wagon_features.get(wagon_dir, set())
191
+ if feature_slug not in manifest_features:
192
+ violations.append(
193
+ f"{path.relative_to(REPO_ROOT)}: not in wagon manifest features[]"
194
+ )
195
+
196
+ if violations:
197
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
198
+ pytest.fail(
199
+ f"COVERAGE-PLAN-2.2a: Features not in wagon manifest:\n " +
200
+ "\n ".join(violations) +
201
+ "\n\nAdd to wagon features[] or coverage.exceptions.features_orphaned"
202
+ )
203
+ else:
204
+ for violation in violations:
205
+ emit_coverage_warning(
206
+ "COVERAGE-PLAN-2.2a",
207
+ violation,
208
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
209
+ )
210
+
211
+
212
+ @pytest.mark.planner
213
+ def test_all_wagon_feature_refs_exist(wagon_manifests):
214
+ """
215
+ COVERAGE-PLAN-2.2b: Every wagon features[] entry has YAML file.
216
+
217
+ Given: Wagon manifests with features[] references
218
+ When: Checking for corresponding files
219
+ Then: Every features[] entry has a YAML file in wagon/features/
220
+ """
221
+ violations = []
222
+
223
+ for path, manifest in wagon_manifests:
224
+ wagon_dir = path.parent
225
+ features_dir = wagon_dir / "features"
226
+ features_list = manifest.get("features", [])
227
+
228
+ for feature in features_list:
229
+ feature_slug = None
230
+
231
+ if isinstance(feature, dict) and "urn" in feature:
232
+ # URN format: feature:wagon-slug:feature-slug
233
+ urn = feature["urn"]
234
+ parts = urn.split(":")
235
+ if len(parts) >= 3:
236
+ feature_slug = parts[2]
237
+ elif isinstance(feature, str):
238
+ if feature.startswith("feature:"):
239
+ parts = feature.split(":")
240
+ if len(parts) >= 3:
241
+ feature_slug = parts[2]
242
+ else:
243
+ feature_slug = feature
244
+
245
+ if not feature_slug:
246
+ continue
247
+
248
+ # Convert slug to filename (kebab-case -> snake_case)
249
+ feature_filename = feature_slug.replace("-", "_") + ".yaml"
250
+ feature_path = features_dir / feature_filename
251
+
252
+ if not feature_path.exists():
253
+ wagon_slug = manifest.get("wagon", wagon_dir.name)
254
+ violations.append(
255
+ f"{wagon_slug}: features[] references '{feature_slug}' but "
256
+ f"{feature_path.relative_to(REPO_ROOT)} does not exist"
257
+ )
258
+
259
+ if violations:
260
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
261
+ pytest.fail(
262
+ f"COVERAGE-PLAN-2.2b: Wagon feature references without files:\n " +
263
+ "\n ".join(violations)
264
+ )
265
+ else:
266
+ for violation in violations:
267
+ emit_coverage_warning(
268
+ "COVERAGE-PLAN-2.2b",
269
+ violation,
270
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
271
+ )
272
+
273
+
274
+ # ============================================================================
275
+ # COVERAGE-PLAN-2.3: WMBT <-> Feature Coverage
276
+ # ============================================================================
277
+
278
+
279
+ @pytest.mark.planner
280
+ def test_all_wmbts_in_at_least_one_feature(wmbt_files, feature_files):
281
+ """
282
+ COVERAGE-PLAN-2.3: Every WMBT appears in at least one feature's wmbts.
283
+
284
+ Given: WMBT files in plan/*/
285
+ When: Checking feature wmbts[] references
286
+ Then: Every WMBT file is referenced by at least one feature
287
+ """
288
+ # Build set of all WMBT references from features
289
+ referenced_wmbts: Set[str] = set()
290
+
291
+ for path, feature_data in feature_files:
292
+ wmbts_list = feature_data.get("wmbts", [])
293
+ for wmbt_ref in wmbts_list:
294
+ if isinstance(wmbt_ref, dict) and "urn" in wmbt_ref:
295
+ referenced_wmbts.add(wmbt_ref["urn"])
296
+ elif isinstance(wmbt_ref, str):
297
+ referenced_wmbts.add(wmbt_ref)
298
+
299
+ violations = []
300
+
301
+ for path, wmbt_data in wmbt_files:
302
+ wmbt_urn = wmbt_data.get("urn", "")
303
+ status = wmbt_data.get("status", "")
304
+
305
+ # Skip draft WMBTs
306
+ if status == "draft":
307
+ continue
308
+
309
+ # Check both URN and file-based reference
310
+ wmbt_id = path.stem # e.g., "D001"
311
+ wagon_slug = path.parent.name.replace("_", "-")
312
+
313
+ # Try various reference formats
314
+ is_referenced = (
315
+ wmbt_urn in referenced_wmbts or
316
+ f"wmbt:{wagon_slug}:{wmbt_id}" in referenced_wmbts or
317
+ wmbt_id in referenced_wmbts
318
+ )
319
+
320
+ if not is_referenced:
321
+ violations.append(
322
+ f"{path.relative_to(REPO_ROOT)}: not referenced by any feature"
323
+ )
324
+
325
+ if violations:
326
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
327
+ pytest.fail(
328
+ f"COVERAGE-PLAN-2.3: WMBTs not in any feature:\n " +
329
+ "\n ".join(violations) +
330
+ "\n\nAdd WMBT to a feature's wmbts[] list"
331
+ )
332
+ else:
333
+ for violation in violations:
334
+ emit_coverage_warning(
335
+ "COVERAGE-PLAN-2.3",
336
+ violation,
337
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
338
+ )
339
+
340
+
341
+ # ============================================================================
342
+ # COVERAGE-PLAN-2.4: WMBT <-> Acceptance Coverage
343
+ # ============================================================================
344
+
345
+
346
+ @pytest.mark.planner
347
+ def test_all_wmbts_have_acceptances(wmbt_files, coverage_exceptions):
348
+ """
349
+ COVERAGE-PLAN-2.4: Every non-draft WMBT has at least one acceptance.
350
+
351
+ Given: WMBT files in plan/*/
352
+ When: Checking acceptances[]
353
+ Then: Every WMBT (except draft) has at least one acceptance
354
+ """
355
+ allowed_wmbts = set(coverage_exceptions.get("wmbts_without_acceptance", []))
356
+ violations = []
357
+
358
+ for path, wmbt_data in wmbt_files:
359
+ wmbt_urn = wmbt_data.get("urn", "")
360
+ status = wmbt_data.get("status", "")
361
+ acceptances = wmbt_data.get("acceptances", [])
362
+
363
+ # Skip draft WMBTs
364
+ if status == "draft":
365
+ continue
366
+
367
+ # Skip allowed exceptions
368
+ if wmbt_urn in allowed_wmbts:
369
+ continue
370
+
371
+ # Check for acceptances
372
+ if not acceptances or len(acceptances) == 0:
373
+ violations.append(
374
+ f"{path.relative_to(REPO_ROOT)}: no acceptances defined"
375
+ )
376
+
377
+ if violations:
378
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
379
+ pytest.fail(
380
+ f"COVERAGE-PLAN-2.4: WMBTs without acceptances:\n " +
381
+ "\n ".join(violations) +
382
+ "\n\nAdd acceptances or coverage.exceptions.wmbts_without_acceptance"
383
+ )
384
+ else:
385
+ for violation in violations:
386
+ emit_coverage_warning(
387
+ "COVERAGE-PLAN-2.4",
388
+ violation,
389
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT
390
+ )
391
+
392
+
393
+ # ============================================================================
394
+ # COVERAGE SUMMARY
395
+ # ============================================================================
396
+
397
+
398
+ @pytest.mark.planner
399
+ def test_planner_coverage_summary(
400
+ wagon_manifests,
401
+ feature_files,
402
+ wmbt_files,
403
+ wagon_to_train_mapping
404
+ ):
405
+ """
406
+ COVERAGE-PLAN-SUMMARY: Report planner coverage statistics.
407
+
408
+ This test always passes but reports coverage metrics for visibility.
409
+ """
410
+ # Count wagons in trains
411
+ wagons_in_trains = len(wagon_to_train_mapping)
412
+ total_wagons = len(wagon_manifests)
413
+
414
+ # Count features
415
+ total_features = len(feature_files)
416
+
417
+ # Count WMBTs with acceptances
418
+ wmbts_with_acceptances = sum(
419
+ 1 for _, data in wmbt_files
420
+ if data.get("acceptances") and len(data.get("acceptances", [])) > 0
421
+ )
422
+ total_wmbts = len(wmbt_files)
423
+
424
+ # Report summary (always passes)
425
+ summary = (
426
+ f"\n\nPlanner Coverage Summary:\n"
427
+ f" Wagons in trains: {wagons_in_trains}/{total_wagons}\n"
428
+ f" Features defined: {total_features}\n"
429
+ f" WMBTs with acceptances: {wmbts_with_acceptances}/{total_wmbts}"
430
+ )
431
+
432
+ # This test always passes - it's informational
433
+ assert True, summary
@@ -0,0 +1,114 @@
1
+ version: "1.0"
2
+ name: "Tester Coverage Convention"
3
+ description: "Section 3 of ATDD Hierarchy Coverage Spec v0.1 - Ensures test artifacts have complete coverage"
4
+
5
+ rules:
6
+ - id: "COVERAGE-TEST-3.1"
7
+ name: "Acceptance - Tests Coverage"
8
+ description: "Every acceptance criterion must have corresponding test(s)"
9
+ bidirectional:
10
+ - direction: "acceptance -> test"
11
+ requirement: "Each acceptance URN must be referenced by at least one test"
12
+ exceptions: "acceptance_without_tests allow-list"
13
+ validator: "test_all_acceptances_have_tests"
14
+
15
+ - id: "COVERAGE-TEST-3.2"
16
+ name: "Contract - Wagon Coverage"
17
+ description: "Bidirectional coverage between contracts and wagon produce/consume"
18
+ bidirectional:
19
+ - direction: "contract -> wagon"
20
+ requirement: "Every contract schema must be referenced by produce/consume"
21
+ exceptions: "contracts_unreferenced allow-list or x-artifact-metadata.status: draft|external|deprecated"
22
+ validator: "test_all_contracts_referenced"
23
+
24
+ - direction: "wagon -> contract"
25
+ requirement: "Every produce/consume contract ref must have schema file"
26
+ validator: "test_all_contract_refs_exist"
27
+
28
+ - id: "COVERAGE-TEST-3.3"
29
+ name: "Telemetry - Wagon Coverage"
30
+ description: "Bidirectional coverage between telemetry signals and wagon produce"
31
+ bidirectional:
32
+ - direction: "telemetry -> wagon"
33
+ requirement: "Every telemetry signal must be referenced by wagon produce"
34
+ exceptions: "telemetry_unreferenced allow-list or status: draft|external|deprecated"
35
+ validator: "test_all_telemetry_referenced"
36
+
37
+ - direction: "wagon -> telemetry"
38
+ requirement: "Every produce telemetry ref must have signal files"
39
+ validator: "test_all_telemetry_refs_exist"
40
+
41
+ - id: "COVERAGE-TEST-3.4"
42
+ name: "Telemetry Tracking Manifest"
43
+ description: "Tracking manifest must be complete if present"
44
+ requirement: "If telemetry/_tracking_manifest.yaml exists, all signals must be present"
45
+ validator: "test_telemetry_manifest_complete"
46
+
47
+ coverage_graph:
48
+ description: "The tester coverage graph ensures acceptance-to-test traceability and artifact completeness"
49
+ levels:
50
+ - name: "Acceptance"
51
+ covered_by: "Tests (Python, TypeScript, Dart)"
52
+ validators: ["COVERAGE-TEST-3.1"]
53
+
54
+ - name: "Contract"
55
+ referenced_by: "Wagon produce/consume"
56
+ validators: ["COVERAGE-TEST-3.2"]
57
+
58
+ - name: "Telemetry"
59
+ referenced_by: "Wagon produce"
60
+ validators: ["COVERAGE-TEST-3.3", "COVERAGE-TEST-3.4"]
61
+
62
+ exception_handling:
63
+ description: "How exceptions are handled in tester coverage validation"
64
+ allow_lists:
65
+ acceptance_without_tests:
66
+ description: "Acceptance URNs that are allowed without test coverage"
67
+ use_case: "Future acceptances, integration-only criteria, or manual verification"
68
+ config_path: "coverage.exceptions.acceptance_without_tests"
69
+
70
+ contracts_unreferenced:
71
+ description: "Contract URNs that are allowed without produce/consume"
72
+ use_case: "External contracts, deprecated schemas, or system-level contracts"
73
+ config_path: "coverage.exceptions.contracts_unreferenced"
74
+
75
+ telemetry_unreferenced:
76
+ description: "Telemetry URNs that are allowed without references"
77
+ use_case: "System telemetry, deprecated signals, or external observability"
78
+ config_path: "coverage.exceptions.telemetry_unreferenced"
79
+
80
+ metadata_exemptions:
81
+ contracts:
82
+ field: "x-artifact-metadata.status"
83
+ values: ["draft", "external", "deprecated"]
84
+ reason: "Contract schemas with these statuses are exempt from coverage requirements"
85
+
86
+ telemetry:
87
+ field: "status"
88
+ values: ["draft", "external", "deprecated"]
89
+ reason: "Telemetry signals with these statuses are exempt from coverage requirements"
90
+
91
+ test_discovery:
92
+ description: "How tests are discovered and linked to acceptances"
93
+ patterns:
94
+ python:
95
+ location: "python/{wagon}/"
96
+ filename: "test_*.py"
97
+ reference_method: "Docstring URN reference or header comment"
98
+ example: "# URN: acc:maintain-ux:D001-UNIT-001"
99
+
100
+ typescript:
101
+ location: "supabase/functions/{wagon}/{feature}/test/"
102
+ filename: "*.test.ts"
103
+ reference_method: "Comment at top of file or JSDoc annotation"
104
+ example: "// urn: acc:maintain-ux:D001-UNIT-001"
105
+
106
+ dart:
107
+ location: "test/"
108
+ filename: "*_test.dart"
109
+ reference_method: "Comment at top of file"
110
+ example: "// urn: acc:maintain-ux:D001-UNIT-001"
111
+
112
+ rollout:
113
+ phase: "PLANNER_TESTER_ENFORCEMENT"
114
+ description: "Section 3 validators become strict in Phase 2"