Skip to main content

somatize_fabric_wire/
provision.rs

1//! Who turns an artifact into a catalog. The far side.
2//!
3//! The hole this crate leaves so as not to learn what a `cloudpickle` is. A
4//! generic worker starts with a `Provision` and no catalog; when a client
5//! arrives, it decides whether to accept it and what to build.
6//!
7//! | question | why it is not ours |
8//! |---|---|
9//! | can this client provision me? | only whoever deserializes knows what couples to what |
10//! | what comes out of these bytes? | we do not know what the bytes are |
11//!
12//! `accepts` is the original's lesson written as a method: its worker chose an
13//! interpreter with `$SOMA_PYTHON` or `python3` from the `PATH`, and a pickled
14//! filter can only be rebuilt by an interpreter close enough to the one that
15//! pickled it — with a different one cloudpickle returns the class's `__dict__`
16//! instead of an instance, surfacing as `'dict' object is not callable` from
17//! inside a subprocess with nothing pointing at the version gap. Hence the
18//! client **identifies itself** in the greeting.
19
20use somatize_core::Catalog;
21use std::fmt;
22
23/// Knows how to turn an artifact into a catalog.
24pub trait Provision: Send + Sync {
25    /// Whether a client that identifies itself this way — `cpython-3.13/…`,
26    /// opaque here — can provision this worker with an artifact of this kind.
27    ///
28    /// The `kind` matters: one carrying serialized objects demands that both
29    /// sides look very much alike, one carrying names and state almost nothing.
30    fn accepts(&self, runtime: &str, kind: &str) -> Result<(), ProvisionError>;
31
32    /// What this artifact yields.
33    fn provide(&self, kind: &str, bytes: &[u8]) -> Result<Provisioned, ProvisionError>;
34}
35
36/// What comes out of an artifact: the implementations.
37pub struct Provisioned {
38    /// Who executes each node.
39    pub catalog: Catalog,
40}
41
42impl Provisioned {
43    /// An artifact's implementations, unpacked.
44    pub fn new(catalog: Catalog) -> Self {
45        Self { catalog }
46    }
47}
48
49/// Why a worker will not let itself be provisioned.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum ProvisionError {
52    /// The client and the worker cannot understand each other.
53    Incompatible {
54        /// How the client identified itself.
55        client: String,
56        /// And what there is on this side.
57        worker: String,
58    },
59    /// A kind of artifact this worker cannot interpret.
60    UnknownKind(String),
61    /// It knew how to interpret it and could not.
62    Broken(String),
63}
64
65impl fmt::Display for ProvisionError {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Incompatible { client, worker } => write!(
69                f,
70                "this worker runs `{worker}` and the client `{client}`: what was \
71                 serialized there cannot be rebuilt here"
72            ),
73            Self::UnknownKind(kind) => {
74                write!(f, "this worker cannot interpret a `{kind}` artifact")
75            }
76            Self::Broken(why) => write!(f, "the artifact could not be opened: {why}"),
77        }
78    }
79}
80
81impl std::error::Error for ProvisionError {}