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.
- atdd/__init__.py +0 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.1.0.dist-info/METADATA +191 -0
- atdd-0.1.0.dist-info/RECORD +183 -0
- atdd-0.1.0.dist-info/WHEEL +5 -0
- atdd-0.1.0.dist-info/entry_points.txt +2 -0
- atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
- atdd-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
schema_version: "1.0.0"
|
|
2
|
+
convention_id: "coder.boundaries"
|
|
3
|
+
name: "Wagon Boundary Convention"
|
|
4
|
+
description: |
|
|
5
|
+
Defines how wagons maintain clean boundaries through namespacing (package structure)
|
|
6
|
+
and interaction rules (contracts only, no direct imports).
|
|
7
|
+
|
|
8
|
+
rationale: |
|
|
9
|
+
Wagons are isolated units that must not interfere with each other's internals.
|
|
10
|
+
|
|
11
|
+
ARCHITECTURAL PRINCIPLE (from design.convention.yaml):
|
|
12
|
+
- "Wagons cannot import from other wagons" (VC-DS-06)
|
|
13
|
+
|
|
14
|
+
MECHANISM:
|
|
15
|
+
1. Namespacing: Qualified imports prevent module name collisions
|
|
16
|
+
2. Interaction: Wagons communicate only via contracts, never direct imports
|
|
17
|
+
|
|
18
|
+
Without proper boundaries, wagons with identical layer names (domain/, application/)
|
|
19
|
+
shadow each other's modules, causing import collisions and breaking tests.
|
|
20
|
+
|
|
21
|
+
cross_references:
|
|
22
|
+
architectural_rules:
|
|
23
|
+
- file: "design.convention.yaml"
|
|
24
|
+
rule: "VC-DS-06: No cross-wagon imports"
|
|
25
|
+
note: "This convention provides implementation details for that rule"
|
|
26
|
+
|
|
27
|
+
composition_patterns:
|
|
28
|
+
- file: "green.convention.yaml"
|
|
29
|
+
section: "composition_root"
|
|
30
|
+
note: "Defines composition.py and wagon.py patterns"
|
|
31
|
+
|
|
32
|
+
refactoring_stability:
|
|
33
|
+
- file: "refactor.convention.yaml"
|
|
34
|
+
section: "composition_root"
|
|
35
|
+
note: "Composition roots survive refactoring; only import paths change"
|
|
36
|
+
|
|
37
|
+
wagon_communication:
|
|
38
|
+
- file: "../tester/contract.convention.yaml"
|
|
39
|
+
section: "x-artifact-metadata.producer/consumers"
|
|
40
|
+
note: "Contracts define official wagon interaction interface"
|
|
41
|
+
|
|
42
|
+
layer_definitions:
|
|
43
|
+
- file: "backend.convention.yaml"
|
|
44
|
+
note: "Defines domain/application/integration/presentation layers"
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# SECTION 1: NAMESPACING (How to structure packages and imports)
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
namespacing:
|
|
51
|
+
description: |
|
|
52
|
+
Use qualified imports with full package paths to prevent module collisions.
|
|
53
|
+
Multiple wagons use identical layer names (domain, application, integration),
|
|
54
|
+
so bare imports like "from domain.X import Y" cause shadowing.
|
|
55
|
+
|
|
56
|
+
package_hierarchy:
|
|
57
|
+
description: "Establish Python/Dart/TS package structure for all wagons"
|
|
58
|
+
|
|
59
|
+
required_structure:
|
|
60
|
+
python:
|
|
61
|
+
- path: "python/__init__.py"
|
|
62
|
+
purpose: "Make python/ the package root"
|
|
63
|
+
|
|
64
|
+
- path: "python/{wagon}/__init__.py"
|
|
65
|
+
purpose: "Make wagon a package"
|
|
66
|
+
example: "python/commit_state/__init__.py"
|
|
67
|
+
|
|
68
|
+
- path: "python/{wagon}/{feature}/__init__.py"
|
|
69
|
+
purpose: "Make feature a package"
|
|
70
|
+
example: "python/commit_state/sign_commit/__init__.py"
|
|
71
|
+
|
|
72
|
+
- path: "python/{wagon}/{feature}/src/__init__.py"
|
|
73
|
+
purpose: "Make src a package (usually already exists)"
|
|
74
|
+
note: "Created during GREEN phase with clean architecture"
|
|
75
|
+
|
|
76
|
+
dart:
|
|
77
|
+
- path: "lib/{wagon}/{feature}/src/"
|
|
78
|
+
purpose: "Dart package structure via pubspec.yaml"
|
|
79
|
+
note: "Dart uses package: imports, not __init__.dart files"
|
|
80
|
+
|
|
81
|
+
typescript:
|
|
82
|
+
- path: "supabase/functions/{wagon}/{feature}/src/"
|
|
83
|
+
purpose: "TypeScript module structure"
|
|
84
|
+
note: "Uses path aliases in tsconfig.json"
|
|
85
|
+
|
|
86
|
+
import_patterns:
|
|
87
|
+
description: "Use qualified imports to prevent name collisions"
|
|
88
|
+
|
|
89
|
+
qualified_pattern:
|
|
90
|
+
format: "from {wagon}.{feature}.src.{layer}.{module} import {Class}"
|
|
91
|
+
|
|
92
|
+
examples:
|
|
93
|
+
python_domain:
|
|
94
|
+
import: "from commit_state.sign_commit.src.domain.signature_algorithm import SignatureAlgorithm"
|
|
95
|
+
context: "Test or implementation importing domain entity"
|
|
96
|
+
|
|
97
|
+
python_integration:
|
|
98
|
+
import: "from juggle_domains.score_domains.src.integration.repositories.domain_repository import YamlDomainRepository"
|
|
99
|
+
context: "Test importing repository"
|
|
100
|
+
|
|
101
|
+
dart_domain:
|
|
102
|
+
import: "import 'package:jel/maintain_ux/provide_foundations/src/domain/entities/foundation.dart';"
|
|
103
|
+
context: "Flutter widget importing domain model"
|
|
104
|
+
|
|
105
|
+
typescript_application:
|
|
106
|
+
import: "import { CaptureChoiceUseCase } from '@resolve-dilemmas/capture-decision/src/application/use-cases/capture-choice';"
|
|
107
|
+
context: "Edge function importing use case"
|
|
108
|
+
|
|
109
|
+
forbidden_patterns:
|
|
110
|
+
bare_layer_imports:
|
|
111
|
+
pattern: "from domain.X import Y"
|
|
112
|
+
reason: "Multiple wagons have 'domain' modules; causes shadowing"
|
|
113
|
+
example: "from domain.signature_algorithm import SignatureAlgorithm # ❌ FORBIDDEN"
|
|
114
|
+
|
|
115
|
+
bare_application_imports:
|
|
116
|
+
pattern: "from application.X import Y"
|
|
117
|
+
reason: "Causes module shadowing across wagons"
|
|
118
|
+
example: "from application.use_cases.X import Y # ❌ FORBIDDEN"
|
|
119
|
+
|
|
120
|
+
src_relative_imports:
|
|
121
|
+
pattern: "from src.domain.X import Y"
|
|
122
|
+
reason: "Only works if src/ in sys.path; breaks cross-wagon tests"
|
|
123
|
+
example: "from src.domain.signature_algorithm import SignatureAlgorithm # ❌ FORBIDDEN"
|
|
124
|
+
|
|
125
|
+
syspath_manipulation_in_tests:
|
|
126
|
+
pattern: "sys.path.insert(0, str(src_path))"
|
|
127
|
+
reason: "Causes path collisions; use pytest pythonpath instead"
|
|
128
|
+
location: "test_*.py files"
|
|
129
|
+
example: |
|
|
130
|
+
# ❌ FORBIDDEN in test files
|
|
131
|
+
import sys
|
|
132
|
+
from pathlib import Path
|
|
133
|
+
src_path = Path(__file__).parent.parent / "src"
|
|
134
|
+
sys.path.insert(0, str(src_path))
|
|
135
|
+
|
|
136
|
+
allowed_patterns:
|
|
137
|
+
qualified_imports:
|
|
138
|
+
pattern: "from {wagon}.{feature}.src.{layer}.{module} import {Class}"
|
|
139
|
+
required: true
|
|
140
|
+
example: "from commit_state.sign_commit.src.domain.signature_algorithm import SignatureAlgorithm # ✅ CORRECT"
|
|
141
|
+
|
|
142
|
+
relative_imports_within_layer:
|
|
143
|
+
pattern: "from .{sibling} import {Class}"
|
|
144
|
+
allowed: true
|
|
145
|
+
scope: "Within same directory only"
|
|
146
|
+
example: "from .base_repository import BaseRepository # ✅ OK within same layer"
|
|
147
|
+
note: "Use sparingly; qualified imports preferred for clarity"
|
|
148
|
+
|
|
149
|
+
test_configuration:
|
|
150
|
+
description: "Configure test runners to make package root available"
|
|
151
|
+
|
|
152
|
+
pytest:
|
|
153
|
+
file: "python/pyproject.toml"
|
|
154
|
+
section: "tool.pytest.ini_options"
|
|
155
|
+
setting: "pythonpath = [\".\"]"
|
|
156
|
+
purpose: "Add python/ directory to PYTHONPATH for all tests"
|
|
157
|
+
|
|
158
|
+
example: |
|
|
159
|
+
[tool.pytest.ini_options]
|
|
160
|
+
pythonpath = ["."]
|
|
161
|
+
testpaths = [
|
|
162
|
+
"commit-state/",
|
|
163
|
+
"juggle-domains/",
|
|
164
|
+
]
|
|
165
|
+
python_files = "test_*.py"
|
|
166
|
+
|
|
167
|
+
validation:
|
|
168
|
+
command: "pytest python/commit_state/*/test/ python/juggle_domains/*/test/ -v"
|
|
169
|
+
success_criteria:
|
|
170
|
+
- "All tests from multiple wagons pass when run together"
|
|
171
|
+
- "No ModuleNotFoundError for domain/application/integration"
|
|
172
|
+
- "No module shadowing"
|
|
173
|
+
|
|
174
|
+
dart_flutter:
|
|
175
|
+
file: "pubspec.yaml"
|
|
176
|
+
note: "Dart package resolution automatic via pubspec.yaml"
|
|
177
|
+
|
|
178
|
+
typescript:
|
|
179
|
+
file: "tsconfig.json"
|
|
180
|
+
setting: "paths"
|
|
181
|
+
example: |
|
|
182
|
+
{
|
|
183
|
+
"compilerOptions": {
|
|
184
|
+
"paths": {
|
|
185
|
+
"@resolve-dilemmas/*": ["supabase/functions/resolve_dilemmas/*"],
|
|
186
|
+
"@commit-state/*": ["supabase/functions/commit_state/*"]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
conftest_prohibition:
|
|
192
|
+
description: "Feature-level conftest.py must NOT manipulate sys.path"
|
|
193
|
+
|
|
194
|
+
forbidden:
|
|
195
|
+
- pattern: "sys.path.insert(0, str(feature_src))"
|
|
196
|
+
location: "python/{wagon}/{feature}/test/conftest.py"
|
|
197
|
+
reason: "Causes cross-wagon path collisions"
|
|
198
|
+
|
|
199
|
+
- pattern: "sys.path.append(str(src_path))"
|
|
200
|
+
location: "python/{wagon}/{feature}/test/conftest.py"
|
|
201
|
+
reason: "Causes cross-wagon path collisions"
|
|
202
|
+
|
|
203
|
+
allowed_purpose: "conftest.py should only define fixtures, not manipulate sys.path"
|
|
204
|
+
|
|
205
|
+
correct_example: |
|
|
206
|
+
# python/{wagon}/{feature}/test/conftest.py
|
|
207
|
+
"""Pytest configuration for {feature} tests. Defines feature-specific fixtures."""
|
|
208
|
+
import pytest
|
|
209
|
+
|
|
210
|
+
@pytest.fixture
|
|
211
|
+
def sample_data():
|
|
212
|
+
return {"key": "value"}
|
|
213
|
+
|
|
214
|
+
# NO sys.path manipulation!
|
|
215
|
+
|
|
216
|
+
# ============================================================================
|
|
217
|
+
# SECTION 2: INTERACTION (How wagons communicate)
|
|
218
|
+
# ============================================================================
|
|
219
|
+
|
|
220
|
+
interaction:
|
|
221
|
+
description: |
|
|
222
|
+
Wagons communicate ONLY via contracts, never via direct imports.
|
|
223
|
+
This enforces loose coupling and enables independent evolution.
|
|
224
|
+
|
|
225
|
+
communication_channels:
|
|
226
|
+
contracts_only:
|
|
227
|
+
rule: "Wagons interact exclusively through contract schemas"
|
|
228
|
+
reference: "../tester/contract.convention.yaml"
|
|
229
|
+
mechanism: "producer/consumer artifact declarations in wagon manifests"
|
|
230
|
+
|
|
231
|
+
example: |
|
|
232
|
+
# plan/commit_state/_commit_state.yaml
|
|
233
|
+
produce:
|
|
234
|
+
- artifact: "state:committed-decision"
|
|
235
|
+
contract: "contracts/state/decision/committed.schema.json"
|
|
236
|
+
|
|
237
|
+
# plan/juggle_domains/_juggle_domains.yaml
|
|
238
|
+
consume:
|
|
239
|
+
- artifact: "state:committed-decision"
|
|
240
|
+
contract: "contracts/state/decision/committed.schema.json"
|
|
241
|
+
|
|
242
|
+
validation: "Contracts define producer/consumer relationships in x-artifact-metadata"
|
|
243
|
+
|
|
244
|
+
composition_roots:
|
|
245
|
+
description: |
|
|
246
|
+
Composition roots (composition.py, wagon.py, trains/runner.py, game.py) are entrypoints
|
|
247
|
+
that wire dependencies. They are executed, never imported by other code.
|
|
248
|
+
|
|
249
|
+
feature_composition:
|
|
250
|
+
file: "python/{wagon}/{feature}/composition.py"
|
|
251
|
+
purpose: "Wire dependencies for a single feature"
|
|
252
|
+
|
|
253
|
+
pattern: |
|
|
254
|
+
#!/usr/bin/env python3
|
|
255
|
+
# urn: component:{wagon}:{feature}.composition.backend.infrastructure
|
|
256
|
+
|
|
257
|
+
import sys
|
|
258
|
+
from pathlib import Path
|
|
259
|
+
|
|
260
|
+
# ⚠️ composition.py MAY use sys.path (it's an entrypoint, not imported)
|
|
261
|
+
src_path = Path(__file__).parent / "src"
|
|
262
|
+
sys.path.insert(0, str(src_path))
|
|
263
|
+
|
|
264
|
+
# Can use bare imports within composition.py only
|
|
265
|
+
from domain.signature_algorithm import SignatureAlgorithm
|
|
266
|
+
from application.signature_verifier import SignatureVerifier
|
|
267
|
+
|
|
268
|
+
class SignCommitComposition:
|
|
269
|
+
def __init__(self):
|
|
270
|
+
self.algorithm = SignatureAlgorithm()
|
|
271
|
+
self.verifier = SignatureVerifier(self.algorithm)
|
|
272
|
+
|
|
273
|
+
def main():
|
|
274
|
+
composition = SignCommitComposition()
|
|
275
|
+
composition.run()
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
main()
|
|
279
|
+
|
|
280
|
+
note: "composition.py is executed, never imported; sys.path manipulation allowed here"
|
|
281
|
+
|
|
282
|
+
train_composition:
|
|
283
|
+
file: "python/trains/runner.py"
|
|
284
|
+
purpose: "Orchestrate wagons to execute user journeys (SESSION-12)"
|
|
285
|
+
note: "Production orchestration layer - loads train YAML, calls wagon.run_train()"
|
|
286
|
+
|
|
287
|
+
wagon_composition:
|
|
288
|
+
file: "python/{wagon}/wagon.py"
|
|
289
|
+
purpose: "Orchestrate multiple features within a wagon"
|
|
290
|
+
|
|
291
|
+
pattern: |
|
|
292
|
+
#!/usr/bin/env python3
|
|
293
|
+
# urn: component:{wagon}.wagon.infrastructure
|
|
294
|
+
|
|
295
|
+
import sys
|
|
296
|
+
from pathlib import Path
|
|
297
|
+
|
|
298
|
+
# Import from feature composition roots
|
|
299
|
+
sign_commit_path = Path(__file__).parent / "sign_commit"
|
|
300
|
+
sys.path.insert(0, str(sign_commit_path))
|
|
301
|
+
from composition import SignCommitComposition
|
|
302
|
+
|
|
303
|
+
class CommitStateWagon:
|
|
304
|
+
def __init__(self):
|
|
305
|
+
self.features = {
|
|
306
|
+
'sign-commit': SignCommitComposition(),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def run_all(self):
|
|
310
|
+
for name, feature in self.features.items():
|
|
311
|
+
feature.run()
|
|
312
|
+
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
CommitStateWagon().run_all()
|
|
315
|
+
|
|
316
|
+
note: "wagon.py orchestrates features; allowed to manipulate sys.path"
|
|
317
|
+
|
|
318
|
+
stability_during_refactor:
|
|
319
|
+
rule: "Composition roots are the LAST files to change during refactoring"
|
|
320
|
+
reference: "refactor.convention.yaml::composition_root"
|
|
321
|
+
|
|
322
|
+
approach: |
|
|
323
|
+
During REFACTOR phase:
|
|
324
|
+
1. Refactor domain/application/integration layers (move, rename, split)
|
|
325
|
+
2. Update import paths in composition.py/wagon.py
|
|
326
|
+
3. Composition LOGIC stays unchanged (only import paths change)
|
|
327
|
+
|
|
328
|
+
benefit: "As long as composition.py works, external consumers unaffected"
|
|
329
|
+
|
|
330
|
+
# ========================================================================
|
|
331
|
+
# STATION MASTER PATTERN (Monolith Composition)
|
|
332
|
+
# ========================================================================
|
|
333
|
+
station_master_pattern:
|
|
334
|
+
description: |
|
|
335
|
+
When multiple wagons run in a single process (game.py), the Station Master
|
|
336
|
+
pattern enables shared dependency injection without HTTP self-calls.
|
|
337
|
+
|
|
338
|
+
game.py creates shared singletons (StateRepository, EventBus, etc.) and
|
|
339
|
+
passes them to wagon composition.py functions, which decide internally
|
|
340
|
+
whether to use Direct adapters (monolith) or HTTP adapters (microservices).
|
|
341
|
+
|
|
342
|
+
architecture: |
|
|
343
|
+
game.py (Station Master / Thin Router)
|
|
344
|
+
│
|
|
345
|
+
├── Creates shared singletons:
|
|
346
|
+
│ - StateRepository (commit-state data)
|
|
347
|
+
│ - EventBus (cross-wagon events)
|
|
348
|
+
│ - player_timebanks (burn-timebank data)
|
|
349
|
+
│
|
|
350
|
+
└── Calls: wagon.composition.wire_api_dependencies(
|
|
351
|
+
state_repository=state_repository,
|
|
352
|
+
player_timebanks=player_timebanks,
|
|
353
|
+
match_repository=match_repository
|
|
354
|
+
)
|
|
355
|
+
│
|
|
356
|
+
└── composition.py (Wagon Engine)
|
|
357
|
+
├── When shared deps provided → DirectXXXClient
|
|
358
|
+
├── When shared deps None → FakeXXXClient (testing)
|
|
359
|
+
└── Makes all wiring decisions internally
|
|
360
|
+
|
|
361
|
+
shared_dependencies_class:
|
|
362
|
+
description: "Use SharedDependencies dataclass for clean dependency passing"
|
|
363
|
+
location: "python/commons/composition/shared_dependencies.py"
|
|
364
|
+
|
|
365
|
+
benefits:
|
|
366
|
+
- "Single parameter instead of many individual parameters"
|
|
367
|
+
- "Type-safe with dataclass and Optional type hints"
|
|
368
|
+
- "Self-documenting through field names"
|
|
369
|
+
- "Extensible: add new fields without changing function signatures"
|
|
370
|
+
- "IDE autocomplete support"
|
|
371
|
+
|
|
372
|
+
usage_in_game_py: |
|
|
373
|
+
from commons.composition import SharedDependencies
|
|
374
|
+
|
|
375
|
+
# Create SharedDependencies with all monolith singletons
|
|
376
|
+
shared = SharedDependencies(
|
|
377
|
+
state_repository=state_repository,
|
|
378
|
+
player_timebanks=player_timebanks,
|
|
379
|
+
match_repository=match_repository,
|
|
380
|
+
event_bus=event_bus
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Pass single parameter to wagon composition
|
|
384
|
+
wire_api_dependencies(shared=shared)
|
|
385
|
+
|
|
386
|
+
composition_function_signature:
|
|
387
|
+
description: "Wagon composition.py SHOULD accept SharedDependencies parameter"
|
|
388
|
+
|
|
389
|
+
pattern: |
|
|
390
|
+
def wire_api_dependencies(shared=None):
|
|
391
|
+
"""Wire dependencies with optional SharedDependencies.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
shared: SharedDependencies from game.py (monolith mode).
|
|
395
|
+
When provided, uses Direct adapters for cross-wagon data.
|
|
396
|
+
When None, uses Fake adapters for standalone testing.
|
|
397
|
+
"""
|
|
398
|
+
from commons.composition import SharedDependencies
|
|
399
|
+
shared = shared or SharedDependencies() # Default empty
|
|
400
|
+
|
|
401
|
+
if shared.state_repository is not None:
|
|
402
|
+
commit_client = DirectCommitStateClient(shared.state_repository)
|
|
403
|
+
else:
|
|
404
|
+
commit_client = FakeCommitStateClient()
|
|
405
|
+
|
|
406
|
+
direct_adapter_naming:
|
|
407
|
+
pattern: "Direct{WagonName}Client"
|
|
408
|
+
examples:
|
|
409
|
+
- "DirectCommitStateClient - reads from shared StateRepository"
|
|
410
|
+
- "DirectTimebankClient - reads from shared player_timebanks dict"
|
|
411
|
+
- "DirectJuggleDomainsClient - calls shared score_domain_use_case"
|
|
412
|
+
|
|
413
|
+
location: "python/{wagon}/{feature}/src/integration/clients/direct_*_client.py"
|
|
414
|
+
|
|
415
|
+
why_not_http_self_calls:
|
|
416
|
+
problem: |
|
|
417
|
+
HTTP calls to localhost (http://127.0.0.1:8000) fail in containers:
|
|
418
|
+
- Railway: Container network doesn't route localhost to itself
|
|
419
|
+
- Docker: Container localhost is isolated
|
|
420
|
+
- Kubernetes: Pod localhost is isolated
|
|
421
|
+
|
|
422
|
+
solution: |
|
|
423
|
+
Direct adapters read from shared memory instead of making HTTP calls.
|
|
424
|
+
Same interface (implements Port), different implementation.
|
|
425
|
+
|
|
426
|
+
station_master_responsibilities:
|
|
427
|
+
game_py:
|
|
428
|
+
- "Create shared singletons (StateRepository, EventBus)"
|
|
429
|
+
- "Pass shared deps to wagon composition.py"
|
|
430
|
+
- "Include wagon routers in FastAPI app"
|
|
431
|
+
- "NOT duplicate wagon wiring logic"
|
|
432
|
+
|
|
433
|
+
composition_py:
|
|
434
|
+
- "Accept optional shared dependency parameters"
|
|
435
|
+
- "Decide adapter type based on what's provided"
|
|
436
|
+
- "Own ALL wiring decisions for the wagon"
|
|
437
|
+
- "Export wired components to controllers"
|
|
438
|
+
|
|
439
|
+
validation:
|
|
440
|
+
required:
|
|
441
|
+
- "composition.py accepts optional shared dependency parameters"
|
|
442
|
+
- "Direct adapters exist for cross-wagon data access"
|
|
443
|
+
- "game.py calls composition.py, not duplicates wiring"
|
|
444
|
+
|
|
445
|
+
forbidden:
|
|
446
|
+
- "game.py creating use cases that composition.py should own"
|
|
447
|
+
- "HTTP clients calling localhost in production monolith"
|
|
448
|
+
- "Duplicated wiring logic between game.py and composition.py"
|
|
449
|
+
|
|
450
|
+
forbidden_cross_wagon_imports:
|
|
451
|
+
rule: "Code in wagon A MUST NOT import directly from wagon B"
|
|
452
|
+
reference: "design.convention.yaml::VC-DS-06"
|
|
453
|
+
|
|
454
|
+
examples:
|
|
455
|
+
forbidden:
|
|
456
|
+
- from_wagon: "commit_state"
|
|
457
|
+
to_wagon: "juggle_domains"
|
|
458
|
+
import: "from juggle_domains.score_domains.src.domain.choice import Choice"
|
|
459
|
+
reason: "Direct import creates tight coupling"
|
|
460
|
+
verdict: "❌ FORBIDDEN"
|
|
461
|
+
|
|
462
|
+
- from_wagon: "resolve_dilemmas"
|
|
463
|
+
to_wagon: "commit_state"
|
|
464
|
+
import: "from commit_state.sign_commit.src.application.signature_verifier import SignatureVerifier"
|
|
465
|
+
reason: "Bypasses contract interface"
|
|
466
|
+
verdict: "❌ FORBIDDEN"
|
|
467
|
+
|
|
468
|
+
allowed:
|
|
469
|
+
- from_wagon: "commit_state"
|
|
470
|
+
to_wagon: "juggle_domains"
|
|
471
|
+
mechanism: "Via contract: state:committed-decision"
|
|
472
|
+
approach: "Emit event → contract schema → wagons consume via their own adapters"
|
|
473
|
+
verdict: "✅ CORRECT"
|
|
474
|
+
|
|
475
|
+
# ============================================================================
|
|
476
|
+
# ENFORCEMENT
|
|
477
|
+
# ============================================================================
|
|
478
|
+
|
|
479
|
+
enforcement:
|
|
480
|
+
phase: "GREEN and RED"
|
|
481
|
+
agents:
|
|
482
|
+
- "coder: Implements components with qualified imports"
|
|
483
|
+
- "tester: Creates tests with qualified imports"
|
|
484
|
+
|
|
485
|
+
validation_checklist:
|
|
486
|
+
before_implementation:
|
|
487
|
+
- "Verify package hierarchy exists (__init__.py files at python/, wagon/, feature/)"
|
|
488
|
+
- "Verify pytest pythonpath configured in pyproject.toml"
|
|
489
|
+
- "Verify wagon manifests declare produce/consume contracts"
|
|
490
|
+
|
|
491
|
+
during_implementation:
|
|
492
|
+
- "Use qualified imports: from {wagon}.{feature}.src.{layer}.{module} import Class"
|
|
493
|
+
- "NEVER use bare imports: from domain.X import Y"
|
|
494
|
+
- "NEVER add sys.path manipulation in implementation files"
|
|
495
|
+
- "NEVER import directly from other wagons"
|
|
496
|
+
|
|
497
|
+
during_test_creation:
|
|
498
|
+
- "Use qualified imports in test files"
|
|
499
|
+
- "NEVER add sys.path.insert() in test files"
|
|
500
|
+
- "NEVER add sys.path manipulation in feature-level conftest.py"
|
|
501
|
+
|
|
502
|
+
after_implementation:
|
|
503
|
+
- "Run pytest on multiple wagons together to verify no collisions"
|
|
504
|
+
- "Verify imports work from python/ as package root"
|
|
505
|
+
- "Verify composition.py and wagon.py execute successfully"
|
|
506
|
+
|
|
507
|
+
automated_checks:
|
|
508
|
+
lint_bare_imports:
|
|
509
|
+
command: "grep -r 'from domain\\.' python/*/src/ python/*/test/"
|
|
510
|
+
expected: "No matches (all imports should be qualified)"
|
|
511
|
+
|
|
512
|
+
lint_syspath_in_tests:
|
|
513
|
+
command: "grep -r 'sys.path.insert' python/*/test/*.py"
|
|
514
|
+
expected: "No matches (tests should not manipulate sys.path)"
|
|
515
|
+
|
|
516
|
+
test_cross_wagon:
|
|
517
|
+
command: "pytest python/*/test/ -v"
|
|
518
|
+
expected: "All tests pass when run together across wagons"
|
|
519
|
+
|
|
520
|
+
# ============================================================================
|
|
521
|
+
# TROUBLESHOOTING
|
|
522
|
+
# ============================================================================
|
|
523
|
+
|
|
524
|
+
troubleshooting:
|
|
525
|
+
module_not_found:
|
|
526
|
+
symptom: "ModuleNotFoundError: No module named 'commit_state'"
|
|
527
|
+
causes:
|
|
528
|
+
- "pytest pythonpath not configured"
|
|
529
|
+
- "__init__.py files missing"
|
|
530
|
+
|
|
531
|
+
solution: |
|
|
532
|
+
1. Add pythonpath = ["."] to python/pyproject.toml
|
|
533
|
+
2. Verify __init__.py exists: python/, python/{wagon}/, python/{wagon}/{feature}/
|
|
534
|
+
3. Run pytest from python/ directory
|
|
535
|
+
|
|
536
|
+
module_shadowing:
|
|
537
|
+
symptom: "Tests pass individually but fail together; wrong module imported"
|
|
538
|
+
cause: "Using bare imports (from domain.X) instead of qualified imports"
|
|
539
|
+
|
|
540
|
+
solution: |
|
|
541
|
+
1. Update imports to: from {wagon}.{feature}.src.{layer}.{module} import Class
|
|
542
|
+
2. Remove sys.path manipulation from test files
|
|
543
|
+
3. Remove sys.path manipulation from conftest.py
|
|
544
|
+
|
|
545
|
+
composition_works_tests_fail:
|
|
546
|
+
symptom: "composition.py runs fine but tests can't import"
|
|
547
|
+
cause: "composition.py uses sys.path (allowed) but tests don't use qualified imports"
|
|
548
|
+
|
|
549
|
+
solution: "Tests must use qualified imports; composition.py can use bare imports"
|
|
550
|
+
|
|
551
|
+
# ============================================================================
|
|
552
|
+
# MIGRATION FROM OLD PATTERN
|
|
553
|
+
# ============================================================================
|
|
554
|
+
|
|
555
|
+
migration:
|
|
556
|
+
description: "How to migrate existing code from bare imports to qualified imports"
|
|
557
|
+
|
|
558
|
+
step_1_create_packages:
|
|
559
|
+
action: "Create package hierarchy"
|
|
560
|
+
command: |
|
|
561
|
+
touch python/__init__.py
|
|
562
|
+
find python/*/src -type d | while read src_dir; do
|
|
563
|
+
feature_dir=$(dirname "$src_dir")
|
|
564
|
+
wagon_dir=$(dirname "$feature_dir")
|
|
565
|
+
touch "$wagon_dir/__init__.py"
|
|
566
|
+
touch "$feature_dir/__init__.py"
|
|
567
|
+
done
|
|
568
|
+
|
|
569
|
+
step_2_update_test_imports:
|
|
570
|
+
action: "Replace bare imports with qualified imports"
|
|
571
|
+
example: |
|
|
572
|
+
# For commit_state wagon
|
|
573
|
+
sed -i.bak \
|
|
574
|
+
-e 's|from domain\.|from commit_state.sign_commit.src.domain.|g' \
|
|
575
|
+
-e 's|from application\.|from commit_state.sign_commit.src.application.|g' \
|
|
576
|
+
-e 's|from integration\.|from commit_state.sign_commit.src.integration.|g' \
|
|
577
|
+
python/commit_state/sign_commit/test/test_*.py
|
|
578
|
+
|
|
579
|
+
step_3_remove_syspath:
|
|
580
|
+
action: "Remove sys.path manipulation from test files"
|
|
581
|
+
command: |
|
|
582
|
+
sed -i.bak \
|
|
583
|
+
-e '/^import sys$/d' \
|
|
584
|
+
-e '/^from pathlib import Path$/d' \
|
|
585
|
+
-e '/^src_path = /d' \
|
|
586
|
+
-e '/sys\.path\.insert/d' \
|
|
587
|
+
python/*/test/test_*.py
|
|
588
|
+
|
|
589
|
+
step_4_configure_pytest:
|
|
590
|
+
action: "Add pythonpath to pyproject.toml"
|
|
591
|
+
file: "python/pyproject.toml"
|
|
592
|
+
add: |
|
|
593
|
+
[tool.pytest.ini_options]
|
|
594
|
+
pythonpath = ["."]
|
|
595
|
+
|
|
596
|
+
step_5_validate:
|
|
597
|
+
action: "Verify cross-wagon tests pass"
|
|
598
|
+
command: "pytest python/commit_state/*/test/ python/juggle_domains/*/test/ -v"
|
|
599
|
+
|
|
600
|
+
# ============================================================================
|
|
601
|
+
# EXAMPLES
|
|
602
|
+
# ============================================================================
|
|
603
|
+
|
|
604
|
+
examples:
|
|
605
|
+
test_file_correct:
|
|
606
|
+
path: "python/commit_state/sign_commit/test/test_d001_unit_001_signature_schema_structure.py"
|
|
607
|
+
content: |
|
|
608
|
+
# urn: acc:commit-state:D001-UNIT-001-signature-schema-structure
|
|
609
|
+
# Runtime: python
|
|
610
|
+
|
|
611
|
+
"""Test for Define state commit schema with cryptographic signature fields."""
|
|
612
|
+
|
|
613
|
+
def test_ac_unit_001_signature_schema_structure():
|
|
614
|
+
# ✅ Qualified import
|
|
615
|
+
from commit_state.sign_commit.src.domain.state_commit_schema import StateCommitSchema
|
|
616
|
+
|
|
617
|
+
schema = StateCommitSchema.define()
|
|
618
|
+
assert hasattr(schema, 'decision_id')
|
|
619
|
+
|
|
620
|
+
test_file_incorrect:
|
|
621
|
+
path: "python/commit_state/sign_commit/test/test_WRONG_pattern.py"
|
|
622
|
+
content: |
|
|
623
|
+
# ❌ WRONG - This causes module shadowing
|
|
624
|
+
|
|
625
|
+
import sys
|
|
626
|
+
from pathlib import Path
|
|
627
|
+
|
|
628
|
+
# ❌ sys.path manipulation
|
|
629
|
+
src_path = Path(__file__).parent.parent / "src"
|
|
630
|
+
sys.path.insert(0, str(src_path))
|
|
631
|
+
|
|
632
|
+
def test_wrong_pattern():
|
|
633
|
+
# ❌ Bare import
|
|
634
|
+
from domain.state_commit_schema import StateCommitSchema
|
|
635
|
+
|
|
636
|
+
schema = StateCommitSchema.define()
|
|
637
|
+
|
|
638
|
+
composition_file:
|
|
639
|
+
path: "python/commit_state/sign_commit/composition.py"
|
|
640
|
+
content: |
|
|
641
|
+
#!/usr/bin/env python3
|
|
642
|
+
# urn: component:commit-state:sign-commit.composition.backend.infrastructure
|
|
643
|
+
|
|
644
|
+
"""Composition root for sign-commit feature."""
|
|
645
|
+
|
|
646
|
+
import sys
|
|
647
|
+
from pathlib import Path
|
|
648
|
+
|
|
649
|
+
# ⚠️ Allowed: composition.py is an entrypoint
|
|
650
|
+
src_path = Path(__file__).parent / "src"
|
|
651
|
+
sys.path.insert(0, str(src_path))
|
|
652
|
+
|
|
653
|
+
# Can use bare imports within composition.py
|
|
654
|
+
from domain.signature_algorithm import SignatureAlgorithm
|
|
655
|
+
from application.signature_verifier import SignatureVerifier
|
|
656
|
+
|
|
657
|
+
class SignCommitComposition:
|
|
658
|
+
def __init__(self):
|
|
659
|
+
self.algorithm = SignatureAlgorithm()
|
|
660
|
+
self.verifier = SignatureVerifier(self.algorithm)
|
|
661
|
+
|
|
662
|
+
def run(self):
|
|
663
|
+
print("✅ Sign Commit feature running")
|
|
664
|
+
|
|
665
|
+
if __name__ == "__main__":
|
|
666
|
+
SignCommitComposition().run()
|