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 {}