atdd 0.6.0__py3-none-any.whl → 0.7.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.
- atdd/coach/schemas/config.schema.json +11 -0
- atdd/coach/templates/ATDD.md +1 -1
- atdd/coach/utils/locale_phase.py +97 -0
- atdd/coach/validators/shared_fixtures.py +49 -0
- atdd/coder/conventions/backend.convention.yaml +1 -1
- atdd/coder/conventions/boundaries.convention.yaml +9 -9
- atdd/coder/conventions/presentation.convention.yaml +8 -8
- atdd/coder/conventions/train.convention.yaml +15 -14
- atdd/coder/validators/test_i18n_runtime.py +171 -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/tester/schemas/locale_manifest.schema.json +53 -0
- atdd/tester/validators/test_locale_coverage.py +451 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/METADATA +1 -1
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/RECORD +21 -17
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/WHEEL +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/entry_points.txt +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ Station Master Pattern Validator
|
|
|
4
4
|
Validates that wagons follow the Station Master pattern for monolith composition:
|
|
5
5
|
1. composition.py accepts optional shared dependency parameters
|
|
6
6
|
2. Direct adapters exist for cross-wagon data access
|
|
7
|
-
3.
|
|
7
|
+
3. app.py delegates to composition.py instead of duplicating wiring
|
|
8
8
|
|
|
9
9
|
Convention: atdd/coder/conventions/boundaries.convention.yaml::station_master_pattern
|
|
10
10
|
"""
|
|
@@ -21,6 +21,9 @@ def get_python_dir() -> Path:
|
|
|
21
21
|
"""Get the python directory path in the consumer repo."""
|
|
22
22
|
return find_repo_root() / "python"
|
|
23
23
|
|
|
24
|
+
def resolve_server_file() -> Path:
|
|
25
|
+
"""Resolve station master entrypoint (app.py)."""
|
|
26
|
+
return get_python_dir() / "app.py"
|
|
24
27
|
|
|
25
28
|
def test_composition_accepts_shared_dependencies():
|
|
26
29
|
"""
|
|
@@ -161,12 +164,12 @@ def test_direct_adapters_exist_for_cross_wagon_clients():
|
|
|
161
164
|
|
|
162
165
|
def test_game_py_delegates_to_composition():
|
|
163
166
|
"""
|
|
164
|
-
Validate that
|
|
167
|
+
Validate that app.py delegates wiring to wagon composition.py files
|
|
165
168
|
instead of duplicating wiring logic.
|
|
166
169
|
|
|
167
170
|
Convention: boundaries.convention.yaml::station_master_pattern.station_master_responsibilities
|
|
168
171
|
|
|
169
|
-
Forbidden patterns in
|
|
172
|
+
Forbidden patterns in app.py:
|
|
170
173
|
- Creating use cases that composition.py should own
|
|
171
174
|
- Directly instantiating wagon clients without delegation
|
|
172
175
|
|
|
@@ -174,14 +177,13 @@ def test_game_py_delegates_to_composition():
|
|
|
174
177
|
- from wagon.composition import wire_api_dependencies
|
|
175
178
|
- wire_api_dependencies(state_repository=..., ...)
|
|
176
179
|
"""
|
|
177
|
-
|
|
178
|
-
game_py = python_dir / "game.py"
|
|
180
|
+
server_file = resolve_server_file()
|
|
179
181
|
|
|
180
|
-
if not
|
|
181
|
-
print("
|
|
182
|
+
if not server_file.exists():
|
|
183
|
+
print("app.py not found - skipping Station Master delegation check")
|
|
182
184
|
return
|
|
183
185
|
|
|
184
|
-
source =
|
|
186
|
+
source = server_file.read_text()
|
|
185
187
|
|
|
186
188
|
# Check for composition imports
|
|
187
189
|
imports_composition = "from play_match.orchestrate_match.composition import wire_api_dependencies" in source
|
|
@@ -190,7 +192,7 @@ def test_game_py_delegates_to_composition():
|
|
|
190
192
|
calls_wire_api = "wire_api_dependencies(" in source
|
|
191
193
|
|
|
192
194
|
# Check for forbidden patterns (duplicated wiring)
|
|
193
|
-
# These are patterns that should be in composition.py, not
|
|
195
|
+
# These are patterns that should be in composition.py, not app.py
|
|
194
196
|
forbidden_patterns = [
|
|
195
197
|
("PlayMatchUseCase(", "PlayMatchUseCase should be created in composition.py"),
|
|
196
198
|
("CommitStateClient(mode=", "CommitStateClient mode should be set in composition.py"),
|
|
@@ -204,7 +206,7 @@ def test_game_py_delegates_to_composition():
|
|
|
204
206
|
|
|
205
207
|
# Report results
|
|
206
208
|
print("\n" + "=" * 70)
|
|
207
|
-
print(" Station Master Pattern:
|
|
209
|
+
print(" Station Master Pattern: app.py Delegation")
|
|
208
210
|
print("=" * 70)
|
|
209
211
|
|
|
210
212
|
print(f"\nDelegation to composition.py:")
|
|
@@ -212,19 +214,19 @@ def test_game_py_delegates_to_composition():
|
|
|
212
214
|
print(f" {'✓' if calls_wire_api else '❌'} Calls wire_api_dependencies()")
|
|
213
215
|
|
|
214
216
|
if violations:
|
|
215
|
-
print(
|
|
217
|
+
print("\n❌ Violations found in app.py:")
|
|
216
218
|
for pattern, message in violations:
|
|
217
219
|
print(f" ❌ {message}")
|
|
218
220
|
print(f" Found: {pattern}")
|
|
219
221
|
|
|
220
222
|
# This is a real validation
|
|
221
223
|
assert imports_composition or not calls_wire_api, \
|
|
222
|
-
"
|
|
224
|
+
"app.py should import wire_api_dependencies from composition.py"
|
|
223
225
|
|
|
224
226
|
assert len(violations) == 0, \
|
|
225
|
-
f"
|
|
227
|
+
f"app.py has {len(violations)} Station Master pattern violations"
|
|
226
228
|
|
|
227
|
-
print("\n✓
|
|
229
|
+
print("\n✓ app.py follows Station Master pattern")
|
|
228
230
|
|
|
229
231
|
|
|
230
232
|
def main():
|
|
@@ -11,7 +11,7 @@ Enforces:
|
|
|
11
11
|
- Wagons implement run_train() for train mode
|
|
12
12
|
- Contract validator is real (not mock)
|
|
13
13
|
- E2E tests use production TrainRunner
|
|
14
|
-
- Station Master pattern in
|
|
14
|
+
- Station Master pattern in app.py
|
|
15
15
|
|
|
16
16
|
Train First-Class Spec v0.6 additions:
|
|
17
17
|
- SPEC-TRAIN-VAL-0031: Backend runner paths
|
|
@@ -45,7 +45,7 @@ from atdd.coach.utils.config import get_train_config
|
|
|
45
45
|
REPO_ROOT = find_repo_root()
|
|
46
46
|
TRAINS_DIR = REPO_ROOT / "python" / "trains"
|
|
47
47
|
WAGONS_DIR = REPO_ROOT / "python"
|
|
48
|
-
|
|
48
|
+
APP_PY = REPO_ROOT / "python" / "app.py"
|
|
49
49
|
E2E_CONFTEST = REPO_ROOT / "e2e" / "conftest.py"
|
|
50
50
|
CONTRACT_VALIDATOR = REPO_ROOT / "e2e" / "shared" / "fixtures" / "contract_validator.py"
|
|
51
51
|
|
|
@@ -110,6 +110,11 @@ def extract_imports_from_file(file_path: Path) -> Set[str]:
|
|
|
110
110
|
return imports
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def resolve_server_file() -> Path:
|
|
114
|
+
"""Resolve station master entrypoint (app.py)."""
|
|
115
|
+
return APP_PY
|
|
116
|
+
|
|
117
|
+
|
|
113
118
|
# ============================================================================
|
|
114
119
|
# TRAIN INFRASTRUCTURE TESTS
|
|
115
120
|
# ============================================================================
|
|
@@ -247,45 +252,48 @@ def test_wagons_implement_run_train():
|
|
|
247
252
|
|
|
248
253
|
|
|
249
254
|
# ============================================================================
|
|
250
|
-
# STATION MASTER TESTS (
|
|
255
|
+
# STATION MASTER TESTS (app.py)
|
|
251
256
|
# ============================================================================
|
|
252
257
|
|
|
253
258
|
def test_game_py_imports_train_runner():
|
|
254
|
-
"""
|
|
255
|
-
|
|
259
|
+
"""app.py must import TrainRunner (Station Master pattern)."""
|
|
260
|
+
server_file = resolve_server_file()
|
|
261
|
+
assert server_file.exists(), f"app.py not found: {server_file}"
|
|
256
262
|
|
|
257
|
-
imports = extract_imports_from_file(
|
|
263
|
+
imports = extract_imports_from_file(server_file)
|
|
258
264
|
|
|
259
265
|
has_train_import = any("trains.runner import TrainRunner" in imp for imp in imports)
|
|
260
266
|
|
|
261
267
|
assert has_train_import, (
|
|
262
|
-
"
|
|
268
|
+
"app.py must import TrainRunner\n"
|
|
263
269
|
"Expected: from trains.runner import TrainRunner\n"
|
|
264
270
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
265
271
|
)
|
|
266
272
|
|
|
267
273
|
|
|
268
274
|
def test_game_py_has_journey_map():
|
|
269
|
-
"""
|
|
270
|
-
|
|
275
|
+
"""app.py must have JOURNEY_MAP routing actions to trains."""
|
|
276
|
+
server_file = resolve_server_file()
|
|
277
|
+
with open(server_file, 'r', encoding='utf-8') as f:
|
|
271
278
|
content = f.read()
|
|
272
279
|
|
|
273
280
|
assert "JOURNEY_MAP" in content, (
|
|
274
|
-
"
|
|
281
|
+
"app.py must define JOURNEY_MAP dictionary\n"
|
|
275
282
|
"Expected: JOURNEY_MAP = {'action': 'train_id', ...}\n"
|
|
276
283
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
277
284
|
)
|
|
278
285
|
|
|
279
286
|
|
|
280
287
|
def test_game_py_has_train_execution_endpoint():
|
|
281
|
-
"""
|
|
282
|
-
|
|
288
|
+
"""app.py must have /trains/execute endpoint."""
|
|
289
|
+
server_file = resolve_server_file()
|
|
290
|
+
with open(server_file, 'r', encoding='utf-8') as f:
|
|
283
291
|
content = f.read()
|
|
284
292
|
|
|
285
293
|
has_endpoint = '"/trains/execute"' in content or "'/trains/execute'" in content
|
|
286
294
|
|
|
287
295
|
assert has_endpoint, (
|
|
288
|
-
"
|
|
296
|
+
"app.py must have /trains/execute endpoint\n"
|
|
289
297
|
"Expected: @app.post('/trains/execute')\n"
|
|
290
298
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
291
299
|
)
|
|
@@ -407,7 +415,7 @@ def test_train_convention_documents_key_patterns():
|
|
|
407
415
|
- composition_hierarchy (with train level)
|
|
408
416
|
- wagon_train_mode (run_train signature)
|
|
409
417
|
- cargo_pattern (artifact flow)
|
|
410
|
-
- station_master (
|
|
418
|
+
- station_master (app.py pattern)
|
|
411
419
|
- testing_pattern (E2E tests)
|
|
412
420
|
"""
|
|
413
421
|
with open(TRAIN_CONVENTION, 'r', encoding='utf-8') as f:
|
|
@@ -78,8 +78,8 @@ def find_implementation_files() -> List[Path]:
|
|
|
78
78
|
# Skip __pycache__
|
|
79
79
|
if '__pycache__' in str(py_file):
|
|
80
80
|
continue
|
|
81
|
-
# Skip wagon.py, composition.py, and
|
|
82
|
-
if py_file.name in ['wagon.py', 'composition.py', '
|
|
81
|
+
# Skip wagon.py, composition.py, and app entrypoint (wagon/app-level orchestration)
|
|
82
|
+
if py_file.name in ['wagon.py', 'composition.py', 'app.py']:
|
|
83
83
|
continue
|
|
84
84
|
# Skip shared/ directory (theme/train-level orchestration - can import across wagons)
|
|
85
85
|
if '/shared/' in str(py_file):
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://atdd.dev/schemas/tester/locale_manifest.schema.json",
|
|
4
|
+
"title": "Locale Manifest",
|
|
5
|
+
"description": "Schema for localization manifest.json - single source of truth for supported locales and namespaces (Localization Manifest Spec v1)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["reference", "locales", "namespaces"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"reference": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"pattern": "^[a-z]{2}(-[A-Z]{2})?$",
|
|
12
|
+
"description": "Reference locale code (e.g., 'en', 'en-US'). All other locales must match this locale's keys."
|
|
13
|
+
},
|
|
14
|
+
"locales": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"minItems": 1,
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"pattern": "^[a-z]{2}(-[A-Z]{2})?$"
|
|
20
|
+
},
|
|
21
|
+
"uniqueItems": true,
|
|
22
|
+
"description": "List of supported locale codes. Must include the reference locale."
|
|
23
|
+
},
|
|
24
|
+
"namespaces": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"minItems": 1,
|
|
27
|
+
"items": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^[a-z][a-z0-9_-]*$"
|
|
30
|
+
},
|
|
31
|
+
"uniqueItems": true,
|
|
32
|
+
"description": "Required namespaces that must exist for all locales (e.g., 'common', 'ui', 'errors')"
|
|
33
|
+
},
|
|
34
|
+
"optional_namespaces": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"pattern": "^[a-z][a-z0-9_-]*$"
|
|
39
|
+
},
|
|
40
|
+
"uniqueItems": true,
|
|
41
|
+
"description": "Optional namespaces that may not exist for all locales. If present, must match reference keys."
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"examples": [
|
|
46
|
+
{
|
|
47
|
+
"reference": "en",
|
|
48
|
+
"locales": ["en", "es", "fr", "de", "ja"],
|
|
49
|
+
"namespaces": ["common", "ui", "errors"],
|
|
50
|
+
"optional_namespaces": ["landing", "marketing"]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|