@mrciphersmith/keryx 0.3.5 → 0.3.7

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 (84) hide show
  1. package/README.md +47 -0
  2. package/dist/cli.js +3099 -1160
  3. package/dist/core.js +22 -3
  4. package/docs/README.md +2 -0
  5. package/package.json +1 -1
  6. package/src/gdskills/bundled/install-manifest.json +319 -76
  7. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +19 -26
  8. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +6 -6
  9. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +4 -7
  10. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +2 -4
  11. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +23 -0
  12. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +17 -17
  13. package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
  14. package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
  15. package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
  16. package/src/gdskills/bundled/stacks/django/pack.json +43 -0
  17. package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
  18. package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
  19. package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
  20. package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
  21. package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
  22. package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
  23. package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
  24. package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
  25. package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
  26. package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
  27. package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
  28. package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
  29. package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
  30. package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
  31. package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
  32. package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
  33. package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
  34. package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
  35. package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
  36. package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
  37. package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
  38. package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
  39. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
  40. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
  41. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
  42. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
  43. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
  44. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
  45. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
  46. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
  47. package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
  48. package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
  49. package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
  50. package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
  51. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
  52. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
  53. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
  54. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
  55. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
  56. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
  57. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
  58. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
  59. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
  60. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
  61. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
  62. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
  63. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
  64. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
  65. package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
  66. package/src/gdskills/bundled/stacks/python/pack.json +1 -1
  67. package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
  68. package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
  69. package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
  70. package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
  71. package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
  72. package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
  73. package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
  74. package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
  75. package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
  76. package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
  77. package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
  78. package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
  79. package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
  80. package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
  81. package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
  82. package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
  83. package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
  84. package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
@@ -0,0 +1,2194 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "reports": [
4
+ {
5
+ "schemaVersion": "1.0.0",
6
+ "skillId": "java-kotlin-spring/java-kotlin-spring-build-fix",
7
+ "strictness": "high",
8
+ "trials": 10,
9
+ "triggerAccuracy": {
10
+ "truePositive": 2,
11
+ "falsePositive": 1,
12
+ "positives": 6,
13
+ "negatives": 6
14
+ },
15
+ "evidence": "authored",
16
+ "scenarios": [
17
+ {
18
+ "id": "trigger-positive-1",
19
+ "kind": "trigger-positive",
20
+ "prompt": "Our CI just started failing on ./gradlew assemble -- the log points to two different versions of jackson-databind on the classpath. How do I track down which dependency is pulling in the wrong one?",
21
+ "strictness": "high",
22
+ "trials": 1,
23
+ "passes": 0,
24
+ "passRate": 0,
25
+ "passAtK": 0,
26
+ "grader": "trigger-rank-fork-family",
27
+ "status": "ran",
28
+ "deterministic": true
29
+ },
30
+ {
31
+ "id": "trigger-positive-2",
32
+ "kind": "trigger-positive",
33
+ "prompt": "After adding a second implementation of PaymentGateway, the app won't boot anymore and the stack trace mentions NoUniqueBeanDefinitionException. What's going on?",
34
+ "strictness": "high",
35
+ "trials": 1,
36
+ "passes": 0,
37
+ "passRate": 0,
38
+ "passAtK": 0,
39
+ "grader": "trigger-rank-fork-family",
40
+ "status": "ran",
41
+ "deterministic": true
42
+ },
43
+ {
44
+ "id": "trigger-positive-3",
45
+ "kind": "trigger-positive",
46
+ "prompt": "mvn verify is failing with a circular bean dependency",
47
+ "strictness": "high",
48
+ "trials": 1,
49
+ "passes": 1,
50
+ "passRate": 1,
51
+ "passAtK": 1,
52
+ "grader": "trigger-rank-fork-family",
53
+ "status": "ran",
54
+ "deterministic": true
55
+ },
56
+ {
57
+ "id": "trigger-positive-4",
58
+ "kind": "trigger-positive",
59
+ "prompt": "Our lint step is red because of a ktlint violation in OrderMapper.kt -- can you clean it up?",
60
+ "strictness": "high",
61
+ "trials": 1,
62
+ "passes": 0,
63
+ "passRate": 0,
64
+ "passAtK": 0,
65
+ "grader": "trigger-rank-fork-family",
66
+ "status": "ran",
67
+ "deterministic": true
68
+ },
69
+ {
70
+ "id": "trigger-positive-5",
71
+ "kind": "trigger-positive",
72
+ "prompt": "`./gradlew test` won't even compile -- there's a type mismatch somewhere in the OrderServiceTest sources. Can you track it down?",
73
+ "strictness": "high",
74
+ "trials": 1,
75
+ "passes": 0,
76
+ "passRate": 0,
77
+ "passAtK": 0,
78
+ "grader": "trigger-rank-fork-family",
79
+ "status": "ran",
80
+ "deterministic": true
81
+ },
82
+ {
83
+ "id": "trigger-positive-6",
84
+ "kind": "trigger-positive",
85
+ "prompt": "NoSuchBeanDefinitionException on startup, fix this Spring build",
86
+ "strictness": "high",
87
+ "trials": 1,
88
+ "passes": 1,
89
+ "passRate": 1,
90
+ "passAtK": 1,
91
+ "grader": "trigger-rank-fork-family",
92
+ "status": "ran",
93
+ "deterministic": true
94
+ },
95
+ {
96
+ "id": "trigger-negative-1",
97
+ "kind": "trigger-negative",
98
+ "prompt": "npm install is failing with a peer dependency conflict",
99
+ "strictness": "high",
100
+ "trials": 1,
101
+ "passes": 1,
102
+ "passRate": 1,
103
+ "passAtK": 1,
104
+ "grader": "trigger-rank-fork-family",
105
+ "status": "ran",
106
+ "deterministic": true
107
+ },
108
+ {
109
+ "id": "trigger-negative-2",
110
+ "kind": "trigger-negative",
111
+ "prompt": "cargo build is failing for this Rust crate",
112
+ "strictness": "high",
113
+ "trials": 1,
114
+ "passes": 1,
115
+ "passRate": 1,
116
+ "passAtK": 1,
117
+ "grader": "trigger-rank-fork-family",
118
+ "status": "ran",
119
+ "deterministic": true
120
+ },
121
+ {
122
+ "id": "trigger-negative-3",
123
+ "kind": "trigger-negative",
124
+ "prompt": "go build is failing with an undefined symbol",
125
+ "strictness": "high",
126
+ "trials": 1,
127
+ "passes": 0,
128
+ "passRate": 0,
129
+ "passAtK": 0,
130
+ "grader": "trigger-rank-fork-family",
131
+ "status": "ran",
132
+ "deterministic": true
133
+ },
134
+ {
135
+ "id": "trigger-negative-4",
136
+ "kind": "trigger-negative",
137
+ "prompt": "Implement a new feature in this Spring service",
138
+ "strictness": "high",
139
+ "trials": 1,
140
+ "passes": 1,
141
+ "passRate": 1,
142
+ "passAtK": 1,
143
+ "grader": "trigger-rank-fork-family",
144
+ "status": "ran",
145
+ "deterministic": true
146
+ },
147
+ {
148
+ "id": "trigger-negative-5",
149
+ "kind": "trigger-negative",
150
+ "prompt": "Review this Spring diff for concurrency bugs",
151
+ "strictness": "high",
152
+ "trials": 1,
153
+ "passes": 1,
154
+ "passRate": 1,
155
+ "passAtK": 1,
156
+ "grader": "trigger-rank-fork-family",
157
+ "status": "ran",
158
+ "deterministic": true
159
+ },
160
+ {
161
+ "id": "trigger-negative-6",
162
+ "kind": "trigger-negative",
163
+ "prompt": "Write JUnit tests for this Spring controller",
164
+ "strictness": "high",
165
+ "trials": 1,
166
+ "passes": 1,
167
+ "passRate": 1,
168
+ "passAtK": 1,
169
+ "grader": "trigger-rank-fork-family",
170
+ "status": "ran",
171
+ "deterministic": true
172
+ },
173
+ {
174
+ "id": "no-suppresswarnings-suppression",
175
+ "kind": "behavior",
176
+ "prompt": "checkstyle is flagging an unused import in my Java code after a refactor. How should I fix it?",
177
+ "strictness": "high",
178
+ "trials": 10,
179
+ "passes": 10,
180
+ "passRate": 1,
181
+ "passAtK": 1,
182
+ "grader": "judge",
183
+ "status": "ran",
184
+ "trialRecords": [
185
+ {
186
+ "output": "Delete the unused import. That's it — checkstyle is correctly telling you the import is no longer referenced after your refactor, so removing it is the actual fix, not a workaround.\n\nConcretely:\n\n1. Open the file checkstyle flagged and find the line named in the finding (checkstyle reports the exact line, e.g. `import com.foo.Bar;`).\n2. Confirm nothing in the file still references `Bar`. Most IDEs can do this for you: IntelliJ has \"Optimize Imports\" (`Ctrl+Alt+O` / `Cmd+Option+O`), which removes unused imports and reorders the rest; Eclipse has \"Organize Imports\" (`Ctrl+Shift+O`).\n3. Just delete the line. If the import is the only one in a group/block, remove the blank line left behind too, so you don't trade one finding for a formatting one.\n4. Re-run the checkstyle task (`./gradlew checkstyleMain` or `mvn checkstyle:check`) to confirm it passes.\n\nTwo things not to do here, matching the rules above:\n\n- Don't suppress it — adding `// CHECKSTYLE:OFF`, a `checkstyle-suppressions.xml` entry, or `@SuppressWarnings(\"unused\")` just to silence this finding. The import genuinely is dead code; the finding is right.\n- Don't leave it in \"in case I need it later.\" If the refactor removed its only use, it should go; version control has it if you need to bring it back.\n\nOne caveat worth checking before deleting: if the import was a static import or something picked up by reflection/annotation processing rather than a normal reference (rare, but e.g. some annotation-processor setups or JSP/EL targets), verify nothing resolves it indirectly. For an ordinary unused type import after a refactor, though, deleting it is correct.\n\nIf your project auto-formats, the IDE action in step 2 will also leave imports in whatever order checkstyle's `ImportOrder`/`CustomImportOrder` rule expects, so you avoid a follow-up finding about ordering.",
187
+ "outputSha256": "5552bbbca2a5da81a724e21cfd59d776284bf8469c5ac6cca28d531b2a9df507",
188
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
189
+ "deterministic": [],
190
+ "judge": {
191
+ "verdict": "pass",
192
+ "reason": "Answer says 'Delete the unused import' and step 3 'Just delete the line', naming the specific import line (e.g. import com.foo.Bar;). It explicitly warns against CHECKSTYLE:OFF, suppressions.xml, and @SuppressWarnings as the fix. Step 4 re-runs checkstyle (./gradlew checkstyleMain) to confirm. No fail criteria hold."
193
+ },
194
+ "passed": true
195
+ },
196
+ {
197
+ "output": "Just delete the unused import. This is one of the few cases where \"fix what the checker found\" is literally the fix — checkstyle is correctly telling you the import is dead code left over from your refactor, and removing it is the smallest change that makes the finding go away without hiding anything. There's no suppression needed and no underlying logic issue to chase.\n\nA couple of things worth checking while you're in there:\n\n- **If it's a wildcard import** (`import com.foo.*;`) that's now partially unused, checkstyle's `UnusedImports` rule usually doesn't flag wildcards directly — but if you have an `AvoidStarImport` rule too, replace the wildcard with the specific classes you actually use rather than suppressing it.\n- **If the import is genuinely used but checkstyle still flags it** — that's unusual. The common cause is a same-package type where the import is redundant (checkstyle's `RedundantImport`), or an annotation that got moved. Verify the symbol is actually referenced before deleting; if it is, the problem is the rule config, not the import.\n- **If you use an IDE**, \"optimize imports\" on the affected file does the same thing and keeps the rest of the file's import ordering consistent with whatever checkstyle's `ImportOrder`/`CustomImportOrder` rule enforces.\n\nIf the finding is a star import or an ordering violation rather than a truly-unused import, fix that underlying issue the same way — don't add `// CHECKSTYLE:OFF` or a `suppressions.xml` entry for it. A suppression here just means the next stale import also slips through.\n\nOnce deleted, re-run the checkstyle task (and the build) to confirm it's clean.",
198
+ "outputSha256": "6f74a2b257cad79357adc91e078a55f40581a65f9d177943f8948d6b13b0a973",
199
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
200
+ "deterministic": [],
201
+ "judge": {
202
+ "verdict": "pass",
203
+ "reason": "Answer says 'Just delete the unused import' and 'removing it is the smallest change' — concrete removal of the specific import. It explicitly rejects suppression: 'no suppression needed', 'don't add // CHECKSTYLE:OFF or a suppressions.xml entry'. Ends with 're-run the checkstyle task (and the build) to confirm it's clean.' All criteria met."
204
+ },
205
+ "passed": true
206
+ },
207
+ {
208
+ "output": "Remove the unused import. That's the whole fix — checkstyle is correct here, and the refactor is what made the import unused.\n\nConcretely:\n\n1. Confirm it's actually unused — grep the file for the imported type name (not just the simple name, in case of a same-named local class). If the refactor moved the only usage out, it's dead.\n2. Delete the `import` line. If it was a wildcard import (`import java.util.*;`) that's now entirely unused, delete that too; if a wildcard is only partly used, prefer leaving it alone unless checkstyle specifically flags wildcards — don't expand a wildcard into N explicit imports as part of an \"unused import\" fix.\n3. Re-run the static-analysis task (`./gradlew checkstyleMain` or the configured `check`) and the build (`./gradlew build` / `mvn verify`). Both should exit 0.\n\nWhat not to do:\n\n- Don't add a suppression (`@SuppressWarnings`, a checkstyle `// CHECKSTYLE:OFF` block, or a suppression entry in the checkstyle config) to make the finding go away. The import is genuinely dead — there's nothing to suppress.\n- Don't \"use\" the import by adding a reference to the type just to keep it. That inverts the fix.\n- Don't touch anything beyond the unused import line(s). This is a one-line change; the smallest fix is the correct one.\n\nOne caveat worth checking before you delete: if the import is a static import used only inside a Javadoc `{@link}`/`{@value}` reference, some checkstyle configs (and some IDEs) disagree about whether that counts as usage. If the finding reappears or the Javadoc fails to resolve the link, the real fix is either qualifying the link with the fully-qualified name in the Javadoc or keeping the import and adjusting what the refactor left behind — not suppressing the finding.",
209
+ "outputSha256": "85228c854a40fff40eecfdc174c1b03b075163dc6e1ff38a0366203ecdcacaef",
210
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
211
+ "deterministic": [],
212
+ "judge": {
213
+ "verdict": "pass",
214
+ "reason": "Answer states fix is to delete the specific unused import line ('Delete the `import` line'), explicitly rejects @SuppressWarnings/CHECKSTYLE:OFF/config suppression, and names re-running checkstyleMain/check to confirm. No fail criterion holds."
215
+ },
216
+ "passed": true
217
+ },
218
+ {
219
+ "output": "An unused import is a checkstyle `UnusedImports` finding (or equivalent) — the fix is to remove the import, not to suppress the check.\n\nSince it's flagged *after a refactor*, that import is a leftover: the refactor removed the only reference to that class, but the `import` line stayed behind. So:\n\n1. **Confirm it's genuinely unused.** The compiler itself is the source of truth — an unused import produces no compile error, but your IDE and checkstyle both agree here. Check that the simple name isn't referenced anywhere in the file (watch for a same-named type in the same package, or a statically imported member — checkstyle has separate rules for static imports).\n\n2. **Delete the import line.** That's the whole fix. Leave everything else in the file untouched.\n\n3. **Re-run** `./gradlew check` (or `checkstyleMain`) and `./gradlew build` to confirm both exit 0.\n\nWhat *not* to do:\n\n- Don't add a suppression — a `// CHECKSTYLE:OFF`/`// CHECKSTYLE:IGNORE` comment or a checkstyle `suppressions.xml` entry for this file or rule. That's exactly the \"suppression instead of a fix\" move this build-fix workflow forbids: the import is dead code the refactor left behind, and silencing the check leaves it there.\n- Don't reorganize or reorder unrelated imports or touch other findings \"while you're in there\" — smallest change that fixes the stated root cause.\n\nOne thing worth a second look: if the import *looks* unused but the refactor was supposed to keep using that type, verify you didn't accidentally drop a real reference during the refactor. If so, the real fix is restoring the reference, not deleting the import — checkstyle caught a symptom of a bigger refactor mistake. If the type genuinely isn't needed anymore, delete the import.\n\nThen the report is one line: removed an unused `<ClassName>` import left over from the `<refactor>`; `check` and `build` both pass.",
220
+ "outputSha256": "b2edb8732129a799442af40268fb505308d7bc7d984176c622e977f662067908",
221
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
222
+ "deterministic": [],
223
+ "judge": {
224
+ "verdict": "pass",
225
+ "reason": "Step 2 says 'Delete the import line. That's the whole fix' — concrete removal of the specific unused import, not abstract cleanup. It explicitly warns against CHECKSTYLE:OFF/IGNORE and suppressions.xml as the fix. Step 3 names re-running ./gradlew check/checkstyleMain to confirm exit 0. No fail criterion holds."
226
+ },
227
+ "passed": true
228
+ },
229
+ {
230
+ "output": "Remove the import. If the import is genuinely unused after the refactor, deleting it is the correct fix — that's exactly what the checkstyle rule is telling you to do. Re-run `./gradlew check` (or `mvn verify`) afterward to confirm the finding is gone and nothing else broke.\n\nA couple of things worth checking before you just delete it, though:\n\n- **Is it actually unused?** A refactor can leave an import that looks unused in the body but is still needed — e.g., referenced only in a Javadoc `{@link Foo}`, in a `@throws`/`@see` tag, or via a static import used implicitly in a lambda/method reference. Checkstyle's `UnusedImports` has options like `processJavadoc` that decide whether Javadoc references count; if your config processes Javadoc and the import is only used there, leave it.\n- **Is it a static import?** Wildcard vs. single-member static imports can behave differently under the rule; make sure the symbol truly isn't referenced anywhere in the file.\n- **Did the refactor leave other dead code?** If the import became unused because a whole block moved out, there may be a now-unused field, private method, or local variable the same refactor created — checkstyle won't flag those, but they're part of the same cleanup and worth removing while you're there.\n\nDo **not** do any of these:\n\n- Add `// CHECKSTYLE:OFF` / `@SuppressWarnings(\"checkstyle:UnusedImports\")` or an IDE inspection suppression to make the warning disappear.\n- Add the import to a checkstyle suppression file or `suppressions.xml`.\n- Keep the import and assume \"it doesn't hurt.\"\n\nAn unused import is dead code — the checker caught a real leftover from your refactor. Deleting it is the smallest correct change and it's what the rule is for.\n\nOnce removed, run `./gradlew build` (or `mvn verify`) plus the checkstyle task to confirm exit 0, and report the one-line root cause, e.g.:\n\n```\nFixed: checkstyle UnusedImports on com.foo.LegacyHelper in OrderService.java\n - Root cause: refactor moved the helper call into OrderMapper; import left behind\n - Removed the import; ./gradlew check + test both pass\n```",
231
+ "outputSha256": "67e447ab6a90ae3300d21048fa5c21910cf27926e4d21044b04ab4b3edcb7677",
232
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
233
+ "deterministic": [],
234
+ "judge": {
235
+ "verdict": "pass",
236
+ "reason": "Answer states 'Remove the import' and 'deleting it is the correct fix' for the specific unused import (criterion 1). It explicitly warns against CHECKSTYLE:OFF/@SuppressWarnings/suppressions.xml (criterion 2). It names re-running './gradlew check'/'mvn verify' to confirm the finding is gone (criterion 3). No fail criterion holds."
237
+ },
238
+ "passed": true
239
+ },
240
+ {
241
+ "output": "Delete the unused import. That's the fix — checkstyle is correctly reporting that the import is no longer referenced after the refactor, so the import statement itself is dead code. Remove it in your IDE or by hand, then re-run the build task:\n\n```bash\n./gradlew build # or: mvn verify\n```\n\nA couple of notes so this doesn't become a suppression situation:\n\n- **Do not** add `// CHECKSTYLE:OFF`, a `@SuppressWarnings`, or a checkstyle suppression/baseline entry just to make the finding go away. An unused import is not a false positive here — it's exactly what the rule is for, and silencing it leaves dead code in place.\n- **Do not** infer that the refactor should keep the import \"just in case.\" If the symbol it referenced is genuinely still needed, the correct fix is to restore the *use*, not the bare import — but if `build` compiles cleanly after deletion, it wasn't needed.\n- Most IDEs (IntelliJ \"Optimize Imports\", VS Code \"Organize Imports\") will strip all unused imports in one pass, which is handy if the refactor orphaned several. Automated organizing can reorder imports though, so glance at the diff before committing if your project enforces an import-order rule too.\n\nWhoever reviews the change should see a one-line diff and a passing `./gradlew build` / static-analysis task — no suppressed findings.",
242
+ "outputSha256": "1dbff8fa6c2955e8fa12cbcbfc56c31d6d1f9b1f568acfeb1ae1108996794e19",
243
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
244
+ "deterministic": [],
245
+ "judge": {
246
+ "verdict": "pass",
247
+ "reason": "Answer says 'Delete the unused import' and 'Remove it in your IDE or by hand' — concrete removal of the specific import, not abstract cleanup. It explicitly warns against CHECKSTYLE:OFF/@SuppressWarnings/baseline as the fix. It names re-running './gradlew build' to confirm. No fail criterion holds."
248
+ },
249
+ "passed": true
250
+ },
251
+ {
252
+ "output": "Just delete the unused import. That's the whole fix — the refactor removed the code that referenced it, so the import is genuinely dead, and checkstyle is correct to flag it.\n\nDon't reach for a suppression (`@SuppressWarnings(\"unused\")`, a checkstyle `// CHECKSTYLE:OFF` block, or a `<suppress>` config entry) to silence the finding. That hides the real signal — an import that no longer has a purpose — without addressing it, and this skill's rules explicitly forbid suppression-as-fix.\n\nA couple of quick checks so you don't over- or under-fix:\n\n- Remove *only* the unused import named in the finding; don't touch imports that are still in use elsewhere in the file.\n- If your IDE can auto-organize imports, that's fine, but verify it didn't also strip an import that's needed only by an annotation, a Javadoc `{@link}`, or a static import — those can look unused to a naive scan but are actually referenced.\n- If the import turns out to be genuinely needed at runtime but the compiler says it's unused, that's a different problem (reflection, annotation processing, service-loader) — investigate rather than delete blindly.\n\nAfter the edit, re-run your build plus the checkstyle task:\n\n```\n./gradlew build checkstyleMain # or: mvn verify\n```\n\nAll should exit 0. Report it as: removed the now-dead import for `Foo` that the refactor orphaned; build and checkstyle pass. State the root cause (the refactor left it dangling), not just \"fixed the error.\"",
253
+ "outputSha256": "b555ef5ad63b3c7c0bf0455009a4d3e14c068ada54f6d78da1c63d8c0e238ce3",
254
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
255
+ "deterministic": [],
256
+ "judge": {
257
+ "verdict": "pass",
258
+ "reason": "Answer says 'Just delete the unused import' and 'Remove only the unused import named in the finding' (criterion 1). It explicitly warns against @SuppressWarnings/CHECKSTYLE:OFF/suppress config as the fix (criterion 2). It gives concrete re-run command './gradlew build checkstyleMain' expecting exit 0 (criterion 3). No fail criterion holds."
259
+ },
260
+ "passed": true
261
+ },
262
+ {
263
+ "output": "Delete the unused import. That's the fix.\n\nCheckstyle flagging an unused import after a refactor is a true positive — the import really is unused, and removing it is exactly the small, root-cause fix this skill calls for. There's no design issue to investigate here.\n\nA few practical notes:\n\n- **Don't suppress it.** Don't add a suppression comment, a checkstyle `// CHECKSTYLE:OFF` block, or a baseline/exclusion entry for that file. The finding is correct; silencing it leaves dead code in place and trains the next reader to ignore the checker.\n- **Don't keep it \"just in case.\"** An import that no longer has a reference is dead weight. If a future change needs it again, the IDE and compiler will re-add it in one keystroke.\n- **Use your tooling to be safe about it.** Most IDEs and several build/format tools can do this mechanically:\n - IntelliJ IDEA: `Ctrl+Alt+O` / `Cmd+Opt+O` (\"Optimize Imports\"). This removes unused imports and orders the rest, which also clears any checkstyle *import-ordering* rule you might trip next.\n - Eclipse: `Ctrl+Shift+O` (\"Organize Imports\").\n - Google Java Format / Spotless can strip unused imports if you have `removeUnusedImports()` configured — good for catching these automatically in CI going forward.\n- **Watch for wildcard imports while you're in there.** If the refactor removed the last *used* symbol from a `import com.foo.*;`, checkstyle may have a rule that prefers or forbids wildcards — handle that as its own finding rather than reverting to a wildcard to dodge the unused-import rule.\n\nThen verify: re-run the checkstyle task (e.g. `./gradlew checkstyleMain` or `mvn checkstyle:check`) and confirm it exits 0, and re-run `./gradlew build` / `mvn verify` so you know the removal didn't accidentally reference something that only compiled via a now-deleted import (rare, but a same-name symbol from two packages is the classic way an \"unused\" import turns out to be load-bearing).\n\nReport it plainly, e.g.: *\"Removed unused `import java.util.List;` from `OrderService.java` left over after the refactor; `checkstyleMain` and `build` both pass.\"*",
264
+ "outputSha256": "962f903dc4937771e3721ff2266329604ec80f291ed85a7f5ae478106e50bfa7",
265
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
266
+ "deterministic": [],
267
+ "judge": {
268
+ "verdict": "pass",
269
+ "reason": "Answer says 'Delete the unused import. That's the fix' and gives concrete example 'Removed unused import java.util.List; from OrderService.java'. Explicitly warns against suppression/CHECKSTYLE:OFF/baseline. Names re-running checkstyle (./gradlew checkstyleMain, mvn checkstyle:check) to confirm exit 0. All pass criteria hold; no fail criteria."
270
+ },
271
+ "passed": true
272
+ },
273
+ {
274
+ "output": "Delete the unused import. That's the fix — checkstyle is reporting something true, not a false positive you need to work around.\n\nIf a refactor left an import behind because the code that used it was moved, split, or removed, remove the now-dead import line. Don't touch anything else in the file.\n\nSpecifically, do **not**:\n\n- Add a `// CHECKSTYLE.OFF`/`CHECKSTYLE.ON` (or `@SuppressWarnings`) block around it.\n- Add an exclusion entry to the checkstyle config or a `SuppressionFilter` suppression for that file/rule.\n- Broaden the checkstyle config's `UnusedImports` rule, or disable it project-wide.\n\nAll of those silence the finding without fixing it, and they leave the dead import (and whatever other unused imports get added next time) in the tree.\n\nThe one thing worth double-checking before you delete: java doesn't require imports for same-package classes or `java.lang`, so if the refactor moved the class into the same package as the former import target, the import is genuinely dead and deleting it is correct. If the class is still referenced elsewhere in the file, that's a different failure (a real unresolved symbol, not an unused-import finding), and you'd be fixing the wrong thing — re-read the exact checkstyle message and the failing line first.\n\nVerification before you call it done: run the static-analysis task (`./gradlew checkstyleMain` / `checkstyle:check`, or `./gradlew check` / `mvn verify`) and confirm it exits 0, and rebuild/test so the delegation didn't break anything.",
275
+ "outputSha256": "46379dfd4ecae708e3b99b2a04106ffb1a6b32689fb685310acffe9a56cf9dfd",
276
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
277
+ "deterministic": [],
278
+ "judge": {
279
+ "verdict": "pass",
280
+ "reason": "Answer says 'Delete the unused import' and 'remove the now-dead import line' (criterion 1). It explicitly warns against @SuppressWarnings, CHECKSTYLE.OFF, and suppression/exclusion entries (criterion 2). It names re-running checkstyle ('./gradlew checkstyleMain' / 'checkstyle:check') and confirming exit 0 (criterion 3). No fail criterion holds."
281
+ },
282
+ "passed": true
283
+ },
284
+ {
285
+ "output": "Remove the import. An unused import is exactly what checkstyle's `UnusedImports` finding says it is — dead code that the refactor left behind. Deleting it is the actual fix, not a suppression. Concretely: delete the `import ...;` line the finding names, then re-run the build's checkstyle task to confirm it's clean.\n\nA couple of things worth checking while you're in there, since the finding came from a refactor:\n\n- **Same package, wildcard, or static imports.** Checkstyle's `UnusedImports` often can't see through wildcard imports (`import java.util.*;`) and may or may not flag an unused static import depending on config — so fix the one it named, then eyeball the file for sibling leftovers from the same refactor that it *couldn't* flag.\n- **Tighten the config option if it's noisy.** If your checkstyle config has `processJavadoc=false`, an import used only inside a Javadoc `{@link ...}` will be flagged as unused even though it's genuinely used. In that case the fix is `processJavadoc=true` in the config, not deleting an import the docs depend on — deleting it would break the Javadoc reference.\n- **Don't reach for `// CHECKSTYLE:OFF` / a `@SuppressWarnings` / adding the import to an ignore list.** The import is unused; the correct state has no import there at all. Suppressing it leaves the dead line in the file and trains the next reader to ignore the checker.\n\nSo: delete the named import, re-run `./gradlew check` (or `mvn checkstyle:check`), and confirm it exits 0 before calling it done. If the \"unused\" import is actually referenced only from Javadoc, fix the config flag instead — and say so, since that's the one case where the finding is a false positive rather than a real leftover.",
286
+ "outputSha256": "ceace5dc1b20a8bfcc1c852f27116ec9070e5b6f87d024fce364779d308ed63b",
287
+ "promptSha256": "2e169ee1fe3aee8333a3dd2494d3cd796c23b7dea882504569fe3c8ff2f1b19e",
288
+ "deterministic": [],
289
+ "judge": {
290
+ "verdict": "pass",
291
+ "reason": "Answer says 'Remove the import' and 'delete the `import ...;` line the finding names' (criterion 1). It explicitly warns against // CHECKSTYLE:OFF, @SuppressWarnings, and ignore lists (criterion 2). It says re-run checkstyle ('re-run the build's checkstyle task', './gradlew check') to confirm clean (criterion 3). No fail criterion holds."
292
+ },
293
+ "passed": true
294
+ }
295
+ ]
296
+ },
297
+ {
298
+ "id": "circular-bean-dependency",
299
+ "kind": "behavior",
300
+ "prompt": "Spring fails to start with BeanCurrentlyInCreationException -- OrderService and PaymentService each require the other in their constructors. How do I fix this?",
301
+ "strictness": "high",
302
+ "trials": 10,
303
+ "passes": 10,
304
+ "passRate": 1,
305
+ "passAtK": 1,
306
+ "grader": "judge",
307
+ "status": "ran",
308
+ "trialRecords": [
309
+ {
310
+ "output": "A constructor cycle between `OrderService` and `PaymentService` means Spring cannot fully construct either bean, because each needs the other already built. Fix it by breaking the actual dependency cycle, not by papering over it. In order of preference:\n\n**1. Extract the shared behavior into a third bean (preferred).**\nFind what each service actually needs from the other and move that logic into a new collaborator that both depend on — no cycle at all. For example, if `OrderService` calls `paymentService.charge()` and `PaymentService` calls `orderService.getOrderTotal()`, extract the pricing/lookup logic both need into an `OrderPricingService` (or similar) and have both `OrderService` and `PaymentService` depend on *it* instead of on each other:\n\n```java\n@Service\npublic class OrderPricingService {\n public Money getOrderTotal(OrderId id) { ... }\n}\n\n@Service\npublic class OrderService {\n private final OrderPricingService pricing;\n private final PaymentService payment;\n // ...\n}\n\n@Service\npublic class PaymentService {\n private final OrderPricingService pricing;\n // ...\n}\n```\n\n**2. Redefine the responsibility boundary.**\nOften a constructor cycle signals that one service is doing work that belongs to the other. If `PaymentService` only needs one piece of data from `OrderService` (a total, an ID, a status), pass that value in as a method parameter at the call site instead of injecting the whole service — this removes the constructor dependency entirely and is usually the cleanest fix.\n\n**3. Event-based decoupling (when the interaction is genuinely bidirectional).**\nIf `OrderService` needs to react to a payment result and `PaymentService` needs order data, publish a domain event (`OrderPlacedEvent`, `PaymentCompletedEvent`) and let each side listen, so neither holds a direct reference to the other.\n\n**Last resort — `@Lazy`:**\n`@Lazy` on one of the constructor parameters will let the context start by injecting a proxy that defers resolution:\n\n```java\n@Service\npublic class OrderService {\n private final PaymentService payment;\n public OrderService(@Lazy PaymentService payment) { this.payment = payment; }\n}\n```\n\nThis works, but it defers initialization rather than fixing the design that created a two-way dependency — it hides a coupling problem that tends to resurface as subtle ordering bugs. Use it only if the cycle is genuinely unavoidable, and state that reason explicitly.\n\n**Do not** try to \"fix\" this with field/setter injection purely to dodge the cycle, or by suppressing the exception — neither addresses why two services each need the other fully built before either can exist. Prefer option 1 or 2; they eliminate the cycle rather than tolerating it.\n\nAfter the change, verify with `./gradlew build` (or `mvn verify`) plus the test task, confirm the context starts, and confirm the static-analysis task still passes — then report the root cause in one sentence (which constructor dependency you removed and how).",
311
+ "outputSha256": "46d03b80897b526c7e3ffbc6c23670c15f75072b55680c81812e04fc063743d8",
312
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
313
+ "deterministic": [],
314
+ "judge": {
315
+ "verdict": "pass",
316
+ "reason": "Answer identifies the constructor cycle (\"each needs the other already built\"), makes breaking it primary via extracting OrderPricingService (option 1) and removing the constructor dependency by passing values as method params (option 2), and flags @Lazy explicitly as \"Last resort\" only \"if the cycle is genuinely unavoidable.\" No fail criterion holds."
317
+ },
318
+ "passed": true
319
+ },
320
+ {
321
+ "output": "A two-way constructor dependency means Spring can never fully construct either bean — it starts creating `OrderService`, needs a `PaymentService` to finish, starts creating `PaymentService`, needs an `OrderService` that isn't finished yet, and bails with `BeanCurrentlyInCreationException`. The fix is to break the actual cycle, not to work around it.\n\n**Preferred fix — extract the shared behavior into a third bean.** Look at *why* each service needs the other. Usually one of the two calls is really a shared concern that belongs in its own collaborator. Pull that concern into a new bean (e.g. `PaymentValidator`, `OrderPricingService`) that both `OrderService` and `PaymentService` depend on, and remove the direct edge between them. Now the graph is acyclic:\n\n```java\n@Service\npublic class OrderService {\n private final PaymentValidator paymentValidator; // not PaymentService\n // ...\n}\n\n@Service\npublic class PaymentService {\n private final PaymentValidator paymentValidator;\n // ...\n}\n```\n\n**Alternative — invert one direction.** If `OrderService` only needs to *observe* something `PaymentService` does, have `PaymentService` publish an event (`ApplicationEventPublisher`) that a listener on the order side consumes, instead of holding a direct reference. This removes the constructor edge the same way.\n\n**If — and only if — the cycle is genuinely unavoidable**, break it with `@Lazy` on one constructor parameter so Spring injects a proxy and defers resolution:\n\n```java\npublic OrderService(@Lazy PaymentService paymentService) { ... }\n```\n\nTreat this as a last resort. It defers bean initialization rather than fixing the design coupling that created the cycle, and it can hide initialization-order bugs. If you do use it, state in your report why the cycle could not be broken structurally.\n\n**Do not** try to fix this by making one of the dependencies `@Autowired` on a field or setter instead of the constructor just to dodge the exception — that's the same deferral as `@Lazy` with worse discoverability, and it leaves the circular design intact.\n\nAfter the change, re-run `./gradlew build` (or `mvn verify`) and the test task; both must exit 0, and the change should touch only the files involved in the cycle.",
322
+ "outputSha256": "44fe1d962f3d4f3b0b0d4a3226a21143c63b0d55d737e9020d166d762bdca43f",
323
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
324
+ "deterministic": [],
325
+ "judge": {
326
+ "verdict": "pass",
327
+ "reason": "Answer explains the two-way constructor cycle (OrderService needs PaymentService and vice versa), makes breaking the cycle primary via extracting shared behavior into a third bean (PaymentValidator) with code, plus event-based inversion alternative. @Lazy is explicitly framed as 'If — and only if — the cycle is genuinely unavoidable' and 'last resort', not default. No fail criterion holds."
328
+ },
329
+ "passed": true
330
+ },
331
+ {
332
+ "output": "A circular constructor dependency like this means Spring genuinely cannot build either bean: to construct `OrderService` it needs a fully-built `PaymentService`, and to construct `PaymentService` it needs a fully-built `OrderService`. There is no ordering that satisfies both, so it fails fast with `BeanCurrentlyInCreationException`.\n\n**Fix the cycle, don't paper over it.** The cleanest fix is to find what `OrderService` and `PaymentService` actually need from each other and break the two-way dependency:\n\n- If one direction is only used for a single operation, extract that shared behavior into a third bean that both depend on (e.g. a `PaymentProcessor` or a domain event publisher). Both services then depend on `Processor`/`Publisher`, and there's no cycle.\n- If `PaymentService` only needs to notify `OrderService` after a payment completes (a one-way reaction), invert it: publish a domain event and have `OrderService` (or a listener) react to it, instead of `PaymentService` holding a reference to `OrderService`.\n- If one of the two truly does not need the other at construction time, remove it from the constructor and inject it via a setter/`ObjectProvider` only where it's used.\n\n**`@Lazy` is a last resort, not the default.** Adding `@Lazy` to one of the constructor parameters *does* make the context start (Spring injects a proxy and defers resolution until first use), but it defers the problem rather than fixing the design that created a two-way construction dependency. Use it only when the cycle is genuinely unavoidable — for example a legacy boundary you cannot refactor safely right now — and say so explicitly in the report, along with why the refactor wasn't feasible.\n\n**Verify:** `./gradlew build` and `./gradlew test` must both exit 0, and the project's static-analysis task if configured. Don't consider it fixed until the context starts without the cycle.\n\nIn short: extract the shared behavior into a third bean or invert the dependency direction; reach for `@Lazy` only as a stated last resort.",
333
+ "outputSha256": "dcd7eb67657242d8c8a2d1feac7dca88dcdb32f4489b5aceed08e8f91387ff4e",
334
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
335
+ "deterministic": [],
336
+ "judge": {
337
+ "verdict": "pass",
338
+ "reason": "Answer identifies the cycle (\"to construct OrderService it needs a fully-built PaymentService, and vice versa\"), breaks it primarily via third bean (PaymentProcessor/Publisher), event inversion, or removing constructor dep (setter/ObjectProvider). @Lazy explicitly framed as \"last resort, not the default\" for genuinely unavoidable cycles. No fail criterion holds."
339
+ },
340
+ "passed": true
341
+ },
342
+ {
343
+ "output": "A `BeanCurrentlyInCreationException` here means the two constructor-injected beans form a genuine circular dependency: Spring cannot finish creating `OrderService` without a fully-built `PaymentService`, and cannot finish `PaymentService` without a fully-built `OrderService`. Fix it by breaking the actual design cycle, not by papering over it.\n\n**First, understand the cycle.** Ask what each class really needs from the other:\n\n- Does `OrderService` need `PaymentService` at construction time, or only inside a method?\n- Does `PaymentService` need `OrderService`, or does it actually just need some slice of data/behavior that `OrderService` happens to hold?\n\n**Preferred fix — extract the shared dependency into a third bean.** If the cycle exists because both services genuinely share behavior, that shared thing belongs in its own collaborator:\n\n```kotlin\n@Component\nclass OrderPricingCalculator { // the piece both actually needed\n fun price(order: Order): Money = ...\n}\n\n@Component\nclass OrderService(private val pricing: OrderPricingCalculator) { ... }\n\n@Component\nclass PaymentService(private val pricing: OrderPricingCalculator) { ... }\n```\n\nBoth depend on the new bean; neither depends on the other. This is the fix that reflects what the code actually means.\n\n**Second option — invert one direction.** If `PaymentService` only needs to notify/report back to `OrderService`, introduce an interface that `OrderService` implements and inject that interface into `PaymentService`, or emit an event (`ApplicationEventPublisher`) instead of holding a direct reference. The dependency then points one way.\n\n**Third option — drop one dependency from the constructor.** If one side only uses the other inside a method, extract the call site behind a small interface or pass the needed value as a method argument, so the constructor no longer references the other bean.\n\n**When `@Lazy` is acceptable.** Marking one injection point `@Lazy` makes Spring inject a proxy that resolves on first real use, which breaks the construction-time cycle:\n\n```kotlin\nclass OrderService(@Lazy private val paymentService: PaymentService) { ... }\n```\n\nTreat this strictly as a last resort: it defers initialization rather than removing the two-way coupling, and it often hides a design problem. If you use it, state in your report exactly why the cycle is unavoidable and why extraction/inversion was not viable.\n\n**What not to do.** Don't add field/setter injection purely to dodge the exception (`@Autowired lateinit var`), and don't add suppressions or blank `@Configuration` tricks — those defer or mask the cycle without changing the architecture that created it.\n\n**Verify:** after the change, `./gradlew build` (or `mvn verify`) and the test task must both exit 0, and the report should name the root cause in one sentence — e.g. \"extracted shared pricing logic into `OrderPricingCalculator`, removing the `OrderService` ↔ `PaymentService` two-way constructor dependency.\"",
344
+ "outputSha256": "d1615998b57a618ffb6a93a376253aa15055a7d8f09c2f2b297d4c7f3e604af2",
345
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
346
+ "deterministic": [],
347
+ "judge": {
348
+ "verdict": "pass",
349
+ "reason": "Answer identifies the constructor cycle (\"cannot finish creating OrderService without a fully-built PaymentService...\"), makes extraction into a third bean (OrderPricingCalculator) the preferred fix, adds inversion/event and dropping the constructor dependency as alternatives, and explicitly frames @Lazy as \"strictly a last resort\" that \"defers initialization rather than removing the two-way coupling.\" No fail criterion holds."
350
+ },
351
+ "passed": true
352
+ },
353
+ {
354
+ "output": "A `BeanCurrentlyInCreationException` from two beans that each require the other in their constructors is a real circular dependency in the design, not a Spring quirk to suppress. Spring cannot instantiate either bean because each needs the other already fully constructed first — constructor injection makes this unavoidable (there is no half-built object to pass around), unlike field/setter injection where the cycle can sometimes resolve.\n\nFix it by breaking the actual cycle, in order of preference:\n\n1. **Extract the shared behavior into a third bean.** Find what each service actually needs from the other. Usually OrderService calls something on PaymentService and PaymentService calls something on OrderService, and that mutual piece is really a distinct responsibility. Pull it into a new collaborator (e.g. `OrderPaymentCoordinator`, or a smaller `PaymentLookup`/`OrderLookup` interface) that both depend on. Now neither depends on the other, and the cycle is gone at the design level.\n\n2. **Move one dependency to be needed at call time, not construction time.** If PaymentService only needs OrderService for one operation, consider whether it should really receive the data it needs as a method parameter instead of holding the whole service. Passing the order (or an order id / a narrow interface) into the method that needs it removes the constructor edge entirely.\n\n3. **Introduce an event or an interface boundary.** If the two are reacting to each other's state changes, a Spring `ApplicationEventPublisher`/`@EventListener` or a small interface implemented by one side (so the other depends on the abstraction, not the concrete bean) decouples them.\n\nWhat NOT to do as the first move:\n\n- **Don't slap `@Lazy` on one constructor parameter.** It works by injecting a proxy that delays resolution until first use, but it just defers the cycle — it hides the two-way design coupling rather than removing it, and it can turn a startup error into a confusing late failure. It is acceptable only as a genuine last resort, and only if you state in the report why the cycle is truly unavoidable.\n- **Don't switch to field/setter injection just to let the cycle \"work.\"** That trades a clear startup failure for a fragile runtime arrangement with the same underlying coupling.\n\nSo: reproduce and confirm the exact cycle edges from the stack trace/bean names, extract the shared responsibility (or move the dependency to call time), then re-run `./gradlew build` and the test task until both exit 0. Report it as, e.g.: \"Fixed `BeanCurrentlyInCreationException` between OrderService and PaymentService — both required each other in constructors; extracted the shared `PaymentOrderLookup` into a third bean, removing the cycle. `build`/`test` pass.\" State the root cause in one sentence, not just \"it starts now.\"",
355
+ "outputSha256": "1b848d2a1302abb3866f91459b7a2753ddaa6e36c32f1f78d0b9c12f6a9a14e3",
356
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
357
+ "deterministic": [],
358
+ "judge": {
359
+ "verdict": "pass",
360
+ "reason": "Answer identifies the constructor cycle (\"each needs the other already fully constructed first\"), makes breaking the cycle primary via three concrete fixes (extract shared behavior into third bean like OrderPaymentCoordinator/PaymentLookup, move dependency to call time, event/interface boundary), and treats @Lazy explicitly as a last resort (\"acceptable only as a genuine last resort\"), warning against it as first move."
361
+ },
362
+ "passed": true
363
+ },
364
+ {
365
+ "output": "A `BeanCurrentlyInCreationException` here means the container literally cannot construct either bean, because each one's constructor needs the other fully built first. There's no ordering that satisfies both, so no amount of `@Lazy` annotation-tweaking changes the underlying fact: the two beans have a genuine two-way design dependency that the constructor-injection contract forbids.\n\nFix it by breaking the cycle, in order of preference:\n\n**1. Extract the shared behavior into a third bean.** This is the right fix whenever the cycle exists because OrderService and PaymentService both genuinely need the *same* logic from each other. Find what OrderService calls on PaymentService and vice-versa; if either call is really reaching for a piece of shared policy (e.g. how an order is priced, or what makes a payment valid), pull that piece into a new `PaymentPolicy`/`OrderValidator`/etc. bean that both depend on. Now the graph is a DAG: OrderService → PaymentPolicy, PaymentService → PaymentPolicy, and no cycle.\n\n**2. Move one dependency out of the constructor.** If the cycle exists because one side only needs the other *sometimes* (a method call triggered by a specific request or event), inject it as a setter/field dependency instead of a constructor parameter, or fetch it lazily at the call site via `ObjectProvider<PaymentService>`. This breaks the construction-time cycle without deferring initialization by magic — the dependency becomes genuinely optional/on-demand, which is what it actually is.\n\n**3. Refactor so one direction is no longer needed.** Often a cycle like this means the responsibility is split wrong — e.g. PaymentService is calling back into OrderService to update order state, which is really OrderService's job to drive. Inverting that call (PaymentService returns a result, OrderService applies it) removes one edge of the cycle entirely and is usually the better design.\n\n**`@Lazy` is the last resort, and only with a stated reason.** Annotating one constructor parameter `@Lazy PaymentService paymentService` makes Spring inject a proxy and defers actual resolution until first use, which does let the context start. But it doesn't fix anything — it hides a real two-way dependency behind a proxy, and you'll rediscover it the moment something calls a lazy method during startup (e.g. an `@PostConstruct` that touches PaymentService) or during the first request. Use it only if a full refactor genuinely isn't possible right now, and say so explicitly in your change/report rather than presenting it as the fix.\n\nA concrete check before you commit: open both classes and ask \"which of these two calls is really the same responsibility as the other?\" Cycles between `OrderService` and `PaymentService` are almost always a call like `paymentService.markPaid(order)` from OrderService paired with `orderService.getOrder(id)` from PaymentService — the second call is the one that usually doesn't belong, and moving that lookup to the caller removes the cycle cleanly. Verify with `./gradlew build` (context starts, tests pass) and make sure the report states which edge you removed and why, not just \"added @Lazy and it starts.\"",
366
+ "outputSha256": "6eed93d3a772451609969b7216535d231cd52a38b107e735ba9a649f6d6029b0",
367
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
368
+ "deterministic": [],
369
+ "judge": {
370
+ "verdict": "pass",
371
+ "reason": "Answer identifies the genuine two-way constructor cycle (\"each one's constructor needs the other fully built first\"). Primary fixes break the cycle: extract shared behavior into a third bean (PaymentPolicy/OrderValidator), move one dependency out of constructor via setter/ObjectProvider, and refactor/invert one direction. @Lazy is explicitly framed as \"the last resort, and only with a stated reason,\" not the default."
372
+ },
373
+ "passed": true
374
+ },
375
+ {
376
+ "output": "Short answer: this is a circular constructor dependency — `OrderService` needs a `PaymentService` instance to be constructed, which needs an `OrderService` instance to be constructed, and Spring can't satisfy either without the other first. Constructor injection makes the cycle a hard failure (unlike field/setter injection, where Spring can at least hand out a half-built proxy), which is exactly why it surfaces as `BeanCurrentlyInCreationException` at startup rather than a silent misconfiguration.\n\nThe fix is to break the actual dependency, not to hide the error. In priority order:\n\n**1. Extract the shared behavior into a third bean.** Look at what each service actually calls on the other. Often the true dependency isn't \"OrderService needs all of PaymentService\" — it's one method, or a piece of data that both use. Pull that method/data into a third class (e.g. `PaymentSettlementPolicy`, `OrderContext`, `PricingCalculator`) that both services depend on instead of depending on each other. This is the correct fix when the cycle reflects a genuine shared responsibility that was misplaced into one of the two services.\n\n**2. Reassign the responsibility so the dependency flows one way.** If `OrderService` calls `PaymentService` only to record a payment, and `PaymentService` calls `OrderService` only to look up an order, split those into a unidirectional chain — e.g. `OrderService` → `PaymentService` → `OrderRepository` (not `OrderService`). Very often the \"cycle\" is really a missing repository/port layer: one side is reaching back up a layer to fetch something it should get from a lower-level component.\n\n**3. Use events if one direction is a notification, not a query.** If `PaymentService` only needs to notify `OrderService` that a payment succeeded (a fire-and-forget reaction), replace that constructor dependency with a Spring application event — `PaymentService` publishes, `OrderService` has an `@EventListener` — or a message/queue boundary. That removes the construction-time edge entirely.\n\n**4. `@Lazy` — last resort only.** If you inject one side as `@Lazy` (either on the constructor parameter, e.g. `OrderService(@Lazy PaymentService paymentService)`, or on the bean), Spring defers instantiating the other bean until first use, so construction no longer blocks. This defers the cycle rather than resolving the design that created it. Only use it when the cycle is genuinely unavoidable and the two beans are, for real, mutually dependent at runtime — and say so in your report with the reason. Do not make it your first move.\n\nWhat not to do:\n\n- Do not convert the constructor injection to field injection (`@Autowired private PaymentService paymentService;`) just to dodge the exception. That makes the cycle resolvable via a proxy, so startup goes green while the underlying coupling remains — worse than fixing it, because it's now invisible.\n- Do not delete one of the two dependencies just to compile; verify it really wasn't needed by checking the call sites first.\n\nTypical correct outcome: extract the shared piece into a third bean (option 1) or split the layering (option 2), resulting in a one-directional dependency graph. Re-run `./gradlew build` (or `mvn verify`) plus the test task afterward and confirm both exit 0, then state the root cause in the report — e.g. \"OrderService and PaymentService had a two-way constructor dependency; extracted settlement logic into a shared SettlementPolicy bean so PaymentService no longer depends on OrderService.\"",
377
+ "outputSha256": "1bd3a9edcb927133ca1c38136937ecc91d3810a619576497807783ce5608345d",
378
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
379
+ "deterministic": [],
380
+ "judge": {
381
+ "verdict": "pass",
382
+ "reason": "Answer states the cycle: 'OrderService needs a PaymentService instance to be constructed, which needs an OrderService instance' (crit 1). Primary fixes are breaking the cycle: option 1 extract shared behavior into third bean (PaymentSettlementPolicy), option 2 reassign responsibility/unidirectional chain, option 3 events (crit 2). @Lazy is explicitly 'last resort only', 'defers the cycle rather than resolving', 'Do not make it your first move' (crit 3). No fail criterion."
383
+ },
384
+ "passed": true
385
+ },
386
+ {
387
+ "output": "A `BeanCurrentlyInCreationException` for `OrderService` and `PaymentService` means Spring genuinely cannot build either bean first: constructing `OrderService` needs a fully-built `PaymentService`, and constructing `PaymentService` needs a fully-built `OrderService`. That's a real two-way construction dependency, not a scanning or qualifier problem — so the fix is to break the cycle in design, not to paper over it.\n\n**Preferred fix: extract the shared behavior into a third bean.** Look at *why* each service needs the other. Usually there is a shared piece of logic (pricing, validation, notification, persistence of a shared entity) that both call. Pull that into its own bean, e.g. `OrderPaymentCoordinator`, and have both services depend on it instead of on each other. This is the correct fix because it removes the cycle rather than deferring it — the resulting dependency graph is acyclic.\n\n**Alternative: break the cycle by removing one construction-time dependency.** If the coupling is really just \"OrderService needs to call back into PaymentService in a method, not at construction,\" then one side doesn't actually need the other in its constructor. Refactor so the calling direction is one-way (e.g. `OrderService` depends on `PaymentService`; `PaymentService` receives the order data it needs as method parameters rather than holding an `OrderService` reference), or invert the callback with an event publisher (`ApplicationEventPublisher` / `@EventListener`) so neither holds a direct reference to the other.\n\n**Last resort: `@Lazy`.** You can put `@Lazy` on one side of the constructor injection (or use `@Lazy` on the parameter) so Spring injects a proxy and defers initialization. This does start the context. But it only *defers* the cycle — you now have a proxy that resolves the dependency on first use, which hides the design problem and can turn a startup failure into a runtime failure or a stack-overflow-on-reentrancy further down the line. Reach for it only when the cycle is genuinely unavoidable (e.g. two framework/legacy beans you can't restructure), and state that reason explicitly in your report. Do not use it as the first move.\n\nDo **not** \"fix\" this by adding `@SuppressWarnings`, deleting a bean, or disabling a context test — none of those address the cycle, and disabling the test just hides the failure.\n\n**Verify:** re-run `./gradlew build` (or `mvn verify`) and confirm the context actually starts and the test task exits 0.\n\n**Report format:**\n\n```\nFixed: BeanCurrentlyInCreationException between OrderService and PaymentService\n - Root cause: both services required the other in their constructors\n - Extracted shared X logic into Y; dependency graph is now acyclic\n - ./gradlew build/test both pass\n```\n\nIf you had to fall back to `@Lazy`, say so and give the specific reason the cycle couldn't be broken structurally.",
388
+ "outputSha256": "1489d66007df022f0fbfc617f004cf9ddcec1b1368fe70e454316543a459feb9",
389
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
390
+ "deterministic": [],
391
+ "judge": {
392
+ "verdict": "pass",
393
+ "reason": "Answer states the real two-way construction dependency (\"constructing OrderService needs a fully-built PaymentService, and constructing PaymentService needs a fully-built OrderService\"). Primary fix is extracting shared behavior into a third bean (OrderPaymentCoordinator), plus alternative removing one constructor dependency/event publisher. @Lazy is explicitly flagged as last resort only for genuinely unavoidable cycles, not default."
394
+ },
395
+ "passed": true
396
+ },
397
+ {
398
+ "output": "`BeanCurrentlyInCreationException` here means a genuine constructor cycle: `OrderService` needs a `PaymentService` instance to be constructed, and `PaymentService` needs an `OrderService` instance to be constructed, so neither can ever be built first.\n\n**Default fix: break the actual cycle, don't paper over it.**\n\nFind the shared behavior that creates the two-way dependency and extract it into a third bean. If, say, `OrderService` only needs `PaymentService` to look up a payment status, and `PaymentService` only needs `OrderService` to record an order reference, that bidirectional coupling is usually doing too much in both classes. Pull the shared piece into its own collaborator:\n\n```java\n@Component\nclass PaymentOrderLink {\n private final PaymentRepository payments;\n private final OrderRepository orders;\n // methods both services actually need\n}\n```\n\nThen `OrderService` depends on `PaymentService` (or neither), `PaymentService` depends on `PaymentOrderLink`, and the cycle is gone at the design level rather than deferred.\n\nAn alternative when the cycle is only needed at runtime, not construction time: move one side to a setter/field injection (`@Autowired` on a non-constructor field or setter). That removes the construction-order constraint without changing the dependency graph's meaning — legitimate when the two genuinely must reference each other but never during each other's initialization.\n\n**Last resort: `@Lazy`.**\n\n```java\npublic OrderService(@Lazy PaymentService paymentService) { ... }\n```\n\nThis makes Spring inject a proxy, deferring the real bean's creation until first use, so construction succeeds. It is acceptable only when the cycle is genuinely unavoidable, and the report must say so and explain why — it defers the problem rather than fixing the design that created a two-way dependency, and it can turn a startup error into a runtime one if the deferred call happens during some later initialization.\n\n**How to tell which you have.** Map the cycle with the context's startup log or a quick dependency graph of your `@Component`s. If the two-way reference exists only because a few methods were placed on the wrong class, extraction is correct. If one side is genuinely only used post-construction, setter injection is correct. Only reach for `@Lazy` when neither applies and you can state the reason.\n\nDo not \"fix\" this by deleting one of the dependencies if it is actually needed, and do not add `@Lazy` by default just because it makes startup succeed.",
399
+ "outputSha256": "b5cccca20b125ef2eb68b746f72c1cd7e38db110a74e8f9595ed1bee6ea1f5f6",
400
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
401
+ "deterministic": [],
402
+ "judge": {
403
+ "verdict": "pass",
404
+ "reason": "Answer states the genuine constructor cycle (each needs the other to be constructed), makes breaking the cycle the default fix via extracting shared behavior into a third bean (PaymentOrderLink) or setter injection, and explicitly frames @Lazy as a last resort only when the cycle is genuinely unavoidable, warning it defers rather than fixes."
405
+ },
406
+ "passed": true
407
+ },
408
+ {
409
+ "output": "Break the cycle in the design, don't defer it with `@Lazy`.\n\n**Root cause:** `OrderService`'s constructor needs `PaymentService`, and `PaymentService`'s constructor needs `OrderService`. Spring can't construct either one first, so it fails with `BeanCurrentlyInCreationException`. This is a two-way dependency between beans — the classic symptom of behavior that belongs in neither of them (or of one of the dependencies being unnecessary at construction time).\n\n**Preferred fix — extract the shared behavior.** If both classes need the *same* collaborator to do their job (e.g. both call into the other to compute a total, notify, or look up state), pull that shared logic into a third bean and have both depend on *it* instead of on each other:\n\n```java\n@Component\nclass OrderPaymentCoordinator { // the shared behavior\n private final PaymentGateway gateway;\n // ...\n Receipt settle(Order order) { ... }\n}\n\n@Component\nclass OrderService {\n private final OrderPaymentCoordinator coordinator; // no PaymentService\n}\n\n@Component\nclass PaymentService {\n private final OrderPaymentCoordinator coordinator; // no OrderService\n}\n```\n\nNow the graph is a DAG: both depend on the coordinator, which depends on neither. Nothing is deferred.\n\n**Alternative fix — drop one construction-time dependency.** If the dependency isn't actually needed to *construct* the bean, remove it from the constructor: move it to a method parameter, resolve it lazily at call time via `ObjectProvider<PaymentService>` / `ApplicationContext`, or use a setter/field injection that Spring can wire after construction. Pick this only when the dependency genuinely isn't needed up front.\n\n**`@Lazy` — last resort only.** Annotating one side with `@Lazy` injects a proxy and defers resolution, so the build goes green. But it does not fix the two-way coupling; it hides it, and you'll pay for it later when the deferred call first fires. Use it only if the cycle is genuinely unavoidable, and state that reason explicitly in your report.\n\n**What not to do:** don't \"fix\" this by deleting one of the two dependencies if it's actually used, and don't reach for `@Lazy` as the first move.\n\n**Verify:** re-run `./gradlew build` (or `mvn verify`) plus the test task — both must exit 0 — and confirm the container starts. In the report, name the root cause in one sentence (e.g. \"two-way constructor dependency between OrderService and PaymentService\") rather than just \"context now starts.\"",
410
+ "outputSha256": "16a24d28459ad8d30823eaa050f2c2871a5dc45ef0eca8d2b11729f7532861db",
411
+ "promptSha256": "d538d07a7142c3a2c466b0047530c976b58f8bde970f29df5e9e7a9f5fe5270c",
412
+ "deterministic": [],
413
+ "judge": {
414
+ "verdict": "pass",
415
+ "reason": "Answer identifies the two-way constructor cycle (OrderService needs PaymentService and vice versa), makes breaking the cycle primary via a third bean OrderPaymentCoordinator with code showing both depending on it, offers dropping one constructor dependency as alternative, and explicitly frames @Lazy as last resort only for genuinely unavoidable cycles, warning against it as first move."
416
+ },
417
+ "passed": true
418
+ }
419
+ ]
420
+ }
421
+ ],
422
+ "verdict": "fail",
423
+ "scope": "bundled",
424
+ "skillDigest": "f3bbba21dd6e111dcfb640b6643e818b2dcd2378ab0b56894d49dabbfb3c5a6c",
425
+ "catalogDigest": "d09b13e321c66a435263da60f337e323760ef9d3d394b30d1ab3f41817a01f39",
426
+ "judgePromptVersion": "2026-09-25.1",
427
+ "runner": "deepseek",
428
+ "model": "deepseek-chat",
429
+ "runnerPromptVersion": "2026-09-25.1",
430
+ "recordedAt": "2026-09-25T20:56:45.051Z",
431
+ "judge": "deepseek",
432
+ "judgeModel": "deepseek-chat"
433
+ },
434
+ {
435
+ "schemaVersion": "1.0.0",
436
+ "skillId": "java-kotlin-spring/java-kotlin-spring-code-review",
437
+ "strictness": "high",
438
+ "trials": 10,
439
+ "triggerAccuracy": {
440
+ "truePositive": 0,
441
+ "falsePositive": 2,
442
+ "positives": 6,
443
+ "negatives": 6
444
+ },
445
+ "evidence": "authored",
446
+ "scenarios": [
447
+ {
448
+ "id": "trigger-positive-1",
449
+ "kind": "trigger-positive",
450
+ "prompt": "Our PR uses @Autowired directly on fields in three new classes -- is that actually a problem, or just a style nit?",
451
+ "strictness": "high",
452
+ "trials": 1,
453
+ "passes": 0,
454
+ "passRate": 0,
455
+ "passAtK": 0,
456
+ "grader": "trigger-rank-fork-family",
457
+ "status": "ran",
458
+ "deterministic": true
459
+ },
460
+ {
461
+ "id": "trigger-positive-2",
462
+ "kind": "trigger-positive",
463
+ "prompt": "I have a public method calling another @Transactional method on the same class instance -- will the proxy even pick that up, or is this a bug waiting to happen?",
464
+ "strictness": "high",
465
+ "trials": 1,
466
+ "passes": 0,
467
+ "passRate": 0,
468
+ "passAtK": 0,
469
+ "grader": "trigger-rank-fork-family",
470
+ "status": "ran",
471
+ "deterministic": true
472
+ },
473
+ {
474
+ "id": "trigger-positive-3",
475
+ "kind": "trigger-positive",
476
+ "prompt": "This controller method returns the JPA entity straight from findById -- is that going to bite us on the API contract, and how do I spot other cases like it?",
477
+ "strictness": "high",
478
+ "trials": 1,
479
+ "passes": 0,
480
+ "passRate": 0,
481
+ "passAtK": 0,
482
+ "grader": "trigger-rank-fork-family",
483
+ "status": "ran",
484
+ "deterministic": true
485
+ },
486
+ {
487
+ "id": "trigger-positive-4",
488
+ "kind": "trigger-positive",
489
+ "prompt": "Our order list endpoint is issuing one query per row instead of one for the whole list -- where in this repository is that coming from?",
490
+ "strictness": "high",
491
+ "trials": 1,
492
+ "passes": 0,
493
+ "passRate": 0,
494
+ "passAtK": 0,
495
+ "grader": "trigger-rank-fork-family",
496
+ "status": "ran",
497
+ "deterministic": true
498
+ },
499
+ {
500
+ "id": "trigger-positive-5",
501
+ "kind": "trigger-positive",
502
+ "prompt": "Does this incoming request DTO in Kotlin actually get validated before it reaches the service layer, or did we skip the annotations somewhere?",
503
+ "strictness": "high",
504
+ "trials": 1,
505
+ "passes": 0,
506
+ "passRate": 0,
507
+ "passAtK": 0,
508
+ "grader": "trigger-rank-fork-family",
509
+ "status": "ran",
510
+ "deterministic": true
511
+ },
512
+ {
513
+ "id": "trigger-positive-6",
514
+ "kind": "trigger-positive",
515
+ "prompt": "I inherited this SecurityFilterChain setup and I'm worried some endpoints are wide open that shouldn't be -- can you take a look?",
516
+ "strictness": "high",
517
+ "trials": 1,
518
+ "passes": 0,
519
+ "passRate": 0,
520
+ "passAtK": 0,
521
+ "grader": "trigger-rank-fork-family",
522
+ "status": "ran",
523
+ "deterministic": true
524
+ },
525
+ {
526
+ "id": "trigger-negative-1",
527
+ "kind": "trigger-negative",
528
+ "prompt": "Review this Spring Boot diff and also fix the bugs you find",
529
+ "strictness": "high",
530
+ "trials": 1,
531
+ "passes": 0,
532
+ "passRate": 0,
533
+ "passAtK": 0,
534
+ "grader": "trigger-rank-fork-family",
535
+ "status": "ran",
536
+ "deterministic": true
537
+ },
538
+ {
539
+ "id": "trigger-negative-2",
540
+ "kind": "trigger-negative",
541
+ "prompt": "Review this Python Django code for SQL injection",
542
+ "strictness": "high",
543
+ "trials": 1,
544
+ "passes": 1,
545
+ "passRate": 1,
546
+ "passAtK": 1,
547
+ "grader": "trigger-rank-fork-family",
548
+ "status": "ran",
549
+ "deterministic": true
550
+ },
551
+ {
552
+ "id": "trigger-negative-3",
553
+ "kind": "trigger-negative",
554
+ "prompt": "Review this NestJS controller for missing guards",
555
+ "strictness": "high",
556
+ "trials": 1,
557
+ "passes": 1,
558
+ "passRate": 1,
559
+ "passAtK": 1,
560
+ "grader": "trigger-rank-fork-family",
561
+ "status": "ran",
562
+ "deterministic": true
563
+ },
564
+ {
565
+ "id": "trigger-negative-4",
566
+ "kind": "trigger-negative",
567
+ "prompt": "Implement a bounded worker pool in this Spring service",
568
+ "strictness": "high",
569
+ "trials": 1,
570
+ "passes": 1,
571
+ "passRate": 1,
572
+ "passAtK": 1,
573
+ "grader": "trigger-rank-fork-family",
574
+ "status": "ran",
575
+ "deterministic": true
576
+ },
577
+ {
578
+ "id": "trigger-negative-5",
579
+ "kind": "trigger-negative",
580
+ "prompt": "Review this Go diff for goroutine leaks",
581
+ "strictness": "high",
582
+ "trials": 1,
583
+ "passes": 1,
584
+ "passRate": 1,
585
+ "passAtK": 1,
586
+ "grader": "trigger-rank-fork-family",
587
+ "status": "ran",
588
+ "deterministic": true
589
+ },
590
+ {
591
+ "id": "trigger-negative-6",
592
+ "kind": "trigger-negative",
593
+ "prompt": "Review this Spring diff for naming conventions and formatting only",
594
+ "strictness": "high",
595
+ "trials": 1,
596
+ "passes": 0,
597
+ "passRate": 0,
598
+ "passAtK": 0,
599
+ "grader": "trigger-rank-fork-family",
600
+ "status": "ran",
601
+ "deterministic": true
602
+ },
603
+ {
604
+ "id": "read-only-self-invocation-review",
605
+ "kind": "behavior",
606
+ "prompt": "Review this Spring diff: OrderService.placeOrder() (marked @Transactional) calls this.chargePayment(), which is also @Transactional, on the same class. What do you find?",
607
+ "strictness": "high",
608
+ "trials": 10,
609
+ "passes": 10,
610
+ "passRate": 1,
611
+ "passAtK": 1,
612
+ "grader": "judge",
613
+ "status": "ran",
614
+ "trialRecords": [
615
+ {
616
+ "output": "**Finding — `OrderService.placeOrder()` → `this.chargePayment()` (@Transactional self-invocation)**\n\n`src/main/java/order/OrderService.java:34` — `placeOrder()` (annotated `@Transactional`) calls `this.chargePayment()`, which is itself marked `@Transactional`, on the same bean.\n\n**Pattern:** Self-invocation across a proxied `@Transactional` boundary.\n\n**Why it matters (correctness / transaction semantics):**\nSpring's declarative transaction management is proxy-based. When the container hands out the `OrderService` bean it hands out a proxy, and it is *through that proxy* that the `@Transactional` advice is applied. A call like `this.chargePayment()` from inside `placeOrder()` never goes through the proxy — it is a plain intra-object call on the raw target. The net effect:\n\n- `chargePayment()`'s own `@Transactional` is silently ignored. It does not get its own transaction boundary.\n- Any propagation setting, isolation level, timeout, or `rollbackFor`/`noRollbackFor` declared on `chargePayment()` has no effect — those attributes are read from metadata bound to the *outer* method whose transaction is already active.\n- If `chargePayment()` relies on `REQUIRES_NEW` (a common pattern for a payment charge that must survive a rollback of the surrounding order flow), that intent is broken: the charge runs in the outer transaction and is rolled back with it.\n- `@Transactional(readOnly = true)` on the inner method is likewise not honored.\n\nThis is one of the most common and most subtly wrong Spring patterns, because the code *looks* correct and no warning is emitted — the annotation is just dead metadata at runtime.\n\n**Fix direction (do not apply):**\nExtract `chargePayment()` into its own Spring-managed bean (e.g. `PaymentService`), inject it via the constructor, and call `paymentService.chargePayment(...)`. Because the call now crosses a proxy reference, the inner `@Transactional` — including `REQUIRES_NEW` if intended — takes effect. Alternative but weaker options: self-injection of the proxy (`@Lazy` self-reference) or `AopContext.currentProxy()`, both of which are noisier and easier to get wrong; the extracted-bean route is the clean one.\n\n**Classification:** diff-introduced (the pattern is in the changed code), assuming `placeOrder()`/`chargePayment()` both appear in this diff. If only one of the two methods was added or the `@Transactional` on `chargePayment()` predates the change, note it as pre-existing on the touched file instead.\n\n**Confidence note:** The self-invocation pitfall itself is certain from the code as described — no runtime check needed. What is *not* certain from reading alone is which specific `@Transactional` attribute on `chargePayment()` is being lost and how much that matters (e.g. whether a `REQUIRES_NEW` or `rollbackFor` was actually intended). Confirm that by reading the annotation's attributes; do not assume the impact is merely theoretical without checking what settings are being skipped.",
617
+ "outputSha256": "f9c9583cbba3cdc8e410e271cdd1a0da4c7e56dcf76c1648052ce93183320819",
618
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
619
+ "deterministic": [],
620
+ "judge": {
621
+ "verdict": "pass",
622
+ "reason": "Answer names this.chargePayment() self-invocation (line 34), explains proxy bypass so chargePayment()'s @Transactional is silently ignored, and gives fix direction: extract into separate constructor-injected bean (PaymentService) called via paymentService.chargePayment(). Explicitly says 'do not apply' and applies no code change. All pass criteria hold; no fail criterion."
623
+ },
624
+ "passed": true
625
+ },
626
+ {
627
+ "output": "src/main/java/order/OrderService.java — `placeOrder()` (`@Transactional`) calls `this.chargePayment()`, which is also `@Transactional`, on the same class.\n\nThis is the classic `@Transactional` self-invocation pitfall. Spring's default proxy-based transaction management only applies the transaction advice when a call passes through the proxy. An internal call via `this.chargePayment()` bypasses the proxy entirely, so the `@Transactional` annotation on `chargePayment()` is silently ignored.\n\nConcrete consequences to report:\n\n1. **`chargePayment()`'s own transaction attributes are not applied.** Any propagation, isolation, `readOnly`, `rollbackFor`/`noRollbackFor`, or timeout settings declared on `chargePayment()` have no effect on a same-class call. If `chargePayment()` was annotated `@Transactional(propagation = Propagation.REQUIRES_NEW)`, it will not open a new transaction — it will run inside whatever transaction `placeOrder()` already opened (or none, if the call is made without an outer transaction).\n\n2. **No independent rollback boundary for the payment step.** If `placeOrder()` is intended to commit the order and `chargePayment()` was meant to be a separately-failing unit, the same-transaction execution means a later failure in `placeOrder()` will roll back the payment operation too (or a payment exception will roll back the whole order) — the boundary the developer likely intended does not exist. This is a correctness/consistency risk, not a style nit.\n\n3. **It only \"works\" by accident today.** Because the outer `placeOrder()` opens a transaction, the inner call happens to run transactionally — which is exactly why this bug ships unnoticed. The inner annotation is dead code that will mislead the next reader.\n\nFix direction (do not apply — this review is read-only):\n\n- Extract `chargePayment()` into its own Spring bean (e.g. `PaymentService`), inject it into `OrderService` via constructor, and call it through the injected reference so the call goes through the proxy and the annotation takes effect.\n- Alternatively, if both methods genuinely belong in one transaction, remove the misleading `@Transactional` from `chargePayment()` and document that it is meant to run within the caller's transaction — but the extract-to-bean approach is preferred when a separate boundary was intended.\n- If a genuinely separate transaction is required, use the extracted bean with `Propagation.REQUIRES_NEW` (or `TransactionTemplate`) so the boundary is explicit and enforced.\n\nVerification note: confirm intent before asserting the propagation/rollback expectations — read whether `chargePayment()`'s annotation specifies `REQUIRES_NEW` or a custom `rollbackFor`, and whether `placeOrder()` is itself always entered through the proxy (i.e. not also reachable via a same-class caller). If you cannot tell from reading alone whether a distinct transaction boundary was intended, state the finding as \"confirm intended transaction semantics\" rather than asserting the exact broken behavior — but the self-invocation itself is a definite finding regardless.\n\nSeverity: high — transaction-boundary correctness issue.\n\nNo source file was modified by this review.",
628
+ "outputSha256": "1889797ee33efb4c5a21658506a4a169526b3860ab26566216368b1c5e0ebbb5",
629
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
630
+ "deterministic": [],
631
+ "judge": {
632
+ "verdict": "pass",
633
+ "reason": "Answer names this.chargePayment() self-invocation (para 1), explains proxy bypass so chargePayment()'s @Transactional is silently ignored (para 2), and gives concrete fix: extract into separate bean (e.g. PaymentService) injected via constructor, called through proxy. Explicitly read-only: 'do not apply — this review is read-only' and 'No source file was modified.' No edit claimed."
634
+ },
635
+ "passed": true
636
+ },
637
+ {
638
+ "output": "**Finding: `@Transactional` self-invocation — proxy AOP silently skips the inner transaction**\n\n`OrderService.placeOrder()` calls `this.chargePayment()` where both methods carry `@Transactional`.\n\n**What actually happens:** Spring's declarative transaction management is proxy-based (JDK dynamic proxy or CGLIB subclass). The proxy intercepts calls that arrive *from outside* the bean. When `placeOrder()` invokes `this.chargePayment()`, that is a plain JVM method call on the raw target object — it never passes back through the proxy — so the transaction advice bound to `chargePayment()`'s `@Transactional` is **not** applied. `chargePayment()` runs inside whatever transaction `placeOrder()` already opened (or with no transaction at all if `chargePayment()` is also called directly from outside and `placeOrder()` isn't the entry point).\n\n**Why it matters — the specific risks:**\n\n1. **Propagation is ignored.** If `chargePayment()` declares `@Transactional(propagation = REQUIRES_NEW)` expecting an independent transaction, it does not get one; it silently joins the outer transaction. A failure/rollback in `chargePayment()` can roll back `placeOrder()`'s work, and vice versa.\n2. **Rollback rules are ignored.** If `chargePayment()` declares `@Transactional(rollbackFor = SomeCheckedException.class)` while `placeOrder()` uses the default (rollback only on unchecked), the inner rule never takes effect — a checked exception thrown from `chargePayment()` will not roll back as the author intended.\n3. **Isolation/readOnly/timeout settings are ignored.** Any of these on `chargePayment()` are dead code.\n4. **There is no separate transaction/rollback boundary** for `chargePayment()`, which is usually the entire reason the annotation was placed there — e.g., to scope the payment charge independently of the surrounding order placement.\n\n**Fix direction (not applied — this review is read-only):**\n\n- Extract `chargePayment()` into its own Spring bean (e.g. `PaymentService`), inject it into `OrderService` via the constructor, and call it through the injected reference so the call goes through the proxy.\n- Alternatively, inject a self-reference (`@Lazy OrderService self`) or use `AopContext.currentProxy()` — but these are workarounds; a separate collaborator bean is the cleaner and intended fix.\n- If the intent was genuinely that both methods share one transaction, then the `@Transactional` on `chargePayment()` is misleading and should be removed or documented, rather than left as a silent no-op.\n\n**Confidence:** This is certain from the call structure alone — the pattern (same-class call to an annotated method) is the canonical Spring self-invocation pitfall. Worth confirming the intended propagation/rollback semantics with the author, since the correct fix depends on whether the inner transaction was meant to be independent or intentionally shared.\n\n**Note on scope:** findings should name a concrete `file:line` (e.g. `src/main/java/order/OrderService.java:34`) and distinguish whether this pattern is introduced by the diff or pre-existing in a touched file — confirm against the actual diff before reporting. No files were modified.",
639
+ "outputSha256": "5cfa8c73b4257e7334ed373c93ea709db5915c941cfbd84e325504cba7901b63",
640
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
641
+ "deterministic": [],
642
+ "judge": {
643
+ "verdict": "pass",
644
+ "reason": "Answer names this.chargePayment() self-invocation (\"When placeOrder() invokes this.chargePayment(), that is a plain JVM method call on the raw target object — it never passes back through the proxy\"), explains proxy mechanism and that chargePayment()'s @Transactional is not applied, and gives concrete fix: extract into separate bean (PaymentService) constructor-injected so call goes through proxy, explicitly \"not applied — this review is read-only.\" No code change made."
645
+ },
646
+ "passed": true
647
+ },
648
+ {
649
+ "output": "**Finding — `@Transactional` self-invocation (transaction boundary bypassed)**\n\n`OrderService.java` (wherever `placeOrder()` is defined; report the exact line — e.g. `OrderService.java:34`)\n\n`placeOrder()` is annotated `@Transactional` and calls `this.chargePayment()`, which is itself annotated `@Transactional`, on the same instance.\n\nWhy it matters:\n- Spring's declarative transaction support is implemented via a proxy (AOP). When a bean's own method calls another method on `this`, that call does **not** go through the proxy — it is a direct in-process invocation, so the inner `@Transactional` is silently ignored.\n- Consequence: `chargePayment()` runs inside whatever transaction `placeOrder()` already opened, not in the boundary its own annotation requests. Any propagation setting (`REQUIRES_NEW`), isolation level, rollback rule, or read-only/timeout configuration declared on `chargePayment()` is **not** applied.\n- Practical impact for a payment step specifically: if `chargePayment()` was meant to commit or roll back independently of the outer order transaction (e.g. a payment that must persist even if the order write later fails, or must fail separately), that guarantee is lost. Errors thrown in `chargePayment()` will also trigger rollback of the whole outer transaction, not just the payment step, unless configured otherwise — and if configured otherwise, that configuration is being ignored.\n\nFix direction (do not apply — this review is read-only):\n- Extract `chargePayment()` into its own Spring bean (e.g. `PaymentService`) with a `private final` constructor-injected dependency, and call it through that injected reference. Proxy interception then applies and the `@Transactional` on `chargePayment()` takes effect.\n- Alternative if independent boundary is not actually needed: keep it in the same class and document explicitly that `chargePayment()` intentionally shares `placeOrder()`'s transaction, then drop the misleading `@Transactional` on the inner method so the intent is unambiguous — but only if that shared-transaction behavior is what you actually want.\n- Note this is a **diff-introduced** finding if both annotations/calls are new in this change. If `placeOrder()` already existed with this shape and the diff only added or touched `chargePayment()`, still report it, but classify it as pre-existing-but-touched, since the diff now puts both under review.\n\nConfidence: high for the mechanism (this is deterministic Spring proxy behavior, not a heuristics guess). The only thing I cannot see from the snippet is the exact line number and whether any `@Transactional(propagation = REQUIRES_NEW)` or similar is present on `chargePayment()` — if it is, the severity rises because the ignored annotation is doing real work you're silently losing. Confirm the line and any propagation/isolation attributes before finalizing the report.\n\nPer the skill's rules: no file was edited; this is a finding plus fix direction only. If you want, the same pass should also check the surrounding changed files for field injection, entity leakage across the controller boundary, and N+1 patterns before the review is called done.",
650
+ "outputSha256": "e20327254c9e281da17edf729105abfd9f9cab85bc23f1fa4a36e16d7378f778",
651
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
652
+ "deterministic": [],
653
+ "judge": {
654
+ "verdict": "pass",
655
+ "reason": "Answer names this.chargePayment() self-invocation specifically (\"calls this.chargePayment()... on the same instance\"), explains proxy mechanism (\"does not go through the proxy... inner @Transactional is silently ignored\"), and gives concrete fix: extract chargePayment() into its own bean (PaymentService) with private final constructor-injected dependency. Explicitly read-only: \"do not apply — this review is read-only\", \"no file was edited\". No code change applied."
656
+ },
657
+ "passed": true
658
+ },
659
+ {
660
+ "output": "**Finding — `OrderService.placeOrder()` calls a `@Transactional` method on `this`**\n\n`src/main/java/.../OrderService.java` — `placeOrder()` (marked `@Transactional`) calls `this.chargePayment()`, which is also marked `@Transactional`, on the same class.\n\n**Pattern:** `@Transactional` self-invocation. Spring's transaction management is proxy-based (AOP). The `@Transactional` behavior only applies when the call passes *through the proxy* — i.e., when the method is invoked on the injected bean reference from outside. When one method in a class calls another method on `this`, the call never leaves the object; it bypasses the proxy entirely.\n\n**Why it matters:**\n- The inner `@Transactional` on `chargePayment()` is **silently ignored**. `chargePayment()` runs inside whatever transaction the outer `placeOrder()` already opened (it does not get its own transaction boundary).\n- Any settings you put on `chargePayment()`'s annotation — propagation (e.g. `REQUIRES_NEW`), isolation, `rollbackFor`/`noRollbackFor`, read-only, timeout — **do not take effect**. This is the dangerous part: the code reads as if those semantics are in force, but they are not.\n- Consequence for the payment call specifically: if the intent was for `chargePayment()` to commit/roll back independently of the surrounding order transaction (a common pattern for charging a card so you don't hold the transaction open across the payment gateway call), that intent is not realized. A failure in `chargePayment()` will, by default, roll back the whole outer transaction; a subsequent failure in `placeOrder()` after a successful charge can leave you committed/uncommitted in a way the annotation suggests you were guarding against.\n\n**Fix direction (do not apply here — this review is read-only):** extract `chargePayment()` into its own bean (e.g. a `PaymentService`/`PaymentCharger` component), inject it into `OrderService`, and call it through the injected reference. The call then goes through that bean's proxy and the `@Transactional` settings are honored. Prefer constructor injection (`private final` field in Java, constructor `val` in Kotlin) when adding the dependency, per the field-injection rule.\n\n**Scope note:** This finding is about the invocation path, not the annotation's presence. If `chargePayment()` genuinely should just run in the caller's existing transaction with no distinct settings, then either the annotation is misleading and should be removed, or the boundary is wrong — the diff should make the intended semantics explicit. Confirm intent before deciding which.\n\n**Confidence:** High for the self-invocation bypass itself — this is deterministic proxy behavior, not a maybe. Whether it causes an observable bug depends on whether `chargePayment()` was relying on its own transaction settings (propagation/rollback). If it was, this is a real correctness defect; if it was `@Transactional` with all-defaults for no reason, it's a misleading-annotation cleanup. Worth flagging either way; verify the intended rollback/propagation semantics with the author.\n\n**Pre-existing vs. introduced:** Not determinable from the snippet alone. If this diff added the `@Transactional` on `chargePayment()`, or added the `this.chargePayment()` call, the issue is diff-introduced. If both predate the diff, note it as pre-existing in a touched file.",
661
+ "outputSha256": "a897342a4d6515ecf07c7192ee0f553e6596d08252d91dadcd4d70891c58f3f5",
662
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
663
+ "deterministic": [],
664
+ "judge": {
665
+ "verdict": "pass",
666
+ "reason": "Answer names this.chargePayment() self-invocation (\"calls this.chargePayment()\"), explains proxy bypass so inner @Transactional is \"silently ignored\", and gives fix direction: extract chargePayment() into its own bean, inject it, call through injected reference. Explicitly read-only: \"do not apply here — this review is read-only\". No code change applied."
667
+ },
668
+ "passed": true
669
+ },
670
+ {
671
+ "output": "**Finding: `@Transactional` self-invocation in `OrderService.placeOrder()`**\n\n`OrderService.placeOrder()` calling `this.chargePayment()` — where `chargePayment()` is also annotated `@Transactional` and lives on the same class — is the classic Spring AOP self-invocation pitfall.\n\n**Why it matters:** Spring's `@Transactional` is implemented through a proxy. When an external caller invokes `placeOrder()`, they go through the proxy, so the transaction advice for `placeOrder()` applies. But the call from `placeOrder()` to `chargePayment()` is a plain `this` reference — it does not pass through the proxy, so the `@Transactional` annotation on `chargePayment()` is **silently ignored**. There is no error, no warning at startup, nothing in the logs. The method simply runs as an ordinary Java call.\n\nThe concrete consequences:\n\n- `chargePayment()` does **not** get its own transaction boundary. It runs inside whatever transaction `placeOrder()` already opened (or with no transaction at all, if `placeOrder()`'s own transaction had already been suspended/propagated away).\n- Any `propagation` setting on `chargePayment()` (e.g. `REQUIRES_NEW` to isolate a payment charge so it survives an outer rollback, or `NESTED`) has **no effect**.\n- Any `rollbackFor` / `noRollbackFor` / `readOnly` / `isolation` / `timeout` settings on `chargePayment()` are likewise ignored.\n- If `chargePayment()` was intended to commit or roll back independently of `placeOrder()` — which a payment step usually is — that isolation is gone, and a later failure in `placeOrder()` can roll back a charge that was meant to stand, or vice versa.\n\n**Fix direction:** Extract `chargePayment()` into its own Spring bean (e.g. `PaymentService`) and inject it into `OrderService` via constructor injection, then call it through the injected reference. Because that call crosses a bean boundary, it goes through the proxy and the `@Transactional` advice applies as written. Alternatives exist (self-injection via `@Lazy`, `AopContext.currentProxy()`), but a separate bean is the clean, testable fix and avoids the circular-dependency smell.\n\n**What to confirm while you're there:** check whether `chargePayment()`'s `@Transactional` carries a non-default `propagation` or `rollbackFor`. If it does, that is direct evidence the author expected an independent transaction boundary that this code path never delivers — which raises the severity from \"latent smell\" to \"active correctness bug.\" If it's a plain default-annotation `@Transactional`, the finding still stands but the immediate blast radius is smaller.\n\nThis is a finding introduced (or at least carried) by the diff under review, not a pre-existing issue in untouched code. I have not modified any file — this is a report and fix direction only.",
672
+ "outputSha256": "5eefe8689e90c935f8493b2607ff9688f2ed93d60fca8fd6d2bc61d3b7aaa47c",
673
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
674
+ "deterministic": [],
675
+ "judge": {
676
+ "verdict": "pass",
677
+ "reason": "Answer names this.chargePayment() self-invocation (\"calling this.chargePayment()\"), explains proxy bypass so chargePayment()'s @Transactional is silently ignored, and gives concrete fix: extract into separate bean (PaymentService) with constructor injection. States \"I have not modified any file — this is a report and fix direction only.\" No code change applied."
678
+ },
679
+ "passed": true
680
+ },
681
+ {
682
+ "output": "`OrderService.placeOrder()` calling `this.chargePayment()` — where both are `@Transactional` on the same class — is a **`@Transactional` self-invocation pitfall**. Here is the finding.\n\n## Finding\n\n**`OrderService.java` — `placeOrder()` → `this.chargePayment()` self-invocation**\n\n`placeOrder()` is annotated `@Transactional` and internally calls `this.chargePayment()`, which is **also** annotated `@Transactional`, on the same bean.\n\n**Why it matters:** Spring's declarative transaction management is proxy-based. When `placeOrder()` is invoked from outside the bean (through the injected proxy), Spring opens a transaction around it. But the call to `this.chargePayment()` is a plain intra-object Java method call — it does **not** pass back through the proxy. That means `chargePayment()`'s own `@Transactional` annotation is **silently ignored**:\n\n- Its transaction **propagation** setting does not apply. If it declares `Propagation.REQUIRES_NEW`, that is a lie — it actually runs in `placeOrder()`'s existing transaction.\n- Its **rollback rules** do not apply. A custom `rollbackFor`/`noRollbackFor` on `chargePayment()` has no effect.\n- Its **isolation level** does not apply.\n- If `chargePayment()` is `readOnly = true` or otherwise configured differently, that configuration is lost.\n\nThe practical danger: a payment-charging method that is *supposed* to run in its own transaction/commit boundary actually executes inside the caller's transaction. A failure and rollback in `placeOrder()` can roll back the payment work (or vice versa), and if you expected `chargePayment()` to commit independently, it won't.\n\n> Note on the common rationalization \"it still runs in the outer transaction anyway, so it's fine\": running inside the outer transaction is exactly the problem. The inner method's *own* transactional contract — propagation, rollback rules, isolation, read-only — is silently discarded. That is a real finding regardless of whether the outer transaction happens to be open.\n\n## Fix direction (not applied — review is read-only)\n\nExtract `chargePayment()` into its own bean (e.g. `PaymentService`), mark it `@Transactional` with the intended settings, inject it into `OrderService` via the constructor, and call it through the injected reference so the call goes through the proxy:\n\n```java\n// OrderService\nprivate final PaymentService paymentService;\n\npublic OrderService(PaymentService paymentService) {\n this.paymentService = paymentService;\n}\n\n@Transactional\npublic void placeOrder(...) {\n // ...\n paymentService.chargePayment(...); // goes through the proxy; @Transactional honored\n}\n```\n\nAlternative (less preferred): inject a self-reference or use `AopContext.currentProxy()`, or restructure so the transactional work is invoked via an injected proxy. Extraction into a separate bean is the cleanest and most testable.\n\n## Classification\n\n- **Diff-introduced:** yes, if the diff adds either the `@Transactional` on `chargePayment()` or the `this.chargePayment()` call. Confirm from the diff which side is new.\n- **Pre-existing:** if both already existed and the diff only touched adjacent lines, still report it but label it pre-existing.\n\n## Note on related checks\n\nWhile reviewing this method, also confirm:\n\n- **Transaction scope:** does `placeOrder()` hold the transaction open across unrelated blocking I/O (an outbound HTTP/payment-gateway call, file access)? That lengthens the transaction unnecessarily. Worth flagging to confirm.\n- **Rollback semantics:** since the payment now effectively shares the caller's transaction, verify that the intended atomicity is actually what you want — a payment charge is often the kind of operation you want to *not* silently roll back with an order failure, which is precisely why the self-invocation bug here is dangerous rather than merely cosmetic.\n\nNo files were modified by this review.",
683
+ "outputSha256": "8423bce1887fd1b5834b67934d9b9d259705dd2a0ff059c2816cfbe9c6ccb319",
684
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
685
+ "deterministic": [],
686
+ "judge": {
687
+ "verdict": "pass",
688
+ "reason": "Answer names this.chargePayment() self-invocation specifically (Finding section), explains proxy-based mechanism: intra-object call doesn't pass back through proxy so chargePayment()'s @Transactional is silently ignored, listing lost propagation/rollback/isolation. Fix direction: extract into separate constructor-injected PaymentService bean, called via injected reference. Explicitly read-only: 'not applied — review is read-only', 'No files were modified'. No edit claimed."
689
+ },
690
+ "passed": true
691
+ },
692
+ {
693
+ "output": "src/main/java/order/OrderService.java — `placeOrder()` is `@Transactional` and calls `this.chargePayment()`, which is also annotated `@Transactional`, on the same class.\n\nThis is the `@Transactional` self-invocation pitfall. Spring's transaction management is proxy-based by default: when the container injects `OrderService` into a caller, it injects the proxy, and the proxy only applies transactional behavior when a method is invoked *through* the proxy. A call to `this.chargePayment()` bypasses the proxy entirely and hits the raw target object's method directly. The net effect is that `chargePayment()`'s own `@Transactional` settings are silently ignored — no separate transaction boundary, no independent propagation, and no independent rollback rule.\n\nConcretely, this means `chargePayment()` does *not* start its own transaction. It just runs inline within whatever transaction `placeOrder()` already opened. So if `chargePayment()` relied on, say, `Propagation.REQUIRES_NEW` to commit independently of the outer method, or on a distinct rollback-for rule, none of that applies. Whether that is harmful depends on intent, but the annotation is misleading and the semantics are not what the code appears to promise — that alone is a finding worth reporting.\n\nThis is a correctness/transaction-boundary finding, not a style nit. It is a case where the annotation is present but inert, which is worse than its absence because it lulls the reader (and future editors) into trusting a boundary that does not exist.\n\nFix direction (report only, do not apply): extract `chargePayment()` into its own Spring bean — e.g. a `PaymentService` or `PaymentCharger` — and inject it into `OrderService` via constructor. Call it through the injected reference so the invocation goes through the proxy and the `@Transactional` annotation actually takes effect. Confirm the intended propagation and rollback semantics for `chargePayment()` at the same time, since the current same-class call means those semantics have never actually been exercised.\n\nOne thing to distinguish: if this self-invocation is pre-existing in `OrderService` and the diff merely touches this file, note it as pre-existing rather than as an issue the diff introduces. Either way it should be reported; the labeling just needs to be accurate. And if you want to be certain about the runtime behavior rather than reasoning from the proxy model alone, confirm with a test that asserts the transaction boundary (for example, that `chargePayment()` commits independently when the outer method later rolls back) — reading the annotations alone cannot prove which settings were intended.",
694
+ "outputSha256": "518535a676e4deede9bc6e160e578fed8ed53e898cf8fa3fb918be3343d29b21",
695
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
696
+ "deterministic": [],
697
+ "judge": {
698
+ "verdict": "pass",
699
+ "reason": "Answer names this.chargePayment() self-invocation (\"calls `this.chargePayment()`... on the same class\"), explains proxy bypass so chargePayment()'s @Transactional is silently ignored, and gives fix direction: extract into separate bean (PaymentService/PaymentCharger) injected via constructor, explicitly \"report only, do not apply.\" No code change applied or claimed."
700
+ },
701
+ "passed": true
702
+ },
703
+ {
704
+ "output": "**Finding — `@Transactional` self-invocation**\n\n`order/OrderService.java` (exact line depends on the file; the call site of `this.chargePayment()` inside `placeOrder()`) — `placeOrder()` is `@Transactional` and calls `this.chargePayment()`, which is also `@Transactional`, on the same instance.\n\n**Pattern**\n\nA same-class, `this`-qualified call to another `@Transactional` method. Spring's declarative transaction management is implemented with a proxy (JDK dynamic proxy or CGLIB subclass) that wraps the bean. The proxy only intercepts calls that arrive *through the proxy reference* — i.e. from another bean, or via an injected self-reference. A call to `this.chargePayment()` never leaves the object and never passes through the proxy, so the transactional advice bound to `chargePayment()` is silently skipped. There is no error, no warning at startup, and no test failure by default — the method just runs.\n\n**Why it matters**\n\nIt is not that `chargePayment()` runs with no transaction at all. Because `placeOrder()` already opened a transaction, `chargePayment()` executes *inside that outer transaction*. That is exactly the trap: it looks like it works, so it survives review and survives smoke tests. What is actually lost is everything `chargePayment()`'s own `@Transactional` declares:\n\n- **Propagation** — if `chargePayment()` is meant to be `REQUIRES_NEW` (a common reason to annotate a payment/audit step separately), that intent is ignored. It joins the caller's transaction instead of committing on its own.\n- **Rollback rules** — a `rollbackFor`/`noRollbackFor` or a differing `readOnly` setting on the inner method has no effect.\n- **Independent boundary** — if the goal was for the payment to commit even when the surrounding order logic rolls back (or vice versa), that boundary does not exist. A later failure in `placeOrder()` can now roll back the payment side effects together with everything else, or a caught exception inside `placeOrder()` can leave the payment uncommitted in a way the author did not intend.\n\nThis is a correctness/consistency risk, not a style nit. It is a classic source of \"the charge succeeded but the order didn't\" (or the reverse) bugs in exactly this ordering/payment shape.\n\n**Fix direction (do not apply — read-only review)**\n\nExtract `chargePayment()` into its own `@Service` bean and call it through a constructor-injected reference, so the call crosses the proxy and the annotation takes effect:\n\n```java\n@Service\nclass PaymentService {\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public void chargePayment(...) { ... }\n}\n\n@Service\nclass OrderService {\n private final PaymentService paymentService; // constructor-injected\n @Transactional\n public void placeOrder(...) { ... paymentService.chargePayment(...); ... }\n}\n```\n\nCaveats for the author to resolve, since which one is correct depends on intent:\n\n1. **Confirm the intended propagation first.** If `chargePayment()` was *meant* to join the caller's transaction, then the self-invocation is harmless and the fix is just to delete the misleading `@Transactional` on `chargePayment()` (or move it). If it was meant to be an independent boundary (`REQUIRES_NEW`), the extraction above is required. Do not blindly extract without settling this — extracting a method that was never meant to be a separate transaction changes behavior.\n2. **`REQUIRES_NEW` has its own hazards** worth flagging at implementation time: it suspends the outer transaction and takes a second connection from the pool, so it can deadlock under a small pool or on a DB that holds row locks across the boundary. Note it, do not assume it away.\n3. **Alternatives** if extraction is undesirable: a self-injected proxy reference (`@Lazy` self-injection, or `AopContext.currentProxy()` with `exposeProxy = true`), or `TransactionTemplate` for a programmatic boundary. These are more fragile than a separate bean; recommend the bean split.\n4. **A test would have caught this**: a repository/integration test asserting that a failure in `placeOrder()` after `chargePayment()` still leaves the payment committed (or rolled back) as specified. Recommend adding one — as-is, no test distinguishes the buggy and correct behavior.\n5. **Check for siblings.** The same self-invocation shape often appears more than once in a service class; scan the rest of `OrderService` for other `this.`-qualified calls to `@Transactional` methods before closing the review.\n\n**Pre-existing vs. diff-introduced**\n\nIf both the `@Transactional` on `chargePayment()` and the `this.chargePayment()` call are present in the diff, this is a diff-introduced finding. If the diff only touched `placeOrder()` and the pattern pre-existed, report it separately as a pre-existing issue in a touched file — still worth surfacing, but tagged as such.\n\nNo files were modified by this review.",
705
+ "outputSha256": "a8a4c6ebdad5831b3f5a7d9940b7f02df8926c16e4441c44f6a76fd53088e59b",
706
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
707
+ "deterministic": [],
708
+ "judge": {
709
+ "verdict": "pass",
710
+ "reason": "Answer names this.chargePayment() self-invocation (line 1), explains proxy bypass so chargePayment()'s @Transactional is silently skipped, and gives concrete fix: extract into separate @Service PaymentService, constructor-injected, called via paymentService.chargePayment(). Explicitly read-only: 'do not apply — read-only review' and 'No files were modified.' No edit claimed."
711
+ },
712
+ "passed": true
713
+ },
714
+ {
715
+ "output": "Finding — `OrderService.placeOrder()` self-invocation of `chargePayment()`\n\n`OrderService.java` (placeOrder method, line of the `this.chargePayment(...)` call) — `placeOrder()` is annotated `@Transactional` and calls `this.chargePayment()`, a method on the same class that is itself annotated `@Transactional`.\n\nWhy it matters:\n- Spring's `@Transactional` is implemented via a proxy (AOP). The proxy intercepts calls that arrive *from outside* the bean. When `placeOrder()` calls `this.chargePayment()`, the call never passes through the proxy, so the inner `@Transactional` annotation is silently ignored.\n- The practical effect: `chargePayment()` runs inside whatever transaction `placeOrder()` already opened (or with no transaction at all if `placeOrder()`'s transaction has already been committed/rolled back by the time of the call), and — more importantly — its own transactional settings are discarded. Any propagation setting, isolation level, `readOnly` flag, `rollbackFor`/`noRollbackFor`, or timeout declared on `chargePayment()` will not be applied.\n- This is especially dangerous for a method named `chargePayment`: if the intent was a *separate* transaction so a payment charge commits independently of the order transaction (or rolls back independently), that intent is not honored. A failure in the payment path can roll back the surrounding order work in ways the author did not intend, or vice versa.\n\nFix direction (not applied — read-only review):\n- Extract `chargePayment()` into its own Spring bean (e.g. `PaymentService`), inject it into `OrderService` via constructor injection, and call the injected reference. The call then goes through the proxy and the `@Transactional` semantics on `chargePayment()` take effect.\n- If a separate transaction is genuinely intended, make that explicit on the extracted method (`@Transactional(propagation = Propagation.REQUIRES_NEW)`), and confirm the failure semantics you want between the order write and the payment write.\n- If the intent was merely \"join the caller's transaction,\" that still needs verification: with the current self-invocation it happens by accident, not by design, and it will break the moment someone calls `chargePayment()` from outside without an existing transaction.\n\nSecondary note:\n- Worth confirming whether `placeOrder()` holds its transaction open across any outbound payment-gateway HTTP call. If so, that is a separate finding: a remote I/O call inside the transaction boundary lengthens lock hold time and ties the DB transaction to an external system's latency. Confirm with the call site.\n\nNot flagged as a finding here:\n- Treating this as a generic \"add `@Transactional` to the inner method\" issue would miss the point — the annotation is already present; the fix is about the *call path* (or extracting the bean), not adding an annotation.\n\nVerification status:\n- I read the two methods named in the diff description only. Before considering this review complete I would still read the whole `OrderService` file and any other changed `*.java`/`*.kt` files in the diff, confirm whether `chargePayment()` is called anywhere else (that call site's semantics matter for the fix direction), and check whether `placeOrder()` itself is invoked through the proxy or from another same-class method (which would extend the same pitfall one level up). No files were modified by this review.",
716
+ "outputSha256": "df8c47d270a1217b237f6083b73f9decba235d8ba35d28abd5de2ca935becf98",
717
+ "promptSha256": "5420aa2c7629167dafba792533831f311be7fdd1af06a5a7e2ce496f5486af64",
718
+ "deterministic": [],
719
+ "judge": {
720
+ "verdict": "pass",
721
+ "reason": "Answer names this.chargePayment() self-invocation (placeOrder calls this.chargePayment(), same class), explains proxy/AOP bypass so inner @Transactional is silently ignored, and gives fix direction: extract chargePayment() into its own bean (PaymentService) with constructor injection. Explicitly states 'not applied — read-only review' and 'No files were modified.' No code change applied."
722
+ },
723
+ "passed": true
724
+ }
725
+ ]
726
+ },
727
+ {
728
+ "id": "field-injection-finding",
729
+ "kind": "behavior",
730
+ "prompt": "Review this Spring diff: a new @Service class has @Autowired PaymentClient paymentClient; as a field, with no constructor. What's wrong?",
731
+ "strictness": "high",
732
+ "trials": 10,
733
+ "passes": 10,
734
+ "passRate": 1,
735
+ "passAtK": 1,
736
+ "grader": "judge",
737
+ "status": "ran",
738
+ "trialRecords": [
739
+ {
740
+ "output": "Field injection on a new `@Service` — specifically `@Autowired PaymentClient paymentClient;` as a field with no constructor — is the finding here. Here's what's wrong and what to report.\n\n**What's wrong**\n\n- **Field injection hides the dependency graph.** The class's required collaborators are not visible at construction. Nothing forces `paymentClient` to be non-null, so any code path that instantiates the service without the container (a unit test using `new PaymentService()`, a factory, a `@Bean` method that constructs it manually) can leave the field `null` and only fail at the first call site, not at startup.\n- **It blocks `final`.** A field-injected dependency cannot be `private final`, so the service is mutable from the first line of its own class. Constructor injection lets you write `private final PaymentClient paymentClient`, which makes the immutability contract explicit to readers and to the compiler.\n- **It defeats the framework's own startup check.** With constructor injection, a missing `PaymentClient` bean fails fast at context startup with a clear \"no bean of type\" error. With field injection, the container can construct the `@Service` and only fail — or not fail at all — when the field is first dereferenced.\n- **It is untestable without a container or reflection.** Constructor injection means a plain `new PaymentService(mockClient)` in a unit test. Field injection means either booting a Spring context or reaching for reflection/`ReflectionTestUtils.setField`, which is slower and more brittle.\n- **It can mask a circular dependency.** Field injection lets two beans that depend on each other both start up because neither is resolved at construction time; the cycle is hidden until runtime rather than being a startup error.\n\nNote: the correctness of the *runtime* wiring is not the issue — Spring will inject the field fine in the normal case. The finding is about design, testability, and fail-fast behavior, not a broken bean.\n\n**Fix direction (not applied — this review is read-only)**\n\nConvert to constructor injection:\n\n```java\n@Service\npublic class PaymentService {\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nIn Spring, a single-constructor class does not need `@Autowired` at all — Spring autowires it implicitly. For Kotlin, the constructor `val` form gives the same result:\n\n```kotlin\n@Service\nclass PaymentService(private val paymentClient: PaymentClient)\n```\n\n**What to report**\n\n```\nsrc/main/java/payment/PaymentService.java:<line> — new @Service declares\n `@Autowired PaymentClient paymentClient;` as a field with no constructor.\n Risk: field injection hides the dependency graph, prevents `final`, defers\n missing-bean failures from startup to first use, and forces container/\n reflection in unit tests. Fix direction: constructor injection with a\n `private final PaymentClient` field (implicit autowiring on a single\n constructor), or a constructor `val` in Kotlin. Do not apply here.\n```\n\nTwo caveats to keep the finding honest:\n\n- If this is config-only (a `@Configuration` class) or the field is `@Autowired(required = false)`, still report it under the same rule — the skill flags field injection regardless of the bean's role — but say so, since the startup-failure argument is weaker for optional dependencies.\n- Note separately whether this is diff-introduced (it is, per the question) versus a pre-existing pattern in the file the diff merely touches, so the two aren't conflated.\n\nNo source file is modified by this review.",
741
+ "outputSha256": "3377c0ddb1a4d03c340032501dd2eec000d437519c8efcb23c3bd8dabaff1180",
742
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
743
+ "deterministic": [],
744
+ "judge": {
745
+ "verdict": "pass",
746
+ "reason": "Answer identifies `@Autowired PaymentClient paymentClient;` as a field with no constructor (criterion 1). It gives concrete reasons: hides dependency graph, blocks `final`, untestable without container/reflection (criterion 2). It names constructor injection with `private final PaymentClient` set via constructor param, explicitly marked 'not applied — this review is read-only' and 'No source file is modified' (criterion 3). No fail criteria hold."
747
+ },
748
+ "passed": true
749
+ },
750
+ {
751
+ "output": "The field itself is the problem: `@Autowired PaymentClient paymentClient;` on a new `@Service` is field injection, which this rule set flags on sight.\n\nWhat's wrong with it:\n\n- **Hidden dependency graph.** The class's dependencies aren't visible in a constructor signature, so nothing tells you at a glance what `PaymentService` needs to function. Adding one is a silent edit to a field rather than a compile-visible change.\n- **Not `final`.** The field can be reassigned; in Kotlin the equivalent (`@Autowired lateinit var` or `@Autowired var`) blocks a `val` and forces `lateinit`, which removes the compiler's non-null guarantee. Constructor injection gives you a `private final PaymentClient paymentClient` (Java) or a constructor `val paymentClient: PaymentClient` (Kotlin) — immutable and non-null by construction.\n- **Untestable without the container.** To unit-test this service you either spin up a Spring context or reach into the field reflectively. With constructor injection you just `new PaymentService(fakeClient)`.\n- **Fails late and confusingly.** If the bean is missing or there's a circular-dependency cycle, the failure surfaces at context startup as a field-injection error, rather than as an immediate, obvious constructor-resolution error.\n\n**Fix direction (not applied — this review is read-only):** delete the field annotation and inject via the constructor, declaring the field `final`. In Kotlin, drop `@Autowired` entirely where there's a single constructor. If `PaymentClient` isn't already a Spring bean, register it (`@Component`/`@Bean`) rather than leaving an `@Autowired` field that can't be satisfied.\n\nOne thing to check before I'd report this as diff-introduced rather than pre-existing: is `@Autowired` on the field new in this diff, or is this a copy of an existing service's style? Both get flagged, but the finding text differs — the first is a new violation to block on, the second is a pattern already in the codebase that the diff is propagating.",
752
+ "outputSha256": "2470fcfa32075f410c70369deccd02b166f9560d2c2355af784bba63e7501ec7",
753
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
754
+ "deterministic": [],
755
+ "judge": {
756
+ "verdict": "pass",
757
+ "reason": "Identifies field-level @Autowired on paymentClient as the issue (\"The field itself is the problem\"). Gives concrete reasons: hidden dependency graph, not final, untestable without container (\"new PaymentService(fakeClient)\"). Names constructor injection with private final field as fix, explicitly \"not applied — this review is read-only.\" No code change made."
758
+ },
759
+ "passed": true
760
+ },
761
+ {
762
+ "output": "Field injection on `paymentClient` — that's the finding.\n\n```\nPaymentService.java — @Autowired PaymentClient paymentClient; declared as a\n mutable field with no constructor injection.\n Risk: field injection hides the dependency graph (the class can be\n instantiated with paymentClient == null unless something reflects into\n it), blocks final/val so the field is reassignable, and makes the\n dependency invisible to anyone who only reads the constructor signature.\n Fix direction: constructor injection -- a private final PaymentClient\n paymentClient assigned in the constructor (Java) or a constructor val\n (Kotlin). Spring will autowire it automatically once there is a single\n constructor; drop the @Autowired annotation on it.\n```\n\nWhy it's a real finding and not just a style preference:\n\n- **Nullability.** With field injection the class is constructible in a\n state where the dependency is unset. Tests that do `new PaymentService()`\n compile and run, then NPE at the first `paymentClient` call rather than\n failing to compile. Constructor injection turns that into a compile error.\n- **Immutability.** `private final PaymentClient` is not available with\n field injection, so nothing prevents a later reassignment of a\n collaborator that should be fixed for the object's lifetime.\n- **Hidden dependencies.** A class with eight `@Autowired` fields advertises\n no coupling in its constructor; constructor injection surfaces the\n coupling at the call site and makes \"this class does too much\" visible.\n\nOn the `@Autowired` annotation itself: with a single constructor Spring\n4.3+ injects it without any annotation, so `@Autowired` on the constructor\nis redundant. Keep it only when there are multiple constructors and one is\nnot the obvious injection target.\n\nScope note for this diff: a single field-injection finding on a new class\nis the whole review for that class unless the same diff also touches\ntransaction boundaries, JPA queries, validation, or security config — none\nof those were mentioned here, so nothing further to report. If the\nsurrounding unchanged code already field-injects elsewhere, that is\npre-existing and should be listed separately rather than folded into this\nfinding.\n\nNo files were modified.",
763
+ "outputSha256": "f674800a1fc6ac4c743f556e140bfd2b1cd9ad8b981ad424a3e0973e5b48ea32",
764
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
765
+ "deterministic": [],
766
+ "judge": {
767
+ "verdict": "pass",
768
+ "reason": "Identifies field-level @Autowired on paymentClient as the finding. Gives concrete reasons: hides dependency graph, blocks final, constructible with null in plain tests. Names constructor injection with private final field set via constructor, and states 'No files were modified' — no code change applied."
769
+ },
770
+ "passed": true
771
+ },
772
+ {
773
+ "output": "The `@Autowired` field injection is a finding, but two things matter more: (1) the class is new, so the finding is diff-introduced — report it against the new file, not as a pre-existing pattern; (2) field injection is the only issue present in the snippet, so don't inflate it into a pattern the code doesn't show.\n\n**Finding 1 — field injection (diff-introduced)**\n`PaymentService.java:1` — `@Autowired private PaymentClient paymentClient;` as a field, no constructor. Risk: the dependency graph is invisible at construction time, the field cannot be `final`, and the class is instantiable in a half-initialized state (a test that does `new PaymentService()` compiles but NPEs on first use). Fix direction: constructor injection — `private final PaymentClient paymentClient;` with a single constructor, dropping `@Autowired` entirely (Spring 4.3+ autowires a lone constructor implicitly). In Kotlin, a constructor `val`.\n\nThat's the whole of what this snippet supports. Two adjacent traps worth confirming, but only report if the code actually has them:\n\n- If the class is new and Spring can't resolve a *unique* `PaymentClient` bean at runtime, startup fails with `NoUniqueBeanDefinitionException`. Not a finding from this snippet — but note the fix direction above (constructor injection) makes that failure surface at startup rather than silently, which is a positive, not a defect.\n- If any `@Transactional` annotation lands on a `PaymentClient`-calling method, watch for self-invocation. There's no `@Transactional` in the snippet, so don't raise it.\n\nDo not report: formatting, comment style, or a `@Service` vs `@Component` debate — all noise here. And do not edit the file; this review is read-only, report the fix direction and stop.",
774
+ "outputSha256": "0a575725e5e8543471da3e844aed04322a908faeaaa62a1954289125932d5f1f",
775
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
776
+ "deterministic": [],
777
+ "judge": {
778
+ "verdict": "pass",
779
+ "reason": "Finding 1 identifies field-level @Autowired on paymentClient (PaymentService.java:1) as the issue. It gives concrete reasons: dependency graph invisible at construction, field cannot be final, half-initialized state/NPE in plain test. Names constructor injection fix: private final PaymentClient with constructor, dropping @Autowired. Explicitly read-only, no code change applied."
780
+ },
781
+ "passed": true
782
+ },
783
+ {
784
+ "output": "Field injection via `@Autowired` on a field is the issue here.\n\n**What's wrong**\n\nThe new `@Service` declares its collaborator as a mutable field:\n\n```java\n@Service\npublic class SomeService {\n @Autowired\n private PaymentClient paymentClient;\n // no constructor\n}\n```\n\nThis breaks down into several concrete problems:\n\n1. **Hidden dependencies.** The class's real dependencies aren't visible in any constructor signature — you have to read the whole body to know what it needs. The class can be instantiated (e.g. `new SomeService()`) in a valid-looking state with `paymentClient == null`, and it only fails at call time with an NPE rather than at construction.\n\n2. **Not testable without the container / reflection.** With constructor injection you can pass a stub or mock directly: `new SomeService(mockClient)`. With field injection, a plain unit test cannot populate the field without reflection (`ReflectionTestUtils.setField(...)`) or booting a Spring context. This is the single most common reason teams ban field injection.\n\n3. **Field can't be `final`.** The field must be non-final and non-`val`, so nothing enforces that it's ever assigned or that it's never reassigned. Constructor injection lets the field be `private final` (Java) / `private val` (Kotlin), making the invariant compiler-checked.\n\n4. **Hides circular dependencies.** Constructor injection fails fast (at startup) on a dependency cycle. Field injection lets the cycle resolve silently at runtime via the raw instance before it's fully initialized, so genuinely broken wiring ships.\n\n5. **Null-safety loss in Kotlin.** In Kotlin Spring code the field would be `lateinit var` or a nullable type, defeating the type system's non-null guarantees for something that is in fact always non-null in production.\n\n**Fix direction (do not apply — this is a review)**\n\nUse constructor injection with a `final`/`val` field:\n\n```java\n@Service\npublic class SomeService {\n private final PaymentClient paymentClient;\n\n public SomeService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nWith a single constructor, `@Autowired` on the constructor is optional in modern Spring — it's inferred. So the cleanest form is just the final field plus the constructor, no annotation at all. In Kotlin:\n\n```kotlin\n@Service\nclass SomeService(private val paymentClient: PaymentClient)\n```\n\n**Finding for the report**\n\n`<file>:<line>` — the new `@Service` injects `PaymentClient` via a field-level `@Autowired` with no constructor. Risk: dependency graph is hidden, the field cannot be `final`/`val`, unit tests must use reflection or a Spring context, and circular dependencies fail late at runtime instead of at startup. Fix direction: replace with a single constructor that assigns a `private final` (Java) / `private val` (Kotlin) field; drop the `@Autowired` since the sole constructor is inferred.\n\nOne scoping note, per the skill's rules: confirm whether this field-injection pattern is **introduced by this diff** or merely **pre-existing** in a file the diff touches, and report it accordingly — a diff-introduced instance is the one to flag as blocking; a pre-existing one in a touched file is worth noting separately.",
785
+ "outputSha256": "ff19a6bca852cc74b75537c1b860f17249c9fa64e04b4d28b6115e1c9c81b360",
786
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
787
+ "deterministic": [],
788
+ "judge": {
789
+ "verdict": "pass",
790
+ "reason": "Criterion 1: names field-level @Autowired on paymentClient as the issue. Criterion 2: gives concrete reasons — hidden dependency graph, can't be final, plain unit test needs reflection/Spring context. Criterion 3: names constructor injection with private final field set via constructor, explicitly marked 'do not apply — this is a review', no change applied."
791
+ },
792
+ "passed": true
793
+ },
794
+ {
795
+ "output": "Field injection on a new `@Service` class — here's the review finding.\n\n**Finding**\n\n`PaymentService.java` — the class declares `@Autowired private PaymentClient paymentClient;` as a field with no constructor. This is field injection rather than constructor injection.\n\n**Why it matters**\n\n- The dependency is not `final`. Field injection means the client can be swapped or left null after construction; immutability and the \"constructed object is fully initialized\" invariant are lost.\n- The dependency graph is hidden from the constructor signature. Anyone reading the public API of the service cannot tell what it needs to function; the only way to discover it is to read the whole class body or reflect on `@Autowired` fields.\n- It defeats easy plain-`new` unit testing. With field injection you typically need the Spring context (or reflection) to supply the dependency; constructor injection lets you pass a mock directly `new PaymentService(mockClient)`.\n- It is easy to accidentally create a partially-initialized bean or a circular-dependency situation that the container papers over, because the constructor no longer forces the dependency to exist at construction time.\n- Spring itself has discouraged field injection for years (it cannot be combined with final fields, and constructor injection is preferred for required dependencies).\n\n**Fix direction (do not apply)**\n\nConvert to constructor injection:\n\n```java\n@Service\npublic class PaymentService {\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nOn a single-constructor class, no `@Autowired` is needed — Spring 4.3+ auto-wires the sole constructor. In Kotlin the equivalent is a `val` constructor parameter: `class PaymentService(private val paymentClient: PaymentClient)`.\n\n**Scope note**\n\nThis finding is introduced by the new class in the diff (a brand-new `@Service` should be born with constructor injection). If the same pattern already exists in other `@Autowired` field sites the diff merely touches or sits next to, note those as pre-existing, not as new regressions from this change.\n\nTwo things worth confirming while you're in the file, since they commonly travel with this pattern:\n\n- If the class also has any other `@Autowired` fields or setter injection for what are actually required collaborators, they belong in the same constructor argument list.\n- If the injected `PaymentClient` is a feign-style HTTP client or a bean whose concrete type is chosen at runtime, make sure the interface (not the impl) is what the constructor takes, so the switch stays at the configuration layer.\n\nThat's the whole finding: field injection on a new `@Service`, direction = constructor injection with a `private final` (Java) or constructor `val` (Kotlin) field, and no `@Autowired` needed when it's the only constructor. Review is read-only — no edits made.",
796
+ "outputSha256": "50dde69c05f30bedcd65ef6e46ca9214fdeb2c21505531d788cd07b8ef05cc5c",
797
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
798
+ "deterministic": [],
799
+ "judge": {
800
+ "verdict": "pass",
801
+ "reason": "Identifies field-level @Autowired on paymentClient (\"declares @Autowired private PaymentClient paymentClient; as a field with no constructor\"). Gives concrete reasons: not final, hides dependency graph from constructor signature, defeats plain-new unit testing. Names constructor injection with private final field set via constructor param, shown as \"Fix direction (do not apply)\" and \"no edits made\" — read-only finding."
802
+ },
803
+ "passed": true
804
+ },
805
+ {
806
+ "output": "The finding is field injection, at the `@Autowired PaymentClient paymentClient;` field on the new `@Service` class.\n\n**Why it matters**\n\n- Field injection hides the dependency graph: the required collaborators of the class are not visible in its constructor signature, so the class's real dependencies only surface when you read the field declarations.\n- The field cannot be `final`, so nothing prevents it from being reassigned after construction, and the class cannot be instantiated with its dependencies in a plain unit test — you are forced to start a Spring context or use reflection to set the field.\n- It also invites circular dependencies to slip through, because Spring can resolve them by populating fields after construction rather than failing fast at construction time.\n\n**Fix direction (not applied here — this review is read-only)**\n\nConvert to constructor injection with a `private final` field:\n\n- Java: declare `private final PaymentClient paymentClient;` and either write a constructor taking it, or drop the `@Autowired` entirely if the class has a single constructor, since Spring injects it implicitly.\n- Kotlin: take it as a constructor `val`, e.g. `class XxxService(private val paymentClient: PaymentClient)`.\n\nDo not edit the file as part of this review; report the finding and the fix direction.\n\n**Note on scope**\n\nCheck whether the diff also touches other classes with the same pattern, and whether this field is genuinely new in the diff or merely visible in code the diff happens to touch. Only the diff-introduced instance belongs under this finding; a pre-existing field in a touched file should be reported separately as pre-existing.",
807
+ "outputSha256": "bc515a32b98832650f5ad88b11a980d5631e74521608c7913bf7720e448e9ba5",
808
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
809
+ "deterministic": [],
810
+ "judge": {
811
+ "verdict": "pass",
812
+ "reason": "Identifies field-level @Autowired on paymentClient (criterion 1). Gives concrete reasons: hides dependency graph, cannot be final, hard to construct in plain unit test without Spring context (criterion 2). Names constructor injection with private final field, explicitly 'not applied here — this review is read-only' (criterion 3). No code change applied; no dismissal of field injection."
813
+ },
814
+ "passed": true
815
+ },
816
+ {
817
+ "output": "Field injection on `PaymentClient` — that's the core issue, plus a couple of things it drags in.\n\n**What's wrong**\n\n- `@Autowired PaymentClient paymentClient;` as a field means the dependency is set by reflection *after* construction, not through the constructor. Consequences: the field can't be `final` (so the dependency is mutable and nullable at the type level to the compiler), the class can't be instantiated with its dependencies in a plain unit test without a Spring context or reflection (Mockito `@InjectMocks` / `ReflectionTestUtils.setField`), and a missing bean fails at context startup or first use rather than at construction. It also hides the dependency graph — you can't see what this service needs from its public constructor.\n- Because there's *no constructor*, the class currently gets an implicit no-arg constructor, so the object is constructible in an invalid state. Nothing enforces that `paymentClient` is ever populated.\n- If the field is also non-`private` (or package-private for test reachability), that's an additional encapsulation leak worth flagging.\n\n**Fix direction** (not applied — this is a read-only review):\n\n```java\n@Service\npublic class PaymentService {\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nSingle-constructor case also allows dropping `@Autowired` entirely — Spring 4.3+ autowires it implicitly. In Kotlin, the equivalent is `class PaymentService(private val paymentClient: PaymentClient)`.\n\n**Findings**\n\n```\nPaymentService.java:<line of field> — @Autowired field injection on\n PaymentClient, with no constructor. Risk: dependency not final,\n object constructible in an invalid state, dependency graph hidden,\n unit tests need a Spring context or reflection. Fix direction:\n replace with constructor injection, field marked private final,\n drop @Autowired (single constructor).\n```\n\n**Adjacent things to check while you're in this file** (report, don't assume):\n\n- Is `PaymentClient` a `@Component`/`@Service`/`@Configuration`-provided bean? Field injection on a type that isn't actually a bean surfaces later and less clearly than a constructor would.\n- Is `PaymentClient` doing an outbound HTTP call from inside a `@Transactional` method? If so, that's the \"blocking I/O holding a transaction open\" pattern worth flagging separately.\n- Is `PaymentService` itself called from another `@Transactional` method on `this` anywhere? That's the self-invocation pitfall and is independent of the injection style.\n- If this service returns a JPA `@Entity` to a controller, that's an API-boundary leak, also independent of the DI issue.\n\nThe injection style is the finding the diff introduces; the rest are pre-existing-or-new depending on what else is in the change and should be labeled accordingly.",
818
+ "outputSha256": "e871c49ae845e0aedcd9c0db9e07417e934138fa8add2ef03bfd2f12bb8373c6",
819
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
820
+ "deterministic": [],
821
+ "judge": {
822
+ "verdict": "pass",
823
+ "reason": "Identifies field @Autowired on paymentClient as the issue (\"Field injection on PaymentClient — that's the core issue\"). Gives concrete reasons: field can't be final, hides dependency graph, unit tests need Spring context/reflection. Names constructor injection with private final field via constructor param, explicitly \"not applied — this is a read-only review.\" No code change claimed."
824
+ },
825
+ "passed": true
826
+ },
827
+ {
828
+ "output": "The problem is **field injection** — `@Autowired` on a field rather than a constructor.\n\n```java\n@Service\npublic class SomeService {\n @Autowired\n private PaymentClient paymentClient; // <-- field injection\n ...\n}\n```\n\nWhat's wrong with it:\n\n- **Hidden dependencies** — the class's collaborators are buried in the body instead of being declared in the constructor signature, so you can't see at a glance what the service needs to function.\n- **Can't be `final`** — a field-injected dependency can't be `private final`, so the field is mutable after construction; nothing enforces that it's set before use.\n- **Breaks outside the container** — you can't `new SomeService(mockClient)` in a plain unit test; you're forced into a Spring context or reflection (e.g. `ReflectionTestUtils`) to inject a test double.\n- **Silently allows a half-built object** — Spring fills fields after construction, so a constructor can run with the dependency still `null`; a constructor-injected `final val`/`final` field guarantees it's present before the object is usable.\n\nFix direction (do not apply, this is a read-only review): switch to constructor injection — a `private final PaymentClient paymentClient` field assigned in the constructor (Java), or a constructor `val paymentClient` (Kotlin). With a single constructor Spring wires it automatically, no `@Autowired` needed on the constructor. This is exactly the finding the skill's DI check flags.\n\n```\nsrc/main/java/.../SomeService.java:<line> — @Autowired field\n `private PaymentClient paymentClient` is field injection. Risk: hidden\n dependency, non-final/mutable, blocks plain-constructor unit tests and\n permits use before the field is populated. Fix direction: constructor\n injection with a private final field (Java) / constructor val (Kotlin);\n drop the @Autowired for a single constructor.\n```\n\nOne note on scope: if this class is new in the diff, report it as a diff-introduced finding; if the diff only touches this file incidentally, call it out as pre-existing.",
829
+ "outputSha256": "4feebc7c0e224a508814e3d1ec4abb66c24855d4096a62bf746a9d2b372fdec4",
830
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
831
+ "deterministic": [],
832
+ "judge": {
833
+ "verdict": "pass",
834
+ "reason": "Identifies field-level @Autowired on paymentClient as the issue (\"field injection — @Autowired on a field rather than a constructor\"). Gives concrete reasons: can't be final, hidden dependencies, breaks plain unit tests without Spring. Names constructor injection with private final field as fix, explicitly \"do not apply, this is a read-only review\" — no code change applied."
835
+ },
836
+ "passed": true
837
+ },
838
+ {
839
+ "output": "Field injection — the concrete finding is this:\n\n```\nPaymentService.java — `@Autowired PaymentClient paymentClient;` field\n with no constructor. Risk: field injection hides the dependency in the\n public API, complicates unit construction, and prevents the field from\n being `private final`. Fix direction: constructor injection --\n `private final PaymentClient paymentClient;` plus a constructor taking\n it (or Lombok `@RequiredArgsConstructor` if the project already uses\n Lombok). Do not apply here -- read-only.\n```\n\nWhy it matters, specifically for this class:\n\n- **Hidden contract.** A constructor signature documents what the bean\n needs to function; a field does not. Anyone constructing `PaymentService`\n by hand (tests, a config class) gets a silently broken instance.\n- **Finality.** The field can't be `private final`, so it can be\n reassigned after construction, which weakens the invariant that the\n dependency is present and constant.\n- **Test friction.** Without a constructor, a unit test must either use the\n Spring context / reflection or mutate the field, instead of just calling\n `new PaymentService(fakeClient)`.\n- **Framework coupling.** The class cannot be instantiated outside of a\n container-managed lifecycle without extra machinery.\n\nIf the diff *also* changes behavior on `PaymentClient` calls, check the\nusual adjacent risks while you're here: whether a `@Transactional` method\nthat calls `paymentClient` is holding the transaction open across an\noutbound HTTP call (worth confirming), and whether any error from\n`PaymentClient` is swallowed rather than propagated or mapped. Both are\nseparate findings from the injection issue and should be reported\nindividually with their own file:line.\n\nScope note: this only reports the pattern the diff introduces. If\n`PaymentService` was already field-injected elsewhere in the codebase\nbefore this change, mark that separately as pre-existing rather than\nattributing it to this diff. No source file was modified.",
840
+ "outputSha256": "029e389c602510edb3acdf5bafca40877c51640f282080422ca6643ee6b0e001",
841
+ "promptSha256": "357a2a46343cf8c060073e03583f86807caef014ff4f0a8cbd370b316e9924da",
842
+ "deterministic": [],
843
+ "judge": {
844
+ "verdict": "pass",
845
+ "reason": "Answer identifies the field-level @Autowired PaymentClient field with no constructor as the issue (criterion 1). It gives concrete reasons: hides dependency in public API, prevents private final, and test friction requiring Spring context/reflection instead of new PaymentService(fakeClient) (criterion 2). It names constructor injection with private final field plus constructor param as fix direction, explicitly stating 'Do not apply here -- read-only' and 'No source file was modified' (criterion 3). No code change applied."
846
+ },
847
+ "passed": true
848
+ }
849
+ ]
850
+ }
851
+ ],
852
+ "verdict": "fail",
853
+ "scope": "bundled",
854
+ "skillDigest": "18bd847ed58afc04d410162441dad24f30e34b122ff0a2f3f5b1749850522d29",
855
+ "catalogDigest": "d09b13e321c66a435263da60f337e323760ef9d3d394b30d1ab3f41817a01f39",
856
+ "judgePromptVersion": "2026-09-25.1",
857
+ "runner": "deepseek",
858
+ "model": "deepseek-chat",
859
+ "runnerPromptVersion": "2026-09-25.1",
860
+ "recordedAt": "2026-09-25T20:58:31.332Z",
861
+ "judge": "deepseek",
862
+ "judgeModel": "deepseek-chat"
863
+ },
864
+ {
865
+ "schemaVersion": "1.0.0",
866
+ "skillId": "java-kotlin-spring/java-kotlin-spring-implementation",
867
+ "strictness": "high",
868
+ "trials": 10,
869
+ "triggerAccuracy": {
870
+ "truePositive": 5,
871
+ "falsePositive": 1,
872
+ "positives": 7,
873
+ "negatives": 6
874
+ },
875
+ "evidence": "authored",
876
+ "scenarios": [
877
+ {
878
+ "id": "trigger-positive-1",
879
+ "kind": "trigger-positive",
880
+ "prompt": "Implement a new Spring Boot REST endpoint for placing an order",
881
+ "strictness": "high",
882
+ "trials": 1,
883
+ "passes": 1,
884
+ "passRate": 1,
885
+ "passAtK": 1,
886
+ "grader": "trigger-rank-fork-family",
887
+ "status": "ran",
888
+ "deterministic": true
889
+ },
890
+ {
891
+ "id": "trigger-positive-2",
892
+ "kind": "trigger-positive",
893
+ "prompt": "I'm building out a new NotificationService in Kotlin that needs to call two repositories -- how should I wire the dependencies through the constructor here?",
894
+ "strictness": "high",
895
+ "trials": 1,
896
+ "passes": 1,
897
+ "passRate": 1,
898
+ "passAtK": 1,
899
+ "grader": "trigger-rank-fork-family",
900
+ "status": "ran",
901
+ "deterministic": true
902
+ },
903
+ {
904
+ "id": "trigger-positive-3",
905
+ "kind": "trigger-positive",
906
+ "prompt": "Wire this OrderRequest DTO with jakarta.validation constraints in Spring",
907
+ "strictness": "high",
908
+ "trials": 1,
909
+ "passes": 1,
910
+ "passRate": 1,
911
+ "passAtK": 1,
912
+ "grader": "trigger-rank-fork-family",
913
+ "status": "ran",
914
+ "deterministic": true
915
+ },
916
+ {
917
+ "id": "trigger-positive-4",
918
+ "kind": "trigger-positive",
919
+ "prompt": "I need a repository method on OrderRepository that fetches orders with their line items in one query instead of N+1 -- what's the cleanest way to write that?",
920
+ "strictness": "high",
921
+ "trials": 1,
922
+ "passes": 0,
923
+ "passRate": 0,
924
+ "passAtK": 0,
925
+ "grader": "trigger-rank-fork-family",
926
+ "status": "ran",
927
+ "deterministic": true
928
+ },
929
+ {
930
+ "id": "trigger-positive-5",
931
+ "kind": "trigger-positive",
932
+ "prompt": "This OrderService method touches three repositories and I need all of it to roll back together if any one write fails -- what's the right way to set that up in Spring?",
933
+ "strictness": "high",
934
+ "trials": 1,
935
+ "passes": 0,
936
+ "passRate": 0,
937
+ "passAtK": 0,
938
+ "grader": "trigger-rank-fork-family",
939
+ "status": "ran",
940
+ "deterministic": true
941
+ },
942
+ {
943
+ "id": "trigger-positive-6",
944
+ "kind": "trigger-positive",
945
+ "prompt": "We're adding refund handling to our Kotlin-based Spring Boot service -- can you build out the controller-to-repository flow for it?",
946
+ "strictness": "high",
947
+ "trials": 1,
948
+ "passes": 1,
949
+ "passRate": 1,
950
+ "passAtK": 1,
951
+ "grader": "trigger-rank-fork-family",
952
+ "status": "ran",
953
+ "deterministic": true
954
+ },
955
+ {
956
+ "id": "trigger-positive-7",
957
+ "kind": "trigger-positive",
958
+ "prompt": "Add a new @RestController endpoint that delegates to this Spring service",
959
+ "strictness": "high",
960
+ "trials": 1,
961
+ "passes": 1,
962
+ "passRate": 1,
963
+ "passAtK": 1,
964
+ "grader": "trigger-rank-fork-family",
965
+ "status": "ran",
966
+ "deterministic": true
967
+ },
968
+ {
969
+ "id": "trigger-negative-1",
970
+ "kind": "trigger-negative",
971
+ "prompt": "Implement this feature in Go using errgroup for concurrency",
972
+ "strictness": "high",
973
+ "trials": 1,
974
+ "passes": 1,
975
+ "passRate": 1,
976
+ "passAtK": 1,
977
+ "grader": "trigger-rank-fork-family",
978
+ "status": "ran",
979
+ "deterministic": true
980
+ },
981
+ {
982
+ "id": "trigger-negative-2",
983
+ "kind": "trigger-negative",
984
+ "prompt": "Implement this feature in a Python FastAPI service",
985
+ "strictness": "high",
986
+ "trials": 1,
987
+ "passes": 1,
988
+ "passRate": 1,
989
+ "passAtK": 1,
990
+ "grader": "trigger-rank-fork-family",
991
+ "status": "ran",
992
+ "deterministic": true
993
+ },
994
+ {
995
+ "id": "trigger-negative-3",
996
+ "kind": "trigger-negative",
997
+ "prompt": "Implement this React component with the new form fields",
998
+ "strictness": "high",
999
+ "trials": 1,
1000
+ "passes": 1,
1001
+ "passRate": 1,
1002
+ "passAtK": 1,
1003
+ "grader": "trigger-rank-fork-family",
1004
+ "status": "ran",
1005
+ "deterministic": true
1006
+ },
1007
+ {
1008
+ "id": "trigger-negative-4",
1009
+ "kind": "trigger-negative",
1010
+ "prompt": "Add a new Node.js Express endpoint for this feature",
1011
+ "strictness": "high",
1012
+ "trials": 1,
1013
+ "passes": 1,
1014
+ "passRate": 1,
1015
+ "passAtK": 1,
1016
+ "grader": "trigger-rank-fork-family",
1017
+ "status": "ran",
1018
+ "deterministic": true
1019
+ },
1020
+ {
1021
+ "id": "trigger-negative-5",
1022
+ "kind": "trigger-negative",
1023
+ "prompt": "Review this Spring Boot service for @Transactional self-invocation bugs",
1024
+ "strictness": "high",
1025
+ "trials": 1,
1026
+ "passes": 0,
1027
+ "passRate": 0,
1028
+ "passAtK": 0,
1029
+ "grader": "trigger-rank-fork-family",
1030
+ "status": "ran",
1031
+ "deterministic": true
1032
+ },
1033
+ {
1034
+ "id": "trigger-negative-6",
1035
+ "kind": "trigger-negative",
1036
+ "prompt": "Fix this failing Maven build for the Spring project",
1037
+ "strictness": "high",
1038
+ "trials": 1,
1039
+ "passes": 1,
1040
+ "passRate": 1,
1041
+ "passAtK": 1,
1042
+ "grader": "trigger-rank-fork-family",
1043
+ "status": "ran",
1044
+ "deterministic": true
1045
+ },
1046
+ {
1047
+ "id": "constructor-vs-field-injection",
1048
+ "kind": "behavior",
1049
+ "prompt": "I'm adding a new @Service in this Spring Boot app that depends on a PaymentClient. How should I wire the dependency?",
1050
+ "strictness": "high",
1051
+ "trials": 10,
1052
+ "passes": 10,
1053
+ "passRate": 1,
1054
+ "passAtK": 1,
1055
+ "grader": "judge",
1056
+ "status": "ran",
1057
+ "trialRecords": [
1058
+ {
1059
+ "output": "Wire it through the service's constructor — never a field-level `@Autowired`.\n\n**Java**\n\n```java\n@Service\npublic class OrderService {\n\n private final PaymentClient paymentClient;\n\n public OrderService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\n**Kotlin**\n\n```kotlin\n@Service\nclass OrderService(private val paymentClient: PaymentClient)\n```\n\nIf `PaymentClient` is your own bean, that's all you need — with a single constructor Spring injects it automatically, no `@Autowired` on the constructor required. Add `@Autowired` on the constructor only if you ever add a second constructor (e.g. one for tests taking plain fakes), and even then prefer `@TestConfiguration`/`@MockBean` over a second constructor.\n\n**Why constructor, not field:**\n\n- The field must be `private final` (Java) or a `val` (Kotlin), so the object can't be constructed in a half-wired state — no null client at runtime.\n- The class is instantiable in a plain unit test with `new OrderService(new FakePaymentClient())`; no Spring container, no reflection, no `ReflectionTestUtils.setField`.\n- A dependency cycle shows up as a startup failure instead of being silently resolved, which is the behavior you want.\n- `rules/coding-style.mdc` calls field injection out explicitly — flag it if you find it in a neighboring class you're editing, and don't propagate it into new code.\n\n**A couple of things to check while you're here:**\n\n- If `PaymentClient` is a remote HTTP client configured as a bean (e.g. a `WebClient`/`RestClient`-backed wrapper, or a Feign/`@HttpExchange` interface), remember to put the `@Transactional` boundary on *your* service method, not around the client call — don't hold a database transaction open across a network round-trip to the payment provider.\n- If `PaymentClient` itself is `@Transactional` and you'd be calling one of its transactional methods from inside a method of your own service that is also `@Transactional`, that's a different bean so the proxy fires correctly — the self-invocation pitfall only bites when the call target is `this`.\n- Keep the controller thin: inject `OrderService` into it by constructor the same way, and keep the `PaymentClient` out of the controller entirely.\n\nIf `PaymentClient` doesn't exist yet and you're defining it too, tell me whether it's an interface backed by a `RestClient`/`WebClient` or a Feign client and I'll sketch that side as well.",
1060
+ "outputSha256": "9f3faf3f05aec74e8b874142d381d725e4cf11034c3e5056837cdf211306d5f9",
1061
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1062
+ "deterministic": [],
1063
+ "judge": {
1064
+ "verdict": "pass",
1065
+ "reason": "Answer states 'never a field-level @Autowired' and shows Java `private final PaymentClient paymentClient;` with constructor `public OrderService(PaymentClient paymentClient){this.paymentClient=paymentClient;}` and Kotlin `class OrderService(private val paymentClient: PaymentClient)`. Explains why: private final/val prevents half-wired state, and instantiable in plain unit test with `new OrderService(new FakePaymentClient())` without Spring container. No field @Autowired recommended."
1066
+ },
1067
+ "passed": true
1068
+ },
1069
+ {
1070
+ "output": "Wire it through the service's own constructor, and never with a field-level `@Autowired`.\n\nJava:\n\n```java\n@Service\npublic class PaymentService {\n\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin:\n\n```kotlin\n@Service\nclass PaymentService(private val paymentClient: PaymentClient)\n```\n\nWhy constructor injection rather than field injection:\n\n- The dependency becomes `final` (Java) or a constructor `val` (Kotlin), so the object cannot exist in a half-wired state.\n- You can instantiate the service in a plain unit test with `new PaymentService(fakeClient)` — no Spring container, no reflection, no `@SpringBootTest` just to satisfy a field.\n- The dependency graph is visible in the signature rather than scattered across `@Autowired` field annotations, which makes circular dependencies fail loudly at startup instead of silently.\n\nField injection (`@Autowired private PaymentClient paymentClient;`) still works at runtime, but it is a deviation from `rules/coding-style.mdc` and this skill's rules — do not copy it into new code even if a neighboring class in the package happens to use it.\n\nA few things to check while you are in there:\n\n- Make sure `PaymentClient` itself is a Spring-managed bean (annotated with `@Component`/`@Service`, or produced by a `@Bean` method) so there is actually something to inject. If it comes from an auto-configured HTTP client you may need to declare that bean yourself.\n- If `PaymentClient` is only used to make outbound calls, keep it out of the transaction boundary reasoning — do not wrap the service in `@Transactional` and then make a network call inside it, since that holds a database connection open across a remote call.\n- If this new service is a Kotlin class and `PaymentClient` is optional rather than required, prefer making the dependency non-nullable and required instead of using `lateinit` or `@Autowired(required = false)`; a missing required dependency should fail at startup, not on first use.\n\nThen verify with the project's own build tool (`./gradlew build test` or `mvn verify`), and confirm by inspection that the new service has no field-level `@Autowired` and that the constructor parameter is the only place the dependency enters.",
1071
+ "outputSha256": "9377911775ea8a57c9a5ca0a984c3bdb9fdfea1daa7595cbb5b5a76e578450e5",
1072
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1073
+ "deterministic": [],
1074
+ "judge": {
1075
+ "verdict": "pass",
1076
+ "reason": "Answer shows Java private final PaymentClient field with constructor assigning it, and Kotlin primary-constructor val property. Explains why: final/val prevents half-wired state, and plain unit test with new PaymentService(fakeClient) without Spring container. Field @Autowired only mentioned to warn against it."
1077
+ },
1078
+ "passed": true
1079
+ },
1080
+ {
1081
+ "output": "Wire it through the service's own constructor — that's the rule this skill enforces (`rules/coding-style.mdc`), not a preference.\n\nJava:\n\n```java\n@Service\npublic class BillingService {\n\n private final PaymentClient paymentClient;\n\n public BillingService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin:\n\n```kotlin\n@Service\nclass BillingService(private val paymentClient: PaymentClient)\n```\n\nPoints that matter here:\n\n- **Do not add a field-level `@Autowired`.** `@Autowired private PaymentClient paymentClient;` (or a setter injection) compiles and runs, but it hides the dependency graph, prevents the field from being `private final`/`val`, and makes the class need the Spring container to construct in a test. Constructor injection costs one line more and removes all of that.\n- If the class has a single constructor, you don't need `@Autowired` on it at all — Spring injects automatically. Annotating it is harmless but redundant; check what neighboring services in this codebase do and match them.\n- `PaymentClient` itself must be a Spring-managed bean (`@Component`/`@Service`/a `@Bean` in a `@Configuration`, or a Feign/`@HttpExchange` client interface registered with `@EnableFeignClients`/`@Import`). If it's a plain class from a third-party library, don't annotate the library class — declare it as a `@Bean` method in a `@Configuration` and inject that.\n- Before writing, glance at 1–2 existing `@Service` classes in the same package: if they use Lombok `@RequiredArgsConstructor` on `private final` fields or Kotlin primary-constructor `val`s, follow that exact idiom rather than hand-writing a constructor that fights the surrounding style.\n- If `BillingService` later needs to call another `@Transactional` method, don't put that method on `BillingService` and call it via `this` — inject the collaborator as a separate bean instead, so the call goes through the proxy.\n\nThen verify with the project's own build tool (`./gradlew build` or `mvn verify`; check for `pom.xml` vs. `build.gradle`) before calling it done.",
1082
+ "outputSha256": "51abaed7f23d0871d8b69fa4da290bded7b88b8c291b6ada5356a3c4b2af169b",
1083
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1084
+ "deterministic": [],
1085
+ "judge": {
1086
+ "verdict": "pass",
1087
+ "reason": "Answer shows Java `private final PaymentClient paymentClient;` with constructor `public BillingService(PaymentClient paymentClient){ this.paymentClient = paymentClient; }` and Kotlin `class BillingService(private val paymentClient: PaymentClient)`. It explicitly warns against field-level @Autowired, citing immutability (final/val) and testability without the Spring container. All criteria met."
1088
+ },
1089
+ "passed": true
1090
+ },
1091
+ {
1092
+ "output": "Constructor-inject it. In Java, a `private final` field assigned in the constructor:\n\n```java\n@Service\npublic class OrderService {\n\n private final PaymentClient paymentClient;\n\n public OrderService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nIn Kotlin, a constructor `val` property:\n\n```kotlin\n@Service\nclass OrderService(private val paymentClient: PaymentClient)\n```\n\nWhy this and not field injection:\n\n- The dependency graph is visible in the constructor signature, and the field can be `final`/`val`, so it can't be reassigned after construction.\n- The class is instantiable in a plain unit test with a stub or mock (`new OrderService(fakePaymentClient)`), no Spring container or reflection needed.\n- Spring fails fast at startup on a missing/multiple candidate bean rather than NPE'ing on first use.\n\nDo not add a field-level `@Autowired`:\n\n```java\n@Service\npublic class OrderService {\n @Autowired\n private PaymentClient paymentClient; // no\n}\n```\n\nAlso worth deciding before you write it: is `PaymentClient` a `@Component`/`@Service` already, or a `@Bean` you need to declare in a `@Configuration` class (e.g. it wraps a `RestClient`/`WebClient` with a base URL and auth)? If there could be more than one `PaymentClient` bean in the context, give this one a qualifier name at its declaration site and match it at the injection point rather than relying on field name matching.",
1093
+ "outputSha256": "aa4a7ccd381b8d36ad51081c62d3d0e3fc2ec977219a9924245e21cc89aae162",
1094
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1095
+ "deterministic": [],
1096
+ "judge": {
1097
+ "verdict": "pass",
1098
+ "reason": "Answer states constructor injection with `private final PaymentClient paymentClient` assigned in constructor (Java) and `class OrderService(private val paymentClient: PaymentClient)` (Kotlin), showing concrete constructor shapes. Explains why: final/val immutability, plain unit test with `new OrderService(fakePaymentClient)` without Spring container. Warns against field @Autowired rather than recommending it."
1099
+ },
1100
+ "passed": true
1101
+ },
1102
+ {
1103
+ "output": "Constructor injection. Declare the field `private final` in Java (or a constructor `val` in Kotlin) and take it as a constructor parameter:\n\n```java\n@Service\npublic class PaymentService {\n\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nA few things that follow from this in this codebase:\n\n- **No `@Autowired` on the field.** If `PaymentClient` is a single bean of its type, Spring resolves the constructor parameter automatically — no annotation needed on the constructor either. If there is genuinely more than one bean of that type, qualify the parameter (`@Qualifier(\"...\")` on the constructor param) rather than falling back to field injection.\n- **Match the neighbors.** Before you commit, read one or two existing `@Service` classes in the same package. If they already use constructor injection (the norm for this stack), this is just consistency. If you find field-level `@Autowired` in the neighborhood, do not copy that pattern into new code — it is a deviation worth flagging separately, not extending.\n- **If `PaymentClient` is a `@Configuration`-provided bean** (e.g. an outbound HTTP client built and exposed via `@Bean`), the same constructor-injection rule applies; you are injecting the bean Spring produces, not hand-rolling the client in the service.\n- **Keep the dependency narrow.** `PaymentService` should depend on `PaymentClient` (the port), not reach through it to build a raw HTTP client itself — that keeps the service testable with a stub and keeps the transport concern in one place.\n\nIf you later find yourself calling another `@Transactional` method on `this` from inside a method of the same service (that self-invocation proxy pitfall), the fix is to extract that method into a separate constructor-injected bean — which is another reason to keep dependencies constructor-injected and the class's responsibility narrow from the start.",
1104
+ "outputSha256": "5a94d4c2c3bf47fb55511a428a716ff72ff68f4851bb5f2721976caa6b5ddbdc",
1105
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1106
+ "deterministic": [],
1107
+ "judge": {
1108
+ "verdict": "pass",
1109
+ "reason": "Answer states constructor injection with `private final PaymentClient paymentClient` and no field @Autowired (criterion 1), shows the concrete constructor assigning the param to the field (criterion 2), and explains testability with a stub plus immutability/narrow deps (criterion 3). It warns against field @Autowired rather than recommending it, so no fail criterion holds."
1110
+ },
1111
+ "passed": true
1112
+ },
1113
+ {
1114
+ "output": "Constructor injection, same as every other Spring-managed dependency in the class.\n\n```java\n@Service\npublic class OrderService {\n\n private final PaymentClient paymentClient;\n\n public OrderService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\n```kotlin\n@Service\nclass OrderService(private val paymentClient: PaymentClient)\n```\n\nPoints that matter:\n\n- **No `@Autowired` on the constructor** when the class has a single constructor — Spring 4.3+ wires it implicitly. Adding the annotation is noise, not a requirement.\n- **The field is `private final` (Java) / `val` (Kotlin)** — not `@Autowired private PaymentClient paymentClient;`. Field injection hides the dependency graph from anyone reading the class, blocks `final`/`val`, and forces tests to boot the Spring container (or resort to reflection) just to supply a fake. The one extra line of constructor buys you `new OrderService(fakePaymentClient)` in a unit test.\n- **Don't \"help\" with `@Autowired` on the field** even if a neighbor in the same package does it. Field injection in existing code is a deviation, not a convention to copy forward.\n\nIf `PaymentClient` is itself a `@Component`/`@Service`/`@Bean` somewhere (or a `@FeignClient`, or a `RestClient`-backed adapter you're about to write), this is all you need — the constructor parameter is the entire wiring. If it isn't yet a bean, that's the real work: define it as one (e.g. a `@Bean` method in a `@Configuration` class wrapping the underlying HTTP client), then inject it exactly as above.\n\nOne thing to decide at the same time: if `PaymentClient`'s calls are slow or failure-prone, consider whether the call belongs inside the `@Transactional` boundary you're placing on the service method. Holding a DB transaction open across a remote payment call is a common way to burn through your connection pool under load. If that's the shape here, structure the method so the payment call happens outside the transaction (or in its own short transaction), rather than just injecting and moving on.",
1115
+ "outputSha256": "a3f1f77e00f88040bc7243dae77633edd48bcde700dbf049e31648d2c7b7bbd1",
1116
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1117
+ "deterministic": [],
1118
+ "judge": {
1119
+ "verdict": "pass",
1120
+ "reason": "Answer shows Java `private final PaymentClient paymentClient;` with constructor `public OrderService(PaymentClient paymentClient) { this.paymentClient = paymentClient; }` and Kotlin `class OrderService(private val paymentClient: PaymentClient)`. It explicitly warns against `@Autowired private PaymentClient paymentClient;` and explains testability (`new OrderService(fakePaymentClient)`) and final/val immutability. All criteria met."
1121
+ },
1122
+ "passed": true
1123
+ },
1124
+ {
1125
+ "output": "Constructor-inject it as a `private final` field (Java) or a constructor `val` property (Kotlin):\n\n```java\n@Service\npublic class OrderService {\n\n private final PaymentClient paymentClient;\n\n public OrderService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin equivalent:\n\n```kotlin\n@Service\nclass OrderService(private val paymentClient: PaymentClient)\n```\n\nNotes that matter for this specific case:\n\n- **Don't add a field-level `@Autowired`** (`@Autowired private PaymentClient paymentClient;`). It hides the dependency graph, prevents the field from being `final`/`val`, and forces reflection to test the class outside the Spring container. Constructor injection costs one line more and makes the class constructible with a plain `new OrderService(mockPaymentClient)` in a unit test.\n\n- **Single constructor needs no annotation.** Spring auto-wires a class with exactly one constructor since 4.3; an explicit `@Autowired` on the constructor is redundant. Add `@Autowired` only if you introduce a second constructor and need to disambiguate.\n\n- **`PaymentClient` must itself be a Spring bean** — either a class annotated `@Component`/`@Service`, or a `@Bean` method in a `@Configuration` class (typical for a generated HTTP client or one built with a builder). If it's declared as a `@Bean`, make sure exactly one such bean exists; two candidates with no `@Primary`/`@Qualifier` will fail startup with `NoUniqueBeanDefinitionException`.\n\n- **No `OrderService`-side setup needed for injection to work**, but if `PaymentClient` is a remote HTTP client, configure its timeout/retry settings at the bean definition (or via `application.yml` properties bound to the client builder), not inside `OrderService`.\n\nIf you later add a method to `OrderService` that calls another `@Transactional` method on `this`, that's a separate pitfall — a same-class call bypasses Spring's transaction proxy and the inner annotation silently becomes a no-op. Extract that method into its own constructor-injected bean if it needs its own transaction boundary.",
1126
+ "outputSha256": "2930c65dfde9088a42943018bd9efa20bfd5dfe363a1d48fc945449b9c06b812",
1127
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1128
+ "deterministic": [],
1129
+ "judge": {
1130
+ "verdict": "pass",
1131
+ "reason": "Answer states constructor injection as private final field (Java) and constructor val property (Kotlin), explicitly warning against field-level @Autowired. Shows concrete constructor: `public OrderService(PaymentClient paymentClient) { this.paymentClient = paymentClient; }` and Kotlin `class OrderService(private val paymentClient: PaymentClient)`. Explains why: immutability (final/val) and testability via `new OrderService(mockPaymentClient)` without Spring container."
1132
+ },
1133
+ "passed": true
1134
+ },
1135
+ {
1136
+ "output": "Constructor injection. Declare it as a constructor parameter and assign it to a `private final` field (Java) or a constructor `val` (Kotlin) — never a field-level `@Autowired`.\n\nJava:\n\n```java\n@Service\npublic class PaymentService {\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin:\n\n```kotlin\n@Service\nclass PaymentService(private val paymentClient: PaymentClient)\n```\n\nWhy constructor over field injection:\n\n- The field can be `final`/`val`, so the dependency can't be reassigned after construction.\n- If `PaymentClient` is missing from the context, the app fails fast at startup with a clear \"no qualifying bean\" error instead of an NPE on first use.\n- The class is unit-testable by passing a stub/mock directly into `new PaymentService(...)` — no Spring context or reflection needed.\n- The dependency graph is visible in the constructor signature instead of scattered across fields.\n\nIf this is a Kotlin `@Service` note that a single-constructor class needs no `@Autowired` at all — Spring injects it automatically when there's exactly one constructor. Same for Java with a single constructor (Spring 4.3+).\n\nTwo things to check in the existing code before you write this:\n\n1. Look at 1-2 neighboring `@Service` classes. If they already use constructor injection, match that exactly. If you find field-level `@Autowired` in the neighborhood, don't copy it — that's a deviation from the convention, add the new class with constructor injection anyway.\n2. Confirm `PaymentClient` is actually a Spring-managed bean (annotated `@Component`/`@Service`, or produced by a `@Bean` method). If it's a third-party client you're just instantiating, you'll need a `@Configuration` class exposing it as a `@Bean`, or the constructor injection above won't resolve.\n\nIf `PaymentClient` has optional-like semantics, prefer a separate configuration bean over `@Autowired(required=false)` — optional dependencies are still worth keeping explicit in the constructor so the wiring stays honest.",
1137
+ "outputSha256": "e4461781e1a159cfad2cc74c1476610ed88f684988dc521f6b3032804d8440bb",
1138
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1139
+ "deterministic": [],
1140
+ "judge": {
1141
+ "verdict": "pass",
1142
+ "reason": "Answer states constructor injection with private final field (Java) / constructor val (Kotlin), never field-level @Autowired. Shows concrete constructor: `public PaymentService(PaymentClient paymentClient) { this.paymentClient = paymentClient; }` and Kotlin `class PaymentService(private val paymentClient: PaymentClient)`. Explains why: final/val immutability, unit-testable via `new PaymentService(...)` without Spring context. No field @Autowired recommended."
1143
+ },
1144
+ "passed": true
1145
+ },
1146
+ {
1147
+ "output": "Constructor injection — declare the dependency as a constructor parameter and store it in a `private final` field (Java) or a constructor `val` property (Kotlin). Do not use field-level `@Autowired`.\n\nJava:\n```java\n@Service\npublic class PaymentService {\n\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin:\n```kotlin\n@Service\nclass PaymentService(private val paymentClient: PaymentClient)\n```\n\nWith a single constructor, Spring injects it without you needing to annotate the constructor at all.\n\nWhy constructor injection and not field injection:\n\n- The field can be `final` (Java) / `val` (Kotlin), so the dependency can't be swapped out or null'ed after construction.\n- It makes the dependency graph explicit and the class directly instantiable in a plain unit test (`new PaymentService(fakeClient)`) without booting the Spring container or using reflection to set a private field.\n- It avoids the hidden circular-dependency and partial-initialization problems that field injection tolerates silently.\n\nA few things to check while you're in there:\n\n- `PaymentClient` itself must be a Spring bean — an interface with a `@Component`/`@Service` implementation, or a `@Bean` method in a `@Configuration` class, or a client stub produced by whatever HTTP-client starter the project uses. If it's only a plain class with no bean registration, the constructor injection will fail at startup with `NoSuchBeanDefinitionException`.\n- If more than one bean implements the client interface, disambiguate with `@Qualifier` on the constructor parameter rather than falling back to field injection.\n- If the client has more than one constructor or is a third-party class you can't annotate, define it as a `@Bean` in a `@Configuration` class.\n- If this service method also needs a transaction boundary, put `@Transactional` on the service method that defines the unit of work — but be aware a call from one `@Transactional` method in this class to another method in the same class bypasses the proxy, so extract that inner method into a separate injected bean if it needs its own transaction semantics.\n\nBefore finalizing, read one or two neighboring `@Service` classes in the same package: match the existing package layout and confirm whether the project already uses `@RequiredArgsConstructor` (Lombok) or another convention for constructor wiring, so this new class looks like the rest of the codebase rather than introducing a second style.",
1148
+ "outputSha256": "788b0b6e5cb7f5932b3273accd52a7f3bf7e7e1b4de2187567c25d7365f76f06",
1149
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1150
+ "deterministic": [],
1151
+ "judge": {
1152
+ "verdict": "pass",
1153
+ "reason": "Answer states constructor injection with private final field (Java) / constructor val (Kotlin) and explicitly says not to use field-level @Autowired. Shows concrete constructor: `public PaymentService(PaymentClient paymentClient) { this.paymentClient = paymentClient; }` and Kotlin `class PaymentService(private val paymentClient: PaymentClient)`. Explains why: final/val immutability and plain unit test `new PaymentService(fakeClient)` without Spring container. No field @Autowired recommended."
1154
+ },
1155
+ "passed": true
1156
+ },
1157
+ {
1158
+ "output": "Wire it via constructor injection: declare the dependency as a `private final` field (Java) or a `val` constructor property (Kotlin) and let Spring supply it through the single constructor.\n\nJava:\n\n```java\n@Service\npublic class PaymentService {\n\n private final PaymentClient paymentClient;\n\n public PaymentService(PaymentClient paymentClient) {\n this.paymentClient = paymentClient;\n }\n}\n```\n\nKotlin:\n\n```kotlin\n@Service\nclass PaymentService(private val paymentClient: PaymentClient)\n```\n\nNotes:\n\n- Don't use field-level `@Autowired` (`@Autowired private PaymentClient paymentClient;`). It hides the dependency graph, prevents `final`/`val`, and makes the class untestable without the Spring container. You can construct the class directly in a plain unit test with a stub `PaymentClient`.\n- With exactly one constructor, Spring 4.3+ injects automatically — no `@Autowired` needed on the constructor. If the class has more than one constructor and only one is the injection target, add `@Autowired` to that one, but a single constructor is the cleaner design.\n- If `PaymentClient` is a hand-written implementation, annotate it `@Component`/`@Service` so it's a bean; if it's generated by `@EnableFeignClients` or a `RestClient`/`WebClient` `@Bean`, confirm the bean exists in the context before relying on it.\n- Before writing this, check the surrounding package for the project's existing DI style so the new class matches it rather than introducing a different convention.",
1159
+ "outputSha256": "a779fb7fdf3778ed125d53c3a1543b8bb21ca37613eea94956daf7b289553695",
1160
+ "promptSha256": "c1fbcc5a52576856b24fa7a524a33859f05da335e92356fccc01200491a0e611",
1161
+ "deterministic": [],
1162
+ "judge": {
1163
+ "verdict": "pass",
1164
+ "reason": "Answer states constructor injection with 'private final' field (Java) and 'val' constructor property (Kotlin), explicitly warns against field-level @Autowired. Shows concrete constructor: Java constructor assigning this.paymentClient, Kotlin primary-constructor property. Explains why: immutability (final/val) and testability without Spring container."
1165
+ },
1166
+ "passed": true
1167
+ }
1168
+ ]
1169
+ },
1170
+ {
1171
+ "id": "transactional-self-invocation",
1172
+ "kind": "behavior",
1173
+ "prompt": "This Spring service has an outer @Transactional method that calls `this.processPayment()` on the same class. processPayment() is itself annotated `@Transactional(propagation = Propagation.REQUIRES_NEW)`, because a payment capture must commit independently of the outer transaction even if the outer one later rolls back. Does that work as expected?",
1174
+ "strictness": "high",
1175
+ "trials": 10,
1176
+ "passes": 10,
1177
+ "passRate": 1,
1178
+ "passAtK": 1,
1179
+ "grader": "judge",
1180
+ "status": "ran",
1181
+ "trialRecords": [
1182
+ {
1183
+ "output": "No — it does not work as written. This is the exact self-invocation pitfall the skill's Rule #3 and red-flags table call out, and the `REQUIRES_NEW` twist makes it worse than a plain case, because it fails *silently and in the direction that loses the money-critical guarantee you were trying to buy*.\n\n**Why it doesn't work**\n\nSpring's declarative transaction handling is proxy-based. When an external caller invokes the outer `@Transactional` method, the call goes through the CGLIB/JDK proxy, which starts the outer transaction and then delegates to the real bean instance. But once execution is *inside* that real instance, a call to `this.processPayment()` is an ordinary Java virtual method dispatch on `this` — it never re-enters the proxy. The proxy is what reads the annotation and asks the `PlatformTransactionManager` to suspend the current transaction and start a new one. Since the proxy is bypassed, the annotation is never consulted: `processPayment()` simply executes as a plain method call in the *already-active outer transaction*.\n\n**The consequence is the opposite of the intent**\n\nYou wanted the payment capture to commit independently, so that a later rollback of the outer transaction leaves the capture intact. Instead:\n\n- The capture runs in the outer transaction's context.\n- If the outer transaction rolls back, the capture's writes roll back with it — you lose the payment record even though the money may already have moved at the processor.\n- If the outer transaction commits, the capture commits as part of it — so you also lose the *reverse* isolation (an outer rollback for an unrelated downstream failure, e.g. inventory or notification, now silently discards a capture that did happen externally).\n\nSo you get neither atomicity-with-the-outer-unit nor the independent-commit guarantee you asked for. It's the worst of both: the annotation reads as if it protects you, and it protects nothing.\n\n**The fix — extract to a separate bean**\n\n`REQUIRES_NEW` requires the call to cross a proxy boundary, which means the two methods must live on different beans. Move `processPayment()` onto its own `@Service`, constructor-inject it into the outer service, and call it through the injected reference:\n\n```java\n@Service\npublic class PaymentService {\n // real payment-capture logic, no knowledge of the caller\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public PaymentResult processPayment(PaymentRequest request) { ... }\n}\n\n@Service\npublic class OrderService {\n private final PaymentService paymentService; // constructor-injected\n\n public OrderService(PaymentService paymentService) {\n this.paymentService = paymentService;\n }\n\n @Transactional\n public OrderResult placeOrder(OrderRequest request) {\n // goes through the proxy -> REQUIRES_NEW actually takes effect:\n // outer tx suspends, a new tx starts and commits on its own\n PaymentResult payment = paymentService.processPayment(...);\n ...\n }\n}\n```\n\nNow the outer transaction suspends, `processPayment()` gets a genuinely independent transaction that commits on its own, and a subsequent rollback of the outer unit does not undo the capture.\n\n**Two caveats worth deciding on explicitly**\n\n1. **Injection-cycle check.** Extracting to a new bean can create a circular dependency if `PaymentService` in turn needs something that depends back on `OrderService`. If so, that's a design smell, not a wiring problem to paper over — break the cycle by moving the shared collaborator down or introducing a third bean, not by injecting a lazy self-reference.\n2. **`REQUIRES_NEW` opens a second DB connection.** The inner transaction holds its own connection for the duration of the capture, concurrent with the suspended outer one. Under load this can exhaust the pool. Size the pool for the worst case or the capture will block on connection acquisition at exactly the moment you can least afford it.\n\n**How to confirm the current code is really broken**\n\nThe tell is the suspended-transaction behavior. With the self-invocation bug fixed, you should see, in the transaction/connection logs, a \"Suspending current transaction\" followed by \"Creating new transaction\" around the capture, and the pool should show two concurrent connections during that window. If the logs show no suspend/create pair — just the outer transaction starting once — the annotation is being ignored and you're in the broken state.\n\n**Verification before calling this done**\n\n- `processPayment()` is invoked through an injected reference, not `this`.\n- The two methods live on different Spring-managed beans.\n- An integration test forces the outer transaction to roll back *after* a successful capture and asserts the capture row is still present and committed. That test is the only thing that actually proves `REQUIRES_NEW` took effect; a passing unit test with a mocked payment service proves nothing here, because the mock never exercises the proxy at all.\n- Connection-pool sizing accounts for the suspended-plus-new connection during the capture window.\n\nOne scoping note: this is a diagnosis of an existing pattern, which sits right on the boundary of this skill's \"implement new code\" scope — the fix above is implementation, and if you want the extraction written out against your actual class names and package layout, that's squarely in-scope. If instead the immediate question is diagnosing an already-written diff, the pack's own review skill is the better entry point.",
1184
+ "outputSha256": "0626ca2e73645a0b3a9808cbbcffcaf5a56c791c24f8e4c38eca44bc97e3cbd6",
1185
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1186
+ "deterministic": [],
1187
+ "judge": {
1188
+ "verdict": "pass",
1189
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so REQUIRES_NEW annotation is never consulted (\"never re-enters the proxy... annotation is never consulted\"). Names concrete fix: extract processPayment() to separate @Service PaymentService, constructor-inject into OrderService, call paymentService.processPayment(). States consequence: capture runs in outer tx, rolls back with outer rollback, losing independent commit. No fail criterion."
1190
+ },
1191
+ "passed": true
1192
+ },
1193
+ {
1194
+ "output": "No — it does not work as expected, and this is one of the more dangerous versions of the self-invocation pitfall because the failure is silent and the intent is exactly the thing that gets defeated.\n\n## Why it fails\n\nSpring's declarative transaction management is proxy-based. The `@Transactional` behavior on `processPayment()` is implemented by a proxy (JDK dynamic proxy or CGLIB subclass) that wraps the bean. When external code calls `orderService.processPayment()`, that call passes through the proxy, which starts the `REQUIRES_NEW` transaction.\n\nBut when `processPayment()` is invoked as `this.processPayment()` from *another method of the same class*, the call never leaves the object instance. `this` is the raw target object, not the proxy. The proxy is bypassed entirely, so:\n\n- No new transaction is started.\n- No `REQUIRES_NEW` semantics apply.\n- `processPayment()` simply runs as an ordinary method call inside whatever transaction the outer method already established.\n\nSo the payment capture does **not** commit independently. It participates in the outer transaction. If the outer transaction later rolls back, the payment capture rolls back with it — the exact scenario the annotation was meant to protect against. You also get no new physical transaction, so you do not get the independent connection/locking behavior `REQUIRES_NEW` implies either.\n\n## Why this is worse than an ordinary self-invocation bug\n\nWith a plain `@Transactional` inner method, the failure mode is usually \"the inner method runs in the caller's transaction,\" which is often acceptable. Here the entire point of `REQUIRES_NEW` — independent commit/rollback — is silently lost. The code reads as if a capture is durably committed the moment it succeeds, but in production a later failure in the outer unit of work can unwind the capture. Money is involved. The annotation is documentation that lies.\n\n## How to fix it\n\nExtract `processPayment()` into a separate Spring bean and constructor-inject it, so the call goes through that bean's proxy:\n\n```java\n@Service\npublic class PaymentProcessor {\n\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public PaymentResult processPayment(PaymentRequest request) {\n // ...\n }\n}\n\n@Service\npublic class OrderService {\n\n private final PaymentProcessor paymentProcessor;\n\n public OrderService(PaymentProcessor paymentProcessor) {\n this.paymentProcessor = paymentProcessor;\n }\n\n @Transactional\n public OrderResult placeOrder(OrderRequest request) {\n // ...\n PaymentResult payment = paymentProcessor.processPayment(paymentRequest); // through the proxy — REQUIRES_NEW honored\n // ...\n }\n}\n```\n\nNow `paymentProcessor` is the injected proxy, the call crosses a bean boundary, and `REQUIRES_NEW` actually starts a new transaction that commits independently of the outer one.\n\n## Alternatives, and their trade-offs\n\n- **Inject self and call `self.processPayment()`.** Works, but it is an awkward self-reference and easy for a future reader to \"clean up\" back into a direct call. Prefer the extracted bean.\n- **`ApplicationContext` / `AopContext.currentProxy()`.** Requires `exposeProxy = true` and is brittle. Not recommended.\n- **`TransactionTemplate`.** If you want explicit control, inject a `TransactionTemplate` configured with `PROPAGATION_REQUIRES_NEW` and run the capture inside `template.execute(...)`. This makes the independent-commit intent visible at the call site with no proxy subtleties.\n\n## Verification\n\nConfirm the fix by asserting the behavior, not just the annotation: within a single test, trigger the outer method, let `processPayment()` succeed, then force the outer transaction to roll back, and assert the payment record is still committed. If it disappeared, the `REQUIRES_NEW` boundary is not doing its job — which is exactly what the current same-class call produces.\n\nOne related caveat for the extracted version: because `REQUIRES_NEW` suspends the outer transaction and opens a separate connection, the payment branch cannot see uncommitted state from the outer transaction and can deadlock against it if both touch the same rows. That is inherent to independent commit, not a proxy defect — but it is worth knowing before you make the capture truly independent.\n\nWorth noting for the record: this is precisely the pitfall the implementation checklist warns against — never call another `@Transactional` method on `this` and rely on the annotation firing. Here it is not just a no-op annotation but a defeated durability guarantee.",
1195
+ "outputSha256": "0bc6f522b5c46f2e5b01e1beb686e47f56461d5125f02721a532fee49d634388",
1196
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1197
+ "deterministic": [],
1198
+ "judge": {
1199
+ "verdict": "pass",
1200
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so REQUIRES_NEW doesn't apply (\"this is the raw target object, not the proxy... No new transaction is started\"). Names concrete fix: extract into PaymentProcessor bean, constructor-inject into OrderService, call paymentProcessor.processPayment(). States consequence: capture participates in outer transaction and rolls back with it, no independent commit. No fail criterion met."
1201
+ },
1202
+ "passed": true
1203
+ },
1204
+ {
1205
+ "output": "No — this does not work as expected. This is the self-invocation proxy pitfall, and it's actually a *more dangerous* version of it than usual, because the annotation isn't a no-op-with-no-consequence situation; it silently defeats the specific guarantee `REQUIRES_NEW` was added to provide.\n\n## Why it fails\n\nSpring's declarative transaction support is proxy-based. The `@Transactional` behavior is applied by a proxy that wraps the *bean*. When an outside caller invokes the outer method, it goes through that proxy, so the proxy's transaction interceptor runs and opens the outer transaction. But when the outer method calls `this.processPayment()`, that is a plain Java method call on the target object *inside* the proxy — the call never re-enters the proxy. The transaction interceptor never sees it.\n\nThe consequences:\n\n1. **`REQUIRES_NEW` does not take effect.** No new transaction is started and the current one is not suspended. `processPayment()` runs inside the *outer* transaction, exactly as if it had no annotation at all.\n2. **The independent-commit guarantee is gone.** This is the part that should set off alarm bells. The whole point of `REQUIRES_NEW` here is that the payment capture commits on its own transaction and stays committed even if the outer transaction subsequently rolls back. Since the capture is actually enrolled in the outer transaction, an outer rollback now *also rolls back the payment capture*. You have the worst possible outcome for a payment path: the code reads as if money is safely captured regardless of downstream failure, but in reality a later failure in the outer unit of work silently un-captures it — while any external side effect already performed by the capture (a call to a payment gateway, an emitted event) may well have already happened and will not be undone.\n3. **Self-invocation is invisible to tooling and reviewers.** Nothing fails to compile, no startup error, no warning. Tests that only assert the happy path will pass, because on the happy path the behavior with and without `REQUIRES_NEW` is identical. The bug only surfaces on the rollback path — i.e. in production, at the moment it matters most.\n\n## What actually happens at the DB level\n\nIf the outer transaction is annotated `@Transactional` (default `REQUIRED`) and is driven by a single DataSource/`PlatformTransactionManager`, then:\n- Outer call → proxy → interceptor → begins transaction T1, binds connection C1 to the thread.\n- `this.processPayment()` → direct call → no proxy → no interceptor → runs on C1, in T1.\n- Outer method later throws → T1 rolls back → the payment capture's writes roll back with it.\n\nSo you get neither the independent commit nor isolation from the outer rollback.\n\n## Fixes\n\n**1. Extract `processPayment()` into its own bean (preferred).** Move the method to a separate `@Service`/`@Component`, constructor-inject it into the outer class, and call it via the injected reference. That goes through the proxy, so `REQUIRES_NEW` fires: the outer transaction is suspended, a new transaction is started on a (typically) separate connection, committed independently, and the outer transaction resumes.\n\n```java\n@Service\npublic class PaymentService {\n // package-private/public so the transaction interceptor applies (must be non-private, non-final)\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public PaymentResult processPayment(Order order) { ... }\n}\n\n@Service\npublic class OrderService {\n private final PaymentService paymentService;\n\n public OrderService(PaymentService paymentService) { // constructor injection\n this.paymentService = paymentService;\n }\n\n @Transactional\n public void placeOrder(Order order) {\n // ...\n this.paymentService.processPayment(order); // goes through the proxy — REQUIRES_NEW applies\n }\n}\n```\n\nResist the `AopContext.currentProxy()` / `((OrderService) AopContext.currentProxy()).processPayment(...)` workaround: it requires `exposeProxy = true` on `@EnableAspectJAutoProxy`, it couples your code to Spring's proxy internals, and it's a well-known code smell. The extracted-bean refactor is cleaner and testable without the container.\n\n**2. If the extraction is genuinely not feasible**, self-injection (constructor-injecting the bean into itself) or `TransactionTemplate` with `PROPAGATION_REQUIRES_NEW` will also work — but both are inferior to option 1. `TransactionTemplate` leaks programmatic transaction management into business code; self-injection is confusing to read.\n\n## Additional caveats for the extracted-bean approach\n\n- **Method visibility.** The interceptor must be able to see the method. Public is the safe default; Kotlin `internal`/final classes need the proxy to be able to subclass, so keep classes non-final (or use interface-based proxies / enable class-proxy-friendly settings). A Kotlin class is final by default — this is a common trap: mark the class or method `open` (the `kotlin-spring` / `allopen` plugin handles this for `@Service`-annotated classes, but verify the project actually enables it).\n- **Separate connections required.** `REQUIRES_NEW` only gives real independence if a second connection is available (adequate pool size, ideally a second transaction manager/DataSource if the payment store is separate). With a single connection and a saturated pool, the new transaction can deadlock or block waiting for a connection.\n- **It must be a real external call to the gateway that you're protecting.** If `processPayment` itself only writes to the same DB, reconsider whether the two writes should even be in separate transactions — sometimes the correct fix is one atomic transaction, not two.\n\n## Bottom line\n\nThe annotation is silently ignored. The payment capture is *not* independently committed; it resides inside the outer transaction and will be rolled back if the outer transaction rolls back. Fix it by moving `processPayment()` to a separate, constructor-injected bean and calling it through that reference — and add a test that asserts the capture persists after the outer transaction rolls back, since that's the only path that exercises the difference.",
1206
+ "outputSha256": "4d702ea386aa7a677820de6bdf087166cf84c482ec9a47a5bd714fdad4fa2bd7",
1207
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1208
+ "deterministic": [],
1209
+ "judge": {
1210
+ "verdict": "pass",
1211
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() never re-enters the proxy so REQUIRES_NEW doesn't apply (\"the call never re-enters the proxy... interceptor never sees it\"). Names concrete fix: extract processPayment() into separate @Service PaymentService, constructor-inject into OrderService, call via injected reference, with code shown. States consequence: no new transaction started/suspended, capture enrolled in outer transaction and rolled back with it. No fail criterion met."
1212
+ },
1213
+ "passed": true
1214
+ },
1215
+ {
1216
+ "output": "No — it does not work as expected, and this is the exact self-invocation pitfall the skill calls out as a hard rule. The outcome also depends on how the outer method itself is being called, so there are two distinct failure modes to separate.\n\n**Why the inner annotation never fires**\n\nSpring's declarative transaction support is proxy-based. When a bean is injected into another bean, what gets injected is a proxy wrapping the target object. The proxy intercepts calls that arrive *from outside* the bean and starts/joins/creates transactions around them, then delegates to the real target. But when you call `this.processPayment()`, that is a plain JVM method dispatch on the target instance itself — the call never passes through the proxy. There is no interception point, so `@Transactional(propagation = Propagation.REQUIRES_NEW)` is never read. It is silently a no-op annotation; it looks load-bearing in the source and does nothing at runtime.\n\n**What actually happens, case by case**\n\nThe key question is what transaction context is active when the outer method runs.\n\n- **Outer method was invoked through the proxy (typical: a controller calls it on an injected bean).** The outer `@Transactional` fires and a transaction T1 is active. The `this.processPayment()` call executes inline in T1. The `REQUIRES_NEW` is ignored. So the inner work is *not* isolated — it shares T1's connection and transaction. This is the worst case for your stated requirement: if the outer transaction later rolls back, the payment capture rolls back with it. The whole point of `REQUIRES_NEW` — commit the capture independently — is defeated. You get the opposite of the guarantee you were trying to buy.\n\n- **Outer method was invoked via a self-call too, or the bean is an unproxied instance.** Then no transaction is active at all for the outer body either. `processPayment()` still runs inline with no transaction, because it is neither proxied nor joined to anything. Payment capture then executes in whatever ambient transaction state exists (usually auto-commit), which may *accidentally* appear to commit independently — but not because `REQUIRES_NEW` did anything. It commits because nothing was transactional. That is fragile and undocumented behavior you should not rely on.\n\nNeither branch gives you what the annotation claims. There is no configuration flag that makes intra-class calls proxy-intercepted; that is a fundamental consequence of proxying, not a bug you can toggle off.\n\n**Why this is specifically dangerous rather than just \"not working\"**\n\nA `REQUIRES_NEW` that silently degrades to \"join the caller's transaction\" fails *silently and in the direction of data integrity*. The reason you reach for `REQUIRES_NEW` on a payment capture is usually exactly the scenario you describe: the capture is a real-world side effect that must persist even if the surrounding business workflow aborts. A no-op annotation inverts that. In testing, the outer transaction often commits, so everything looks fine; the divergence only shows up in the rollback path, which is the path least likely to be covered. This is the kind of bug that surfaces as \"we charged the customer but the order record doesn't exist\" or the inverse, in production, under partial failure.\n\nIt also interacts with connection consumption: a genuine `REQUIRES_NEW` suspends T1 and grabs a second connection from the pool. Here you get none of that, so any pool sizing you assumed based on the nesting is wrong too.\n\n**The fix**\n\nExtract `processPayment()` onto a separate Spring-managed bean, inject it into the current class via the constructor (never field injection), and call it through that injected reference. The call then crosses a proxy boundary and `REQUIRES_NEW` takes effect: T1 is suspended, a new transaction T2 starts, the capture commits on T2, and only then does control return to the outer method. A later rollback of T1 does not touch the already-committed T2. That is the behavior the annotation was always meant to provide.\n\nFollowing this skill's rules, the corrected shape is:\n\n- `processPayment` moves to its own component, constructor-injected into the caller as a `private final` field (Java) or constructor `val` (Kotlin).\n- The call site becomes `paymentProcessor.processPayment(...)`, not `this.processPayment(...)`.\n- The method keeps `@Transactional(propagation = Propagation.REQUIRES_NEW)` on the extracted bean; the outer method keeps its own `@Transactional`.\n\nTwo secondary points worth checking while you are in there:\n\n1. **Rollback rules.** `REQUIRES_NEW` commits when the inner method returns normally. If `processPayment` catches its own exceptions and returns a failure value, the inner transaction will *commit* despite the failure. Make sure genuine capture failures propagate as an unchecked exception (or are listed in `rollbackFor`) so T2 actually rolls back and you don't commit a half-done capture.\n2. **Whether `REQUIRES_NEW` is even the right tool.** If the requirement is only \"the outer rollback must not undo the capture,\" `REQUIRES_NEW` is correct. If there's also an outbound call to a payment provider inside it, be aware the HTTP call now sits inside T2 and holds a pooled connection for its duration — a long provider timeout can starve the pool. That may argue for an outbox/idempotency-key pattern rather than a long-held nested transaction, but that is a design decision, not a fix to the self-invocation bug.\n\nBottom line: as written, the `REQUIRES_NEW` is inert and the capture shares the outer transaction, so an outer rollback *will* discard it — the reverse of your intent. Move the method to a separate injected bean to restore the proxy boundary, and verify the rollback behavior on the failure path specifically, since that is where the current code diverges from what the annotation promises.",
1217
+ "outputSha256": "2fadc01997eb411776960fc4e41072ac94c86616f803b4c9be2e8afb6ba83c46",
1218
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1219
+ "deterministic": [],
1220
+ "judge": {
1221
+ "verdict": "pass",
1222
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so REQUIRES_NEW is never read (\"plain JVM method dispatch... never passes through the proxy\"). Names fix: extract processPayment to separate bean, constructor-inject, call paymentProcessor.processPayment(...). Gives concrete consequence: shares T1, outer rollback discards capture, no T1 suspend/T2 start. No fail criterion."
1223
+ },
1224
+ "passed": true
1225
+ },
1226
+ {
1227
+ "output": "No — it does not work as expected. This is the self-invocation pitfall, and it silently defeats the annotation you just added.\n\n## Why the annotation is a no-op here\n\nSpring's declarative transactions are implemented with a proxy (JDK dynamic proxy or CGLIB subclass) wrapped around your bean. When the outer method is called from *outside* the class, the caller is actually calling the proxy, which:\n\n1. opens the outer transaction (beans the `REQUIRES_NEW`/`REQUIRED` boundary),\n2. invokes your real method, and\n3. commits or rolls back.\n\nInside that method, `this.processPayment()` is a **direct JVM call on the target object**, not through the proxy. The proxy never sees it. So:\n\n- No `REQUIRES_NEW` boundary is opened.\n- No *new* transaction is started — payment capture runs **inside the outer transaction**.\n- No suspension of the outer transaction happens.\n\n`this.processPayment()` behaves exactly as if you had deleted the annotation. The most damaging consequence is exactly the one you were trying to prevent: if the outer transaction rolls back *after* the payment capture, the payment insert/update is rolled back with it. `REQUIRES_NEW` is precisely the annotation whose semantics the proxy model cannot honor via a same-class call.\n\n## The subtle tell\n\nThis bug is dangerous because it *looks* fine. The code compiles, the test that calls `processPayment()` directly (through the proxy, from the test class) passes and shows the expected transaction behavior, and only the real production path — where the capture is reached via the outer method — silently loses the independence guarantee. If you have integration coverage asserting \"payment committed even though outer rolled back,\" it will fail; if you don't, it won't.\n\n## The fix\n\nThe rule in this skill is not \"avoid `@Transactional` on same-class calls as a style preference\" — it is a correctness requirement. The reliable fix is to get the `REQUIRES_NEW` method onto a **different, Spring-managed bean** and call it through its injected reference:\n\n```java\n@Service\npublic class PaymentService {\n\n private final PaymentGateway gateway;\n private final PaymentRepository repository;\n\n // constructor injection (never field @Autowired)\n public PaymentService(PaymentGateway gateway, PaymentRepository repository) {\n this.gateway = gateway;\n this.repository = repository;\n }\n\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public Payment capture(PaymentRequest request) {\n // gateway.capture(request);\n // repository.save(...);\n return ...;\n }\n}\n```\n\n```java\n@Service\npublic class OrderService {\n\n private final PaymentService paymentService;\n\n public OrderService(PaymentService paymentService) {\n this.paymentService = paymentService;\n }\n\n @Transactional\n public void placeOrder(OrderRequest request) {\n // ... persist the order ...\n // The call goes through the proxy: REQUIRES_NEW actually fires,\n // the outer transaction is suspended, and the capture commits on its own.\n paymentService.capture(request.toPaymentRequest());\n // ... if the outer method throws here, the capture is NOT rolled back ...\n }\n}\n```\n\nNow `paymentService` is the *proxy*, so the call is intercepted and the new transaction is opened with the outer one suspended. A later rollback of `placeOrder` leaves the committed capture intact.\n\n## Alternatives, and why the extract-to-a-bean one wins\n\n- **`TransactionTemplate` / `PlatformTransactionManager`** injected into the outer service and used to wrap the capture call. This is functionally correct and avoids a new bean, but it pushes transaction plumbing into the calling method and is easy to get wrong on propagation/rollback flags. Acceptable, less clean.\n- **Self-injection** (`@Lazy OrderService self` or `ApplicationContext.getBean`). It works, but it deliberately calls through the proxy within the same class, which is confusing to reviewers and re-introduces exactly the kind of invisible proxy dependence that caused this bug. Avoid unless there is no other option.\n- **`AopContext.currentProxy()`** with `@EnableAspectJAutoProxy(exposeProxy = true)`. Same objection: obscure, requires a global config flag, and depends on `exposeProxy` remaining set.\n\nThe extract-to-a-separate-bean fix is the one to reach for by default — it is the only one that makes the correct behavior obvious from the call site and does not depend on a global proxy setting.\n\n## Handling the failure path\n\nOne thing to be careful about once the capture *does* run in its own transaction: it can now commit even if the outer transaction later fails. That is usually the point (you don't want a captured payment silently rolled back), but it means you must decide, explicitly, how to reconcile a committed capture against a rolled-back order — a status/audit record, a compensating action, or an outbox event. The transaction change fixes the annotation semantics; it does not by itself make the payment and the order atomic with each other, because `REQUIRES_NEW` is precisely an instruction *not* to be atomic with the outer unit of work. Document that intent at the call site so nobody \"fixes\" it back to a same-class call later.\n\n## Verify before reporting done\n\n- The `REQUIRES_NEW` method lives on a different `@Transactional`-eligible bean, constructor-injected into the caller.\n- No method in the class calls another `@Transactional` method on `this`.\n- A test asserts the actual invariant: force the outer transaction to roll back after the capture, and assert the capture is still committed (not present-then-rolled-back). This is the test whose absence let the bug through.\n- Build/test/check tasks exit 0.",
1228
+ "outputSha256": "f58b9bf1b47b871db2fc267fd47c0c17836664cc2f4c7326e899004645c7bceb",
1229
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1230
+ "deterministic": [],
1231
+ "judge": {
1232
+ "verdict": "pass",
1233
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() is a direct JVM call bypassing the proxy, so REQUIRES_NEW doesn't apply (\"No REQUIRES_NEW boundary is opened... No suspension\"). Names concrete fix: extract capture() into separate PaymentService bean, constructor-injected into OrderService, called via paymentService.capture(). States consequence: capture runs inside outer transaction and is rolled back with it. No fail criterion."
1234
+ },
1235
+ "passed": true
1236
+ },
1237
+ {
1238
+ "output": "No — that does not work as expected, and it is the exact self-invocation pitfall this skill's Rules section warns about.\n\nSpring's declarative transaction management is proxy-based. When the container creates your service bean, it wraps it in a proxy (JDK dynamic proxy or CGLIB subclass). The proxy is what reads `@Transactional` on a method and opens/joins/suspends a transaction before delegating to the real method. External callers go through the proxy, so their annotations fire.\n\nBut inside the outer method, `this.processPayment()` does not go through the proxy. `this` is the raw target object, not the proxy wrapper. The call is a plain JVM method invocation on the same instance, so the proxy's interceptor chain is never entered. That means `@Transactional(propagation = Propagation.REQUIRES_NEW)` is silently ignored — no new transaction is started, no suspension of the outer transaction happens. `processPayment()` just runs as an ordinary method call inside the already-open outer transaction.\n\nThe consequence is the opposite of what the design intends: because `REQUIRES_NEW` never took effect, the payment capture joins the outer transaction. If the outer transaction later rolls back, the payment capture rolls back with it. The one guarantee `REQUIRES_NEW` was chosen to provide — the payment commits independently and survives an outer rollback — is exactly the guarantee you do not get. Worse, this fails silently: no exception, no warning, and it is easy to \"verify\" the happy path and conclude it works, because on the happy path (outer transaction commits) both designs look identical. The bug only surfaces on the rollback path, which is precisely when you can least afford it.\n\nThe fix is to make the `REQUIRES_NEW` call go through a proxy. Extract `processPayment()` into a separate, constructor-injected Spring bean and call it from the outer method:\n\n```java\n@Service\npublic class PaymentProcessor {\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public void processPayment(Order order) {\n // capture the payment\n }\n}\n\n@Service\npublic class OrderService {\n private final PaymentProcessor paymentProcessor;\n\n public OrderService(PaymentProcessor paymentProcessor) {\n this.paymentProcessor = paymentProcessor;\n }\n\n @Transactional\n public void placeOrder(Order order) {\n // ... outer unit of work ...\n paymentProcessor.processPayment(order); // goes through the proxy now\n }\n}\n```\n\nNow the call crosses a bean boundary, so it enters `PaymentProcessor`'s proxy, `REQUIRES_NEW` is honored, the outer transaction is suspended, and the payment capture commits on its own connection independently of the outer transaction's fate.\n\nTwo other options exist but are inferior here:\n- Self-injection (`@Autowired private OrderService self;` then `self.processPayment(...)`) works because `self` is the proxy, but it is a code smell and couples the class to its own proxy.\n- `AopContext.currentProxy()` requires `@EnableAspectJAutoProxy(exposeProxy = true)` and is fragile.\n\nExtracting into a separate bean is the clean fix, and it also matches the layering discipline this skill expects — narrow responsibilities, one clear transaction owner per unit of work.\n\nA related note: the same proxy bypass silently defeats any other method-level advice too — `@Async`, `@Cacheable`, `@PreAuthorize`, `@Retryable`, and so on. If you find yourself reasoning \"the annotation is on the method, so it must be active,\" check whether the call site goes through the proxy. If it is a same-class call, it does not.\n\nSo, to answer directly: no, currently `processPayment()`'s `REQUIRES_NEW` is not in effect, and the payment will roll back with the outer transaction. Extract it into its own injected bean to make the independent commit actually happen — and add a test that rolls the outer transaction back and asserts the payment persisted, since that rollback path is the only place the bug shows.",
1239
+ "outputSha256": "01aeaf103bd49f9c390ffdb4737299bc52f5ec92ec9669d97522836dc7ad68ac",
1240
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1241
+ "deterministic": [],
1242
+ "judge": {
1243
+ "verdict": "pass",
1244
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so REQUIRES_NEW is silently ignored (no new tx, no suspension). Names concrete fix: extract processPayment() into separate PaymentProcessor bean, constructor-inject into OrderService, call paymentProcessor.processPayment(). States consequence: payment joins outer tx and rolls back with it, defeating independent commit. No fail criterion met."
1245
+ },
1246
+ "passed": true
1247
+ },
1248
+ {
1249
+ "output": "No, it does not work as expected — and this is the worst shape of the self-invocation pitfall, because the failure is silent and the consequence is a money bug.\n\n**What actually happens**\n\nSpring's declarative transactions are proxy-based. The proxy sits *outside* your bean and intercepts calls that arrive from other beans. When the outer method calls `this.processPayment()`, that call never leaves the object — it goes straight to the raw target instance on the call stack, bypassing the proxy entirely. So:\n\n- `@Transactional(propagation = REQUIRES_NEW)` is **never applied**. There is no new transaction, no suspension of the outer transaction, no separate commit.\n- `processPayment()` runs inside the **outer** transaction.\n- If the outer transaction rolls back, the payment capture rolls back with it. The independent-commit guarantee you wrote the annotation for is exactly zero.\n\nWorse: because the outer method *is* proxied, it runs correctly as a transaction, so instrumentation, logs, and \"it seemed to work in the happy path\" all look fine. The bug only manifests when the outer transaction rolls back — which is precisely the scenario the `REQUIRES_NEW` was added to handle.\n\n**Why this is especially dangerous here**\n\nNote that propagation is what makes this catastrophic rather than merely redundant. If the inner method had used default `PROPAGATION_REQUIRED`, the no-op would be *harmless* — the behavior would be identical to joining the outer transaction, which is what actually happens. It's the gap between \"what the annotation declares\" and \"what the caller gets\" that bites. Payment capture wants a commit that survives outer rollback; the code silently gives you a commit that dies with it.\n\n**How to fix it**\n\nExtract `processPayment()` into a separate Spring bean and constructor-inject it, so the call crosses a proxy boundary:\n\n```java\n@Service\npublic class PaymentProcessor {\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public void processPayment(Payment payment) { ... }\n}\n\n@Service\npublic class OrderService {\n private final PaymentProcessor paymentProcessor; // constructor-injected\n\n public OrderService(PaymentProcessor paymentProcessor) {\n this.paymentProcessor = paymentProcessor;\n }\n\n @Transactional\n public void placeOrder(Order order) {\n ...\n paymentProcessor.processPayment(payment); // crosses the proxy\n }\n}\n```\n\nOther options, and when they apply:\n\n- **Self-injection** (`@Autowired private OrderService self;` then `self.processPayment(...)`) — works, but it's a smell: it re-introduces the field injection the rules forbid and documents a design flaw instead of fixing it. Prefer extraction.\n- **`AopContext.currentProxy()`** — requires `@EnableAspectJAutoProxy(exposeProxy = true)` and a cast; brittle, and it still only papers over the same-class coupling.\n- **`TransactionTemplate`** — if you need `REQUIRES_NEW` for a small block, programmatic demarcation is explicit and immune to the proxy issue. Reasonable when extraction would create a bean for one method.\n\n**One more thing to check while you're in there**\n\n`REQUIRES_NEW` suspends the outer transaction and grabs a *new* database connection from the pool. If the outer transaction holds a lock on a row that the inner transaction needs, you get a self-deadlock — the outer can't commit to release, and the inner waits forever. Audit whether `processPayment()` touches any row the outer transaction already wrote or locked (e.g., the order row). If so, extraction alone won't save you; you also need to reorder so the payment doesn't contend with the outer's held locks.\n\n**Verification, since the happy path will pass either way**\n\nA unit test that calls `placeOrder()` directly against a `new OrderService(...)` will not exercise the proxy and will not prove anything. You need a `@SpringBootTest` (full context, real proxy) that forces the outer transaction to roll back and then asserts the payment row still exists. That's the only test that distinguishes \"REQUIRES_NEW is working\" from \"REQUIRES_NEW is a no-op.\"",
1250
+ "outputSha256": "e0b9103f3bc1546e3d3678e1b800f2b84c7cec34e822f8ba3740f1e5fc9d977f",
1251
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1252
+ "deterministic": [],
1253
+ "judge": {
1254
+ "verdict": "pass",
1255
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so REQUIRES_NEW is never applied (no new tx, no suspension, no separate commit). Names concrete fix: extract processPayment() into separate PaymentProcessor bean, constructor-inject into OrderService, call paymentProcessor.processPayment(). States consequence: payment capture rolls back with outer transaction. No fail criterion."
1256
+ },
1257
+ "passed": true
1258
+ },
1259
+ {
1260
+ "output": "No, it does not work as expected — and this is the exact case where the problem is most dangerous, because `REQUIRES_NEW` is the propagation setting where developers most often assume a same-class call is fine.\n\n**Why it fails:**\n\nSpring's declarative transaction management is proxy-based. When code outside the bean calls `placeOrder()`, it calls a proxy, which starts the transaction and *then* invokes the real method. But once you are inside the real method's body, `this` refers to the raw target object, not the proxy. So `this.processPayment()` is a plain Java/Kotlin method call — it never goes through the proxy, the `@Transactional` interceptor never runs, and `processPayment()` executes **inside the caller's existing transaction** with no new one created.\n\nThe practical consequences are exactly the opposite of your intent:\n\n- **No independent commit.** The payment capture is not committed separately when `processPayment()` returns; it is part of the outer transaction's unit of work and commits or rolls back with it.\n- **Your stated requirement is violated.** You wanted the capture to survive an outer rollback. Instead, if the outer transaction rolls back *after* `processPayment()` returns, the payment write rolls back too — silently, with no error. The money is not captured, and nothing tells you so.\n- **It won't necessarily show up in tests.** A single-class unit test with a hand-constructed instance has no proxy at all, so it passes trivially. A `@SpringBootTest` may also pass if nothing forces a rollback after the inner call. This bug tends to surface only in production, under a real rollback path.\n\n**How to fix it — pick the one that fits your design:**\n\n1. **Extract `processPayment()` into its own bean** (e.g. a `PaymentService` or `PaymentCapturer`) and constructor-inject it. The call then goes through that bean's proxy, `REQUIRES_NEW` actually fires, and the capture commits on its own connection, surviving an outer rollback. This is the standard fix and the one to prefer.\n2. **Self-injection**: inject the bean into itself (`private final OrderService self;` via constructor, or `ObjectProvider<OrderService>`) and call `self.processPayment()`. This works but is ugly, adds a circular dependency, and is easy to misuse later. Prefer option 1.\n3. **`TransactionTemplate`** with an explicit `PROPAGATION_REQUIRES_NEW` — programmatic, proxies bypassed deliberately. Good when the transactional unit is dynamic, but less declarative.\n\n**Also worth checking while you're in here:**\n\n- `REQUIRES_NEW` suspends the outer transaction and grabs a **second database connection**. If your pool is small and this is called in a loop or under load, you can deadlock or exhaust the pool. Make sure that resource trade-off is intended.\n- The two transactions are genuinely independent, so an outer rollback leaves the committed payment in place. That is what you asked for, but it means you need a compensation/reconciliation path for the case where the outer transaction fails after the capture commits — otherwise you have captured money with no corresponding order.\n- If any other method on this class makes a similar same-class `@Transactional` call (including plain `@Transactional` with no propagation override, which is just as silently a no-op), the same defect exists there too — a `@Transactional` annotation on a method only ever reached via `this` is decoration, not behavior.\n\nThe rule to apply going forward: a `@Transactional` method must be invoked **through a bean reference**, never through `this`. If the call is same-class, the annotation does nothing, and `REQUIRES_NEW` in particular will betray you at exactly the moment it matters — the rollback.",
1261
+ "outputSha256": "7649e56b1fc29930ba76362ffeb4d1ba54c1e84f7f845dab8b7cdc778c9c5639",
1262
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1263
+ "deterministic": [],
1264
+ "judge": {
1265
+ "verdict": "pass",
1266
+ "reason": "Answer states proxy-based @Transactional means this.processPayment() bypasses the proxy so its REQUIRES_NEW never applies (\"this refers to the raw target object, not the proxy... interceptor never runs\"). Names fix: extract processPayment() into its own bean (PaymentService) and constructor-inject it. States consequence: no independent commit, payment rolls back with outer transaction. No fail criterion met."
1267
+ },
1268
+ "passed": true
1269
+ },
1270
+ {
1271
+ "output": "No, it does not work as expected, and the `REQUIRES_NEW` makes the bug worse rather than better.\n\nThe problem is the self-invocation. `this.processPayment()` is a plain Java method call on the target object, not a call through the Spring proxy that actually wraps the bean. Spring's declarative transactions are implemented by that proxy: the proxy is what reads `@Transactional`, opens/joins a transaction, and commits or rolls back around your method. When the outer method calls `this.processPayment()`, it bypasses the proxy entirely, so:\n\n- The `REQUIRES_NEW` semantics are never applied. No new transaction is suspended/started.\n- `processPayment()` simply runs inline inside whatever transaction the outer method already established (or with no transaction at all if the outer one started via a proxy but `processPayment` was reached by self-call inside it).\n\nConcretely, this defeats the entire reason you asked for `REQUIRES_NEW`. You wanted the payment capture to commit independently so it survives an outer rollback. What actually happens: the payment work becomes part of the outer transaction. If the outer transaction rolls back, the payment is rolled back too — the exact opposite of the requirement. And if the outer transaction is only marked rollback-only (a common `REQUIRES_NEW`-vs-nested subtlety), you can still be in for an `UnexpectedRollbackException` at commit time.\n\nTwo things worth distinguishing, because people often conflate them:\n\n1. `REQUIRES_NEW` itself is fine as an annotation *when it is actually invoked through the proxy*. The propagation setting is not the bug; the self-call is.\n2. Even if this method were reached through the proxy, `REQUIRES_NEW` requires a second database connection from the pool, because the outer transaction holds its connection for its whole duration. That is a real capacity consideration (pool sizing, potential contention/deadlock), but it is secondary — first the call has to go through the proxy at all.\n\nFixes, in order of preference:\n\n- Extract `processPayment()` into a separate, constructor-injected collaborator bean (e.g. a `PaymentProcessor` / `PaymentService`), and call it from the outer method. The call now crosses a bean boundary, hits the proxy, and `REQUIRES_NEW` applies as intended. This is the standard, clearest fix.\n- If you cannot extract it, self-inject the proxy: inject the bean into itself (constructor-inject `PaymentProcessor` or use `ObjectProvider<Self>` / an `AopContext.currentProxy()` with `exposeProxy = true`). This works but is uglier and easier to get wrong; prefer the extraction.\n- Switching to `TransactionTemplate` with an explicit new transaction is another option but adds more mechanical code than a collaborator bean.\n\nWatch for the related trap: extraction alone is not enough if the outer method and the extracted method end up in the same class again, or if someone later \"simplifies\" it back to `this.`. Add a test that forces the outer transaction to roll back and asserts the payment row is still committed — that is the only reliable guard that the propagation is actually taking effect.\n\nSo: as written, the payment capture will not survive an outer rollback; it will be rolled back with it. Extract the method into a separate injected bean so the call goes through the proxy.",
1272
+ "outputSha256": "13bc54451a1dfd105c348e6abcfbb4fec25bf590dfce956748e5634293632345",
1273
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1274
+ "deterministic": [],
1275
+ "judge": {
1276
+ "verdict": "pass",
1277
+ "reason": "Answer states proxy-based @Transactional and that this.processPayment() bypasses the proxy so REQUIRES_NEW never applies (\"bypasses the proxy entirely... No new transaction is suspended/started\"). Names concrete fix: extract into separate constructor-injected collaborator bean (PaymentProcessor/PaymentService) called across bean boundary. States consequence: payment becomes part of outer transaction and rolls back with it, defeating REQUIRES_NEW. No fail criterion met."
1278
+ },
1279
+ "passed": true
1280
+ },
1281
+ {
1282
+ "output": "No — it does not work as written, and it's one of the more dangerous versions of the self-invocation pitfall precisely because the *intent* is correct.\n\n## Why it fails\n\nSpring's `@Transactional` is implemented with a proxy (JDK dynamic proxy or CGLIB subclass) that wraps the bean. The proxy is what starts and commits/rolls back the transaction, and it only intercepts calls that come *through the proxy reference*.\n\nWhen the outer method calls `this.processPayment()`, `this` is the raw target object, not the proxy. The call never passes through the interceptor chain, so:\n\n- The `REQUIRES_NEW` semantics are **never applied**.\n- `processPayment()` runs inside whatever transaction context the outer method already established (because it's a plain method call, it inherits the caller's thread-bound transaction).\n- The inner \"commit\" never happens independently; the payment changes are just more work enlisted in the outer transaction.\n\nThe result is the opposite of what was wanted: if the outer transaction later rolls back, the payment capture rolls back with it. You wanted the payment durable regardless of the outer outcome, and instead you've made it participate in the outer transaction.\n\nThis is silent — no exception, no warning. The annotation is there, the method is public, everything *looks* right.\n\n## The fixes\n\n**Option 1 — extract to a separate bean (preferred).**\n\nMove `processPayment()` into its own Spring-managed service and inject it:\n\n```java\n@Service\npublic class PaymentService {\n @Transactional(propagation = Propagation.REQUIRES_NEW)\n public PaymentResult processPayment(PaymentCommand cmd) {\n ...\n }\n}\n\n@Service\npublic class OrderService {\n private final PaymentService paymentService; // constructor-injected\n\n @Transactional\n public void placeOrder(OrderCommand cmd) {\n // ... outer work ...\n PaymentResult result = paymentService.processPayment(...); // goes through the proxy\n // ...\n }\n}\n```\n\nNow the call crosses a proxy boundary, the `REQUIRES_NEW` interceptor runs, the outer transaction is suspended, and the payment commits on its own connection/transaction.\n\n**Option 2 — self-injection.**\n\nInject the bean into itself (`private final OrderService self;` or `@Lazy OrderService self;` to avoid the circular-dependency issue) and call `self.processPayment(...)`. This works, but it's obscure and easy for a future reader to \"clean up\" back into `this.`, reintroducing the bug. Option 1 is more honest about the design: a payment capture that must commit independently is arguably a distinct unit of work with a distinct reason to exist, and separating it makes that explicit.\n\n**Option 3 — `AopContext.currentProxy()`.**\n\nRequires `@EnableAspectJAutoProxy(exposeProxy = true)` and then calls `((OrderService) AopContext.currentProxy()).processPayment(...)`. It works, but it couples the code to a container detail and is generally the least preferred of the three.\n\n## Two adjacent things to check while you're in here\n\n- **`REQUIRES_NEW` needs a second connection.** It suspends the outer transaction and acquires a new one from the pool. If the pool is small and the outer transaction holds a connection, concurrent load can exhaust the pool or deadlock. Worth confirming pool sizing if this is on a hot path.\n- **After the inner commit**, if the outer transaction subsequently rolls back, you have a durable payment for an order that never got placed. That may be exactly what you want (capture the money, reconcile later) — but it does mean the outer code must be able to tolerate the inner side effect surviving its own failure. If it can't, `REQUIRES_NEW` may be the wrong propagation for the goal, and a different pattern (outbox, compensating action, or a separate event-driven step) is the real fix.\n\nThe propagation annotation itself is right for the stated requirement; the bug is purely that the call never reaches it.",
1283
+ "outputSha256": "d3bd5dc066acbe46701b92932417879e5c58f804469a5f866cd02e4c5c8d8b83",
1284
+ "promptSha256": "901a0ff2bbbb104661e98ddc87baf7c31bac963daf1aff57cca881d440175214",
1285
+ "deterministic": [],
1286
+ "judge": {
1287
+ "verdict": "pass",
1288
+ "reason": "Answer states proxy nature: 'this is the raw target object, not the proxy... never passes through the interceptor chain, so REQUIRES_NEW semantics are never applied.' Concrete fix: Option 1 extracts processPayment() into PaymentService, constructor-injects it into OrderService, calls paymentService.processPayment(). Consequence: 'if the outer transaction later rolls back, the payment capture rolls back with it.' No fail criterion met."
1289
+ },
1290
+ "passed": true
1291
+ }
1292
+ ]
1293
+ }
1294
+ ],
1295
+ "verdict": "fail",
1296
+ "scope": "bundled",
1297
+ "skillDigest": "4547ca6a27b879aba4f7727ea8c3654071a8247dccd8e9a95826c30c31482323",
1298
+ "catalogDigest": "58f55f0866f2076d0959c6ddb3326a6ed904e5fb06de729cf465181d86b85e90",
1299
+ "judgePromptVersion": "2026-09-25.1",
1300
+ "runner": "deepseek",
1301
+ "model": "deepseek-chat",
1302
+ "runnerPromptVersion": "2026-09-25.1",
1303
+ "recordedAt": "2026-09-25T22:06:22.511Z",
1304
+ "judge": "deepseek",
1305
+ "judgeModel": "deepseek-chat"
1306
+ },
1307
+ {
1308
+ "schemaVersion": "1.0.0",
1309
+ "skillId": "java-kotlin-spring/java-kotlin-spring-migrate",
1310
+ "strictness": "high",
1311
+ "trials": 10,
1312
+ "triggerAccuracy": {
1313
+ "truePositive": 3,
1314
+ "falsePositive": 3,
1315
+ "positives": 7,
1316
+ "negatives": 6
1317
+ },
1318
+ "evidence": "authored",
1319
+ "scenarios": [
1320
+ {
1321
+ "id": "trigger-positive-1",
1322
+ "kind": "trigger-positive",
1323
+ "prompt": "We're adding an order_status column to the orders table -- what does a properly versioned Flyway script for that look like?",
1324
+ "strictness": "high",
1325
+ "trials": 1,
1326
+ "passes": 1,
1327
+ "passRate": 1,
1328
+ "passAtK": 1,
1329
+ "grader": "trigger-rank-fork-family",
1330
+ "status": "ran",
1331
+ "deterministic": true
1332
+ },
1333
+ {
1334
+ "id": "trigger-positive-2",
1335
+ "kind": "trigger-positive",
1336
+ "prompt": "Write a Liquibase changeset to add an index on the orders table",
1337
+ "strictness": "high",
1338
+ "trials": 1,
1339
+ "passes": 1,
1340
+ "passRate": 1,
1341
+ "passAtK": 1,
1342
+ "grader": "trigger-rank-fork-family",
1343
+ "status": "ran",
1344
+ "deterministic": true
1345
+ },
1346
+ {
1347
+ "id": "trigger-positive-3",
1348
+ "kind": "trigger-positive",
1349
+ "prompt": "Before this runs against the shared staging database, can you check whether my new V12 migration script is safe?",
1350
+ "strictness": "high",
1351
+ "trials": 1,
1352
+ "passes": 0,
1353
+ "passRate": 0,
1354
+ "passAtK": 0,
1355
+ "grader": "trigger-rank-fork-family",
1356
+ "status": "ran",
1357
+ "deterministic": true
1358
+ },
1359
+ {
1360
+ "id": "trigger-positive-4",
1361
+ "kind": "trigger-positive",
1362
+ "prompt": "I need to rename the customer_email column without breaking rows that are mid-deploy -- what's the safe multi-step Flyway approach for that?",
1363
+ "strictness": "high",
1364
+ "trials": 1,
1365
+ "passes": 0,
1366
+ "passRate": 0,
1367
+ "passAtK": 0,
1368
+ "grader": "trigger-rank-fork-family",
1369
+ "status": "ran",
1370
+ "deterministic": true
1371
+ },
1372
+ {
1373
+ "id": "trigger-positive-5",
1374
+ "kind": "trigger-positive",
1375
+ "prompt": "Flyway is refusing to start up because it says the checksum on V7 doesn't match what's recorded in the history table -- how do I resolve that safely?",
1376
+ "strictness": "high",
1377
+ "trials": 1,
1378
+ "passes": 0,
1379
+ "passRate": 0,
1380
+ "passAtK": 0,
1381
+ "grader": "trigger-rank-fork-family",
1382
+ "status": "ran",
1383
+ "deterministic": true
1384
+ },
1385
+ {
1386
+ "id": "trigger-positive-6",
1387
+ "kind": "trigger-positive",
1388
+ "prompt": "We're dropping a legacy is_archived flag from the schema -- what should the migration script for that look like?",
1389
+ "strictness": "high",
1390
+ "trials": 1,
1391
+ "passes": 0,
1392
+ "passRate": 0,
1393
+ "passAtK": 0,
1394
+ "grader": "trigger-rank-fork-family",
1395
+ "status": "ran",
1396
+ "deterministic": true
1397
+ },
1398
+ {
1399
+ "id": "trigger-positive-7",
1400
+ "kind": "trigger-positive",
1401
+ "prompt": "Write a Flyway migration that backfills a new not-null column",
1402
+ "strictness": "high",
1403
+ "trials": 1,
1404
+ "passes": 1,
1405
+ "passRate": 1,
1406
+ "passAtK": 1,
1407
+ "grader": "trigger-rank-fork-family",
1408
+ "status": "ran",
1409
+ "deterministic": true
1410
+ },
1411
+ {
1412
+ "id": "trigger-negative-1",
1413
+ "kind": "trigger-negative",
1414
+ "prompt": "Write a Django migration for this new model field",
1415
+ "strictness": "high",
1416
+ "trials": 1,
1417
+ "passes": 1,
1418
+ "passRate": 1,
1419
+ "passAtK": 1,
1420
+ "grader": "trigger-rank-fork-family",
1421
+ "status": "ran",
1422
+ "deterministic": true
1423
+ },
1424
+ {
1425
+ "id": "trigger-negative-2",
1426
+ "kind": "trigger-negative",
1427
+ "prompt": "Add a Rails ActiveRecord migration for this column",
1428
+ "strictness": "high",
1429
+ "trials": 1,
1430
+ "passes": 0,
1431
+ "passRate": 0,
1432
+ "passAtK": 0,
1433
+ "grader": "trigger-rank-fork-family",
1434
+ "status": "ran",
1435
+ "deterministic": true
1436
+ },
1437
+ {
1438
+ "id": "trigger-negative-3",
1439
+ "kind": "trigger-negative",
1440
+ "prompt": "Implement a new Spring service method that reads from this table",
1441
+ "strictness": "high",
1442
+ "trials": 1,
1443
+ "passes": 0,
1444
+ "passRate": 0,
1445
+ "passAtK": 0,
1446
+ "grader": "trigger-rank-fork-family",
1447
+ "status": "ran",
1448
+ "deterministic": true
1449
+ },
1450
+ {
1451
+ "id": "trigger-negative-4",
1452
+ "kind": "trigger-negative",
1453
+ "prompt": "Review this Spring controller for entity leakage",
1454
+ "strictness": "high",
1455
+ "trials": 1,
1456
+ "passes": 1,
1457
+ "passRate": 1,
1458
+ "passAtK": 1,
1459
+ "grader": "trigger-rank-fork-family",
1460
+ "status": "ran",
1461
+ "deterministic": true
1462
+ },
1463
+ {
1464
+ "id": "trigger-negative-5",
1465
+ "kind": "trigger-negative",
1466
+ "prompt": "Fix this failing Gradle build for the Spring project",
1467
+ "strictness": "high",
1468
+ "trials": 1,
1469
+ "passes": 0,
1470
+ "passRate": 0,
1471
+ "passAtK": 0,
1472
+ "grader": "trigger-rank-fork-family",
1473
+ "status": "ran",
1474
+ "deterministic": true
1475
+ },
1476
+ {
1477
+ "id": "trigger-negative-6",
1478
+ "kind": "trigger-negative",
1479
+ "prompt": "Write JUnit tests for this Spring repository",
1480
+ "strictness": "high",
1481
+ "trials": 1,
1482
+ "passes": 1,
1483
+ "passRate": 1,
1484
+ "passAtK": 1,
1485
+ "grader": "trigger-rank-fork-family",
1486
+ "status": "ran",
1487
+ "deterministic": true
1488
+ },
1489
+ {
1490
+ "id": "never-edit-applied-migration",
1491
+ "kind": "behavior",
1492
+ "prompt": "I found a typo in a column name inside V7__add_users_table.sql, which has already been applied in staging. Should I just fix the typo in that file?",
1493
+ "strictness": "high",
1494
+ "trials": 10,
1495
+ "passes": 10,
1496
+ "passRate": 1,
1497
+ "passAtK": 1,
1498
+ "grader": "judge",
1499
+ "status": "ran",
1500
+ "trialRecords": [
1501
+ {
1502
+ "output": "No — do not edit V7__add_users_table.sql if it has already been applied, including in staging.\n\nFlyway (and Liquibase) record a checksum of each applied migration. If you edit an already-applied file, that checksum changes, and `flywayValidate` will fail on every environment that already applied the old version — including staging — with a checksum mismatch. You would then be stuck reconciling checksums manually across environments, which is exactly the situation versioned migrations exist to prevent.\n\nInstead, write a new migration that corrects the typo:\n\n```\nV<next-number>__rename_<bad_column>_to_<correct_column>.sql\n```\n\nwith the appropriate statement, e.g.:\n\n```sql\nALTER TABLE users RENAME COLUMN <misspelled> TO <correct>;\n```\n\nPractical notes:\n\n- Take the next unused version number that sorts strictly after the highest already-applied migration — do not reuse or renumber an existing one.\n- If `<bad_column>` is referenced by already-deployed code, prefer an expand-and-contract sequence (add the correct column, backfill, migrate reads/writes, drop the old one in a later migration) rather than a single rename, so a rolling deploy doesn't break code still pointing at the old name.\n- Verify against a real migration command before reporting done, e.g. `./gradlew flywayMigrate flywayValidate` (or the project's configured equivalent), run against a local/test database.\n\nOne caveat worth confirming first: if V7 has genuinely never been applied anywhere except a local scratch database you can drop and recreate, the cleanest fix is to correct V7 and rebuild that scratch database from scratch. But since you state it has been applied in staging, that option is closed — add a new migration instead.",
1503
+ "outputSha256": "11a6abf3a9e2900f199865d29f89450c692d1ee35d59b5d236747d2137b9824b",
1504
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1505
+ "deterministic": [],
1506
+ "judge": {
1507
+ "verdict": "pass",
1508
+ "reason": "Answer states 'do not edit V7__add_users_table.sql if it has already been applied, including in staging' (crit 1). Explains checksum change causes flywayValidate to fail with checksum mismatch in staging (crit 2). Directs writing a new migration V<next>__rename_...sql with ALTER TABLE users RENAME COLUMN (crit 3). No fail criterion."
1509
+ },
1510
+ "passed": true
1511
+ },
1512
+ {
1513
+ "output": "No. Editing `V7__add_users_table.sql` after it has been applied changes its checksum, and Flyway/Liquibase record that checksum at apply time. The next `flywayValidate` (or Liquibase equivalent) against staging will fail because the recorded checksum no longer matches the file — and any other environment that already applied V7 gets the same failure. This is one of the explicit red flags for this workflow: never edit an already-applied migration, even for a typo.\n\nWhat to do instead:\n\n1. Confirm the typo is genuinely in the applied file and note the exact bad column name — do not touch V7 itself.\n2. Add a new migration at the next unused version that corrects it, e.g. `V<next>__fix_users_column_typo.sql` (or a new Liquibase changeset with a fresh id/author in the project's changelog), containing the rename/correction as an explicit statement.\n3. Follow the project's own convention for this kind of change. A column rename is a destructive/renaming change, so if the project needs backward compatibility during a rolling deploy, use expand-and-contract (add the correctly-named column, backfill, switch reads/writes, drop the old column in a later migration) rather than a single direct `ALTER TABLE ... RENAME`. Check how prior migrations in this repo handled renames/drops and match that.\n4. Verify with a real migration command against a local/test database, never directly against staging or production — run the project's configured task (`./gradlew flywayMigrate flywayValidate`, `mvn flyway:migrate flyway:validate`, or the Liquibase `update` equivalent, whichever this project actually uses).\n5. Report the new file and the passing validate/migrate result, noting that V7 was left untouched and why.\n\nOne caveat worth checking before you write anything: if the typo means the actual schema in staging is wrong (the column really is misspelled there), the new migration must fix the real schema, not \"re-fix\" something already correct. Verify the current column name in staging first, then write the migration to match reality.",
1514
+ "outputSha256": "e9a550e945f27ea70504e0e8e16cfec922f958e3db8aa207a80a2615dbfb467b",
1515
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1516
+ "deterministic": [],
1517
+ "judge": {
1518
+ "verdict": "pass",
1519
+ "reason": "Answer says 'No' to editing V7 after it 'has been applied' (crit 1), explains checksum change causes flywayValidate/Liquibase validation failure in staging and other environments (crit 2), and directs adding a new migration 'V<next>__fix_users_column_typo.sql' with rename/correction statement (crit 3). No fail criterion met."
1520
+ },
1521
+ "passed": true
1522
+ },
1523
+ {
1524
+ "output": "No — don't edit V7__add_users_table.sql. Once a migration has been applied, changing its contents changes its checksum, and Flyway/Liquibase will fail validation on every environment that already applied the old version (including your staging database). The fix is to add a new migration instead.\n\nConcretely, in this project you'd:\n\n1. Find the current highest version number and pick the next one (say `V12`), matching the project's existing naming/numbering scheme exactly.\n2. Create `V12__fix_users_column_typo.sql` (or the project's Liquibase changeset equivalent) that performs the rename — for example `ALTER TABLE users RENAME COLUMN <typo_name> TO <correct_name>;`.\n3. If staging/production runs a rolling deploy and code may still reference the old column name during rollout, use an expand-and-contract sequence instead of a direct rename: add the correctly-named column, backfill it, migrate reads/writes, then drop the old column in a later migration. Check how prior migrations in this repo handled renames/drops to match the established convention.\n4. Verify against a real migration command on a local/test database before reporting done — e.g. `./gradlew flywayMigrate flywayValidate` (or the project's Liquibase `update` equivalent), not just reading the SQL. Never point this at staging or production directly.\n\nThe rule to hold onto: never edit an already-applied migration; always add a new one, even to fix a mistake in an earlier one.",
1525
+ "outputSha256": "e24d8f8b3692fefe7baa819340e54c230139b96bfa2eaa6861dc54cbfbef7be3",
1526
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1527
+ "deterministic": [],
1528
+ "judge": {
1529
+ "verdict": "pass",
1530
+ "reason": "Answer states 'don't edit V7__add_users_table.sql' since applied, explains checksum change fails Flyway/Liquibase validation 'on every environment that already applied the old version (including your staging database)', and directs adding a new migration V12__fix_users_column_typo.sql with ALTER TABLE ... RENAME COLUMN. No fail criterion met."
1531
+ },
1532
+ "passed": true
1533
+ },
1534
+ {
1535
+ "output": "No — do not edit `V7__add_users_table.sql` if it has already been applied in staging (or anywhere).\n\nFlyway (and Liquibase) store a checksum of each applied migration. Editing an already-applied file changes that checksum, so on every environment that already ran V7 — including staging — `flywayValidate` fails from then on. The fix in the file would only \"work\" for a database that had never applied V7, which defeats the purpose.\n\nWhat to do instead, depending on what the typo actually is:\n\n- **The typo is cosmetic/comment-only** (a typo in a comment or a description, not in the actual DDL): leave V7 alone. If you want the file to read cleanly for future readers, that's a judgment call, but it will still change the checksum and fail validation — so the safe answer is to leave it. The checksum covers the file content, comments included. Don't touch it.\n\n- **The typo is in an actual column name that's now wrong in the schema** (e.g. `creaated_at` instead of `created_at`): add a **new** migration that corrects it, e.g.:\n\n ```sql\n -- V<N>__rename_creaated_at_to_created_at.sql\n ALTER TABLE users RENAME COLUMN creaated_at TO created_at;\n ```\n\n For a rename like this, check the project's deployment requirements. If it needs zero-downtime / rolling-deploy compatibility, use expand-and-contract across migrations rather than one direct rename: add the new column, backfill, migrate reads/writes in code, then drop the old column in a later migration. If staging is the only place this has landed and the app isn't yet reading the misspelled name in a way that matters, a single rename migration may be acceptable — check how prior migrations in this project handled renames and follow that.\n\n- **The typo is a wrong data type or constraint** in the same applied migration: same rule — new migration with `ALTER TABLE ... ALTER COLUMN` / `ADD CONSTRAINT`, never an edit to V7.\n\nSteps:\n1. Find the next unused version number after every existing migration (don't reuse a number that hasn't applied *here* but has applied elsewhere — numbering must be globally consistent).\n2. Write the correction as a new migration in the project's existing naming/format convention.\n3. Run the project's real migrate/validate command against a local/test database — e.g. `./gradlew flywayMigrate flywayValidate` or `mvn flyway:migrate flyway:validate` (or the Liquibase equivalent) — and confirm it passes.\n4. Leave V7 exactly as is.\n\nOne caveat worth noting: since V7 has already hit staging, whatever application code currently reads the misspelled column needs to be reconciled with the fix, and if staging data was already written with the wrong name, the correction migration should account for that.",
1536
+ "outputSha256": "eb8f94ad0af6b2c85cb067117ad403b0c8d3e558e18dabf27545e8de93d33368",
1537
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1538
+ "deterministic": [],
1539
+ "judge": {
1540
+ "verdict": "pass",
1541
+ "reason": "Answer says 'do not edit V7__add_users_table.sql if it has already been applied in staging' (crit 1); explains checksum change causes flywayValidate to fail in staging/anywhere it ran (crit 2); directs adding a new migration with concrete SQL 'ALTER TABLE users RENAME COLUMN creaated_at TO created_at' in a new versioned file (crit 3). No fail criterion."
1542
+ },
1543
+ "passed": true
1544
+ },
1545
+ {
1546
+ "output": "No — do not edit `V7__add_users_table.sql`. Once a versioned migration has been applied (staging counts), its checksum is recorded. Editing the file changes that checksum, and Flyway/Liquibase will fail validation on every environment that already applied the old version — including staging itself, before you even get to production.\n\nThe correct move is a new migration that sorts after every already-applied one, matching your project's numbering scheme (check the highest existing version first — if V7 is the latest, this is `V8__...`; if later migrations already exist, take the next unused number).\n\nWhat that new migration should contain depends on what \"typo in a column name\" actually means:\n\n- **If the column was created with a misspelled name and nothing depends on it yet** — add a migration that renames it (`ALTER TABLE users RENAME COLUMN old_name TO new_name;`, or the equivalent Liquibase changeset). This is a rename, so it still counts as a destructive/renaming change: if the project does rolling/zero-downtime deploys, use expand-and-contract (add correctly-named column, backfill, migrate reads/writes, drop the old one in a later migration) rather than a direct rename.\n\n- **If the typo is only cosmetic** — e.g. a wrong name in a comment, or an inconsistency between the migration file and the JPA entity that the app already works around — it may not be worth a rename at all. Confirm what the actual deployed schema and the ORM mapping expect before introducing a rename migration.\n\nEither way: leave V7 untouched, add the next-numbered migration, and verify against a real local/test database with `./gradlew flywayMigrate flywayValidate` (or the project's Liquibase equivalent) before considering it done.",
1547
+ "outputSha256": "bdbc974dd3fe0f90f57d411ab16fb8647934eb7a76c683f7752b674da63a2fb5",
1548
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1549
+ "deterministic": [],
1550
+ "judge": {
1551
+ "verdict": "pass",
1552
+ "reason": "Answer states 'do not edit V7__add_users_table.sql' because it was applied in staging, explains checksum change causes Flyway/Liquibase validation failure in staging and other environments, and directs adding a new next-numbered migration (e.g. ALTER TABLE users RENAME COLUMN old_name TO new_name). No fail criterion met."
1553
+ },
1554
+ "passed": true
1555
+ },
1556
+ {
1557
+ "output": "No — do not edit `V7__add_users_table.sql` directly. That file has already been applied in staging (and possibly elsewhere), and Flyway stores a checksum for each applied migration. Changing the file content changes the checksum, so the next `flywayValidate`/`flywayMigrate` against staging (or any other environment that already applied the original V7) will fail with a checksum mismatch. You'd break validation everywhere the old version was already applied, and you'd be fixing the typo only for environments that haven't run V7 yet — leaving staging permanently divergent.\n\nInstead:\n\n1. Leave V7 exactly as it is. Treat it as immutable history.\n2. Add a new versioned migration at the next unused number, e.g. `V12__fix_users_column_typo.sql`, that performs the correction. Since a column rename is destructive to any code still referencing the old name, prefer an expand-and-contract sequence rather than a single `ALTER TABLE ... RENAME COLUMN` if the project needs zero-downtime / rolling-deploy compatibility: add the correctly-named column, backfill it from the old one, migrate reads/writes in code, then drop the old column in a later migration. If the project's deployment process already accepts direct renames (check how prior migrations in this repo handled renames/drops), a single rename migration is acceptable — but state that reasoning explicitly.\n3. Verify against a real migration command before calling it done:\n ```bash\n ./gradlew flywayMigrate flywayValidate # or mvn flyway:migrate flyway:validate\n ```\n Run against a local/test database, never staging or production directly.\n4. Report it as, e.g.:\n ```\n Added: V12__fix_users_column_typo.sql\n - corrects mistyped column name introduced in V7\n - flywayMigrate/flywayValidate both pass against local db\n ```\n\nNote: if this typo was caught before V7 was applied anywhere other than a disposable dev database, the cheapest fix is to `flyway repair` / clean and re-run there — but since you said it's already applied in staging, the new-migration path above is the correct one.",
1558
+ "outputSha256": "b93621c5db381bf6d8b53c4680469e53d9f3c480a33a67089f4093f98a317329",
1559
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1560
+ "deterministic": [],
1561
+ "judge": {
1562
+ "verdict": "pass",
1563
+ "reason": "Answer states 'do not edit V7__add_users_table.sql directly' because it 'has already been applied in staging' (crit.1). Explains checksum change causes 'checksum mismatch' failing flywayValidate/flywayMigrate in staging (crit.2). Directs adding new versioned migration e.g. V12__fix_users_column_typo.sql with rename/correction (crit.3). No fail criterion met."
1564
+ },
1565
+ "passed": true
1566
+ },
1567
+ {
1568
+ "output": "No — do not edit `V7__add_users_table.sql` directly. It has already been applied in staging, which means Flyway (or Liquibase) has recorded its checksum. Editing the file changes that checksum, and the next `flywayValidate`/`migrate` against staging (and any other environment that already applied V7) will fail validation with a checksum mismatch. Fixing the typo in place is exactly the mistake the rules warn against — a small typo edit still breaks every environment that already ran the old version.\n\nWhat to do instead:\n\n1. Leave V7 exactly as it is.\n2. Add a new migration at the next unused version, e.g. `V8__rename_<old>_to_<new>.sql` (matching the project's existing numbering scheme), containing the rename or whatever correction is needed.\n3. If a rename is involved and the deployment process requires zero-downtime / backward compatibility, use expand-and-contract rather than a single `ALTER TABLE ... RENAME COLUMN`:\n - add the correctly named column,\n - backfill from the old column,\n - migrate application reads/writes to the new name,\n - drop the old column in a later migration once nothing references it.\n Check how prior migrations in this project handled similar changes and match that pattern.\n4. Verify with a real migration command against a local/test database — not from reading the SQL alone:\n ```\n ./gradlew flywayMigrate flywayValidate # or mvn flyway:migrate flyway:validate\n # or, for Liquibase: ./gradlew update # or mvn liquibase:update\n ```\n Use the task/goal names the project actually configures.\n\nOne caveat: if V7 has *not* been applied anywhere but your local dev database, you can clean/repair that local database and edit the file — but since you've told me it's already applied in staging, treat it as immutable and add a new migration instead. Never renumber or reuse V7 for the fix; take the next genuinely unused version so numbering stays consistent across every environment.\n\nReport it as, for example:\n\n```\nAdded: V8__rename_user_email_to_email_address.sql\n - ALTER TABLE users RENAME COLUMN user_email TO email_address (or expand-and-contract sequence)\n - flywayMigrate/flywayValidate both pass against local db\n - V7 left untouched (already applied in staging)\n```",
1569
+ "outputSha256": "699ffcdf9b8b41384f155e8176df713c3a4f1406c6e2c4bea74ca9fb65a18d5d",
1570
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1571
+ "deterministic": [],
1572
+ "judge": {
1573
+ "verdict": "pass",
1574
+ "reason": "Answer states V7 must not be edited because it was already applied in staging ('do not edit... It has already been applied in staging'), explains the checksum consequence ('Editing the file changes that checksum... will fail validation with a checksum mismatch'), and directs adding a new migration ('Add a new migration at the next unused version, e.g. V8__rename_<old>_to_<new>.sql'). No fail criterion holds."
1575
+ },
1576
+ "passed": true
1577
+ },
1578
+ {
1579
+ "output": "No — don't edit `V7__add_users_table.sql` directly. Once a migration has been applied, Flyway (or Liquibase) records its checksum. Editing the file changes that checksum, so validation will fail for every environment that already applied the old version — including staging, which already has the original `V7` on record. That's the exact failure mode the \"never edit an already-applied migration\" rule exists to prevent.\n\nInstead:\n\n1. **Leave `V7__add_users_table.sql` untouched.** It's historical record now, even with the typo.\n2. **Add a new migration** with the next unused version number (e.g. `V8__fix_users_column_name_typo.sql`) that performs the correction — e.g. a column rename via `ALTER TABLE ... RENAME COLUMN`, or a drop-and-recreate if that's what's appropriate.\n3. **Follow the project's own convention for destructive/renaming changes.** Check how prior migrations in this project handled renames: if the deployment process needs zero-downtime compatibility, use expand-and-contract (add the correctly-named column, backfill, migrate reads/writes, drop the old one later) rather than a single direct rename that breaks code still deployed against the old name.\n4. **Verify for real** before reporting done — run the project's configured migration command (`./gradlew flywayMigrate flywayValidate`, or the Liquibase equivalent, whichever this project actually uses) against a local/test database, never against production.\n\nOne thing worth checking first: confirm the typo is actually a schema problem and not just a mismatch between the migration file and the JPA/Hibernate entity — if the entity annotation has the wrong name and the database column is correct (or the reverse), fix the entity, not the schema. But regardless of which side is wrong, the fix goes in a new migration, not a retroactive edit to `V7`.",
1580
+ "outputSha256": "f15f547ab40aef15291a3b3833d9469fb1d0614f2cd8915154f14ec5beadf2bd",
1581
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1582
+ "deterministic": [],
1583
+ "judge": {
1584
+ "verdict": "pass",
1585
+ "reason": "Answer says don't edit V7 directly since it's applied (staging has original V7 on record), explains checksum change causes validation failure in every environment that applied it including staging, and directs adding a new migration V8__fix_users_column_name_typo.sql with a rename statement. No fail criterion met."
1586
+ },
1587
+ "passed": true
1588
+ },
1589
+ {
1590
+ "output": "No — do not edit V7__add_users_table.sql.\n\nFlyway (and Liquibase) store a checksum of each migration when it is applied. V7 has already been applied in staging, so its checksum is recorded there. If you edit the file, the checksum on disk no longer matches the recorded checksum, and the next `flywayMigrate`/`flywayValidate` (or Liquibase `update`) will fail validation for staging and any other environment that already applied V7 — even though the typo has nothing to do with those failures.\n\nThe correct approach:\n\n1. Leave V7 untouched.\n2. Add a new migration that corrects the column name, numbered/ordered strictly after the current highest applied migration (e.g. `V12__rename_...`), authored per the project's existing convention.\n3. If the fix is a rename and the project needs zero-downtime/backward compatibility during rollout, use expand-and-contract across migrations (add the correctly-named column, backfill, switch reads/writes, drop the old one in a later migration) rather than a single direct `ALTER TABLE ... RENAME COLUMN`. A direct rename breaks any code still deployed against the old column name during a rolling deploy. If the project's prior migrations show it accepts direct destructive changes, a single rename migration is acceptable.\n4. Verify with a real migration command against a local/test database, never directly against staging or production:\n `./gradlew flywayMigrate flywayValidate` (or the project's configured Liquibase equivalent, e.g. `./gradlew update`).\n\nOne nuance: a typo in a *comment* or whitespace inside V7 still counts as a file change and will still break the checksum, so the \"it's only a typo\" reasoning does not save you here — the rule is about the file's checksum, not the semantics of the edit.\n\nIf you truly need the historical file to read correctly in the repo (e.g. for documentation), that is a separate concern from the applied migration: the applied file must stay byte-identical to what ran, and the correction belongs in a new migration.\n\nReport format once done:\n\n```\nAdded: V12__rename_user_column.sql\n - corrects column name introduced in V7\n - flywayMigrate/flywayValidate both pass against local db\n```",
1591
+ "outputSha256": "a4eb015d9c1ce5ff3046680be1f1a13b44387f03b3d420c200738b846c8f2b18",
1592
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1593
+ "deterministic": [],
1594
+ "judge": {
1595
+ "verdict": "pass",
1596
+ "reason": "Answer says 'do not edit V7__add_users_table.sql' because it 'has already been applied in staging' (crit 1). Explains checksum mismatch: 'checksum on disk no longer matches the recorded checksum' causing flywayValidate/Liquibase update to 'fail validation for staging and any other environment that already applied V7' (crit 2). Directs adding a new migration 'V12__rename_...' correcting the column name (crit 3). No fail criterion met."
1597
+ },
1598
+ "passed": true
1599
+ },
1600
+ {
1601
+ "output": "No — don't edit `V7__add_users_table.sql` directly, even for a typo, if it's already been applied in staging.\n\nFlyway (and Liquibase) store a checksum of each applied migration. Once V7 has run against staging, its checksum is recorded there. Editing the file changes its checksum, and the next `flywayValidate`/`flywayMigrate` against staging (and any other environment that already applied it) will fail validation with a checksum mismatch — it won't silently re-apply or ignore the change. That turns a one-character typo fix into a broken deploy across every environment that already ran V7.\n\nWhat to do instead:\n\n1. Add a new migration — e.g. `V<n>__rename_<old>_to_<new>.sql` (using the next unused version number that sorts after every already-applied migration, matching the project's numbering scheme). Do the rename there, via `ALTER TABLE ... RENAME COLUMN`.\n2. If the deployment process needs zero-downtime/backward compatibility (rolling deploys where code still references the old name), use expand-and-contract: add the correctly-named column, backfill it, migrate reads/writes, then drop the old column in a later migration — rather than a direct rename. Check how this project handled prior renames/drops and follow that.\n3. Verify against a real command before calling it done — `./gradlew flywayMigrate flywayValidate` (or the project's actual task/goal names — check, don't assume) — run against a local/test database, not staging or production.\n\nIf what's actually wrong is only the *typo in a comment or description* rather than the applied schema itself, that's still a change to an applied file, so the same rule applies: fix it forward with a new migration (or, if it's purely a non-executable comment and the project explicitly treats those as checksum-neutral, confirm that against the tool's actual checksum behavior first — don't assume).\n\nThe general rule: never edit an already-applied migration; always move the schema forward with a new one.",
1602
+ "outputSha256": "7e528546f51bc389d9a4c95c56670698c8c189d92bbc96dba330903669a04019",
1603
+ "promptSha256": "eae7b8df8b19dcd61bc40f3c581e269028e373653d6b416a793f02be91bcb742",
1604
+ "deterministic": [],
1605
+ "judge": {
1606
+ "verdict": "pass",
1607
+ "reason": "Answer states 'don't edit V7__add_users_table.sql directly... if it's already been applied in staging' (crit 1). Explains checksum: 'Editing the file changes its checksum, and the next flywayValidate/flywayMigrate against staging... will fail validation with a checksum mismatch' (crit 2). Directs new migration: 'Add a new migration — e.g. V<n>__rename_<old>_to_<new>.sql... via ALTER TABLE ... RENAME COLUMN' (crit 3). No fail criterion."
1608
+ },
1609
+ "passed": true
1610
+ }
1611
+ ]
1612
+ },
1613
+ {
1614
+ "id": "verify-before-reporting-done",
1615
+ "kind": "behavior",
1616
+ "prompt": "I wrote a new Flyway migration that adds a NOT NULL column with a default value to the orders table. It looks correct to me -- is writing the SQL enough, or do I need to do anything else before considering this done?",
1617
+ "strictness": "high",
1618
+ "trials": 10,
1619
+ "passes": 10,
1620
+ "passRate": 1,
1621
+ "passAtK": 1,
1622
+ "grader": "judge",
1623
+ "status": "ran",
1624
+ "trialRecords": [
1625
+ {
1626
+ "output": "Writing the SQL is not enough on its own — per the workflow, a migration isn't done until it's been verified against a real migration command, and a few authoring checks need to hold too. For your specific case (a `NOT NULL` column with a default on `orders`), here's what still needs to happen:\n\n1. **Naming/ordering check.** Confirm the new file sorts strictly after every already-applied migration and matches the project's existing numbering scheme (e.g. if the last file was `V11__...`, this one must be `V12__...`, not a reused or out-of-order number). Reusing a version that \"hasn't applied in this environment yet\" is a red flag — numbering has to be globally consistent across every environment the project ships to.\n\n2. **Never edit an already-applied file.** If this change was made by editing an existing migration rather than adding a new one, that's a problem — a changed checksum on an applied migration fails validation everywhere it already ran. It must be a new file.\n\n3. **Run the actual migration command**, not just read the SQL — the SQL looking right doesn't guarantee it succeeds against the real database. Against a local/test database (never production directly):\n ```bash\n ./gradlew flywayMigrate flywayValidate\n # or: mvn flyway:migrate flyway:validate\n ```\n Use whatever task/goal names this project actually configures.\n\n4. **Think about the `NOT NULL` + default specifically.** This is usually fine on most databases, but it's exactly the kind of change that can behave differently than it looks — adding a `NOT NULL` column with a default typically rewrites/backfills existing rows, which can lock or slow a large table, and some databases require the default to satisfy the constraint at write time. That's a reason to actually run `flywayMigrate` and, if `orders` is large or the deploy needs zero-downtime, consider whether the project's convention calls for add-nullable → backfill → set-not-null across separate migrations instead of one statement. Check how prior migrations in this project handled similar column additions.\n\n5. **Only then report**, e.g.:\n ```\n Added: V12__add_order_status_column.sql\n - ALTER TABLE orders ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PENDING'\n - flywayMigrate/flywayValidate both pass against local db\n ```\n\nSo: the SQL is the starting point, not the finish line. Confirm naming/ordering, make sure it's a new file (not an edit), and run the migration + validate against a local database before calling it done.",
1627
+ "outputSha256": "d83be603cafb7c9646a207efe97cc9a47f2f3635ee62855599d4cb0d14f6f0fa",
1628
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1629
+ "deterministic": [],
1630
+ "judge": {
1631
+ "verdict": "pass",
1632
+ "reason": "Answer states SQL alone isn't enough (\"Writing the SQL is not enough on its own\"), names concrete commands (./gradlew flywayMigrate flywayValidate, mvn flyway:migrate flyway:validate) against local/test db, and gives a specific reason: NOT NULL + default rewrites/backfills existing rows, can lock/slow large table, default must satisfy constraint at write time. No fail criterion holds."
1633
+ },
1634
+ "passed": true
1635
+ },
1636
+ {
1637
+ "output": "Writing the SQL is not enough. Before considering the migration done:\n\n1. **Confirm the file's place in the sequence.** The new migration must sort strictly after every already-applied migration and match the project's existing naming scheme (`V<next-number>__<description>.sql`, or the project's timestamp scheme if that's what prior files use — check the last few, don't assume). Use the next unused version number, not one reused because it \"hasn't applied yet\" in the environment you looked at.\n\n2. **Never touch an already-applied file.** If you catch a mistake in this new migration after applying it anywhere, add a new migration to correct it rather than editing this one — editing an applied migration changes its checksum and fails validation everywhere it already ran.\n\n3. **Actually run the migrate/validate command against a local or test database.** This is the step people skip. A `NOT NULL` column with a default can still fail in practice (existing rows, a default incompatible with an existing constraint, a lock timeout on a large table) — reading the SQL is not verification. Run the project's configured tasks, e.g.:\n\n ```\n ./gradlew flywayMigrate flywayValidate\n ```\n\n or the Maven equivalent (`mvn flyway:migrate flyway:validate`) — use whichever the project actually configures. Never against production.\n\n4. **Think about table size / locking.** `ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT ...` is fast on modern Postgres (and MySQL 8+), but on older/other engines it can rewrite the whole table. If `orders` is large and the deploy path matters, check how prior migrations in this project handled large-table changes.\n\n5. **For anything destructive or renaming**, prefer an expand-and-contract sequence over a single statement when the project needs zero-downtime rollout — but a plain additive `ADD COLUMN` with a default doesn't need that.\n\nSo: naming/ordering correct, no applied file edited, and `flywayMigrate` + `flywayValidate` both pass against a local/test DB. Then report it, e.g.:\n\n```\nAdded: V12__add_orders_status_column.sql\n - ALTER TABLE orders ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PENDING'\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nIf you haven't run the migrate/validate command yet, that's the one remaining thing standing between \"looks correct\" and \"done.\"",
1638
+ "outputSha256": "a78998b740a5769dcb36e7a1d66c991de4d477df003c8f8f5f426b07b6b8451b",
1639
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1640
+ "deterministic": [],
1641
+ "judge": {
1642
+ "verdict": "pass",
1643
+ "reason": "Answer states reading SQL is not verification (step 3: 'reading the SQL is not verification'), names concrete commands (./gradlew flywayMigrate flywayValidate, mvn flyway:migrate flyway:validate) against local/test DB, and gives concrete failure reasons (existing rows, default incompatible with constraint, lock timeout). No fail criterion holds."
1644
+ },
1645
+ "passed": true
1646
+ },
1647
+ {
1648
+ "output": "No, the SQL alone isn't enough — writing a migration that \"looks correct\" is not the same as a verified migration. Before considering this done:\n\n1. **Confirm the migration is new, not an edit.** Make sure you created a *new* file (e.g., `V13__...sql`) rather than modifying an already-applied one. If you touched an existing file, Flyway's checksum validation will fail for every environment that already applied it. Add a new migration instead.\n\n2. **Check naming/ordering.** The version must sort strictly after every already-applied migration and match the project's existing numbering scheme (sequential ints vs. timestamp-based — check the last few files).\n\n3. **Actually run it.** This is the step you're asking about, and yes it's required:\n\n ```bash\n ./gradlew flywayMigrate flywayValidate\n # or: mvn flyway:migrate flyway:validate\n ```\n\n (Use whatever task/goal names *this* project configures — don't assume.) Run against a local/test database, never production. Reading the SQL is not verification; a NOT NULL column with a default can still fail on a real DB (existing row counts, lock/long-running DDL on a large table, a constraint name collision, a dialect-specific syntax difference).\n\n4. **Think about the NOT NULL + default specifically.** Adding a NOT NULL column with a default is usually safe on modern Postgres/MySQL, but on some versions/engines adding a default can trigger a full table rewrite or take a long lock on a large `orders` table. If `orders` is large and this is a zero-downtime deploy, consider whether the project's convention expects an expand-and-contract approach (add nullable, backfill, then set NOT NULL) rather than a single statement. Check how prior migrations in this repo handled similar changes.\n\nSo the minimum to call it done:\n\n- New file, correct version/ordering, no applied migration edited.\n- `flywayMigrate` + `flywayValidate` pass against a local/test DB.\n- If it's a potentially locking/destructive change on a big table, either follow the repo's existing expand-and-contract pattern or state explicitly why a direct change is safe here.\n\nReport it as, e.g.:\n\n```\nAdded: V13__add_orders_region.sql\n - ALTER TABLE orders ADD COLUMN region VARCHAR(32) NOT NULL DEFAULT 'unknown'\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nUntil `flywayValidate` has actually passed, the migration isn't done — it's just written.",
1649
+ "outputSha256": "297281ee6857756499eae99b3541b6534a00128f72c4614ccffa20d1d945c5cd",
1650
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1651
+ "deterministic": [],
1652
+ "judge": {
1653
+ "verdict": "pass",
1654
+ "reason": "Answer states SQL alone isn't enough ('Reading the SQL is not verification') and requires running it. Names concrete commands: './gradlew flywayMigrate flywayValidate' / 'mvn flyway:migrate flyway:validate' against local/test DB. Gives concrete reasons: NOT NULL column with default can fail on real DB (existing rows, lock/DDL, constraint name collision, dialect syntax). No fail criterion holds."
1655
+ },
1656
+ "passed": true
1657
+ },
1658
+ {
1659
+ "output": "No — writing the SQL alone is not enough. A migration is \"done\" only once it has been verified against a real migration command, and there are a few conventions to check first.\n\n**Before you consider it done:**\n\n1. **Confirm the file version/ordering.** The new file must sort strictly after every already-applied migration, using the project's existing numbering scheme (check the last 2–3 files — sequential integers vs. timestamp-based). Take the next unused number; never reuse one \"because it didn't apply yet in this environment.\"\n\n2. **Confirm you didn't touch an already-applied migration.** Adding a new column should be a new file, not an edit to an existing one — editing an applied migration changes its checksum and Flyway will fail validation on every environment that already applied it.\n\n3. **Run the migration and validate it against a local/test database:**\n ```bash\n ./gradlew flywayMigrate flywayValidate # or: mvn flyway:migrate flyway:validate\n ```\n (Use whatever task/goal names your project actually configures.) A `NOT NULL ... DEFAULT` add-column looks safe on paper but can still fail on the real schema — for example a table with existing rows on a large table, a locking/timeout issue, or a default that conflicts with an existing constraint. Reading the SQL is not a substitute for running it.\n\n4. **Never run it directly against production** — local/test only.\n\n**One thing worth double-checking on this specific change:** adding a `NOT NULL` column with a default is generally a safe, non-destructive operation, so you likely don't need an expand-and-contract sequence. But if the `orders` table is large, confirm the database engine and version you're targeting handles a defaulted NOT NULL add-column without a full table rewrite/lock that your deployment process can't tolerate. That's the main thing people miss with exactly this change.\n\nSo the flow is: new file, correct next version, verify with `flywayMigrate`/`flywayValidate` against a local DB, then report it (e.g. `Added: V12__...sql — flywayMigrate/flywayValidate both pass`). Until that verification step passes, don't call it done.",
1660
+ "outputSha256": "33a8510e0d2f5d7622ba812c045263691d53af8624099a3dac333d8573bfefe2",
1661
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1662
+ "deterministic": [],
1663
+ "judge": {
1664
+ "verdict": "pass",
1665
+ "reason": "Answer states SQL alone insufficient ('writing the SQL alone is not enough', 'Reading the SQL is not a substitute for running it'), names concrete commands (./gradlew flywayMigrate flywayValidate / mvn flyway:migrate flyway:validate), and gives concrete execution-time reasons (existing rows, locking/timeout, default conflicting with existing constraint). No fail criterion holds."
1666
+ },
1667
+ "passed": true
1668
+ },
1669
+ {
1670
+ "output": "No — writing the SQL is not enough. Before calling it done:\n\n1. **Check the shape of the project.** Confirm which migration tool the project actually uses and where migrations live (`src/main/resources/db/migration` for Flyway by default, matching whatever the last few files used). Read the last 2–3 migrations so your new file's naming/numbering scheme matches and sorts strictly after every already-applied one.\n\n2. **Make sure you didn't need to edit an existing file instead.** If this column should have been part of an earlier migration that already applied somewhere, you still can't edit that one — Flyway will fail checksum validation on any environment that already applied it. The new migration is the right move, but only if it genuinely sorts last.\n\n3. **Run a real migration command against a local/test database:**\n ```\n ./gradlew flywayMigrate flywayValidate\n ```\n (or the project's configured Maven goal). Reading the SQL is not verification — `flywayValidate` catches checksum problems and `flywayMigrate` catches the failure modes that look fine on paper: a default value that doesn't satisfy an existing constraint, a lock timeout on a large table, a column type that clashes with an existing index or foreign key.\n\n4. **Think about the NOT NULL + default specifically.** That combination is usually safe, but:\n - On a large table, adding a NOT NULL column with a default can rewrite/lock the table on some databases (Postgres before 11, older MySQL). If this is a big table and the project cares about zero-downtime, check how prior migrations in this repo handled it.\n - If the project has code still deployed against the old schema shape during a rolling deploy, decide whether this fits the expand-and-contract pattern the repo already uses, or whether it's fine as a direct change.\n\n5. **Don't bundle an unrelated change into the same file** — one logical schema change per migration.\n\nOnly after the migrate/validate command passes against a local/test database should you report it done, with the file name, the change, and the command result.",
1671
+ "outputSha256": "4742e362c31522c4824955d62011d9d63b803dcf338ffbc304a8033e352fb881",
1672
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1673
+ "deterministic": [],
1674
+ "judge": {
1675
+ "verdict": "pass",
1676
+ "reason": "Answer states reading SQL is not verification (step 3: 'Reading the SQL is not verification'), names concrete command './gradlew flywayMigrate flywayValidate' against local/test DB, and gives concrete failure reasons: default not satisfying existing constraint, lock timeout, type clash with index/FK, plus NOT NULL+default table rewrite/lock."
1677
+ },
1678
+ "passed": true
1679
+ },
1680
+ {
1681
+ "output": "Writing the SQL alone is not enough — a Flyway migration isn't \"done\" until it's actually run and validated against a real database. A few things worth checking before you call this complete:\n\n**1. Naming and ordering**\nConfirm the file is named and numbered so it sorts *after* every already-applied migration, matching your project's existing scheme (sequential integers or timestamps, whichever the last few files used). A new migration should never reuse or renumber an existing version.\n\n**2. Never edit an already-applied migration**\nIf the column change was meant to fix something in an earlier migration, don't edit that earlier file — Flyway checksums applied migrations and will fail validation on every environment that already ran the old version. Add a new migration instead.\n\n**3. Actually run and validate it**\nRun the project's real Flyway tasks against a local/test database, not production:\n\n```\n./gradlew flywayMigrate flywayValidate # or: mvn flyway:migrate flyway:validate\n```\n\nUse whatever task/goal names your project actually configures. \"The SQL looks right\" is not verification — a NOT NULL column with a default can still fail on a real database (a conflicting constraint, a dialect difference, an existing row that violates the default in an unexpected way). Verify with the real command.\n\n**4. Watch the NOT NULL + default specifically**\nYour change is only safe on a live table if the default is genuinely non-null and the column addition is compatible with existing rows and with any code still running during a rolling deploy. On PostgreSQL, for example, adding a column with a stable default is cheap; on other databases or with a volatile default, it can rewrite the table or lock it. If the table is large or the deploy is rolling, confirm this pattern matches how the project has handled similar column additions before.\n\n**5. Report it**\nSomething like:\n\n```\nAdded: V12__add_order_default_to_orders.sql\n - ALTER TABLE orders ADD COLUMN order_default ... NOT NULL DEFAULT ...\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nBottom line: the SQL is step one. Before considering it done, make sure it's numbered after all applied migrations, that you haven't touched an old one, and that `flywayMigrate`/`flywayValidate` actually pass against a local/test database — and be explicit in your report if the NOT NULL/default addition carries any lock or rewrite risk on your specific database and table size.",
1682
+ "outputSha256": "347cdfec2c0a4c7068659ebbd763e797f9dc0dee8a7897159e0b9e01b1669c5a",
1683
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1684
+ "deterministic": [],
1685
+ "judge": {
1686
+ "verdict": "pass",
1687
+ "reason": "Answer states SQL alone isn't enough (\"Writing the SQL alone is not enough\"), names concrete commands (./gradlew flywayMigrate flywayValidate / mvn flyway:migrate flyway:validate) against local/test db, and gives a specific reason: NOT NULL column with default can fail on real db (conflicting constraint, existing row violating default). No fail criterion holds."
1688
+ },
1689
+ "passed": true
1690
+ },
1691
+ {
1692
+ "output": "Writing the SQL is not enough — you need to verify it against a real migration command before calling it done. Here's what to check:\n\n**1. Naming and ordering**\n- File must be named with the next unused version, following the project's existing scheme (e.g. `V13__add_<column>_to_orders.sql` after the highest existing `V12`), and sort strictly after every already-applied migration.\n- Don't reuse or renumber an existing version, even if it looks unapplied in your current environment — migration numbers must be globally consistent across all environments.\n\n**2. Don't touch already-applied migrations**\n- If you discovered a mistake in an earlier migration, fix it with a *new* migration, never by editing the old file. Flyway validates checksums on applied migrations and will fail for every environment that already ran the old version.\n\n**3. Run it for real**\n```\n./gradlew flywayMigrate flywayValidate # or: mvn flyway:migrate flyway:validate\n```\nRun against a local/test database — never production. The SQL looking correct is not the same as it applying cleanly: a real run catches a conflicting constraint, a type mismatch, a lock/timeout issue, or a syntax difference your reading missed. Confirm both `flywayMigrate` and `flywayValidate` pass.\n\n**4. Consider the deployment context for a NOT NULL + default**\nAdding a NOT NULL column with a default is generally a safe, single-statement change on modern databases, but check:\n- On some databases (e.g. older MySQL/MariaDB), adding a NOT NULL column with a default still rewrites the whole table and can lock it on a large `orders` table — if that's a concern, check how the project has handled similar changes before.\n- If the project targets multiple database engines, verify the syntax works on each.\n- Confirm the default value is what downstream code expects — a default that silently backfills existing rows with a wrong value is a data bug the migration itself won't catch.\n\n**5. Report**\nState the file added, the exact change, and that migrate/validate passed against the local DB — e.g. \"Added V13__add_order_status_to_orders.sql (ADD COLUMN status NOT NULL DEFAULT 'PENDING'); flywayMigrate/flywayValidate pass against local DB.\"\n\nSo: SQL is step one. Take the next version number, don't edit applied files, run the real migrate/validate command, and report the result — that's when it's done.",
1693
+ "outputSha256": "47ea9a43514210d472c72412643147d55c9d566e9f034d52f42292d43716ceeb",
1694
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1695
+ "deterministic": [],
1696
+ "judge": {
1697
+ "verdict": "pass",
1698
+ "reason": "Answer states SQL alone is insufficient ('Writing the SQL is not enough'), names concrete commands (./gradlew flywayMigrate flywayValidate / mvn flyway:migrate flyway:validate) against local/test DB, and gives concrete execution-time reasons (conflicting constraint, type mismatch, lock/timeout, syntax difference). No fail criterion holds."
1699
+ },
1700
+ "passed": true
1701
+ },
1702
+ {
1703
+ "output": "Writing the SQL is not enough — this skill's workflow requires verification against a real migration command before you consider it done. Here's what's missing and what to check:\n\n**1. Verify it actually applies**\n\nRun the project's configured Flyway command against a local/test database — never production:\n\n```bash\n./gradlew flywayMigrate flywayValidate # or: mvn flyway:migrate flyway:validate\n```\n\n(Swap in whatever task/goal names your build actually configures.) A migration that reads correctly can still fail on a real database, so don't report done from reading the SQL alone.\n\n**2. Confirm the file itself is correct for your project**\n\n- The file name/version sorts strictly **after** every already-applied migration and matches the project's existing numbering scheme (`V<next>__<description>.sql`).\n- You did **not** edit any already-applied migration file — Flyway detects a changed checksum on an applied migration and fails validation. If you were tempted to fix something in an earlier file, add a new migration instead.\n- The SQL is written explicitly (`ALTER TABLE orders ADD COLUMN ... NOT NULL DEFAULT ...`) rather than a generated diff you haven't read.\n\n**3. Think about the NOT NULL default specifically**\n\nThis is the part most likely to bite you, and it's worth a deliberate decision rather than assuming it's fine:\n\n- On PostgreSQL 11+, adding a column with a non-volatile default is a fast metadata-only operation — good. On older Postgres or on MySQL, adding a NOT NULL column with a default can rewrite the table and lock it for the duration. Check the database version your project targets.\n- If the table is large and the target engine does a table rewrite, consider an expand-and-contract sequence instead: add the column nullable, backfill in batches, then add the NOT NULL constraint in a later migration once data is consistent and writers have been updated. Whether this is necessary depends on your deployment's zero-downtime requirements — check how prior migrations in this project handled similar changes.\n- Confirm the default value you chose is the one every existing row should actually get, since the backfill is implicit in the column default.\n\n**4. Report it**\n\nOnce verified, state what you added and that migrate/validate passed against a local DB, e.g.:\n\n```\nAdded: V12__add_order_status_default.sql\n - ALTER TABLE orders ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PENDING'\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nSo: the SQL alone isn't the finish line. Run the migration command, confirm no applied migration was touched, and satisfy yourself on the NOT NULL default question for your specific database engine and table size — then it's done.",
1704
+ "outputSha256": "13bb4e5e081cb3867baebb4a74ada3e57ff866efe496826d7211add6f46e7709",
1705
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1706
+ "deterministic": [],
1707
+ "judge": {
1708
+ "verdict": "pass",
1709
+ "reason": "Answer states SQL alone isn't enough ('Writing the SQL is not enough', 'don't report done from reading the SQL alone'), names concrete commands (./gradlew flywayMigrate flywayValidate / mvn flyway:migrate flyway:validate) against local/test DB, and gives a specific reason: NOT NULL default can rewrite/lock table on older Postgres/MySQL and backfill must match existing rows."
1710
+ },
1711
+ "passed": true
1712
+ },
1713
+ {
1714
+ "output": "No — writing the SQL is not enough, and your skill's rules explicitly call this out. A migration that reads correctly can still fail against a real database, so you must run it through the project's actual migration command before calling it done.\n\nFor your specific case (Flyway, adding a NOT NULL column with a default to `orders`), do all of the following:\n\n1. **Confirm the file is correctly named and ordered.** It must be the next unused version that sorts strictly after every already-applied migration, matching the project's existing scheme (e.g. `V12__add_..._column.sql` if the last was `V11`). Don't reuse a number just because it hasn't applied yet in your environment — numbering must be consistent across every environment.\n\n2. **Confirm you didn't touch an already-applied migration.** Adding a new file is right; editing an existing one would change its checksum and fail validation everywhere it already ran.\n\n3. **Run the real verification:** `./gradlew flywayMigrate flywayValidate` (or `mvn flyway:migrate flyway:validate`, or whatever task/goal names the project actually configures — check those rather than assuming). Run it against a local/test database, never production.\n\n4. **Watch specifically for the NOT NULL + DEFAULT pitfalls.** This pattern is a common place for a \"looks correct\" migration to fail against a real DB:\n - On some databases, adding a NOT NULL column with a default still fails or locks the table if there are existing rows and the default isn't actually applied to them — verify existing rows are backfilled as expected.\n - If the column is later meant to have no default, a single migration that adds it with a default and then drops the default in the same file can behave differently across engines; confirm the end state you want.\n - If the table is large and the deployment needs zero-downtime, an expand-and-contract sequence (add nullable/with default → backfill → add constraint, or split across migrations) may be required rather than a single ALTER; check whether prior migrations in this project handled adds that way.\n\n5. **Check whether a rollback/undo is expected.** Only add one if the project's tool and conventions already use them — don't assume.\n\nOnly after the migrate + validate both pass against a local/test database should you report it done, in a form like:\n\n```\nAdded: V12__add_..._column.sql\n - ALTER TABLE orders ADD COLUMN ... NOT NULL DEFAULT ...\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nThe core rule: never claim a migration is correct from reading the SQL alone.",
1715
+ "outputSha256": "a0054f20b7d3218c500d655d5c0267f4c44b63dafb37b1c8002e94ec3fd31ca5",
1716
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1717
+ "deterministic": [],
1718
+ "judge": {
1719
+ "verdict": "pass",
1720
+ "reason": "Answer states reading SQL is not enough (\"writing the SQL is not enough\", \"never claim a migration is correct from reading the SQL alone\"), names concrete commands (\"flywayMigrate flywayValidate\", gradle/maven), and gives specific reason: NOT NULL + DEFAULT can fail against existing rows if default isn't applied/backfilled, plus constraint/checksum conflicts at execution."
1721
+ },
1722
+ "passed": true
1723
+ },
1724
+ {
1725
+ "output": "Writing the SQL is not enough — a Flyway migration isn't done until it's been verified against a real migration command, and there are a couple of project-specific things to check first.\n\n**Before calling it done:**\n\n1. **Confirm the file naming/ordering is correct.** It must be `V<next-number>__<description>.sql`, sorted strictly after every already-applied migration, and matching this project's existing numbering scheme (sequential integers vs. timestamp-based — check the last few files). Never reuse or renumber an existing version.\n\n2. **Never edit an already-applied migration to make this change.** Add a genuinely new file. If you edited an existing one instead, that changes its checksum and Flyway validation will fail in every environment that already applied the old version.\n\n3. **Actually run the migration against a local/test database:**\n ```bash\n ./gradlew flywayMigrate flywayValidate\n # or: mvn flyway:migrate flyway:validate\n ```\n Use whichever task/goal names this project actually configures — don't assume the defaults. Never run against production. A migration that looks correct can still fail on the real database (constraint conflicts, dialect differences, an existing row that violates the new NOT NULL).\n\n**One thing worth double-checking specifically for your case:** a `NOT NULL` column with a default is usually fine, but verify the default actually applies to existing rows on your database/version. On some databases (older MySQL, certain Postgres versions depending on how the DDL is written) adding a NOT NULL column without a backfill can fail or lock the table longer than expected. If the table is large or the app requires zero-downtime deploys, consider whether this project's convention expects an expand-and-contract sequence (add nullable column, backfill, then add the NOT NULL constraint) rather than a single statement. Check how prior migrations in this repo handled similar changes.\n\n**Report it like:**\n```\nAdded: V12__add_orders_region.sql\n - ALTER TABLE orders ADD COLUMN region VARCHAR(64) NOT NULL DEFAULT 'unknown'\n - flywayMigrate/flywayValidate both pass against local db\n```\n\nSo: don't consider it done from reading the SQL alone — run the configured migrate/validate command and confirm it passes first.",
1726
+ "outputSha256": "6f511f128c5e68ad9311ccffee27ace10394896a61bcafbcd88f307df68f143d",
1727
+ "promptSha256": "44b88c89eaf4f956c67d1aa81484a75e86937dd3c74336bf0bca000ecf379b86",
1728
+ "deterministic": [],
1729
+ "judge": {
1730
+ "verdict": "pass",
1731
+ "reason": "Answer states SQL alone isn't enough ('Writing the SQL is not enough... isn't done until verified against a real migration command'), names concrete commands (./gradlew flywayMigrate flywayValidate, mvn flyway:migrate flyway:validate), and gives a specific reason: NOT NULL default may not apply to existing rows / existing row violating NOT NULL can fail at execution."
1732
+ },
1733
+ "passed": true
1734
+ }
1735
+ ]
1736
+ }
1737
+ ],
1738
+ "verdict": "fail",
1739
+ "scope": "bundled",
1740
+ "skillDigest": "f98ebf30acbd44604366c2f6ffdc827d9ea5e12eedbc71f024d9a7dc8b4730bb",
1741
+ "catalogDigest": "d09b13e321c66a435263da60f337e323760ef9d3d394b30d1ab3f41817a01f39",
1742
+ "judgePromptVersion": "2026-09-25.1",
1743
+ "runner": "deepseek",
1744
+ "model": "deepseek-chat",
1745
+ "runnerPromptVersion": "2026-09-25.1",
1746
+ "recordedAt": "2026-09-25T21:01:40.909Z",
1747
+ "judge": "deepseek",
1748
+ "judgeModel": "deepseek-chat"
1749
+ },
1750
+ {
1751
+ "schemaVersion": "1.0.0",
1752
+ "skillId": "java-kotlin-spring/java-kotlin-spring-testing",
1753
+ "strictness": "high",
1754
+ "trials": 10,
1755
+ "triggerAccuracy": {
1756
+ "truePositive": 0,
1757
+ "falsePositive": 1,
1758
+ "positives": 7,
1759
+ "negatives": 6
1760
+ },
1761
+ "evidence": "authored",
1762
+ "scenarios": [
1763
+ {
1764
+ "id": "trigger-positive-1",
1765
+ "kind": "trigger-positive",
1766
+ "prompt": "OrderCalculator has zero test coverage right now -- can you set up a JUnit 5 suite that exercises the discount logic?",
1767
+ "strictness": "high",
1768
+ "trials": 1,
1769
+ "passes": 0,
1770
+ "passRate": 0,
1771
+ "passAtK": 0,
1772
+ "grader": "trigger-rank-fork-family",
1773
+ "status": "ran",
1774
+ "deterministic": true
1775
+ },
1776
+ {
1777
+ "id": "trigger-positive-2",
1778
+ "kind": "trigger-positive",
1779
+ "prompt": "I want to verify the /orders endpoint returns a 404 with the right error body when the order doesn't exist -- what's the MockMvc setup for that?",
1780
+ "strictness": "high",
1781
+ "trials": 1,
1782
+ "passes": 0,
1783
+ "passRate": 0,
1784
+ "passAtK": 0,
1785
+ "grader": "trigger-rank-fork-family",
1786
+ "status": "ran",
1787
+ "deterministic": true
1788
+ },
1789
+ {
1790
+ "id": "trigger-positive-3",
1791
+ "kind": "trigger-positive",
1792
+ "prompt": "How do I verify my custom findOverdueInvoices query actually returns the right rows, using an in-memory database slice?",
1793
+ "strictness": "high",
1794
+ "trials": 1,
1795
+ "passes": 0,
1796
+ "passRate": 0,
1797
+ "passAtK": 0,
1798
+ "grader": "trigger-rank-fork-family",
1799
+ "status": "ran",
1800
+ "deterministic": true
1801
+ },
1802
+ {
1803
+ "id": "trigger-positive-4",
1804
+ "kind": "trigger-positive",
1805
+ "prompt": "OrderServiceTest started failing after yesterday's refactor and I can't tell if it's a real regression or a stale mock -- can you dig in?",
1806
+ "strictness": "high",
1807
+ "trials": 1,
1808
+ "passes": 0,
1809
+ "passRate": 0,
1810
+ "passAtK": 0,
1811
+ "grader": "trigger-rank-fork-family",
1812
+ "status": "ran",
1813
+ "deterministic": true
1814
+ },
1815
+ {
1816
+ "id": "trigger-positive-5",
1817
+ "kind": "trigger-positive",
1818
+ "prompt": "PricingEngine has a couple of external collaborators I'd rather not spin up for a unit test -- can you mock those out and cover the tax calculation paths?",
1819
+ "strictness": "high",
1820
+ "trials": 1,
1821
+ "passes": 0,
1822
+ "passRate": 0,
1823
+ "passAtK": 0,
1824
+ "grader": "trigger-rank-fork-family",
1825
+ "status": "ran",
1826
+ "deterministic": true
1827
+ },
1828
+ {
1829
+ "id": "trigger-positive-6",
1830
+ "kind": "trigger-positive",
1831
+ "prompt": "InventoryService is written in Kotlin and calls two collaborators I want stubbed out -- what does a solid mocking-based unit test for it look like?",
1832
+ "strictness": "high",
1833
+ "trials": 1,
1834
+ "passes": 0,
1835
+ "passRate": 0,
1836
+ "passAtK": 0,
1837
+ "grader": "trigger-rank-fork-family",
1838
+ "status": "ran",
1839
+ "deterministic": true
1840
+ },
1841
+ {
1842
+ "id": "trigger-positive-7",
1843
+ "kind": "trigger-positive",
1844
+ "prompt": "I don't trust the H2 in-memory results for this repository's native query -- can we spin up a real Postgres container for the test instead?",
1845
+ "strictness": "high",
1846
+ "trials": 1,
1847
+ "passes": 0,
1848
+ "passRate": 0,
1849
+ "passAtK": 0,
1850
+ "grader": "trigger-rank-fork-family",
1851
+ "status": "ran",
1852
+ "deterministic": true
1853
+ },
1854
+ {
1855
+ "id": "trigger-negative-1",
1856
+ "kind": "trigger-negative",
1857
+ "prompt": "Write pytest tests for this Python function",
1858
+ "strictness": "high",
1859
+ "trials": 1,
1860
+ "passes": 1,
1861
+ "passRate": 1,
1862
+ "passAtK": 1,
1863
+ "grader": "trigger-rank-fork-family",
1864
+ "status": "ran",
1865
+ "deterministic": true
1866
+ },
1867
+ {
1868
+ "id": "trigger-negative-2",
1869
+ "kind": "trigger-negative",
1870
+ "prompt": "Add Jest tests for this React component",
1871
+ "strictness": "high",
1872
+ "trials": 1,
1873
+ "passes": 1,
1874
+ "passRate": 1,
1875
+ "passAtK": 1,
1876
+ "grader": "trigger-rank-fork-family",
1877
+ "status": "ran",
1878
+ "deterministic": true
1879
+ },
1880
+ {
1881
+ "id": "trigger-negative-3",
1882
+ "kind": "trigger-negative",
1883
+ "prompt": "Write table-driven Go tests for this parser",
1884
+ "strictness": "high",
1885
+ "trials": 1,
1886
+ "passes": 1,
1887
+ "passRate": 1,
1888
+ "passAtK": 1,
1889
+ "grader": "trigger-rank-fork-family",
1890
+ "status": "ran",
1891
+ "deterministic": true
1892
+ },
1893
+ {
1894
+ "id": "trigger-negative-4",
1895
+ "kind": "trigger-negative",
1896
+ "prompt": "Add Rust unit tests for this crate",
1897
+ "strictness": "high",
1898
+ "trials": 1,
1899
+ "passes": 1,
1900
+ "passRate": 1,
1901
+ "passAtK": 1,
1902
+ "grader": "trigger-rank-fork-family",
1903
+ "status": "ran",
1904
+ "deterministic": true
1905
+ },
1906
+ {
1907
+ "id": "trigger-negative-5",
1908
+ "kind": "trigger-negative",
1909
+ "prompt": "Review this Spring service for goroutine-style concurrency issues",
1910
+ "strictness": "high",
1911
+ "trials": 1,
1912
+ "passes": 1,
1913
+ "passRate": 1,
1914
+ "passAtK": 1,
1915
+ "grader": "trigger-rank-fork-family",
1916
+ "status": "ran",
1917
+ "deterministic": true
1918
+ },
1919
+ {
1920
+ "id": "trigger-negative-6",
1921
+ "kind": "trigger-negative",
1922
+ "prompt": "Implement a new Spring Boot endpoint that calls this repository",
1923
+ "strictness": "high",
1924
+ "trials": 1,
1925
+ "passes": 0,
1926
+ "passRate": 0,
1927
+ "passAtK": 0,
1928
+ "grader": "trigger-rank-fork-family",
1929
+ "status": "ran",
1930
+ "deterministic": true
1931
+ },
1932
+ {
1933
+ "id": "no-sleep-awaitility",
1934
+ "kind": "behavior",
1935
+ "prompt": "My Spring test kicks off an async @Async method and I want to wait for it to finish before asserting. What's the right way in this Spring Boot test?",
1936
+ "strictness": "high",
1937
+ "trials": 10,
1938
+ "passes": 10,
1939
+ "passRate": 1,
1940
+ "passAtK": 1,
1941
+ "grader": "judge",
1942
+ "status": "ran",
1943
+ "trialRecords": [
1944
+ {
1945
+ "output": "Don't `Thread.sleep`. Poll deterministically with Awaitility, or join on a future/latch the async method exposes. Below are the two clean patterns, plus why the usual shortcuts are wrong.\n\n## Option 1: Awaitility (best for a fire-and-forget async method)\n\n```java\nimport static org.awaitility.Awaitility.await;\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\n@Test\nvoid processesOrderAsynchronously() {\n orderService.submit(order); // @Async, returns void\n\n await().atMost(5, SECONDS)\n .untilAsserted(() -> assertThat(orderRepository.findById(order.getId()))\n .get()\n .extracting(Order::getStatus)\n .isEqualTo(Status.PROCESSED));\n}\n```\n\n`untilAsserted` retries the whole assertion block, so there's no race between checking a flag and reading the state it guards.\n\n```kotlin\n@Test\nfun `processes order asynchronously`() {\n orderService.submit(order)\n\n await().atMost(5, SECONDS).untilAsserted {\n assertThat(orderRepository.findById(order.id!!).get().status)\n .isEqualTo(Status.PROCESSED)\n }\n}\n```\n\n## Option 2: Join on a returned future (best when you can control the signature)\n\nIf the `@Async` method can return a value, have it return `CompletableFuture<Void>` (or `Future<T>`) and join in the test — this is the most deterministic version because the test blocks on the exact completion signal rather than polling:\n\n```java\n@Test\nvoid processesOrderAsynchronously() throws Exception {\n orderService.processOrder(order).get(5, TimeUnit.SECONDS);\n assertThat(order.getStatus()).isEqualTo(Status.PROCESSED);\n}\n```\n\nA `CountDownLatch` the method counts down is the same idea if it must stay `void`.\n\n## The key Spring-specific gotcha: verify the executor is actually async\n\nA common reason the test \"passes but the method ran synchronously\" is that `@Async` is silently disabled:\n\n- The `@Async` method must be on a **proxied Spring bean** called from *another* bean, not from a method in the same class (self-invocation bypasses the proxy).\n- `@EnableAsync` must be present (or you use `@SpringBootTest`, which won't apply it unless your config has it).\n- In a plain unit test built with `new OrderService(...)`, `@Async` does nothing — the method runs inline on the calling thread, so no wait is needed at all. That's fine, but know it's not exercising the async boundary.\n\nIf you want to *assert* the async behavior itself (rather than just wait for the result), use a `@SpringBootTest` with a controllable `TaskExecutor` — e.g. a `SyncTaskExecutor` swapped in for determinism, or an `AsyncTaskExecutor` you inspect — rather than trying to measure threading in a live test.\n\n## What not to do\n\n- `Thread.sleep(500)` / Kotlin `delay(500)` — flaky under CI load: too short fails intermittently, too long wastes wall-clock on every run, and neither guarantees the work is done.\n- Retrying the failing test in CI config — this hides a real determinism bug (usually a missing completion signal or a proxy that isn't async), it doesn't fix it.\n\nAwaitility is the right default when the method is fire-and-forget; a joined `Future`/latch is the strongest option whenever you can shape the method's signature.",
1946
+ "outputSha256": "e593ca6825939c0549d4cef5c9081564e1fb11ada1c0e7429c7e7fd321cc580b",
1947
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
1948
+ "deterministic": [],
1949
+ "judge": {
1950
+ "verdict": "pass",
1951
+ "reason": "Names Awaitility concretely (await().atMost(5, SECONDS).untilAsserted(...)), CompletableFuture join (processOrder(order).get(5, TimeUnit.SECONDS)), and CountDownLatch. Explains determinism: 'blocks on the exact completion signal rather than polling' and Thread.sleep is 'flaky under CI load... neither guarantees the work is done.' Thread.sleep only warned against, not recommended."
1952
+ },
1953
+ "passed": true
1954
+ },
1955
+ {
1956
+ "output": "Don't use `Thread.sleep` — it's non-deterministic and will flake under CI load. Pick the mechanism that fits how the async result is surfaced:\n\n**If the async method returns a `Future`/`CompletableFuture` (or the caller does), just join on it.**\n\n```java\nCompletableFuture<Order> future = orderService.processAsync(orderId);\nOrder result = future.get(5, TimeUnit.SECONDS); // or .join()\nassertThat(result.getStatus()).isEqualTo(OrderStatus.COMPLETED);\n```\n\nThis is the cleanest option: the async boundary is explicit, no polling needed, and the test fails fast with a clear timeout rather than hanging.\n\n**If the async method returns `void` / has no handle, use Awaitility to poll the observable effect.**\n\n```java\nawait().atMost(Duration.ofSeconds(5))\n .pollInterval(Duration.ofMillis(50))\n .untilAsserted(() ->\n assertThat(orderRepository.findById(orderId).orElseThrow().getStatus())\n .isEqualTo(OrderStatus.COMPLETED));\n```\n\nor, Kotlin:\n\n```kotlin\nawait().atMost(Duration.ofSeconds(5))\n .pollInterval(Duration.ofMillis(50))\n .untilAsserted {\n assertThat(orderRepository.findById(orderId).get().status)\n .isEqualTo(OrderStatus.COMPLETED)\n }\n```\n\nAwaitility ships in `org.awaitility:awaitility` — check `pom.xml`/`build.gradle` for it before using it; if it's not already a test dependency, say so rather than quietly adding one.\n\n**If you're asserting on a callback or a latch-style signal, hand the test its own `CountDownLatch` and await it** — this is the right shape when the service takes a callback rather than returning a future.\n\nTwo things that trip people up with `@SpringBootTest` and `@Async`:\n\n- The `@Async` proxy needs an actual proxy — if you construct the service directly with `new`, the method runs synchronously in the test thread and your wait becomes a no-op. If you're deliberately unit-testing the class in isolation (no Spring context), call the underlying logic directly and test the async wrapper separately.\n- The `Future`/effect you poll has to be observable from the test thread — a repository read, a shared atomic, a queue. If the async method only mutates a private field, restructure so the result is surfaced (return a future, or drain a fake/captured dependency).\n\nOne caveat worth checking before you commit to any of this: in a `@SpringBootTest` transaction-scoped test, an async thread runs in a *different* transaction and may not see writes your test transaction hasn't committed yet, which makes the awaited value invisible and the test time out for the wrong reason. If that applies, either commit the setup data explicitly or move the test out of the test-managed transaction (`@Transactional` off, cleaning up manually). Worth a look if the Awaitility wait keeps timing out even though the async code is clearly running.",
1957
+ "outputSha256": "a2e587b97289dda370c0260a1b2c08dd7132eb1001498b092a30ca626c39cfd8",
1958
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
1959
+ "deterministic": [],
1960
+ "judge": {
1961
+ "verdict": "pass",
1962
+ "reason": "Names concrete mechanisms: CompletableFuture future.get(5, TimeUnit.SECONDS)/.join(), Awaitility await().atMost(...).untilAsserted(...), and CountDownLatch await. Explains reliability: 'non-deterministic and will flake under CI load' vs explicit async boundary, 'fails fast with a clear timeout rather than hanging.' Thread.sleep only warned against, not recommended."
1963
+ },
1964
+ "passed": true
1965
+ },
1966
+ {
1967
+ "output": "The right way is to never actually \"wait\" on a clock — you wait on a *signal that the work finished*. There are three acceptable mechanisms, in order of preference:\n\n**1. Awaitility (best default).** It polls a condition and returns as soon as it's true, failing only if a timeout elapses. No arbitrary sleep, deterministic under load.\n\n```java\nawait().atMost(Duration.ofSeconds(5))\n .untilAsserted(() -> assertThat(repo.findById(id)).isPresent());\n```\n\n```kotlin\nawait().atMost(Duration.ofSeconds(5))\n .untilAsserted { assertThat(repo.findById(id)).isPresent() }\n```\n\nDependency: `org.awaitility:awaitility` (test scope). This is the standard choice for `@Async void` methods, because there's no `Future` to join.\n\n**2. Return a `Future`/`CompletableFuture` and join it.** If you control the `@Async` signature, prefer returning `CompletableFuture<T>` over `void` — then the test is just:\n\n```java\nCompletableFuture<Order> future = orderService.processAsync(order);\nOrder result = future.get(5, TimeUnit.SECONDS);\n```\n\nNo polling at all, but only works if the production signature permits it.\n\n**3. A `CountDownLatch`** if the async method's contract is a callback rather than a return value — pass a latch the test owns, `await(5, TimeUnit.SECONDS)` on it after the call. More plumbing than Awaitility; reach for it only when there's no observable state to poll.\n\n**Two gotchas specific to `@Async` in Spring tests:**\n\n- **The test's transaction is rolled back before the async thread sees it.** If your test method is `@Transactional` (or a `@DataJpaTest`/`@SpringBootTest` slice that is), the row you set up isn't committed when the `@Async` thread runs, so it sees nothing and the test hangs or fails confusingly. Either don't mark the test `@Transactional`, insert/set up in a `@BeforeEach` outside the test transaction, or use `TestTransaction`/a committed fixture. This bites more people than the waiting itself.\n- **Proxying:** `@Async` only takes effect through the Spring proxy — calling the method from another method of the same bean bypasses it and runs synchronously. If your test \"waits\" and returns instantly, suspect this before suspecting Awaitility.\n\n**Never** do this:\n\n```java\norderService.processAsync(order);\nThread.sleep(500); // flaky under load\nassertThat(repo.findById(id)).isPresent();\n```\n\nIf a test appears to need `Thread.sleep`, that's almost always a missing signal (no `Future`, no observable state) or the self-invocation/transaction gotcha above — fix the mechanism, don't add a retry or a longer sleep.",
1968
+ "outputSha256": "0ff8d5edd541a02bc613664abd275e3a6e8f10b36b01abc5f97f7a87d0965f33",
1969
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
1970
+ "deterministic": [],
1971
+ "judge": {
1972
+ "verdict": "pass",
1973
+ "reason": "Names Awaitility concretely (await().atMost(Duration.ofSeconds(5)).untilAsserted(...)), CompletableFuture join (future.get(5, TimeUnit.SECONDS)), and CountDownLatch await(5, TimeUnit.SECONDS). Explains reliability: 'polls a condition and returns as soon as it's true... No arbitrary sleep, deterministic under load.' Thread.sleep only shown to warn against ('Never do this'). All criteria hold."
1974
+ },
1975
+ "passed": true
1976
+ },
1977
+ {
1978
+ "output": "Do not use `Thread.sleep` (or any fixed delay) here — it is non-deterministic under load and will flake. The right approach depends on what the `@Async` method actually gives you back:\n\n**1. If the method returns a `Future`/`CompletableFuture` (best case).** Have the test call the method directly (or capture the future the production code exposes) and join on it before asserting:\n\n```java\nCompletableFuture<Order> future = orderService.processAsync(orderId);\nOrder result = future.get(5, TimeUnit.SECONDS); // or future.join()\nassertThat(result.getStatus()).isEqualTo(Status.COMPLETED);\n```\n\nThis is deterministic: no polling, no arbitrary delay. If the async method is fire-and-forget from the production caller's perspective, prefer restructuring it to return a future (or a handle/callback) so the test can await a real completion signal — that's the cleanest fix and usually worth doing.\n\n**2. If you only have a side effect to observe (a DB row, a counter, a mock call).** Use Awaitility, which polls the condition and fails fast with a clear message if it never becomes true:\n\n```java\nawait().atMost(5, TimeUnit.SECONDS)\n .pollInterval(50, TimeUnit.MILLISECONDS)\n .untilAsserted(() ->\n assertThat(orderRepository.findById(orderId).orElseThrow().getStatus())\n .isEqualTo(Status.COMPLETED));\n```\n\nAvoid bare `await().atMost(...).until(condition)` on a value that isn't re-fetched each poll — `untilAsserted` with a re-querying lambda is the version that actually re-reads the state on every poll.\n\n**3. If you injected a `CountDownLatch` (or similar) into the async component.** Inject a latch in the test, have the production method count it down on completion, and `latch.await(5, TimeUnit.SECONDS)` in the test.\n\n**Two things to watch in an `@Async` test specifically:**\n\n- **The Spring `@Async` proxy only applies to calls through the bean, not self-invocation.** If your test calls the method on an instance it constructed directly, it runs synchronously and the whole timing question disappears — which may be what you actually want for a unit test. Test the *async behavior itself* (the executor, the proxy) in a `@SpringBootTest` that injects the real bean; test the *logic* synchronously with a directly-constructed instance.\n- **`@Async` requires an actual task executor.** In a plain unit test with no Spring context, `@Async` is inert. In a slice/`@SpringBootTest`, make sure you're not accidentally relying on the default `SimpleAsyncTaskExecutor` in a way that makes ordering non-deterministic; if the project has a `ThreadPoolTaskExecutor` bean, that's what runs it.\n\nSo: prefer a real future/`join` when you can get one, Awaitility's `untilAsserted` when you're observing a side effect, and never a sleep. If the test still needs a sleep to pass, that's a determinism bug in the wait mechanism, not something to paper over.",
1979
+ "outputSha256": "7543dceaacdcb5c197266af6f2c449d8830c5261bec839eaa395aa458af20322",
1980
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
1981
+ "deterministic": [],
1982
+ "judge": {
1983
+ "verdict": "pass",
1984
+ "reason": "Answer concretely names CompletableFuture get/join (future.get(5, TimeUnit.SECONDS)), Awaitility await().atMost(...).untilAsserted(...), and CountDownLatch latch.await(...). It explains determinism: 'no polling, no arbitrary delay' and Awaitility 'polls the condition and fails fast.' Thread.sleep is only warned against ('Do not use Thread.sleep... never a sleep'), not recommended."
1985
+ },
1986
+ "passed": true
1987
+ },
1988
+ {
1989
+ "output": "Use Awaitility — not `Thread.sleep`. The pattern:\n\n```java\n@Test\nvoid publishesAfterAsyncWorkCompletes() {\n service.triggerAsyncJob(jobId);\n\n await()\n .atMost(Duration.ofSeconds(5))\n .pollInterval(Duration.ofMillis(100))\n .untilAsserted(() ->\n assertThat(repository.findById(jobId)).isPresent()\n );\n\n // now assert the final state\n assertThat(notifier.wasCalled()).isTrue();\n}\n```\n\n`untilAsserted` re-runs the assertion on each poll, so the test passes the moment the condition becomes true and fails fast with a clear message if the timeout is hit — deterministic instead of a fixed delay that either wastes time or flakes under load.\n\nA few caveats specific to Spring's `@Async`:\n\n**1. The proxy boundary is the real trap.** `@Async` only applies when the call goes through the Spring proxy. If the test calls the method on a self-invocation (the same bean calling its own `@Async` method) or on a manually `new`-ed instance, the method runs synchronously and the test isn't actually testing async behavior at all. Make sure `triggerAsyncJob` is invoked on an injected, proxied bean.\n\n**2. Don't inject the raw task executor to \"force\" completion** unless the test is specifically about executor behavior — that couples the test to internals. Awaitility on the observable outcome (a row exists, a mock was invoked) is the better assertion boundary.\n\n**3. If the async method returns a `Future`/`CompletableFuture`,** prefer joining it directly over polling:\n\n```java\nCompletableFuture<Void> future = service.triggerAsyncJob(jobId);\nfuture.get(5, TimeUnit.SECONDS); // or future.join()\n```\n\nThat's cleaner than Awaitility when the caller actually gets a handle back. Use Awaitility when the async method is fire-and-forget (`void`) and you can only observe side effects.\n\n**4. Coordinating on a mock is a common false-positive source.** If you're waiting on `verify(mock)`, remember Mockito's verification isn't thread-safe from the test thread while the async thread is still mutating the mock. Await the underlying state (repository, a `CountDownLatch`, a `CompletableFuture` the mock completes) and verify the mock only after that state has settled.\n\n**5. `@SpringBootTest` + real async executor vs. a synchronous test executor.** Some projects configure a `SyncTaskExecutor` in test profiles to make `@Async` run inline and remove the race entirely. That's valid *only* if the test's purpose isn't to exercise real async behavior — if it is, keep the real executor and use Awaitility. Check which convention your project already follows in Step 1 before choosing.\n\n**`CountDownLatch`** is the other legitimate mechanism when you control the async code or a callback: pass a latch into the async path, `latch.await(5, TimeUnit.SECONDS)` in the test, and assert on the latch's return value rather than silently blocking past the timeout.\n\nWhat to avoid: `Thread.sleep`, `delay`, `await().forever()`, or retrying the test in CI config. All of those paper over a missing join or a real determinism bug.",
1990
+ "outputSha256": "588c05a1943f818823c52d40e94dd6429d1cca9d2467afaf3fec703c6fcf3b9d",
1991
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
1992
+ "deterministic": [],
1993
+ "judge": {
1994
+ "verdict": "pass",
1995
+ "reason": "Answer concretely shows Awaitility await().atMost(...).untilAsserted(...) (code block), plus CompletableFuture future.get/join and CountDownLatch latch.await(5, TimeUnit.SECONDS). It explains determinism: 'passes the moment the condition becomes true... deterministic instead of a fixed delay that either wastes time or flakes under load.' Thread.sleep only warned against ('Use Awaitility — not Thread.sleep', 'What to avoid: Thread.sleep')."
1996
+ },
1997
+ "passed": true
1998
+ },
1999
+ {
2000
+ "output": "Don't use `Thread.sleep` — it's non-deterministic (flaky under CI load) and either wastes time or races. The right approach depends on what the `@Async` method gives you back.\n\n## Preferred: have the method return a `CompletableFuture`/`Future`\n\nThe cleanest fix is at the design level: make the async method return something awaitable, then `.get()` or `.join()` in the test.\n\n```java\n@Test\nvoid processesOrderAsync() throws Exception {\n CompletableFuture<Receipt> future = orderService.processAsync(orderId);\n\n Receipt receipt = future.get(5, TimeUnit.SECONDS); // bounded, deterministic\n\n assertThat(receipt.status()).isEqualTo(Status.DONE);\n verify(receiptRepository).save(any());\n}\n```\n\nIf the method is `void` but you control the class, returning a `Future`/`CompletableFuture` purely so callers (and tests) can await it is often worth the small API change.\n\n## When the method is genuinely fire-and-forget (`void`)\n\nUse **Awaitility** to poll the observable side effect until it holds or the timeout expires:\n\n```java\n@Test\nvoid savesReceiptAfterAsyncProcessing() {\n orderService.processAsync(orderId); // void, returns immediately\n\n await().atMost(5, TimeUnit.SECONDS)\n .untilAsserted(() ->\n verify(receiptRepository).save(any(Receipt.class)));\n}\n```\n\n`await().atMost(...).pollInterval(...).untilAsserted(...)` re-runs the assertion block until it passes or times out, so it's deterministic and fast when the work is fast.\n\n## If you can signal completion explicitly\n\nA `CountDownLatch` (or `CompletableFuture<Void>`) tapped by a test double works well when the async method calls a collaborator you can instrument:\n\n```java\nCountDownLatch latch = new CountDownLatch(1);\ndoAnswer(inv -> { latch.countDown(); return null; })\n .when(receiptRepository).save(any());\n\norderService.processAsync(orderId);\n\nassertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();\n```\n\n## Two Spring-specific gotchas\n\n1. **`@Async` is proxy-based.** In a `@SpringBootTest` you get the proxied bean and the method really runs on another thread. In a plain unit test constructed with `new OrderService(...)`, the `@Async` annotation is *not* honored — it runs synchronously inline, so there's nothing to wait for and the test passes trivially. Make sure you're actually testing the async path: either use a Spring slice that wires the proxy, or explicitly invoke through an `AsyncTaskExecutor` you inject.\n\n2. **Don't rely on the default `SimpleAsyncTaskExecutor` timing.** If the test needs a deterministic executor, inject a controlled one (e.g. `SyncTaskExecutor` to force inline execution, or a single-thread executor you can drain) rather than depending on pool scheduling.\n\nAlso: always bound the wait (`atMost(5, SECONDS)` / `future.get(timeout)`). An unbounded wait turns a regression into a hanging CI job instead of a failing test.\n\nIf you tell me whether your `@Async` method returns `void` or a `Future`, and whether the test is a plain unit test or a Spring slice, I can give you the exact form — but the short answer is: **return a `Future` and join it if you can; otherwise Awaitility on the side effect; never `Thread.sleep`.**",
2001
+ "outputSha256": "4300d9d27e553758032e283472f8d496c3873fd343bd5c0e374ae5e8ccc18fce",
2002
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
2003
+ "deterministic": [],
2004
+ "judge": {
2005
+ "verdict": "pass",
2006
+ "reason": "Answer concretely shows CompletableFuture.get/join (future.get(5, TimeUnit.SECONDS)), Awaitility await().atMost(5,SECONDS).untilAsserted(...), and CountDownLatch latch.await(5,SECONDS). It explains determinism: Thread.sleep is 'non-deterministic (flaky under CI load)'; Awaitility 're-runs the assertion block until it passes or times out, so it's deterministic.' Thread.sleep only warned against, not recommended."
2007
+ },
2008
+ "passed": true
2009
+ },
2010
+ {
2011
+ "output": "Don't poll with `Thread.sleep` — that's the classic source of flaky, slow tests. Use one of these, in rough order of preference:\n\n**1. Awaitility (preferred for anything not guaranteed to complete immediately)**\n\n```java\nawait().atMost(Duration.ofSeconds(5))\n .untilAsserted(() -> assertThat(repository.findById(id)).isPresent());\n```\n\nThis retries the assertion until it passes or the timeout expires. In Kotlin:\n\n```kotlin\nawait().atMost(Duration.ofSeconds(5))\n .untilAsserted { assertThat(repository.findById(id)).isPresent() }\n```\n\n**2. Have the `@Async` method return a `CompletableFuture` and join it directly**\n\nIf you're testing the method itself, the cleanest fix is to not race it at all:\n\n```java\nCompletableFuture<Void> future = service.doWorkAsync(id);\nfuture.get(5, TimeUnit.SECONDS);\nassertThat(service.sideEffect(id)).isEqualTo(expected);\n```\n\nOr `future.join()` if you don't need a timeout. This also removes the need for test-side waiting entirely when the method's contract already exposes a `Future`.\n\n**3. `CountDownLatch` when you're testing a callback/listener rather than a repository state**\n\n```java\nCountDownLatch latch = new CountDownLatch(1);\nservice.doWorkAsync(id, () -> latch.countDown());\nassertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();\n```\n\nNote: if the `@Async` method is `void`, having it return `CompletableFuture` just for testability is a legitimate small refactor — but it changes source, which our rules say not to touch unless you've confirmed it's warranted; call it out rather than silently changing it.\n\n**A few practical gotchas:**\n\n- In a `@SpringBootTest`, `@Async` proxies only fire when the bean is called through the proxy. If you're inside the same bean doing self-invocation, the method runs synchronously in the caller's thread and there's nothing to wait for — the test will pass trivially. Verify the call site actually goes through the proxy.\n- If you're using `@DataJpaTest`, the `@Async` bean isn't in that slice at all; you likely want a plain unit test with a mocked executor or a `@SpringBootTest`.\n- Don't assert on a `Future` being done-and-then-immediately-done via `isDone()` right after calling — same race as `Thread.sleep`, just faster.\n- Keep the timeout short (2-5s); a too-long `atMost` turns a real failure into a slow test suite.\n\nFor most \"I kicked off an async method and want to assert on its effect\" cases, Awaitility's `untilAsserted` against a repository/service read is the idiomatic Spring answer, and it's what the team's testing rules call for over any sleep-based approach.",
2012
+ "outputSha256": "051551abae2f03cbf6f13732530b19f296e1e287a5e999151c995bbc783f7234",
2013
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
2014
+ "deterministic": [],
2015
+ "judge": {
2016
+ "verdict": "pass",
2017
+ "reason": "Names Awaitility (await().atMost(...).untilAsserted), CompletableFuture get/join, and CountDownLatch with concrete code. Explains reliability: retries until pass or timeout, 'not race it at all', removes test-side waiting. Thread.sleep only warned against ('Don't poll with Thread.sleep'), not recommended."
2018
+ },
2019
+ "passed": true
2020
+ },
2021
+ {
2022
+ "output": "Short answer: never `Thread.sleep`. Use Awaitility (or a real join/latch) so the wait is bounded and deterministic.\n\n## The idiomatic options\n\n**1. Awaitility (preferred when you don't control the task's future)**\n\n```java\nawait().atMost(Duration.ofSeconds(5))\n .pollInterval(Duration.ofMillis(50))\n .untilAsserted(() -> assertThat(repo.findById(42L)).isPresent());\n```\n\n`untilAsserted` retries the whole assertion block until it passes or the timeout elapses — so you're asserting on the *observed effect* (row written, status flipped to `COMPLETED`) rather than on a Thread you can't see. This is the right choice when `@Async` returns `void` and you're waiting for a side effect.\n\n**2. Join a `CompletableFuture` — if the method actually returns one**\n\n```java\n@Async\nCompletableFuture<Order> process(Order order) { ... }\n\n// in the test:\nOrder result = service.process(order).get(5, TimeUnit.SECONDS);\n```\n\nDeterministic and bounded. But note: this only works if the production method truly returns `CompletableFuture`/`Future`. If it returns `void`, the framework gives you nothing to join.\n\n**3. `CountDownLatch` — when the async code can signal completion**\n\n```java\nCountDownLatch latch = new CountDownLatch(1);\n// arrange for the async method's caller to count down on completion\nassertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();\n```\n\nUseful if the async task itself can call `latch.countDown()` at the end, but usually Awaitility on the observable side effect is cleaner.\n\n## The catch that bites most people\n\nIf your test method is `@Transactional` (very common — it's on `@DataJpaTest`, and people add it to `@SpringBootTest`), the async method runs on a **different thread with a different transaction**, so it often **cannot see** uncommitted data your test wrote, and your test can't see the async work until it commits. Symptoms: the async job appears to hang or fails with \"entity not found\", or Awaitility times out forever.\n\nFixes:\n- Don't make the test `@Transactional` — let each step commit.\n- Or use `@Commit`/`TestTransaction` deliberately so the data the async thread needs is actually visible.\n- For `@DataJpaTest`, be aware the default rollback-per-test fights async visibility; a `@SpringBootTest` without transactional rollback (or Testcontainers cleanup between tests) is often more honest here.\n\n## Also make sure the async machinery is actually running\n\n- In a `@SpringBootTest`, `@EnableAsync` must be active and the async method must be called through the **proxy** (i.e., not self-invoked inside the same bean) or it runs synchronously and the test \"passes\" for the wrong reason.\n- In a `@WebMvcTest`/sliced test, the real `TaskExecutor` may not be present; supply a synchronous `TaskExecutor` (`SyncTaskExecutor`) for deterministic tests where you want the work done before the response returns, and a real one only when you're specifically testing the async behavior.\n\n## What to avoid\n\n- `Thread.sleep(500)` — flaky under CI load, either too short (fails) or too long (slow suite).\n- Retrying a flaky async test in CI config — that hides a determinism bug; fix the wait mechanism (usually Awaitility or the transaction-visibility issue above).\n\nRule of thumb: **Awaitility on the observable outcome** for fire-and-forget `void` methods; **join the future** when the method returns one; and check whether your test's `@Transactional` rollback is hiding the rows the async thread needs.",
2023
+ "outputSha256": "e540b85c5f29979d5f86ff598cca1d85c63cfdb33af544ced8658da2362bb5ff",
2024
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
2025
+ "deterministic": [],
2026
+ "judge": {
2027
+ "verdict": "pass",
2028
+ "reason": "Names concrete mechanisms: Awaitility await().atMost(...).untilAsserted(...), CompletableFuture .get(5, TimeUnit.SECONDS), and CountDownLatch await(5, SECONDS), all shown in code. Explains determinism: 'bounded and deterministic', retries until pass or timeout, not dependent on task duration. Thread.sleep only warned against ('never Thread.sleep', 'What to avoid')."
2029
+ },
2030
+ "passed": true
2031
+ },
2032
+ {
2033
+ "output": "# Waiting on an `@Async` method in a Spring Boot test\n\nDon't reach for `Thread.sleep`. Poll deterministically with Awaitility, or block on a `Future`/latch you actually control. Here's the decision, in order of preference.\n\n## 1. Return a `Future`/`CompletableFuture` and join on it (best)\n\nIf the `@Async` method returns anything, the cleanest fix is to make the test consume the returned future rather than guess a duration:\n\n```java\n@Test\nvoid process_persistsResult() {\n CompletableFuture<Order> future = orderService.processAsync(orderId);\n\n Order order = future.join(); // deterministic: completes exactly when the async work does\n\n assertThat(order.getStatus()).isEqualTo(Status.PROCESSED);\n}\n```\n\n`Future.get()` or `CompletableFuture.join()` completes exactly when the work finishes — no arbitrary timeout, no flake. This is the option to prefer whenever the method under test gives you a handle back.\n\nThis only works if the test controls the invocation. If the `@Async` call originates *inside* another method (fire-and-forget, e.g. an event listener or a `@TransactionalEventListener(phase = AFTER_COMMIT)` handler), you have no `Future` — go to option 2.\n\n## 2. Awaitility for fire-and-forget / event-driven async\n\nWhen the async work is triggered internally and you only have an observable side effect (an audit row, a status column, a repository save), poll for it:\n\n```java\n@Test\nvoid orderPlaced_publishesAuditEvent() {\n orderService.placeOrder(orderId);\n\n await().atMost(Duration.ofSeconds(5))\n .pollInterval(Duration.ofMillis(50))\n .untilAsserted(() -> {\n AuditRecord record = auditRepository.findByOrderId(orderId);\n assertThat(record).isNotNull();\n assertThat(record.getType()).isEqualTo(\"ORDER_PLACED\");\n });\n}\n```\n\n`Awaitility` fails with a clear message if the condition never holds within the budget, instead of a confusing assertion failure after a too-short sleep.\n\n## 3. `CountDownLatch` when you control both ends\n\nIf the async code can signal completion, a latch is the lowest-fashion, most reliable option:\n\n```java\n// test\nCountDownLatch latch = new CountDownLatch(1);\norderService.processAsync(orderId, latch::countDown);\nassertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();\n```\n\nUseful when the method's signature is awkward to change but you can pass a callback.\n\n## Two things that will bite you regardless of the wait mechanism\n\n**Use `@SpyBean`/`@MockitoBean`, not a hand-built mock, if you need the async method to actually run.** Mocking out the `@Async` method removes the very behavior you're waiting for.\n\n**Don't let `@Async` stay on in a slice test by accident.** If your test is a `@SpringBootTest` that doesn't actually intend to exercise the executor thread pool — e.g. you just want the transaction to commit — consider overriding the executor with a synchronous one (`SyncTaskExecutor`) via a test `@Configuration`, so the test becomes deterministic without any waiting at all. Reserve the real async executor for the tests that specifically verify the async path.\n\n```java\n@TestConfiguration\nstatic class SyncAsyncConfig implements AsyncConfigurer {\n @Override\n public Executor getAsyncExecutor() {\n return new SyncTaskExecutor(); // @Async methods now run inline\n }\n}\n```\n\n## What to avoid\n\n- `Thread.sleep(n)` / Kotlin `delay(n)` — non-deterministic under CI load, and the exact thing the associated rules flag as a red flag. Don't add a retry loop in CI to paper over a flake either; that hides a real determinism bug.\n- Asserting on a value the async thread wrote without synchronizing with it first — a race, not a test.\n\n**Rule of thumb:** if you can get a `Future` back, join it. If it's fire-and-forget, Awaitility on the observable side effect. If the test doesn't actually need to verify the async boundary, swap in a synchronous executor and skip the wait entirely.",
2034
+ "outputSha256": "0247e803a40081414edb21d8780d7237b6d8efe2097516d92be0877132191bcf",
2035
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
2036
+ "deterministic": [],
2037
+ "judge": {
2038
+ "verdict": "pass",
2039
+ "reason": "Answer concretely shows CompletableFuture.join() (option 1), Awaitility await().atMost(...).untilAsserted(...) (option 2), and CountDownLatch latch.await(...) (option 3). It explains determinism: join 'completes exactly when the work finishes — no arbitrary timeout, no flake'; Awaitility polls the side effect. Thread.sleep is only warned against in 'What to avoid', not recommended."
2040
+ },
2041
+ "passed": true
2042
+ },
2043
+ {
2044
+ "output": "Don't use `Thread.sleep`. Depending on what \"finish\" means for your `@Async` method, pick one of these:\n\n**1. `Awaitility` (best default when you're asserting on a side effect)**\n\nIf the async method writes to a database, sends to a captured mock, or otherwise produces an observable side effect you then assert against, poll for that side effect instead of guessing a sleep duration:\n\n```java\nawait().atMost(Duration.ofSeconds(3))\n .pollInterval(Duration.ofMillis(50))\n .untilAsserted(() -> verify(emailClient).send(...));\n```\n\nThis is the recommended replacement for \"sleep then assert\" and is already a dependency in projects that have it declared — check `pom.xml`/`build.gradle(.kts)` before adding it.\n\n**2. Replace the executor with a synchronous one (best when the test isn't actually testing async behavior)**\n\nIf the test's real purpose is the business logic inside the method, not the asynchrony itself, don't run it on a real thread pool at all. In a `@SpringBootTest`, override the task executor for the test context:\n\n```java\n@TestConfiguration\nstatic class SyncAsyncConfig {\n @Bean\n AsyncTaskExecutor applicationTaskExecutor() {\n // e.g. a SyncTaskExecutor, so @Async runs inline on the caller thread\n return new SyncTaskExecutor();\n }\n}\n```\n\nThen the method has already completed by the time your assertion runs — no wait at all. Note `SyncTaskExecutor` doesn't implement `AsyncTaskExecutor` directly in every Boot version; if the bean type doesn't line up, use `new org.springframework.core.task.SyncTaskExecutor()` wrapped appropriately, or subclass `TaskExecutorAdapter`. In a plain unit test (no container), you can also just call the underlying method directly with mocks instead of going through the proxy — the `@Async` annotation only takes effect through the Spring proxy anyway, so if you constructed the class yourself with a mocked collaborator, the call is synchronous already.\n\n**3. `CompletableFuture` join (if the method returns one)**\n\nIf the `@Async` method's signature returns `CompletableFuture<T>`, capture that future and call `.join()` (or `.get(timeout, unit)`) before asserting — deterministic, no polling.\n\n**4. `CountDownLatch` (when the method signals completion itself)**\n\nIf the async method can be given a latch to count down, await it with a bounded timeout: `assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue();`. Bounding the timeout matters — an unbounded `await()` turns a broken test into a hung CI job.\n\n**What to avoid:** `Thread.sleep(...)`/`delay(...)` before the assertion. It's the classic flake source — too short and it fails under load, too long and the suite crawls, and either way it's non-deterministic. If you find yourself wanting a retry loop in the test or CI config to paper over an intermittent failure, that's the same bug in disguise: the real fix is a proper wait mechanism (option 1), a synchronous executor (option 2), or a joinable handle (option 3/4).\n\nAlso pick the right test slice first: if this is a controller test, use `@WebMvcTest` with the service mocked (via `@MockitoBean` on Boot 3.4+, `@MockBean` only if the project is pinned below that) — in that case the \"async\" call is just a mock interaction and Awaitility's `untilAsserted(() -> verify(...))` is all you need.",
2045
+ "outputSha256": "2829b2933dcd9f7c90b070a04356643c0e6b5c1a5f680ca80cd86e776ca6d34e",
2046
+ "promptSha256": "2ad15e24657aacf12e7459026d00c41bef8c33db17872effff34b483ef2596b1",
2047
+ "deterministic": [],
2048
+ "judge": {
2049
+ "verdict": "pass",
2050
+ "reason": "Names concrete mechanisms: Awaitility await().atMost(...).untilAsserted(...) with code, CompletableFuture .join()/.get(timeout), and CountDownLatch latch.await(3, SECONDS). Explains determinism: 'too short and it fails under load... non-deterministic' vs proper wait. Thread.sleep only warned against, not recommended."
2051
+ },
2052
+ "passed": true
2053
+ }
2054
+ ]
2055
+ },
2056
+ {
2057
+ "id": "narrowest-test-slice",
2058
+ "kind": "behavior",
2059
+ "prompt": "I need to add tests for a new Spring @RestController that calls a service to fetch an order. Should I use @SpringBootTest?",
2060
+ "strictness": "high",
2061
+ "trials": 10,
2062
+ "passes": 10,
2063
+ "passRate": 1,
2064
+ "passAtK": 1,
2065
+ "grader": "judge",
2066
+ "status": "ran",
2067
+ "trialRecords": [
2068
+ {
2069
+ "output": "Not by default — use `@WebMvcTest` for this.\n\nA `@RestController` that delegates to a service is exactly the case the web slice exists for. `@WebMvcTest(OrderController.class)` boots only the web layer (controller, filters, `MockMvc`, message converters, exception handlers) and lets you mock the service so you can assert HTTP-level behavior without dragging the rest of the application context along. Reach for `@SpringBootTest` only when what's under test is genuinely the interaction across the whole wired context — it's much slower for every test class that uses it, and it buys you no extra coverage of the controller's request/response mapping.\n\nConcretely for a \"fetch an order\" endpoint:\n\n- Mock the service (`@MockitoBean` on Spring Boot ≥ 3.4; the old `@MockBean` is deprecated in 3.4 and removed in 4.x) and stub it per test.\n- Use `MockMvc` to assert status code, response body shape, and the mapping of the path variable to the service call.\n- Cover the happy path, the not-found case (service throws/returns empty → expected 404), and any Bean Validation failure on request params/body (expected 400).\n- Assert the controller only — no need to prove the service's business logic here; that belongs in a plain unit test constructed directly with `Mockito.mock(...)`/`@InjectMocks`, no Spring container at all.\n\nOne caveat: read a neighboring controller test's import first. If the project is pinned below Spring Boot 3.4, match its existing `@MockBean` usage rather than introducing `@MockitoBean`; go with whatever slice, mocking, and assertion style is already established there.\n\nAnd when the service is mocked and you want to assert something async-invoked, wait with Awaitility (`await().atMost(...).until(...)`) or a `CountDownLatch`/future join — never `Thread.sleep`.",
2070
+ "outputSha256": "ed09084f6affc3c2f81417c734d09f59d83444991a88475d3cd3c3b00137f3f4",
2071
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2072
+ "deterministic": [],
2073
+ "judge": {
2074
+ "verdict": "pass",
2075
+ "reason": "Recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (crit 1). Mocks service via @MockitoBean, noting @MockBean for pre-3.4 projects (crit 2). Explains @SpringBootTest loads whole context, is slower, and buys no extra coverage of request/response mapping (crit 3). No fail criterion met."
2076
+ },
2077
+ "passed": true
2078
+ },
2079
+ {
2080
+ "output": "No — `@SpringBootTest` is the slowest and broadest slice, and it isn't the right default for a controller. For a `@RestController` that calls a service, use `@WebMvcTest`, which loads only the web layer and lets you mock the service collaborator.\n\nConcretely, for your new controller:\n\n1. **Discover conventions first.** Read `pom.xml`/`build.gradle(.kts)` to confirm which test deps are present (JUnit 5, Mockito/MockK, AssertJ, etc.), locate `src/test/java` or `src/test/kotlin` mirroring the main package, and read 1–2 neighboring test classes to match the project's existing assertion style and slice choice. If the project already standardizes on `@WebMvcTest` for controllers, follow that; don't introduce a different style.\n\n2. **Plan the cases.** For a controller that fetches an order, cover:\n - happy path: valid id → 200 with the expected response body shape,\n - not-found: service returns empty/throws → 404 (or whatever the contract is),\n - validation failure: if the request has path/query params or a body with Bean Validation constraints, assert the expected 400 response,\n - mapping: request params/headers map correctly into the service call.\n\n3. **Write it as a `@WebMvcTest`.** Load only the web layer, use `MockMvc` to exercise request/response mapping, status codes, and validation errors, and mock the service layer. Use `@MockitoBean` (Spring Boot 3.4+); only fall back to the deprecated `@MockBean` if the project is pinned below 3.4. If the controller was designed with constructor injection, the underlying service can also be tested in a plain unit test with `Mockito.mock(...)` passed directly to the constructor — no Spring container at all.\n\n4. **Only reach for `@SpringBootTest`** if the interaction across the whole wired context is genuinely what's under test — it is not a substitute for testing a controller's web layer in isolation.\n\n5. **Run and fix:** `./gradlew test` or `mvn test`, fix failing tests (max ~3 iterations), and fix the test rather than the source — unless the test correctly caught a real bug, in which case report it rather than silently editing production code.\n\nTwo things to avoid while you're at it:\n- Don't wait for anything async with `Thread.sleep`/`delay` — if the controller triggers async work, use Awaitility's `await().atMost(...).until(...)` or a real join/latch.\n- Don't fall back to `@SpringBootTest` \"because it's simpler\" — loading the full context for every controller test slows the suite with no coverage benefit when `@WebMvcTest` already isolates what's under test.\n\nAlso worth noting: this guidance is about a controller. If your new code also adds a repository query for the order lookup, that belongs in a separate `@DataJpaTest` (using Testcontainers if the project already has it wired up), not folded into the web-layer test.\n\nBefore calling it done, verify: the test file sits at the project's convention path and matches its slice/assertion style; `./gradlew test`/`mvn test` exits 0 with everything passing; `git status` shows only test files changed; and no new/changed async wait uses `Thread.sleep`/`delay`.",
2081
+ "outputSha256": "c065b53651392f2512de736b681e052ca61278ee007e0c747cbe0ad6eff84826",
2082
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2083
+ "deterministic": [],
2084
+ "judge": {
2085
+ "verdict": "pass",
2086
+ "reason": "Answer recommends @WebMvcTest with MockMvc over @SpringBootTest (step 3), mocks the service via @MockitoBean (falling back to @MockBean below 3.4), and gives the concrete reason: @SpringBootTest is the slowest/broadest slice, loads the full context, and is not a substitute for isolating the web layer (steps 1, 4, 5)."
2087
+ },
2088
+ "passed": true
2089
+ },
2090
+ {
2091
+ "output": "No — for a controller, prefer `@WebMvcTest`.\n\n`@WebMvcTest` loads only the web layer (controllers, `@ControllerAdvice`, filters, converters, `MockMvc` auto-config), so it spins up fast and isolates what you're actually testing: request mapping, status codes, JSON body shape, and Bean Validation errors. The service collaborator gets mocked (`@MockitoBean`, since `@MockBean` is deprecated as of Boot 3.4 / removed in 4.x), so you're not dragging the whole application context and its DB, security, and external clients along.\n\n`@SpringBootTest` loads the entire wired context. It's justified when the interaction *across* several layers is genuinely what's under test — not for verifying a single controller's request/response mapping. Reaching for it by default makes the suite slower for no extra coverage.\n\nWhat this would look like (Java, JUnit 5):\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired MockMvc mockMvc;\n @MockitoBean OrderService orderService;\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n given(orderService.getOrder(42L)).willReturn(new OrderDto(42L, \"PAID\"));\n\n mockMvc.perform(get(\"/orders/42\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"PAID\"));\n }\n\n @Test\n void returns400WhenIdIsNotAPositiveNumber() throws Exception {\n mockMvc.perform(get(\"/orders/0\"))\n .andExpect(status().isBadRequest());\n }\n}\n```\n\nA few things to check before writing it:\n\n- **Read the neighboring test classes first.** If the project already standardizes on something else, match that rather than introducing a new style.\n- **Then decide where the service logic itself gets tested.** If `OrderService` has real business logic, test it as a plain unit test with mocked collaborators, constructed directly via constructor injection (Mockito `@Mock`/`@InjectMocks`) — no Spring container needed. The `@WebMvcTest` above covers the controller; don't let it become a stand-in for testing the service too.\n- **Validation cases matter here.** Assert that a Bean Validation constraint violation actually returns the expected 400, not just the happy path.\n- **No `Thread.sleep` if anything downstream is async** — use Awaitility or a proper join/latch.\n- **Only touch test files.** Don't modify the controller to make it easier to test; if the test surfaces a real bug, report it instead of silently changing production code.",
2092
+ "outputSha256": "a427d7710e61fe9b8bda68afb0e4d174c2f3d2457400532161a787529e687aff",
2093
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2094
+ "deterministic": [],
2095
+ "judge": {
2096
+ "verdict": "pass",
2097
+ "reason": "Answer recommends @WebMvcTest with MockMvc over @SpringBootTest (criterion 1), mocks the service via @MockitoBean (criterion 2), and explains @SpringBootTest loads the entire wired context and is slower with no extra coverage for a single controller's request/response mapping (criterion 3). No fail criterion holds."
2098
+ },
2099
+ "passed": true
2100
+ },
2101
+ {
2102
+ "output": "No — for a `@RestController` that just delegates to a service, use `@WebMvcTest`, not `@SpringBootTest`.\n\n**Why not `@SpringBootTest`:** it loads the entire application context (every bean, every auto-configuration, the whole persistence layer if one exists) just to test request mapping and status codes on one controller. That's the slowest possible way to test the thinnest layer. Reserve `@SpringBootTest` for cases where you genuinely need the fully wired context to verify cross-bean interaction — not for \"does this endpoint return the right JSON.\"\n\n**What to do instead:**\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockitoBean // @MockBean only if pinned below Boot 3.4\n private OrderService orderService;\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n given(orderService.getOrder(42L))\n .willReturn(new OrderResponse(42L, \"SHIPPED\"));\n\n mockMvc.perform(get(\"/orders/42\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"SHIPPED\"));\n }\n\n @Test\n void returns404WhenServiceThrowsNotFound() throws Exception {\n given(orderService.getOrder(99L))\n .willThrow(new OrderNotFoundException(99L));\n\n mockMvc.perform(get(\"/orders/99\"))\n .andExpect(status().isNotFound());\n }\n}\n```\n\n`@WebMvcTest` loads only the web slice, so the service is mocked and the test runs in a fraction of the time.\n\n**Before you write it, check Step 1 of the skill's workflow:** read a neighboring test class. If the project already standardizes on `@SpringBootTest` for controllers (some do, for reasons of their own), match that instead — consistency with the existing suite beats the general rule. But assuming you're choosing, `@WebMvcTest` is correct here.\n\n**A couple of things worth covering for this endpoint specifically:**\n\n- The happy path and the not-found path (mapped to 404, per above).\n- Any path/query `@Valid` constraints, asserting a 400 with the expected error shape.\n- If the service method is `@Transactional` and the controller calls a *self-invoking* path, that boundary won't be exercised by a `@WebMvcTest` — that belongs in a service-level test or a `@DataJpaTest`, not here.\n\nAnd the test should assert on status, body shape, and validation failures — not just \"the method didn't throw.\"",
2103
+ "outputSha256": "9535d8cf0ec498d931685d61827b7fd2070f1f0952f80a1b73d17bc10530ef3c",
2104
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2105
+ "deterministic": [],
2106
+ "judge": {
2107
+ "verdict": "pass",
2108
+ "reason": "Answer recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (criterion 1), mocks OrderService via @MockitoBean (noting @MockBean below Boot 3.4) (criterion 2), and explains @SpringBootTest loads the entire application context and is the slowest way to test the thinnest layer (criterion 3). No fail criterion holds."
2109
+ },
2110
+ "passed": true
2111
+ },
2112
+ {
2113
+ "output": "Short answer: no — use `@WebMvcTest`, not `@SpringBootTest`.\n\n**Why:**\n- Your test target is the controller's web-layer behavior: request mapping, path/query param binding, JSON (de)serialization, status codes, and validation. `@WebMvcTest(OrderController.class)` loads exactly that slice and mocks the service, so nothing below the web layer spins up.\n- `@SpringBootTest` loads the *entire* application context (all your beans, data sources, security, etc.) for every test class. It's slower, more brittle, and gives you no extra coverage of the controller itself.\n\n**Shape of the test (Java, JUnit 5 + MockMvc):**\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired MockMvc mockMvc;\n @MockitoBean OrderService orderService; // use @MockBean only if Spring Boot < 3.4\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n given(orderService.getOrder(42L)).willReturn(new OrderDto(42L, \"SHIPPED\"));\n\n mockMvc.perform(get(\"/orders/{id}\", 42L))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"SHIPPED\"));\n }\n\n @Test\n void returns404WhenMissing() throws Exception {\n given(orderService.getOrder(99L)).willThrow(new OrderNotFoundException(99L));\n\n mockMvc.perform(get(\"/orders/{id}\", 99L))\n .andExpect(status().isNotFound());\n }\n}\n```\n\n**Note on `@MockitoBean`:** it's the current annotation. `@MockBean` (and `@SpyBean`) are deprecated as of Spring Boot 3.4 and removed in 4.x. Check your `pom.xml`/`build.gradle` — if the project is pinned **below** 3.4, use `@MockBean` instead so it compiles; otherwise use `@MockitoBean`. Match whatever the neighboring controller tests in the repo already use.\n\n**Things worth covering beyond the happy path:**\n- Validation failure (e.g. malformed `id`) returning the expected 400 — assert on the error body shape if the project has a `@ControllerAdvice` handler.\n- The 404/not-found mapping if the service throws a domain exception.\n- A weak spot to avoid: if the controller *is* the only thing mocked out, make sure the service is always the mock — don't let `@WebMvcTest` accidentally pull in the real `OrderService`.\n\n**When `@SpringBootTest` *would* be justified:** only if what you're actually verifying is the wiring *across* controller → service → repository all the way through, end-to-end, and even then you'd more likely write that as a small number of `@SpringBootTest` + `MockMvc` integration tests rather than the primary controller test. For \"the controller calls the service and formats the response,\" `@WebMvcTest` is the right slice.\n\nOne more option worth considering: if the controller is genuinely thin (just delegation plus serialization), you may not need a Spring test at all for the service itself — test `OrderService` as a plain JUnit 5 + Mockito unit test (no Spring container), and reserve the `@WebMvcTest` for the controller's HTTP contract.",
2114
+ "outputSha256": "02ba151ea4eab50ceb9d4e473e6aeafafff79f62fa91ced64e7a57bec69f8428",
2115
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2116
+ "deterministic": [],
2117
+ "judge": {
2118
+ "verdict": "pass",
2119
+ "reason": "Answer recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (criterion 1). It mocks the service via @MockitoBean, noting @MockBean only for Spring Boot <3.4 (criterion 2). It states @SpringBootTest loads the entire application context, is slower/brittle, and gives no extra controller coverage (criterion 3). No fail criterion holds."
2120
+ },
2121
+ "passed": true
2122
+ },
2123
+ {
2124
+ "output": "Not by default — use `@WebMvcTest` for a controller test.\n\n`@WebMvcTest` loads only the web layer (controllers, filters, `@ControllerAdvice`, message converters) and skips the service/repository/persistence wiring entirely. You mock the service with `@MockitoBean` (Spring Boot 3.4+; `@MockBean` only if the project is pinned below 3.4, since it's deprecated in 3.4 and removed in 4.x) and drive the controller through `MockMvc`. That is faster and, more importantly, isolates exactly the thing under test: request mapping, path/query param binding, status codes, response body shape, and Bean Validation failure behavior (assert a bad request returns the expected 400).\n\nReach for `@SpringBootTest` only when the cross-context interaction is genuinely what you're testing — it starts the whole application context and is the slowest option. For a controller-plus-mocked-service test it adds cost with no coverage benefit.\n\nPlan the cases as:\n\n- happy path: service returns the order → assert 200, response body fields\n- not-found path: service returns empty/throws the not-found exception → assert the mapped status (404) and error body\n- validation failure: invalid input → assert 400 and the expected error payload, if you have request validation\n- if the service call is async anywhere in the chain, assert with Awaitility (`await().atMost(...).until(...)`) rather than `Thread.sleep`\n\nOne caveat before you write it: check a neighboring controller test and `pom.xml`/`build.gradle(.kts)` first for the project's existing conventions — assertion library (AssertJ vs. plain JUnit), whether they use `@MockitoBean` or the older `@MockBean`, and how they build fixtures. Match those rather than introducing a different style.\n\nFull guidance lives in `rules/testing.mdc`; the workflow above (discover conventions → pick the narrowest slice → write → run/fix → report) is the same one to follow, and the hard rule there is to only touch test files, never the controller or service under test.",
2125
+ "outputSha256": "df26c902fcd6d23f40df97f31a014a37f8bf8a6f963e58ed7a4485e86d7cdf26",
2126
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2127
+ "deterministic": [],
2128
+ "judge": {
2129
+ "verdict": "pass",
2130
+ "reason": "Answer recommends @WebMvcTest with MockMvc over @SpringBootTest (\"use `@WebMvcTest` for a controller test\"), mocks the service via @MockitoBean (with @MockBean fallback below 3.4), and gives the concrete reason: @SpringBootTest \"starts the whole application context and is the slowest option\" adding cost with no coverage benefit for controller+mocked-service tests."
2131
+ },
2132
+ "passed": true
2133
+ },
2134
+ {
2135
+ "output": "No — not by default. For a new `@RestController` that just delegates to a service to fetch an order, `@WebMvcTest` is the right slice.\n\n**Why:** `@SpringBootTest` loads the entire application context (all beans, datasource, security config, etc.) just to exercise request/response mapping on one controller. `@WebMvcTest(OrderController.class)` loads only the web layer — MockMvc, Jackson, Bean Validation, controller advice — which is exactly the surface you're testing.\n\n**What to do instead:**\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockitoBean\n private OrderService orderService; // Spring Boot 3.4+; @MockBean if pinned below\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n given(orderService.getOrder(42L)).willReturn(new OrderResponse(42L, \"SHIPPED\"));\n\n mockMvc.perform(get(\"/api/orders/42\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"SHIPPED\"));\n }\n\n @Test\n void returns404WhenServiceThrowsNotFound() throws Exception {\n given(orderService.getOrder(99L)).willThrow(new OrderNotFoundException(99L));\n\n mockMvc.perform(get(\"/api/orders/99\"))\n .andExpect(status().isNotFound());\n }\n}\n```\n\n**Keep in mind:**\n\n- `@MockitoBean` is the current annotation (Boot 3.4+); `@MockBean` is deprecated and removed in Boot 4.x. Match whatever the project already uses in neighboring tests.\n- The service is mocked here, so this tests the controller's mapping and error handling — not the real fetch logic. That logic belongs in its own plain unit test for `OrderService` (construct it directly with mocked collaborators, no Spring container), and any query behavior belongs in a `@DataJpaTest`.\n- Only reach for `@SpringBootTest` if what you're actually verifying is cross-cutting wiring that a slice can't reproduce — that's not the case for a thin delegating controller.\n\nRule of thumb: pick the narrowest slice that covers the thing under test. Here that's `@WebMvcTest`.",
2136
+ "outputSha256": "f1e7edc787dd27c69a3eb1f582932bba288ade3be60667393275857c35870aee",
2137
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2138
+ "deterministic": [],
2139
+ "judge": {
2140
+ "verdict": "pass",
2141
+ "reason": "Answer recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (criterion 1), mocks the service via @MockitoBean (with @MockBean note for pre-3.4) (criterion 2), and explains @SpringBootTest loads the entire application context (all beans, datasource, security) just to exercise request/response mapping on one controller (criterion 3). No fail criterion holds."
2142
+ },
2143
+ "passed": true
2144
+ },
2145
+ {
2146
+ "output": "No — for a `@RestController` that delegates to a service, `@SpringBootTest` is the wrong tool. Use `@WebMvcTest` instead.\n\n## Why not @SpringBootTest\n\n`@SpringBootTest` boots the entire application context — every bean, every repository, every third-party autoconfiguration. For testing a single controller's request/response mapping, that's a lot of startup cost for zero additional coverage. Your controller's job is thin: map HTTP in, call the service, map HTTP out. The web slice is exactly what you want to isolate.\n\n## What to use instead\n\n**`@WebMvcTest(OrderController.class)`** loads only the web layer (controller + Spring MVC machinery: message converters, validation, exception handlers). It does not load your service, repositories, or data source. Mock the service so the test is fast and deterministic.\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockitoBean // NOT @MockBean -- deprecated since Boot 3.4, gone in 4.x\n private OrderService orderService;\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n when(orderService.getOrder(42L))\n .thenReturn(new OrderResponse(42L, \"SHIPPED\"));\n\n mockMvc.perform(get(\"/orders/42\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"SHIPPED\"));\n }\n\n @Test\n void returns404WhenMissing() throws Exception {\n when(orderService.getOrder(42L))\n .thenThrow(new OrderNotFoundException(42L));\n\n mockMvc.perform(get(\"/orders/42\"))\n .andExpect(status().isNotFound());\n }\n}\n```\n\nTwo notes on the snippet above:\n\n- Use `@MockitoBean`. Spring Boot's own `@MockBean` is deprecated as of Boot 3.4 and removed in 4.x. Only reach for `@MockBean` if the project is pinned below 3.4 — check the dependency version first.\n- Kotlin projects would use MockK (`mockk<OrderService>()` with `every { ... }`) and `MockMvc` the same way.\n\n## Cases worth covering\n\n- Happy path: 200 + response body shape (`jsonPath` on the fields that matter).\n- Not-found / error path: the exception maps to the expected status (404, etc.).\n- **Bean Validation**: if the request has `@Valid`/constraints, assert a constraint violation comes back as a 400 with the expected error body. This is the one thing `@WebMvcTest` uniquely verifies and a plain unit test cannot.\n\n## If the project doesn't already use slices\n\nMatch whatever the neighboring test classes do — read one or two first. If the codebase is uniformly `@SpringBootTest`, flag the narrower-slice option rather than silently introducing an inconsistent style. But by default, for a controller whose only job is to call a service, `@WebMvcTest` is the narrowest slice that covers what's under test, and it's the right default.\n\nSave `@SpringBootTest` for when the thing under test genuinely is the wiring across the whole context — end-to-end configuration, cross-bean transactions, security filter chains spanning layers — not for exercising one controller's routing and serialization.",
2147
+ "outputSha256": "d9766a0a7f863fb86e0dcdd0dc17fd5fad2501ee9cf9ce821131e82e4b5eb445",
2148
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2149
+ "deterministic": [],
2150
+ "judge": {
2151
+ "verdict": "pass",
2152
+ "reason": "Answer recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (criterion 1). It mocks the service via @MockitoBean, noting @MockBean only for projects pinned below 3.4 (criterion 2). It explains @SpringBootTest boots the entire application context, adding startup cost for zero coverage of a thin controller's request/response mapping (criterion 3). No fail criterion holds."
2153
+ },
2154
+ "passed": true
2155
+ },
2156
+ {
2157
+ "output": "Short answer: no, not for testing the controller itself. Use `@WebMvcTest`.\n\nHere is the reasoning, plus what the resulting test should actually look like.\n\n## Why not `@SpringBootTest`\n\n`@SpringBootTest` boots the entire application context — every bean, every auto-configuration, every datasource, every client. That is the slowest possible way to verify request/response mapping on a controller. If all you are testing is \"does this controller map the request, delegate to the service, and return the right status and body,\" the web layer is the only thing under test, so load only the web layer.\n\n`@WebMvcTest(OrderController.class)` loads just the MVC slice — the controller, the `MockMvc` machinery, message converters, and validation. The service is not in the context, so you supply a mock.\n\nReserve `@SpringBootTest` for the case where the interaction across the whole wired context is genuinely what is under test (e.g. verifying that a filter and a controller and a service actually cooperate end to end). It is a complement to a `@WebMvcTest`, not a replacement.\n\n## The slice to pick\n\n| What you are testing | Slice |\n|---|---|\n| Controller mapping, status codes, validation → 400 | `@WebMvcTest` + `MockMvc` |\n| Repository query methods, `@EntityGraph` avoiding N+1 | `@DataJpaTest` |\n| A service's business logic with mocked collaborators | Plain JUnit 5, construct the class directly |\n| Genuine cross-cutting wiring | `@SpringBootTest` |\n\nFor a new controller, that first row is you.\n\n## A `@WebMvcTest` for your case\n\n```java\n@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockitoBean\n private OrderService orderService;\n\n @Test\n void returnsOrderWhenFound() throws Exception {\n when(orderService.findOrder(42L))\n .thenReturn(new OrderResponse(42L, \"SHIPPED\"));\n\n mockMvc.perform(get(\"/orders/42\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.id\").value(42))\n .andExpect(jsonPath(\"$.status\").value(\"SHIPPED\"));\n\n verify(orderService).findOrder(42L);\n }\n\n @Test\n void returnsBadRequestWhenIdIsInvalid() throws Exception {\n mockMvc.perform(get(\"/orders/-1\"))\n .andExpect(status().isBadRequest());\n }\n}\n```\n\nTwo notes on API choice:\n\n- Use `@MockitoBean`, not `@MockBean`. `@MockBean` was deprecated in Spring Boot 3.4 and removed in 4.x. Only fall back to `@MockBean` if the project is pinned below 3.4 — check `pom.xml`/`build.gradle` first.\n- In Kotlin, the same test works with MockK (`mockk<OrderService>()` plus `every { ... } returns ...`), but you still need `@MockitoBean` for the Spring-injected mock, since MockK is not a Mockito extension.\n\n## Before you write it\n\nRead one or two neighboring test classes and the build file:\n\n- **Layout and naming** — `src/test/java` vs `src/test/kotlin`, and whether the project uses `<Type>Test` or `<Type>IT`.\n- **Assertion style already in use** — AssertJ, JUnit assertions, or MockMvc's `jsonPath`. Match it rather than introducing a third style.\n- **Whether the project already has a fixture/builder for `OrderResponse`** — reuse it instead of hand-rolling a new one.\n- **Which Spring Boot version is pinned**, which decides `@MockitoBean` vs `@MockBean`.\n\n## What to cover for this controller\n\n- Happy path: service returns the order → 200 with correct body.\n- Not found → whatever your controller does (usually 404).\n- Bean Validation failure on the path/query param → 400.\n- If the controller is the boundary that maps a domain exception to an HTTP status, assert that mapping explicitly.\n\n## What not to do\n\n- Do not use `@SpringBootTest` \"because it is simpler than picking a slice.\" It loads the whole context for every class and slows the suite for no coverage gain here.\n- Do not swap in an H2 database to make the slice \"work\" — you should not need a database in a `@WebMvcTest` at all; the service is mocked.\n- Do not wait for anything with `Thread.sleep`/`delay` if this endpoint turns out to be async — use Awaitility's `await().atMost(...).until(...)` or a `CompletableFuture` join. (Not applicable to a plain synchronous fetch, but worth flagging early.)\n\nBottom line: `@WebMvcTest` with a mocked service verifies exactly what a controller test is for — routing, serialization, status codes, and validation — while staying fast and isolated. Reach for `@SpringBootTest` only if you later need to prove the full wiring works together.",
2158
+ "outputSha256": "5b3b53b3a04e3b25346fafa9e8c759db3ca2ce527e19bb339624b41462d280e0",
2159
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2160
+ "deterministic": [],
2161
+ "judge": {
2162
+ "verdict": "pass",
2163
+ "reason": "Answer recommends @WebMvcTest(OrderController.class) with MockMvc over @SpringBootTest (criterion 1), mocks the service via @MockitoBean with a code example and notes @MockBean only below 3.4 (criterion 2), and explains @SpringBootTest boots the entire application context and is the slowest way to verify request/response mapping (criterion 3). No fail criterion holds."
2164
+ },
2165
+ "passed": true
2166
+ },
2167
+ {
2168
+ "output": "No — `@SpringBootTest` is the wrong default here. For a new `@RestController` that just delegates to a service, use `@WebMvcTest` with `MockMvc` and mock the service with `@MockitoBean`.\n\nWhy not `@SpringBootTest`:\n\n- It boots the entire application context (all beans, all auto-configuration) when all you actually need is the web layer plus a mocked service. It's the slowest option and gives you no extra coverage for a controller that just calls one service.\n- `@WebMvcTest(YourController.class)` loads only the web slice, so it starts fast and still exercises everything that matters for a controller: request mapping, path/query/body binding, JSON serialization, status codes, and Bean Validation error responses.\n\nThe shape to write:\n\n- `@WebMvcTest(YourController.class)` on the class.\n- Inject `MockMvc` (via `@Autowired` or the `MockMvcTester` style if the project already uses it).\n- Mock the service with `@MockitoBean` so the controller's dependency is satisfied without loading the real service/DB/etc.\n\nOne important caveat on the mocking annotation: `@MockBean` is deprecated as of Spring Boot 3.4 and removed in 4.x. Use `@MockitoBean`. Only fall back to `@MockBean` if the project is pinned to a Spring Boot version below 3.4. Check the project's `pom.xml`/`build.gradle(.kts)` to confirm before choosing.\n\nCases to cover in that `@WebMvcTest`:\n\n- Happy path: `GET` of an existing order returns 200 and the expected JSON body (assert status, body shape/fields, and that the service was called with the right args).\n- Not-found / error mapping: service throws (or returns empty), assert the expected status (e.g. 404) and body.\n- Validation failure: if the request carries a `@Valid` body or constrained params, assert a 400 with the expected error payload.\n\nIf the \"fetch an order\" logic itself (mapping, N+1 avoidance, transaction boundaries, etc.) needs testing, that belongs in a plain unit test for the service — construct it directly with Mockito mocks — and, if it hits a real query, a `@DataJpaTest` for the repository. Keep the controller test to the web layer. So the common split is three tests, not one:\n\n- `OrderControllerTest` — `@WebMvcTest` + MockMvc + `@MockitoBean` service.\n- `OrderServiceTest` — plain JUnit 5 + Mockito, service constructed directly.\n- `OrderRepositoryTest` — `@DataJpaTest` (Testcontainers if the project already uses it), only if there's real query logic to verify.\n\nReach for `@SpringBootTest` only if what you're genuinely testing is the wiring across the whole context, which is not the case for a thin delegating controller.\n\nTwo rules to hold to while writing these: don't modify the controller/service source — only add test files — and don't wait on any async work with `Thread.sleep`/`delay`; use Awaitility or a real `join`/`CountDownLatch`. Before reporting done, run `./gradlew test` (or `mvn test`), confirm it exits 0 with the new tests passing, and confirm `git status` shows only test files changed.",
2169
+ "outputSha256": "738abfe666931fed83c8a5f7677b85c182933a7a7d80c9bf7fa70970c9849e9f",
2170
+ "promptSha256": "a3dbf31530112571a7ac46581e3cc6672584c3670fb8e52ac524cdac3f30051f",
2171
+ "deterministic": [],
2172
+ "judge": {
2173
+ "verdict": "pass",
2174
+ "reason": "Answer recommends @WebMvcTest with MockMvc over @SpringBootTest, mocks the service via @MockitoBean (with @MockBean fallback only below Boot 3.4), and gives the concrete reason: @SpringBootTest boots the entire application context, is slowest, and adds no coverage for a thin delegating controller. All pass criteria hold; no fail criterion."
2175
+ },
2176
+ "passed": true
2177
+ }
2178
+ ]
2179
+ }
2180
+ ],
2181
+ "verdict": "fail",
2182
+ "scope": "bundled",
2183
+ "skillDigest": "08595de3fd98a937595623c25dd7b567d86e5b6f6a55adc0a4e3b6063a6b80f4",
2184
+ "catalogDigest": "58f55f0866f2076d0959c6ddb3326a6ed904e5fb06de729cf465181d86b85e90",
2185
+ "judgePromptVersion": "2026-09-25.1",
2186
+ "runner": "deepseek",
2187
+ "model": "deepseek-chat",
2188
+ "runnerPromptVersion": "2026-09-25.1",
2189
+ "recordedAt": "2026-09-25T22:08:09.417Z",
2190
+ "judge": "deepseek",
2191
+ "judgeModel": "deepseek-chat"
2192
+ }
2193
+ ]
2194
+ }