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>
claude-code
marked the pull request as ready for review 2026-10-10 15:53:08 +02:00
hint writing rules violated in the changed docs: .claude/skills/architecture/SKILL.md:121: more than one sentence on a line; .claude/skills/architecture/SKILL.md:121: line longer than 400 characters (884); .claude/skills/architecture/SKILL.md:123: more than one sentence on a line; and 1 more
hint 19 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 3 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 (each larger than [review] max_full_file_chars = 80000, and no excerpt fits the limits either): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html, src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt (advisory: Jev judged every changed file)
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 (all full files together would exceed [review] max_full_files_chars = 160000): src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt, src/main/kotlin/de/hoennig/werkator/metrics/SystemHealth.kt
hint 9 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/main/kotlin/de/hoennig/werkator/metrics/SystemMetrics.kt, src/main/kotlin/de/hoennig/werkator/metrics/SystemMetricsCollector.kt, src/main/kotlin/de/hoennig/werkator/server/HomeViews.kt and 6 more (advisory: Jev judged every changed file)
hint 1 finding(s) of low severity or rated minor by Jev: docs/prs/2026-10-10-PR#146-swap-out-card.md:134-135
Findings
Layout numbers slightly off at docs/prs/2026-10-10-PR#146-swap-out-card.md:134-135, severity unknown, counted as medium, hint, as a line comment
10 of 12 units passed all questions (1 PR document(s)).
3 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 93657, calls 1, tokens in 89795 / out 29540 (reasoning included in out), duration 544.6 s, cost 0.0408 USD (reported), classifier `jev-1.13.0` calls 12, tokens in 10757, cost 0.0005 USD, finding judge `jev-1.13.0` calls 1, tokens in 2235, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 14, tokens in 187497 / out 3818, cost 0.0253 USD (reported), build file judge `jev-1.13.0` calls 20, tokens in 40334, cost 0.0017 USD, phases: path_judge 1.3 s, classifier 0.7 s, context 58.8 s, model_review 481.8 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 4026d37).
Reviewed head `3cb75bada467cbfbacf2cd78e5977c47566c88d5` against base `4026d3725d9e99adc100db4d3577b33f296b130a`, werkrevisor 0.32.1.
<!-- werkrevisor:3cb75bada467cbfbacf2cd78e5977c47566c88d5 -->
<!-- werkrevisor-verdict:YELLOW -->
<!-- werkrevisor-blocks:74760052defbe7d785dd2897813330ea,1c7d0262dc9a1cb94c564a7adece588d,1c9d8f573f8125a107c67803b104ef22,046ac81a5f8208470516d7c56b08b956,1db2591df75d6249cb28e4eec2546dc6,67aa5f06be112a7510f24aca44736e14,95e39c43177f2304bf9b140532b01f7b,6eb9974a7ec79bb6040721bd9b4f0ab9,467c23b171b4b07c0c44c7d14ef2ee16,a1dcbde088db20428f9c472f95cb562e,595d11876c8ddd29a26fa152d2e68d35,c86774d200a8bf35c9015d7a4cf6da2b,607060db3c0492b78f27f76fb90434ef,377102a6521dcf2788f70dd02c5a80ac,ad0e5e9d03ea05ae79cc7c5cec087f92,39d0acbe9c6da3593d6e4a57490bc36f,7f4d231ededbf7ce91d2191fd48be11b,11bdd05fa8ad3515dec0201dc9593216,398b022129ca8fbaf53462aad8b306cd,5d93dfee85241d559e1f76d540885ed2,c5d1eb3a42488ceb20408bb73f3e388d,415960e5eae9405935f13a4b959efbbb,e806be30f9c8da65b474d15931d29321,a3f7c1dbbcf138519ae8fcbfb1064b15,5d851a392a36deff213db9f6e12f2a3b,1d76987ad68cd7f0f5672993f919348e,4eb263209aad9f20ba4c79fd36c801b5,83d205b40e97301db4db25b879920359,1010687223017763796c549edd89caa8,2f5f9935f22ae96c9a733f071e11f216,e31f6ab289ee56c50d02a3abb99870b3,c0f6beb1675c621fc2ddd2c4f5994800,3b5d68583429594bd5d497f3253d8697,7ce91d497bed1f9f3c6f59ded8d77b07,f974290f39347fc0220237993626c4d2,e914ae84730fd472ce937a25e33adfae,7432214747a7626d3294b6e241fa6a12,a9183f349767e3ad09a4c6f80eecbfb8,68ff0c4a2b0af81573189f58451befd9,ddf3ef1006cb14b11e1d6991d562a3d9,6bba7e35e5d695bb624ac4d8dd803b97,703942548500158268911305db636341,55dd993c7a9c88ad32dbe2a586cf4393,7dc026279d81163dde0b409dd2848aef,0ac3202a07500677f3768831a2555d41,1cc4e7c052639a5fa1ec245940cd9b66,53f185e5f6851618cf7510842783a695,d8d174fc27be7d518b079ddf5ab94864,c540babce87295b17acda8a3655e7bc8,ed4d2d7231966823a335ff0168201872,23c9f2ac31f5ebedcf7edcad66ef8c87,aacac84320dd37f77b7f3966e5443bba,ddc8fb4605696a83d77d198d1c3c9ac6,f49f40f60bc2150462c05fb0e7964eec,3a048d3c449351217eeedc4bceb56ac3,4cb5ed151a5ccc0b49d105ea7f517bb0,4bcd09003b408d553abdd85277894e46,ebf2b536ffa210d4232bc89c20e76f33,e3c2912cacf4bc03775506402acdf09a,beaf3bf27fcd120aa818242064c3a8ee,36b96e3379f12a91f26e8b65e7831397,5470023b31ae62b32741b6525fff5b03,03e28daa59e739501f35208d1f8f8874,b98554612ae4056d0cd9006f60e2c9a1,dea66b59cd5a5ec79f9714b79362330f,25a1786e6976c4a9a7c2c1f983e9dc91,2fec7ff257aa3823cbb9d31cefb638f9,5da9d2d41c32e9db44482f4d172625a3,25b7c3db3fbc10c5f115597a691b4b5e,7c27b01a6b0638a1d35beda0de29b1f2,60758a4451f6bc844b4b562ba80f7715,7ac727c38203affb6f5d2385a78c7bdf,2307693bd22b6529243bb7cd3905e11b,fd34cd0a043ceb67089289735f4e730a,0e5c3a51d3f21fc9213e2e5a086c1106,184393cbbd7f582cb32c299fb2936900,4ce2ecbd650499ea7637d07ce5ebbc7a,f160209d0c40e43ec6d141fb6f6fa3ae,7fd6194c7cbf02f691aa6c94dab79487,42a85a2f95f8f5ce93b0800c858d35b5,09ac38082fb9a3e2eee59b63e30fe6be,6aeed3fc684d94763588ec276f1e850d,311db2eee313343c0ee87d87b2d6ea76,28cc1de18d9ccf429d038f66a0f474dd,b5f6258528c33f5f86d02d9cd952a16b,2ec9ee8f4440efbb6f43aa4cb063b4e8,901acb0aa209f1ec14a3c3baa4043d1a,02f2906e9099f1c5d75cb3d5edbafd93,6f6397f9f7170af2c82c3044efd7c0a5,3ac91310e5709c18d45e3239ae35f5f6,152a68cd5d3c8234845474c8ff6c694f,98d4521de64ee70a4b87e90c8eb9144b,3ab88dc103841dbcf448a56d1d434e9a,f307878d099264f0e47313ce7316f14d,0b2f35c0f732ff9b8931265f65386e65,d9008256c4de41e07152cfab53d74a27,65301c668171e7896607db74b9c7095e,3a6c11c6c7c20fac89c3fdf2e1d1f0c9,22f80e7366e5ec58b80cb64bc8d0f322,b3f3850d2b1f7c3f3e59a501ee2b1ba4,ccab1ef29a1c2b5e368b08b58ba369ed,c788e2743a04955f363bbe2b63870a50,53a4179e45fc9970ad826cc8cecfd86f,b188e64a4b679f74ec1112e98e75e500,e566d2b4d7b93a1fe6413171ba69dca2,5ab36247fb7e185f4538153d4adf482f,67dec887337dfa52c5921eb9d1452658,5115da253f95e403cae5fe79b8934236,68f17b88445ed9fe300b19d69fd3a470,05f15a54ff1c26b42ec71cb1d5406017,2b2fddc5a6b55e2f55aecde4d244467c,469a6837803a5128e3b44a6974def802,75e67e551b9a2bc2d9a2d86a6390595d,c9cd482d7092b6b7ea96698c9658399b,04ea9b79c1e107d42c25af84fdf62929,d08fc20c38eb9716005db88ae2a7e296,03384a9674b5340eaa40ad6931838948,4916d16a956ddfc9a1cdb0457dbf306f,d86bbd5c028b9448a3b4df31e00d6a5a,61f05de5c0f7c2d1cfa6217df913e86a,cd3d0ae50defb478a03bb9a5cf17fb66,4bcdbf19d94b34a2513be83820c2d3c5,dfe95ec3c6f989ccd5c7e4e4c66ef67f,8d0527e025513ebd59ccd338031d76d6,1ed3f0ea1df91a139604fdd790cbfa28,6b0d0313dc31f54f33b9cf3c14a27d22,ce1668ba18aab03c43fba8a2a09bafa1 -->
## 🟡 werkrevisor: YELLOW / HUMAN_REVIEW
Change kind: `mixed`.
### Reasons
- **YELLOW** spec: docs/prs/2026-10-10-PR#146-swap-out-card.md Scenario#146.02 more_than_one_aspect (0.73 > yellow_above = 0.6)
- **YELLOW** spec: docs/prs/2026-10-10-PR#146-swap-out-card.md Scenario#146.03 more_than_one_aspect (0.79 > yellow_above = 0.6)
- **hint** writing rules violated in the changed docs: .claude/skills/architecture/SKILL.md:121: more than one sentence on a line; .claude/skills/architecture/SKILL.md:121: line longer than 400 characters (884); .claude/skills/architecture/SKILL.md:123: more than one sentence on a line; and 1 more
- **hint** 19 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** 3 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 (each larger than [review] max_full_file_chars = 80000, and no excerpt fits the limits either): src/main/resources/static/werkator.js, src/main/resources/templates/releases.html, src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt (advisory: Jev judged every changed file)
- **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 (all full files together would exceed [review] max_full_files_chars = 160000): src/main/kotlin/de/hoennig/werkator/metrics/MetricsHistory.kt, src/main/kotlin/de/hoennig/werkator/metrics/SystemHealth.kt
- **hint** 9 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/main/kotlin/de/hoennig/werkator/metrics/SystemMetrics.kt, src/main/kotlin/de/hoennig/werkator/metrics/SystemMetricsCollector.kt, src/main/kotlin/de/hoennig/werkator/server/HomeViews.kt and 6 more (advisory: Jev judged every changed file)
- **hint** 1 finding(s) of low severity or rated minor by Jev: docs/prs/2026-10-10-PR#146-swap-out-card.md:134-135
### Findings
1. **Layout numbers slightly off** at `docs/prs/2026-10-10-PR#146-swap-out-card.md:134-135`, severity unknown, counted as medium, hint, as a line comment
### Classifier
| Unit | Question | Problem | Confidence | Verdict |
|---|---|---|---|---|
| docs/prs/2026-10-10-PR#146-swap-out-card.md Scenario#146.02 | more_than_one_aspect | 0.73 | – | YELLOW |
| docs/prs/2026-10-10-PR#146-swap-out-card.md Scenario#146.03 | more_than_one_aspect | 0.79 | – | YELLOW |
10 of 12 units passed all questions (1 PR document(s)).
3 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 93657, calls 1, tokens in 89795 / out 29540 (reasoning included in out), duration 544.6 s, cost 0.0408 USD (reported), classifier `jev-1.13.0` calls 12, tokens in 10757, cost 0.0005 USD, finding judge `jev-1.13.0` calls 1, tokens in 2235, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 14, tokens in 187497 / out 3818, cost 0.0253 USD (reported), build file judge `jev-1.13.0` calls 20, tokens in 40334, cost 0.0017 USD, phases: path_judge 1.3 s, classifier 0.7 s, context 58.8 s, model_review 481.8 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 4026d37).<br>
Reviewed head `3cb75bada467cbfbacf2cd78e5977c47566c88d5` against base `4026d3725d9e99adc100db4d3577b33f296b130a`, werkrevisor 0.32.1.
</sub>
werkrevisor: YELLOW / HUMAN_REVIEW for 3cb75bada467cbfbacf2cd78e5977c47566c88d5.
The result comment of werkrevisor holds the details.
werkrevisor: YELLOW / HUMAN_REVIEW for `3cb75bada467cbfbacf2cd78e5977c47566c88d5`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:3cb75bada467cbfbacf2cd78e5977c47566c88d5 -->
`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM and swap (`/proc/meminfo`), 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.
`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.
`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 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
- `werkator.js` mirrors it: `renderSwapOutCard`, and `drawSpark` takes `limits` for a rate — the dashed lines at the two thresholds, the colours by them, and the critical line at 90 % of the height as for a total.
The average beside the value is the bare number, since the value already carries the unit.
- The home rail keeps swap-out as a tile, not a row: `Swap-out` and `0.02 MiB/s` fit neither the label nor the value column of a row.
- From 1100 px window width the cards and the tiles each flow into one row (`grid-auto-flow: column`); the tile values are 16 px there, since `0 of 1 running` breaks at 18 px.
Layout numbers slightly off, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.71, severity 1.2 of 3
The Solution states "At 1100 px five cards are 196 px wide and seven tiles 139 px" but with the CSS gap: 14px on .gauges and gap: 10px on .tiles, the math gives ~209 px per card and ~149 px per tile, not 196/139. The numbers don't match what the implemented layout actually produces.
**Layout numbers slightly off**, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.71, severity 1.2 of 3
The Solution states "At 1100 px five cards are 196 px wide and seven tiles 139 px" but with the CSS `gap: 14px` on `.gauges` and `gap: 10px` on `.tiles`, the math gives ~209 px per card and ~149 px per tile, not 196/139. The numbers don't match what the implemented layout actually produces.
ai-reviewer
scheduled this pull request to auto merge when all checks succeed 2026-10-10 16:02:27 +02:00
ai-reviewer
requested review from mi 2026-10-10 16:02:27 +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#146-swap-out-card.md
🤖 Generated with Claude Code
🟡 werkrevisor: YELLOW / HUMAN_REVIEW
Change kind:
mixed.Reasons
Findings
docs/prs/2026-10-10-PR#146-swap-out-card.md:134-135, severity unknown, counted as medium, hint, as a line commentClassifier
10 of 12 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 93657, calls 1, tokens in 89795 / out 29540 (reasoning included in out), duration 544.6 s, cost 0.0408 USD (reported), classifier `jev-1.13.0` calls 12, tokens in 10757, cost 0.0005 USD, finding judge `jev-1.13.0` calls 1, tokens in 2235, cost 0.0001 USD, excerpt selection `openrouter/openai/gpt-6-luna` calls 14, tokens in 187497 / out 3818, cost 0.0253 USD (reported), build file judge `jev-1.13.0` calls 20, tokens in 40334, cost 0.0017 USD, phases: path_judge 1.3 s, classifier 0.7 s, context 58.8 s, model_review 481.8 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main3 answers ignored because of a confidence below [classifier] min_confidence.
4026d37).Reviewed head `3cb75bada467cbfbacf2cd78e5977c47566c88d5` against base `4026d3725d9e99adc100db4d3577b33f296b130a`, werkrevisor 0.32.1.
werkrevisor: YELLOW / HUMAN_REVIEW for
3cb75bada467cbfbacf2cd78e5977c47566c88d5.The result comment of werkrevisor holds the details.
@@ -119,3 +119,3 @@## System Metrics`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM and swap (`/proc/meminfo`), 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.`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.Writing 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 (884) (werkrevisor writing rule)
Fix the line, or reply why it stays, and resolve this comment.
@@ -122,2 +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 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 sparklinWriting 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 (5522) (werkrevisor writing rule)
Fix the line, or reply why it stays, and resolve this comment.
@@ -0,0 +131,4 @@- `werkator.js` mirrors it: `renderSwapOutCard`, and `drawSpark` takes `limits` for a rate — the dashed lines at the two thresholds, the colours by them, and the critical line at 90 % of the height as for a total.The average beside the value is the bare number, since the value already carries the unit.- The home rail keeps swap-out as a tile, not a row: `Swap-out` and `0.02 MiB/s` fit neither the label nor the value column of a row.- From 1100 px window width the cards and the tiles each flow into one row (`grid-auto-flow: column`); the tile values are 16 px there, since `0 of 1 running` breaks at 18 px.Layout numbers slightly off, severity unknown, counted as medium (werkrevisor)
hint; Jev: real 0.71, severity 1.2 of 3
The Solution states "At 1100 px five cards are 196 px wide and seven tiles 139 px" but with the CSS
gap: 14pxon.gaugesandgap: 10pxon.tiles, the math gives ~209 px per card and ~149 px per tile, not 196/139. The numbers don't match what the implemented layout actually produces.