Skip to main content

provable_contracts/ontology/extract/
kernel.rs

1//! ONT-001 §3.7, §5 ONT-4c4 — `extract:kernel` over `extract:code`: a bound `#[kernel]` symbol becomes an
2//! `ont:KernelSymbol` focus node, and the tracked kernel receipts (`apr-kernel-receipt/v1`) are joined to it.
3//!
4//! **Over the `syn` walk, not beside it.** ONT-4b2's `extract:code` already resolves every bound symbol and
5//! records its attributes by path; a symbol whose attributes include `kernel` is the cuda-oxide sense of a kernel
6//! (pure Rust → PTX). This extractor adds one `rdf:type ont:KernelSymbol` to the SAME IRI and declares
7//! `ont:KernelSymbol rdfs:subClassOf ont:Symbol`, so every `ont:Symbol` shape still sees it. A hand-written PTX string
8//! is never a focus node: nothing binds it, so the walk never finds it.
9//!
10//! **The reference.** A kernel's CPU reference is the resolved, non-`#[kernel]` symbol the same contract binds to
11//! the same equation — the contract names it by binding both. None → `kernel:referenceMissing` naming the
12//! kernel, which `kernel-parity` refuses (a kernel with nothing to be at parity WITH is not at parity).
13//!
14//! **The receipts.** Every `*.json` under [`EVIDENCE_DIR`] must carry [`SCHEMA`]; any other schema (the ladder's
15//! among them) is refused by name, exit 3 — a receipt under the wrong root is a declaration fault, never skipped.
16//! A row is a **witness** iff its `kernel` equals the symbol (bare name or `module::name`) and its `sha` equals the
17//! receipt's build `sha`; a matching row with a different sha is `kernel:receiptShaMismatch` naming the file.
18//! Per witness the resolver materializes:
19//!
20//! | fact | rule |
21//! |---|---|
22//! | `kernel:parityOn "<host>"` | `cos ≥ 0.9999 ∧ max\|Δ\| < 1e-3` on some witness row for that host |
23//! | `kernel:missingParityHost "<host>"` | a required host without `parityOn` |
24//! | `kernel:timingOn "<host>"` | `oxide_us / ptx_us ≤ 1.2` on some witness row, or an exemption receipt |
25//! | `kernel:missingTimingHost "<host>"` | a required host without `timingOn` |
26//! | `kernel:safety` | `"true"` iff `sym:unsafeFree ∧ sym:boundsChecked ∧ kernel:registerBudget` present |
27//!
28//! Required hosts: the lines of [`REQUIRED_HOSTS_FILE`] when it exists, otherwise every host with a witness row
29//! for that kernel — so a host that measured below parity is named, never averaged away. An exemption receipt
30//! is the file `evidence/kernels/<kernel>/EXEMPTION` (non-empty: the reason), recorded as
31//! `kernel:exemptionReceipt`. Deterministic: files in byte order, the graph a set (R-15).
32
33use std::collections::{BTreeMap, BTreeSet};
34use std::path::{Path, PathBuf};
35
36use super::code::sym;
37use crate::ontology::rdf::{iri, ont, Graph, Term, RDF_TYPE};
38use crate::ontology::shapes::RDFS_SUBCLASS_OF;
39
40/// The one layout this reader accepts under [`EVIDENCE_DIR`].
41pub const SCHEMA: &str = "apr-kernel-receipt/v1";
42/// Where the kernel receipts live, relative to the repository root: `<kernel>/<host>.json`.
43pub const EVIDENCE_DIR: &str = "evidence/kernels";
44/// Optional: one required CUDA host per line (`#` comments). Absent → the hosts that measured.
45pub const REQUIRED_HOSTS_FILE: &str = "evidence/kernels/REQUIRED_HOSTS";
46/// `parityOn` floor on cosine similarity.
47pub const PARITY_MIN_COS: f64 = 0.9999;
48/// `parityOn` ceiling on max |Δ| (strict).
49pub const PARITY_MAX_DIFF: f64 = 1e-3;
50/// `timingOn` ceiling on `oxide_us / ptx_us`.
51pub const TIMING_MAX_RATIO: f64 = 1.2;
52
53/// A `kernel:*` vocabulary term.
54#[must_use]
55pub fn kern(name: &str) -> String {
56    ont(&format!("kernel/{name}"))
57}
58
59/// A receipt this reader refused, by file.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct KernelError {
62    pub file: String,
63    pub reason: String,
64}
65
66impl std::fmt::Display for KernelError {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "kernel receipt {}: {}", self.file, self.reason)
69    }
70}
71
72impl std::error::Error for KernelError {}
73
74/// Counts the extractor reports beside the graph.
75#[derive(Debug, Clone, Default, PartialEq, Eq)]
76pub struct KernelStats {
77    /// `ont:KernelSymbol` focus nodes.
78    pub kernels: usize,
79    pub receipt_files: usize,
80    pub witnesses: usize,
81    pub sha_mismatches: usize,
82    /// Rows naming no bound kernel: counted, not graded.
83    pub orphan_rows: usize,
84}
85
86/// One row of one receipt, as read.
87#[derive(Debug, Clone, PartialEq)]
88pub struct Row {
89    pub kernel: String,
90    pub host: String,
91    pub cc: String,
92    pub sha: String,
93    pub cos: Option<f64>,
94    pub maxdiff: Option<f64>,
95    pub oxide_us: Option<f64>,
96    pub ptx_us: Option<f64>,
97    pub register_budget: Option<u64>,
98    pub ptxas_version: String,
99    pub authoring: String,
100}
101
102/// One receipt file: its build sha and its rows.
103#[derive(Debug, Clone, PartialEq)]
104pub struct ReceiptFile {
105    /// Repo-relative path.
106    pub file: String,
107    pub sha: String,
108    pub rows: Vec<Row>,
109}
110
111/// What the resolver needs besides the graph.
112#[derive(Debug, Clone, Default)]
113pub struct Inputs {
114    pub receipts: Vec<ReceiptFile>,
115    /// `None` → per kernel, the hosts that measured.
116    pub required_hosts: Option<Vec<String>>,
117    /// Kernel name → repo-relative exemption file.
118    pub exemptions: BTreeMap<String, String>,
119}
120
121/// Read the receipts, the required hosts and the exemptions under `root`, then type and join the kernels in `g`.
122///
123/// # Errors
124/// A file under [`EVIDENCE_DIR`] that is not JSON or does not carry [`SCHEMA`], named.
125pub fn extract(root: &Path, g: &mut Graph) -> Result<KernelStats, KernelError> {
126    let inputs = read_inputs(root)?;
127    let stats = resolve(g, &inputs);
128    symbol_and_contract(g)?;
129    Ok(stats)
130}
131
132fn read_inputs(root: &Path) -> Result<Inputs, KernelError> {
133    let dir = root.join(EVIDENCE_DIR);
134    let mut files = Vec::new();
135    walk(&dir, &mut files);
136    files.sort();
137    let rel = |p: &Path| {
138        p.strip_prefix(root)
139            .unwrap_or(p)
140            .to_string_lossy()
141            .replace('\\', "/")
142    };
143    let receipts = files
144        .iter()
145        .map(|f| {
146            let name = rel(f);
147            let text = std::fs::read_to_string(f).map_err(|e| KernelError {
148                file: name.clone(),
149                reason: format!("unreadable: {e}"),
150            })?;
151            let v: serde_json::Value = serde_json::from_str(&text).map_err(|e| KernelError {
152                file: name.clone(),
153                reason: format!("not JSON: {e}"),
154            })?;
155            parse_receipt(&name, &v)
156        })
157        .collect::<Result<Vec<_>, _>>()?;
158    let required_hosts = std::fs::read_to_string(root.join(REQUIRED_HOSTS_FILE))
159        .ok()
160        .map(|t| {
161            t.lines()
162                .map(|l| l.split('#').next().unwrap_or_default().trim().to_string())
163                .filter(|l| !l.is_empty())
164                .collect()
165        });
166    let mut exemptions = BTreeMap::new();
167    if let Ok(rd) = std::fs::read_dir(&dir) {
168        for e in rd.flatten() {
169            let p = e.path().join("EXEMPTION");
170            let reason = std::fs::read_to_string(&p).unwrap_or_default();
171            if !reason.trim().is_empty() {
172                exemptions.insert(e.file_name().to_string_lossy().into_owned(), rel(&p));
173            }
174        }
175    }
176    Ok(Inputs {
177        receipts,
178        required_hosts,
179        exemptions,
180    })
181}
182
183fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
184    let Ok(rd) = std::fs::read_dir(dir) else {
185        return;
186    };
187    for e in rd.flatten() {
188        let p = e.path();
189        if p.is_dir() {
190            walk(&p, out);
191        } else if p.extension().and_then(|x| x.to_str()) == Some("json") {
192            out.push(p);
193        }
194    }
195}
196
197fn s(v: &serde_json::Value, k: &str) -> String {
198    match v.get(k) {
199        Some(serde_json::Value::String(s)) => s.clone(),
200        Some(serde_json::Value::Number(n)) => n.to_string(),
201        _ => String::new(),
202    }
203}
204
205fn f(v: &serde_json::Value, outer: &str, k: &str) -> Option<f64> {
206    v.get(outer)
207        .and_then(|o| o.get(k))
208        .and_then(serde_json::Value::as_f64)
209}
210
211/// One receipt, schema-checked.
212///
213/// # Errors
214/// A `schema` other than [`SCHEMA`] (the ladder's, or none), or no build `sha`, named by file.
215pub fn parse_receipt(file: &str, v: &serde_json::Value) -> Result<ReceiptFile, KernelError> {
216    let schema = s(v, "schema");
217    if schema != SCHEMA {
218        return Err(KernelError {
219            file: file.to_string(),
220            reason: format!(
221                "schema `{schema}` under {EVIDENCE_DIR}/ — this root holds {SCHEMA} only"
222            ),
223        });
224    }
225    let sha = s(v, "sha").to_ascii_lowercase();
226    if sha.is_empty() {
227        return Err(KernelError {
228            file: file.to_string(),
229            reason: "no build `sha`: no row can be a witness of an unnamed build".into(),
230        });
231    }
232    let rows = v
233        .get("rows")
234        .and_then(serde_json::Value::as_array)
235        .map(|rows| {
236            rows.iter()
237                .map(|r| Row {
238                    kernel: s(r, "kernel"),
239                    host: s(r, "host"),
240                    cc: s(r, "cc"),
241                    sha: s(r, "sha").to_ascii_lowercase(),
242                    cos: f(r, "parity", "cos"),
243                    maxdiff: f(r, "parity", "maxdiff"),
244                    oxide_us: f(r, "timing", "oxide_us"),
245                    ptx_us: f(r, "timing", "ptx_us"),
246                    register_budget: r.get("register_budget").and_then(serde_json::Value::as_u64),
247                    ptxas_version: s(r, "ptxas_version"),
248                    authoring: s(r, "authoring"),
249                })
250                .collect()
251        })
252        .unwrap_or_default();
253    Ok(ReceiptFile {
254        file: file.to_string(),
255        sha,
256        rows,
257    })
258}
259
260fn literal<'g>(g: &'g Graph, subject: &str, predicate: &str) -> Vec<&'g str> {
261    g.objects(subject, predicate)
262        .into_iter()
263        .filter_map(|t| t.as_literal().map(|(v, _)| v))
264        .collect()
265}
266
267/// The bound `#[kernel]` symbols in `g`, resolved, in byte order.
268fn kernel_symbols(g: &Graph) -> Vec<String> {
269    let mut out: Vec<String> = g
270        .instances_of(&ont("Symbol"))
271        .into_iter()
272        .filter(|s| literal(g, s, &sym("attribute")).contains(&"kernel"))
273        .filter(|s| literal(g, s, &sym("resolved")).contains(&"true"))
274        .map(String::from)
275        .collect();
276    out.sort();
277    out.dedup();
278    out
279}
280
281/// The CPU reference of `kernel`: a resolved non-kernel symbol bound to the same contract equation.
282fn reference_of(g: &Graph, kernel: &str) -> Option<String> {
283    let contracts: BTreeSet<String> = g
284        .objects(kernel, &sym("implements"))
285        .into_iter()
286        .filter_map(|t| t.as_iri().map(String::from))
287        .collect();
288    let equations: BTreeSet<&str> = literal(g, kernel, &sym("equation")).into_iter().collect();
289    let mut found: Vec<&str> = g
290        .instances_of(&ont("Symbol"))
291        .into_iter()
292        .filter(|s| *s != kernel)
293        .filter(|s| !literal(g, s, &sym("attribute")).contains(&"kernel"))
294        .filter(|s| literal(g, s, &sym("resolved")).contains(&"true"))
295        .filter(|s| {
296            g.objects(s, &sym("implements"))
297                .into_iter()
298                .any(|t| t.as_iri().is_some_and(|i| contracts.contains(i)))
299        })
300        .filter(|s| {
301            literal(g, s, &sym("equation"))
302                .iter()
303                .any(|e| equations.contains(e))
304        })
305        .collect();
306    found.sort_unstable();
307    found.first().map(|s| (*s).to_string())
308}
309
310/// `row.kernel ∈ {name(k), module(k)::name(k)}` (ont-kernel-receipts-v1 `witness`). A bare module is NOT a
311/// name: a module-only row would witness every kernel in that module.
312fn names_kernel(row: &str, name: &str, module: &str) -> bool {
313    row == name || row == format!("{module}::{name}")
314}
315
316/// Type every bound `#[kernel]` in `g` and join `inputs` to it.
317/// What one kernel's witness rows established, per host.
318#[derive(Default)]
319struct Witnessed {
320    parity_on: BTreeSet<String>,
321    timing_on: BTreeSet<String>,
322    measured: BTreeSet<String>,
323    budget: bool,
324}
325
326fn first_literal(g: &Graph, k: &str, p: &str) -> String {
327    literal(g, k, &sym(p))
328        .first()
329        .map(|s| (*s).to_string())
330        .unwrap_or_default()
331}
332
333fn within_timing(row: &Row) -> bool {
334    match (row.oxide_us, row.ptx_us) {
335        (Some(o), Some(p)) if p > 0.0 => o / p <= TIMING_MAX_RATIO,
336        _ => false,
337    }
338}
339
340fn at_parity(row: &Row) -> bool {
341    row.cos.is_some_and(|c| c >= PARITY_MIN_COS)
342        && row.maxdiff.is_some_and(|d| d.abs() < PARITY_MAX_DIFF)
343}
344
345/// Type `k`, name its CPU reference (or its absence) and its exemption.
346fn type_kernel(g: &mut Graph, k: &str, name: &str, module: &str, inputs: &Inputs) {
347    g.insert(k.to_string(), RDF_TYPE, Term::iri(ont("KernelSymbol")));
348    g.insert(k.to_string(), kern("authoring"), Term::string("oxide"));
349    match reference_of(g, k) {
350        Some(r) => g.insert(k.to_string(), kern("reference"), Term::iri(r)),
351        None => g.insert(
352            k.to_string(),
353            kern("referenceMissing"),
354            Term::string(format!(
355                "#[kernel] {module}::{name} has no CPU reference: its contract binds no other resolved symbol to its equation"
356            )),
357        ),
358    }
359    if let Some(ex) = inputs.exemptions.get(name) {
360        g.insert(k.to_string(), kern("exemptionReceipt"), Term::string(ex));
361    }
362}
363
364/// Grade one witness row (sha already matched) into `w`.
365fn witness(g: &mut Graph, k: &str, rf: &ReceiptFile, ri: usize, exempt: bool, w: &mut Witnessed) {
366    let row = &rf.rows[ri];
367    let rnode = iri("kernel", &format!("{}#{ri}", rf.file));
368    emit_witness(g, &rnode, row);
369    g.insert(k.to_string(), kern("parityReceipt"), Term::iri(&rnode));
370    w.measured.insert(row.host.clone());
371    if let Some(b) = row.register_budget {
372        w.budget = true;
373        g.insert(k.to_string(), kern("registerBudget"), Term::integer(b));
374    }
375    if at_parity(row) {
376        w.parity_on.insert(row.host.clone());
377    }
378    if within_timing(row) || exempt {
379        w.timing_on.insert(row.host.clone());
380    }
381}
382
383/// Join every receipt row that names `k` to it; a row with the wrong sha is named, never counted.
384fn join_receipts(
385    g: &mut Graph,
386    k: &str,
387    name: &str,
388    module: &str,
389    inputs: &Inputs,
390    stats: &mut KernelStats,
391    claimed: &mut BTreeSet<(usize, usize)>,
392) -> Witnessed {
393    let exempt = inputs.exemptions.contains_key(name);
394    let mut w = Witnessed::default();
395    for (fi, rf) in inputs.receipts.iter().enumerate() {
396        for (ri, row) in rf.rows.iter().enumerate() {
397            if !names_kernel(&row.kernel, name, module) {
398                continue;
399            }
400            claimed.insert((fi, ri));
401            if row.sha != rf.sha || row.sha.is_empty() {
402                stats.sha_mismatches += 1;
403                let why = format!(
404                    "{} row {ri}: sha {} is not the receipt's build sha {}",
405                    rf.file, row.sha, rf.sha
406                );
407                g.insert(k.to_string(), kern("receiptShaMismatch"), Term::string(why));
408                continue;
409            }
410            stats.witnesses += 1;
411            witness(g, k, rf, ri, exempt, &mut w);
412        }
413    }
414    w
415}
416
417/// Materialize the per-host facts and the safety verdict for `k`.
418fn emit_hosts(
419    g: &mut Graph,
420    k: &str,
421    w: Witnessed,
422    required_hosts: Option<&Vec<String>>,
423    safe_code: bool,
424) {
425    let required: BTreeSet<String> = match required_hosts {
426        Some(h) => h.iter().cloned().collect(),
427        None => w.measured,
428    };
429    let facts = [
430        ("parityOn", w.parity_on.iter().collect::<Vec<_>>()),
431        ("timingOn", w.timing_on.iter().collect()),
432        (
433            "missingParityHost",
434            required.difference(&w.parity_on).collect(),
435        ),
436        (
437            "missingTimingHost",
438            required.difference(&w.timing_on).collect(),
439        ),
440    ];
441    for (p, hosts) in facts {
442        for h in hosts {
443            g.insert(k.to_string(), kern(p), Term::string(h));
444        }
445    }
446    let safe = safe_code && w.budget;
447    g.insert(
448        k.to_string(),
449        kern("safety"),
450        Term::string(safe.to_string()),
451    );
452}
453
454/// Type every bound `#[kernel]` in `g` and join `inputs` to it.
455pub fn resolve(g: &mut Graph, inputs: &Inputs) -> KernelStats {
456    g.insert(
457        ont("KernelSymbol"),
458        RDFS_SUBCLASS_OF.to_string(),
459        Term::iri(ont("Symbol")),
460    );
461    let kernels = kernel_symbols(g);
462    let mut stats = KernelStats {
463        kernels: kernels.len(),
464        receipt_files: inputs.receipts.len(),
465        ..KernelStats::default()
466    };
467    let mut claimed: BTreeSet<(usize, usize)> = BTreeSet::new();
468    for k in &kernels {
469        let name = first_literal(g, k, "name");
470        let module = first_literal(g, k, "module");
471        let safe_code = literal(g, k, &sym("unsafeFree")).contains(&"true")
472            && literal(g, k, &sym("boundsChecked")).contains(&"true");
473        type_kernel(g, k, &name, &module, inputs);
474        let w = join_receipts(g, k, &name, &module, inputs, &mut stats, &mut claimed);
475        emit_hosts(g, k, w, inputs.required_hosts.as_ref(), safe_code);
476    }
477    stats.orphan_rows = inputs
478        .receipts
479        .iter()
480        .enumerate()
481        .map(|(fi, rf)| {
482            (0..rf.rows.len())
483                .filter(|ri| !claimed.contains(&(fi, *ri)))
484                .count()
485        })
486        .sum();
487    stats
488}
489
490/// ONT-001 v4.15: a subject typed both `ont:Symbol` and `ont:Contract` is refused, by IRI. `ont:Kernel` is a
491/// Contract; a `#[kernel]` fn is a `KernelSymbol`. One IRI in both classes would put every Contract shape on a
492/// Rust fn.
493fn symbol_and_contract(g: &Graph) -> Result<(), KernelError> {
494    let contracts: BTreeSet<&str> = g.instances_of(&ont("Contract")).into_iter().collect();
495    let mut both: Vec<&str> = g
496        .instances_of(&ont("Symbol"))
497        .into_iter()
498        .chain(g.instances_of(&ont("KernelSymbol")))
499        .filter(|s| contracts.contains(s))
500        .collect();
501    both.sort_unstable();
502    match both.first() {
503        None => Ok(()),
504        Some(s) => Err(KernelError {
505            file: (*s).to_string(),
506            reason: format!("kernel: {s} typed both Symbol and Contract"),
507        }),
508    }
509}
510
511fn emit_witness(g: &mut Graph, rnode: &str, row: &Row) {
512    g.insert(rnode.to_string(), RDF_TYPE, Term::iri(kern("Receipt")));
513    g.insert(rnode.to_string(), kern("host"), Term::string(&row.host));
514    g.insert(rnode.to_string(), kern("cc"), Term::string(&row.cc));
515    g.insert(rnode.to_string(), kern("sha"), Term::string(&row.sha));
516    let nums = [
517        ("cos", row.cos),
518        ("maxdiff", row.maxdiff),
519        ("oxideUs", row.oxide_us),
520        ("ptxUs", row.ptx_us),
521    ];
522    for (p, v) in nums {
523        if let Some(x) = v {
524            g.insert(rnode.to_string(), kern(p), Term::double(x));
525        }
526    }
527    if let Some(b) = row.register_budget {
528        g.insert(rnode.to_string(), kern("registerBudget"), Term::integer(b));
529    }
530    if !row.ptxas_version.is_empty() {
531        g.insert(
532            rnode.to_string(),
533            kern("ptxasVersion"),
534            Term::string(&row.ptxas_version),
535        );
536    }
537    if !row.authoring.is_empty() {
538        g.insert(
539            rnode.to_string(),
540            kern("authoring"),
541            Term::string(&row.authoring),
542        );
543    }
544}
545
546/// A two-symbol graph: `k::gated_rmsnorm` (`#[kernel]`) and, when `with_reference`, `k::rmsnorm_f64` bound to the
547/// same contract equation.
548#[must_use]
549pub fn control_graph(with_reference: bool) -> Graph {
550    let mut g = Graph::new();
551    let contract = iri("contract", "rmsnorm-kernel-v1");
552    let mut add = |name: &str, kernel: bool| {
553        let s = iri("symbol", &format!("k::{name}"));
554        g.insert(s.clone(), RDF_TYPE, Term::iri(ont("Symbol")));
555        g.insert(s.clone(), sym("module"), Term::string("k"));
556        g.insert(s.clone(), sym("name"), Term::string(name));
557        g.insert(s.clone(), sym("implements"), Term::iri(&contract));
558        g.insert(s.clone(), sym("equation"), Term::string("rmsnorm"));
559        g.insert(s.clone(), sym("resolved"), Term::boolean(true));
560        if kernel {
561            g.insert(s.clone(), sym("attribute"), Term::string("kernel"));
562            g.insert(s.clone(), sym("unsafeFree"), Term::boolean(true));
563            g.insert(s, sym("boundsChecked"), Term::boolean(true));
564        }
565    };
566    add("gated_rmsnorm", true);
567    if with_reference {
568        add("rmsnorm_f64", false);
569    }
570    g
571}
572
573/// Positive control (`pc_extract.kernel`, run in memory every gate run): a `#[kernel]` whose reference does not
574/// resolve must be named `kernel:referenceMissing`, one whose reference does must not be, and a ladder-schema file
575/// under the kernel root must be refused — a resolver that typed every kernel clean would make every
576/// `kernel-parity` a Pass.
577#[must_use]
578pub fn positive_control() -> bool {
579    let k = iri("symbol", "k::gated_rmsnorm");
580    let mut ghost = control_graph(false);
581    resolve(&mut ghost, &Inputs::default());
582    let mut ok = control_graph(true);
583    resolve(&mut ok, &Inputs::default());
584    let foreign = serde_json::Value::Object(
585        [("schema", "apr-model-ladder-receipt/v1"), ("sha", "ab")]
586            .into_iter()
587            .map(|(k, v)| (k.to_string(), serde_json::Value::from(v)))
588            .collect(),
589    );
590    !ghost.objects(&k, &kern("referenceMissing")).is_empty()
591        && ok.objects(&k, &kern("referenceMissing")).is_empty()
592        && ok.instances_of(&ont("KernelSymbol")).contains(&k.as_str())
593        && parse_receipt("evidence/kernels/x/lambda.json", &foreign).is_err()
594}
595
596#[cfg(test)]
597#[path = "kernel_tests.rs"]
598mod tests;