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}