// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! The job API every runner implements (milestone 0, stable from 0.1.0:
//! additions only). Heimdall is a defensive sandbox: a runner runs one
//! untrusted job so that it can reach only its own input and output, and
//! answers with a result signed by a per-boot key that never enters a job.
use serde::{Deserialize, Serialize};
use crate::signed::{ResultError, SignedResult};
use crate::types::{Features, Job};
pub use ed25519_dalek::VerifyingKey;
/// Something that runs jobs: today `heimdall_plain::PlainYoukiRunner`, later
/// the protected Heimdall runner. Callers code against this trait only.
pub trait JobRunner: Send + Sync {
/// Run one job to the end (blocking; callers put it on a job queue).
///
/// `Ok` is a signed result, Accepted or Refused. `Err` means nothing ran
/// to a verdict (bad job, unsupported kind, broken host): nothing is signed.
fn run(&self, job: Job) -> Result<SignedResult, RunError>;
/// The per-boot verifying key. The caller pins it and passes it to
/// [`crate::verify`].
fn verifying_key(&self) -> VerifyingKey;
/// What protects this runner's jobs, stated truthfully. Every signed
/// result carries the same value in `runner`.
fn features(&self) -> Features;
}
/// Why a job did not run to a verdict. Nothing is signed.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
pub enum RunError {
/// This runner does not run this kind of job (e.g. wasm on youki-plain)
/// or this policy (e.g. a network peer without a broker).
#[error("unsupported by this runner: {what}")]
Unsupported { what: String },
/// The job itself is malformed (bad digest, bad paths, bad limits,
/// `output_dir` not empty).
#[error("invalid job: {reason}")]
InvalidJob { reason: String },
/// The pinned image could not be found or does not match its digest.
#[error("image: {reason}")]
Image { reason: String },
/// The host could not set the job up (namespaces, cgroups, runtime).
#[error("setup: {reason}")]
Setup { reason: String },
/// A host I/O error outside the job.
#[error("i/o: {reason}")]
Io { reason: String },
/// The result could not be signed.
#[error("the result could not be signed: {0}")]
Sign(#[from] ResultError),
}