hive-c0re: accept friendly MemoryMax size spellings, normalize for systemd

mara hit this directly: setting an agent's memory cap to "16GB"
fails with 'invalid MemoryMax "16GB": expected a size such as
"8G"...'. Confirmed directly against a running systemd 260
(systemd-run -p MemoryMax=<value>): systemd's own parser accepts a
bare byte count, <digits>B, or <digits> plus exactly one uppercase
K/M/G/T, and rejects both "16GB" (redundant B after a multiplier)
and "16g" (lowercase) with "Invalid argument". So the prior
rejection of "16GB" matched systemd, but is bad UX for input a
human reasonably expects to work.

validate_memory_max now returns the value to store (not just an
ok/err verdict): it accepts 8gb/8Gb/8GB/8g/8G interchangeably and
normalizes all of them to systemd's own 8G form before it is ever
persisted or passed downstream. Also fixes the adjacent bug the same
investigation turned up: the old validator incorrectly accepted
lowercase (8g) even though systemd itself rejects it.

Updated both call sites (server.rs, dashboard/lifecycle_ops.rs) to
use the normalized return value. New test
friendly_size_spellings_normalize_to_systemds_own_form; flipped the
old "8GB should be rejected" assertion, which encoded the
human-hostile behavior this fixes.
This commit is contained in:
iris 2026-09-19 01:35:57 +02:00 committed by mara
commit 27bed74d42
3 changed files with 85 additions and 23 deletions

View file

@ -502,13 +502,17 @@ async fn handle_set_resource_limits(
if let Some(value) = cpu_quota {
crate::resource_limits::validate_cpu_quota(value).map_err(anyhow::Error::msg)?;
}
if let Some(value) = memory_max {
crate::resource_limits::validate_memory_max(value).map_err(anyhow::Error::msg)?;
}
// `validate_memory_max` returns the value to store, not just an
// ok/err verdict: a friendly spelling like "8GB" is accepted but
// normalized to the "8G" systemd's own parser actually takes.
let memory_max = memory_max
.map(crate::resource_limits::validate_memory_max)
.transpose()
.map_err(anyhow::Error::msg)?;
tracing::info!(%name, ?cpu_quota, ?memory_max, "set_resource_limits");
let limits = crate::resource_limits::AgentLimits {
cpu_quota: cpu_quota.map(ToOwned::to_owned),
memory_max: memory_max.map(ToOwned::to_owned),
memory_max,
};
// Goes through `meta::commit_resource_limits`, not the bare
// `resource_limits::set_limits`: the write has to be staged +