eva4j 1.0.17 → 1.0.18

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 (134) hide show
  1. package/AGENTS.md +2 -0
  2. package/DOMAIN_YAML_GUIDE.md +3 -1
  3. package/QUICK_REFERENCE.md +8 -4
  4. package/bin/eva4j.js +70 -2
  5. package/config/defaults.json +1 -0
  6. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  7. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  8. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  9. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  10. package/docs/commands/EVALUATE_SYSTEM.md +272 -8
  11. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  12. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  13. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  14. package/docs/commands/INDEX.md +27 -3
  15. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  16. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  17. package/docs/prototype/system/RISKS.md +277 -0
  18. package/docs/prototype/system/customers.yaml +133 -0
  19. package/docs/prototype/system/inventory.yaml +109 -0
  20. package/docs/prototype/system/notifications.yaml +131 -0
  21. package/docs/prototype/system/orders.yaml +241 -0
  22. package/docs/prototype/system/payments.yaml +256 -0
  23. package/docs/prototype/system/products.yaml +168 -0
  24. package/docs/prototype/system/system.yaml +269 -0
  25. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  26. package/examples/domain-read-models.yaml +2 -2
  27. package/examples/system/customer.yaml +89 -0
  28. package/examples/system/orders.yaml +119 -0
  29. package/examples/system/product.yaml +27 -0
  30. package/examples/system/system.yaml +80 -0
  31. package/package.json +1 -1
  32. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  33. package/src/agents/design-gap-analyst.agent.md +383 -0
  34. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  35. package/src/agents/design-reviewer.agent.md +31 -5
  36. package/src/agents/implement-use-cases.prompt.md +179 -0
  37. package/src/agents/ux-gap-analyst.agent.md +412 -0
  38. package/src/commands/add-rabbitmq-client.js +261 -0
  39. package/src/commands/add-temporal-client.js +22 -2
  40. package/src/commands/build.js +267 -11
  41. package/src/commands/evaluate-system.js +700 -13
  42. package/src/commands/generate-entities.js +308 -16
  43. package/src/commands/generate-rabbitmq-event.js +665 -0
  44. package/src/commands/generate-rabbitmq-listener.js +205 -0
  45. package/src/commands/generate-temporal-activity.js +968 -34
  46. package/src/commands/generate-temporal-flow.js +95 -38
  47. package/src/commands/generate-temporal-system.js +708 -0
  48. package/src/skills/build-system-yaml/SKILL.md +222 -2
  49. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
  50. package/src/skills/build-system-yaml/references/module-spec.md +57 -8
  51. package/src/skills/build-temporal-system/SKILL.md +752 -0
  52. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  53. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  54. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  55. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  56. package/src/skills/implement-use-case/SKILL.md +350 -0
  57. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  58. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  59. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  60. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  61. package/src/utils/bounded-context-diagram.js +844 -0
  62. package/src/utils/domain-validator.js +266 -4
  63. package/src/utils/naming.js +10 -0
  64. package/src/utils/system-validator.js +169 -11
  65. package/src/utils/system-yaml-parser.js +318 -0
  66. package/src/utils/temporal-validator.js +497 -0
  67. package/src/utils/yaml-to-entity.js +10 -7
  68. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  69. package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
  70. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  71. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  72. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  73. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  74. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  75. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  76. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  77. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  78. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  79. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  80. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  81. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  82. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  83. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  84. package/templates/base/root/AGENTS.md.ejs +1 -1
  85. package/templates/crud/EndpointsController.java.ejs +1 -1
  86. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  87. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  88. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  89. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  90. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  91. package/templates/evaluate/report.html.ejs +1447 -90
  92. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  93. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  94. package/templates/ports/PortAclMapper.java.ejs +35 -0
  95. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  96. package/templates/ports/PortFeignClient.java.ejs +4 -0
  97. package/templates/ports/PortResponseDto.java.ejs +1 -1
  98. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  99. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  100. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  101. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  102. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  103. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  104. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  105. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  106. package/templates/read-model/ReadModelJpa.java.ejs +1 -1
  107. package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
  108. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  109. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
  110. package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
  111. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  112. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  113. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  114. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  115. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  116. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  117. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  118. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  119. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  120. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  121. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  122. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  123. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  124. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  125. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  126. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  127. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  128. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  129. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  130. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  131. package/COMMAND_EVALUATION.md +0 -911
  132. package/test-c2010.js +0 -49
  133. package/test-update-compat.js +0 -109
  134. package/test-update-lifecycle.js +0 -121
