use runx_contracts::schema::{IsoDateTime, NonEmptyString};
use runx_contracts::{
AuthorityBounds, AuthorityCapability, AuthorityResourceFamily, AuthoritySubsetComparison,
AuthoritySubsetProof, AuthoritySubsetRelation, AuthoritySubsetResult, AuthorityTerm,
AuthorityVerb, Reference, sha256_hex,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::authority_algebra::{
items_subset, optional_ref_bound_subset, parent_items_preserved, same_reference_address,
};
pub trait FamilySubsetComparator {
fn comparison_algorithm(&self) -> &str;
fn narrow_bounds(
&self,
parent: &AuthorityTerm,
requested: &AuthorityBounds,
) -> AuthorityBounds {
let _ = parent;
requested.clone()
}
fn bounds_subset(&self, child: &AuthorityTerm, parent: &AuthorityTerm) -> bool;
}
#[derive(Clone, Debug, Default)]
pub struct ScopeBoundsComparator;
impl ScopeBoundsComparator {
pub const ALGORITHM: &'static str = "runx.scope-bounds-subset.v1";
}
impl FamilySubsetComparator for ScopeBoundsComparator {
fn comparison_algorithm(&self) -> &str {
Self::ALGORITHM
}
fn bounds_subset(&self, child: &AuthorityTerm, parent: &AuthorityTerm) -> bool {
let AuthorityBounds {
repo_path_globs: child_repo_path_globs,
branch_patterns: child_branch_patterns,
filesystem_roots: child_filesystem_roots,
network_destinations: child_network_destinations,
deployment_environments: child_deployment_environments,
token_audiences: child_token_audiences,
max_cost_units: _, effect_limits: child_effect_limits,
effects: child_effects,
max_runtime_ms: child_max_runtime_ms,
max_fanout: child_max_fanout,
max_child_depth: child_max_child_depth,
} = &child.bounds;
let AuthorityBounds {
repo_path_globs: parent_repo_path_globs,
branch_patterns: parent_branch_patterns,
filesystem_roots: parent_filesystem_roots,
network_destinations: parent_network_destinations,
deployment_environments: parent_deployment_environments,
token_audiences: parent_token_audiences,
max_cost_units: _, effect_limits: _, effects: _, max_runtime_ms: parent_max_runtime_ms,
max_fanout: parent_max_fanout,
max_child_depth: parent_max_child_depth,
} = &parent.bounds;
child_effect_limits.is_empty()
&& child_effects.is_empty()
&& items_subset(child_repo_path_globs, parent_repo_path_globs)
&& items_subset(child_branch_patterns, parent_branch_patterns)
&& items_subset(child_filesystem_roots, parent_filesystem_roots)
&& items_subset(child_network_destinations, parent_network_destinations)
&& items_subset(
child_deployment_environments,
parent_deployment_environments,
)
&& items_subset(child_token_audiences, parent_token_audiences)
&& max_cost_units_subset(&child.bounds, &parent.bounds)
&& optional_ref_bound_subset(
child_max_runtime_ms.as_ref(),
parent_max_runtime_ms.as_ref(),
)
&& optional_ref_bound_subset(child_max_fanout.as_ref(), parent_max_fanout.as_ref())
&& optional_ref_bound_subset(
child_max_child_depth.as_ref(),
parent_max_child_depth.as_ref(),
)
}
}
fn max_cost_units_subset(child: &AuthorityBounds, parent: &AuthorityBounds) -> bool {
match (
child.max_cost_units.as_ref().and_then(|n| n.as_f64()),
parent.max_cost_units.as_ref().and_then(|n| n.as_f64()),
) {
(Some(child), Some(parent)) => child <= parent,
(None, Some(_)) => false,
(_, None) => parent.max_cost_units.is_none(),
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AttenuationRequest {
pub principal_ref: Reference,
pub resource_ref: Reference,
pub resource_family: AuthorityResourceFamily,
pub verbs: Vec<AuthorityVerb>,
pub capabilities: Vec<AuthorityCapability>,
pub bounds: AuthorityBounds,
pub expires_at: Option<IsoDateTime>,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum AttenuationError {
#[error("requested resource family does not match the parent authority")]
ResourceFamilyMismatch,
#[error("requested resource address does not match the parent authority")]
ResourceAddressMismatch,
#[error("requested verbs are not a subset of the parent authority")]
VerbsNotSubset,
#[error("requested capabilities are not a subset of the parent authority")]
CapabilitiesNotSubset,
#[error("requested expiry outlives the parent authority")]
ExpiryNotSubset,
#[error("derived child authority is not a subset of the parent authority")]
ChildNotSubset,
}
pub fn mint_attenuated(
parent: &AuthorityTerm,
request: &AttenuationRequest,
comparator: &dyn FamilySubsetComparator,
checked_at: IsoDateTime,
) -> Result<(AuthorityTerm, AuthoritySubsetProof), AttenuationError> {
if request.resource_family != parent.resource_family {
return Err(AttenuationError::ResourceFamilyMismatch);
}
if !same_reference_address(&request.resource_ref, &parent.resource_ref) {
return Err(AttenuationError::ResourceAddressMismatch);
}
if !items_subset(&request.verbs, &parent.verbs) {
return Err(AttenuationError::VerbsNotSubset);
}
if !items_subset(&request.capabilities, &parent.capabilities) {
return Err(AttenuationError::CapabilitiesNotSubset);
}
if !optional_ref_bound_subset(request.expires_at.as_ref(), parent.expires_at.as_ref()) {
return Err(AttenuationError::ExpiryNotSubset);
}
let bounds = comparator.narrow_bounds(parent, &request.bounds);
let term_id = derive_term_id(parent, request, &bounds);
let child = AuthorityTerm {
term_id,
principal_ref: request.principal_ref.clone(),
resource_ref: request.resource_ref.clone(),
resource_family: request.resource_family.clone(),
verbs: request.verbs.clone(),
bounds,
conditions: parent.conditions.clone(),
approvals: parent.approvals.clone(),
capabilities: request.capabilities.clone(),
expires_at: request.expires_at.clone(),
issued_by_ref: parent.issued_by_ref.clone(),
credential_ref: parent.credential_ref.clone(),
};
if !is_authority_subset(&child, parent, comparator) {
return Err(AttenuationError::ChildNotSubset);
}
let proof = AuthoritySubsetProof {
parent_authority_ref: parent.resource_ref.clone(),
comparison_algorithm: NonEmptyString::from(comparator.comparison_algorithm().to_owned()),
result: AuthoritySubsetResult::Subset,
compared_terms: vec![AuthoritySubsetComparison {
child_term_id: child.term_id.clone(),
parent_term_id: parent.term_id.clone(),
relation: AuthoritySubsetRelation::Subset,
}],
proof_ref: None,
checked_at,
};
Ok((child, proof))
}
#[must_use]
pub fn is_authority_subset(
child: &AuthorityTerm,
parent: &AuthorityTerm,
comparator: &dyn FamilySubsetComparator,
) -> bool {
child.resource_family == parent.resource_family
&& same_reference_address(&child.resource_ref, &parent.resource_ref)
&& items_subset(&child.verbs, &parent.verbs)
&& items_subset(&child.capabilities, &parent.capabilities)
&& parent_items_preserved(&child.conditions, &parent.conditions)
&& parent_items_preserved(&child.approvals, &parent.approvals)
&& optional_ref_bound_subset(child.expires_at.as_ref(), parent.expires_at.as_ref())
&& comparator.bounds_subset(child, parent)
}
fn derive_term_id(
parent: &AuthorityTerm,
request: &AttenuationRequest,
bounds: &AuthorityBounds,
) -> NonEmptyString {
let fingerprint = serde_json::json!({
"parent_term_id": parent.term_id.as_str(),
"principal_ref": request.principal_ref,
"resource_ref": request.resource_ref,
"resource_family": request.resource_family,
"verbs": request.verbs,
"capabilities": request.capabilities,
"bounds": bounds,
"expires_at": request.expires_at.as_ref().map(IsoDateTime::as_str),
});
let digest = sha256_hex(fingerprint.to_string().as_bytes());
NonEmptyString::from(format!("mint-{digest}"))
}
pub fn ensure_subset_proof(
proof: Option<&AuthoritySubsetProof>,
child: &AuthorityTerm,
parent: &AuthorityTerm,
) -> Result<(), SubsetProofError> {
let Some(proof) = proof else {
return Err(SubsetProofError::Missing);
};
if proof.comparison_algorithm.trim().is_empty() || proof.checked_at.trim().is_empty() {
return Err(SubsetProofError::Invalid);
}
if proof.parent_authority_ref != parent.resource_ref {
return Err(SubsetProofError::Invalid);
}
if !matches!(proof.result, AuthoritySubsetResult::Subset) {
return Err(SubsetProofError::Invalid);
}
let compared_terms_match = proof.compared_terms.iter().any(|comparison| {
comparison.child_term_id == child.term_id
&& comparison.parent_term_id == parent.term_id
&& matches!(
comparison.relation,
AuthoritySubsetRelation::Subset | AuthoritySubsetRelation::Equal
)
});
if !compared_terms_match {
return Err(SubsetProofError::Invalid);
}
Ok(())
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum SubsetProofError {
#[error("authority attenuation requires a subset proof")]
Missing,
#[error("authority attenuation subset proof is invalid")]
Invalid,
}
#[cfg(test)]
mod tests {
use super::{
AttenuationError, AttenuationRequest, FamilySubsetComparator, ScopeBoundsComparator,
SubsetProofError, ensure_subset_proof, is_authority_subset, mint_attenuated,
};
use runx_contracts::schema::IsoDateTime;
use runx_contracts::{
AuthorityApproval, AuthorityBounds, AuthorityCapability, AuthorityCondition,
AuthorityConditionPredicate, AuthorityEffectGuard, AuthorityEffectLimit,
AuthorityResourceFamily, AuthoritySubsetRelation, AuthorityTerm, AuthorityVerb, JsonNumber,
Reference, ReferenceType,
};
const CHECKED_AT: &str = "2026-06-23T00:00:00Z";
fn reference(reference_type: ReferenceType, uri: &str) -> Reference {
Reference::with_uri(reference_type, uri)
}
fn condition() -> AuthorityCondition {
AuthorityCondition {
condition_id: "condition_approval".into(),
predicate: AuthorityConditionPredicate::ApprovalPresent,
refs: Vec::new(),
parameters: None,
}
}
fn approval() -> AuthorityApproval {
AuthorityApproval {
approval_ref: reference(ReferenceType::Decision, "runx:decision:approval"),
approved_by_ref: None,
approved_at: None,
criterion_ids: Vec::new(),
}
}
fn parent_charter() -> AuthorityTerm {
AuthorityTerm {
term_id: "charter".into(),
principal_ref: reference(ReferenceType::Principal, "runx:principal:agency"),
resource_ref: reference(ReferenceType::Repository, "runx:repository:monorepo"),
resource_family: AuthorityResourceFamily::Workspace,
verbs: vec![
AuthorityVerb::Read,
AuthorityVerb::Write,
AuthorityVerb::Review,
],
bounds: AuthorityBounds {
repo_path_globs: vec!["apps/**".into(), "packages/**".into()],
network_destinations: vec!["api.internal".into(), "cdn.internal".into()],
max_fanout: Some(8),
max_child_depth: Some(3),
..AuthorityBounds::default()
},
conditions: vec![condition()],
approvals: vec![approval()],
capabilities: vec![
AuthorityCapability::FilesystemRead,
AuthorityCapability::NetworkEgress,
],
expires_at: Some("2026-12-31T00:00:00Z".into()),
issued_by_ref: reference(ReferenceType::Principal, "runx:principal:operator"),
credential_ref: None,
}
}
fn member_request() -> AttenuationRequest {
AttenuationRequest {
principal_ref: reference(ReferenceType::Principal, "runx:principal:member"),
resource_ref: reference(ReferenceType::Repository, "runx:repository:monorepo"),
resource_family: AuthorityResourceFamily::Workspace,
verbs: vec![AuthorityVerb::Read],
capabilities: vec![AuthorityCapability::FilesystemRead],
bounds: AuthorityBounds {
repo_path_globs: vec!["apps/**".into()],
network_destinations: vec!["api.internal".into()],
max_fanout: Some(2),
max_child_depth: Some(1),
..AuthorityBounds::default()
},
expires_at: Some("2026-06-30T00:00:00Z".into()),
}
}
fn checked_at() -> IsoDateTime {
CHECKED_AT.into()
}
#[test]
fn valid_narrowing_yields_child_and_proof_the_validator_accepts() -> Result<(), AttenuationError>
{
let parent = parent_charter();
let (child, proof) = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?;
assert_eq!(child.verbs, vec![AuthorityVerb::Read]);
assert_eq!(
child.capabilities,
vec![AuthorityCapability::FilesystemRead]
);
assert_eq!(child.conditions, parent.conditions);
assert_eq!(child.approvals, parent.approvals);
assert_eq!(child.resource_family, parent.resource_family);
assert_eq!(proof.parent_authority_ref, parent.resource_ref);
assert_eq!(proof.comparison_algorithm, ScopeBoundsComparator::ALGORITHM);
assert_eq!(proof.compared_terms.len(), 1);
assert_eq!(proof.compared_terms[0].child_term_id, child.term_id);
assert_eq!(proof.compared_terms[0].parent_term_id, parent.term_id);
assert_eq!(
proof.compared_terms[0].relation,
AuthoritySubsetRelation::Subset
);
assert_eq!(ensure_subset_proof(Some(&proof), &child, &parent), Ok(()));
assert!(is_authority_subset(&child, &parent, &ScopeBoundsComparator));
Ok(())
}
#[test]
fn term_id_is_deterministic() -> Result<(), AttenuationError> {
let parent = parent_charter();
let first = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?
.0;
let second = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?
.0;
assert_eq!(first.term_id, second.term_id);
assert!(first.term_id.as_str().starts_with("mint-"));
Ok(())
}
#[test]
fn widening_a_verb_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.verbs = vec![AuthorityVerb::Delete];
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::VerbsNotSubset)
);
}
#[test]
fn widening_a_capability_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.capabilities = vec![AuthorityCapability::SecretRead];
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::CapabilitiesNotSubset)
);
}
#[test]
fn widening_a_string_bound_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.bounds.repo_path_globs = vec!["secrets/**".into()];
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
}
#[test]
fn widening_a_numeric_bound_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.bounds.max_fanout = Some(99);
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
}
#[test]
fn widening_max_cost_units_errors_fail_closed() {
let mut parent = parent_charter();
parent.bounds.max_cost_units = Some(JsonNumber::U64(100));
let mut request = member_request();
request.bounds.max_cost_units = Some(JsonNumber::U64(500));
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
}
#[test]
fn dropping_max_cost_units_when_parent_caps_it_errors_fail_closed() {
let mut parent = parent_charter();
parent.bounds.max_cost_units = Some(JsonNumber::U64(100));
let mut request = member_request();
request.bounds.max_cost_units = None;
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
}
#[test]
fn narrowing_max_cost_units_succeeds() -> Result<(), AttenuationError> {
let mut parent = parent_charter();
parent.bounds.max_cost_units = Some(JsonNumber::U64(500));
let mut request = member_request();
request.bounds.max_cost_units = Some(JsonNumber::U64(100));
let (child, _) = mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at())?;
assert!(is_authority_subset(&child, &parent, &ScopeBoundsComparator));
Ok(())
}
#[test]
fn effect_bearing_child_under_scope_comparator_errors_fail_closed() {
let parent = parent_charter();
let mut limit_request = member_request();
limit_request.bounds.effect_limits = vec![AuthorityEffectLimit {
family: "deployment".into(),
unit: "USD".into(),
max_per_call_units: Some(1_000),
max_per_run_units: Some(5_000),
max_per_period_units: None,
period: None,
channels: vec!["stripe".into()],
realm: None,
peer: None,
operation: None,
preflight_ttl_ms: None,
approval_threshold_units: None,
authorization_form: None,
preflight_required: false,
commitment_required: false,
idempotency_required: false,
recovery_required: false,
receipt_before_success: false,
single_use_capability: false,
}];
assert_eq!(
mint_attenuated(
&parent,
&limit_request,
&ScopeBoundsComparator,
checked_at()
)
.map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
let mut guard_request = member_request();
guard_request.bounds.effects = vec![AuthorityEffectGuard {
family: "deployment".into(),
guard_kinds: Vec::new(),
proof_kinds: Vec::new(),
}];
assert_eq!(
mint_attenuated(
&parent,
&guard_request,
&ScopeBoundsComparator,
checked_at()
)
.map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
}
#[test]
fn widening_expiry_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.expires_at = Some("2027-01-01T00:00:00Z".into());
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ExpiryNotSubset)
);
}
#[test]
fn dropping_expiry_when_parent_bounds_it_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.expires_at = None;
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ExpiryNotSubset)
);
}
#[test]
fn mismatched_resource_family_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.resource_family = AuthorityResourceFamily::Effect;
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ResourceFamilyMismatch)
);
}
#[test]
fn mismatched_resource_address_errors_fail_closed() {
let parent = parent_charter();
let mut request = member_request();
request.resource_ref = reference(ReferenceType::Repository, "runx:repository:other");
assert_eq!(
mint_attenuated(&parent, &request, &ScopeBoundsComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ResourceAddressMismatch)
);
}
#[test]
fn conditions_and_approvals_cannot_be_dropped() -> Result<(), AttenuationError> {
let parent = parent_charter();
let (mut child, _) = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?;
assert_eq!(child.conditions, parent.conditions);
assert_eq!(child.approvals, parent.approvals);
child.conditions = Vec::new();
child.approvals = Vec::new();
assert!(!is_authority_subset(
&child,
&parent,
&ScopeBoundsComparator
));
Ok(())
}
#[test]
fn ensure_subset_proof_rejects_absent_proof() -> Result<(), AttenuationError> {
let parent = parent_charter();
let (child, _) = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?;
assert_eq!(
ensure_subset_proof(None, &child, &parent),
Err(SubsetProofError::Missing)
);
Ok(())
}
#[test]
fn ensure_subset_proof_rejects_mismatched_terms() -> Result<(), AttenuationError> {
let parent = parent_charter();
let (child, mut proof) = mint_attenuated(
&parent,
&member_request(),
&ScopeBoundsComparator,
checked_at(),
)?;
proof.compared_terms[0].child_term_id = "other".into();
assert_eq!(
ensure_subset_proof(Some(&proof), &child, &parent),
Err(SubsetProofError::Invalid)
);
Ok(())
}
struct ExactAudienceComparator;
impl FamilySubsetComparator for ExactAudienceComparator {
fn comparison_algorithm(&self) -> &str {
"test.exact-audience.v1"
}
fn bounds_subset(&self, child: &AuthorityTerm, parent: &AuthorityTerm) -> bool {
child.bounds.token_audiences == parent.bounds.token_audiences
}
}
#[test]
fn stub_family_comparator_drives_the_bounds_decision() -> Result<(), AttenuationError> {
let mut parent = parent_charter();
parent.bounds.token_audiences = vec!["aud:a".into()];
let mut request = member_request();
request.bounds.token_audiences = vec!["aud:a".into()];
let (child, proof) =
mint_attenuated(&parent, &request, &ExactAudienceComparator, checked_at())?;
assert_eq!(proof.comparison_algorithm, "test.exact-audience.v1");
assert!(is_authority_subset(
&child,
&parent,
&ExactAudienceComparator
));
let mut widened = member_request();
widened.bounds.token_audiences = vec!["aud:b".into()];
assert_eq!(
mint_attenuated(&parent, &widened, &ExactAudienceComparator, checked_at()).map(|_| ()),
Err(AttenuationError::ChildNotSubset)
);
Ok(())
}
}