lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 636c661ed6818e46d348c10a5b029535d57bb4b7
parent e64662ad9d9a5c76aceef659049b16db5f28c51c
Author: triesap <tyson@radroots.org>
Date:   Thu, 30 Jul 2026 10:43:52 +0000

signing: define the object-safe signer SPI

- expose runtime-neutral boxed futures for status and sign operations
- keep local remote and host-mediated implementations dyn compatible
- document cancellation deadlines and durable side-effect boundaries
- add compile coverage for object safety Send Sync and curated exports

Diffstat:
Mcrates/signing/src/error.rs | 18++++++++++++++++++
Mcrates/signing/src/lib.rs | 5+++++
Mcrates/signing/src/receipt.rs | 6++++++
Mcrates/signing/src/request.rs | 7+++++++
Mcrates/signing/src/signer.rs | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/signing/src/status.rs | 7+++++++
Mcrates/signing/tests/package_boundary.rs | 21++++++++++++++++++---
7 files changed, 138 insertions(+), 3 deletions(-)

diff --git a/crates/signing/src/error.rs b/crates/signing/src/error.rs @@ -1 +1,19 @@ //! Normalized signing failures. + +use core::fmt; + +/// A signing failure. +/// +/// Step 104 replaces this opaque pre-release value with the governed error +/// catalog. It intentionally carries no dependency-specific or secret data. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Error; + +impl fmt::Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("signing operation failed") + } +} + +impl core::error::Error for Error {} diff --git a/crates/signing/src/lib.rs b/crates/signing/src/lib.rs @@ -14,3 +14,8 @@ pub mod signer; pub mod status; pub use actor::Actor; +pub use error::Error; +pub use receipt::SignReceipt; +pub use request::SignRequest; +pub use signer::Signer; +pub use status::SignerStatus; diff --git a/crates/signing/src/receipt.rs b/crates/signing/src/receipt.rs @@ -1 +1,7 @@ //! Signing receipts. + +/// Opaque receipt vocabulary for the object-safe SPI. +/// +/// Step 102 defines the validated receipt contract before consumer migration. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct SignReceipt; diff --git a/crates/signing/src/request.rs b/crates/signing/src/request.rs @@ -1 +1,8 @@ //! Validated signing requests. + +/// Opaque request vocabulary for the object-safe SPI. +/// +/// Step 102 defines the actor, frozen-draft, deadline, policy, and progress +/// fields before consumer migration. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct SignRequest; diff --git a/crates/signing/src/signer.rs b/crates/signing/src/signer.rs @@ -1 +1,78 @@ //! Object-safe signer service-provider interface. + +use core::{future::Future, pin::Pin}; + +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(feature = "std")] +use std::boxed::Box; + +use crate::{Error, SignReceipt, SignRequest, SignerStatus}; + +/// A boxed, dynamically dispatched signer future. +pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; + +/// Protocol-neutral signing service-provider interface. +/// +/// The owned request and boxed futures keep this trait dyn-compatible for +/// local, remote, and host-mediated implementations without choosing an async +/// runtime. Implementations must document the point at which a request creates +/// a durable remote side effect. Dropping the future before that point must +/// leave no durable effect; dropping it afterward does not imply rollback. +pub trait Signer: Send + Sync { + /// Reports current capabilities and progress without creating a signing + /// request or another durable side effect. + fn status(&self) -> BoxFuture<'_, Result<SignerStatus, Error>>; + + /// Signs one already-authorized request. + /// + /// The request's deadline and cancellation policy remain authoritative + /// throughout the operation. Implementations must not install an executor, + /// spawn hidden workers, or convert cancellation into silent success. + fn sign(&self, request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, Error>>; +} + +#[cfg(test)] +mod tests { + use super::*; + + struct LocalSigner; + struct RemoteSigner; + + impl Signer for LocalSigner { + fn status(&self) -> BoxFuture<'_, Result<SignerStatus, Error>> { + Box::pin(async { Ok(SignerStatus) }) + } + + fn sign(&self, _request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, Error>> { + Box::pin(async { Ok(SignReceipt) }) + } + } + + impl Signer for RemoteSigner { + fn status(&self) -> BoxFuture<'_, Result<SignerStatus, Error>> { + Box::pin(async { Ok(SignerStatus) }) + } + + fn sign(&self, _request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, Error>> { + Box::pin(async { Err(Error) }) + } + } + + fn assert_dyn_signer(signer: &dyn Signer) { + drop(signer.status()); + drop(signer.sign(SignRequest)); + } + + #[test] + fn local_and_remote_implementations_are_dyn_compatible() { + assert_dyn_signer(&LocalSigner); + assert_dyn_signer(&RemoteSigner); + } + + #[test] + fn trait_objects_remain_send_and_sync() { + fn assert_send_sync<T: Send + Sync + ?Sized>() {} + assert_send_sync::<dyn Signer>(); + } +} diff --git a/crates/signing/src/status.rs b/crates/signing/src/status.rs @@ -1 +1,8 @@ //! Signer progress and status models. + +/// Opaque status vocabulary for the object-safe SPI. +/// +/// Step 102 defines the capability, progress, and challenge state model before +/// consumer migration. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct SignerStatus; diff --git a/crates/signing/tests/package_boundary.rs b/crates/signing/tests/package_boundary.rs @@ -1,7 +1,7 @@ #[allow(unused_imports)] use radroots_signing::{ - Actor, actor as _, capability as _, error as _, receipt as _, request as _, signer as _, - status as _, + Actor, Error, SignReceipt, SignRequest, Signer, SignerStatus, actor as _, capability as _, + error as _, receipt as _, request as _, signer as _, status as _, }; const MANIFEST: &str = include_str!("../Cargo.toml"); @@ -44,5 +44,20 @@ fn crate_root_declares_the_approved_module_skeleton() { ); } let _ = core::mem::size_of::<Actor>(); - assert!(ROOT.contains("pub use actor::Actor;")); + let _ = core::mem::size_of::<SignRequest>(); + let _ = core::mem::size_of::<SignReceipt>(); + let _ = core::mem::size_of::<SignerStatus>(); + let _ = core::mem::size_of::<Error>(); + fn assert_object_safe(_: &dyn Signer) {} + let _ = assert_object_safe; + for root_export in [ + "pub use actor::Actor;", + "pub use error::Error;", + "pub use receipt::SignReceipt;", + "pub use request::SignRequest;", + "pub use signer::Signer;", + "pub use status::SignerStatus;", + ] { + assert!(ROOT.contains(root_export), "missing {root_export}"); + } }