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

@ -376,14 +376,16 @@ pub(super) async fn post_resource_limits(
{
return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response();
}
if let Some(v) = memory_max
&& let Err(e) = crate::resource_limits::validate_memory_max(v)
{
return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response();
}
// Returns the value to store, not just an ok/err verdict — see
// `validate_memory_max`'s own doc for why that isn't always `v`.
let memory_max = match memory_max.map(crate::resource_limits::validate_memory_max) {
Some(Err(e)) => return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response(),
Some(Ok(v)) => Some(v),
None => None,
};
let limits = crate::resource_limits::AgentLimits {
cpu_quota: cpu_quota.map(str::to_owned),
memory_max: memory_max.map(str::to_owned),
memory_max,
};
if let Err(e) = crate::meta::commit_resource_limits(ident.as_str(), &limits).await {
return error_response(&format!("set limits {logical}: {e:#}"));