atdd 0.5.0__py3-none-any.whl → 0.6.1__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.
- atdd/coach/schemas/config.schema.json +63 -0
- atdd/coach/templates/ATDD.md +1 -1
- atdd/coach/utils/coverage_phase.py +97 -0
- atdd/coach/validators/shared_fixtures.py +154 -0
- atdd/coder/conventions/backend.convention.yaml +1 -1
- atdd/coder/conventions/boundaries.convention.yaml +9 -9
- atdd/coder/conventions/coverage.convention.yaml +85 -0
- atdd/coder/conventions/presentation.convention.yaml +8 -8
- atdd/coder/conventions/train.convention.yaml +15 -14
- atdd/coder/validators/conftest.py +5 -0
- atdd/coder/validators/test_hierarchy_coverage.py +361 -0
- atdd/coder/validators/test_presentation_convention.py +11 -11
- atdd/coder/validators/test_station_master_pattern.py +16 -14
- atdd/coder/validators/test_train_infrastructure.py +22 -14
- atdd/coder/validators/test_wagon_boundaries.py +2 -2
- atdd/planner/conventions/coverage.convention.yaml +95 -0
- atdd/planner/validators/test_hierarchy_coverage.py +433 -0
- atdd/tester/conventions/coverage.convention.yaml +114 -0
- atdd/tester/validators/test_hierarchy_coverage.py +604 -0
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/METADATA +1 -1
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/RECORD +25 -17
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/WHEEL +0 -0
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/entry_points.txt +0 -0
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {atdd-0.5.0.dist-info → atdd-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -58,6 +58,69 @@
|
|
|
58
58
|
},
|
|
59
59
|
"required": ["last_version"],
|
|
60
60
|
"additionalProperties": false
|
|
61
|
+
},
|
|
62
|
+
"coverage": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"description": "Hierarchy coverage validation settings (ATDD Hierarchy Coverage Spec v0.1)",
|
|
65
|
+
"properties": {
|
|
66
|
+
"exceptions": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"description": "Allow-lists for coverage exceptions",
|
|
69
|
+
"properties": {
|
|
70
|
+
"wagons_not_in_train": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": {"type": "string"},
|
|
73
|
+
"description": "Wagon slugs allowed without train coverage"
|
|
74
|
+
},
|
|
75
|
+
"features_orphaned": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": {"type": "string"},
|
|
78
|
+
"description": "Feature URNs allowed without wagon manifest reference"
|
|
79
|
+
},
|
|
80
|
+
"wmbts_without_acceptance": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {"type": "string"},
|
|
83
|
+
"description": "WMBT URNs allowed without acceptance criteria"
|
|
84
|
+
},
|
|
85
|
+
"acceptance_without_tests": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": {"type": "string"},
|
|
88
|
+
"description": "Acceptance URNs allowed without test coverage"
|
|
89
|
+
},
|
|
90
|
+
"contracts_unreferenced": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": {"type": "string"},
|
|
93
|
+
"description": "Contract URNs allowed without produce/consume"
|
|
94
|
+
},
|
|
95
|
+
"telemetry_unreferenced": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": {"type": "string"},
|
|
98
|
+
"description": "Telemetry URNs allowed without references"
|
|
99
|
+
},
|
|
100
|
+
"features_without_implementation": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"items": {"type": "string"},
|
|
103
|
+
"description": "Feature URNs allowed without code implementation"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"additionalProperties": false
|
|
107
|
+
},
|
|
108
|
+
"thresholds": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"description": "Coverage threshold settings",
|
|
111
|
+
"properties": {
|
|
112
|
+
"min_acceptance_coverage": {
|
|
113
|
+
"type": "number",
|
|
114
|
+
"minimum": 0,
|
|
115
|
+
"maximum": 100,
|
|
116
|
+
"default": 80,
|
|
117
|
+
"description": "Minimum percentage of acceptances that must have tests"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": false
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"additionalProperties": false
|
|
61
124
|
}
|
|
62
125
|
},
|
|
63
126
|
"required": ["version", "release"],
|
atdd/coach/templates/ATDD.md
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ATDD Hierarchy Coverage Spec v0.1 Rollout Phase Controller.
|
|
3
|
+
|
|
4
|
+
Manages the phased rollout of coverage validation rules:
|
|
5
|
+
- Phase 1 (WARNINGS_ONLY): All validators emit warnings only
|
|
6
|
+
- Phase 2 (PLANNER_TESTER_ENFORCEMENT): Sections 2 + 3 validators strict
|
|
7
|
+
- Phase 3 (FULL_ENFORCEMENT): All validators (including Section 4) strict
|
|
8
|
+
|
|
9
|
+
Usage in validators:
|
|
10
|
+
from atdd.coach.utils.coverage_phase import CoveragePhase, should_enforce
|
|
11
|
+
|
|
12
|
+
if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
|
|
13
|
+
assert condition, "Error message"
|
|
14
|
+
else:
|
|
15
|
+
if not condition:
|
|
16
|
+
emit_coverage_warning("COVERAGE-PLAN-2.1", "Warning message", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from enum import IntEnum
|
|
20
|
+
from typing import Optional
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CoveragePhase(IntEnum):
|
|
25
|
+
"""
|
|
26
|
+
Rollout phases for ATDD Hierarchy Coverage Spec v0.1.
|
|
27
|
+
|
|
28
|
+
Phases are ordered by strictness level:
|
|
29
|
+
- WARNINGS_ONLY (1): All new validators emit warnings, no assertions
|
|
30
|
+
- PLANNER_TESTER_ENFORCEMENT (2): Planner (Section 2) + Tester (Section 3) strict
|
|
31
|
+
- FULL_ENFORCEMENT (3): All validators including Coder (Section 4) strict
|
|
32
|
+
"""
|
|
33
|
+
WARNINGS_ONLY = 1
|
|
34
|
+
PLANNER_TESTER_ENFORCEMENT = 2
|
|
35
|
+
FULL_ENFORCEMENT = 3
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Current rollout phase - update this to advance through phases
|
|
39
|
+
CURRENT_PHASE = CoveragePhase.WARNINGS_ONLY
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def should_enforce(validator_phase: CoveragePhase) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Check if a validator should enforce strict mode.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
validator_phase: The phase at which this validator becomes strict
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if current phase >= validator_phase (should enforce)
|
|
51
|
+
False if current phase < validator_phase (should warn only)
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
# This validator becomes strict in Phase 2
|
|
55
|
+
if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
|
|
56
|
+
assert wagon_in_train, "Wagon must be in at least one train"
|
|
57
|
+
else:
|
|
58
|
+
if not wagon_in_train:
|
|
59
|
+
emit_coverage_warning("COVERAGE-PLAN-2.1", "Wagon not in any train", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
|
|
60
|
+
"""
|
|
61
|
+
return CURRENT_PHASE >= validator_phase
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_current_phase() -> CoveragePhase:
|
|
65
|
+
"""Get the current rollout phase."""
|
|
66
|
+
return CURRENT_PHASE
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_phase_name(phase: Optional[CoveragePhase] = None) -> str:
|
|
70
|
+
"""Get human-readable name for a phase."""
|
|
71
|
+
phase = phase or CURRENT_PHASE
|
|
72
|
+
return {
|
|
73
|
+
CoveragePhase.WARNINGS_ONLY: "Phase 1: Warnings Only",
|
|
74
|
+
CoveragePhase.PLANNER_TESTER_ENFORCEMENT: "Phase 2: Planner+Tester Enforcement",
|
|
75
|
+
CoveragePhase.FULL_ENFORCEMENT: "Phase 3: Full Enforcement",
|
|
76
|
+
}.get(phase, "Unknown Phase")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def emit_coverage_warning(
|
|
80
|
+
spec_id: str,
|
|
81
|
+
message: str,
|
|
82
|
+
validator_phase: CoveragePhase = CoveragePhase.PLANNER_TESTER_ENFORCEMENT
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Emit a coverage validation warning with phase context.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
spec_id: The SPEC ID (e.g., "COVERAGE-PLAN-2.1")
|
|
89
|
+
message: The warning message
|
|
90
|
+
validator_phase: Phase when this becomes an error
|
|
91
|
+
"""
|
|
92
|
+
phase_name = get_phase_name(validator_phase)
|
|
93
|
+
warnings.warn(
|
|
94
|
+
f"[{spec_id}] {message} (will become error in {phase_name})",
|
|
95
|
+
category=UserWarning,
|
|
96
|
+
stacklevel=3
|
|
97
|
+
)
|
|
@@ -445,3 +445,157 @@ def pytest_html_results_summary(prefix, summary, postfix):
|
|
|
445
445
|
'against platform schemas and conventions.</p>'
|
|
446
446
|
'</div>'
|
|
447
447
|
])
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ============================================================================
|
|
451
|
+
# COVERAGE VALIDATION FIXTURES (ATDD Hierarchy Coverage Spec v0.1)
|
|
452
|
+
# ============================================================================
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@pytest.fixture(scope="module")
|
|
456
|
+
def coverage_exceptions(atdd_config: Dict[str, Any]) -> Dict[str, List[str]]:
|
|
457
|
+
"""
|
|
458
|
+
Load coverage exception allow-lists from .atdd/config.yaml.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Dict mapping exception type to list of allowed URNs/slugs
|
|
462
|
+
"""
|
|
463
|
+
return atdd_config.get("coverage", {}).get("exceptions", {})
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@pytest.fixture(scope="module")
|
|
467
|
+
def coverage_thresholds(atdd_config: Dict[str, Any]) -> Dict[str, Any]:
|
|
468
|
+
"""
|
|
469
|
+
Load coverage threshold settings from .atdd/config.yaml.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Dict with threshold settings (min_acceptance_coverage, etc.)
|
|
473
|
+
"""
|
|
474
|
+
defaults = {"min_acceptance_coverage": 80}
|
|
475
|
+
thresholds = atdd_config.get("coverage", {}).get("thresholds", {})
|
|
476
|
+
return {**defaults, **thresholds}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@pytest.fixture(scope="module")
|
|
480
|
+
def feature_files() -> List[Tuple[Path, Dict[str, Any]]]:
|
|
481
|
+
"""
|
|
482
|
+
Discover all feature files in plan/*/features/.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
List of (path, feature_data) tuples
|
|
486
|
+
"""
|
|
487
|
+
import re
|
|
488
|
+
features = []
|
|
489
|
+
if not PLAN_DIR.exists():
|
|
490
|
+
return features
|
|
491
|
+
|
|
492
|
+
for wagon_dir in PLAN_DIR.iterdir():
|
|
493
|
+
if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
|
|
494
|
+
features_dir = wagon_dir / "features"
|
|
495
|
+
if features_dir.exists():
|
|
496
|
+
for feature_file in features_dir.glob("*.yaml"):
|
|
497
|
+
try:
|
|
498
|
+
with open(feature_file) as f:
|
|
499
|
+
data = yaml.safe_load(f)
|
|
500
|
+
if data:
|
|
501
|
+
features.append((feature_file, data))
|
|
502
|
+
except Exception:
|
|
503
|
+
pass
|
|
504
|
+
return features
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@pytest.fixture(scope="module")
|
|
508
|
+
def wmbt_files() -> List[Tuple[Path, Dict[str, Any]]]:
|
|
509
|
+
"""
|
|
510
|
+
Discover all WMBT files in plan/*/.
|
|
511
|
+
|
|
512
|
+
WMBT files match pattern: [DLPCEMYRK]NNN.yaml (e.g., D001.yaml, L010.yaml)
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of (path, wmbt_data) tuples
|
|
516
|
+
"""
|
|
517
|
+
import re
|
|
518
|
+
wmbts = []
|
|
519
|
+
if not PLAN_DIR.exists():
|
|
520
|
+
return wmbts
|
|
521
|
+
|
|
522
|
+
wmbt_pattern = re.compile(r"^[DLPCEMYRK]\d{3}\.yaml$")
|
|
523
|
+
|
|
524
|
+
for wagon_dir in PLAN_DIR.iterdir():
|
|
525
|
+
if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
|
|
526
|
+
for wmbt_file in wagon_dir.glob("*.yaml"):
|
|
527
|
+
if wmbt_pattern.match(wmbt_file.name):
|
|
528
|
+
try:
|
|
529
|
+
with open(wmbt_file) as f:
|
|
530
|
+
data = yaml.safe_load(f)
|
|
531
|
+
if data:
|
|
532
|
+
wmbts.append((wmbt_file, data))
|
|
533
|
+
except Exception:
|
|
534
|
+
pass
|
|
535
|
+
return wmbts
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@pytest.fixture(scope="module")
|
|
539
|
+
def acceptance_urns_by_wagon(wmbt_files: List[Tuple[Path, Dict[str, Any]]]) -> Dict[str, List[str]]:
|
|
540
|
+
"""
|
|
541
|
+
Extract all acceptance URNs grouped by wagon slug.
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Dict mapping wagon slug to list of acceptance URNs
|
|
545
|
+
"""
|
|
546
|
+
by_wagon: Dict[str, List[str]] = {}
|
|
547
|
+
|
|
548
|
+
for path, wmbt_data in wmbt_files:
|
|
549
|
+
# Derive wagon slug from directory name (snake_case -> kebab-case)
|
|
550
|
+
wagon_slug = path.parent.name.replace("_", "-")
|
|
551
|
+
|
|
552
|
+
if wagon_slug not in by_wagon:
|
|
553
|
+
by_wagon[wagon_slug] = []
|
|
554
|
+
|
|
555
|
+
for acc in wmbt_data.get("acceptances", []):
|
|
556
|
+
if isinstance(acc, dict) and "identity" in acc:
|
|
557
|
+
urn = acc["identity"].get("urn", "")
|
|
558
|
+
if urn:
|
|
559
|
+
by_wagon[wagon_slug].append(urn)
|
|
560
|
+
elif isinstance(acc, str):
|
|
561
|
+
by_wagon[wagon_slug].append(acc)
|
|
562
|
+
|
|
563
|
+
return by_wagon
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@pytest.fixture(scope="module")
|
|
567
|
+
def all_acceptance_urns(acceptance_urns_by_wagon: Dict[str, List[str]]) -> List[str]:
|
|
568
|
+
"""
|
|
569
|
+
Get flat list of all acceptance URNs across all wagons.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
List of all acceptance URNs
|
|
573
|
+
"""
|
|
574
|
+
all_urns = []
|
|
575
|
+
for urns in acceptance_urns_by_wagon.values():
|
|
576
|
+
all_urns.extend(urns)
|
|
577
|
+
return all_urns
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
@pytest.fixture(scope="module")
|
|
581
|
+
def wagon_to_train_mapping(train_files: List[Tuple[Path, Dict]]) -> Dict[str, List[str]]:
|
|
582
|
+
"""
|
|
583
|
+
Build mapping of wagon slugs to train IDs that reference them.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Dict mapping wagon slug to list of train IDs
|
|
587
|
+
"""
|
|
588
|
+
mapping: Dict[str, List[str]] = {}
|
|
589
|
+
|
|
590
|
+
for train_path, train_data in train_files:
|
|
591
|
+
train_id = train_data.get("train_id", train_path.stem)
|
|
592
|
+
participants = train_data.get("participants", [])
|
|
593
|
+
|
|
594
|
+
for participant in participants:
|
|
595
|
+
if isinstance(participant, str) and participant.startswith("wagon:"):
|
|
596
|
+
wagon_slug = participant.replace("wagon:", "")
|
|
597
|
+
if wagon_slug not in mapping:
|
|
598
|
+
mapping[wagon_slug] = []
|
|
599
|
+
mapping[wagon_slug].append(train_id)
|
|
600
|
+
|
|
601
|
+
return mapping
|
|
@@ -281,7 +281,7 @@ backend:
|
|
|
281
281
|
|
|
282
282
|
direct_adapter:
|
|
283
283
|
pattern: "direct_*_client.py"
|
|
284
|
-
use_when: "Wagons run in same process (monolith via
|
|
284
|
+
use_when: "Wagons run in same process (monolith via app.py)"
|
|
285
285
|
example: "DirectCommitStateClient reads from shared StateRepository"
|
|
286
286
|
benefits:
|
|
287
287
|
- "No network latency"
|
|
@@ -243,7 +243,7 @@ interaction:
|
|
|
243
243
|
|
|
244
244
|
composition_roots:
|
|
245
245
|
description: |
|
|
246
|
-
Composition roots (composition.py, wagon.py, trains/runner.py,
|
|
246
|
+
Composition roots (composition.py, wagon.py, trains/runner.py, app.py) are entrypoints
|
|
247
247
|
that wire dependencies. They are executed, never imported by other code.
|
|
248
248
|
|
|
249
249
|
feature_composition:
|
|
@@ -332,15 +332,15 @@ interaction:
|
|
|
332
332
|
# ========================================================================
|
|
333
333
|
station_master_pattern:
|
|
334
334
|
description: |
|
|
335
|
-
When multiple wagons run in a single process (
|
|
335
|
+
When multiple wagons run in a single process (app.py), the Station Master
|
|
336
336
|
pattern enables shared dependency injection without HTTP self-calls.
|
|
337
337
|
|
|
338
|
-
|
|
338
|
+
app.py creates shared singletons (StateRepository, EventBus, etc.) and
|
|
339
339
|
passes them to wagon composition.py functions, which decide internally
|
|
340
340
|
whether to use Direct adapters (monolith) or HTTP adapters (microservices).
|
|
341
341
|
|
|
342
342
|
architecture: |
|
|
343
|
-
|
|
343
|
+
app.py (Station Master / Thin Router)
|
|
344
344
|
│
|
|
345
345
|
├── Creates shared singletons:
|
|
346
346
|
│ - StateRepository (commit-state data)
|
|
@@ -391,7 +391,7 @@ interaction:
|
|
|
391
391
|
"""Wire dependencies with optional SharedDependencies.
|
|
392
392
|
|
|
393
393
|
Args:
|
|
394
|
-
shared: SharedDependencies from
|
|
394
|
+
shared: SharedDependencies from app.py (monolith mode).
|
|
395
395
|
When provided, uses Direct adapters for cross-wagon data.
|
|
396
396
|
When None, uses Fake adapters for standalone testing.
|
|
397
397
|
"""
|
|
@@ -424,7 +424,7 @@ interaction:
|
|
|
424
424
|
Same interface (implements Port), different implementation.
|
|
425
425
|
|
|
426
426
|
station_master_responsibilities:
|
|
427
|
-
|
|
427
|
+
app_py:
|
|
428
428
|
- "Create shared singletons (StateRepository, EventBus)"
|
|
429
429
|
- "Pass shared deps to wagon composition.py"
|
|
430
430
|
- "Include wagon routers in FastAPI app"
|
|
@@ -440,12 +440,12 @@ interaction:
|
|
|
440
440
|
required:
|
|
441
441
|
- "composition.py accepts optional shared dependency parameters"
|
|
442
442
|
- "Direct adapters exist for cross-wagon data access"
|
|
443
|
-
- "
|
|
443
|
+
- "app.py calls composition.py, not duplicates wiring"
|
|
444
444
|
|
|
445
445
|
forbidden:
|
|
446
|
-
- "
|
|
446
|
+
- "app.py creating use cases that composition.py should own"
|
|
447
447
|
- "HTTP clients calling localhost in production monolith"
|
|
448
|
-
- "Duplicated wiring logic between
|
|
448
|
+
- "Duplicated wiring logic between app.py and composition.py"
|
|
449
449
|
|
|
450
450
|
forbidden_cross_wagon_imports:
|
|
451
451
|
rule: "Code in wagon A MUST NOT import directly from wagon B"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: "Coder Coverage Convention"
|
|
3
|
+
description: "Section 4 of ATDD Hierarchy Coverage Spec v0.1 - Ensures code implementation covers all features"
|
|
4
|
+
|
|
5
|
+
rules:
|
|
6
|
+
- id: "COVERAGE-CODE-4.1"
|
|
7
|
+
name: "Feature - Implementation Coverage"
|
|
8
|
+
description: "Every feature must have corresponding code implementation"
|
|
9
|
+
requirement: "Each feature's components must map to code paths"
|
|
10
|
+
exceptions: "features_without_implementation allow-list"
|
|
11
|
+
validator: "test_all_features_have_implementations"
|
|
12
|
+
|
|
13
|
+
- id: "COVERAGE-CODE-4.2"
|
|
14
|
+
name: "Implementation - Tests Coverage"
|
|
15
|
+
description: "Every implementation must have at least one linked test"
|
|
16
|
+
requirement: "Each implementation must have at least one linked test"
|
|
17
|
+
validator: "test_all_implementations_have_tests"
|
|
18
|
+
|
|
19
|
+
coverage_graph:
|
|
20
|
+
description: "The coder coverage graph ensures features are implemented and tested"
|
|
21
|
+
levels:
|
|
22
|
+
- name: "Feature"
|
|
23
|
+
implemented_by: "Code files in python/, supabase/, web/"
|
|
24
|
+
validators: ["COVERAGE-CODE-4.1"]
|
|
25
|
+
|
|
26
|
+
- name: "Implementation"
|
|
27
|
+
tested_by: "Test files referencing feature/component"
|
|
28
|
+
validators: ["COVERAGE-CODE-4.2"]
|
|
29
|
+
|
|
30
|
+
exception_handling:
|
|
31
|
+
description: "How exceptions are handled in coder coverage validation"
|
|
32
|
+
allow_lists:
|
|
33
|
+
features_without_implementation:
|
|
34
|
+
description: "Feature URNs that are allowed without code implementation"
|
|
35
|
+
use_case: "Planned features, design-only features, or features implemented externally"
|
|
36
|
+
config_path: "coverage.exceptions.features_without_implementation"
|
|
37
|
+
|
|
38
|
+
implementation_patterns:
|
|
39
|
+
description: "How features map to code implementations"
|
|
40
|
+
|
|
41
|
+
python:
|
|
42
|
+
location: "python/{wagon}/"
|
|
43
|
+
patterns:
|
|
44
|
+
- "use_case_{feature_slug}.py"
|
|
45
|
+
- "service_{feature_slug}.py"
|
|
46
|
+
- "{feature_slug}_handler.py"
|
|
47
|
+
example: "python/maintain_ux/use_case_provide_foundations.py"
|
|
48
|
+
|
|
49
|
+
typescript:
|
|
50
|
+
location: "supabase/functions/{wagon}/{feature}/"
|
|
51
|
+
patterns:
|
|
52
|
+
- "index.ts"
|
|
53
|
+
- "handler.ts"
|
|
54
|
+
- "{feature_slug}.ts"
|
|
55
|
+
example: "supabase/functions/maintain-ux/provide-foundations/index.ts"
|
|
56
|
+
|
|
57
|
+
web:
|
|
58
|
+
location: "web/src/features/{wagon}/{feature}/"
|
|
59
|
+
patterns:
|
|
60
|
+
- "index.tsx"
|
|
61
|
+
- "{feature_slug}.tsx"
|
|
62
|
+
- "components/"
|
|
63
|
+
example: "web/src/features/maintain-ux/provide-foundations/index.tsx"
|
|
64
|
+
|
|
65
|
+
test_patterns:
|
|
66
|
+
description: "How implementations map to tests"
|
|
67
|
+
|
|
68
|
+
python:
|
|
69
|
+
location: "python/{wagon}/"
|
|
70
|
+
filename: "test_*.py"
|
|
71
|
+
reference_method: "Import or reference to implementation module"
|
|
72
|
+
|
|
73
|
+
typescript:
|
|
74
|
+
location: "supabase/functions/{wagon}/{feature}/test/"
|
|
75
|
+
filename: "*.test.ts"
|
|
76
|
+
reference_method: "Import or reference to implementation"
|
|
77
|
+
|
|
78
|
+
web:
|
|
79
|
+
location: "web/tests/{wagon}/{feature}/"
|
|
80
|
+
filename: "*.test.tsx"
|
|
81
|
+
reference_method: "Import or reference to component"
|
|
82
|
+
|
|
83
|
+
rollout:
|
|
84
|
+
phase: "FULL_ENFORCEMENT"
|
|
85
|
+
description: "Section 4 validators become strict in Phase 3 (after planner+tester)"
|
|
@@ -540,25 +540,25 @@ validation:
|
|
|
540
540
|
- "Simple error handling has TODO(REFACTOR) marker"
|
|
541
541
|
|
|
542
542
|
unified_game_server:
|
|
543
|
-
description: "python/
|
|
544
|
-
file: "python/
|
|
543
|
+
description: "python/app.py must aggregate all wagon presentation layers"
|
|
544
|
+
file: "python/app.py"
|
|
545
545
|
requirements:
|
|
546
|
-
- "When new wagon adds FastAPI controller,
|
|
547
|
-
- "
|
|
546
|
+
- "When new wagon adds FastAPI controller, app.py MUST be updated"
|
|
547
|
+
- "app.py imports controller's app and includes via app.include_router()"
|
|
548
548
|
- "All wagon endpoints accessible via unified game server"
|
|
549
|
-
- "Validator checks: all wagons with presentation are registered in
|
|
549
|
+
- "Validator checks: all wagons with presentation are registered in app.py"
|
|
550
550
|
|
|
551
551
|
rationale: |
|
|
552
|
-
|
|
552
|
+
app.py is the unified entry point for all backend services.
|
|
553
553
|
Without registration, wagon endpoints are inaccessible to game server/mobile app.
|
|
554
554
|
|
|
555
555
|
pattern: |
|
|
556
|
-
# python/
|
|
556
|
+
# python/app.py
|
|
557
557
|
from burn_timebank.track_remaining.src.presentation.controllers.remaining_controller import app as timebank_app
|
|
558
558
|
app.include_router(timebank_app.router, prefix="/timebank")
|
|
559
559
|
|
|
560
560
|
anti_pattern: |
|
|
561
|
-
# ❌ WRONG: Wagon has FastAPI controller but not registered in
|
|
561
|
+
# ❌ WRONG: Wagon has FastAPI controller but not registered in app.py
|
|
562
562
|
# Result: Endpoints exist but unreachable from unified server
|
|
563
563
|
|
|
564
564
|
usage: |
|
|
@@ -8,8 +8,8 @@ rationale: |
|
|
|
8
8
|
defined in YAML specifications by coordinating wagons through contract-based communication.
|
|
9
9
|
|
|
10
10
|
TRANSFORMATION (SESSION-12):
|
|
11
|
-
- OLD: Trains in e2e/ (test-only), custom orchestration in
|
|
12
|
-
- NEW: Trains in python/trains/ (production),
|
|
11
|
+
- OLD: Trains in e2e/ (test-only), custom orchestration in app.py
|
|
12
|
+
- NEW: Trains in python/trains/ (production), app.py becomes thin Station Master
|
|
13
13
|
|
|
14
14
|
BENEFITS:
|
|
15
15
|
- Single source of truth (YAML defines both production AND tests)
|
|
@@ -21,7 +21,7 @@ cross_references:
|
|
|
21
21
|
composition_hierarchy:
|
|
22
22
|
- file: "refactor.convention.yaml"
|
|
23
23
|
section: "composition_root.hierarchy"
|
|
24
|
-
note: "Trains add new orchestration layer between
|
|
24
|
+
note: "Trains add new orchestration layer between app.py and wagon.py"
|
|
25
25
|
|
|
26
26
|
boundaries:
|
|
27
27
|
- file: "boundaries.convention.yaml"
|
|
@@ -37,14 +37,15 @@ cross_references:
|
|
|
37
37
|
# ============================================================================
|
|
38
38
|
|
|
39
39
|
composition_hierarchy:
|
|
40
|
-
description: "Trains sit between application (
|
|
40
|
+
description: "Trains sit between application (app.py) and wagons (wagon.py)"
|
|
41
41
|
|
|
42
42
|
levels:
|
|
43
43
|
application:
|
|
44
|
-
file: "python/
|
|
44
|
+
file: "python/app.py"
|
|
45
45
|
role: "Station Master - thin router"
|
|
46
46
|
responsibility: "Map user actions → train_ids, invoke TrainRunner"
|
|
47
47
|
size: "< 50 lines of routing logic"
|
|
48
|
+
note: "app.py is the Station Master entrypoint"
|
|
48
49
|
|
|
49
50
|
train:
|
|
50
51
|
file: "python/trains/runner.py"
|
|
@@ -65,8 +66,8 @@ composition_hierarchy:
|
|
|
65
66
|
|
|
66
67
|
dependency_flow:
|
|
67
68
|
rule: "Dependencies flow downward only"
|
|
68
|
-
chain: "
|
|
69
|
-
forbidden: "NEVER: wagon imports from trains or
|
|
69
|
+
chain: "app.py → trains.runner → wagon.py → composition.py → src/"
|
|
70
|
+
forbidden: "NEVER: wagon imports from trains or app"
|
|
70
71
|
|
|
71
72
|
# ============================================================================
|
|
72
73
|
# TRAIN INFRASTRUCTURE
|
|
@@ -138,7 +139,7 @@ wagon_train_mode:
|
|
|
138
139
|
multi_mode_pattern:
|
|
139
140
|
description: "Wagons support multiple execution modes"
|
|
140
141
|
cli: "Interactive demo (python3 wagon.py)"
|
|
141
|
-
http: "API endpoints (via
|
|
142
|
+
http: "API endpoints (via app.py)"
|
|
142
143
|
train: "Production orchestration (via TrainRunner)"
|
|
143
144
|
|
|
144
145
|
# ============================================================================
|
|
@@ -170,14 +171,14 @@ cargo_pattern:
|
|
|
170
171
|
method: "JSON Schema Draft-07 validation"
|
|
171
172
|
|
|
172
173
|
# ============================================================================
|
|
173
|
-
# STATION MASTER PATTERN (
|
|
174
|
+
# STATION MASTER PATTERN (app.py)
|
|
174
175
|
# ============================================================================
|
|
175
176
|
|
|
176
177
|
station_master:
|
|
177
|
-
description: "
|
|
178
|
+
description: "app.py becomes thin router that delegates to TrainRunner"
|
|
178
179
|
|
|
179
180
|
pattern: |
|
|
180
|
-
#
|
|
181
|
+
# app.py - Station Master
|
|
181
182
|
from trains.runner import TrainRunner
|
|
182
183
|
|
|
183
184
|
JOURNEY_MAP = {
|
|
@@ -198,7 +199,7 @@ station_master:
|
|
|
198
199
|
- "Translate HTTP ↔ artifacts"
|
|
199
200
|
- "Handle errors"
|
|
200
201
|
|
|
201
|
-
anti_pattern: "Business logic in
|
|
202
|
+
anti_pattern: "Business logic in app.py (belongs in trains/wagons)"
|
|
202
203
|
|
|
203
204
|
# ============================================================================
|
|
204
205
|
# TESTING
|
|
@@ -278,10 +279,10 @@ observability:
|
|
|
278
279
|
# ============================================================================
|
|
279
280
|
|
|
280
281
|
migration:
|
|
281
|
-
step_1: "Identify user journeys in
|
|
282
|
+
step_1: "Identify user journeys in app.py"
|
|
282
283
|
step_2: "Create train YAML (plan/_trains/{train_id}.yaml)"
|
|
283
284
|
step_3: "Add run_train() to participating wagons"
|
|
284
|
-
step_4: "Refactor
|
|
285
|
+
step_4: "Refactor app.py to Station Master pattern"
|
|
285
286
|
step_5: "Update tests to use TrainRunner"
|
|
286
287
|
|
|
287
288
|
# ============================================================================
|