tenuo 0.3.3

Task-scoped authorization for AI agents: signed warrants that constrain tools and arguments, narrow on delegation, and verify offline. Rust core of Tenuo.
Documentation
#![cfg_attr(docsrs, feature(doc_cfg))]

//! # Tenuo Core
//!
//! Task-scoped authorization for AI agents: the Rust core of Tenuo.
//!
//! A warrant is a signed grant of which tools an agent may call, with which argument
//! constraints, and for how long. Warrants are holder-bound, can only narrow when
//! delegated, verify offline where the action runs, and produce signed receipts.
//!
//! Unlike traditional IAM systems that answer "Who are you?", Tenuo answers
//! "Who delegated this authority, what task context does it carry, and is this action
//! within the delegated bounds?"
//!
//! ## Key Concepts
//!
//! - **Warrant**: A transferable token of authority containing tool name, constraints, TTL, and signature
//! - **Attenuation**: Capabilities can only shrink when delegated, never expand
//! - **Constraints**: Restrictions on argument values (Pattern, Exact, OneOf, Range, CEL)
//!

//! ## Example
//!
//! ```rust
//! use std::collections::HashMap;
//! use std::time::Duration;
//! use tenuo::{
//!     Authorizer, Constraint, ConstraintSet, ConstraintValue, Pattern, SigningKey, Warrant,
//! };
//!
//! let issuer_key = SigningKey::generate();
//! let holder_key = SigningKey::generate();
//!
//! let mut constraints = ConstraintSet::new();
//! constraints.insert(
//!     "path",
//!     Constraint::Pattern(Pattern::new("/data/*").expect("pattern")),
//! );
//! let warrant = Warrant::builder()
//!     .capability("read_file", constraints)
//!     .holder(holder_key.public_key())
//!     .ttl(Duration::from_secs(300))
//!     .build(&issuer_key)
//!     .expect("mint");
//!
//! let mut authorizer = Authorizer::new();
//! authorizer.add_trusted_root(issuer_key.public_key());
//! let mut args = HashMap::new();
//! args.insert(
//!     "path".into(),
//!     ConstraintValue::String("/data/report.txt".into()),
//! );
//! let pop = warrant
//!     .sign(&holder_key, "read_file", &args)
//!     .expect("pop");
//! authorizer
//!     .authorize_one(&warrant, "read_file", &args, Some(&pop), &[])
//!     .expect("authorize");
//! ```
//!
//! With the `sdk` feature, [`Runtime`](crate::sdk::Runtime) is the holder entry:
//!
//! ```rust
//! # #[cfg(feature = "sdk")]
//! # {
//! use std::collections::HashMap;
//! use std::time::Duration;
//! use tenuo::sdk::prelude::*;
//! use tenuo::{ConstraintSet, Warrant};
//!
//! let issuer = SigningKey::generate();
//! let holder = SigningKey::generate();
//! let warrant = Warrant::builder()
//!     .capability("read", ConstraintSet::new())
//!     .holder(holder.public_key())
//!     .ttl(Duration::from_secs(300))
//!     .build(&issuer)
//!     .expect("mint");
//! let runtime = Runtime::builder()
//!     .holder(holder)
//!     .trusted_root(issuer.public_key())
//!     .ttl_fallback(Duration::from_secs(600))
//!     .build()
//!     .expect("runtime");
//! let session = runtime.session_from_warrant(warrant).expect("session");
//! let args = HashMap::new();
//! assert!(session
//!     .diagnostics()
//!     .why_denied(&Call::borrowed("write", &args))
//!     .is_some());
//! # }
//! ```

pub mod approval;
pub mod approval_gate;
pub mod audit;
pub mod cel;
pub mod constraints;
pub mod crypto;
pub mod diff;
pub mod domain;
pub mod error;
pub mod extraction;
pub mod gateway_config;
pub mod mcp;
pub mod meta_envelope;
pub mod payload;
pub mod planes;
pub mod receipt;
pub mod revocation;
pub mod revocation_manager;
pub mod revocation_tracker;
#[cfg(feature = "sdk")]
#[deny(missing_docs)]
#[cfg_attr(docsrs, doc(cfg(feature = "sdk")))]
pub mod sdk;
pub mod strict_json;
pub mod verification;
pub mod warrant;
pub mod wire;

pub use strict_json::{parse_json_strict, StrictJsonError};

// Re-export extraction types
pub use extraction::{
    CompiledExtractionRule, CompiledExtractionRules, CompiledPath, ExtractionRule,
    ExtractionSource, RequestContext,
};

