atdd 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. atdd/__init__.py +0 -0
  2. atdd/cli.py +404 -0
  3. atdd/coach/__init__.py +0 -0
  4. atdd/coach/commands/__init__.py +0 -0
  5. atdd/coach/commands/add_persistence_metadata.py +215 -0
  6. atdd/coach/commands/analyze_migrations.py +188 -0
  7. atdd/coach/commands/consumers.py +720 -0
  8. atdd/coach/commands/infer_governance_status.py +149 -0
  9. atdd/coach/commands/initializer.py +177 -0
  10. atdd/coach/commands/interface.py +1078 -0
  11. atdd/coach/commands/inventory.py +565 -0
  12. atdd/coach/commands/migration.py +240 -0
  13. atdd/coach/commands/registry.py +1560 -0
  14. atdd/coach/commands/session.py +430 -0
  15. atdd/coach/commands/sync.py +405 -0
  16. atdd/coach/commands/test_interface.py +399 -0
  17. atdd/coach/commands/test_runner.py +141 -0
  18. atdd/coach/commands/tests/__init__.py +1 -0
  19. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  20. atdd/coach/commands/traceability.py +4264 -0
  21. atdd/coach/conventions/session.convention.yaml +754 -0
  22. atdd/coach/overlays/__init__.py +2 -0
  23. atdd/coach/overlays/claude.md +2 -0
  24. atdd/coach/schemas/config.schema.json +34 -0
  25. atdd/coach/schemas/manifest.schema.json +101 -0
  26. atdd/coach/templates/ATDD.md +282 -0
  27. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  28. atdd/coach/utils/__init__.py +0 -0
  29. atdd/coach/utils/graph/__init__.py +0 -0
  30. atdd/coach/utils/graph/urn.py +875 -0
  31. atdd/coach/validators/__init__.py +0 -0
  32. atdd/coach/validators/shared_fixtures.py +365 -0
  33. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  34. atdd/coach/validators/test_registry.py +575 -0
  35. atdd/coach/validators/test_session_validation.py +1183 -0
  36. atdd/coach/validators/test_traceability.py +448 -0
  37. atdd/coach/validators/test_update_feature_paths.py +108 -0
  38. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  39. atdd/coder/__init__.py +1 -0
  40. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  41. atdd/coder/conventions/backend.convention.yaml +460 -0
  42. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  43. atdd/coder/conventions/commons.convention.yaml +460 -0
  44. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  45. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  46. atdd/coder/conventions/design.convention.yaml +327 -0
  47. atdd/coder/conventions/design.recipe.yaml +273 -0
  48. atdd/coder/conventions/dto.convention.yaml +660 -0
  49. atdd/coder/conventions/frontend.convention.yaml +542 -0
  50. atdd/coder/conventions/green.convention.yaml +1012 -0
  51. atdd/coder/conventions/presentation.convention.yaml +587 -0
  52. atdd/coder/conventions/refactor.convention.yaml +535 -0
  53. atdd/coder/conventions/technology.convention.yaml +206 -0
  54. atdd/coder/conventions/tests/__init__.py +0 -0
  55. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  56. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  57. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  58. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  59. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  60. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  61. atdd/coder/conventions/train.convention.yaml +325 -0
  62. atdd/coder/conventions/verification.protocol.yaml +53 -0
  63. atdd/coder/schemas/design_system.schema.json +361 -0
  64. atdd/coder/validators/__init__.py +0 -0
  65. atdd/coder/validators/test_commons_structure.py +485 -0
  66. atdd/coder/validators/test_complexity.py +416 -0
  67. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  68. atdd/coder/validators/test_design_system_compliance.py +413 -0
  69. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  70. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  71. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  72. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  73. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  74. atdd/coder/validators/test_import_boundaries.py +396 -0
  75. atdd/coder/validators/test_init_file_urns.py +593 -0
  76. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  77. atdd/coder/validators/test_presentation_convention.py +260 -0
  78. atdd/coder/validators/test_python_architecture.py +674 -0
  79. atdd/coder/validators/test_quality_metrics.py +420 -0
  80. atdd/coder/validators/test_station_master_pattern.py +244 -0
  81. atdd/coder/validators/test_train_infrastructure.py +454 -0
  82. atdd/coder/validators/test_train_urns.py +293 -0
  83. atdd/coder/validators/test_typescript_architecture.py +616 -0
  84. atdd/coder/validators/test_usecase_structure.py +421 -0
  85. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  86. atdd/conftest.py +126 -0
  87. atdd/planner/__init__.py +1 -0
  88. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  89. atdd/planner/conventions/appendix.convention.yaml +187 -0
  90. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  91. atdd/planner/conventions/component.convention.yaml +670 -0
  92. atdd/planner/conventions/criteria.convention.yaml +141 -0
  93. atdd/planner/conventions/feature.convention.yaml +371 -0
  94. atdd/planner/conventions/interface.convention.yaml +382 -0
  95. atdd/planner/conventions/steps.convention.yaml +141 -0
  96. atdd/planner/conventions/train.convention.yaml +552 -0
  97. atdd/planner/conventions/wagon.convention.yaml +275 -0
  98. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  99. atdd/planner/schemas/acceptance.schema.json +336 -0
  100. atdd/planner/schemas/appendix.schema.json +78 -0
  101. atdd/planner/schemas/component.schema.json +114 -0
  102. atdd/planner/schemas/feature.schema.json +197 -0
  103. atdd/planner/schemas/train.schema.json +192 -0
  104. atdd/planner/schemas/wagon.schema.json +281 -0
  105. atdd/planner/schemas/wmbt.schema.json +59 -0
  106. atdd/planner/validators/__init__.py +0 -0
  107. atdd/planner/validators/conftest.py +5 -0
  108. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  109. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  110. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  111. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  112. atdd/planner/validators/test_plan_wagons.py +174 -0
  113. atdd/planner/validators/test_train_validation.py +514 -0
  114. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  115. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  116. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  117. atdd/tester/__init__.py +1 -0
  118. atdd/tester/conventions/artifact.convention.yaml +257 -0
  119. atdd/tester/conventions/contract.convention.yaml +1009 -0
  120. atdd/tester/conventions/filename.convention.yaml +555 -0
  121. atdd/tester/conventions/migration.convention.yaml +509 -0
  122. atdd/tester/conventions/red.convention.yaml +797 -0
  123. atdd/tester/conventions/routing.convention.yaml +51 -0
  124. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  125. atdd/tester/schemas/a11y.tmpl.json +17 -0
  126. atdd/tester/schemas/artifact.schema.json +189 -0
  127. atdd/tester/schemas/contract.schema.json +591 -0
  128. atdd/tester/schemas/contract.tmpl.json +95 -0
  129. atdd/tester/schemas/db.tmpl.json +20 -0
  130. atdd/tester/schemas/e2e.tmpl.json +17 -0
  131. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  132. atdd/tester/schemas/event.tmpl.json +17 -0
  133. atdd/tester/schemas/http.tmpl.json +19 -0
  134. atdd/tester/schemas/job.tmpl.json +18 -0
  135. atdd/tester/schemas/load.tmpl.json +21 -0
  136. atdd/tester/schemas/metric.tmpl.json +19 -0
  137. atdd/tester/schemas/pack.schema.json +139 -0
  138. atdd/tester/schemas/realtime.tmpl.json +20 -0
  139. atdd/tester/schemas/rls.tmpl.json +18 -0
  140. atdd/tester/schemas/script.tmpl.json +16 -0
  141. atdd/tester/schemas/sec.tmpl.json +18 -0
  142. atdd/tester/schemas/storage.tmpl.json +18 -0
  143. atdd/tester/schemas/telemetry.schema.json +128 -0
  144. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  145. atdd/tester/schemas/test_filename.schema.json +194 -0
  146. atdd/tester/schemas/test_intent.schema.json +179 -0
  147. atdd/tester/schemas/unit.tmpl.json +18 -0
  148. atdd/tester/schemas/visual.tmpl.json +18 -0
  149. atdd/tester/schemas/ws.tmpl.json +17 -0
  150. atdd/tester/utils/__init__.py +0 -0
  151. atdd/tester/utils/filename.py +300 -0
  152. atdd/tester/validators/__init__.py +0 -0
  153. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  154. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  155. atdd/tester/validators/conftest.py +5 -0
  156. atdd/tester/validators/coverage_gap_report.py +321 -0
  157. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  158. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  159. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  160. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  161. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  162. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  163. atdd/tester/validators/test_contracts_structure.py +200 -0
  164. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  165. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  166. atdd/tester/validators/test_fixture_validity.py +372 -0
  167. atdd/tester/validators/test_isolation.py +487 -0
  168. atdd/tester/validators/test_migration_coverage.py +204 -0
  169. atdd/tester/validators/test_migration_criteria.py +276 -0
  170. atdd/tester/validators/test_migration_generation.py +116 -0
  171. atdd/tester/validators/test_python_test_naming.py +410 -0
  172. atdd/tester/validators/test_red_layer_validation.py +95 -0
  173. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  174. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  175. atdd/tester/validators/test_telemetry_structure.py +634 -0
  176. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  177. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  178. atdd-0.1.0.dist-info/METADATA +191 -0
  179. atdd-0.1.0.dist-info/RECORD +183 -0
  180. atdd-0.1.0.dist-info/WHEEL +5 -0
  181. atdd-0.1.0.dist-info/entry_points.txt +2 -0
  182. atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
  183. atdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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()