Skip to main content

appcore_capabilities/
contract.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: contract.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/07/22 15:41:18 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/22 15:41:18 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11use appcore_core::{CapabilityDescriptor, CapabilityMode, CapabilityName, CoreId, TraceContext};
12use appcore_distributed_contracts::PeerRecord;
13
14/// Result returned by capability registry, resolution, and invocation operations.
15pub type CapabilityResult<T> = Result<T, CapabilityError>;
16
17/// Failures produced while registering, resolving, or invoking a capability.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum CapabilityError {
20    /// A descriptor is already present in the composed capability catalog.
21    DescriptorAlreadyRegistered(CapabilityName),
22    /// The composed host did not declare the requested capability.
23    CapabilityNotDeclared(CapabilityName),
24    /// A local handler is already registered for the capability.
25    HandlerAlreadyRegistered(CapabilityName),
26    /// The selected local handler is no longer present in the registry.
27    HandlerNotFound(CapabilityName),
28    /// No healthy compatible provider can serve the capability.
29    ProviderUnavailable(CapabilityName),
30    /// The capability requires a valid leadership lease.
31    RequiresLeader(CapabilityName),
32    /// The leadership lease has expired.
33    LeaseExpired(CapabilityName),
34    /// The leadership epoch is older than the active lease epoch.
35    StaleEpoch(CapabilityName),
36    /// The host's current operational mode does not permit writes.
37    WritesDisabled(CapabilityName),
38    /// A remote provider was selected but has no usable peer endpoint.
39    RemoteEndpointUnavailable(CapabilityName),
40    /// The peer RPC transport failed to invoke a remote provider.
41    RemoteInvocationFailed(String),
42    /// A handler rejected the request before or during execution.
43    HandlerRejected(String),
44}
45
46/// Transport-neutral request for a named runtime capability.
47#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48pub struct CapabilityRequest {
49    /// Caller-assigned identifier used for tracing and response correlation.
50    pub request_id: String,
51    /// Capability to resolve and invoke.
52    pub capability: CapabilityName,
53    /// Invocation mode required by the caller.
54    pub mode: CapabilityMode,
55    /// Opaque application-owned payload.
56    pub payload: Vec<u8>,
57    /// Optional key used to deduplicate mutating requests.
58    pub idempotency_key: Option<String>,
59    /// Optional distributed trace context propagated to the provider.
60    pub trace: Option<TraceContext>,
61}
62
63/// Transport-neutral result of a capability invocation.
64#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
65pub struct CapabilityResponse {
66    /// Whether the provider accepted and completed the request.
67    pub accepted: bool,
68    /// Opaque application-owned response payload.
69    pub payload: Vec<u8>,
70    /// Core that handled the request, when known.
71    pub provider_core_id: Option<CoreId>,
72    /// Controlled rejection or informational message.
73    pub message: Option<String>,
74}
75
76impl CapabilityResponse {
77    /// Creates an accepted response with an optional provider identity.
78    pub fn accepted(payload: Vec<u8>, provider_core_id: Option<CoreId>) -> Self {
79        Self {
80            accepted: true,
81            payload,
82            provider_core_id,
83            message: None,
84        }
85    }
86
87    /// Creates a rejected response without a payload.
88    pub fn rejected(message: impl Into<String>) -> Self {
89        Self {
90            accepted: false,
91            payload: Vec::new(),
92            provider_core_id: None,
93            message: Some(message.into()),
94        }
95    }
96}
97
98/// Application-provided implementation of a capability hosted in this process.
99pub trait LocalCapabilityHandler: Send + Sync {
100    /// Describes the capability exposed by this handler.
101    fn descriptor(&self) -> CapabilityDescriptor;
102
103    /// Reports whether this handler may currently receive traffic.
104    fn is_healthy(&self) -> bool {
105        true
106    }
107
108    /// Handles one validated capability request.
109    fn handle(&self, request: &CapabilityRequest) -> CapabilityResult<CapabilityResponse>;
110}
111
112/// Adapter used to invoke a capability hosted by a remote runtime peer.
113pub trait RemoteCapabilityInvoker: Send + Sync {
114    /// Sends one request to the selected peer and returns its response.
115    fn invoke_remote(
116        &self,
117        peer: &PeerRecord,
118        request: &CapabilityRequest,
119    ) -> CapabilityResult<CapabilityResponse>;
120
121    /// Consumes one request when the caller can transfer its payload ownership.
122    ///
123    /// The default preserves compatibility with borrowed-only invokers. An
124    /// implementation may override this method to move request fields into its
125    /// transport without copying the opaque payload.
126    fn invoke_remote_owned(
127        &self,
128        peer: &PeerRecord,
129        request: CapabilityRequest,
130    ) -> CapabilityResult<CapabilityResponse> {
131        self.invoke_remote(peer, &request)
132    }
133}