// Re-export gateway config types
pub use gateway_config::{
    CompiledGatewayConfig, CompiledRoute, GatewayConfig, GatewaySettings, MethodMask, RouteConfig,
    RouteMatch, ToolConfig,
};

// Re-export MCP config types
pub use mcp::{CompiledMcpConfig, CompiledTool, McpConfig, McpSettings};

#[cfg(feature = "python")]
pub mod python;

pub mod connect_token;

#[cfg(feature = "server")]
pub mod heartbeat;

#[cfg(feature = "python-server")]
pub mod python_control_plane;

// Re-exports for convenience
pub use constraints::{
    All, Any, AnyOf, CelConstraint, Cidr, Constraint, ConstraintSet, ConstraintValue, Contains,
    Exact, Not, NotOneOf, OneOf, Pattern, Range, RegexConstraint, Subpath, Subset, UrlPattern,
    Wildcard, MAX_CONSTRAINT_DEPTH,
};
pub use crypto::{PublicKey, Signature, SigningKey};
pub use error::{Error, ErrorCode, Result};

#[allow(deprecated)]
pub use planes::DataPlane;
pub use planes::{
    Authorizer, AuthorizerBuilder, ChainStep, ChainVerificationResult, ControlPlane,
    VerifiedApproval, DEFAULT_CLOCK_TOLERANCE_SECS,
};
pub use receipt::{
    canonical_policy_bytes, policy_commitment_digest, srl_commitment_digest, trusted_roots_digest,
    Receipt, ReceiptPayload,
};
pub use revocation::{
    RevocationRequest, SignedRevocationList, SrlBuilder, MAX_REVOCATION_REQUEST_AGE_SECS,
};
pub use revocation_manager::RevocationManager;
pub use revocation_tracker::{
    FileFloorStore, InMemoryFloorStore, RevocationError, RevocationFloorStore, RevocationTracker,
    RevocationUpdate,
};
#[cfg(any(feature = "mcp-transport", feature = "http-transport"))]
pub use sdk::transport::TransportError;
#[cfg(feature = "test-utils")]
pub use sdk::FixedClock;
#[cfg(feature = "sdk")]
pub use sdk::{
    ApprovalError, ApprovalProvider, ArgumentShape, ArgumentShapePolicy, AuthorizationAttempt,
    AuthorizedCall, Call, Clock, Decision, DecisionMetadata, DelegationError, DelegationProfile,
    Denial, DenialReporting, Diagnostics, Guard, GuardBuildError, GuardError, HolderSigner,
    IdentityError, LocalApprovalSigner, LocalSigner, ObservationRecord, ObservationVerdict,
    ObserveBuildError, ObserveError, Observed, ObservedOutcome, ObservingGuard,
    ObservingGuardBuilder, OwnedReceivedAuthorization, PersistentIdentity, PresentedAuthority,
    PresentedRequest, ReceivedAuthorization, Retryability, RevocationMode, Runtime, RuntimeError,
    SdkDenialKind, Session, SessionWarrant, SystemClock, Tenuo, TenuoBuildError, ValueClass,
    VerifiedProjection,
};
#[cfg(feature = "async")]
pub use sdk::{
    AsyncHolderSigner, AsyncRevocationProvider, AttemptControl, PresentedAsyncAuthority,
};
#[cfg(feature = "receipts")]
pub use sdk::{
    EvidencePolicy, LocalReceiptSigner, MemoryReceiptSink, ReceiptRef, ReceiptSigner,
    ReceiptSignerError, ReceiptSigningRequest, ReceiptSink, ReceiptSinkError,
};
pub use verification::{
    RevocationSnapshot, RevocationState, VerificationContext, VerificationInstant,
};
pub use warrant::{
    Clearance, OwnedAttenuationBuilder, OwnedIssuanceBuilder, PreparedDelegation, Warrant,
    WarrantBuilder, WarrantId, WarrantType, POP_MAX_WINDOWS, POP_TIMESTAMP_WINDOW_SECS,
    WARRANT_ID_PREFIX,
};
pub use wire::MAX_WARRANT_SIZE;

// Re-export approval gate types
pub use approval_gate::{
    approval_requirement, encode_approval_gate_map, evaluate_approval_gates, inspect_approval_gate,
    merge_approval_gate_maps, normalize_approval_gate_message, parse_approval_gate_map,
    propagate_approval_gates, resolve_approval_required_message, ApprovalGateError,
    ApprovalGateInspection, ApprovalGateKind, ApprovalGateMap, ApprovalRequirement,
    ArgApprovalGate, ToolApprovalGate, APPROVAL_GATE_MESSAGE_MAX_CHARS,
};

