Only pages written to the swap count now: they change the blocks the mirror and the backup carry.
The swap tile becomes a card between RAM and the disks, scaled against the critical threshold.
The thresholds are metrics.swapOut…MibPerSecond; the swapTraffic… names are still read, with a warning.
From 1100 px the cards stand in one row and the tiles in another.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OOM kills series is counted: its buckets hold the sum, and the history API serves the range total.
The System page shows it as a tile filled by werkator.js for the selected range.
The eight tiles stand in two rows of four, on a phone in four rows of two.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; review requested from mi; merge scheduled for when the checks succeed
ORANGE 2 finding(s) that Jev rates as probably real and severe: src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt:75-84, src/main/kotlin/de/hoennig/werkator/server/SystemApiController.kt:136-145
hint writing rules violated in the changed docs: .claude/skills/architecture/SKILL.md:123: more than one sentence on a line; .claude/skills/architecture/SKILL.md:123: line longer than 400 characters (5756)
hint 10 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
hint 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
hint 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
Findings
Misleading field name at src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt:75-84, severity unknown, counted as medium, ORANGE, as a line comment
Inconsistent API semantics at src/main/kotlin/de/hoennig/werkator/server/SystemApiController.kt:136-145, severity unknown, counted as medium, ORANGE, as a line comment
6 of 8 units passed all questions (1 PR document(s)).
2 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 68591, calls 1, tokens in 65872 / out 10559 (reasoning included in out), duration 133.0 s, cost 0.0324 USD (reported), classifier `jev-1.13.0` calls 8, tokens in 6436, cost 0.0003 USD, finding judge `jev-1.13.0` calls 2, tokens in 4267, cost 0.0002 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82739 / out 860, cost 0.0108 USD (reported), build file judge `jev-1.13.0` calls 11, tokens in 14182, cost 0.0006 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 20.0 s, model_review 110.7 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).
Reviewed head `5934a37db59d8e1d6909d71b0b8ae428bcb66cf5` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
<!-- werkrevisor:5934a37db59d8e1d6909d71b0b8ae428bcb66cf5 -->
<!-- werkrevisor-verdict:ORANGE -->
<!-- werkrevisor-blocks:f3a206ebd99b12225b42bd896c1d6361,7dfd69509aa1f77e29a77448ad7707c5,a2cfdd3b4d359c0326312db70565d038,dd3aeca035393e23c46290b0ea09bdf5,56d579a241ee4264e584e031fb8c1130,9d737f956c96477880e98c94517cbddc,7e06a869d443ddc8e0cddb1f9d6e10de,2970823d3f76e3c4250d93870fa0cd83,23cebde2b2d07d8ee42477b4a7db5668,0e0ed7cc648588124c13076d45e50b77,6affbdf3f3a72a4cea80d5b21dfc5a1e,ad4cfab060e6940054ec03fa8652245d,6e6f99eb12793bf706249aceb2ad067f,0138d3d5894c9b84dc54330140d4a919,d40267aef979ae9c5313bb9e83558b4b,e7182a944eb07def6ba6dd2ec4602ef7,d39d495fcbc223fe69657e5f861819d2,8082b21c507822e96c7fbba2ec092826,6aebfc5ca62f6067359cea339efb0d65,6704beaa85db07038dead9fd48ea086c,df43835044b78bd1f20f0585374c3b89,27fddd2d47d10514e78cb7fd1a415df8,89dc0cdb5def01fd14ebc0dfd24bd6de,5a8463d1443f3c2d07ae0f1157a5de10 -->
<!-- werkrevisor-folded -->
<details><summary>🟠 werkrevisor ORANGE for <code>5934a37</code>, superseded by <a href="http://git.javagil.de/mi/werkator/pulls/147#issuecomment-3248">the result for <code>9f34c2a</code></a></summary>
## 🟠 werkrevisor: ORANGE / FINDINGS
Change kind: `mixed`.
### Reasons
- **ORANGE** 2 finding(s) that Jev rates as probably real and severe: src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt:75-84, src/main/kotlin/de/hoennig/werkator/server/SystemApiController.kt:136-145
- **YELLOW** spec: docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.01 more_than_one_aspect (0.66 > yellow_above = 0.6)
- **YELLOW** spec: docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.03 more_than_one_aspect (0.72 > yellow_above = 0.6)
- **hint** writing rules violated in the changed docs: .claude/skills/architecture/SKILL.md:123: more than one sentence on a line; .claude/skills/architecture/SKILL.md:123: line longer than 400 characters (5756)
- **hint** 10 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
- **hint** 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
- **hint** 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
### Findings
1. **Misleading field name** at `src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt:75-84`, severity unknown, counted as medium, ORANGE, as a line comment
2. **Inconsistent API semantics** at `src/main/kotlin/de/hoennig/werkator/server/SystemApiController.kt:136-145`, severity unknown, counted as medium, ORANGE, as a line comment
### Classifier
| Unit | Question | Problem | Confidence | Verdict |
|---|---|---|---|---|
| docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.01 | more_than_one_aspect | 0.66 | – | YELLOW |
| docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.03 | more_than_one_aspect | 0.72 | – | YELLOW |
6 of 8 units passed all questions (1 PR document(s)).
2 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 68591, calls 1, tokens in 65872 / out 10559 (reasoning included in out), duration 133.0 s, cost 0.0324 USD (reported), classifier `jev-1.13.0` calls 8, tokens in 6436, cost 0.0003 USD, finding judge `jev-1.13.0` calls 2, tokens in 4267, cost 0.0002 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82739 / out 860, cost 0.0108 USD (reported), build file judge `jev-1.13.0` calls 11, tokens in 14182, cost 0.0006 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 20.0 s, model_review 110.7 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).<br>
Reviewed head `5934a37db59d8e1d6909d71b0b8ae428bcb66cf5` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
</sub>
</details>
werkrevisor: ORANGE / FINDINGS for 5934a37db59d8e1d6909d71b0b8ae428bcb66cf5.
The result comment of werkrevisor holds the details.
werkrevisor: ORANGE / FINDINGS for `5934a37db59d8e1d6909d71b0b8ae428bcb66cf5`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:5934a37db59d8e1d6909d71b0b8ae428bcb66cf5 -->
`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM and swap (`/proc/meminfo`), the swap-out (`pswpout` of `/proc/vmstat` over five minutes, PR#146), the load average (`/proc/loadavg`), the pressure-stall information (`/proc/pressure/cpu`, `/proc/pressure/memory` and `/proc/pressure/io`, PSI `some avg60`), disk, and repository size every 15s, but only after `ServerMetricsLifecycle` (server profile) calls `start()` — like the watcher, nothing is scheduled in CLI runs or tests. Min/max/avg aggregation state persists as JSON in the instance's state home (`${XDG_STATE_HOME:-~/.local/state}/werkator/system-metrics-state.json`, moved there from the artifact root once, PR#33), so restarts continue the series. Unavailable sources (e.g. no `/proc` outside Linux) yield null metrics served as HTTP 200 by `GET /api/system` — the `/system` page shows `n/a`, never an error.
The same state file carries the sample history (`MetricsHistory`, PR#27), three tiers: every 15 s sample for four hours, one point per minute for a day and one per quarter hour for a month, the aggregated tiers with average *and maximum*, column-wise (one timestamp list, one value list per metric key) so the file stays a few hundred kilobytes; a minute is aggregated from the samples when the next one opens, a quarter from the minutes, so no accumulator is persisted, and the file is written once a minute (`PERSIST_EVERY_SAMPLES`) and on shutdown. `GET /api/system/history?range=1h|4h|24h|7d|30d` serves a cut of one tier (samples up to four hours, minutes up to a day, quarters beyond) with its peaks (`HistoryPeaks`: the highest value, when, and the `UtilizationLevel` it reached against the *current* total) — the evidence for a capacity decision. The verdict is computed on the server (`SystemHealth.assess`, `healthy`/`pressure`/`overloaded` with the rules that fired as reasons) and only rendered by the page, so a later executor change can act on the very same rules; CPU utilization alone never counts, waiting does (load above the core count, pressure, swap). `CapacitySizing.of` (PR#30, corrected in PR#42 and PR#58) sizes the machine from the quarter-hour tier. RAM is judged by *waiting*, never by a fill level (RFC 0004): a quarter hour counts as stalled when its average `memoryPressure` reached `SystemHealth.MEMORY_OVERLOAD_PERCENT` — shared, so a machine the health verdict calls healthy cannot be red in the sizing — and RAM is `tight` from 1 % and `upgrade` from 5 % of stalled quarter hours, the share CPU already uses; swap occupancy plays no part. The peak is named in the reason and never judged, with the single exception that it may still prove slack for a downsize — without the 40 % gate CPU and disk keep (PR#79), since the smaller bookable GiB that holds peak, headroom and reserve is the whole proof; it sizes a raise when one is due, rounded up and at least one step above what is booked, because a stall proves the peak was the ceiling. Disk is judged by the level it *holds* — the 95th percentile of the quarter-hour maxima of the last seven days (`DISK_LEVEL_DAYS`), the percentile the CPU watch band already uses, so a single quarter hour is a moment and not a level (PR#78; upgrade from 90 %, `tight` from 80 % where there is something to raise; the recommendation leaves a fifth free, a third for disks) — and by its growth, the median of the daily deltas of the last week, so a step counts on one day and a trend on most, extrapolated thirty days ahead from the level, only from seven days of history on. The window maximum is named in the reason and vetoes a downsize (under 40 %), never asks for a raise. Every size is rounded to what can be booked (`BookableSteps`, a parameter of `of(...)` that the controllers derive from `HostingEnvironments.bookableSteps` — for Hostsharing 10 GiB per quota, 250 GB per volume, 1 GiB RAM; elsewhere the whole unit), and a downsize is advised only when the next bookable size down would still leave the target headroom, otherwise `fits` with a reason saying why. The steps, unlike the hosting itself, reach every viewer: they name nothing, and one machine must not advise two different things depending on who asks. `ioPressure` (PR#69) is sampled and shown like the other two but read by neither the health verdict nor the sizing, in RFC 0004's order — the series has to exist before anyone can say what a number means, and on a shared hive the disks are the part shared hardest, so it will say more about the neighbours than about this instance until somebody has watched it. `oomKills` is sampled into the history from `/proc/vmstat` as the count per bucket but deliberately not read by the sizing — the counter is machine-wide, so on a shared hive it counts every webspace's kills. CPU is sized from the quarter-hour *averages*, never the maxima — waiting (load above the thread count in more than 5 % of the quarters, the 95th percentile of those loads being the recommendation) and, below that, the 95th percentile of sustained utilization for the watch band. A CPU is counted in hardware threads (`CapacitySizing.CPU_UNIT`), which is what `availableProcessors` reports and what a webspace is billed by. RAM is advised in bookable whole GiB: the visible total rounded up is the booked size, and the difference the reserve the host keeps. Nothing is advised from less than a day. Served as `capacity` on `GET /api/system` and shown as colour-coded chips in the verdict panel; where the instance can tell it runs on a Hostsharing Managed Webspace (`HostingEnvironments.detect`, served as `hosting`) the chips carry an order button that composes a German mail to the service desk from the changes the user ticks — the page sends nothing itself. The `/system` page is a verdict line with the sizing chips beneath it, one gauge card per resource (percentage, bar, level pill, inline-SVG sparklin
The same state file carries the sample history (`MetricsHistory`, PR#27), three tiers: every 15 s sample for four hours, one point per minute for a day and one per quarter hour for a month, the aggregated tiers with average *and maximum*, column-wise (one timestamp list, one value list per metric key) so the file stays a few hundred kilobytes; a minute is aggregated from the samples when the next one opens, a quarter from the minutes, so no accumulator is persisted, and the file is written once a minute (`PERSIST_EVERY_SAMPLES`) and on shutdown. `GET /api/system/history?range=1h|4h|24h|7d|30d` serves a cut of one tier (samples up to four hours, minutes up to a day, quarters beyond) with its peaks (`HistoryPeaks`: the highest value, when, and the `UtilizationLevel` it reached against the *current* total) — the evidence for a capacity decision. The verdict is computed on the server (`SystemHealth.assess`, `healthy`/`pressure`/`overloaded` with the rules that fired as reasons) and only rendered by the page, so a later executor change can act on the very same rules; CPU utilization alone never counts, waiting does (load above the core count, pressure, swap). `CapacitySizing.of` (PR#30, corrected in PR#42 and PR#58) sizes the machine from the quarter-hour tier. RAM is judged by *waiting*, never by a fill level (RFC 0004): a quarter hour counts as stalled when its average `memoryPressure` reached `SystemHealth.MEMORY_OVERLOAD_PERCENT` — shared, so a machine the health verdict calls healthy cannot be red in the sizing — and RAM is `tight` from 1 % and `upgrade` from 5 % of stalled quarter hours, the share CPU already uses; swap occupancy plays no part. The peak is named in the reason and never judged, with the single exception that it may still prove slack for a downsize — without the 40 % gate CPU and disk keep (PR#79), since the smaller bookable GiB that holds peak, headroom and reserve is the whole proof; it sizes a raise when one is due, rounded up and at least one step above what is booked, because a stall proves the peak was the ceiling. Disk is judged by the level it *holds* — the 95th percentile of the quarter-hour maxima of the last seven days (`DISK_LEVEL_DAYS`), the percentile the CPU watch band already uses, so a single quarter hour is a moment and not a level (PR#78; upgrade from 90 %, `tight` from 80 % where there is something to raise; the recommendation leaves a fifth free, a third for disks) — and by its growth, the median of the daily deltas of the last week, so a step counts on one day and a trend on most, extrapolated thirty days ahead from the level, only from seven days of history on. The window maximum is named in the reason and vetoes a downsize (under 40 %), never asks for a raise. Every size is rounded to what can be booked (`BookableSteps`, a parameter of `of(...)` that the controllers derive from `HostingEnvironments.bookableSteps` — for Hostsharing 10 GiB per quota, 250 GB per volume, 1 GiB RAM; elsewhere the whole unit), and a downsize is advised only when the next bookable size down would still leave the target headroom, otherwise `fits` with a reason saying why. The steps, unlike the hosting itself, reach every viewer: they name nothing, and one machine must not advise two different things depending on who asks. `ioPressure` (PR#69) is sampled and shown like the other two but read by neither the health verdict nor the sizing, in RFC 0004's order — the series has to exist before anyone can say what a number means, and on a shared hive the disks are the part shared hardest, so it will say more about the neighbours than about this instance until somebody has watched it. `oomKills` is sampled into the history from `/proc/vmstat` as the count per bucket — a counted series (`MetricsHistory.COUNTED_KEYS`, PR#147) whose buckets hold the sum, not the average, and whose range total `GET /api/system/history` serves as `totals` for the OOM kills tile — but deliberately not read by the sizing — the counter is machine-wide, so on a shared hive it counts every webspace's kills. CPU is sized from the quarter-hour *averages*, never the maxima — waiting (load above the thread count in more than 5 % of the quarters, the 95th percentile of those loads being the recommendation) and, below that, the 95th percentile of sustained utilization for the watch band. A CPU is counted in hardware threads (`CapacitySizing.CPU_UNIT`), which is what `availableProcessors` reports and what a webspace is billed by. RAM is advised in bookable whole GiB: the visible total rounded up is the booked size, and the difference the reserve the host keeps. Nothing is advised from less than a day. Served as `capacity` on `GET /api/system` and shown as colour-coded chips in the verdict panel; where the instance can tell it runs on a Hostsharing Managed Webspace (`HostingEnvironments.detect`, served as `hosting`) the chips carry an order button that composes a German mail to the service desk from the change
Misleading field name, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.71, severity 2.6 of 3
The internal Bucket.averages map now stores sums (not averages) for keys in MetricsHistory.COUNTED_KEYS (so far oomKills). The field name no longer matches its content; this propagates into HistoryTier.values["oomKills"], where every consumer now sees per-bucket totals rather than per-sample values. A future reader of Bucket.averages["oomKills"] will assume it is a per-bucket average.
**Misleading field name**, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.71, severity 2.6 of 3
The internal `Bucket.averages` map now stores sums (not averages) for keys in `MetricsHistory.COUNTED_KEYS` (so far `oomKills`). The field name no longer matches its content; this propagates into `HistoryTier.values["oomKills"]`, where every consumer now sees per-bucket totals rather than per-sample values. A future reader of `Bucket.averages["oomKills"]` will assume it is a per-bucket average.
Inconsistent API semantics, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.61, severity 2.8 of 3
SystemHistoryDto.averages.oomKills is now the average of bucket sums, with units that change by tier (kills-per-minute for the 24h range, kills-per-quarter for 30d). For every other series averages.x means "mean over the range"; for oomKills it is "mean of bucket totals", which is not a rate anyone would expect. No current consumer reads it, but it is publicly exposed JSON, and the architecture skill does not flag the contract change for averages.
**Inconsistent API semantics**, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.61, severity 2.8 of 3
`SystemHistoryDto.averages.oomKills` is now the average of bucket sums, with units that change by tier (kills-per-minute for the 24h range, kills-per-quarter for 30d). For every other series `averages.x` means "mean over the range"; for `oomKills` it is "mean of bucket totals", which is not a rate anyone would expect. No current consumer reads it, but it is publicly exposed JSON, and the architecture skill does not flag the contract change for `averages`.
Fixed in 9f34c2a: HistoryAverages leaves the counted series out, so averages has no oomKills; the range count is totals.oomKills only. Asserted in Scenario#147.03.
Fixed in 9f34c2a: `HistoryAverages` leaves the counted series out, so `averages` has no `oomKills`; the range count is `totals.oomKills` only. Asserted in Scenario#147.03.
claude-code marked this conversation as resolved
ai-reviewer
scheduled this pull request to auto merge when all checks succeed 2026-10-10 16:43:56 +02:00
ai-reviewer
requested review from mi 2026-10-10 16:43:56 +02:00
claude-code
marked the pull request as work in progress 2026-10-10 16:59:47 +02:00
HistoryAverages leaves the counted series out; the API serves their totals only.
HistoryTier.Bucket.averages is points, since it holds a sum for a counted series.
Scenario#147.01 and #147.03 split into one aspect each; the skill's metrics paragraph has one sentence per line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-code
marked the pull request as ready for review 2026-10-10 17:03:44 +02:00
hint 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
hint 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
hint 1 changed file was given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (all full files together would exceed [review] max_full_files_chars = 160000): src/test/kotlin/de/hoennig/werkator/metrics/MetricsHistoryUnitTest.kt
hint 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
9 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 70733, calls 1, tokens in 67935 / out 28697 (reasoning included in out), duration 237.7 s, cost 0.0548 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8243, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 5, tokens in 86120 / out 1168, cost 0.0042 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.9 s, classifier 0.7 s, context 24.2 s, model_review 211.4 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).
Reviewed head `9f34c2a861c9ad385c58bfde47541ec7c4f70708` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
<!-- werkrevisor:9f34c2a861c9ad385c58bfde47541ec7c4f70708 -->
<!-- werkrevisor-verdict:YELLOW -->
<!-- werkrevisor-blocks:dd462548f19c4155c2b1deb59b216189,7dfd69509aa1f77e29a77448ad7707c5,91b0e4c693e8a00475d3afc048e03e32,8e2a6fed32e3d76afd60957a5c30e219,fff8294e546803d51a74a7b996d7e93a,dd3aeca035393e23c46290b0ea09bdf5,ac64271143a21d10a412ecc33f234ce8,118fb51055ce97f00203b91900fcab81,9d737f956c96477880e98c94517cbddc,e7722a2ee0a13e8f2083cffa5c96f229,460665d361f39dc5f2636bd45c1d3fd4,7e06a869d443ddc8e0cddb1f9d6e10de,2970823d3f76e3c4250d93870fa0cd83,1c66b54436540f80f7b3ae6783667b07,23cebde2b2d07d8ee42477b4a7db5668,0e0ed7cc648588124c13076d45e50b77,6affbdf3f3a72a4cea80d5b21dfc5a1e,ad4cfab060e6940054ec03fa8652245d,6e6f99eb12793bf706249aceb2ad067f,0138d3d5894c9b84dc54330140d4a919,d40267aef979ae9c5313bb9e83558b4b,e7182a944eb07def6ba6dd2ec4602ef7,d39d495fcbc223fe69657e5f861819d2,8082b21c507822e96c7fbba2ec092826,0c220290e233982387c8039aff073163,ef42c4b586f84aec309085ea3e482b2d,da293cf68ac1aaea74983c2ce188c834,27fddd2d47d10514e78cb7fd1a415df8,89dc0cdb5def01fd14ebc0dfd24bd6de,c2ecf68b78e9f2e6eef0a7a41dbb370a,6d354ebc8b090a2e78409c68b09db66c -->
<!-- werkrevisor-folded -->
<details><summary>🟡 werkrevisor YELLOW for <code>9f34c2a</code>, superseded by <a href="http://git.javagil.de/mi/werkator/pulls/147#issuecomment-3252">the result for <code>9c71b55</code></a></summary>
## 🟡 werkrevisor: YELLOW / HUMAN_REVIEW
Change kind: `mixed`.
### Reasons
- **YELLOW** spec: docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.04 hard_to_read (0.55 > yellow_above = 0.5)
- **YELLOW** spec: docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.04 given_when_then_misused (0.61 > yellow_above = 0.6)
- **hint** 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
- **hint** 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
- **hint** 1 changed file was given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (all full files together would exceed [review] max_full_files_chars = 160000): src/test/kotlin/de/hoennig/werkator/metrics/MetricsHistoryUnitTest.kt
- **hint** 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
### Classifier
| Unit | Question | Problem | Confidence | Verdict |
|---|---|---|---|---|
| docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.04 | hard_to_read | 0.55 | 0.60 | YELLOW |
| docs/prs/2026-10-10-PR#147-oom-kills-tile.md Scenario#147.04 | given_when_then_misused | 0.61 | – | YELLOW |
9 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 70733, calls 1, tokens in 67935 / out 28697 (reasoning included in out), duration 237.7 s, cost 0.0548 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8243, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 5, tokens in 86120 / out 1168, cost 0.0042 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.9 s, classifier 0.7 s, context 24.2 s, model_review 211.4 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).<br>
Reviewed head `9f34c2a861c9ad385c58bfde47541ec7c4f70708` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
</sub>
</details>
claude-code
marked the pull request as work in progress 2026-10-10 18:33:43 +02:00
ai-review/assessment AI review passed; approved by werkrevisor; merge waits for 5 open hint(s): reply to each line comment and resolve it, then restart the review or push a commit
Gate: approved by werkrevisor; merge waits for 5 open hint(s): reply to each line comment and resolve it, then restart the review or push a commit.
Change kind: mixed.
Reasons
hint 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
hint 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
hint 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
hint 1 finding(s) of low severity or rated minor by Jev: src/main/kotlin/de/hoennig/werkator/metrics/HistoryTotals.kt:9-17
Findings
Minor doc/code wording at src/main/kotlin/de/hoennig/werkator/metrics/HistoryTotals.kt:9-17, severity unknown, counted as medium, hint, as a line comment
Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71713, calls 1, tokens in 68879 / out 13355 (reasoning included in out), duration 93.0 s, cost 0.0367 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8227, cost 0.0003 USD, finding judge `jev-1.13.0` calls 1, tokens in 1662, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82888 / out 523, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 10.5 s, model_review 80.2 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).
Reviewed head `9c71b555c068a37b0f1b084f83bebb4ce264fdcd` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
<!-- werkrevisor:9c71b555c068a37b0f1b084f83bebb4ce264fdcd -->
<!-- werkrevisor-verdict:GREEN -->
<!-- werkrevisor-blocks:dd462548f19c4155c2b1deb59b216189,7dfd69509aa1f77e29a77448ad7707c5,ed4c1e68291e422ede004504d30c9865,8e2a6fed32e3d76afd60957a5c30e219,fff8294e546803d51a74a7b996d7e93a,dd3aeca035393e23c46290b0ea09bdf5,ac64271143a21d10a412ecc33f234ce8,118fb51055ce97f00203b91900fcab81,9d737f956c96477880e98c94517cbddc,e7722a2ee0a13e8f2083cffa5c96f229,460665d361f39dc5f2636bd45c1d3fd4,7e06a869d443ddc8e0cddb1f9d6e10de,2970823d3f76e3c4250d93870fa0cd83,1c66b54436540f80f7b3ae6783667b07,23cebde2b2d07d8ee42477b4a7db5668,0e0ed7cc648588124c13076d45e50b77,6affbdf3f3a72a4cea80d5b21dfc5a1e,ad4cfab060e6940054ec03fa8652245d,6e6f99eb12793bf706249aceb2ad067f,0138d3d5894c9b84dc54330140d4a919,d40267aef979ae9c5313bb9e83558b4b,e7182a944eb07def6ba6dd2ec4602ef7,d39d495fcbc223fe69657e5f861819d2,8082b21c507822e96c7fbba2ec092826,0c220290e233982387c8039aff073163,ef42c4b586f84aec309085ea3e482b2d,da293cf68ac1aaea74983c2ce188c834,27fddd2d47d10514e78cb7fd1a415df8,89dc0cdb5def01fd14ebc0dfd24bd6de,c2ecf68b78e9f2e6eef0a7a41dbb370a,6d354ebc8b090a2e78409c68b09db66c -->
<!-- werkrevisor-folded -->
<details><summary>🟢 werkrevisor GREEN for <code>9c71b55</code>, superseded by <a href="http://git.javagil.de/mi/werkator/pulls/147#issuecomment-3433">the result for <code>f5dcd99</code></a></summary>
## 🟢 werkrevisor: GREEN / PASS
Gate: approved by werkrevisor; merge waits for 5 open hint(s): reply to each line comment and resolve it, then restart the review or push a commit.
Change kind: `mixed`.
### Reasons
- **hint** 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
- **hint** 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
- **hint** 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
- **hint** 1 finding(s) of low severity or rated minor by Jev: src/main/kotlin/de/hoennig/werkator/metrics/HistoryTotals.kt:9-17
### Findings
1. **Minor doc/code wording** at `src/main/kotlin/de/hoennig/werkator/metrics/HistoryTotals.kt:9-17`, severity unknown, counted as medium, hint, as a line comment
### Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71713, calls 1, tokens in 68879 / out 13355 (reasoning included in out), duration 93.0 s, cost 0.0367 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8227, cost 0.0003 USD, finding judge `jev-1.13.0` calls 1, tokens in 1662, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82888 / out 523, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 10.5 s, model_review 80.2 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 41da441).<br>
Reviewed head `9c71b555c068a37b0f1b084f83bebb4ce264fdcd` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
</sub>
</details>
werkrevisor: GREEN / PASS for 9c71b555c068a37b0f1b084f83bebb4ce264fdcd.
The result comment of werkrevisor holds the details.
werkrevisor: GREEN / PASS for `9c71b555c068a37b0f1b084f83bebb4ce264fdcd`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:9c71b555c068a37b0f1b084f83bebb4ce264fdcd -->
Minor doc/code wording, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.34, severity 1.0 of 3
HistoryTotals.of always returns a map with every key in MetricsHistory.COUNTED_KEYS, mapping to null when the tier has no column or only nulls. The JSON response therefore always contains totals.oomKills: null, even when the series was never sampled. The PR doc (Scenario#147.03) phrases this as "a range without the series has no totals.oomKills", which a literal reader could interpret as the key being absent rather than null-valued. The behaviour is consistent and the test relies on JsonPath treating null as not-existing, so it is not a bug; the doc wording is the only thing slightly off.
**Minor doc/code wording**, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.34, severity 1.0 of 3
`HistoryTotals.of` always returns a map with every key in `MetricsHistory.COUNTED_KEYS`, mapping to `null` when the tier has no column or only nulls. The JSON response therefore always contains `totals.oomKills: null`, even when the series was never sampled. The PR doc (Scenario#147.03) phrases this as "a range without the series has no `totals.oomKills`", which a literal reader could interpret as the key being absent rather than null-valued. The behaviour is consistent and the test relies on JsonPath treating null as not-existing, so it is not a bug; the doc wording is the only thing slightly off.
hint approval of werkrevisor for 9c71b55 not carried over: 2 approved block(s) are missing
hint 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
hint 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
hint 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71421, calls 1, tokens in 68601 / out 11030 (reasoning included in out), duration 127.6 s, cost 0.0338 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8233, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82924 / out 581, cost 0.0107 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16184, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 14.6 s, model_review 110.9 s, config: defaults + host + repository (.werkrevisor.toml at main 964a28a).
Reviewed head `f5dcd996ca82f8ce662014b472b5373ee27029da` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
<!-- werkrevisor:f5dcd996ca82f8ce662014b472b5373ee27029da -->
<!-- werkrevisor-verdict:GREEN -->
<!-- werkrevisor-blocks:dd462548f19c4155c2b1deb59b216189,7dfd69509aa1f77e29a77448ad7707c5,cbe68ac4245cddafdcb5c9e4e68c59c0,8e2a6fed32e3d76afd60957a5c30e219,fff8294e546803d51a74a7b996d7e93a,dd3aeca035393e23c46290b0ea09bdf5,ac64271143a21d10a412ecc33f234ce8,118fb51055ce97f00203b91900fcab81,9d737f956c96477880e98c94517cbddc,e7722a2ee0a13e8f2083cffa5c96f229,460665d361f39dc5f2636bd45c1d3fd4,7e06a869d443ddc8e0cddb1f9d6e10de,2970823d3f76e3c4250d93870fa0cd83,1c66b54436540f80f7b3ae6783667b07,23cebde2b2d07d8ee42477b4a7db5668,0e0ed7cc648588124c13076d45e50b77,6affbdf3f3a72a4cea80d5b21dfc5a1e,ad4cfab060e6940054ec03fa8652245d,6e6f99eb12793bf706249aceb2ad067f,0138d3d5894c9b84dc54330140d4a919,d40267aef979ae9c5313bb9e83558b4b,e7182a944eb07def6ba6dd2ec4602ef7,d39d495fcbc223fe69657e5f861819d2,8082b21c507822e96c7fbba2ec092826,0c220290e233982387c8039aff073163,b08760044720a37305043648c7d01c35,da293cf68ac1aaea74983c2ce188c834,27fddd2d47d10514e78cb7fd1a415df8,89dc0cdb5def01fd14ebc0dfd24bd6de,c2ecf68b78e9f2e6eef0a7a41dbb370a,6d354ebc8b090a2e78409c68b09db66c -->
<!-- werkrevisor-folded -->
<details><summary>🟢 werkrevisor GREEN for <code>f5dcd99</code>, superseded by <a href="http://git.javagil.de/mi/werkator/pulls/147#issuecomment-3445">the result for <code>942d0b8</code></a></summary>
## 🟢 werkrevisor: GREEN / PASS
Change kind: `mixed`.
### Reasons
- **hint** approval of werkrevisor for `9c71b55` not carried over: 2 approved block(s) are missing
- **hint** 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
- **hint** 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
- **hint** 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
### Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71421, calls 1, tokens in 68601 / out 11030 (reasoning included in out), duration 127.6 s, cost 0.0338 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8233, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82924 / out 581, cost 0.0107 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16184, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 14.6 s, model_review 110.9 s, config: defaults + host + repository (.werkrevisor.toml at main 964a28a).<br>
Reviewed head `f5dcd996ca82f8ce662014b472b5373ee27029da` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
</sub>
</details>
werkrevisor: GREEN / PASS for f5dcd996ca82f8ce662014b472b5373ee27029da.
The result comment of werkrevisor holds the details.
werkrevisor: GREEN / PASS for `f5dcd996ca82f8ce662014b472b5373ee27029da`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:f5dcd996ca82f8ce662014b472b5373ee27029da -->
hint approval of werkrevisor for f5dcd99 not carried over: 2 approved block(s) are missing
hint 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
hint 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
hint 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71370, calls 1, tokens in 68552 / out 19122 (reasoning included in out), duration 129.4 s, cost 0.0435 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8231, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82988 / out 506, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16177, cost 0.0007 USD, phases: path_judge 0.7 s, classifier 0.7 s, context 13.4 s, model_review 113.8 s, config: defaults + host + repository (.werkrevisor.toml at main 964a28a).
Reviewed head `942d0b8dac045d827ebe010e65d146b5b0909832` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
<!-- werkrevisor:942d0b8dac045d827ebe010e65d146b5b0909832 -->
<!-- werkrevisor-verdict:GREEN -->
<!-- werkrevisor-blocks:dd462548f19c4155c2b1deb59b216189,816c3882f576d1005341b19690666a66,dedec1d6a67f58c3fc92dd7395cbd59d,8e2a6fed32e3d76afd60957a5c30e219,fff8294e546803d51a74a7b996d7e93a,dd3aeca035393e23c46290b0ea09bdf5,ac64271143a21d10a412ecc33f234ce8,118fb51055ce97f00203b91900fcab81,9d737f956c96477880e98c94517cbddc,e7722a2ee0a13e8f2083cffa5c96f229,460665d361f39dc5f2636bd45c1d3fd4,7e06a869d443ddc8e0cddb1f9d6e10de,2970823d3f76e3c4250d93870fa0cd83,1c66b54436540f80f7b3ae6783667b07,23cebde2b2d07d8ee42477b4a7db5668,0e0ed7cc648588124c13076d45e50b77,6affbdf3f3a72a4cea80d5b21dfc5a1e,ad4cfab060e6940054ec03fa8652245d,6e6f99eb12793bf706249aceb2ad067f,0138d3d5894c9b84dc54330140d4a919,d40267aef979ae9c5313bb9e83558b4b,e7182a944eb07def6ba6dd2ec4602ef7,d39d495fcbc223fe69657e5f861819d2,8082b21c507822e96c7fbba2ec092826,0c220290e233982387c8039aff073163,b08760044720a37305043648c7d01c35,da293cf68ac1aaea74983c2ce188c834,27fddd2d47d10514e78cb7fd1a415df8,89dc0cdb5def01fd14ebc0dfd24bd6de,c2ecf68b78e9f2e6eef0a7a41dbb370a,6d354ebc8b090a2e78409c68b09db66c -->
## 🟢 werkrevisor: GREEN / PASS
Change kind: `mixed`.
### Reasons
- **hint** approval of werkrevisor for `f5dcd99` not carried over: 2 approved block(s) are missing
- **hint** 11 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 1 build change(s) of low risk according to Jev: build.gradle.kts (weakens checks 0.03, risk 0.00, security 0.01)
- **hint** 2 changed files were given to the AI as excerpt of the selected lines and 5 lines around each changed line, not in full (each larger than [review] max_full_file_chars = 80000): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html
- **hint** 2 changed files were given to the AI only as diff, not in full, so it could not check them against the rest of their file (all full files together would exceed [review] max_full_files_chars = 160000, and no excerpt fits the limits either): src/test/kotlin/de/hoennig/werkator/server/SystemApiControllerRestTest.kt, src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt (advisory: Jev judged every changed file)
### Classifier
10 of 10 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71370, calls 1, tokens in 68552 / out 19122 (reasoning included in out), duration 129.4 s, cost 0.0435 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8231, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82988 / out 506, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16177, cost 0.0007 USD, phases: path_judge 0.7 s, classifier 0.7 s, context 13.4 s, model_review 113.8 s, config: defaults + host + repository (.werkrevisor.toml at main 964a28a).<br>
Reviewed head `942d0b8dac045d827ebe010e65d146b5b0909832` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
</sub>
werkrevisor: GREEN / PASS for 942d0b8dac045d827ebe010e65d146b5b0909832.
The result comment of werkrevisor holds the details.
werkrevisor: GREEN / PASS for `942d0b8dac045d827ebe010e65d146b5b0909832`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:942d0b8dac045d827ebe010e65d146b5b0909832 -->
ai-reviewer
scheduled this pull request to auto merge when all checks succeed 2026-10-10 22:55:46 +02:00
ai-reviewer
removed review request for mi 2026-10-10 22:55:46 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
See docs/prs/2026-10-10-PR#147-oom-kills-tile.md
Stacked on #146.
🤖 Generated with Claude Code
🟠 werkrevisor ORANGE for
5934a37, superseded by the result for9f34c2a🟠 werkrevisor: ORANGE / FINDINGS
Change kind:
mixed.Reasons
Findings
Misleading field name at
src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt:75-84, severity unknown, counted as medium, ORANGE, as a line commentInconsistent API semantics at
src/main/kotlin/de/hoennig/werkator/server/SystemApiController.kt:136-145, severity unknown, counted as medium, ORANGE, as a line commentClassifier
6 of 8 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 68591, calls 1, tokens in 65872 / out 10559 (reasoning included in out), duration 133.0 s, cost 0.0324 USD (reported), classifier `jev-1.13.0` calls 8, tokens in 6436, cost 0.0003 USD, finding judge `jev-1.13.0` calls 2, tokens in 4267, cost 0.0002 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82739 / out 860, cost 0.0108 USD (reported), build file judge `jev-1.13.0` calls 11, tokens in 14182, cost 0.0006 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 20.0 s, model_review 110.7 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main2 answers ignored because of a confidence below [classifier] min_confidence.
41da441).Reviewed head `5934a37db59d8e1d6909d71b0b8ae428bcb66cf5` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
werkrevisor: ORANGE / FINDINGS for
5934a37db59d8e1d6909d71b0b8ae428bcb66cf5.The result comment of werkrevisor holds the details.
@@ -121,3 +121,3 @@`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM and swap (`/proc/meminfo`), the swap-out (`pswpout` of `/proc/vmstat` over five minutes, PR#146), the load average (`/proc/loadavg`), the pressure-stall information (`/proc/pressure/cpu`, `/proc/pressure/memory` and `/proc/pressure/io`, PSI `some avg60`), disk, and repository size every 15s, but only after `ServerMetricsLifecycle` (server profile) calls `start()` — like the watcher, nothing is scheduled in CLI runs or tests. Min/max/avg aggregation state persists as JSON in the instance's state home (`${XDG_STATE_HOME:-~/.local/state}/werkator/system-metrics-state.json`, moved there from the artifact root once, PR#33), so restarts continue the series. Unavailable sources (e.g. no `/proc` outside Linux) yield null metrics served as HTTP 200 by `GET /api/system` — the `/system` page shows `n/a`, never an error.The same state file carries the sample history (`MetricsHistory`, PR#27), three tiers: every 15 s sample for four hours, one point per minute for a day and one per quarter hour for a month, the aggregated tiers with average *and maximum*, column-wise (one timestamp list, one value list per metric key) so the file stays a few hundred kilobytes; a minute is aggregated from the samples when the next one opens, a quarter from the minutes, so no accumulator is persisted, and the file is written once a minute (`PERSIST_EVERY_SAMPLES`) and on shutdown. `GET /api/system/history?range=1h|4h|24h|7d|30d` serves a cut of one tier (samples up to four hours, minutes up to a day, quarters beyond) with its peaks (`HistoryPeaks`: the highest value, when, and the `UtilizationLevel` it reached against the *current* total) — the evidence for a capacity decision. The verdict is computed on the server (`SystemHealth.assess`, `healthy`/`pressure`/`overloaded` with the rules that fired as reasons) and only rendered by the page, so a later executor change can act on the very same rules; CPU utilization alone never counts, waiting does (load above the core count, pressure, swap). `CapacitySizing.of` (PR#30, corrected in PR#42 and PR#58) sizes the machine from the quarter-hour tier. RAM is judged by *waiting*, never by a fill level (RFC 0004): a quarter hour counts as stalled when its average `memoryPressure` reached `SystemHealth.MEMORY_OVERLOAD_PERCENT` — shared, so a machine the health verdict calls healthy cannot be red in the sizing — and RAM is `tight` from 1 % and `upgrade` from 5 % of stalled quarter hours, the share CPU already uses; swap occupancy plays no part. The peak is named in the reason and never judged, with the single exception that it may still prove slack for a downsize — without the 40 % gate CPU and disk keep (PR#79), since the smaller bookable GiB that holds peak, headroom and reserve is the whole proof; it sizes a raise when one is due, rounded up and at least one step above what is booked, because a stall proves the peak was the ceiling. Disk is judged by the level it *holds* — the 95th percentile of the quarter-hour maxima of the last seven days (`DISK_LEVEL_DAYS`), the percentile the CPU watch band already uses, so a single quarter hour is a moment and not a level (PR#78; upgrade from 90 %, `tight` from 80 % where there is something to raise; the recommendation leaves a fifth free, a third for disks) — and by its growth, the median of the daily deltas of the last week, so a step counts on one day and a trend on most, extrapolated thirty days ahead from the level, only from seven days of history on. The window maximum is named in the reason and vetoes a downsize (under 40 %), never asks for a raise. Every size is rounded to what can be booked (`BookableSteps`, a parameter of `of(...)` that the controllers derive from `HostingEnvironments.bookableSteps` — for Hostsharing 10 GiB per quota, 250 GB per volume, 1 GiB RAM; elsewhere the whole unit), and a downsize is advised only when the next bookable size down would still leave the target headroom, otherwise `fits` with a reason saying why. The steps, unlike the hosting itself, reach every viewer: they name nothing, and one machine must not advise two different things depending on who asks. `ioPressure` (PR#69) is sampled and shown like the other two but read by neither the health verdict nor the sizing, in RFC 0004's order — the series has to exist before anyone can say what a number means, and on a shared hive the disks are the part shared hardest, so it will say more about the neighbours than about this instance until somebody has watched it. `oomKills` is sampled into the history from `/proc/vmstat` as the count per bucket but deliberately not read by the sizing — the counter is machine-wide, so on a shared hive it counts every webspace's kills. CPU is sized from the quarter-hour *averages*, never the maxima — waiting (load above the thread count in more than 5 % of the quarters, the 95th percentile of those loads being the recommendation) and, below that, the 95th percentile of sustained utilization for the watch band. A CPU is counted in hardware threads (`CapacitySizing.CPU_UNIT`), which is what `availableProcessors` reports and what a webspace is billed by. RAM is advised in bookable whole GiB: the visible total rounded up is the booked size, and the difference the reserve the host keeps. Nothing is advised from less than a day. Served as `capacity` on `GET /api/system` and shown as colour-coded chips in the verdict panel; where the instance can tell it runs on a Hostsharing Managed Webspace (`HostingEnvironments.detect`, served as `hosting`) the chips carry an order button that composes a German mail to the service desk from the changes the user ticks — the page sends nothing itself. The `/system` page is a verdict line with the sizing chips beneath it, one gauge card per resource (percentage, bar, level pill, inline-SVG sparklinThe same state file carries the sample history (`MetricsHistory`, PR#27), three tiers: every 15 s sample for four hours, one point per minute for a day and one per quarter hour for a month, the aggregated tiers with average *and maximum*, column-wise (one timestamp list, one value list per metric key) so the file stays a few hundred kilobytes; a minute is aggregated from the samples when the next one opens, a quarter from the minutes, so no accumulator is persisted, and the file is written once a minute (`PERSIST_EVERY_SAMPLES`) and on shutdown. `GET /api/system/history?range=1h|4h|24h|7d|30d` serves a cut of one tier (samples up to four hours, minutes up to a day, quarters beyond) with its peaks (`HistoryPeaks`: the highest value, when, and the `UtilizationLevel` it reached against the *current* total) — the evidence for a capacity decision. The verdict is computed on the server (`SystemHealth.assess`, `healthy`/`pressure`/`overloaded` with the rules that fired as reasons) and only rendered by the page, so a later executor change can act on the very same rules; CPU utilization alone never counts, waiting does (load above the core count, pressure, swap). `CapacitySizing.of` (PR#30, corrected in PR#42 and PR#58) sizes the machine from the quarter-hour tier. RAM is judged by *waiting*, never by a fill level (RFC 0004): a quarter hour counts as stalled when its average `memoryPressure` reached `SystemHealth.MEMORY_OVERLOAD_PERCENT` — shared, so a machine the health verdict calls healthy cannot be red in the sizing — and RAM is `tight` from 1 % and `upgrade` from 5 % of stalled quarter hours, the share CPU already uses; swap occupancy plays no part. The peak is named in the reason and never judged, with the single exception that it may still prove slack for a downsize — without the 40 % gate CPU and disk keep (PR#79), since the smaller bookable GiB that holds peak, headroom and reserve is the whole proof; it sizes a raise when one is due, rounded up and at least one step above what is booked, because a stall proves the peak was the ceiling. Disk is judged by the level it *holds* — the 95th percentile of the quarter-hour maxima of the last seven days (`DISK_LEVEL_DAYS`), the percentile the CPU watch band already uses, so a single quarter hour is a moment and not a level (PR#78; upgrade from 90 %, `tight` from 80 % where there is something to raise; the recommendation leaves a fifth free, a third for disks) — and by its growth, the median of the daily deltas of the last week, so a step counts on one day and a trend on most, extrapolated thirty days ahead from the level, only from seven days of history on. The window maximum is named in the reason and vetoes a downsize (under 40 %), never asks for a raise. Every size is rounded to what can be booked (`BookableSteps`, a parameter of `of(...)` that the controllers derive from `HostingEnvironments.bookableSteps` — for Hostsharing 10 GiB per quota, 250 GB per volume, 1 GiB RAM; elsewhere the whole unit), and a downsize is advised only when the next bookable size down would still leave the target headroom, otherwise `fits` with a reason saying why. The steps, unlike the hosting itself, reach every viewer: they name nothing, and one machine must not advise two different things depending on who asks. `ioPressure` (PR#69) is sampled and shown like the other two but read by neither the health verdict nor the sizing, in RFC 0004's order — the series has to exist before anyone can say what a number means, and on a shared hive the disks are the part shared hardest, so it will say more about the neighbours than about this instance until somebody has watched it. `oomKills` is sampled into the history from `/proc/vmstat` as the count per bucket — a counted series (`MetricsHistory.COUNTED_KEYS`, PR#147) whose buckets hold the sum, not the average, and whose range total `GET /api/system/history` serves as `totals` for the OOM kills tile — but deliberately not read by the sizing — the counter is machine-wide, so on a shared hive it counts every webspace's kills. CPU is sized from the quarter-hour *averages*, never the maxima — waiting (load above the thread count in more than 5 % of the quarters, the 95th percentile of those loads being the recommendation) and, below that, the 95th percentile of sustained utilization for the watch band. A CPU is counted in hardware threads (`CapacitySizing.CPU_UNIT`), which is what `availableProcessors` reports and what a webspace is billed by. RAM is advised in bookable whole GiB: the visible total rounded up is the booked size, and the difference the reserve the host keeps. Nothing is advised from less than a day. Served as `capacity` on `GET /api/system` and shown as colour-coded chips in the verdict panel; where the instance can tell it runs on a Hostsharing Managed Webspace (`HostingEnvironments.detect`, served as `hosting`) the chips carry an order button that composes a German mail to the service desk from the changeWriting rule: more than one sentence on a line (werkrevisor writing rule)
Fix the line, or reply why it stays, and resolve this comment.
Writing rule: line longer than 400 characters (5756) (werkrevisor writing rule)
Fix the line, or reply why it stays, and resolve this comment.
Fixed in
9f34c2a: the system-metrics paragraph of the skill has one sentence per line.Fixed in
9f34c2a: no line of that paragraph is longer than 400 characters now. Other paragraphs of the file are out of this PR's scope.@@ -74,9 +74,11 @@ data class HistoryTier() = indices.mapNotNull { source[key]?.getOrNull(it) }return Bucket(Misleading field name, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.71, severity 2.6 of 3
The internal
Bucket.averagesmap now stores sums (not averages) for keys inMetricsHistory.COUNTED_KEYS(so faroomKills). The field name no longer matches its content; this propagates intoHistoryTier.values["oomKills"], where every consumer now sees per-bucket totals rather than per-sample values. A future reader ofBucket.averages["oomKills"]will assume it is a per-bucket average.Fixed in
9f34c2a: the field isHistoryTier.Bucket.pointsnow, documented as the average of a level or the sum of a counted series.Inconsistent API semantics, severity unknown, counted as medium (werkrevisor)
ORANGE; Jev: real 0.61, severity 2.8 of 3
SystemHistoryDto.averages.oomKillsis now the average of bucket sums, with units that change by tier (kills-per-minute for the 24h range, kills-per-quarter for 30d). For every other seriesaverages.xmeans "mean over the range"; foroomKillsit is "mean of bucket totals", which is not a rate anyone would expect. No current consumer reads it, but it is publicly exposed JSON, and the architecture skill does not flag the contract change foraverages.Fixed in
9f34c2a:HistoryAveragesleaves the counted series out, soaverageshas nooomKills; the range count istotals.oomKillsonly. Asserted in Scenario#147.03.🟡 werkrevisor YELLOW for
9f34c2a, superseded by the result for9c71b55🟡 werkrevisor: YELLOW / HUMAN_REVIEW
Change kind:
mixed.Reasons
Classifier
9 of 10 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 70733, calls 1, tokens in 67935 / out 28697 (reasoning included in out), duration 237.7 s, cost 0.0548 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8243, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 5, tokens in 86120 / out 1168, cost 0.0042 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.9 s, classifier 0.7 s, context 24.2 s, model_review 211.4 s, config: defaults + host + repository (.werkrevisor.toml at main4 answers ignored because of a confidence below [classifier] min_confidence.
41da441).Reviewed head `9f34c2a861c9ad385c58bfde47541ec7c4f70708` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
🟢 werkrevisor GREEN for
9c71b55, superseded by the result forf5dcd99🟢 werkrevisor: GREEN / PASS
Gate: approved by werkrevisor; merge waits for 5 open hint(s): reply to each line comment and resolve it, then restart the review or push a commit.
Change kind:
mixed.Reasons
Findings
src/main/kotlin/de/hoennig/werkator/metrics/HistoryTotals.kt:9-17, severity unknown, counted as medium, hint, as a line commentClassifier
10 of 10 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71713, calls 1, tokens in 68879 / out 13355 (reasoning included in out), duration 93.0 s, cost 0.0367 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8227, cost 0.0003 USD, finding judge `jev-1.13.0` calls 1, tokens in 1662, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82888 / out 523, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16165, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 10.5 s, model_review 80.2 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main4 answers ignored because of a confidence below [classifier] min_confidence.
41da441).Reviewed head `9c71b555c068a37b0f1b084f83bebb4ce264fdcd` against base `41da4413b5ad29581cf7affc7467f82ad227adae`, werkrevisor 0.32.2.
werkrevisor: GREEN / PASS for
9c71b555c068a37b0f1b084f83bebb4ce264fdcd.The result comment of werkrevisor holds the details.
@@ -0,0 +6,4 @@* or month. `null` where the range holds no point of the series, so a range sampled before the* series existed says `n/a` rather than `0`.*/object HistoryTotals {Minor doc/code wording, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.34, severity 1.0 of 3
HistoryTotals.ofalways returns a map with every key inMetricsHistory.COUNTED_KEYS, mapping tonullwhen the tier has no column or only nulls. The JSON response therefore always containstotals.oomKills: null, even when the series was never sampled. The PR doc (Scenario#147.03) phrases this as "a range without the series has nototals.oomKills", which a literal reader could interpret as the key being absent rather than null-valued. The behaviour is consistent and the test relies on JsonPath treating null as not-existing, so it is not a bug; the doc wording is the only thing slightly off.Agreed: the key is present with
null. Scenario#147.03 says so now, and the test assertsnullexplicitly instead ofdoesNotExist(next commit).New commits pushed, approval review dismissed automatically according to repository settings
🟢 werkrevisor GREEN for
f5dcd99, superseded by the result for942d0b8🟢 werkrevisor: GREEN / PASS
Change kind:
mixed.Reasons
9c71b55not carried over: 2 approved block(s) are missingClassifier
10 of 10 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71421, calls 1, tokens in 68601 / out 11030 (reasoning included in out), duration 127.6 s, cost 0.0338 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8233, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82924 / out 581, cost 0.0107 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16184, cost 0.0007 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 14.6 s, model_review 110.9 s, config: defaults + host + repository (.werkrevisor.toml at main4 answers ignored because of a confidence below [classifier] min_confidence.
964a28a).Reviewed head `f5dcd996ca82f8ce662014b472b5373ee27029da` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
werkrevisor: GREEN / PASS for
f5dcd996ca82f8ce662014b472b5373ee27029da.The result comment of werkrevisor holds the details.
New commits pushed, approval review dismissed automatically according to repository settings
🟢 werkrevisor: GREEN / PASS
Change kind:
mixed.Reasons
f5dcd99not carried over: 2 approved block(s) are missingClassifier
10 of 10 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 71370, calls 1, tokens in 68552 / out 19122 (reasoning included in out), duration 129.4 s, cost 0.0435 USD (reported), classifier `jev-1.13.0` calls 10, tokens in 8231, cost 0.0003 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 4, tokens in 82988 / out 506, cost 0.0106 USD (reported), build file judge `jev-1.13.0` calls 12, tokens in 16177, cost 0.0007 USD, phases: path_judge 0.7 s, classifier 0.7 s, context 13.4 s, model_review 113.8 s, config: defaults + host + repository (.werkrevisor.toml at main4 answers ignored because of a confidence below [classifier] min_confidence.
964a28a).Reviewed head `942d0b8dac045d827ebe010e65d146b5b0909832` against base `964a28a9b654eee448f44cf9f0e8a2f875e9fd99`, werkrevisor 0.32.2.
werkrevisor: GREEN / PASS for
942d0b8dac045d827ebe010e65d146b5b0909832.The result comment of werkrevisor holds the details.