@@ -0,0 +1,192 @@
1
+ # Command `add rabbitmq-client`
2
+
3
+ ## Description
4
+
5
+ Adds RabbitMQ client support to an existing eva4j project. Installs Spring AMQP dependencies, generates configuration files for all environments, creates the `RabbitMQConfig.java` class with production-ready defaults, and adds a RabbitMQ service to `docker-compose.yaml`.
6
+
7
+ ## Purpose
8
+
9
+ Enable asynchronous event-driven communication between modules or microservices using RabbitMQ as the message broker. This command sets up the foundational infrastructure so that subsequent commands (`eva g rabbitmq-event`, `eva g rabbitmq-listener`) can generate producers and consumers.
10
+
11
+ ## Syntax
12
+
13
+ ```bash
14
+ eva add rabbitmq-client
15
+ ```
16
+
17
+ No parameters required.
18
+
19
+ ## Prerequisites
20
+
21
+ - Must be inside an eva4j project (created with `eva create`)
22
+ - At least one module must exist (created with `eva add module`)
23
+ - **Kafka must NOT be installed** — only one broker per project is allowed
24
+
25
+ ## What It Generates
26
+
27
+ ### 1. build.gradle Dependencies
28
+
29
+ ```groovy
30
+ // RabbitMQ
31
+ implementation 'org.springframework.boot:spring-boot-starter-amqp'
32
+ testImplementation 'org.springframework.amqp:spring-rabbit-test'
33
+ ```
34
+
35
+ ### 2. Configuration Files (per environment)
36
+
37
+ **`parameters/local/rabbitmq.yaml`:**
38
+ ```yaml
39
+ spring:
40
+ rabbitmq:
41
+ host: localhost
42
+ port: 5672
43
+ username: guest
44
+ password: guest
45
+ virtual-host: /
46
+ listener:
47
+ simple:
48
+ acknowledge-mode: manual
49
+ concurrency: 3
50
+ retry:
51
+ enabled: true
52
+ max-attempts: 3
53
+ initial-interval: 1500
54
+ ```
55
+
56
+ **`parameters/production/rabbitmq.yaml`:**
57
+ ```yaml
58
+ spring:
59
+ rabbitmq:
60
+ host: ${RABBITMQ_HOST}
61
+ port: ${RABBITMQ_PORT:5672}
62
+ username: ${RABBITMQ_USERNAME}
63
+ password: ${RABBITMQ_PASSWORD}
64
+ virtual-host: ${RABBITMQ_VHOST:/}
65
+ connection-timeout: 5000
66
+ requested-heartbeat: 60
67
+ publisher-confirm-type: correlated
68
+ publisher-returns: true
69
+ ssl:
70
+ enabled: ${RABBITMQ_SSL_ENABLED:false}
71
+ cache:
72
+ channel:
73
+ size: 25
74
+ template:
75
+ mandatory: true
76
+ listener:
77
+ simple:
78
+ acknowledge-mode: manual
79
+ concurrency: 5
80
+ max-concurrency: 20
81
+ prefetch: 10
82
+ retry:
83
+ enabled: true
84
+ max-attempts: 5
85
+ initial-interval: 2000
86
+ multiplier: 2.0
87
+ ```
88
+
89
+ ### 3. RabbitMQConfig.java
90
+
91
+ Located at `shared/infrastructure/configurations/rabbitmqConfig/RabbitMQConfig.java`.
92
+
93
+ Includes:
94
+ - `Jackson2JsonMessageConverter` for JSON serialization
95
+ - `RabbitAdmin` for auto-declaring exchanges, queues, and bindings
96
+ - `RabbitTemplate` with publisher confirms + returns
97
+ - `SimpleRabbitListenerContainerFactory` with manual ack mode and exponential backoff retry
98
+
99
+ ### 4. Docker Compose Service
100
+
101
+ ```yaml
102
+ rabbitmq:
103
+ image: rabbitmq:4-management
104
+ container_name: <project>-rabbitmq
105
+ ports:
106
+ - "5672:5672" # AMQP protocol
107
+ - "15672:15672" # Management UI
108
+ environment:
109
+ RABBITMQ_DEFAULT_USER: guest
110
+ RABBITMQ_DEFAULT_PASS: guest
111
+ RABBITMQ_DEFAULT_VHOST: /
112
+ ```
113
+
114
+ ### 5. Application YAML Imports
115
+
116
+ Adds `rabbitmq.yaml` to the Spring config import chain in all `application-{env}.yaml` files:
117
+
118
+ ```yaml
119
+ spring:
120
+ config:
121
+ import:
122
+ - "classpath:parameters/local/rabbitmq.yaml"
123
+ ```
124
+
125
+ ## Generated Files Summary
126
+
127
+ ```
128
+ project/
129
+ ├── build.gradle # + AMQP dependencies
130
+ ├── docker-compose.yaml # + RabbitMQ service
131
+ ├── src/main/java/.../shared/
132
+ │ └── infrastructure/configurations/rabbitmqConfig/
133
+ │ └── RabbitMQConfig.java # Core config class
134
+ └── src/main/resources/
135
+ ├── application-local.yaml # + rabbitmq.yaml import
136
+ ├── application-develop.yaml # + rabbitmq.yaml import
137
+ ├── application-test.yaml # + rabbitmq.yaml import
138
+ ├── application-production.yaml # + rabbitmq.yaml import
139
+ └── parameters/
140
+ ├── local/rabbitmq.yaml # Local config
141
+ ├── develop/rabbitmq.yaml # Develop config
142
+ ├── test/rabbitmq.yaml # Test config
143
+ └── production/rabbitmq.yaml # Production config
144
+ ```
145
+
146
+ ## Mutual Exclusivity with Kafka
147
+
148
+ Only **one** message broker is allowed per project:
149
+
150
+ ```bash
151
+ eva add kafka-client # ✅ Installs Kafka
152
+ eva add rabbitmq-client # ❌ Error: Kafka client is already installed
153
+
154
+ # Or vice versa:
155
+ eva add rabbitmq-client # ✅ Installs RabbitMQ
156
+ eva add kafka-client # ❌ Error: RabbitMQ client is already installed
157
+ ```
158
+
159
+ To switch brokers, you must manually remove the existing broker's dependencies, config files, and feature flag from `.eva4j.json`.
160
+
161
+ ## After Installation
162
+
163
+ ### Start RabbitMQ
164
+
165
+ ```bash
166
+ docker-compose up -d
167
+ ```
168
+
169
+ ### Access Management UI
170
+
171
+ Open `http://localhost:15672` — credentials: `guest` / `guest`.
172
+
173
+ ### Generate Events and Listeners
174
+
175
+ ```bash
176
+ # Create an event producer
177
+ eva g rabbitmq-event user user-created
178
+
179
+ # Create an event consumer
180
+ eva g rabbitmq-listener notification
181
+ ```
182
+
183
+ ### Or use domain.yaml
184
+
185
+ Declare events in `domain.yaml` and run `eva g entities <module>` — all RabbitMQ infrastructure is auto-wired when `rabbitmq-client` is installed.
186
+
187
+ ## See Also
188
+
189
+ - [`generate rabbitmq-event`](./GENERATE_RABBITMQ_EVENT.md) — Create event publisher
190
+ - [`generate rabbitmq-listener`](./GENERATE_RABBITMQ_LISTENER.md) — Create event consumer
191
+ - [`generate kafka-event`](./GENERATE_KAFKA_EVENT.md) — Kafka equivalent
192
+ - [RabbitMQ Production Config](../RABBITMQ_PRODUCTION_CONFIG.md) — Detailed parameter reference
@@ -9,15 +9,67 @@
9
9
  3. [system.yaml structure required](#3-systemyaml-structure-required)
10
10
  4. [system.yaml evaluation criteria (S1–S5)](#4-systemyaml-evaluation-criteria-s1s5)
11
11
  - [S1 — Module integrity](#s1--module-integrity)
12
+ - [S1-001 — Undeclared module referenced in integrations](#s1-001--undeclared-module-referenced-in-integrations)
13
+ - [S1-002 — Module with no responsibilities](#s1-002--module-with-no-responsibilities)
14
+ - [S1-003 — Module without description](#s1-003--module-without-description)
15
+ - [S1-004 — Purely reactive module not documented](#s1-004--purely-reactive-module-not-documented)
12
16
  - [S2 — Async event graph integrity](#s2--async-event-graph-integrity)
17
+ - [S2-001 — Event with no consumers](#s2-001--event-with-no-consumers)
18
+ - [S2-002 — Duplicate topic value](#s2-002--duplicate-topic-value)
19
+ - [S2-003 — Self-loop (module consuming its own event)](#s2-003--self-loop-module-consuming-its-own-event)
20
+ - [S2-004/S2-005 — Unbalanced producer/consumer roles](#s2-004s2-005--unbalanced-producerconsumer-roles)
21
+ - [S2-006 — Event name convention](#s2-006--event-name-convention)
22
+ - [S2-007 — Topic without configured prefix](#s2-007--topic-without-configured-prefix-info)
13
23
  - [S3 — Sync call integrity](#s3--sync-call-integrity)
24
+ - [S3-001 — Sync call to module without endpoints](#s3-001--sync-call-to-module-without-endpoints)
25
+ - [S3-002 — Endpoint not declared in target module](#s3-002--endpoint-not-declared-in-target-module)
26
+ - [S3-003 — Bidirectional sync coupling](#s3-003--bidirectional-sync-coupling)
27
+ - [S3-004 — Too many outgoing sync dependencies](#s3-004--too-many-outgoing-sync-dependencies)
28
+ - [S3-005 — Module consulted synchronously but emits no events](#s3-005--module-consulted-synchronously-but-emits-no-events-info)
29
+ - [S3-006 — Duplicate port name across caller modules](#s3-006--duplicate-port-name-across-caller-modules)
14
30
  - [S4 — Endpoint coherence](#s4--endpoint-coherence)
31
+ - [S4-001 — Duplicate route](#s4-001--duplicate-route)
32
+ - [S4-002 — PUT without GET for same resource](#s4-002--put-without-get-for-same-resource)
33
+ - [S4-003 — DELETE without description](#s4-003--delete-without-description)
34
+ - [S4-004 — Endpoint without description](#s4-004--endpoint-without-description-info)
35
+ - [S4-005 — POST without GET /{id}](#s4-005--post-without-get-id-info)
15
36
  - [S5 — Global system coherence](#s5--global-system-coherence)
37
+ - [S5-001 — Messaging disabled but events declared](#s5-001--messaging-disabled-but-events-declared)
38
+ - [S5-002 — Success event without matching failure event](#s5-002--success-event-without-matching-failure-event)
39
+ - [S5-003 — Auth module without integrations](#s5-003--auth-module-without-integrations-info)
40
+ - [S5-004 — Isolated module](#s5-004--isolated-module-info)
16
41
  5. [Domain evaluation criteria (C1–C4) — `--domain`](#5-domain-evaluation-criteria-c1c4----domain)
17
42
  - [C1 — Kafka event contracts](#c1--kafka-event-contracts)
43
+ - [C1-001 — Produced event with no consumers in system.yaml](#c1-001--produced-event-with-no-consumers-in-systemyaml)
44
+ - [C1-002 — Listener references event no module produces](#c1-002--listener-references-event-no-module-produces)
45
+ - [C1-003 — Field in listener missing in producer event](#c1-003--field-in-listener-missing-in-producer-event)
46
+ - [C1-004 — Incompatible field types between producer and consumer](#c1-004--incompatible-field-types-between-producer-and-consumer)
47
+ - [C1-005 — Registered consumer has no listener or readModel.syncedBy](#c1-005--registered-consumer-has-no-listener-or-readmodelsyncedby-in-domainyaml)
48
+ - [C1-006 — listener.producer references wrong module](#c1-006--listenerproducer-references-wrong-module)
49
+ - [C1-007 — readModel field not covered by any UPSERT event](#c1-007--readmodel-field-not-covered-by-any-upsert-event)
18
50
  - [C2 — Behavior gaps](#c2--behavior-gaps)
51
+ - [C2-001 — Transition with no endpoint or listener](#c2-001--transition-with-no-endpoint-or-listener)
52
+ - [C2-002 — Listener useCase without REST endpoint](#c2-002--c2-003--completeness-checks-info--warning)
53
+ - [C2-004 — Trigger references non-existent transition method](#c2-004--trigger-references-non-existent-transition-method)
54
+ - [C2-005 — Transition without a Domain Event](#c2-005--transition-without-a-domain-event-info)
55
+ - [C2-006 — useCase collision between endpoints and listeners](#c2-006--usecase-name-collision-between-endpoints-and-listeners)
56
+ - [C2-007 — FindAll useCase with incorrect plural form](#c2-007--findall-usecase-with-incorrect-plural-form)
57
+ - [C2-008 — Event with invalid lifecycle value](#c2-008--event-with-invalid-lifecycle-value)
58
+ - [C2-009 — Lifecycle value incompatible with entity configuration](#c2-009--lifecycle-value-incompatible-with-entity-configuration)
59
+ - [C2-010 — Lifecycle event field not found in root entity](#c2-010--lifecycle-event-field-not-found-in-root-entity)
60
+ - [C2-011 — Endpoint useCase not resolvable to any aggregate](#c2-011--endpoint-usecase-not-resolvable-to-any-aggregate)
19
61
  - [C3 — Cross-reference integrity](#c3--cross-reference-integrity)
62
+ - [C3-001 — Cross-aggregate reference without a port or listener](#c3-001--cross-aggregate-reference-without-a-port-or-listener)
63
+ - [C3-002 — Port target missing domain.yaml](#c3-002--port-target-missing-domainyaml)
64
+ - [C3-003 — Port calls undeclared endpoint](#c3-003--port-calls-undeclared-endpoint)
65
+ - [C3-005 — Bidirectional sync coupling](#c3-005--bidirectional-sync-coupling)
66
+ - [C3-006 — system.yaml sync call without matching port](#c3-006--systemyaml-sync-call-without-matching-port)
67
+ - [C3-007 — Cross-module useCase name collision](#c3-007--cross-module-usecase-name-collision-bean-conflict)
20
68
  - [C4 — Audit & traceability](#c4--audit--traceability)
69
+ - [C4-001 — Child entity deletable without audit trail](#c4-001--child-entity-deletable-without-audit-trail)
70
+ - [C4-002 — Critical root entity without audit](#c4-002--critical-root-entity-without-audit)
71
+ - [C4-003 — External data stored as unstructured String](#c4-003--external-data-stored-as-unstructured-string)
72
+ - [C4-004 — readOnly field not surfaced in any event](#c4-004--readonly-field-not-surfaced-in-any-event)
21
73
  6. [Score calculation](#6-score-calculation)
22
74
  7. [Report output](#7-report-output)
23
75
  8. [Practical examples with real findings](#8-practical-examples-with-real-findings)
@@ -130,7 +182,7 @@ All fields are optional except `modules[].name`. The evaluator gracefully handle
130
182
 
131
183
  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
184
 
133
- The evaluator runs **5 rule groups (S1–S5)** with a total of **20 rules** across three severity levels:
185
+ The evaluator runs **5 rule groups (S1–S5)** with a total of **26 rules** across three severity levels:
134
186
 
135
187
  | Severity | Symbol | Affects score | Description |
136
188
  |----------|--------|--------------|-------------|
@@ -149,7 +201,7 @@ Verifies that all modules declared in `modules[]` have defined responsibilities
149
201
  | S1-001 | 🔴 error | Module referenced in `integrations` but not declared in `modules[]` |
150
202
  | S1-002 | 🔴 error | Module with no responsibilities — no exposes, no events produced or consumed |
151
203
  | S1-003 | 🟡 warning | Module without a `description` field |
152
- | S1-004 | 🟡 warning | Purely reactive module (only consumes events) not documented explicitly in its description |
204
+ | S1-004 | info | Purely reactive module (only consumes events) not documented explicitly in its description |
153
205
 
154
206
  #### S1-001 — Undeclared module referenced in integrations
155
207
 
@@ -271,6 +323,7 @@ Verifies that all synchronous dependencies declared in `integrations.sync` refer
271
323
  | S3-003 | 🟡 warning | Bidirectional sync coupling — A calls B and B calls A |
272
324
  | S3-004 | 🟡 warning | Module with more than 3 distinct outgoing sync dependencies |
273
325
  | S3-005 | 🔵 info | Module consulted synchronously but emits no events when its state changes |
326
+ | S3-006 | 🔴 error | Same `port:` name declared by two or more different caller modules — causes `ConflictingBeanDefinitionException` |
274
327
 
275
328
  #### S3-001 — Sync call to module without endpoints
276
329
 
@@ -326,6 +379,36 @@ When other modules depend synchronously on a module but that module never publis
326
379
 
327
380
  **Message:** `[S3-005] Módulo 'movies' es consultado síncronamente pero no emite ningún evento cuando su estado cambia`
328
381
 
382
+ #### S3-006 — Duplicate port name across caller modules
383
+
384
+ When two or more modules declare a sync integration with **the same `port:` value**, each module generates a `{PortName}FeignAdapter.java` bean. Since `@Component` registers beans by class name without a module namespace, Spring throws `ConflictingBeanDefinitionException` at startup.
385
+
386
+ ```yaml
387
+ # ❌ ERROR — both modules use the same port name 'CustomerService'
388
+ sync:
389
+ - caller: orders
390
+ calls: customers
391
+ port: CustomerService # generates CustomerServiceFeignAdapter in orders
392
+ - caller: deliveries
393
+ calls: customers
394
+ port: CustomerService # generates CustomerServiceFeignAdapter in deliveries → collision
395
+ ```
396
+
397
+ **Message:** `[S3-006] Port 'CustomerService' es usado por módulos distintos (orders, deliveries). Esto causa ConflictingBeanDefinitionException en Spring. Cada módulo debe usar un nombre propio: ej. OrdersCustomerService, DeliveriesCustomerService`
398
+
399
+ **Fix:** Give each module a unique, context-specific port name:
400
+
401
+ ```yaml
402
+ sync:
403
+ - caller: orders
404
+ calls: customers
405
+ port: OrderCustomerService # ✅ generates OrderCustomerServiceFeignAdapter in orders
406
+
407
+ - caller: deliveries
408
+ calls: customers
409
+ port: DeliveryCustomerService # ✅ generates DeliveryCustomerServiceFeignAdapter in deliveries
410
+ ```
411
+
329
412
  ---
330
413
 
331
414
  ### S4 — Endpoint coherence
@@ -452,7 +535,7 @@ When `--domain` is passed, the evaluator additionally loads a `domain.yaml` file
452
535
  - Modules without a `domain.yaml` are silently skipped — their rules will not fire
453
536
  - Domain findings appear in the **Dominio** tab of the HTML report
454
537
 
455
- Domain rules are organized in **4 rule groups (C1–C4)** with a total of **19 rules**:
538
+ Domain rules are organized in **4 rule groups (C1–C4)** with a total of **29 rules**:
456
539
 
457
540
  | Severity | Symbol | Affects score | Description |
458
541
  |----------|--------|--------------|-------------|
@@ -468,12 +551,13 @@ Verifies that the producer → consumer graph is coherent at the code level: eve
468
551
 
469
552
  | Rule | Severity | Description |
470
553
  |------|----------|-------------|
471
- | C1-001 | 🟡 warning | Domain event produced by a module but has no consumers registered in `system.yaml` |
554
+ | C1-001 | info | Domain event produced by a module but has no consumers registered in `system.yaml` |
472
555
  | C1-002 | 🔴 error | `listeners[]` references an event that no domain module produces |
473
556
  | C1-003 | 🔴 error | Field in `listener.fields` does not exist in the producer event |
474
557
  | C1-004 | 🔴 error | Field exists in both producer and consumer but with incompatible types |
475
558
  | C1-005 | 🔴 error | `system.yaml` registers a module as consumer but that module has no matching `listener` or `readModels[].syncedBy` |
476
559
  | C1-006 | 🔴 error | `listener.producer` references the wrong producer module |
560
+ | C1-007 | 🟡 warning | Field declared in a `readModels[]` entry is not covered by any UPSERT event from the source module |
477
561
 
478
562
  #### C1-001 — Produced event with no consumers in system.yaml
479
563
 
@@ -482,7 +566,7 @@ Verifies that the producer → consumer graph is coherent at the code level: eve
482
566
  aggregates:
483
567
  - name: Order
484
568
  events:
485
- - name: OrderConfirmed # ⚠️ WARNING — not registered in system.yaml
569
+ - name: OrderConfirmed # 🔵 INFO — not registered in system.yaml
486
570
  fields: [...]
487
571
  ```
488
572
 
@@ -576,6 +660,35 @@ listeners:
576
660
 
577
661
  **Fix:** Update `producer:` to match the actual producing module.
578
662
 
663
+ #### C1-007 — readModel field not covered by any UPSERT event
664
+
665
+ When a `readModels[]` entry declares a field that is not included in any event with `action: UPSERT` from the source module's `syncedBy`, that field will always be `null` after synchronization because no event ever carries a value for it.
666
+
667
+ ```yaml
668
+ # orders/domain.yaml
669
+ readModels:
670
+ - name: ProductReadModel
671
+ source:
672
+ module: products
673
+ tableName: rm_products
674
+ fields:
675
+ - name: id
676
+ type: String
677
+ - name: name
678
+ type: String
679
+ - name: stock # ⚠️ WARNING — no UPSERT event from 'products' includes 'stock'
680
+ type: Integer
681
+ syncedBy:
682
+ - event: ProductCreatedEvent
683
+ action: UPSERT
684
+ - event: ProductUpdatedEvent
685
+ action: UPSERT
686
+ ```
687
+
688
+ **Message:** `[C1-007] ReadModel 'ProductReadModel' tiene campo 'stock' que no aparece en ningún evento UPSERT de syncedBy`
689
+
690
+ **Fix:** Add `stock` to the `fields[]` of `ProductCreatedEvent` and `ProductUpdatedEvent` in `products/domain.yaml`, or remove `stock` from the readModel if it is not needed.
691
+
579
692
  ---
580
693
 
581
694
  ### C2 — Behavior gaps
@@ -589,6 +702,12 @@ Verifies that every transition method and every use case has a traceable activat
589
702
  | C2-003 | 🟡 warning | Value in a `*Type` enum with no traceable Kafka event as origin |
590
703
  | C2-004 | 🔴 error | Event `triggers[]` references a transition method that does not exist |
591
704
  | C2-005 | 🔵 info | Transition method without any associated Domain Event (no `triggers`) |
705
+ | C2-006 | 🔴 error | Same `useCase` name declared in both `endpoints:` and `listeners:` in the same module |
706
+ | C2-007 | 🔴 error | `FindAll` use case name uses incorrect plural form — generator will produce a scaffold instead of full implementation |
707
+ | C2-008 | 🔴 error | Event declares `lifecycle:` with an invalid value (not `create`, `update`, `delete`, or `softDelete`) |
708
+ | C2-009 | 🟡 warning | `lifecycle: softDelete` on entity without `hasSoftDelete: true`, or `lifecycle: delete` on entity with `hasSoftDelete: true` |
709
+ | C2-010 | 🔴 error | Field declared in a `lifecycle` event does not exist in the root entity |
710
+ | C2-011 | 🔴 error | Endpoint `useCase` cannot be resolved to any aggregate in the module — generator will assign wrong types |
592
711
 
593
712
  > **Note on C2-001:** Silenced automatically when the transition method already has an event `trigger` declared — the trigger itself is sufficient design evidence.
594
713
 
@@ -631,6 +750,151 @@ events:
631
750
  - `[C2-002]` — Listener `useCase` has no REST equivalent in the same module (info; acceptable when the module is purely event-driven)
632
751
  - `[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
633
752
 
753
+ #### C2-006 — useCase name collision between endpoints and listeners
754
+
755
+ When a `useCase` name appears in both `endpoints[].operations[]` and `listeners[]` in the same module, both generate a `{UseCase}Command.java` file. The endpoint version silently overwrites the listener version at generation time, leaving the listener with broken code.
756
+
757
+ ```yaml
758
+ # ❌ ERROR — 'CreateOrder' is declared in both endpoints and listeners
759
+ endpoints:
760
+ versions:
761
+ - version: v1
762
+ operations:
763
+ - useCase: CreateOrder # → CreateOrderCommand.java (endpoint version)
764
+ method: POST
765
+ path: /orders
766
+
767
+ listeners:
768
+ - event: CartCheckedOutEvent
769
+ useCase: CreateOrder # ❌ also generates CreateOrderCommand.java → overwritten
770
+ ```
771
+
772
+ **Message:** `[C2-006] UseCase 'CreateOrder' está declarado en endpoints: y en listeners: (evento 'CartCheckedOutEvent')`
773
+
774
+ **Fix:** Rename the listener `useCase` to reflect its bounded context:
775
+
776
+ ```yaml
777
+ listeners:
778
+ - event: CartCheckedOutEvent
779
+ useCase: InitializeOrder # ✅ distinct name; no collision
780
+ ```
781
+
782
+ #### C2-007 — FindAll useCase with incorrect plural form
783
+
784
+ The generator recognizes `FindAll{Plural}` as the standard list query and produces the full paginated implementation. If the plural is wrong (e.g., a naive `s` suffix on an irregular noun), the generator creates an empty scaffold instead.
785
+
786
+ ```yaml
787
+ # ❌ ERROR — 'FindAllCategorys' is incorrect English plural
788
+ endpoints:
789
+ versions:
790
+ - version: v1
791
+ operations:
792
+ - useCase: FindAllCategorys # ❌ should be FindAllCategories
793
+ ```
794
+
795
+ **Message:** `[C2-007] UseCase 'FindAllCategorys' debería ser 'FindAllCategories' (plural correcto de 'Category')`
796
+
797
+ **Fix:** Use the correct English plural:
798
+
799
+ ```yaml
800
+ - useCase: FindAllCategories # ✅ correct plural → generates full implementation
801
+ - useCase: FindAllInventories # ✅ correct for 'Inventory'
802
+ - useCase: FindAllPolicies # ✅ correct for 'Policy'
803
+ ```
804
+
805
+ #### C2-008 — Event with invalid lifecycle value
806
+
807
+ The `lifecycle:` property on a domain event only accepts `create`, `update`, `delete`, or `softDelete`. Any other value is silently ignored by the generator, leaving the event without auto-wiring.
808
+
809
+ ```yaml
810
+ events:
811
+ - name: ProductArchivedEvent
812
+ lifecycle: archive # ❌ ERROR — not a valid lifecycle value
813
+ ```
814
+
815
+ **Message:** `[C2-008] Evento 'ProductArchivedEvent' tiene lifecycle: 'archive' que no es un valor válido`
816
+
817
+ **Fix:** Use one of the valid values: `create`, `update`, `delete`, `softDelete`. If `archive` is a business transition, use `transitions:` with `triggers:` instead.
818
+
819
+ #### C2-009 — Lifecycle value incompatible with entity configuration
820
+
821
+ `lifecycle: softDelete` requires the root entity to have `hasSoftDelete: true`, and `lifecycle: delete` requires `hasSoftDelete` to be absent or `false`. Mixing these configurations generates code that either won't compile or creates an incorrect deletion mechanism.
822
+
823
+ ```yaml
824
+ # ⚠️ WARNING — entity has no soft delete but event declares softDelete lifecycle
825
+ entities:
826
+ - name: Product
827
+ isRoot: true
828
+ # hasSoftDelete: true is missing
829
+ events:
830
+ - name: ProductDeactivatedEvent
831
+ lifecycle: softDelete # ⚠️ WARNING — entity doesn't support soft delete
832
+ ```
833
+
834
+ **Messages:**
835
+ - `[C2-009] Evento 'ProductDeactivatedEvent' tiene lifecycle: 'softDelete' pero la entidad raíz 'Product' no tiene hasSoftDelete: true`
836
+ - `[C2-009] Evento 'ProductDeletedEvent' tiene lifecycle: 'delete' pero la entidad raíz 'Product' tiene hasSoftDelete: true`
837
+
838
+ **Fix:** Align the lifecycle value with the entity configuration:
839
+ - Add `hasSoftDelete: true` to the entity, **or** change `lifecycle: softDelete` to `lifecycle: delete`
840
+
841
+ #### C2-010 — Lifecycle event field not found in root entity
842
+
843
+ Fields declared in a `lifecycle` event must exist on the root entity so the generator can resolve them automatically. Two exceptions apply: `{entityName}Id` (mapped to `aggregateId`) and any field ending in `At` with type `LocalDateTime` (auto-resolved to `LocalDateTime.now()`).
844
+
845
+ ```yaml
846
+ entities:
847
+ - name: Product
848
+ isRoot: true
849
+ fields:
850
+ - name: id
851
+ type: String
852
+ - name: name
853
+ type: String
854
+
855
+ events:
856
+ - name: ProductCreatedEvent
857
+ lifecycle: create
858
+ fields:
859
+ - name: productId # ✅ exception — maps to aggregateId
860
+ type: String
861
+ - name: name # ✅ exists in entity
862
+ type: String
863
+ - name: createdAt # ✅ exception — *At + LocalDateTime auto-resolved
864
+ type: LocalDateTime
865
+ - name: categoryCode # ❌ ERROR — does not exist in Product entity
866
+ type: String
867
+ ```
868
+
869
+ **Message:** `[C2-010] Evento 'ProductCreatedEvent' (lifecycle: create) tiene campo 'categoryCode' que no existe en la entidad raíz 'Product'`
870
+
871
+ **Fix:** Remove `categoryCode` from the event fields, or add it as a field to the `Product` entity.
872
+
873
+ #### C2-011 — Endpoint useCase not resolvable to any aggregate
874
+
875
+ The generator resolves each `useCase` name to an aggregate to determine which `ResponseDto` and `CommandHandler` types to use. If the name cannot be matched via standard patterns (`Create{X}`, `Update{X}`, `Delete{X}`, `Get{X}`, `FindAll{Plural(X)}`) or fuzzy matching, the generator assigns the first aggregate in the module by default and produces code with incorrect types that won't compile.
876
+
877
+ ```yaml
878
+ aggregates:
879
+ - name: Quote
880
+
881
+ endpoints:
882
+ versions:
883
+ - version: v1
884
+ operations:
885
+ - useCase: CalculatePremium # ⚠️ — 'Premium' doesn't match aggregate 'Quote'
886
+ - useCase: IssuePolicy # ❌ ERROR — 'Policy' is not an aggregate in this module
887
+ ```
888
+
889
+ **Message:** `[C2-011] UseCase 'IssuePolicy' no se resuelve a ningún agregado del módulo — se asignará al primero ('Quote') y generará código con tipos incorrectos`
890
+
891
+ **Fix:** Ensure the useCase name contains the aggregate name (or a recognizable part of it):
892
+
893
+ ```yaml
894
+ - useCase: CalculateQuotePremium # ✅ contains 'Quote' — resolves correctly
895
+ - useCase: CompleteQuote # ✅ contains 'Quote'
896
+ ```
897
+
634
898
  ---
635
899
 
636
900
  ### C3 — Cross-reference integrity
@@ -639,7 +903,7 @@ Verifies that all declared dependencies between modules are consistent: cross-ag
639
903
 
640
904
  | Rule | Severity | Description |
641
905
  |------|----------|-------------|
642
- | C3-001 | 🟡 warning | Field with `reference.module=X` but no port or listener connecting to X |
906
+ | C3-001 | info | Field with `reference.module=X` but no port or listener connecting to X |
643
907
  | C3-002 | 🔴 error | `port.target` refers to an internal module with no `domain.yaml` loaded |
644
908
  | C3-003 | 🟡 warning | Port calls an endpoint not declared in the target module |
645
909
  | C3-004 | 🟡 warning | Sync dependency on a module that emits no Kafka events |
@@ -658,7 +922,7 @@ fields:
658
922
  type: String
659
923
  reference:
660
924
  aggregate: Customer
661
- module: customers # ⚠️ WARNING — no port or listener to 'customers'
925
+ module: customers # 🔵 INFO — no port or listener to 'customers'
662
926
  ```
663
927
 
664
928
  **Message:** `[C3-001] Campo 'customerId' referencia módulo 'customers' pero no hay port ni listener que conecte con ese módulo`
@@ -890,7 +1154,7 @@ Running `eva evaluate system` on a cinema booking `system.yaml` with 7 modules a
890
1154
 
891
1155
  | Rule | Severity | Finding | Recommendation |
892
1156
  |------|----------|---------|----------------|
893
- | S1-004 | 🟡 | `notifications` is purely reactive but description doesn't say so | Add "consumes events" to its description |
1157
+ | S1-004 | | `notifications` is purely reactive but description doesn't say so | Add "consumes events" to its description |
894
1158
  | S2-005 | 🟡 | `customers` consumes events but produces none | Intentional — accumulates loyalty points. Acceptable. |
895
1159
  | S2-005 | 🟡 | `notifications` consumes events but produces none | Intentional — pure notification sink. Acceptable. |
896
1160
  | S2-007 | 🔵 | 9 topics don't include the `cinema` prefix | Rename topics to `cinema.RESERVATION_CREATED`, etc. |