// Re-export approval request
pub use approval::ApprovalRequest;

// Reserved extension keys. The core (data plane) enforces `tenuo.approval_gates`
// on every verification. Other `tenuo.*` keys are carried opaquely and
// enforced only by the control plane.
pub use approval_gate::APPROVAL_GATE_EXTENSION_KEY; // "tenuo.approval_gates" — data-plane enforced
pub const IDENTITY_BINDING_EXTENSION_KEY: &str = "tenuo.identity_binding"; // control-plane enforced

// Re-export diff types
pub use diff::{
    ChangeType, ClearanceDiff, ConstraintDiff, DelegationDiff, DelegationReceipt, DepthDiff,
    ToolsDiff, TtlDiff,
};

/// Maximum delegation depth to prevent unbounded chains (protocol-level hard cap).
///
/// Individual warrants can set a lower limit via `max_depth` in the payload.
/// This constant prevents DoS attacks from extremely deep chains.
///
/// 64 levels is sufficient for complex storage hierarchies or extensive delegation chains,
/// while still preventing unbounded recursion or DoS attacks.
///
/// **SAFETY**: This MUST NOT exceed 255 because `WarrantPayload.max_depth` is `u8`.
pub const MAX_DELEGATION_DEPTH: u32 = 64;

// Compile-time assertion: MAX_DELEGATION_DEPTH must fit in u8 (wire format uses u8)
const _: () = assert!(
    MAX_DELEGATION_DEPTH <= 255,
    "MAX_DELEGATION_DEPTH must not exceed 255 (u8 max) for wire format compatibility"
);

/// Protocol-level maximum TTL (90 days).
///
/// This is the absolute ceiling enforced by the protocol. Deployments can
/// (and should) configure stricter limits via `Authorizer::with_max_ttl()`.
///
/// 90 days aligns with industry precedent (e.g., Let's Encrypt certificates)
/// while being generous enough for edge cases. Most warrants should use
/// much shorter TTLs (minutes to hours).
pub const MAX_WARRANT_TTL_SECS: u64 = 90 * 24 * 60 * 60; // 7,776,000 seconds

/// Default TTL when not specified (5 minutes).
///
/// Short by design - task-scoped warrants should expire quickly.
/// Expand only as needed for specific use cases.
pub const DEFAULT_WARRANT_TTL_SECS: u64 = 5 * 60; // 300 seconds

/// Context string for Ed25519 signatures (prevents cross-protocol attacks).
///
/// All signatures are computed over: `SIGNATURE_CONTEXT || payload`
///
/// This prevents a signature from one protocol being valid in another.
pub use domain::WARRANT_CONTEXT as SIGNATURE_CONTEXT;

/// Current wire format version
pub const WIRE_VERSION: u8 = 1;

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn test_basic_warrant_creation() {
        let keypair = SigningKey::generate();

        let mut constraints = ConstraintSet::new();
        constraints.insert("cluster".to_string(), Pattern::new("staging-*").unwrap());

        let warrant = Warrant::builder()
            .capability("upgrade_cluster", constraints)
            .ttl(Duration::from_secs(600))
            .holder(keypair.public_key())
            .build(&keypair)
            .unwrap();

        let caps = warrant.capabilities().unwrap();
        assert!(caps.contains_key("upgrade_cluster"));
        assert!(warrant.verify(&keypair.public_key()).is_ok());
    }

    #[test]
    fn test_attenuation_narrows_constraints() {
        let keypair = SigningKey::generate();
        let _child_keypair = SigningKey::generate(); // Unused with new delegation API

        let mut p_constraints = ConstraintSet::new();
        p_constraints.insert("cluster".to_string(), Pattern::new("staging-*").unwrap());

        let parent = Warrant::builder()
            .capability("upgrade_cluster", p_constraints)
            .ttl(Duration::from_secs(600))
            .holder(keypair.public_key())
            .build(&keypair)
            .unwrap();

        let mut c_constraints = ConstraintSet::new();
        c_constraints.insert("cluster".to_string(), Exact::new("staging-web"));

        let child = parent
            .attenuate()
            .capability("upgrade_cluster", c_constraints)
            .build(&keypair) // keypair is parent's holder
            .unwrap();

        assert!(child.expires_at() <= parent.expires_at());
        assert_eq!(child.depth(), parent.depth() + 1);
    }
}