lifeloop-cli 0.1.0

Provider-neutral lifecycle abstraction and normalizer for AI harnesses
Documentation
//! Trait seams for follow-up router stages.
//!
//! The skeleton router (issue #7) defines these traits but does **not**
//! implement them. They name the integration shape future issues will
//! plug into:
//!
//! * [`NegotiationStrategy`] — capability negotiation against an
//!   [`AdapterManifest`] before dispatch.
//! * [`CallbackInvoker`] — invoke a client callback for a routed
//!   event. Issue #3 (parallel branch, protocol renderers) will
//!   provide a renderer-backed implementation; the trait method
//!   signature is shaped so a renderer can plug in cleanly without
//!   this module growing a `protocol` dependency.
//! * [`ReceiptEmitter`] — persist or forward the
//!   [`crate::LifecycleReceipt`] produced by a dispatch.
//! * [`FailureMapper`] — turn a [`super::RouteError`] (or a
//!   downstream stage's error) into the [`crate::FailureClass`] /
//!   [`crate::RetryClass`] pair the receipt carries.
//!
//! Methods either return `todo!()` in the skeleton's tests or are
//! left without default implementations — implementers in follow-up
//! issues are expected to provide them.

use crate::{
    CallbackResponse, FailureClass, LifecycleReceipt, NegotiationOutcome, PayloadEnvelope,
    RetryClass,
};

use super::plan::RoutingPlan;
use super::validation::RouteError;

/// Capability negotiation seam.
///
/// A future router issue will resolve `acceptable_placements` against
/// the manifest's placement claims, identity-correlation requirements
/// against `session_identity`, and so on. The skeleton defines the
/// trait shape only.
pub trait NegotiationStrategy {
    /// Negotiate the routing plan against the adapter manifest it
    /// already carries. Implementations decide whether the request
    /// is satisfied, must be degraded, is unsupported, or requires
    /// operator intervention.
    fn negotiate(&self, plan: &RoutingPlan) -> NegotiationOutcome;
}

/// Callback invocation seam.
///
/// Issue #3 owns the protocol renderer side of this seam; the
/// signature is shaped so a renderer-backed implementation can plug
/// in without this module knowing anything about a `protocol`
/// module.
///
/// The argument shape mirrors the renderer-input tuple coordinated
/// with issue #3:
/// `(lifecycle_event, adapter_id, adapter_version, integration_mode,
///   frame_ctx, payload_envelope, placement_class, receipt_meta)`.
/// All of those fields are reachable from a [`RoutingPlan`] plus the
/// optional [`PayloadEnvelope`]s the caller is delivering, so the
/// trait method takes those two and lets the implementation
/// destructure.
///
/// The [`PayloadEnvelope::body`] is treated as opaque bytes/value —
/// implementations must not parse it.
pub trait CallbackInvoker {
    /// Error produced by the callback transport. Kept generic so a
    /// renderer-backed impl, an in-process impl, and a future
    /// network impl can each carry their own diagnostic shape.
    type Error;

    /// Invoke the client callback for a routed event.
    ///
    /// `payloads` carries any [`PayloadEnvelope`]s the caller is
    /// delivering alongside the event. The router does not inspect
    /// payload bodies; the invoker may, but is not required to.
    fn invoke(
        &self,
        plan: &RoutingPlan,
        payloads: &[PayloadEnvelope],
    ) -> Result<CallbackResponse, Self::Error>;
}

/// Receipt emission seam.
///
/// A future router issue will synthesize the
/// [`crate::LifecycleReceipt`] and route it to a ledger or
/// notification surface. The skeleton defines the trait shape only.
pub trait ReceiptEmitter {
    /// Error produced by the receipt sink.
    type Error;

    /// Emit (persist, forward, or otherwise hand off) a
    /// [`LifecycleReceipt`].
    fn emit(&self, receipt: &LifecycleReceipt) -> Result<(), Self::Error>;
}

/// Failure-class mapping seam.
///
/// A future router issue will map [`RouteError`] (and downstream
/// stage errors) onto the
/// [`FailureClass`] / [`RetryClass`] pair carried on a
/// `status=failed` receipt. The skeleton defines the trait shape
/// only.
pub trait FailureMapper {
    /// Map a [`RouteError`] to a `(failure_class, retry_class)`
    /// pair. Implementations must be deterministic — the same
    /// [`RouteError`] variant must always map to the same pair so
    /// receipt ledgers replay consistently.
    fn map_route_error(&self, err: &RouteError) -> (FailureClass, RetryClass);
}