Skip to main content

clawft_kernel/workload_ctl/
cog_kind.rs

1//! The cog kind's requirements (ADR-100 section 2): how a verified cog
2//! package becomes a placement [`WorkloadSpec`].
3//!
4//! Per binary arch, three routes, in the order a kind prefers them:
5//!
6//! - `<arch>-native`: `cpu.arch.<arch>`, `os.linux` (cog binaries are Linux
7//!   ELF) and a `runtime.native` whose `arches_native` has the arch;
8//! - `<arch>-container`: a `runtime.container.*` running the arch natively;
9//! - `<arch>-emulated`: a `runtime.container.*` emulating the arch
10//!   (placed only when the operator sets `allow_emulated`).
11//!
12//! Memory is `[resources].ram_mb`; the policy needs a paired node (native
13//! isolation is weak until landlock / seccomp, ADR-100 section 3) and lists
14//! the package id, signer keys and artifact hashes as revocable refs.
15
16use clawft_types::placement::engine::{
17    Execution, ExecutionVariant, PlacementPolicy, TrustTier, WorkloadRequirements, WorkloadSpec,
18};
19use clawft_types::placement::{
20    AttrPredicate, CapabilityId, MemoryDemand, PlacementTypeError, Requirement,
21};
22
23use crate::workload_runtime::VerifiedWorkload;
24
25/// Arch preference order when a package ships several binaries.
26const ARCH_ORDER: &[&str] = &["aarch64", "armv7", "x86_64"];
27
28fn exact(id: &str) -> Result<Requirement, PlacementTypeError> {
29    Ok(Requirement::exact(CapabilityId::new(id)?))
30}
31
32fn runtime(prefix: &str, attr: &str, arch: &str) -> Result<Requirement, PlacementTypeError> {
33    Requirement::prefix(prefix)?.try_with_where(AttrPredicate::has(attr, arch))
34}
35
36/// The three routes for one arch.
37pub fn arch_routes(arch: &str) -> Result<Vec<ExecutionVariant>, PlacementTypeError> {
38    Ok(vec![
39        ExecutionVariant {
40            name: format!("{arch}-native"),
41            execution: Execution::Native,
42            requirements: vec![
43                exact(&format!("cpu.arch.{arch}"))?,
44                exact("os.linux")?,
45                runtime("runtime.native", "arches_native", arch)?,
46            ],
47        },
48        ExecutionVariant {
49            name: format!("{arch}-container"),
50            execution: Execution::Native,
51            requirements: vec![runtime("runtime.container", "arches_native", arch)?],
52        },
53        ExecutionVariant {
54            name: format!("{arch}-emulated"),
55            execution: Execution::Emulated,
56            requirements: vec![runtime("runtime.container", "arches_emulated", arch)?],
57        },
58    ])
59}
60
61/// The route kind a variant name ends with (`native`, `container`,
62/// `emulated`), which the target maps to an adapter.
63pub fn route_of(variant: &str) -> &str {
64    variant.rsplit('-').next().unwrap_or(variant)
65}
66
67/// The arch a variant name starts with.
68pub fn arch_of(variant: &str) -> &str {
69    variant.split('-').next().unwrap_or(variant)
70}
71
72/// Placement spec for a verified cog package (signed packages only; store
73/// pins are placed on an operator-assigned Seed node id, not by requirements).
74pub fn cog_workload_spec(w: &VerifiedWorkload) -> Result<WorkloadSpec, String> {
75    let p = w.signed("placement").map_err(|e| e.to_string())?;
76    let mut arches: Vec<&str> = ARCH_ORDER
77        .iter()
78        .copied()
79        .filter(|a| p.binaries.contains_key(*a))
80        .collect();
81    // Unknown arches (a package may ship more) come after the known ones.
82    let mut extra: Vec<&str> = p
83        .binaries
84        .keys()
85        .map(String::as_str)
86        .filter(|a| !ARCH_ORDER.contains(a))
87        .collect();
88    extra.sort_unstable();
89    arches.extend(extra);
90    if arches.is_empty() {
91        return Err("package has no binaries".into());
92    }
93    let mut variants = Vec::new();
94    for a in arches {
95        variants.extend(arch_routes(a).map_err(|e| e.to_string())?);
96    }
97    let mut refs = vec![p.package_id.clone()];
98    refs.extend(p.signer_keys.iter().cloned());
99    refs.extend(p.artifact_hashes.iter().cloned());
100    let spec = WorkloadSpec {
101        kind: w.kind.clone(),
102        name: w.id.clone(),
103        requirements: WorkloadRequirements {
104            common: Vec::new(),
105            variants,
106            memory: MemoryDemand {
107                host_bytes: u64::from(p.spec.resources.ram_mb) << 20,
108                accel_bytes: 0,
109            },
110        },
111        policy: PlacementPolicy {
112            min_trust: TrustTier::Paired,
113            revocable_refs: refs,
114            ..PlacementPolicy::default()
115        },
116    };
117    spec.validate().map_err(|e| e.to_string())?;
118    Ok(spec)
119}