eva4j 1.0.13 → 1.0.15

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 (106) hide show
  1. package/AGENTS.md +314 -10
  2. package/COMMAND_EVALUATION.md +15 -16
  3. package/DOMAIN_YAML_GUIDE.md +576 -10
  4. package/FUTURE_FEATURES.md +1627 -1168
  5. package/README.md +318 -13
  6. package/bin/eva4j.js +34 -0
  7. package/config/defaults.json +1 -0
  8. package/design-system.md +797 -0
  9. package/docs/commands/EVALUATE_SYSTEM.md +994 -0
  10. package/docs/commands/GENERATE_ENTITIES.md +795 -6
  11. package/docs/commands/INDEX.md +10 -1
  12. package/examples/domain-endpoints-relations.yaml +353 -0
  13. package/examples/domain-endpoints-versioned.yaml +144 -0
  14. package/examples/domain-endpoints.yaml +135 -0
  15. package/examples/domain-events.yaml +166 -20
  16. package/examples/domain-listeners.yaml +212 -0
  17. package/examples/domain-one-to-many.yaml +1 -0
  18. package/examples/domain-one-to-one.yaml +1 -0
  19. package/examples/domain-ports.yaml +414 -0
  20. package/examples/domain-soft-delete.yaml +47 -44
  21. package/examples/system/notification.yaml +147 -0
  22. package/examples/system/product.yaml +185 -0
  23. package/examples/system/system.yaml +112 -0
  24. package/examples/system-report.html +971 -0
  25. package/examples/system.yaml +332 -0
  26. package/package.json +2 -1
  27. package/src/commands/build.js +714 -0
  28. package/src/commands/create.js +7 -3
  29. package/src/commands/detach.js +1 -0
  30. package/src/commands/evaluate-system.js +610 -0
  31. package/src/commands/generate-entities.js +1331 -49
  32. package/src/commands/generate-http-exchange.js +2 -0
  33. package/src/commands/generate-kafka-event.js +98 -11
  34. package/src/generators/base-generator.js +8 -1
  35. package/src/generators/postman-generator.js +188 -0
  36. package/src/generators/shared-generator.js +10 -0
  37. package/src/utils/config-manager.js +54 -0
  38. package/src/utils/context-builder.js +1 -0
  39. package/src/utils/domain-diagram.js +192 -0
  40. package/src/utils/domain-validator.js +970 -0
  41. package/src/utils/fake-data.js +376 -0
  42. package/src/utils/naming.js +3 -2
  43. package/src/utils/system-validator.js +434 -0
  44. package/src/utils/yaml-to-entity.js +302 -8
  45. package/templates/aggregate/AggregateMapper.java.ejs +3 -2
  46. package/templates/aggregate/AggregateRepository.java.ejs +8 -2
  47. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
  48. package/templates/aggregate/AggregateRoot.java.ejs +60 -2
  49. package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
  50. package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
  51. package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
  52. package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
  53. package/templates/aggregate/JpaRepository.java.ejs +5 -0
  54. package/templates/base/gradle/build.gradle.ejs +3 -2
  55. package/templates/base/root/AGENTS.md.ejs +306 -45
  56. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
  57. package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
  58. package/templates/base/root/system.yaml.ejs +97 -0
  59. package/templates/crud/ApplicationMapper.java.ejs +4 -0
  60. package/templates/crud/Controller.java.ejs +4 -4
  61. package/templates/crud/CreateCommand.java.ejs +4 -0
  62. package/templates/crud/CreateItemDto.java.ejs +4 -0
  63. package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
  64. package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
  65. package/templates/crud/EndpointsController.java.ejs +178 -0
  66. package/templates/crud/FindByQuery.java.ejs +17 -0
  67. package/templates/crud/FindByQueryHandler.java.ejs +57 -0
  68. package/templates/crud/ListQuery.java.ejs +1 -1
  69. package/templates/crud/ListQueryHandler.java.ejs +8 -8
  70. package/templates/crud/ScaffoldCommand.java.ejs +12 -0
  71. package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
  72. package/templates/crud/ScaffoldQuery.java.ejs +13 -0
  73. package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
  74. package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
  75. package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
  76. package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
  77. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
  78. package/templates/crud/TransitionCommand.java.ejs +9 -0
  79. package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
  80. package/templates/crud/UpdateCommand.java.ejs +4 -0
  81. package/templates/evaluate/report.html.ejs +1363 -0
  82. package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
  83. package/templates/kafka-event/Event.java.ejs +16 -0
  84. package/templates/kafka-listener/KafkaController.java.ejs +1 -1
  85. package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
  86. package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
  87. package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
  88. package/templates/kafka-listener/ListenerCommandHandler.java.ejs +23 -0
  89. package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
  90. package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
  91. package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
  92. package/templates/mock/MockEvent.java.ejs +10 -0
  93. package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
  94. package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
  95. package/templates/mock/SpringEventListener.java.ejs +61 -0
  96. package/templates/ports/PortDomainModel.java.ejs +35 -0
  97. package/templates/ports/PortFeignAdapter.java.ejs +67 -0
  98. package/templates/ports/PortFeignClient.java.ejs +45 -0
  99. package/templates/ports/PortFeignConfig.java.ejs +24 -0
  100. package/templates/ports/PortInterface.java.ejs +45 -0
  101. package/templates/ports/PortNestedType.java.ejs +28 -0
  102. package/templates/ports/PortRequestDto.java.ejs +30 -0
  103. package/templates/ports/PortResponseDto.java.ejs +28 -0
  104. package/templates/postman/Collection.json.ejs +1 -1
  105. package/templates/postman/UnifiedCollection.json.ejs +185 -0
  106. package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
