aspect-std 0.1.2

Standard aspects library for aspect-rs AOP framework
Documentation
//! RQ3 microbenchmarks: per-aspect overhead and 4-aspect composition
//! overhead at the tool-dispatch join point.
//!
//! Groups:
//!   - `baseline`        — raw dispatcher, no aspects
//!   - `single_aspect`   — each of the 5 evaluation aspects alone
//!   - `chain`           — 2/3/4-aspect chains over the same dispatcher
//!   - `denied`          — short-circuit path (sandbox rejects)
//!
//! All groups run the same trivial dispatcher closure so that any
//! delta between groups is overhead.

use aspect_core::{Aspect, AspectError};
use aspect_runtime::{ChainSlot, Composition};
use aspect_std::{
    ActionAuditTrailAspect, AuditContext, LoggingAspect, RateLimitAspect, TokenBudgetAspect,
    TokenCharge, ToolCall, ToolScopeSandboxAspect,
};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::any::Any;
use std::time::Duration;

// ---------------------------------------------------------------------------
// Target: a trivial tool-dispatch body. The host-side machinery (allowlist
// lookup, charge debit, audit append, log emit) is what the aspects add.
// ---------------------------------------------------------------------------

#[inline(never)]
fn dispatch_body(arg: u64) -> Result<u64, AspectError> {
    Ok(black_box(arg.wrapping_add(1)))
}

// ---------------------------------------------------------------------------
// Helpers to build single-aspect and multi-aspect chains.
// ---------------------------------------------------------------------------

fn sandbox_seeded() -> ToolScopeSandboxAspect {
    let s = ToolScopeSandboxAspect::new();
    s.allow("bench", ["dispatch"]);
    s
}

fn budget_seeded() -> TokenBudgetAspect {
    let b = TokenBudgetAspect::new();
    b.set_budget("bench", u64::MAX);
    b
}

fn bind_all(
    sandbox: &ToolScopeSandboxAspect,
    budget: &TokenBudgetAspect,
    audit: &ActionAuditTrailAspect,
    tool: &str,
) -> (u64, u64, u64) {
    let k_s = sandbox.bind(ToolCall {
        session: "bench".into(),
        tool: tool.into(),
    });
    let k_b = budget.bind(TokenCharge {
        session: "bench".into(),
        estimated: 1,
    });
    let k_a = audit.bind(AuditContext {
        session: "bench".into(),
        tool: tool.into(),
    });
    (k_s, k_b, k_a)
}

fn run_single_aspect<A: Aspect + 'static>(name: &'static str, aspect: A, arg: u64) {
    let chain = Composition::new(vec![ChainSlot::new(name, aspect)]);
    let _ = chain.run("dispatch", move || dispatch_body(arg));
}

// ---------------------------------------------------------------------------
// Benches
// ---------------------------------------------------------------------------

fn bench_baseline(c: &mut Criterion) {
    let mut group = c.benchmark_group("rq3_baseline");
    group.throughput(Throughput::Elements(1));
    group.bench_function("dispatch_body", |b| {
        b.iter(|| dispatch_body(black_box(7)))
    });
    // Empty Composition (chain with no slots) measures the harness overhead.
    let empty = Composition::new(vec![]);
    group.bench_function("empty_chain", |b| {
        b.iter(|| {
            let _ = empty.run("dispatch", || dispatch_body(black_box(7)));
        })
    });
    group.finish();
}

