klieo-ops 0.3.0

Operational layer above klieo-core: supervisor, governor, gates, escalation, worklog, handoff.
Documentation
//! `Handoff` trait and supporting types.

use super::envelope::{HandoffEnvelope, HandoffProof};
use crate::types::{AgentId, TenantId};
use async_trait::async_trait;
use ed25519_dalek::SigningKey;
use thiserror::Error;

/// Inputs to `Handoff::package`.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct HandoffState {
    /// Source `RunId`.
    pub from_run: String,
    /// Hex-encoded Merkle root at packaging time.
    pub merkle_root: String,
    /// Optional source-run sequence-number anchor.
    pub at_seq: Option<u64>,
    /// CBOR-serialised and redacted state body. Caller must pre-redact.
    pub redacted_state: Vec<u8>,
    /// Source agent identity.
    pub source_identity: AgentId,
    /// Tenant tag.
    pub tenant: Option<TenantId>,
}

impl HandoffState {
    /// Construct a `HandoffState`. Use this instead of struct literal syntax —
    /// `#[non_exhaustive]` blocks cross-crate struct literals.
    pub fn new(
        from_run: impl Into<String>,
        merkle_root: impl Into<String>,
        redacted_state: Vec<u8>,
        source_identity: AgentId,
        tenant: Option<TenantId>,
        at_seq: Option<u64>,
    ) -> Self {
        Self {
            from_run: from_run.into(),
            merkle_root: merkle_root.into(),
            at_seq,
            redacted_state,
            source_identity,
            tenant,
        }
    }
}

/// Errors raised by `Handoff` calls.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HandoffError {
    /// Envelope has expired.
    #[error("envelope expired at {expired_at}")]
    Expired {
        /// RFC 3339 expiry timestamp.
        expired_at: String,
    },
    /// Signature is mathematically invalid.
    #[error("signature invalid: {0}")]
    SignatureInvalid(String),
    /// Source identity is not in the receiver's trust registry.
    #[error("untrusted source identity: {0}")]
    UntrustedSource(String),
    /// CBOR or canonical-JSON serialisation failed.
    #[error("serialisation: {0}")]
    Serialisation(String),
    /// KV storage write failed.
    #[error("storage unavailable: {0}")]
    Storage(String),
    /// Unexpected internal error.
    #[error("internal: {0}")]
    Internal(String),
}

/// Handoff primitive: package, deliver, and verify signed state-transfer
/// envelopes between agent runs.
///
/// Distinct from `klieo-a2a` (in-flight RPC within one workflow) and
/// `klieo-runlog` replay (post-hoc inspection). See spec § 1.7.
#[async_trait]
pub trait Handoff: Send + Sync {
    /// Package a handoff envelope. Signs the canonical payload with the
    /// supplied signing key. Callers must pre-redact the state and
    /// CBOR-serialise it before passing `redacted_state` in `HandoffState`.
    async fn package(
        &self,
        state: HandoffState,
        signer: &SigningKey,
        ttl: chrono::Duration,
    ) -> Result<HandoffEnvelope, HandoffError>;

    /// Persist and deliver an envelope to a receiver agent. Returns the
    /// source `RunId` the receiver should adopt.
    async fn deliver(&self, envelope: HandoffEnvelope, to: AgentId)
        -> Result<String, HandoffError>;

    /// Verify-before-adopt: checks signature math, expiry, and (when
    /// configured) source-identity trust. Returns a `HandoffProof` on
    /// success — the only way to obtain one.
    async fn verify(&self, envelope: &HandoffEnvelope) -> Result<HandoffProof, HandoffError>;
}