clawft_kernel/workload_kind/
project.rs1#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
15use std::path::Path;
16
17#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
18use clawft_types::placement::engine::{PlacementPolicy, WorkloadRequirements, WorkloadSpec};
19#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
20use clawft_types::placement::{CapabilityId, MemoryDemand, Requirement};
21
22#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
23use crate::workload_ctl::PlaneError;
24#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
25use crate::workload_pkg::TrustAnchors;
26use crate::workload_pkg::{ManifestEnvelope, ManifestError};
27#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
28use crate::workload_runtime::{CAP_PROJECT_LOGICAL, ProjectPayload, VerifiedWorkload, WorkloadSource};
29
30use super::WorkloadKind;
31#[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
32use super::KindRegistry;
33
34pub const KIND_PROJECT: &str = "project";
36
37#[derive(Debug, Clone, Copy, Default)]
39pub struct ProjectKind;
40
41impl WorkloadKind for ProjectKind {
42 fn id(&self) -> &'static str {
43 KIND_PROJECT
44 }
45
46 fn health(&self) -> super::HealthSpec {
47 super::HealthSpec {
49 interval_ms: 5_000,
50 miss_limit: 2,
51 }
52 }
53
54 fn migratable(&self) -> bool {
56 false
57 }
58
59 fn validate(&self, _envelope: &ManifestEnvelope) -> Result<(), ManifestError> {
60 Err(ManifestError::Invalid(
61 "project workloads have no signed package; the user daemon supervisor starts them".into(),
62 ))
63 }
64
65 #[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
66 fn load(
67 &self,
68 _package_dir: &Path,
69 _anchors: &TrustAnchors,
70 _kinds: &KindRegistry,
71 ) -> Result<VerifiedWorkload, PlaneError> {
72 Err(PlaneError::Package(
73 "project workloads are authorised by a project certificate, not a package; \
74 use `project.start` on the user daemon"
75 .into(),
76 ))
77 }
78
79 #[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
80 fn spec(&self, workload: &VerifiedWorkload) -> Result<WorkloadSpec, String> {
81 let capability = match &workload.source {
82 WorkloadSource::Project(p) if p.adapter == "wasmtime-project-v1" => "runtime.project.wasmtime",
83 WorkloadSource::Project(p) if p.adapter == "logical" => CAP_PROJECT_LOGICAL,
84 _ => return Err("unsupported project adapter".into()),
85 };
86 let id = CapabilityId::new(capability).map_err(|e| e.to_string())?;
87 let spec = WorkloadSpec {
88 kind: workload.kind.clone(),
89 name: workload.id.clone(),
90 requirements: WorkloadRequirements {
91 common: vec![Requirement::exact(id)],
92 variants: Vec::new(),
93 memory: MemoryDemand::default(),
94 },
95 policy: PlacementPolicy::default(),
96 };
97 spec.validate().map_err(|e| e.to_string())?;
98 Ok(spec)
99 }
100
101 #[cfg(all(feature = "workload-runtime", feature = "mesh", unix))]
102 fn adapters(&self) -> &'static [&'static str] {
103 &["logical", "wasmtime-project-v1"]
104 }
105}
106
107#[cfg(all(feature = "workload-runtime", feature = "mesh", unix, feature = "native"))]
109pub struct ProjectFacts<'a> {
110 pub cert: Option<&'a clawft_types::project::ProjectCert>,
114 pub user_pubkey: &'a [u8; 32],
116 pub revocations: &'a crate::project_identity::RevocationView,
118 pub manifest_id: &'a str,
120 pub root: &'a Path,
122 pub policy_hash: &'a str,
125}
126
127#[cfg(all(feature = "workload-runtime", feature = "mesh", unix, feature = "native"))]
129#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
130pub enum ProjectPrepareError {
131 #[error("certificate: {0}")]
133 Cert(String),
134 #[error("identity: {0}")]
136 Identity(String),
137 #[error("certificate is for project {cert}, manifest is {manifest}")]
139 WrongProject {
140 cert: String,
142 manifest: String,
144 },
145 #[error("project root {0} is not a directory")]
147 RootGone(String),
148}
149
150#[cfg(all(feature = "workload-runtime", feature = "mesh", unix, feature = "native"))]
154pub fn prepare_project(f: &ProjectFacts<'_>) -> Result<VerifiedWorkload, ProjectPrepareError> {
155 let (project_id, key_id, serial, user_key_id) = match f.cert {
156 Some(cert) => {
157 crate::project_identity::verify_cert(cert, f.user_pubkey)
158 .map_err(|e| ProjectPrepareError::Cert(e.to_string()))?;
159 f.revocations
160 .check_cert(cert)
161 .map_err(|e| ProjectPrepareError::Identity(e.to_string()))?;
162 if cert.project_id != f.manifest_id {
163 return Err(ProjectPrepareError::WrongProject {
164 cert: cert.project_id.clone(),
165 manifest: f.manifest_id.to_owned(),
166 });
167 }
168 (
169 cert.project_id.clone(),
170 cert.project_key_id.clone(),
171 cert.serial,
172 cert.user_key_id.clone(),
173 )
174 }
175 None => (
176 f.manifest_id.to_owned(),
177 "unregistered".to_owned(),
178 0,
179 clawft_types::project::cert::key_id(f.user_pubkey),
180 ),
181 };
182 if !f.root.is_dir() {
183 return Err(ProjectPrepareError::RootGone(f.root.display().to_string()));
184 }
185 Ok(VerifiedWorkload {
186 kind: KIND_PROJECT.into(),
187 id: project_id.clone(),
188 version: format!("cert-{serial}"),
189 source: WorkloadSource::Project(ProjectPayload {
190 adapter: "logical".into(),
191 project_id,
192 key_id,
193 cert_serial: serial,
194 user_key_id,
195 policy_hash: f.policy_hash.to_owned(),
196 root: f.root.to_path_buf(),
197 }),
198 })
199}
200
201pub fn project_healthy(expected_id: &str, handshake_project_id: Option<&str>, shared_all_down: bool) -> bool {
204 handshake_project_id == Some(expected_id) && !shared_all_down
205}