clawft_kernel/licence/
run_gate.rs1use std::sync::Arc;
22
23use super::gate::{RunPermit, RunRequest};
24use super::{ApprovalStore, CheckoutGrantStore};
25
26#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
28pub enum RunRefusal {
29 #[error("no Seed binding is in effect on this node ({0})")]
32 BindingInactive(String),
33 #[error("no checkout grant is held for this cog version")]
35 NoGrant,
36 #[error("the checkout grant for this cog version has lapsed (expired, withdrawn or its key revoked)")]
38 GrantLapsed,
39 #[error("the valid checkout grant does not list this binary")]
41 NotInGrant,
42 #[error("the binary's hash is revoked")]
44 HashRevoked,
45 #[error("no operator approval covers this binary")]
47 NoApproval,
48 #[error("Cognitum cogs on this machine run under its licence holder, not this daemon ({0})")]
52 NotHolder(String),
53}
54
55impl RunRefusal {
56 pub fn code(&self) -> &'static str {
58 match self {
59 Self::BindingInactive(_) => "binding_inactive",
60 Self::NoGrant => "no_grant",
61 Self::GrantLapsed => "grant_lapsed",
62 Self::NotInGrant => "not_in_grant",
63 Self::HashRevoked => "hash_revoked",
64 Self::NoApproval => "no_approval",
65 Self::NotHolder(_) => "not_holder",
66 }
67 }
68
69 pub fn remedy(&self, cog_id: &str, version: &str) -> String {
71 match self {
72 Self::BindingInactive(_) => "check `weaver workload node status`".into(),
73 Self::NoGrant | Self::NotInGrant => {
74 format!("weaver cog checkout {cog_id}@{version} --arch <arch>")
75 }
76 Self::GrantLapsed => "renew through the steward (a lapsed licence stops new starts)".into(),
77 Self::HashRevoked => "the artifact was revoked; it cannot run".into(),
78 Self::NoApproval => format!("weaver cog checkout approve {cog_id}@{version}"),
79 Self::NotHolder(_) => {
80 format!("place {cog_id}@{version} from the cluster owner's daemon (the machine's licence holder)")
81 }
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum RunVerdict {
89 NotSeedBound,
91 Permit(RunPermit),
93}
94
95pub fn check_run(
99 grants: &CheckoutGrantStore,
100 approvals: Option<&ApprovalStore>,
101 req: &RunRequest<'_>,
102) -> Result<RunVerdict, RunRefusal> {
103 let approvals_held = approvals.is_some_and(|a| !a.is_empty() || a.poisoned().is_some());
104 let bound_signal = grants.held_binding().is_some()
105 || grants.poisoned().is_some()
106 || grants.was_ever_bound()
107 || approvals_held;
108 if !bound_signal {
109 return Ok(RunVerdict::NotSeedBound);
110 }
111 if grants.held_binding().is_none() && grants.poisoned().is_none() {
112 return Err(RunRefusal::BindingInactive(
115 "this node was Seed-bound but its licence store holds no binding (store missing?)".into(),
116 ));
117 }
118 grants.binding_status().map_err(|e| RunRefusal::BindingInactive(e.to_string()))?;
119 let Some((grant, art)) = grants.valid_grant_for_artifact(req.cog_id, req.version, req.sha256, req.blake3)
120 else {
121 if grants.held_grant(req.cog_id, req.version).is_none() {
122 return Err(RunRefusal::NoGrant);
123 }
124 let valid = grants
125 .verified_grants()
126 .iter()
127 .any(|v| v.grant().cog_id == req.cog_id && v.grant().version == req.version);
128 return Err(if valid { RunRefusal::NotInGrant } else { RunRefusal::GrantLapsed });
129 };
130 if grants.is_hash_revoked(req.blake3) {
131 return Err(RunRefusal::HashRevoked);
132 }
133 let approval_id = approvals
134 .and_then(|a| a.covering(req.cog_id, req.version, req.sha256))
135 .ok_or(RunRefusal::NoApproval)?;
136 Ok(RunVerdict::Permit(RunPermit { grant_id: grant.grant_id, approval_id, blake3: art.blake3 }))
137}
138
139pub trait CognitumRunGate: Send + Sync {
142 fn check(&self, req: &RunRequest<'_>) -> Result<RunVerdict, RunRefusal>;
144 fn claims(&self, _sha256: &str, _blake3: &str) -> bool {
147 false
148 }
149 fn revoked(&self, _blake3: &str) -> bool {
151 false
152 }
153}
154
155pub struct StoreRunGate {
157 pub grants: Arc<CheckoutGrantStore>,
159 pub approvals: Option<Arc<ApprovalStore>>,
161}
162
163impl CognitumRunGate for StoreRunGate {
164 fn check(&self, req: &RunRequest<'_>) -> Result<RunVerdict, RunRefusal> {
165 check_run(&self.grants, self.approvals.as_deref(), req)
166 }
167
168 fn claims(&self, sha256: &str, blake3: &str) -> bool {
169 self.grants.claims_artifact(sha256, blake3) || self.grants.is_hash_revoked(blake3)
170 }
171
172 fn revoked(&self, blake3: &str) -> bool {
173 self.grants.is_hash_revoked(blake3)
174 }
175}