Skip to main content

clawft_kernel/licence/
policy.rs

1//! [`MeshCheckoutPolicy`]: the swarm redistribution policy of a Seed-bound
2//! mesh (ADR-106 section 5).
3//!
4//! It wraps [`ManifestPolicy`]: if that allows, so does this. Otherwise it
5//! allows a Cognitum-origin hash only for a verified [`Audience::Serve`] peer
6//! or [`Audience::Seed`], and only while a valid grant for this mesh covers
7//! the hash with the same cog and version. [`Audience::Advertise`] is always
8//! denied. It reads the binding and grants live on every call, so a binding
9//! that arrives later needs no restart, and with no binding in effect it is
10//! exactly [`ManifestPolicy`].
11
12use std::sync::Arc;
13
14use super::{CheckoutGrantStore, VerifiedCheckoutGrant};
15use crate::mesh_artifact::ArtifactExchange;
16use crate::mesh_swarm_state::{
17    Audience, GrantInfo, GrantOrigin, ManifestPolicy, RedistributionPolicy,
18};
19use crate::workload_pkg::codec::hex_encode;
20
21/// Package id prefix of grants made by [`ArtifactExchange::grant_checkout`].
22pub const CHECKOUT_PACKAGE_PREFIX: &str = "checkout:";
23
24/// The checkout-aware redistribution policy.
25#[derive(Debug)]
26pub struct MeshCheckoutPolicy {
27    base: ManifestPolicy,
28    store: Arc<CheckoutGrantStore>,
29}
30
31impl MeshCheckoutPolicy {
32    /// A policy reading `store` (which holds the live local mesh id).
33    pub fn new(store: Arc<CheckoutGrantStore>) -> Self {
34        Self { base: ManifestPolicy, store }
35    }
36
37    /// The policy the daemon installs unconditionally: a store under
38    /// `dir/licence` (poisoned, not fatal, if its file is bad), the
39    /// revocation list attached, and the local mesh id taken from `local`
40    /// (unset until the mesh nonce is configured, which makes the policy
41    /// behave exactly like `ManifestPolicy`).
42    pub fn open(
43        dir: &std::path::Path,
44        anchors: crate::workload_pkg::TrustAnchors,
45        revocations: Arc<crate::revocation::RevocationList>,
46        local: super::LocalMeshId,
47    ) -> Arc<Self> {
48        let store = CheckoutGrantStore::open_or_poisoned(
49            &dir.join("licence"),
50            Arc::new(anchors),
51            local,
52            super::system_clock(),
53        );
54        if let Some(why) = store.poisoned() {
55            tracing::warn!(%why, "licence store unreadable; checkout policy stays off");
56        }
57        store.attach_revocations(revocations);
58        Arc::new(Self::new(Arc::new(store)))
59    }
60
61    /// The store the policy reads.
62    pub fn store(&self) -> &Arc<CheckoutGrantStore> {
63        &self.store
64    }
65}
66
67impl RedistributionPolicy for MeshCheckoutPolicy {
68    fn allows(&self, hash: &[u8; 32], grants: &[GrantInfo], audience: &Audience<'_>) -> bool {
69        if self.base.allows(hash, grants, audience) {
70            return true;
71        }
72        match audience {
73            Audience::Advertise => return false,
74            Audience::Serve(peer) if !peer.verified => return false,
75            _ => {}
76        }
77        if grants.is_empty() {
78            return false;
79        }
80        let hex = hex_encode(hash);
81        grants.iter().filter(|g| !g.is_opt_in()).all(|g| match &g.origin {
82            GrantOrigin::Cognitum { cog_id, version } => {
83                self.store.valid_grant_covering(&hex, cog_id, version).is_some()
84            }
85            _ => false,
86        })
87    }
88}
89
90impl ArtifactExchange {
91    /// Make the bytes of a verified checkout grant *shareable* inside the
92    /// mesh: one grant per listed BLAKE3, signed by the bound grant key, with
93    /// `GrantOrigin::Cognitum`. A revoked key or hash is skipped by
94    /// `grant_with`, and revocation applies through the usual sweep. This
95    /// does not make the bytes runnable (see [`super::may_run`]).
96    pub fn grant_checkout(&self, v: &VerifiedCheckoutGrant) {
97        let g = v.grant();
98        let package = format!("{CHECKOUT_PACKAGE_PREFIX}{}@{}", g.cog_id, g.version);
99        for a in &g.artifacts {
100            let Some(hash) = crate::workload_pkg::codec::hex_decode_exact::<32>(&a.blake3) else {
101                continue;
102            };
103            self.grant_with(
104                hash,
105                &package,
106                vec![v.grant_pubkey_hex().to_owned()],
107                GrantOrigin::Cognitum { cog_id: g.cog_id.clone(), version: g.version.clone() },
108            );
109        }
110    }
111}