Delegation
Your customer grants a bank application the right to pay on their behalf: the application may initiate payments up to 100.00 EUR, but the customer’s own standing grant allows 10,000.00 EUR and also covers cancellation. The effective permission the application holds is the intersection of both grants: what the application may do is bounded by both what the customer was granted and what the application was granted.
compute_intersection computes that intersection over lists of
AuthorizationDetail.
Computing the intersection
#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;
let subject_allowed = vec![AuthorizationDetail::new(
"payment_initiation",
serde_json::json!({
"actions": ["initiate", "cancel"],
"instructedAmount": {"currency": "EUR", "amount": 10000.0}
}),
)];
let actor_allowed = vec![AuthorizationDetail::new(
"payment_initiation",
serde_json::json!({
"actions": ["initiate", "status"],
"instructedAmount": {"currency": "EUR", "amount": 100.0}
}),
)];
let registry: Vec<TypeSchema> = vec![];
let effective = compute_intersection(&subject_allowed, &actor_allowed, ®istry).unwrap();
assert_eq!(effective.len(), 1);
assert_eq!(effective[0].type_id, "payment_initiation");
assert_eq!(
effective[0].detail_object,
serde_json::json!({
"actions": ["initiate"],
"instructedAmount": {"currency": "EUR", "amount": 100.0}
})
);
}
For every subject/actor pair that shares a type, the pair’s detail objects are merged and one result entry is produced. A pair whose arrays share no elements or whose values conflict contributes nothing, without affecting the other pairs. If nothing intersects – no shared type, or only conflicting pairs – the result is an empty list: there is nothing to delegate.
With a TypeRegistry (see Schema Validation) the same
operation is registry.intersect(&subject_allowed, &actor_allowed), using
the comparators registered with each type.
Typed vs. structural merge
If the registry contains a schema for the type with a ComparatorConfig,
the merge is typed: every registered rule merges its field:
NumericFieldRulepaths take the minimum of the two values (MustMatchrules require equal values). RFC 9396 decimal strings are compared numerically and keep their original representation.ArrayFieldRulepaths take the intersection of the two arrays (MustMatchrules require the same set of elements).
A ruled field present on only one side keeps that side’s value. Every
field covered by neither rule is merged structurally, exactly as below, so
restrictions from both grants survive: an actor-only locations list is
kept, and a currency that differs between the grants is a conflict.
Rule paths must name a single field ($.a.b or $['a']['b']); anything
else fails with InvalidMerge.
Without a comparator, the merge is structural:
| Kind | Rule |
|---|---|
| Object | Common fields merge recursively; fields present on only one side are kept |
| Array | Intersection of the elements |
| Number | Minimum of the two |
| Identical primitives | Passed through |
| Anything else | Rejected as a merge conflict |
The structural merge above explains the example result: actions
intersects to ["initiate"], currency matches exactly, and amount takes
the minimum, 100.0.
Error handling
A disjoint delegation is a policy outcome, reported as an empty list. The
only error is DelegationError::InvalidMerge(String): a comparator rule is
malformed (a path that does not name a single field) or a ruled value has
the wrong type (a non-numeric amount under a numeric rule), both of which
indicate misconfigured grants.
#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;
let subject = [AuthorizationDetail::new("payment_initiation", serde_json::json!({"actions": ["initiate"]}))];
let search_agent = [AuthorizationDetail::new("web_search", serde_json::json!({"actions": ["query"]}))];
let delegated = compute_intersection(&subject, &search_agent, &[]).unwrap();
assert!(delegated.is_empty()); // refuse the delegation
}
Best practices
- Use your
TypeRegistry(registry.intersect) whenever the types have comparators; typed merges honor yourCanReduce/CanNarrowintent instead of falling back to structural rules. - Treat an empty result as a delegation refusal, not an internal error – two parties can legally hold disjoint grants.
- Audit the merged detail objects before storing them: they are the exact grant the actor may exercise on the subject’s behalf.
Next steps
For field-level restrictions on the grants themselves, see HBAC Integration; to enforce that a token re-request does not exceed a single grant, use Subset Comparison.