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,660 @@
1
+ version: "1.0"
2
+ name: "DTO Convention"
3
+ description: "Data Transfer Objects and mapping patterns for wagon boundaries across all languages"
4
+
5
+ purpose: |
6
+ Defines how to implement contract boundaries in code across Python, Dart, TypeScript, and other languages.
7
+
8
+ DTOs are the CODE-SIDE mirror of contract JSON schemas (spec side).
9
+ They enable type-safe wagon-to-wagon communication without coupling wagons together.
10
+
11
+ Key principle: Wagons communicate via contracts (DTOs), never via direct imports of domain entities.
12
+
13
+ relationship:
14
+ mirrors: "contract.convention.yaml (spec side - language-agnostic JSON schemas)"
15
+ enforced_by: "boundaries.convention.yaml (forbidden cross-wagon imports)"
16
+ used_in: "green.convention.yaml (implementation guidance for wagon internals)"
17
+ generated_by: "coder agent (manual first, codegen later)"
18
+
19
+ architecture:
20
+ layers:
21
+ 1_contract_spec:
22
+ what: "JSON Schema files"
23
+ location: "contracts/{theme}/{domain}/{resource}.schema.json"
24
+ authority: "contract.convention.yaml"
25
+ owner: "tester agent"
26
+
27
+ 2_contract_dto:
28
+ what: "Language-specific data classes mirroring schemas"
29
+ location: "python/contracts/, dart/lib/contracts/, ts/contracts/"
30
+ authority: "dto.convention.yaml (this file)"
31
+ owner: "coder agent"
32
+
33
+ 3_domain_model:
34
+ what: "Wagon-internal business logic entities"
35
+ location: "{wagon}/src/domain/entities/"
36
+ authority: "green.convention.yaml"
37
+ owner: "coder agent"
38
+
39
+ 4_mapper:
40
+ what: "DTO ↔ Domain conversion functions"
41
+ location: "{wagon}/src/integration/dto_mapping.{ext}"
42
+ authority: "dto.convention.yaml (mapper_patterns section)"
43
+ owner: "coder agent"
44
+
45
+ flow: |
46
+ Producer Wagon:
47
+ domain_model → mapper.domain_to_dto() → DTO → (serialize to JSON)
48
+
49
+ Contract Boundary:
50
+ JSON payload (validated against contract schema)
51
+
52
+ Consumer Wagon:
53
+ (deserialize from JSON) → DTO → mapper.dto_to_domain() → domain_model
54
+
55
+ # ============================================================================
56
+ # PYTHON
57
+ # ============================================================================
58
+
59
+ languages:
60
+ python:
61
+ version_support: "3.11+"
62
+
63
+ dto_structure:
64
+ base_class: "dataclass from dataclasses"
65
+ immutability: "frozen=True recommended for DTOs"
66
+ location_pattern: "python/contracts/{theme}/{domain}/{resource}.py"
67
+ naming_convention: "{Resource}DTO"
68
+ urn_marker: "# urn: contract:{theme}:{domain}:{resource}.dto"
69
+
70
+ file_header_template: |
71
+ """
72
+ # urn: contract:{theme}:{domain}:{resource}.dto
73
+
74
+ {Resource}DTO - Contract Data Transfer Object
75
+
76
+ Generated from: contracts/{theme}/{domain}/{resource}.schema.json
77
+ Contract: {theme}:{domain}:{resource}
78
+ Version: {version}
79
+
80
+ This DTO is a pure data structure with NO business logic.
81
+ It represents the contract boundary between wagons.
82
+
83
+ Producer: wagon:{producer_wagon}
84
+ Consumers: {consumer_list}
85
+ """
86
+ from dataclasses import dataclass
87
+ from typing import Optional, List, Dict, Any
88
+
89
+ @dataclass(frozen=True)
90
+ class {Resource}DTO:
91
+ """Contract DTO for {theme}:{domain}:{resource}"""
92
+ # fields derived from JSON schema
93
+
94
+ examples:
95
+ simple:
96
+ schema_file: "contracts/match/dilemma/current.schema.json"
97
+ dto_file: "python/contracts/match/dilemma/current.py"
98
+ code: |
99
+ """
100
+ # urn: contract:match:dilemma.current.dto
101
+
102
+ CurrentDilemmaDTO - Contract Data Transfer Object
103
+ """
104
+ from dataclasses import dataclass
105
+ from typing import Optional
106
+
107
+ @dataclass(frozen=True)
108
+ class FragmentDTO:
109
+ """Fragment within a dilemma"""
110
+ id: str
111
+ label: str
112
+ description: str
113
+ kg_subject: str
114
+ domain_attribute: str
115
+
116
+ @dataclass(frozen=True)
117
+ class CurrentDilemmaDTO:
118
+ """Current dilemma for player presentation"""
119
+ id: str
120
+ fragment_a: FragmentDTO
121
+ fragment_b: FragmentDTO
122
+ selection_metadata: Optional[dict] = None
123
+ presentation: Optional[dict] = None
124
+
125
+ type_mapping:
126
+ description: "JSON Schema types → Python types"
127
+ primitives:
128
+ string: "str"
129
+ integer: "int"
130
+ number: "float"
131
+ boolean: "bool"
132
+ "null": "None"
133
+
134
+ containers:
135
+ array: "List[T]"
136
+ object: "Dict[str, Any]"
137
+
138
+ optionality:
139
+ required_field: "field_name: str"
140
+ optional_field: "field_name: Optional[str] = None"
141
+
142
+ format_specific:
143
+ "date-time": "str # ISO 8601 string, not datetime object in DTO"
144
+ uuid: "str # UUID string, not UUID object in DTO"
145
+ uri: "str"
146
+ email: "str"
147
+
148
+ rationale: |
149
+ DTOs use simple types (str, int, float) rather than rich types (datetime, UUID, Decimal)
150
+ because DTOs cross boundaries and must serialize/deserialize cleanly.
151
+ Rich types belong in domain models, not DTOs.
152
+
153
+ mapper_patterns:
154
+ location: "{wagon}/src/integration/dto_mapping.py"
155
+ naming:
156
+ to_domain: "dto_to_domain()"
157
+ to_dto: "domain_to_dto()"
158
+
159
+ file_structure: |
160
+ """
161
+ DTO Mapping - Integration Layer
162
+
163
+ Converts between contract DTOs and wagon domain models.
164
+ """
165
+ from contracts.{theme}.{domain}.{resource} import {Resource}DTO
166
+ from {wagon}.src.domain.entities.{resource} import {Resource}
167
+
168
+ def dto_to_domain(dto: {Resource}DTO) -> {Resource}:
169
+ """
170
+ Convert contract DTO to wagon domain model.
171
+
172
+ This is where you:
173
+ - Parse/validate rich types (str → UUID, str → datetime)
174
+ - Apply domain-specific business rules
175
+ - Enrich with wagon-specific context
176
+
177
+ Args:
178
+ dto: Contract DTO from producer wagon
179
+
180
+ Returns:
181
+ Domain model for this wagon's internal use
182
+ """
183
+ return {Resource}(
184
+ id=UUID(dto.id), # str → UUID
185
+ # ... map fields, apply business logic
186
+ )
187
+
188
+ def domain_to_dto(entity: {Resource}) -> {Resource}DTO:
189
+ """
190
+ Convert wagon domain model to contract DTO.
191
+
192
+ This is where you:
193
+ - Serialize rich types (UUID → str, datetime → str)
194
+ - Project to contract shape (may omit internal fields)
195
+ - Ensure contract compliance
196
+
197
+ Args:
198
+ entity: Domain model from this wagon
199
+
200
+ Returns:
201
+ Contract DTO for consumer wagons
202
+ """
203
+ return {Resource}DTO(
204
+ id=str(entity.id), # UUID → str
205
+ # ... map fields to DTO shape
206
+ )
207
+
208
+ examples:
209
+ full_mapper:
210
+ file: "python/resolve_dilemmas/src/integration/dto_mapping.py"
211
+ code: |
212
+ from uuid import UUID
213
+ from contracts.match.dilemma.current import CurrentDilemmaDTO, FragmentDTO
214
+ from resolve_dilemmas.src.domain.entities.dilemma import Dilemma
215
+ from resolve_dilemmas.src.domain.entities.fragment import Fragment
216
+
217
+ def dto_to_domain(dto: CurrentDilemmaDTO) -> Dilemma:
218
+ """Convert CurrentDilemmaDTO to Dilemma domain model."""
219
+ return Dilemma(
220
+ id=UUID(dto.id),
221
+ fragment_a=fragment_dto_to_domain(dto.fragment_a),
222
+ fragment_b=fragment_dto_to_domain(dto.fragment_b),
223
+ # May add wagon-specific enrichment here
224
+ )
225
+
226
+ def fragment_dto_to_domain(dto: FragmentDTO) -> Fragment:
227
+ """Convert FragmentDTO to Fragment domain model."""
228
+ return Fragment(
229
+ id=UUID(dto.id),
230
+ label=dto.label,
231
+ description=dto.description,
232
+ kg_subject=dto.kg_subject,
233
+ domain_attribute=dto.domain_attribute,
234
+ )
235
+
236
+ def domain_to_dto(entity: Dilemma) -> CurrentDilemmaDTO:
237
+ """Convert Dilemma domain model to CurrentDilemmaDTO."""
238
+ return CurrentDilemmaDTO(
239
+ id=str(entity.id),
240
+ fragment_a=fragment_domain_to_dto(entity.fragment_a),
241
+ fragment_b=fragment_domain_to_dto(entity.fragment_b),
242
+ )
243
+
244
+ def fragment_domain_to_dto(entity: Fragment) -> FragmentDTO:
245
+ """Convert Fragment domain model to FragmentDTO."""
246
+ return FragmentDTO(
247
+ id=str(entity.id),
248
+ label=entity.label,
249
+ description=entity.description,
250
+ kg_subject=entity.kg_subject,
251
+ domain_attribute=entity.domain_attribute,
252
+ )
253
+
254
+ validation:
255
+ dto_validation: "DTOs should be validated against JSON schema after deserialization"
256
+ mapper_validation: "Mappers should validate domain invariants when creating domain models"
257
+ testing: "Each mapper should have unit tests covering both directions (dto→domain, domain→dto)"
258
+
259
+ # ============================================================================
260
+ # DART
261
+ # ============================================================================
262
+
263
+ dart:
264
+ version_support: "3.0+"
265
+
266
+ dto_structure:
267
+ base_class: "none (plain immutable class with const constructor)"
268
+ immutability: "All fields final"
269
+ location_pattern: "dart/lib/contracts/{theme}/{domain}/{resource}.dart"
270
+ naming_convention: "{Resource}Dto"
271
+
272
+ file_header_template: |
273
+ /// # urn: contract:{theme}:{domain}:{resource}.dto
274
+ ///
275
+ /// {Resource}Dto - Contract Data Transfer Object
276
+ ///
277
+ /// Generated from: contracts/{theme}/{domain}/{resource}.schema.json
278
+ /// Contract: {theme}:{domain}:{resource}
279
+ /// Version: {version}
280
+ ///
281
+ /// This DTO is a pure data structure with NO business logic.
282
+ /// It represents the contract boundary between wagons.
283
+
284
+ class {Resource}Dto {
285
+ const {Resource}Dto({
286
+ required this.field1,
287
+ required this.field2,
288
+ });
289
+
290
+ final String field1;
291
+ final int field2;
292
+
293
+ Map<String, dynamic> toJson() => {
294
+ 'field1': field1,
295
+ 'field2': field2,
296
+ };
297
+
298
+ factory {Resource}Dto.fromJson(Map<String, dynamic> json) => {Resource}Dto(
299
+ field1: json['field1'] as String,
300
+ field2: json['field2'] as int,
301
+ );
302
+ }
303
+
304
+ examples:
305
+ simple:
306
+ schema_file: "contracts/match/dilemma/current.schema.json"
307
+ dto_file: "dart/lib/contracts/match/dilemma/current.dart"
308
+ code: |
309
+ /// # urn: contract:match:dilemma.current.dto
310
+
311
+ class FragmentDto {
312
+ const FragmentDto({
313
+ required this.id,
314
+ required this.label,
315
+ required this.description,
316
+ required this.kgSubject,
317
+ required this.domainAttribute,
318
+ });
319
+
320
+ final String id;
321
+ final String label;
322
+ final String description;
323
+ final String kgSubject;
324
+ final String domainAttribute;
325
+
326
+ Map<String, dynamic> toJson() => {
327
+ 'id': id,
328
+ 'label': label,
329
+ 'description': description,
330
+ 'kg_subject': kgSubject,
331
+ 'domain_attribute': domainAttribute,
332
+ };
333
+
334
+ factory FragmentDto.fromJson(Map<String, dynamic> json) => FragmentDto(
335
+ id: json['id'] as String,
336
+ label: json['label'] as String,
337
+ description: json['description'] as String,
338
+ kgSubject: json['kg_subject'] as String,
339
+ domainAttribute: json['domain_attribute'] as String,
340
+ );
341
+ }
342
+
343
+ class CurrentDilemmaDto {
344
+ const CurrentDilemmaDto({
345
+ required this.id,
346
+ required this.fragmentA,
347
+ required this.fragmentB,
348
+ this.selectionMetadata,
349
+ this.presentation,
350
+ });
351
+
352
+ final String id;
353
+ final FragmentDto fragmentA;
354
+ final FragmentDto fragmentB;
355
+ final Map<String, dynamic>? selectionMetadata;
356
+ final Map<String, dynamic>? presentation;
357
+
358
+ Map<String, dynamic> toJson() => {
359
+ 'id': id,
360
+ 'fragment_a': fragmentA.toJson(),
361
+ 'fragment_b': fragmentB.toJson(),
362
+ if (selectionMetadata != null) 'selection_metadata': selectionMetadata,
363
+ if (presentation != null) 'presentation': presentation,
364
+ };
365
+
366
+ factory CurrentDilemmaDto.fromJson(Map<String, dynamic> json) => CurrentDilemmaDto(
367
+ id: json['id'] as String,
368
+ fragmentA: FragmentDto.fromJson(json['fragment_a'] as Map<String, dynamic>),
369
+ fragmentB: FragmentDto.fromJson(json['fragment_b'] as Map<String, dynamic>),
370
+ selectionMetadata: json['selection_metadata'] as Map<String, dynamic>?,
371
+ presentation: json['presentation'] as Map<String, dynamic>?,
372
+ );
373
+ }
374
+
375
+ type_mapping:
376
+ description: "JSON Schema types → Dart types"
377
+ primitives:
378
+ string: "String"
379
+ integer: "int"
380
+ number: "double"
381
+ boolean: "bool"
382
+ "null": "null"
383
+
384
+ containers:
385
+ array: "List<T>"
386
+ object: "Map<String, dynamic>"
387
+
388
+ optionality:
389
+ required_field: "required this.fieldName"
390
+ optional_field: "this.fieldName"
391
+
392
+ format_specific:
393
+ "date-time": "String // ISO 8601 string"
394
+ uuid: "String // UUID string"
395
+ uri: "String"
396
+ email: "String"
397
+
398
+ mapper_patterns:
399
+ location: "{wagon}/lib/integration/dto_mapping.dart"
400
+ naming:
401
+ to_domain: "dtoToDomain()"
402
+ to_dto: "domainToDto()"
403
+
404
+ file_structure: |
405
+ import 'package:contracts/match/dilemma/current.dart';
406
+ import 'package:{wagon}/domain/entities/dilemma.dart';
407
+
408
+ Dilemma dtoToDomain(CurrentDilemmaDto dto) {
409
+ return Dilemma(
410
+ id: dto.id,
411
+ // ... map fields
412
+ );
413
+ }
414
+
415
+ CurrentDilemmaDto domainToDto(Dilemma entity) {
416
+ return CurrentDilemmaDto(
417
+ id: entity.id,
418
+ // ... map fields
419
+ );
420
+ }
421
+
422
+ # ============================================================================
423
+ # TYPESCRIPT
424
+ # ============================================================================
425
+
426
+ typescript:
427
+ version_support: "5.0+"
428
+
429
+ dto_structure:
430
+ base_class: "interface (or type alias)"
431
+ immutability: "readonly properties"
432
+ location_pattern: "ts/contracts/{theme}/{domain}/{resource}.ts"
433
+ naming_convention: "{Resource}DTO"
434
+
435
+ file_header_template: |
436
+ /**
437
+ * # urn: contract:{theme}:{domain}:{resource}.dto
438
+ *
439
+ * {Resource}DTO - Contract Data Transfer Object
440
+ *
441
+ * Generated from: contracts/{theme}/{domain}/{resource}.schema.json
442
+ * Contract: {theme}:{domain}:{resource}
443
+ * Version: {version}
444
+ */
445
+
446
+ export interface {Resource}DTO {
447
+ readonly field1: string;
448
+ readonly field2: number;
449
+ }
450
+
451
+ examples:
452
+ simple:
453
+ schema_file: "contracts/match/dilemma/current.schema.json"
454
+ dto_file: "ts/contracts/match/dilemma/current.ts"
455
+ code: |
456
+ /**
457
+ * # urn: contract:match:dilemma.current.dto
458
+ */
459
+
460
+ export interface FragmentDTO {
461
+ readonly id: string;
462
+ readonly label: string;
463
+ readonly description: string;
464
+ readonly kg_subject: string;
465
+ readonly domain_attribute: string;
466
+ }
467
+
468
+ export interface CurrentDilemmaDTO {
469
+ readonly id: string;
470
+ readonly fragment_a: FragmentDTO;
471
+ readonly fragment_b: FragmentDTO;
472
+ readonly selection_metadata?: Record<string, unknown>;
473
+ readonly presentation?: Record<string, unknown>;
474
+ }
475
+
476
+ type_mapping:
477
+ description: "JSON Schema types → TypeScript types"
478
+ primitives:
479
+ string: "string"
480
+ integer: "number"
481
+ number: "number"
482
+ boolean: "boolean"
483
+ "null": "null"
484
+
485
+ containers:
486
+ array: "Array<T> or T[]"
487
+ object: "Record<string, unknown> or { [key: string]: unknown }"
488
+
489
+ optionality:
490
+ required_field: "fieldName: string"
491
+ optional_field: "fieldName?: string"
492
+
493
+ format_specific:
494
+ "date-time": "string // ISO 8601 string"
495
+ uuid: "string // UUID string"
496
+ uri: "string"
497
+ email: "string"
498
+
499
+ mapper_patterns:
500
+ location: "{wagon}/src/integration/dtoMapping.ts"
501
+ naming:
502
+ to_domain: "dtoToDomain()"
503
+ to_dto: "domainToDto()"
504
+
505
+ file_structure: |
506
+ import type { CurrentDilemmaDTO } from '@contracts/match/dilemma/current';
507
+ import { Dilemma } from '@{wagon}/domain/entities/dilemma';
508
+
509
+ export function dtoToDomain(dto: CurrentDilemmaDTO): Dilemma {
510
+ return new Dilemma({
511
+ id: dto.id,
512
+ // ... map fields
513
+ });
514
+ }
515
+
516
+ export function domainToDto(entity: Dilemma): CurrentDilemmaDTO {
517
+ return {
518
+ id: entity.id,
519
+ // ... map fields
520
+ };
521
+ }
522
+
523
+ # ============================================================================
524
+ # VALIDATION & ENFORCEMENT
525
+ # ============================================================================
526
+
527
+ validation:
528
+ dto_requirements:
529
+ - rule: "DTOs MUST be pure data structures with NO methods (except serialization)"
530
+ violation: "Adding business logic to DTO"
531
+ fix: "Move logic to domain model or mapper"
532
+
533
+ - rule: "DTOs MUST match contract schema exactly (fields, types, optionality)"
534
+ violation: "DTO diverges from schema"
535
+ fix: "Regenerate DTO from schema or update schema"
536
+
537
+ - rule: "DTOs MUST use simple types (str, int, float), not rich types (UUID, datetime)"
538
+ violation: "DTO has UUID field instead of str"
539
+ fix: "Use str in DTO, convert in mapper"
540
+
541
+ - rule: "DTOs MUST be immutable (frozen=True in Python, final in Dart, readonly in TS)"
542
+ violation: "Mutable DTO field"
543
+ fix: "Make all fields immutable"
544
+
545
+ mapper_requirements:
546
+ - rule: "Mappers MUST live in integration layer, never in domain"
547
+ violation: "Mapper in domain/entities/"
548
+ fix: "Move to {wagon}/src/integration/dto_mapping.{ext}"
549
+
550
+ - rule: "Mappers MUST handle both directions (dto→domain, domain→dto)"
551
+ violation: "Only one-way mapper"
552
+ fix: "Implement both conversion functions"
553
+
554
+ - rule: "Mappers MUST validate domain invariants when creating domain models"
555
+ violation: "Mapper creates invalid domain object"
556
+ fix: "Add validation in dto_to_domain()"
557
+
558
+ - rule: "Mappers MUST NOT call other wagons' code"
559
+ violation: "Mapper imports from another wagon"
560
+ fix: "Use composition or pass dependencies"
561
+
562
+ testing_requirements:
563
+ - "Each DTO MUST have serialization round-trip test (toJson/fromJson)"
564
+ - "Each mapper MUST have unit tests for both directions"
565
+ - "Mapper tests MUST verify domain invariants are enforced"
566
+ - "Integration tests MUST verify DTOs match contract schemas"
567
+ - "Integration tests MUST use ID comparison (not object identity) when asserting DTO→Entity conversions"
568
+
569
+ testing_patterns:
570
+ dto_entity_boundary_assertions:
571
+ problem: "After DTO→Entity conversion, object identity (in operator) fails"
572
+ reason: "Mapper creates new entity instances; DTO and Entity are different objects"
573
+
574
+ antipattern:
575
+ code: |
576
+ # ❌ WRONG: Object identity fails after DTO→Entity conversion
577
+ returned_entity = use_case.execute(dto_list)
578
+ assert returned_entity in dto_list # FAILS: different types/instances
579
+
580
+ why_fails: "Python 'in' operator uses __eq__ or identity; Entity ≠ DTO"
581
+
582
+ correct_pattern:
583
+ code: |
584
+ # ✅ CORRECT: Use ID comparison across DTO/Entity boundary
585
+ returned_entity = use_case.execute(dto_list)
586
+ dto_ids = {dto.id for dto in dto_list}
587
+ assert returned_entity.id in dto_ids # PASSES: ID is stable
588
+
589
+ rationale: "IDs are stable across DTO/Entity boundary per contract"
590
+
591
+ when_to_use:
592
+ - "Integration tests where use cases accept DTOs but return entities"
593
+ - "Tests validating that returned data came from input pool"
594
+ - "Pairing/selection algorithms that filter input lists"
595
+
596
+ examples:
597
+ - "Fragment pairing: assert dilemma.fragment_a.id in {f.id for f in fragments}"
598
+ - "Hot pool selection: assert selected.id in {f.id for f in warm_library}"
599
+ - "Domain filtering: assert choice.id in {c.id for c in available_choices}"
600
+
601
+ enforcement:
602
+ test_location: "atdd/coder/test_wagon_boundaries.py"
603
+ rules:
604
+ - "No cross-wagon imports of domain entities (use DTOs)"
605
+ - "No cross-wagon imports of use cases or controllers (never allowed)"
606
+ - "DTOs must live in neutral contracts/ namespace"
607
+ - "Mappers must live in wagon integration/ layer"
608
+
609
+ # ============================================================================
610
+ # GENERATION STRATEGY
611
+ # ============================================================================
612
+
613
+ generation:
614
+ phase_1_manual:
615
+ when: "Initial implementation (now)"
616
+ scope: "First 2-3 contracts (Fragment, Dilemma)"
617
+ process:
618
+ - "Read JSON schema"
619
+ - "Hand-write DTO following patterns above"
620
+ - "Hand-write mapper following patterns above"
621
+ - "Validate with tests"
622
+ goal: "Establish pattern and validate conventions"
623
+
624
+ phase_2_codegen:
625
+ when: "After pattern is validated"
626
+ scope: "Remaining contracts"
627
+ tools:
628
+ - "Custom generator reading contracts/**/*.schema.json"
629
+ - "Emits python/contracts/, dart/lib/contracts/, ts/contracts/"
630
+ - "Idempotent and safe to re-run"
631
+ considerations:
632
+ - "May use datamodel-code-generator as base for Python"
633
+ - "Need custom wrapper for URN comments and naming"
634
+ - "Should respect this convention file"
635
+
636
+ # ============================================================================
637
+ # CROSS-REFERENCES
638
+ # ============================================================================
639
+
640
+ references:
641
+ spec_authority: ".claude/conventions/tester/contract.convention.yaml"
642
+ enforcement: ".claude/conventions/coder/boundaries.convention.yaml"
643
+ implementation: ".claude/conventions/coder/green.convention.yaml"
644
+ testing: "atdd/coder/test_wagon_boundaries.py"
645
+
646
+ cross_convention_rules:
647
+ from_contract_convention:
648
+ - "JSON schema is source of truth for DTO structure"
649
+ - "Contract $id maps to DTO URN"
650
+ - "Contract versioning drives DTO versioning"
651
+
652
+ to_boundaries_convention:
653
+ - "DTOs are the ONLY allowed cross-wagon data type"
654
+ - "Use cases/controllers NEVER cross boundaries"
655
+ - "Qualified imports only (python/contracts/..., never from wagon)"
656
+
657
+ to_green_convention:
658
+ - "Domain models are wagon-internal"
659
+ - "Mappers live in integration layer"
660
+ - "Clean Architecture respected within wagons"