Skip to main content

kernel/jobs/
seeding.rs

1//! Seed injection for job payloads. A job's seed is fixed at submit time so the
2//! exact seed that executes is what lands in the artifact's provenance.
3
4use std::collections::BTreeMap;
5use std::hash::{BuildHasher, Hasher};
6
7use crate::records::JsonValue;
8
9/// Inject a random `seed` into `payload` when it is a JSON object (or null,
10/// treated as empty) that carries no non-null `seed`. Anything else, and any
11/// object that already pins a seed, is returned unchanged.
12pub fn seeded(payload: &JsonValue) -> JsonValue {
13    let Some(mut fields) = seedable_fields(payload) else {
14        return payload.clone();
15    };
16    if let Some(seed) = fields.get("seed")
17        && *seed != JsonValue::Null
18    {
19        return JsonValue::Object(fields);
20    }
21    fields.insert("seed".to_owned(), JsonValue::Int(random_seed()));
22    JsonValue::Object(fields)
23}
24
25/// Re-seed `params` with a fresh seed guaranteed to differ from its current one
26/// (used by "vary"). A non-object payload is returned unchanged.
27pub fn reseeded(params: &JsonValue) -> JsonValue {
28    let Some(mut fields) = seedable_fields(params) else {
29        return params.clone();
30    };
31    let previous = fields.get("seed").cloned();
32    let mut fresh = JsonValue::Int(random_seed());
33    while Some(&fresh) == previous.as_ref() {
34        fresh = JsonValue::Int(random_seed());
35    }
36    fields.insert("seed".to_owned(), fresh);
37    JsonValue::Object(fields)
38}
39
40fn seedable_fields(payload: &JsonValue) -> Option<BTreeMap<String, JsonValue>> {
41    match payload {
42        JsonValue::Object(fields) => Some(fields.clone()),
43        JsonValue::Null => Some(BTreeMap::new()),
44        _ => None,
45    }
46}
47
48/// A pseudo-random value in `0..u32::MAX`. Drawn from `RandomState`'s OS-seeded
49/// hasher keys so no `rand` crate is needed.
50fn random_seed() -> i64 {
51    let entropy = std::collections::hash_map::RandomState::new()
52        .build_hasher()
53        .finish();
54    (entropy % u32::MAX as u64) as i64
55}