Skip to main content

qualia_core_db/services/swarm/
job.rs

1//! The job envelope: a deterministic, content-addressed specification of work plus
2//! its result. "Deterministic" matters — a job is reproducible from its `(kind, input,
3//! seed)`, so any node can re-derive or verify it, and a paid job's outcome is
4//! auditable rather than taken on trust.
5
6use crate::solvers::learning::kg_embedding::{EmbeddingTable, TrainConfig};
7
8/// The kind of work a job carries. Each maps to a kernel-class with a CPU reference
9/// (§13) and a verification strategy ([`super::verify`]).
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum JobKind {
12    /// Dense matrix product `C = A·B` (kernel-class `DenseLinear`). Verified by
13    /// Freivalds' algorithm.
14    DenseLinearProduct,
15    /// Train a knowledge-graph embedding table (the heavy, run-once, affordability-
16    /// gated artifact). Verified by ranking reproduction on a held-out check set.
17    EmbeddingArtifact,
18}
19
20/// How a job is dispatched across the socially-defined network.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum JobMode {
23    /// The principal's own devices cooperate. No payment.
24    Personal,
25    /// Done with named peers (DID hashes). No payment.
26    Collaborative { peers: Vec<u64> },
27    /// Dispatched to a provider for payment — the solar-excess case.
28    Paid {
29        requester_did: u64,
30        provider_did: u64,
31        /// Agreed price in abstract minor units (µ-units), settled only on `Verified`.
32        price_micro_units: u64,
33    },
34}
35
36/// The actual work payload. Carries the real inputs (content-addressed into the job
37/// id), so an executor has everything it needs and a verifier can re-derive the answer.
38#[derive(Debug, Clone, PartialEq)]
39pub enum JobInput {
40    /// `A` is `m×k`, `B` is `k×n`, both row-major.
41    DenseLinearProduct {
42        m: usize,
43        k: usize,
44        n: usize,
45        a: Vec<f64>,
46        b: Vec<f64>,
47    },
48    /// Train an embedding over `triples`; `check` is the held-out set used to verify
49    /// the returned table actually learned (not just terminated).
50    EmbeddingArtifact {
51        triples: Vec<(usize, usize, usize)>,
52        n_entities: usize,
53        n_relations: usize,
54        cfg: TrainConfig,
55        check: Vec<(usize, usize, usize)>,
56    },
57}
58
59impl JobInput {
60    pub fn kind(&self) -> JobKind {
61        match self {
62            JobInput::DenseLinearProduct { .. } => JobKind::DenseLinearProduct,
63            JobInput::EmbeddingArtifact { .. } => JobKind::EmbeddingArtifact,
64        }
65    }
66
67    /// True if the input is internally well-formed (dimensions consistent).
68    pub fn is_well_formed(&self) -> bool {
69        match self {
70            JobInput::DenseLinearProduct { m, k, n, a, b } => {
71                *m > 0 && *k > 0 && *n > 0 && a.len() == m * k && b.len() == k * n
72            }
73            JobInput::EmbeddingArtifact {
74                triples,
75                n_entities,
76                n_relations,
77                ..
78            } => {
79                !triples.is_empty()
80                    && *n_entities > 0
81                    && *n_relations > 0
82                    && triples
83                        .iter()
84                        .all(|&(h, r, t)| h < *n_entities && t < *n_entities && r < *n_relations)
85            }
86        }
87    }
88}
89
90/// The result an executor returns.
91#[derive(Debug, Clone, PartialEq)]
92pub enum JobResult {
93    DenseLinearProduct { c: Vec<f64> },
94    EmbeddingArtifact { table: EmbeddingTable },
95}
96
97impl JobResult {
98    pub fn kind(&self) -> JobKind {
99        match self {
100            JobResult::DenseLinearProduct { .. } => JobKind::DenseLinearProduct,
101            JobResult::EmbeddingArtifact { .. } => JobKind::EmbeddingArtifact,
102        }
103    }
104}
105
106/// A full job: a content-addressed id, its dispatch mode, and the work payload.
107#[derive(Debug, Clone, PartialEq)]
108pub struct JobSpec {
109    /// Content id — a deterministic hash of `input`. Two identical jobs share an id.
110    pub id: u64,
111    pub mode: JobMode,
112    pub input: JobInput,
113}
114
115impl JobSpec {
116    pub fn new(mode: JobMode, input: JobInput) -> Self {
117        let id = content_id(&input);
118        Self { id, mode, input }
119    }
120
121    pub fn kind(&self) -> JobKind {
122        self.input.kind()
123    }
124}
125
126/// Deterministic content id (FNV-1a over the structural bytes of the input). Stable
127/// across nodes — the basis for content-addressed dispatch and dedup.
128pub fn content_id(input: &JobInput) -> u64 {
129    let mut h: u64 = 0xcbf29ce484222325;
130    let byte = |b: u8, h: &mut u64| {
131        *h ^= b as u64;
132        *h = h.wrapping_mul(0x100000001b3);
133    };
134    let word = |w: u64, h: &mut u64| {
135        for b in w.to_le_bytes() {
136            byte(b, h);
137        }
138    };
139    match input {
140        JobInput::DenseLinearProduct { m, k, n, a, b } => {
141            byte(0x01, &mut h);
142            word(*m as u64, &mut h);
143            word(*k as u64, &mut h);
144            word(*n as u64, &mut h);
145            for &v in a.iter().chain(b.iter()) {
146                word(v.to_bits(), &mut h);
147            }
148        }
149        JobInput::EmbeddingArtifact {
150            triples,
151            n_entities,
152            n_relations,
153            cfg,
154            check,
155        } => {
156            byte(0x02, &mut h);
157            word(*n_entities as u64, &mut h);
158            word(*n_relations as u64, &mut h);
159            word(cfg.seed, &mut h);
160            word(cfg.rank as u64, &mut h);
161            word(cfg.epochs as u64, &mut h);
162            for &(a, b, c) in triples.iter().chain(check.iter()) {
163                word(a as u64, &mut h);
164                word(b as u64, &mut h);
165                word(c as u64, &mut h);
166            }
167        }
168    }
169    h
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    fn dense() -> JobInput {
177        JobInput::DenseLinearProduct {
178            m: 2,
179            k: 2,
180            n: 2,
181            a: vec![1.0, 2.0, 3.0, 4.0],
182            b: vec![1.0, 0.0, 0.0, 1.0],
183        }
184    }
185
186    #[test]
187    fn content_id_is_deterministic_and_input_sensitive() {
188        let a = content_id(&dense());
189        let b = content_id(&dense());
190        assert_eq!(a, b, "same input → same id");
191        let mut other = dense();
192        if let JobInput::DenseLinearProduct { a, .. } = &mut other {
193            a[0] = 9.0;
194        }
195        assert_ne!(content_id(&other), a, "different input → different id");
196    }
197
198    #[test]
199    fn well_formedness_catches_bad_dims() {
200        assert!(dense().is_well_formed());
201        let bad = JobInput::DenseLinearProduct {
202            m: 2,
203            k: 2,
204            n: 2,
205            a: vec![1.0],
206            b: vec![1.0; 4],
207        };
208        assert!(!bad.is_well_formed());
209    }
210
211    #[test]
212    fn spec_carries_kind_and_id() {
213        let spec = JobSpec::new(JobMode::Personal, dense());
214        assert_eq!(spec.kind(), JobKind::DenseLinearProduct);
215        assert_eq!(spec.id, content_id(&dense()));
216    }
217}