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:
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}");
+ }
}