@@ -0,0 +1,994 @@
1
+ # Command `evaluate system`
2
+
3
+ ---
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Description and purpose](#1-description-and-purpose)
8
+ 2. [Syntax and options](#2-syntax-and-options)
9
+ 3. [system.yaml structure required](#3-systemyaml-structure-required)
10
+ 4. [system.yaml evaluation criteria (S1–S5)](#4-systemyaml-evaluation-criteria-s1s5)
11
+ - [S1 — Module integrity](#s1--module-integrity)
12
+ - [S2 — Async event graph integrity](#s2--async-event-graph-integrity)
13
+ - [S3 — Sync call integrity](#s3--sync-call-integrity)
14
+ - [S4 — Endpoint coherence](#s4--endpoint-coherence)
15
+ - [S5 — Global system coherence](#s5--global-system-coherence)
16
+ 5. [Domain evaluation criteria (C1–C4) — `--domain`](#5-domain-evaluation-criteria-c1c4----domain)
17
+ - [C1 — Kafka event contracts](#c1--kafka-event-contracts)
18
+ - [C2 — Behavior gaps](#c2--behavior-gaps)
19
+ - [C3 — Cross-reference integrity](#c3--cross-reference-integrity)
20
+ - [C4 — Audit & traceability](#c4--audit--traceability)
21
+ 6. [Score calculation](#6-score-calculation)
22
+ 7. [Report output](#7-report-output)
23
+ 8. [Practical examples with real findings](#8-practical-examples-with-real-findings)
24
+ 9. [Common errors and how to fix them](#9-common-errors-and-how-to-fix-them)
25
+
26
+ ---
27
+
28
+ ## 1. Description and purpose
29
+
30
+ `evaluate system` statically analyzes a `system.yaml` file to detect architectural problems in a microservices design **before writing a single line of Java code**.
31
+
32
+ The command is domain-agnostic: it works for any system described in `system.yaml`, whether it's a cinema booking platform, an e-commerce system, a fintech application, or any other domain.
33
+
34
+ **What it produces:**
35
+
36
+ - A **quality score** (0–100%) based on checks passed vs. total
37
+ - A list of **critical errors** (broken references, self-loops, duplicate routes)
38
+ - A list of **warnings** (potential design problems that aren't necessarily wrong)
39
+ - A list of **info notes** (observations that do not affect the score)
40
+ - A list of **passed validations** (proof that good practices are in place)
41
+ - An **interactive HTML report** served on a local HTTP server
42
+ - A **`assets/system-evaluation.md`** file with only errors and warnings for quick review
43
+
44
+ The HTML report contains four interactive tabs:
45
+
46
+ | Tab | Contents |
47
+ |-----|----------|
48
+ | **Validación** | Score cards, collapsible sections for errors / warnings / info / passed |
49
+ | **Simulador de flujos** | Step-by-step playback of each async event flow |
50
+ | **Arquitectura** | Module dependency explorer, sync dependency cards, Kafka topic map, interactive network diagram |
51
+ | **Dominio** | Per-module Mermaid diagrams (only with `--domain` flag) |
52
+
53
+ ---
54
+
55
+ ## 2. Syntax and options
56
+
57
+ ```bash
58
+ eva evaluate system
59
+ eva evaluate system --port 8080 # serve the report on a custom port (default: 3000)
60
+ eva evaluate system --output ./report.html # write HTML to a custom path
61
+ eva evaluate system --domain # also validate domain.yaml files in system/
62
+ ```
63
+
64
+ ### Options
65
+
66
+ | Option | Default | Description |
67
+ |--------|---------|-------------|
68
+ | `--port <n>` | `3000` | Port for the local HTTP preview server |
69
+ | `--output <path>` | `./system-report.html` | Where to write the generated HTML file |
70
+ | `--domain` | off | Also load and cross-validate domain YAML files from `system/` |
71
+
72
+ ### Requirements
73
+
74
+ - Must be run from a directory containing a `system/system.yaml` file
75
+ - No eva4j project scaffold is required — `system.yaml` can be a standalone design file
76
+
77
+ ---
78
+
79
+ ## 3. system.yaml structure required
80
+
81
+ The minimal structure the evaluator expects:
82
+
83
+ ```yaml
84
+ system:
85
+ name: my-system # used as report title
86
+
87
+ messaging:
88
+ enabled: true
89
+ broker: kafka
90
+ kafka:
91
+ topicPrefix: myapp # used by S2-007 topic prefix check
92
+
93
+ modules:
94
+ - name: orders # unique module identifier
95
+ description: "..." # required by S1-003
96
+ exposes: # REST endpoints this module offers
97
+ - method: POST
98
+ path: /orders
99
+ useCase: CreateOrder
100
+ description: "..." # required by S4-004
101
+ - method: GET
102
+ path: /orders/{id}
103
+ useCase: GetOrder
104
+ description: "..."
105
+
106
+ integrations:
107
+ async:
108
+ - event: OrderCreatedEvent # must follow PascalCase + Event suffix (S2-006)
109
+ producer: orders
110
+ topic: ORDER_CREATED
111
+ consumers:
112
+ - module: payments
113
+ useCase: HandleOrderCreated
114
+ - module: notifications
115
+ useCase: NotifyOrderCreated
116
+
117
+ sync:
118
+ - caller: payments
119
+ calls: orders
120
+ port: OrderService
121
+ using:
122
+ - GET /orders/{id} # must exist in orders.exposes[] (S3-002)
123
+ ```
124
+
125
+ All fields are optional except `modules[].name`. The evaluator gracefully handles missing sections.
126
+
127
+ ---
128
+
129
+ ## 4. system.yaml evaluation criteria (S1–S5)
130
+
131
+ These rules evaluate **only** `system.yaml`. For the domain-level criteria applied when using `--domain`, see [section 5](#5-domain-evaluation-criteria-c1c4----domain).
132
+
133
+ The evaluator runs **5 rule groups (S1–S5)** with a total of **20 rules** across three severity levels:
134
+
135
+ | Severity | Symbol | Affects score | Description |
136
+ |----------|--------|--------------|-------------|
137
+ | **error** | 🔴 | Yes (counts as 1) | Must be fixed |
138
+ | **warning** | 🟡 | Yes (counts as 0.5) | Should be reviewed |
139
+ | **info** | 🔵 | No | Observation only |
140
+
141
+ ---
142
+
143
+ ### S1 — Module integrity
144
+
145
+ Verifies that all modules declared in `modules[]` have defined responsibilities and all modules referenced in `integrations` are declared.
146
+
147
+ | Rule | Severity | Description |
148
+ |------|----------|-------------|
149
+ | S1-001 | 🔴 error | Module referenced in `integrations` but not declared in `modules[]` |
150
+ | S1-002 | 🔴 error | Module with no responsibilities — no exposes, no events produced or consumed |
151
+ | S1-003 | 🟡 warning | Module without a `description` field |
152
+ | S1-004 | 🟡 warning | Purely reactive module (only consumes events) not documented explicitly in its description |
153
+
154
+ #### S1-001 — Undeclared module referenced in integrations
155
+
156
+ Covers producers, consumers, sync callers, and sync callees.
157
+
158
+ ```yaml
159
+ # ❌ ERROR — 'billing' is not declared in modules[]
160
+ - event: InvoiceCreatedEvent
161
+ producer: billing
162
+ ```
163
+
164
+ **Message:** `[S1-001] Módulo 'billing' referenciado en integrations pero no declarado en modules[]`
165
+
166
+ **Fix:** Add the missing module to `modules[]` or correct the name typo.
167
+
168
+ #### S1-002 — Module with no responsibilities
169
+
170
+ A module that doesn't expose endpoints, doesn't produce events, and doesn't consume events serves no purpose in the design.
171
+
172
+ **Message:** `[S1-002] Módulo 'reporting' no tiene ninguna responsabilidad — no expone endpoints, no produce ni consume eventos`
173
+
174
+ **Fix:** Add endpoints to `exposes[]`, connect it to an async event, or remove the module.
175
+
176
+ #### S1-003 — Module without description
177
+
178
+ **Message:** `[S1-003] Módulo 'payments' no tiene campo description declarado`
179
+
180
+ **Fix:** Add a `description` field summarizing the module's responsibility.
181
+
182
+ #### S1-004 — Purely reactive module not documented
183
+
184
+ A module that only consumes events (no REST endpoints, no events produced) should say so explicitly in its description.
185
+
186
+ **Message:** `[S1-004] Módulo 'notifications' es puramente reactivo (solo consume eventos) pero su description no lo documenta explícitamente`
187
+
188
+ **Fix:** Add words like "consumes", "reacts to", or "event-driven" to the description.
189
+
190
+ ---
191
+
192
+ ### S2 — Async event graph integrity
193
+
194
+ Verifies that the producer → consumer graph declared in `integrations.async` is coherent: no orphan events, no topic collisions, no self-loops.
195
+
196
+ | Rule | Severity | Description |
197
+ |------|----------|-------------|
198
+ | S2-001 | 🔴 error | Event declared in `integrations.async` with no consumers |
199
+ | S2-002 | 🔴 error | Same `topic` value declared for two different events |
200
+ | S2-003 | 🔴 error | Module listed as consumer of its own event (self-loop) |
201
+ | S2-004 | 🟡 warning | Module that produces events but consumes none |
202
+ | S2-005 | 🟡 warning | Module that consumes events but produces none |
203
+ | S2-006 | 🟡 warning | Event name not following PascalCase + `Event` suffix convention |
204
+ | S2-007 | 🔵 info | Topic name does not include the prefix declared in `messaging.kafka.topicPrefix` |
205
+
206
+ #### S2-001 — Event with no consumers
207
+
208
+ ```yaml
209
+ # ❌ ERROR — no consumers declared
210
+ - event: OrderShippedEvent
211
+ producer: shipping
212
+ topic: ORDER_SHIPPED
213
+ consumers: []
214
+ ```
215
+
216
+ **Message:** `[S2-001] Evento 'OrderShippedEvent' declarado en integrations.async sin consumidores`
217
+
218
+ #### S2-002 — Duplicate topic value
219
+
220
+ ```yaml
221
+ # ❌ ERROR — both events share the same topic
222
+ - event: OrderCreatedEvent
223
+ topic: ORDER_EVENTS
224
+ - event: OrderCancelledEvent
225
+ topic: ORDER_EVENTS # ← collision
226
+ ```
227
+
228
+ **Message:** `[S2-002] Topic 'ORDER_EVENTS' está declarado para dos eventos distintos: 'OrderCreatedEvent' y 'OrderCancelledEvent'`
229
+
230
+ #### S2-003 — Self-loop (module consuming its own event)
231
+
232
+ ```yaml
233
+ # ❌ ERROR — orders producing AND consuming its own event
234
+ - event: OrderCreatedEvent
235
+ producer: orders
236
+ consumers:
237
+ - module: orders # ← self-loop
238
+ ```
239
+
240
+ **Message:** `[S2-003] Módulo 'orders' está listado como consumidor de su propio evento 'OrderCreatedEvent' (self-loop)`
241
+
242
+ #### S2-004/S2-005 — Unbalanced producer/consumer roles
243
+
244
+ - `[S2-004]` — Module produces events but never consumes any (may be intentional)
245
+ - `[S2-005]` — Module consumes events but never produces any (may be intentional for sinks like notifications)
246
+
247
+ #### S2-006 — Event name convention
248
+
249
+ Event names must follow `PascalCase` with an `Event` suffix.
250
+
251
+ **Examples of violations:** `orderCreated`, `ORDER_CREATED`, `OrderCreated` (missing `Event`), `Order_Created_Event`
252
+
253
+ **Message:** `[S2-006] Nombre de evento 'orderCreated' no sigue la convención PascalCase con sufijo 'Event'`
254
+
255
+ #### S2-007 — Topic without configured prefix (info)
256
+
257
+ When `messaging.kafka.topicPrefix` is declared, every topic name should include it for consistency.
258
+
259
+ **Message:** `[S2-007] Topic 'ORDER_CREATED' (evento 'OrderCreatedEvent') no incluye el prefijo configurado 'myapp'`
260
+
261
+ ---
262
+
263
+ ### S3 — Sync call integrity
264
+
265
+ Verifies that all synchronous dependencies declared in `integrations.sync` reference existing modules and endpoints, and do not generate circular or excessive coupling.
266
+
267
+ | Rule | Severity | Description |
268
+ |------|----------|-------------|
269
+ | S3-001 | 🔴 error | Sync call to a module that declares no `exposes[]` |
270
+ | S3-002 | 🔴 error | Path in `sync[].using[]` does not exist in target module's `exposes[]` |
271
+ | S3-003 | 🟡 warning | Bidirectional sync coupling — A calls B and B calls A |
272
+ | S3-004 | 🟡 warning | Module with more than 3 distinct outgoing sync dependencies |
273
+ | S3-005 | 🔵 info | Module consulted synchronously but emits no events when its state changes |
274
+
275
+ #### S3-001 — Sync call to module without endpoints
276
+
277
+ ```yaml
278
+ # ❌ ERROR — 'notifications' has no exposes[]
279
+ sync:
280
+ - caller: orders
281
+ calls: notifications
282
+ using:
283
+ - POST /notifications
284
+ ```
285
+
286
+ **Message:** `[S3-001] 'orders' llama síncronamente a 'notifications' pero este módulo no declara exposes[]`
287
+
288
+ #### S3-002 — Endpoint not declared in target module
289
+
290
+ ```yaml
291
+ sync:
292
+ - caller: payments
293
+ calls: orders
294
+ using:
295
+ - GET /orders/{id}/items # ❌ not in orders.exposes[]
296
+ ```
297
+
298
+ **Message:** `[S3-002] Endpoint 'GET /orders/{id}/items' usado por 'payments' no está declarado en exposes[] de 'orders'`
299
+
300
+ **Fix:** Add the endpoint to `orders.exposes[]` or remove it from `using[]`.
301
+
302
+ #### S3-003 — Bidirectional sync coupling
303
+
304
+ ```yaml
305
+ # ❌ WARNING — A↔B mutual sync dependency
306
+ sync:
307
+ - caller: orders
308
+ calls: inventory
309
+ - caller: inventory
310
+ calls: orders
311
+ ```
312
+
313
+ **Message:** `[S3-003] Acoplamiento síncrono bidireccional: 'orders' llama a 'inventory' y viceversa`
314
+
315
+ **Fix:** Replace one direction with an async event, or extract the shared data into a third read-model module.
316
+
317
+ #### S3-004 — Too many outgoing sync dependencies
318
+
319
+ A module calling more than 3 distinct modules synchronously is tightly coupled and fragile under partial failures.
320
+
321
+ **Message:** `[S3-004] Módulo 'reservations' tiene 4 dependencias síncronas salientes distintas (>3): screenings, customers, payments, inventory`
322
+
323
+ #### S3-005 — Module consulted synchronously but emits no events (info)
324
+
325
+ When other modules depend synchronously on a module but that module never publishes events, downstream consumers have no way to react to its state changes.
326
+
327
+ **Message:** `[S3-005] Módulo 'movies' es consultado síncronamente pero no emite ningún evento cuando su estado cambia`
328
+
329
+ ---
330
+
331
+ ### S4 — Endpoint coherence
332
+
333
+ Verifies that endpoints declared in `modules[].exposes[]` are internally coherent: no route collisions, complete operation pairs, minimal documentation.
334
+
335
+ | Rule | Severity | Description |
336
+ |------|----------|-------------|
337
+ | S4-001 | 🔴 error | Two endpoints with the same HTTP method and path in the same module |
338
+ | S4-002 | 🟡 warning | Module with `PUT /{id}` but no `GET /{id}` for the same resource |
339
+ | S4-003 | 🟡 warning | `DELETE` endpoint exposed without a description indicating physical vs. logical deletion |
340
+ | S4-004 | 🔵 info | Endpoint without a `description` field |
341
+ | S4-005 | 🔵 info | Module with a `POST` creation endpoint but no `GET /{id}` to retrieve the created resource |
342
+
343
+ #### S4-001 — Duplicate route
344
+
345
+ ```yaml
346
+ exposes:
347
+ - method: GET
348
+ path: /orders/{id}
349
+ useCase: GetOrder
350
+ - method: GET
351
+ path: /orders/{id} # ❌ duplicate
352
+ useCase: GetOrderDetail
353
+ ```
354
+
355
+ **Message:** `[S4-001] Módulo 'orders' tiene dos endpoints con el mismo método y path: GET /orders/{id}`
356
+
357
+ #### S4-002 — PUT without GET for same resource
358
+
359
+ ```yaml
360
+ exposes:
361
+ - method: PUT
362
+ path: /orders/{id}
363
+ useCase: UpdateOrder
364
+ # ❌ no GET /orders/{id} declared
365
+ ```
366
+
367
+ **Message:** `[S4-002] Módulo 'orders' tiene PUT /orders/{id} sin el correspondiente GET /orders/{id}`
368
+
369
+ #### S4-003 — DELETE without description
370
+
371
+ ```yaml
372
+ exposes:
373
+ - method: DELETE
374
+ path: /products/{id}
375
+ useCase: DeleteProduct
376
+ # ❌ no description — is this physical or soft delete?
377
+ ```
378
+
379
+ **Message:** `[S4-003] Endpoint DELETE /products/{id} en 'products' no tiene description que indique si el borrado es físico o lógico`
380
+
381
+ **Fix:** Add a description: `"Eliminación lógica: marca el producto como inactivo (soft delete)"` or `"Eliminación física del registro de base de datos"`.
382
+
383
+ #### S4-004 — Endpoint without description (info)
384
+
385
+ **Message:** `[S4-004] Endpoint POST /orders en 'orders' no tiene campo description`
386
+
387
+ #### S4-005 — POST without GET /{id} (info)
388
+
389
+ **Message:** `[S4-005] Módulo 'shipments' tiene POST de creación pero no declara GET /{id} para recuperar el recurso creado`
390
+
391
+ ---
392
+
393
+ ### S5 — Global system coherence
394
+
395
+ Verifies properties that can only be evaluated by observing the entire system: contradictions between configuration and declarations, flows without failure coverage, and disconnected modules.
396
+
397
+ | Rule | Severity | Description |
398
+ |------|----------|-------------|
399
+ | S5-001 | 🟡 warning | `messaging.enabled: false` with async events declared in `integrations.async` |
400
+ | S5-002 | 🟡 warning | Critical business flow with a success event but no corresponding failure event for compensation |
401
+ | S5-003 | 🔵 info | Module handling authentication with no declared integration with any other module |
402
+ | S5-004 | 🔵 info | Module with no connection to the system graph — neither async nor sync |
403
+
404
+ #### S5-001 — Messaging disabled but events declared
405
+
406
+ ```yaml
407
+ messaging:
408
+ enabled: false # ❌ contradicts async events below
409
+
410
+ integrations:
411
+ async:
412
+ - event: OrderCreatedEvent
413
+
414
+ ```
415
+
416
+ **Message:** `[S5-001] messaging.enabled está en false pero hay 3 eventos declarados en integrations.async`
417
+
418
+ #### S5-002 — Success event without matching failure event
419
+
420
+ When a flow has a success event (`*ConfirmedEvent`, `*ApprovedEvent`, `*PlacedEvent`, `*CompletedEvent`), there should be a failure/compensation event for the same subject so consumers can react to the unhappy path.
421
+
422
+ ```yaml
423
+ # ⚠️ WARNING — success exists but no failure counterpart
424
+ - event: PaymentApprovedEvent # ✅ success
425
+ producer: payments
426
+
427
+ # If PaymentRejectedEvent or PaymentFailedEvent were missing → S5-002 fires
428
+ ```
429
+
430
+ **Message:** `[S5-002] Evento de éxito 'PaymentApprovedEvent' existe pero no hay un evento de fallo correspondiente para el sujeto 'payment' que permita compensación`
431
+
432
+ #### S5-003 — Auth module without integrations (info)
433
+
434
+ Modules whose names match `auth`, `security`, `identity`, or `session` that have no declared integrations may be siloed or forgotten.
435
+
436
+ **Message:** `[S5-003] Módulo 'auth' parece manejar autenticación/seguridad pero no tiene ninguna integración declarada con otros módulos`
437
+
438
+ #### S5-004 — Isolated module (info)
439
+
440
+ A module with no async events (produced or consumed) and no sync calls (as caller or callee) is completely disconnected from the system graph.
441
+
442
+ **Message:** `[S5-004] Módulo 'reporting' no tiene ninguna conexión al grafo del sistema — ni async ni sync`
443
+
444
+ ---
445
+
446
+ ## 5. Domain evaluation criteria (--domain)
447
+
448
+ When `--domain` is passed, the evaluator additionally loads a `domain.yaml` file from each module subdirectory under `system/` and cross-validates them against each other and against `system.yaml`.
449
+
450
+ **Requirements:**
451
+ - Each module subdirectory under `system/` must contain a `domain.yaml` file to be included
452
+ - Modules without a `domain.yaml` are silently skipped — their rules will not fire
453
+ - Domain findings appear in the **Dominio** tab of the HTML report
454
+
455
+ Domain rules are organized in **4 rule groups (C1–C4)** with a total of **19 rules**:
456
+
457
+ | Severity | Symbol | Affects score | Description |
458
+ |----------|--------|--------------|-------------|
459
+ | **error** | 🔴 | Yes (counts as 1) | Must be fixed |
460
+ | **warning** | 🟡 | Yes (counts as 0.5) | Should be reviewed |
461
+ | **info** | 🔵 | No | Observation only |
462
+
463
+ ---
464
+
465
+ ### C1 — Kafka event contracts
466
+
467
+ Verifies that the producer → consumer graph is coherent at the code level: events declared in `domain.yaml` match what `system.yaml` declares, and field-level contracts are consistent between producers and consumers.
468
+
469
+ | Rule | Severity | Description |
470
+ |------|----------|-------------|
471
+ | C1-001 | 🟡 warning | Domain event produced by a module but has no consumers registered in `system.yaml` |
472
+ | C1-002 | 🔴 error | `listeners[]` references an event that no domain module produces |
473
+ | C1-003 | 🔴 error | Field in `listener.fields` does not exist in the producer event |
474
+ | C1-004 | 🔴 error | Field exists in both producer and consumer but with incompatible types |
475
+ | C1-005 | 🔴 error | `system.yaml` registers a module as consumer but that module has no matching `listener` |
476
+ | C1-006 | 🔴 error | `listener.producer` references the wrong producer module |
477
+
478
+ #### C1-001 — Produced event with no consumers in system.yaml
479
+
480
+ ```yaml
481
+ # orders/domain.yaml
482
+ aggregates:
483
+ - name: Order
484
+ events:
485
+ - name: OrderConfirmed # ⚠️ WARNING — not registered in system.yaml
486
+ fields: [...]
487
+ ```
488
+
489
+ **Message:** `[C1-001] El evento 'OrderConfirmed' no tiene consumidores registrados en system.yaml`
490
+
491
+ **Fix:** Add the event to `integrations.async` in `system.yaml` with at least one consumer, or remove it from `domain.yaml` if unused.
492
+
493
+ #### C1-002 — Listener references event no module produces
494
+
495
+ ```yaml
496
+ # notifications/domain.yaml
497
+ listeners:
498
+ - event: StockDepletedEvent # ❌ ERROR — no domain.yaml in the system produces this
499
+ producer: inventory
500
+ ```
501
+
502
+ **Message:** `[C1-002] Listener de 'StockDepletedEvent' pero ningún módulo en los domain.yaml lo produce`
503
+
504
+ **Fix:** Declare `StockDepletedEvent` in the `inventory` module's `domain.yaml`, or correct the event name.
505
+
506
+ #### C1-003 — Field in listener missing in producer event
507
+
508
+ ```yaml
509
+ # Producer (payments/domain.yaml):
510
+ events:
511
+ - name: PaymentApprovedEvent
512
+ fields:
513
+ - { name: paymentId, type: String }
514
+
515
+ # Consumer (reservations/domain.yaml):
516
+ listeners:
517
+ - event: PaymentApprovedEvent
518
+ fields:
519
+ - { name: approvedAt, type: LocalDateTime } # ❌ ERROR — not in producer
520
+ ```
521
+
522
+ **Message:** `[C1-003] Campo 'approvedAt' en listener de 'PaymentApprovedEvent' no existe en los campos del evento del productor (payments)`
523
+
524
+ **Fix:** Remove the field from the listener, or add it to the producer event declaration.
525
+
526
+ #### C1-004 — Incompatible field types between producer and consumer
527
+
528
+ ```yaml
529
+ # Producer declares: amount: BigDecimal
530
+ # Consumer declares: amount: String # ❌ ERROR
531
+ ```
532
+
533
+ **Message:** `[C1-004] Campo 'amount' en listener de 'PaymentApprovedEvent': tipo incompatible — Productor declara 'BigDecimal', listener declara 'String'`
534
+
535
+ **Fix:** Align the field type in the listener with the type declared in the producer event.
536
+
537
+ #### C1-005 — Registered consumer has no listener in domain.yaml
538
+
539
+ ```yaml
540
+ # system.yaml registers notifications as consumer:
541
+ consumers:
542
+ - module: notifications
543
+ useCase: NotifyOrderCreated
544
+ # notifications/domain.yaml has no listeners[] for OrderCreatedEvent ❌ ERROR
545
+ ```
546
+
547
+ **Message:** `[C1-005] system.yaml registra 'notifications' como consumidor de 'OrderCreatedEvent' pero el módulo no tiene listener declarado`
548
+
549
+ **Fix:** Add a `listeners[]` entry for `OrderCreatedEvent` in `notifications/domain.yaml`.
550
+
551
+ #### C1-006 — listener.producer references wrong module
552
+
553
+ ```yaml
554
+ listeners:
555
+ - event: PaymentApprovedEvent
556
+ producer: orders # ❌ ERROR — this event is actually produced by 'payments'
557
+ ```
558
+
559
+ **Message:** `[C1-006] Listener declara producer: 'orders' pero 'PaymentApprovedEvent' es producido por 'payments'`
560
+
561
+ **Fix:** Update `producer:` to match the actual producing module.
562
+
563
+ ---
564
+
565
+ ### C2 — Behavior gaps
566
+
567
+ Verifies that every transition method and every use case has a traceable activation mechanism — either an HTTP endpoint or an async listener — ensuring no business behavior is accidentally unreachable.
568
+
569
+ | Rule | Severity | Description |
570
+ |------|----------|-------------|
571
+ | C2-001 | 🟡 warning | Transition method with no matching HTTP endpoint or listener |
572
+ | C2-002 | 🔵 info | Listener `useCase` has no equivalent REST endpoint in a module that exposes REST |
573
+ | C2-003 | 🟡 warning | Value in a `*Type` enum with no traceable Kafka event as origin |
574
+ | C2-004 | 🔴 error | Event `triggers[]` references a transition method that does not exist |
575
+ | C2-005 | 🔵 info | Transition method without any associated Domain Event (no `triggers`) |
576
+
577
+ > **Note on C2-001:** Silenced automatically when the transition method already has an event `trigger` declared — the trigger itself is sufficient design evidence.
578
+
579
+ #### C2-001 — Transition with no endpoint or listener
580
+
581
+ ```yaml
582
+ enums:
583
+ - name: OrderStatus
584
+ transitions:
585
+ - from: CONFIRMED
586
+ to: CANCELLED
587
+ method: cancel # ⚠️ WARNING — nothing calls 'cancel'
588
+ ```
589
+
590
+ **Message:** `[C2-001] Transición 'cancel' de OrderStatus (CONFIRMED → CANCELLED) no tiene endpoint HTTP ni listener asociado`
591
+
592
+ **Fix:** Add a `POST /orders/{id}/cancel` endpoint or connect it to a listener with `useCase: CancelOrder`.
593
+
594
+ #### C2-004 — Trigger references non-existent transition method
595
+
596
+ ```yaml
597
+ events:
598
+ - name: OrderShipped
599
+ triggers:
600
+ - ship # ❌ ERROR — no transition method named 'ship' exists
601
+ ```
602
+
603
+ **Message:** `[C2-004] Evento 'OrderShipped' tiene trigger 'ship' que no corresponde a ningún método de transición`
604
+
605
+ **Fix:** Add a transition with `method: ship`, or update the trigger to reference an existing method.
606
+
607
+ #### C2-005 — Transition without a Domain Event (info)
608
+
609
+ **Message:** `[C2-005] Transición 'confirm' (OrderStatus: PENDING → CONFIRMED) no tiene ningún Domain Event asociado`
610
+
611
+ **Recommendation:** Declare a domain event with `triggers: [confirm]` so downstream modules can react to this state change.
612
+
613
+ #### C2-002 / C2-003 — Completeness checks (info / warning)
614
+
615
+ - `[C2-002]` — Listener `useCase` has no REST equivalent in the same module (info; acceptable when the module is purely event-driven)
616
+ - `[C2-003]` — Value in a `*Type` enum (e.g., `PaymentType.BANK_TRANSFER`) has no Kafka event whose name or fields overlap — suggests the value originates from an external trigger not yet documented
617
+
618
+ ---
619
+
620
+ ### C3 — Cross-reference integrity
621
+
622
+ Verifies that all declared dependencies between modules are consistent: cross-aggregate field references have a corresponding port or listener, port calls target declared endpoints, and there is no hidden circular coupling.
623
+
624
+ | Rule | Severity | Description |
625
+ |------|----------|-------------|
626
+ | C3-001 | 🟡 warning | Field with `reference.module=X` but no port or listener connecting to X |
627
+ | C3-002 | 🔴 error | `port.target` refers to an internal module with no `domain.yaml` loaded |
628
+ | C3-003 | 🟡 warning | Port calls an endpoint not declared in the target module |
629
+ | C3-004 | 🟡 warning | Sync dependency on a module that emits no Kafka events |
630
+ | C3-005 | 🔴 error | Bidirectional sync coupling between two modules (A calls B **and** B calls A) |
631
+ | C3-006 | 🟡 warning | `system.yaml` declares a sync call but the caller module has no matching port |
632
+
633
+ > **Note:** C3-002, C3-003, and C3-004 are skipped for external services — targets ending in `-external` or whose `baseUrl` points to a non-localhost host.
634
+
635
+ #### C3-001 — Cross-aggregate reference without a port or listener
636
+
637
+ ```yaml
638
+ # reservations/domain.yaml
639
+ fields:
640
+ - name: customerId
641
+ type: String
642
+ reference:
643
+ aggregate: Customer
644
+ module: customers # ⚠️ WARNING — no port or listener to 'customers'
645
+ ```
646
+
647
+ **Message:** `[C3-001] Campo 'customerId' referencia módulo 'customers' pero no hay port ni listener que conecte con ese módulo`
648
+
649
+ **Fix:** Add a `ports[]` entry targeting `customers` in `reservations/domain.yaml`.
650
+
651
+ #### C3-002 — Port target missing domain.yaml
652
+
653
+ ```yaml
654
+ ports:
655
+ - name: findScreeningById
656
+ service: ScreeningService
657
+ target: screenings # ❌ ERROR — 'screenings' in system.yaml but no domain.yaml loaded
658
+ ```
659
+
660
+ **Message:** `[C3-002] Port 'ScreeningService' apunta a 'screenings' que está en system.yaml pero no tiene domain.yaml cargado`
661
+
662
+ **Fix:** Ensure `system/screenings/domain.yaml` exists and is reachable when running `--domain`.
663
+
664
+ #### C3-003 — Port calls undeclared endpoint
665
+
666
+ ```yaml
667
+ ports:
668
+ - http: GET /orders/{id}/items # ⚠️ WARNING — not listed in orders.exposes[]
669
+ target: orders
670
+ ```
671
+
672
+ **Message:** `[C3-003] Port 'OrderService' llama 'GET /orders/{id}/items' en 'orders' pero ese endpoint no está declarado en el módulo destino`
673
+
674
+ **Fix:** Add `GET /orders/{id}/items` to `orders.exposes[]` in `system.yaml`, or correct the port path.
675
+
676
+ #### C3-005 — Bidirectional sync coupling
677
+
678
+ **Message:** `[C3-005] Acoplamiento síncrono bidireccional entre 'reservations' y 'payments'`
679
+
680
+ **Fix:** Same as [S3-003](#s3-003--bidirectional-sync-coupling) — break the cycle with an async event or a shared read-model.
681
+
682
+ #### C3-006 — system.yaml sync call without matching port
683
+
684
+ ```yaml
685
+ # system.yaml: reservations calls screenings
686
+ # reservations/domain.yaml: no ports[] for 'screenings' ⚠️ WARNING
687
+ ```
688
+
689
+ **Message:** `[C3-006] system.yaml declara que 'reservations' llama síncronamente a 'screenings' pero el módulo no tiene port declarado hacia 'screenings'`
690
+
691
+ **Fix:** Add a `ports[]` section in `reservations/domain.yaml` with a method targeting `screenings`.
692
+
693
+ ---
694
+
695
+ ### C4 — Audit & traceability
696
+
697
+ Verifies that critical business entities have change-tracking mechanisms and that data from external bounded contexts is stored in a structured format.
698
+
699
+ > **Critical modules heuristic:** A module is considered critical when its name contains any of: `payment`, `billing`, `order`, `reservation`, `customer`, `user`, or `inventory`.
700
+
701
+ | Rule | Severity | Description |
702
+ |------|----------|-------------|
703
+ | C4-001 | 🟡 warning | Child entity with `cascade: REMOVE` has no audit and no soft delete, while its root entity has audit enabled |
704
+ | C4-002 | 🟡 warning | Root entity in a critical module without `audit.enabled: true` |
705
+ | C4-003 | 🟡 warning | Field with an external-data name typed as plain `String` in a module that declares ports |
706
+ | C4-004 | 🟡 warning | `readOnly` field in a critical module that does not appear in any event of that module |
707
+
708
+ #### C4-001 — Child entity deletable without audit trail
709
+
710
+ ```yaml
711
+ entities:
712
+ - name: Order
713
+ isRoot: true
714
+ audit:
715
+ enabled: true
716
+ relationships:
717
+ - type: OneToMany
718
+ target: OrderItem
719
+ cascade: [REMOVE] # root is audited, child is not → ⚠️ WARNING
720
+ ```
721
+
722
+ **Message:** `[C4-001] Entidad hija 'OrderItem' tiene cascade REMOVE pero sin audit ni soft delete`
723
+
724
+ **Fix:** Add `audit.enabled: true` or `hasSoftDelete: true` to the `OrderItem` entity.
725
+
726
+ #### C4-002 — Critical root entity without audit
727
+
728
+ **Message:** `[C4-002] Entidad raíz 'Order' en módulo crítico 'orders' no tiene audit.enabled:true`
729
+
730
+ **Fix:**
731
+ ```yaml
732
+ entities:
733
+ - name: Order
734
+ isRoot: true
735
+ audit:
736
+ enabled: true
737
+ trackUser: true
738
+ ```
739
+
740
+ #### C4-003 — External data stored as unstructured String
741
+
742
+ ```yaml
743
+ fields:
744
+ - name: rawData # ⚠️ WARNING — external payload stored as plain String
745
+ type: String
746
+ ```
747
+
748
+ **Message:** `[C4-003] Campo 'rawData' almacena datos externos como String no estructurado`
749
+
750
+ **Fix:** Replace with a `nestedType` or a structured Value Object mapping the relevant fields explicitly.
751
+
752
+ #### C4-004 — readOnly field not surfaced in any event
753
+
754
+ ```yaml
755
+ fields:
756
+ - name: totalAmount
757
+ type: BigDecimal
758
+ readOnly: true # ⚠️ WARNING — not included in any domain event of this module
759
+ ```
760
+
761
+ **Message:** `[C4-004] Campo readOnly 'totalAmount' en módulo crítico 'reservations' no aparece en ningún evento del módulo`
762
+
763
+ **Fix:** Include `totalAmount` in a relevant domain event, or document why it is intentionally private.
764
+
765
+ ---
766
+
767
+ ## 6. Score calculation
768
+
769
+ The score **only** counts errors, warnings, and passing validations. **Info items do not affect the score.**
770
+
771
+ ```
772
+ score = round(passed / (passed + errors + warnings × 0.5) × 100)
773
+ ```
774
+
775
+ | Score | Color | Interpretation |
776
+ |-------|-------|----------------|
777
+ | > 80% | 🟢 Green | Good architecture — minor issues only |
778
+ | 60–80% | 🟡 Yellow | Moderate issues — review warnings before coding |
779
+ | < 60% | 🔴 Red | Significant problems — resolve errors before proceeding |
780
+
781
+ A score of 100% means zero errors, zero warnings, and at least one passing validation.
782
+
783
+ ---
784
+
785
+ ## 7. Report output
786
+
787
+ The command produces three output artifacts:
788
+
789
+ ### 1. Console summary
790
+
791
+ ```
792
+ ✔ Analysis complete!
793
+
794
+ 📊 Validation Summary
795
+ ────────────────────────────────────────
796
+ 🔴 Errors: 0
797
+ 🟡 Warnings: 3
798
+ 🔵 Info: 12
799
+ 🟢 Passed: 11
800
+ 📈 Score: 88%
801
+
802
+ Report written to: ./system-report.html
803
+ Evaluation written to: assets/system-evaluation.md
804
+
805
+ 🌐 Server running at: http://localhost:3000
806
+ ```
807
+
808
+ ### 2. HTML report (`system-report.html`)
809
+
810
+ Self-contained HTML file with four interactive tabs. Can be shared without a server.
811
+
812
+ ### 3. Markdown evaluation (`assets/system-evaluation.md`)
813
+
814
+ A concise file containing only **errors and warnings** — suitable for committing alongside `system.yaml` as a living architecture review document.
815
+
816
+ ```markdown
817
+ # Evaluación del sistema — my-system
818
+
819
+ > Generado: 2026-03-13 10:45:00
820
+ > Score de calidad: **88%** 🟢 Bueno
821
+ > 🔴 Errores: 0 | 🟡 Advertencias: 3
822
+
823
+ ---
824
+
825
+ ## 🟡 Advertencias
826
+
827
+ - [S1-004] Módulo 'notifications' es puramente reactivo …
828
+ - [S2-005] Módulo 'customers' consume eventos pero no produce ninguno
829
+ - [S2-005] Módulo 'notifications' consume eventos pero no produce ninguno
830
+ ```
831
+
832
+ ---
833
+
834
+ ## 8. Practical examples with real findings
835
+
836
+ ### Example: cinema booking system
837
+
838
+ Running `eva evaluate system` on a cinema booking `system.yaml` with 7 modules and 9 async events produced:
839
+
840
+ **Score: 88% (0 errors, 3 warnings, 11 passed, 12 info)**
841
+
842
+ | Rule | Severity | Finding | Recommendation |
843
+ |------|----------|---------|----------------|
844
+ | S1-004 | 🟡 | `notifications` is purely reactive but description doesn't say so | Add "consumes events" to its description |
845
+ | S2-005 | 🟡 | `customers` consumes events but produces none | Intentional — accumulates loyalty points. Acceptable. |
846
+ | S2-005 | 🟡 | `notifications` consumes events but produces none | Intentional — pure notification sink. Acceptable. |
847
+ | S2-007 | 🔵 | 9 topics don't include the `cinema` prefix | Rename topics to `cinema.RESERVATION_CREATED`, etc. |
848
+ | S3-005 | 🔵 | `movies`, `theaters`, `customers` consulted sync but emit no events | Acceptable for catalog/reference data modules |
849
+
850
+ ---
851
+
852
+ ## 9. Common errors and how to fix them
853
+
854
+ ### Error S1-001 — Module referenced but not declared
855
+
856
+ ```
857
+ [S1-001] Módulo 'billing' referenciado en integrations pero no declarado en modules[]
858
+ ```
859
+
860
+ **Fix:** Add the module to `modules[]` or correct the name typo in `integrations`.
861
+
862
+ ---
863
+
864
+ ### Error S1-002 — Module with no responsibilities
865
+
866
+ ```
867
+ [S1-002] Módulo 'reporting' no tiene ninguna responsabilidad — no expone endpoints,
868
+ no produce ni consume eventos
869
+ ```
870
+
871
+ **Fix:** Add `exposes[]` endpoints, connect the module to an async event, or remove it.
872
+
873
+ ---
874
+
875
+ ### Error S2-001 — Event with no consumers
876
+
877
+ ```
878
+ [S2-001] Evento 'OrderShippedEvent' declarado en integrations.async sin consumidores
879
+ ```
880
+
881
+ **Fix:** Add at least one consumer, or remove the event if it is not yet implemented.
882
+
883
+ ---
884
+
885
+ ### Error S2-002 — Topic collision
886
+
887
+ ```
888
+ [S2-002] Topic 'ORDER_EVENTS' está declarado para dos eventos distintos:
889
+ 'OrderCreatedEvent' y 'OrderCancelledEvent'
890
+ ```
891
+
892
+ **Fix:** Give each event a unique topic name: `ORDER_CREATED`, `ORDER_CANCELLED`.
893
+
894
+ ---
895
+
896
+ ### Error S2-003 — Self-loop
897
+
898
+ ```
899
+ [S2-003] Módulo 'orders' está listado como consumidor de su propio evento
900
+ 'OrderCreatedEvent' (self-loop)
901
+ ```
902
+
903
+ **Fix:** Remove the self-reference from `consumers`, or redesign the flow so a different module consumes the event.
904
+
905
+ ---
906
+
907
+ ### Error S3-001 — Sync call to module without endpoints
908
+
909
+ ```
910
+ [S3-001] 'orders' llama síncronamente a 'notifications' pero este módulo
911
+ no declara exposes[]
912
+ ```
913
+
914
+ **Fix:** Add `exposes[]` to the target module, or replace the sync call with an async event.
915
+
916
+ ---
917
+
918
+ ### Error S3-002 — Endpoint not found in target module
919
+
920
+ ```
921
+ [S3-002] Endpoint 'GET /orders/{id}/items' usado por 'shipping' no está
922
+ declarado en exposes[] de 'orders'
923
+ ```
924
+
925
+ **Fix:** Add the missing endpoint to `orders.exposes[]`:
926
+
927
+ ```yaml
928
+ - method: GET
929
+ path: /orders/{id}/items
930
+ useCase: GetOrderItems
931
+ description: "..."
932
+ ```
933
+
934
+ ---
935
+
936
+ ### Error S4-001 — Duplicate route
937
+
938
+ ```
939
+ [S4-001] Módulo 'orders' tiene dos endpoints con el mismo método y path:
940
+ GET /orders/{id}
941
+ ```
942
+
943
+ **Fix:** Remove the duplicate or rename the path of the second endpoint.
944
+
945
+ ---
946
+
947
+ ### Warning S3-003 — Bidirectional sync coupling
948
+
949
+ ```
950
+ [S3-003] Acoplamiento síncrono bidireccional: 'orders' llama a 'inventory'
951
+ y viceversa
952
+ ```
953
+
954
+ **Fix options:**
955
+ 1. Replace one direction with an async event
956
+ 2. Extract the shared data into a third read-model module that both query
957
+ 3. Pass the needed data in the initial request payload, avoiding the reverse call
958
+
959
+ ---
960
+
961
+ ### Warning S5-001 — Messaging disabled with events declared
962
+
963
+ ```
964
+ [S5-001] messaging.enabled está en false pero hay 5 eventos declarados
965
+ en integrations.async
966
+ ```
967
+
968
+ **Fix:** Set `messaging.enabled: true`, or remove the async events from `integrations` if messaging is truly not used.
969
+
970
+ ---
971
+
972
+ ### Warning S5-002 — Success event without failure counterpart
973
+
974
+ ```
975
+ [S5-002] Evento de éxito 'PaymentApprovedEvent' existe pero no hay un evento
976
+ de fallo correspondiente para el sujeto 'payment' que permita compensación
977
+ ```
978
+
979
+ **Fix:** Add a corresponding failure event so consumers can react to unhappy paths:
980
+
981
+ ```yaml
982
+ - event: PaymentRejectedEvent
983
+ producer: payments
984
+ topic: PAYMENT_REJECTED
985
+ consumers:
986
+ - module: reservations
987
+ useCase: ExpireReservation
988
+ - module: notifications
989
+ useCase: NotifyPaymentRejected
990
+ ```
991
+
992
+
993
+ ---
994
+