fn bench_single_aspect(c: &mut Criterion) {
    let mut group = c.benchmark_group("rq3_single_aspect");
    group.throughput(Throughput::Elements(1));

    // RateLimiter — high cap so the bench is not throttled.
    group.bench_function("ratelimiter", |b| {
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        b.iter(|| run_single_aspect("RateLimiter", rl.clone(), black_box(7)));
    });

    // StructuredLogger — minimal default config.
    group.bench_function("logger", |b| {
        let lg = LoggingAspect::new();
        b.iter(|| run_single_aspect("StructuredLogger", lg.clone(), black_box(7)));
    });

    // ToolScopeSandbox — bind/release each iteration to match real usage.
    group.bench_function("sandbox", |b| {
        let sb = sandbox_seeded();
        b.iter(|| {
            let key = sb.bind(ToolCall {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            run_single_aspect("ToolScopeSandbox", sb.clone(), black_box(7));
            sb.release(key);
        });
    });

    // TokenBudget — bind/release each iteration.
    group.bench_function("token_budget", |b| {
        let bd = budget_seeded();
        b.iter(|| {
            let key = bd.bind(TokenCharge {
                session: "bench".into(),
                estimated: 1,
            });
            run_single_aspect("TokenBudget", bd.clone(), black_box(7));
            bd.release(key);
        });
    });

    // ActionAuditTrail — bind/release; trail grows unbounded but the
    // bench measures per-iteration overhead, not steady-state memory.
    group.bench_function("audit", |b| {
        let ad = ActionAuditTrailAspect::new();
        b.iter(|| {
            let key = ad.bind(AuditContext {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            run_single_aspect("ActionAuditTrail", ad.clone(), black_box(7));
            ad.release(key);
        });
    });

    group.finish();
}

fn bench_chain(c: &mut Criterion) {
    let mut group = c.benchmark_group("rq3_chain");
    group.throughput(Throughput::Elements(1));

    // 2-aspect: Sandbox + RateLimiter (security minimum).
    group.bench_function(BenchmarkId::new("depth", 2), |b| {
        let sb = sandbox_seeded();
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        b.iter(|| {
            let k_s = sb.bind(ToolCall {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            let chain = Composition::new(vec![
                ChainSlot::new("ToolScopeSandbox", sb.clone()),
                ChainSlot::new("RateLimiter", rl.clone()),
            ]);
            let _ = chain.run("dispatch", || dispatch_body(black_box(7)));
            sb.release(k_s);
        });
    });

    // 3-aspect: + AuditTrail.
    group.bench_function(BenchmarkId::new("depth", 3), |b| {
        let sb = sandbox_seeded();
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        let ad = ActionAuditTrailAspect::new();
        b.iter(|| {
            let k_s = sb.bind(ToolCall {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            let k_a = ad.bind(AuditContext {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            let chain = Composition::new(vec![
                ChainSlot::new("ToolScopeSandbox", sb.clone()),
                ChainSlot::new("RateLimiter", rl.clone()),
                ChainSlot::new("ActionAuditTrail", ad.clone()),
            ]);
            let _ = chain.run("dispatch", || dispatch_body(black_box(7)));
            sb.release(k_s);
            ad.release(k_a);
        });
    });

    // 4-aspect: + Logger (production default).
    group.bench_function(BenchmarkId::new("depth", 4), |b| {
        let sb = sandbox_seeded();
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        let ad = ActionAuditTrailAspect::new();
        let lg = LoggingAspect::new();
        b.iter(|| {
            let k_s = sb.bind(ToolCall {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            let k_a = ad.bind(AuditContext {
                session: "bench".into(),
                tool: "dispatch".into(),
            });
            let chain = Composition::new(vec![
                ChainSlot::new("ToolScopeSandbox", sb.clone()),
                ChainSlot::new("RateLimiter", rl.clone()),
                ChainSlot::new("ActionAuditTrail", ad.clone()),
                ChainSlot::new("StructuredLogger", lg.clone()),
            ]);
            let _ = chain.run("dispatch", || dispatch_body(black_box(7)));
            sb.release(k_s);
            ad.release(k_a);
        });
    });

    // 5-aspect: + TokenBudget (full evaluation set).
    group.bench_function(BenchmarkId::new("depth", 5), |b| {
        let sb = sandbox_seeded();
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        let bd = budget_seeded();
        let ad = ActionAuditTrailAspect::new();
        let lg = LoggingAspect::new();
        b.iter(|| {
            let (k_s, k_b, k_a) = bind_all(&sb, &bd, &ad, "dispatch");
            let chain = Composition::new(vec![
                ChainSlot::new("ToolScopeSandbox", sb.clone()),
                ChainSlot::new("RateLimiter", rl.clone()),
                ChainSlot::new("TokenBudget", bd.clone()),
                ChainSlot::new("ActionAuditTrail", ad.clone()),
                ChainSlot::new("StructuredLogger", lg.clone()),
            ]);
            let _ = chain.run("dispatch", || dispatch_body(black_box(7)));
            sb.release(k_s);
            bd.release(k_b);
            ad.release(k_a);
        });
    });

    group.finish();
}

fn bench_denied(c: &mut Criterion) {
    let mut group = c.benchmark_group("rq3_denied");
    group.throughput(Throughput::Elements(1));

    // Short-circuit at the outermost aspect: the inner three never run.
    // This measures the *floor* cost of a security denial.
    group.bench_function("sandbox_denies_4chain", |b| {
        let sb = sandbox_seeded();
        let rl = RateLimitAspect::new(u64::MAX, Duration::from_secs(1));
        let ad = ActionAuditTrailAspect::new();
        let lg = LoggingAspect::new();
        b.iter(|| {
            let k_s = sb.bind(ToolCall {
                session: "bench".into(),
                tool: "out_of_scope".into(),
            });
            let chain = Composition::new(vec![
                ChainSlot::new("ToolScopeSandbox", sb.clone()),
                ChainSlot::new("RateLimiter", rl.clone()),
                ChainSlot::new("ActionAuditTrail", ad.clone()),
                ChainSlot::new("StructuredLogger", lg.clone()),
            ]);
            let _ = chain.run("dispatch", || dispatch_body(black_box(7)));
            sb.release(k_s);
        });
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_baseline,
    bench_single_aspect,
    bench_chain,
    bench_denied
);
criterion_main!(benches);

// Suppress unused-import warnings on _Any if a future refactor drops it.
#[allow(dead_code)]
fn _keep_any_in_scope(_: Box<dyn Any>) {}