Skip to main content

clawft_kernel/licence/
steward_client.rs

1//! The steward's [`LicenceClient`] for whatever Seed is bound right now
2//! (ADR-106 sections 3 and 4, phase 3).
3//!
4//! A binding can arrive, change steward or be withdrawn while the daemon
5//! runs, so the client reads the binding in effect on every call: it signs
6//! for that binding's `device_id` (the audience) as that binding's
7//! `steward_node_id`, and refuses (`seed_not_bound`, `not_steward`) when no
8//! binding is in effect or when it does not name this node and this key.
9//! Nothing is sent in those cases.
10
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use ed25519_dalek::SigningKey;
15
16use super::client::{
17    CheckoutWire, ClockMs, GrantsPage, LicenceClient, LicenceClientError, LicenceResponse, LicenceTransport,
18    SignedLicenceClient,
19};
20use super::request::LicenceRequest;
21use super::{BindingRecord, CheckoutGrantStore, SignedGrant};
22use crate::workload_pkg::codec::hex_encode;
23
24/// A shared transport, so one link serves every per-call client.
25#[derive(Clone)]
26pub struct SharedTransport(pub Arc<dyn LicenceTransport>);
27
28#[async_trait]
29impl LicenceTransport for SharedTransport {
30    async fn call(&self, req: LicenceRequest) -> Result<LicenceResponse, LicenceClientError> {
31        self.0.call(req).await
32    }
33}
34
35/// [`LicenceClient`] bound to the store's binding in effect.
36pub struct StewardLicenceClient {
37    store: Arc<CheckoutGrantStore>,
38    key: SigningKey,
39    node_id: String,
40    transport: SharedTransport,
41    clock: ClockMs,
42}
43
44fn refused(code: &str) -> LicenceClientError {
45    LicenceClientError::Refused { status: 0, code: code.into() }
46}
47
48impl StewardLicenceClient {
49    /// A client for this node (`node_id`, signing with `key`) over `transport`.
50    pub fn new(
51        store: Arc<CheckoutGrantStore>,
52        key: SigningKey,
53        node_id: impl Into<String>,
54        transport: Arc<dyn LicenceTransport>,
55        clock: ClockMs,
56    ) -> Arc<Self> {
57        Arc::new(Self { store, key, node_id: node_id.into(), transport: SharedTransport(transport), clock })
58    }
59
60    /// The binding in effect when it names this node and key as its steward.
61    pub fn steward_binding(&self) -> Result<BindingRecord, LicenceClientError> {
62        let b = self.store.active_binding().ok_or_else(|| refused("seed_not_bound"))?;
63        let me = hex_encode(&self.key.verifying_key().to_bytes());
64        if b.steward_node_id != self.node_id || b.steward_pubkey != me {
65            return Err(refused("not_steward"));
66        }
67        Ok(b)
68    }
69
70    fn signed(&self) -> Result<Arc<SignedLicenceClient<SharedTransport>>, LicenceClientError> {
71        let b = self.steward_binding()?;
72        Ok(SignedLicenceClient::for_binding(self.key.clone(), &b, self.transport.clone(), self.clock.clone()))
73    }
74}
75
76#[async_trait]
77impl LicenceClient for StewardLicenceClient {
78    async fn checkout(&self, req: &CheckoutWire) -> Result<SignedGrant, LicenceClientError> {
79        self.signed()?.checkout(req).await
80    }
81
82    async fn artifact(&self, blake3_hex: &str, max_len: u64) -> Result<Vec<u8>, LicenceClientError> {
83        self.signed()?.artifact(blake3_hex, max_len).await
84    }
85
86    async fn grants_since(&self, since: u64) -> Result<Vec<SignedGrant>, LicenceClientError> {
87        self.signed()?.grants_since(since).await
88    }
89
90    async fn grants_page(&self, since: u64) -> Result<GrantsPage, LicenceClientError> {
91        self.signed()?.grants_page(since).await
92    }
93
94    async fn renew(&self) -> Result<GrantsPage, LicenceClientError> {
95        self.signed()?.renew().await
96    }
97
98    async fn release(&self, cog_id: &str, version: &str) -> Result<GrantsPage, LicenceClientError> {
99        self.signed()?.release(cog_id, version).await
100    }
101}