use std::sync::Arc;
use sim_codec_lisp::LispCodecLib;
use sim_kernel::{
CapabilityName, CapabilitySet, Cx, DefaultFactory, Demand, EagerPolicy, Error, EvalPolicy,
Expr, PreparedArgs, RawArgs, TrustLevel, read_construct_capability, read_eval_capability,
};
use sim_shape::{AnyShape, ExprKind, ExprKindShape};
use super::*;
fn origin() -> RequestOrigin {
RequestOrigin::new(Symbol::qualified("test", "origin"))
}
fn codec() -> Symbol {
Symbol::qualified("codec", "lisp")
}
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()),
)
}
#[test]
fn source_authority_checks_policy_and_preserves_power_semantics() {
let first = CapabilityName::new("power.first");
let second = CapabilityName::new("power.second");
let allowed = CapabilitySet::new()
.grant(second.clone())
.grant(first.clone())
.grant(second.clone());
let authority = SourceAuthority::new(
trusted_read_eval_policy(),
vec![second.clone(), first.clone(), second.clone()],
allowed,
)
.unwrap();
assert_eq!(
authority.requires(),
&[second.clone(), first.clone(), second]
);
assert_eq!(authority.allow().iter().count(), 2);
assert!(authority.allow().contains(&first));
let untrusted = policy(
TrustLevel::Untrusted,
CapabilitySet::new().grant(read_eval_capability()),
);
assert!(matches!(
SourceAuthority::new(untrusted, Vec::new(), CapabilitySet::new()),
Err(Error::TrustDenied { .. })
));
assert!(matches!(
SourceAuthority::new(
policy(TrustLevel::TrustedSource, CapabilitySet::new()),
Vec::new(),
CapabilitySet::new(),
),
Err(Error::CapabilityDenied { .. })
));
}
#[test]
fn source_authority_projection_and_debug_redact_trusted_policy() {
let authority = SourceAuthority::new(
trusted_read_eval_policy(),
vec![CapabilityName::new("power.required")],
CapabilitySet::new().grant(CapabilityName::new("power.allowed")),
)
.unwrap();
let Datum::Node { tag, fields } = authority.decision_datum() else {
panic!("authority decision projection must be a datum node");
};
assert_eq!(tag, Symbol::qualified("source", "authority"));
assert_eq!(
fields
.iter()
.find(|(name, _)| name.name.as_ref() == "read-policy")
.map(|(_, value)| value),
Some(&Datum::Symbol(Symbol::new("redacted")))
);
let rendered = format!("{authority:?}");
assert!(rendered.contains("<redacted>"));
assert!(!rendered.contains("TrustedSource"));
assert!(!rendered.contains("read-eval"));
}
fn request_with(source: ReadEvalSource, expected_shape: Arc<dyn Shape>) -> ReadEvalRequest {
ReadEvalRequest::new(
origin(),
codec(),
source,
SourceAuthority::new(
trusted_read_eval_policy(),
Vec::new(),
CapabilitySet::new().grant(read_eval_capability()),
)
.unwrap(),
expected_shape,
)
}
struct ActiveCapabilityPolicy {
capability: CapabilityName,
}
impl EvalPolicy for ActiveCapabilityPolicy {
fn name(&self) -> &'static str {
"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 install_registers_broker_value() {
let (mut cx, _seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
assert!(install_read_eval_broker(&mut cx).unwrap());
assert!(!install_read_eval_broker(&mut cx).unwrap());
let value = cx.resolve_value(&read_eval_broker_symbol()).unwrap();
assert!(value.object().downcast_ref::<ReadEvalBroker>().is_some());
}
#[test]
fn missing_read_eval_capability_is_denied() {
let err = SourceAuthority::new(
policy(TrustLevel::TrustedSource, CapabilitySet::new()),
Vec::new(),
CapabilitySet::new(),
)
.unwrap_err();
assert!(matches!(
err,
Error::CapabilityDenied { capability } if capability == read_eval_capability()
));
}
#[test]
fn untrusted_read_eval_policy_is_denied() {
let err = SourceAuthority::new(
policy(
TrustLevel::Untrusted,
CapabilitySet::new().grant(read_eval_capability()),
),
Vec::new(),
CapabilitySet::new(),
)
.unwrap_err();
assert!(matches!(
err,
Error::TrustDenied { capability, trust }
if capability == read_eval_capability() && trust == TrustLevel::Untrusted
));
}
#[test]
fn required_capability_must_be_held_by_caller() {
let required = CapabilityName::new("test.required");
let (mut cx, seat) = probe_cx(read_eval_capability());
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let mut request = request_with(
ReadEvalSource::Expr(Expr::Nil),
Arc::new(ExprKindShape::new(ExprKind::Bool)),
);
request.authority = SourceAuthority::new(
trusted_read_eval_policy(),
vec![required.clone()],
CapabilitySet::new().grant(read_eval_capability()),
)
.unwrap();
let err = ReadEvalBroker::new().admit(&mut cx, request).unwrap_err();
assert!(matches!(
err,
Error::CapabilityDenied { capability } if capability == required
));
}
#[test]
fn allowed_capability_absent_from_caller_is_not_active() {
let extra = CapabilityName::new("test.extra");
let (mut cx, seat) = probe_cx(extra.clone());
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let mut request = request_with(
ReadEvalSource::Expr(Expr::Nil),
Arc::new(ExprKindShape::new(ExprKind::Bool)),
);
request.authority = SourceAuthority::new(
trusted_read_eval_policy(),
Vec::new(),
CapabilitySet::new().grant(extra),
)
.unwrap();
let value = ReadEvalBroker::new().admit(&mut cx, request).unwrap();
assert_eq!(value.object().as_expr(&mut cx).unwrap(), Expr::Bool(false));
assert!(cx.capabilities().contains(&read_eval_capability()));
}
#[test]
fn shape_mismatch_is_denied() {
let (mut cx, seat) = probe_cx(read_eval_capability());
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let request = request_with(
ReadEvalSource::Expr(Expr::Nil),
Arc::new(ExprKindShape::new(ExprKind::String)),
);
let err = ReadEvalBroker::new().admit(&mut cx, request).unwrap_err();
assert!(matches!(err, Error::WrongShape { .. }));
}
#[test]
fn happy_path_returns_value_and_restores_caller_capabilities() {
let (mut cx, seat) = probe_cx(read_eval_capability());
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
expect_granted!(seat.grant(&mut cx, read_construct_capability()));
let request = request_with(
ReadEvalSource::Expr(Expr::Nil),
Arc::new(ExprKindShape::new(ExprKind::Bool)),
);
let value = ReadEvalBroker::new().admit(&mut cx, request).unwrap();
assert_eq!(value.object().as_expr(&mut cx).unwrap(), Expr::Bool(true));
assert!(cx.capabilities().contains(&read_eval_capability()));
assert!(cx.capabilities().contains(&read_construct_capability()));
}
#[test]
fn text_source_decodes_through_named_codec() {
let (mut cx, seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let lib = LispCodecLib::new(sim_kernel::CodecId(1)).unwrap();
cx.load_lib(&lib).unwrap();
let request = request_with(
ReadEvalSource::Text("\"ok\"".to_owned()),
Arc::new(ExprKindShape::new(ExprKind::String)),
);
let value = ReadEvalBroker::new().admit(&mut cx, request).unwrap();
assert_eq!(
value.object().as_expr(&mut cx).unwrap(),
Expr::String("ok".to_owned())
);
}
#[test]
fn bytes_source_decodes_through_named_codec() {
let (mut cx, seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let lib = LispCodecLib::new(sim_kernel::CodecId(1)).unwrap();
cx.load_lib(&lib).unwrap();
let request = request_with(ReadEvalSource::Bytes(b"nil".to_vec()), Arc::new(AnyShape));
let value = ReadEvalBroker::new().admit(&mut cx, request).unwrap();
assert_eq!(value.object().as_expr(&mut cx).unwrap(), Expr::Nil);
}
#[test]
fn dynamic_policies_share_broker_ledger_across_origins_and_codecs() {
let (mut cx, seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
cx.load_lib(&LispCodecLib::new(sim_kernel::CodecId(1)).unwrap())
.unwrap();
let broker = ReadEvalBroker::new();
let text_origin = RequestOrigin::with_detail(
Symbol::qualified("test", "dynamic-text"),
Expr::String("caller-supplied-detail".to_owned()),
);
let text = DynamicSourcePolicy::with_broker(
broker.clone(),
Symbol::qualified("codec", "lisp"),
text_origin.clone(),
);
let expr_origin = RequestOrigin::new(Symbol::qualified("test", "dynamic-expr"));
let expr = DynamicSourcePolicy::with_broker(
broker,
Symbol::qualified("codec", "alternate"),
expr_origin.clone(),
);
let text_value = text
.evaluate_text(
&mut cx,
"\"shared\"",
SourceAuthority::new(trusted_read_eval_policy(), Vec::new(), CapabilitySet::new())
.unwrap(),
Arc::new(ExprKindShape::new(ExprKind::String)),
)
.unwrap();
assert_eq!(
text_value.object().as_expr(&mut cx).unwrap(),
Expr::String("shared".to_owned())
);
expr.evaluate_expr(
&mut cx,
Expr::Nil,
SourceAuthority::new(trusted_read_eval_policy(), Vec::new(), CapabilitySet::new()).unwrap(),
Arc::new(AnyShape),
)
.unwrap();
let decisions = text.decisions(&cx).unwrap();
assert_eq!(decisions.len(), 2);
assert_eq!(decisions[0].origin, text_origin);
assert_eq!(decisions[0].codec, Symbol::qualified("codec", "lisp"));
assert_eq!(decisions[1].origin, expr_origin);
assert_eq!(decisions[1].codec, Symbol::qualified("codec", "alternate"));
assert!(
decisions
.iter()
.all(|decision| decision.outcome == ReadEvalOutcome::Admitted)
);
assert_eq!(
expr.events_for_run(&read_eval_decision_run())
.unwrap()
.len(),
2
);
}
#[test]
fn dynamic_policy_bytes_use_the_same_gate_and_codec_failure_law() {
let (mut cx, seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
expect_granted!(seat.grant(&mut cx, read_eval_capability()));
let policy = DynamicSourcePolicy::new(
Symbol::qualified("codec", "missing"),
RequestOrigin::new(Symbol::qualified("test", "dynamic-bytes")),
);
policy
.evaluate_bytes(
&mut cx,
b"nil".to_vec(),
SourceAuthority::new(trusted_read_eval_policy(), Vec::new(), CapabilitySet::new())
.unwrap(),
Arc::new(AnyShape),
)
.unwrap_err();
assert_eq!(
policy.decisions(&cx).unwrap()[0].outcome,
ReadEvalOutcome::DecodeFailed
);
}