clawft_kernel/licence/
store_status.rs1use serde::Serialize;
5
6use super::approval::Approval;
7use super::store::grant_valid;
8use super::{ApprovalStore, CheckoutGrantStore, GrantArtifact};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
12pub struct GrantRow {
13 pub cog_id: String,
15 pub version: String,
17 pub seq: u64,
19 pub grant_id: String,
21 pub issued_at: u64,
23 pub expires_at: u64,
25 pub licence_expires: u64,
27 pub withdrawn: bool,
29 pub valid: bool,
32 pub artifacts: Vec<GrantArtifact>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
38pub struct ApprovalRow {
39 pub approval_id: String,
41 #[serde(flatten)]
43 pub approval: Approval,
44 pub active: bool,
46}
47
48impl CheckoutGrantStore {
49 pub fn grant_rows(&self) -> Vec<GrantRow> {
52 self.run(|inner, ev| {
53 let bound = self.binding_in_effect(inner, ev).ok();
54 let eff = bound.as_ref().map(|b| {
55 let revoked = self.key_revoked(&b.grant_pubkey);
56 (self.eff_now(inner, &b.grant_pubkey), b.mesh_id.clone(), revoked)
57 });
58 inner
59 .slots
60 .values()
61 .filter_map(|s| s.current.as_ref())
62 .map(|h| {
63 let g = &h.body;
64 let valid = eff.as_ref().is_some_and(|(now, mesh, revoked)| {
65 !revoked && &g.mesh_id == mesh && grant_valid(g, *now)
66 });
67 GrantRow {
68 cog_id: g.cog_id.clone(),
69 version: g.version.clone(),
70 seq: g.seq,
71 grant_id: g.grant_id.clone(),
72 issued_at: g.issued_at,
73 expires_at: g.expires_at,
74 licence_expires: g.licence.expires,
75 withdrawn: g.is_withdrawal(),
76 valid,
77 artifacts: g.artifacts.clone(),
78 }
79 })
80 .collect()
81 })
82 }
83
84 pub fn effective_now(&self) -> Option<u64> {
86 self.run(|inner, ev| {
87 let b = self.binding_in_effect(inner, ev).ok()?;
88 Some(self.eff_now(inner, &b.grant_pubkey))
89 })
90 }
91}
92
93pub const BOUND_MARKER: &str = "licence-bound.marker";
97
98impl CheckoutGrantStore {
99 fn marker(&self) -> std::path::PathBuf {
100 self.path.with_file_name(BOUND_MARKER)
101 }
102
103 pub(super) fn note_bound(&self) {
105 let m = self.marker();
106 if m.exists() {
107 return;
108 }
109 if let Err(e) = super::persist::write_atomic(&m, b"bound\n") {
110 tracing::warn!(error = %e, "could not write the licence bound marker");
111 }
112 }
113
114 pub fn was_ever_bound(&self) -> bool {
117 if self.marker().exists() {
118 return true;
119 }
120 if self.held_binding().is_some() {
121 self.note_bound();
122 return true;
123 }
124 false
125 }
126
127 pub fn claims_artifact(&self, sha256: &str, blake3: &str) -> bool {
131 let g = self.lock();
132 g.slots
133 .values()
134 .flat_map(|s| s.current.iter().chain(s.previous.iter()))
135 .flat_map(|h| h.body.artifacts.iter())
136 .any(|a| a.sha256 == sha256 || a.blake3 == blake3)
137 }
138}
139
140impl ApprovalStore {
141 pub fn rows(&self) -> Vec<ApprovalRow> {
143 let local = self.local.get().map(|m| m.to_hex());
144 let g = self.lock();
145 g.0.iter()
146 .map(|(k, (_, a))| ApprovalRow {
147 approval_id: k.clone(),
148 approval: a.clone(),
149 active: g.1.is_none() && Some(&a.mesh_id) == local.as_ref(),
150 })
151 .collect()
152 }
153
154 pub fn orphaned_signed(&self) -> Vec<super::SignedApproval> {
157 let local = self.local.get().map(|m| m.to_hex());
158 let g = self.lock();
159 g.0.values().filter(|(_, a)| Some(&a.mesh_id) != local.as_ref()).map(|(s, _)| s.clone()).collect()
160 }
161
162 pub fn orphaned_approvals(&self) -> Vec<Approval> {
165 self.rows().into_iter().filter(|r| !r.active).map(|r| r.approval).collect()
166 }
167}