Watch
0
0
Fork
You've already forked hyperhive
0

review classifier: recover from a stored record it can't decode

The controller keeps the classifier's newest runs as one JSON array and
its settings as one JSON object, each under one KV key. A document this
build could not decode, as after a rollback past a build that added an
outcome or a settings field, failed every read and every write until
someone deleted the key by hand. A classifier newer than its controller
refused every run request for the missing `model`.

The run store now decodes the array one record at a time: a record that
does not decode is dropped with a WARN naming the bucket, key, index and
error, and a document that is not an array reads as no runs. Listing
keeps working, and the next recorded run writes back only the records
that decoded.

The stored settings ignore a field this build does not know; one that
still does not decode reads as the defaults with a WARN. The PUT body
keeps `deny_unknown_fields`. A PUT now always writes, so a PUT of the
defaults also replaces an undecodable document.

A run request without `model` runs on haiku, the swarm-wide setting's
default, now one constant in swarm-queue-client.

`schedules::read` gains a sibling, `read_with`, taking the decoder.

Closes #5162
This commit is contained in:
atlas 2026-10-13 02:24:26 +02:00 • committed by mara
commit 6f0a72b246
5 changed files with 208 additions and 17 deletions

View file

@ -407,6 +407,18 @@ async fn open_or_create_definitions(client: &async_nats::Client) -> anyhow::Resu
pub(crate) async fn read<T: Default + serde::de::DeserializeOwned>(
store: &Store,
key: &str,
) -> anyhow::Result<(T, Option<u64>)> {
read_with(store, key, |bytes| {
serde_json::from_slice(bytes).with_context(|| format!("{key} is not decodable"))
})
.await
}
/// As [`read`], with the stored bytes decoded by `decode`.
pub(crate) async fn read_with<T: Default>(
store: &Store,
key: &str,
decode: impl FnOnce(&[u8]) -> anyhow::Result<T>,
) -> anyhow::Result<(T, Option<u64>)> {
let Some(entry) = store
.entry(key)
@ -418,9 +430,7 @@ pub(crate) async fn read<T: Default + serde::de::DeserializeOwned>(
if entry.operation != Operation::Put {
return Ok((T::default(), Some(entry.revision)));
}
let value =
serde_json::from_slice(&entry.value).with_context(|| format!("{key} is not decodable"))?;
Ok((value, Some(entry.revision)))
Ok((decode(&entry.value)?, Some(entry.revision)))
}
/// Write `value` to `key` against `revision`. `Ok(false)` when another write