sim-lib-core 0.1.2

SIM workspace package for sim lib core.
Documentation
use std::sync::Arc;

use sim_kernel::{
    CapabilityName, CapabilitySet, Cx, DefaultFactory, Demand, EagerPolicy, Error, EvalPolicy,
    EventKind, Expr, PreparedArgs, RawArgs, Result, TrustLevel, Value, read_construct_capability,
    read_eval_capability,
};
use sim_shape::{AnyShape, ExprKind, ExprKindShape};

use super::*;

fn origin() -> RequestOrigin {
    RequestOrigin::with_detail(
        Symbol::qualified("test", "origin"),
        Expr::String("ledger-case".to_owned()),
    )
}

fn policy(trust: TrustLevel, capabilities: CapabilitySet) -> ReadPolicy {
    ReadPolicy {
        trust,
        capabilities,
    }
}

fn trusted_read_eval_policy() -> ReadPolicy {
    policy(
        TrustLevel::TrustedSource,
        CapabilitySet::new().grant(read_eval_capability()),
    )
}

fn request_with(
    source: ReadEvalSource,
    expected_shape: Arc<dyn Shape>,
    allow: CapabilitySet,
) -> ReadEvalRequest {
    ReadEvalRequest {
        origin: origin(),
        codec: Symbol::qualified("codec", "lisp"),
        source,
        read_policy: trusted_read_eval_policy(),
        requires: Vec::new(),
        allow,
        expected_shape,
    }
}

struct ActiveCapabilityPolicy {
    capability: CapabilityName,
}

impl EvalPolicy for ActiveCapabilityPolicy {
    fn name(&self) -> &'static str {
        "read-eval-ledger-active-capability-probe"
    }

    fn prepare_call_args(
        &self,
        cx: &mut Cx,
        raw: RawArgs,
        demands: &[Demand],
    ) -> Result<PreparedArgs> {
        EagerPolicy.prepare_call_args(cx, raw, demands)
    }

    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
        EagerPolicy.force(cx, value, demand)
    }

    fn eval_expr(&self, cx: &mut Cx, _expr: Expr) -> Result<Value> {
        cx.factory()
            .bool(cx.capabilities().contains(&self.capability))
    }
}

fn probe_cx(capability: CapabilityName) -> (Cx, sim_kernel::GrantSeat) {
    Cx::new_seated(
        Arc::new(ActiveCapabilityPolicy { capability }),
        Arc::new(DefaultFactory),
    )
}

#[test]
fn admitted_request_records_trace_decision_with_diminished_caps() {
    let probe = CapabilityName::new("test.probe");
    let (mut cx, seat) = probe_cx(probe.clone());
    seat.grant(&mut cx, read_eval_capability());
    seat.grant(&mut cx, read_construct_capability());
    seat.grant(&mut cx, probe.clone());
    let broker = ReadEvalBroker::new();
    let request = request_with(
        ReadEvalSource::Expr(Expr::Nil),
        Arc::new(AnyShape),
        CapabilitySet::new()
            .grant(read_eval_capability())
            .grant(probe.clone()),
    );

    let value = broker.admit(&mut cx, request).unwrap();

    assert_eq!(value.object().as_expr(&mut cx).unwrap(), Expr::Bool(true));
    let events = broker.events_for_run(&read_eval_decision_run()).unwrap();
    assert_eq!(events.len(), 1);
    assert!(matches!(events[0].kind, EventKind::Trace(_)));
    let decisions = broker.decisions(&cx).unwrap();
    assert_eq!(decisions.len(), 1);
    let decision = &decisions[0];
    assert_eq!(decision.outcome, ReadEvalOutcome::Admitted);
    assert_eq!(decision.origin, origin());
    assert_eq!(decision.codec, Symbol::qualified("codec", "lisp"));
    assert_eq!(
        decision.expected_shape,
        Some(Symbol::qualified("core", "Any"))
    );
    assert_cap(&decision.requested, &read_eval_capability());
    assert_cap(&decision.requested, &probe);
    assert_cap(&decision.active, &read_eval_capability());
    assert_cap(&decision.active, &probe);
    assert_no_cap(&decision.active, &read_construct_capability());
}

#[test]
fn denied_request_records_trace_decision_with_diminished_caps() {
    let probe = CapabilityName::new("test.probe");
    let (mut cx, seat) = probe_cx(probe.clone());
    seat.grant(&mut cx, read_eval_capability());
    seat.grant(&mut cx, read_construct_capability());
    seat.grant(&mut cx, probe.clone());
    let broker = ReadEvalBroker::new();
    let request = request_with(
        ReadEvalSource::Expr(Expr::Nil),
        Arc::new(ExprKindShape::new(ExprKind::String)),
        CapabilitySet::new().grant(probe.clone()),
    );

    let err = broker.admit(&mut cx, request).unwrap_err();

    assert!(matches!(err, Error::WrongShape { .. }));
    let events = broker.events_for_run(&read_eval_decision_run()).unwrap();
    assert_eq!(events.len(), 1);
    assert!(matches!(events[0].kind, EventKind::Trace(_)));
    let decisions = broker.decisions(&cx).unwrap();
    assert_eq!(decisions.len(), 1);
    let decision = &decisions[0];
    assert_eq!(decision.outcome, ReadEvalOutcome::ShapeDenied);
    assert_cap(&decision.requested, &probe);
    assert_cap(&decision.active, &probe);
    assert_no_cap(&decision.active, &read_eval_capability());
    assert_no_cap(&decision.active, &read_construct_capability());
}

fn assert_cap(capabilities: &[CapabilityName], expected: &CapabilityName) {
    assert!(
        capabilities.iter().any(|capability| capability == expected),
        "missing capability {expected}; got {capabilities:?}"
    );
}

fn assert_no_cap(capabilities: &[CapabilityName], denied: &CapabilityName) {
    assert!(
        capabilities.iter().all(|capability| capability != denied),
        "unexpected capability {denied}; got {capabilities:?}"
    );
}