Skip to content
2 changes: 1 addition & 1 deletion cedar-policy-cli/src/command/tpe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub fn tpe(args: &TpeArgs) -> CedarExitCode {
None => {
println!("UNKNOWN");
println!("All policy residuals:");
for p in ans.residual_policies() {
for p in ans.policies() {
println!("{p}");
}
CedarExitCode::Unknown
Expand Down
6 changes: 5 additions & 1 deletion cedar-policy-core/src/pst/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,11 @@ impl Expr {
/// Does this expression contain any [`Expr::ResidualError`] nodes?
///
/// Returns `true` if any subexpression represents a statically-known
/// evaluation error (as determined by TPE).
/// evaluation error (as determined by TPE). Note that this says nothing
/// about whether the expression will, or even can, evaluate to an error.
/// This function should only be used to decide if the expression can be
/// represented as valid Cedar policy text, since the error residual variant
/// does not exist in the Cedar policy syntax.
#[cfg(feature = "tpe")]
pub fn has_error(&self) -> bool {
self.reduce::<bool>(
Expand Down
302 changes: 168 additions & 134 deletions cedar-policy-core/src/tpe.rs

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions cedar-policy-core/src/tpe/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl<'a> Response<'a> {
}

/// Get satisfied permit residual policies
pub fn satisfied_permits(&self) -> impl Iterator<Item = &ResidualPolicy> {
pub fn true_permits(&self) -> impl Iterator<Item = &ResidualPolicy> {
#[expect(
clippy::unwrap_used,
reason = "we know that the policy ids are in the residuals map"
Comment thread
john-h-kastner-aws marked this conversation as resolved.
Expand All @@ -206,7 +206,7 @@ impl<'a> Response<'a> {
}

/// Get satisfied forbid residual policies
pub fn satisfied_forbids(&self) -> impl Iterator<Item = &ResidualPolicy> {
pub fn true_forbids(&self) -> impl Iterator<Item = &ResidualPolicy> {
#[expect(
clippy::unwrap_used,
reason = "we know that the policy ids are in the residuals map"
Expand Down Expand Up @@ -282,9 +282,9 @@ impl<'a> Response<'a> {
.map(|id| self.residuals.get(id).unwrap())
}

/// Look up the [`Residual`] by [`PolicyID`]
pub fn get_residual(&self, id: &PolicyID) -> Option<&Residual> {
self.residuals.get(id).map(|rp| rp.residual.as_ref())
/// Look up the [`ResidualPolicy`] by [`PolicyID`]
pub fn get_residual_policy(&self, id: &PolicyID) -> Option<&ResidualPolicy> {
self.residuals.get(id)
}

/// Attempt to get the authorization decision
Expand Down Expand Up @@ -318,16 +318,24 @@ impl<'a> Response<'a> {
self.request.check_consistency(request)?;

let authorizer = Authorizer::new();
#[expect(clippy::unwrap_used, reason = "policy ids should not clash")]
Ok(authorizer.is_authorized(
request.clone(),
&PolicySet::try_from_iter(self.residuals.values().map(|rp| rp.clone().into())).unwrap(),
entities,
))
Ok(authorizer.is_authorized(request.clone(), &self.policy_set(), entities))
}

/// Get residual policies
pub fn residual_policies(&self) -> impl Iterator<Item = &ResidualPolicy> {
/// Get all policies (including concrete true/false/error residuals)
pub fn policies(&self) -> impl Iterator<Item = &ResidualPolicy> {
self.residuals.values()
}

/// Get all policies (including concrete true/false/error residuals) as a `PolicySet`
pub fn policy_set(&self) -> PolicySet {
let mut ps = PolicySet::new();
for p in self.policies() {
#[expect(
clippy::unwrap_used,
reason = "`PolicySet::add` only fails on duplicate ids, but all residual policies will have unique ids"
)]
ps.add(p.policy.as_ref().clone()).unwrap()
Comment thread
john-h-kastner-aws marked this conversation as resolved.
}
ps
}
}
7 changes: 6 additions & 1 deletion cedar-policy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ Starting with version 3.2.4, changes marked with a star (*) are _language breaki

### Added
- Public syntax tree (`pst`) support for `variadic-is-in-range` feature: a variadic `isInRange` is modelled by a `pst::Expr::VariadicOp{...}` in the PST (#2380).
- Functions for inspecting TPE policy evaluation results (`TpeResponse::reason` and iterators for true/false/error/residual permit/forbid policy IDs).
- For the experimental `tpe` feature, added functions for inspecting partial evaluation results. Adds `TpeResponse::reason` to get the ids for policies
contributing to the authorization decision, and specific iterators to list true/false/error/residual permit/forbid policy IDs. Also adds `TpeResponse::get_policy`
to lookup a partially evaluated policy by id, and `TpeResponse::policy_set` to retrieve all partial evaluated policies as a `PolicySet`.

### Changed

- The experimental protobuf decoding API now validates its inputs, checking structural invariants on entities, expressions, templates, policy sets, and schemas. Additionally, `Entities::decode` now computes the transitive closure instead of assuming it is already computed. These changes may result in lower performance for protobuf decoding. The previous, unvalidated behavior is available via the new `decode_unchecked` methods (e.g., `Entities::decode_unchecked`) for trusted encoded data.
- For the experimental `tpe` feature, `TpeResponse::residual_policies` is updated to return only _non-trivial_ residuals and
`TpeResponse::nontrivial_residual_policies` is deprecated. The previous behavior (iterating all residuals including trivial ones)
is available via `TpeResponse::policies`.

## [4.11.2] - Coming Soon

Expand Down
11 changes: 5 additions & 6 deletions cedar-policy/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2527,9 +2527,8 @@ impl AsRef<ast::PolicySet> for PolicySet {
}

#[doc(hidden)]
impl TryFrom<ast::PolicySet> for PolicySet {
type Error = PolicySetError;
fn try_from(pset: ast::PolicySet) -> Result<Self, Self::Error> {
impl From<ast::PolicySet> for PolicySet {
fn from(pset: ast::PolicySet) -> Self {
Self::from_ast(pset)
}
Comment thread
john-h-kastner-aws marked this conversation as resolved.
}
Expand Down Expand Up @@ -2626,7 +2625,7 @@ impl PolicySet {
}

/// Build the [`PolicySet`] from just the AST information
pub(crate) fn from_ast(ast: ast::PolicySet) -> Result<Self, PolicySetError> {
pub(crate) fn from_ast(ast: ast::PolicySet) -> Self {
let templates = ast
.templates()
.cloned()
Expand All @@ -2637,11 +2636,11 @@ impl PolicySet {
.cloned()
.map(|p| (PolicyId::new(p.id().clone()), p.into()))
.collect();
Ok(Self {
Self {
ast,
policies,
templates,
})
}
}

/// Construct a [`PolicySet`] from a PST [`pst::PolicySet`].
Expand Down
Loading
Loading