1use std::collections::BTreeSet;
33
34use exo_authority::permission::Permission;
35use exo_core::{Did, Hash256, PublicKey, Signature, Timestamp, crypto, hash::hash_structured};
36use serde::{Deserialize, Serialize};
37
38use crate::{
39 credential::{
40 AVC_SCHEMA_VERSION, AuthorityScope, AutonomousVolitionCredential, AvcConstraints, DataClass,
41 },
42 error::AvcError,
43 llm_usage_receipt::{
44 AVC_LLM_USAGE_EVIDENCE_DOMAIN, LlmUsageCustodyMode, LlmUsageEvidence,
45 validate_llm_usage_evidence,
46 },
47 receipt::AvcTrustReceipt,
48 registry::AvcRegistryRead,
49};
50
51pub const AVC_HUMAN_APPROVAL_SIGNING_DOMAIN: &str = "exo.avc.human-approval.v1";
53pub const AVC_ACTION_SIGNING_DOMAIN: &str = "exo.avc.action.v1";
55pub const AVC_ACTION_COMMITMENT_DOMAIN: &str = "exo.avc.action.commitment.v1";
57pub const AVC_ACTION_DESCRIPTOR_DOMAIN: &str = "exo.avc.action.descriptor.v1";
59pub const AVC_LLM_USAGE_ACTION_NAME: &str = "llm.usage.receipt.emit";
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67pub enum AvcDecision {
68 Allow,
69 Deny,
70 HumanApprovalRequired,
71 ChallengeRequired,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
75pub enum AvcReasonCode {
76 Valid,
77 InvalidSignature,
78 InvalidIssuer,
79 InvalidSubject,
80 InvalidHolder,
81 Expired,
82 NotYetValid,
83 Revoked,
84 Suspended,
85 Quarantined,
86 AuthorityChainMissing,
87 AuthorityChainInvalid,
88 ScopeWidening,
89 PermissionDenied,
90 ToolDenied,
91 CounterpartyDenied,
92 DataClassDenied,
93 BudgetExceeded,
94 RiskExceeded,
95 HumanApprovalMissing,
96 HumanApprovalInvalid,
97 HumanApprovalExpired,
98 DelegationNotAllowed,
99 ConsentMissing,
100 PolicyMissing,
101 MalformedCredential,
102 ForbiddenAction,
103 OutsideTimeWindow,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub struct AvcActionRequest {
112 pub action_id: Hash256,
113 pub actor_did: Did,
114 pub requested_permission: Permission,
115 pub tool: Option<String>,
116 pub target_did: Option<Did>,
117 pub data_class: Option<DataClass>,
118 pub estimated_budget_minor_units: Option<u64>,
119 pub estimated_risk_bp: Option<u32>,
120 #[serde(default)]
121 pub human_approval: Option<AvcHumanApproval>,
122 pub requires_human_approval: bool,
123 pub action_name: Option<String>,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct AvcActionDescriptor {
129 pub schema_version: u16,
130 pub action_id: Hash256,
131 pub actor_did: Did,
132 pub requested_permission: Permission,
133 pub tool: Option<String>,
134 pub target_did: Option<Did>,
135 pub data_class: Option<DataClass>,
136 pub estimated_budget_minor_units: Option<u64>,
137 pub estimated_risk_bp: Option<u32>,
138 pub requires_human_approval: bool,
139 pub human_approval_present: bool,
140 pub action_name: Option<String>,
141}
142
143impl AvcActionDescriptor {
144 #[must_use]
145 pub fn from_action(action: &AvcActionRequest) -> Self {
146 Self {
147 schema_version: AVC_SCHEMA_VERSION,
148 action_id: action.action_id,
149 actor_did: action.actor_did.clone(),
150 requested_permission: action.requested_permission,
151 tool: action.tool.clone(),
152 target_did: action.target_did.clone(),
153 data_class: action.data_class.clone(),
154 estimated_budget_minor_units: action.estimated_budget_minor_units,
155 estimated_risk_bp: action.estimated_risk_bp,
156 requires_human_approval: action.requires_human_approval,
157 human_approval_present: action.human_approval.is_some(),
158 action_name: action.action_name.clone(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct AvcHumanApproval {
165 pub approver_did: Did,
166 pub approved_at: Timestamp,
167 pub expires_at: Option<Timestamp>,
168 pub signature: Signature,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub struct AvcValidationRequest {
173 pub credential: AutonomousVolitionCredential,
174 pub action: Option<AvcActionRequest>,
175 pub now: Timestamp,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179pub struct AvcValidationResult {
180 pub credential_id: Hash256,
181 pub decision: AvcDecision,
182 pub reason_codes: Vec<AvcReasonCode>,
183 pub normalized_holder_did: Did,
184 pub valid_until: Option<Timestamp>,
185 pub receipt: Option<AvcTrustReceipt>,
186}
187
188#[derive(Serialize)]
189struct HumanApprovalSigningPayload<'a> {
190 domain: &'static str,
191 schema_version: u16,
192 credential_id: &'a Hash256,
193 action_id: &'a Hash256,
194 actor_did: &'a Did,
195 requested_permission: &'a Permission,
196 tool: Option<&'a String>,
197 target_did: Option<&'a Did>,
198 data_class: Option<&'a DataClass>,
199 estimated_budget_minor_units: Option<u64>,
200 estimated_risk_bp: Option<u32>,
201 action_name: Option<&'a String>,
202 approver_did: &'a Did,
203 approved_at: &'a Timestamp,
204 expires_at: Option<&'a Timestamp>,
205}
206
207#[derive(Serialize)]
208struct AvcActionSigningPayload<'a> {
209 domain: &'static str,
210 schema_version: u16,
211 credential_id: &'a Hash256,
212 action: &'a AvcActionRequest,
213 validation_now: &'a Timestamp,
214}
215
216#[derive(Serialize)]
217struct AvcActionCommitmentPayload<'a> {
218 domain: &'static str,
219 schema_version: u16,
220 credential_id: &'a Hash256,
221 action: &'a AvcActionRequest,
222 validation_now: &'a Timestamp,
223}
224
225#[derive(Serialize)]
226struct AvcActionDescriptorPayload<'a> {
227 domain: &'static str,
228 descriptor: &'a AvcActionDescriptor,
229}
230
231pub fn validate_avc<R: AvcRegistryRead>(
245 request: &AvcValidationRequest,
246 registry: &R,
247) -> Result<AvcValidationResult, AvcError> {
248 let credential = &request.credential;
249 let credential_id = credential.id()?;
250 let normalized_holder_did = credential.effective_holder().clone();
251 let mut reasons: BTreeSet<AvcReasonCode> = BTreeSet::new();
252 let mut human_approval_required = false;
253
254 if credential.created_at > request.now {
256 reasons.insert(AvcReasonCode::NotYetValid);
257 }
258 if let Some(expires) = credential.expires_at {
259 if expires <= request.now {
260 reasons.insert(AvcReasonCode::Expired);
261 }
262 }
263 if let Some(window) = &credential.constraints.allowed_time_window {
264 if !window.contains(&request.now) {
265 reasons.insert(AvcReasonCode::OutsideTimeWindow);
266 }
267 }
268
269 if credential.signature.is_empty() {
271 reasons.insert(AvcReasonCode::InvalidSignature);
272 } else {
273 match registry.resolve_public_key(&credential.issuer_did) {
274 None => {
275 reasons.insert(AvcReasonCode::InvalidIssuer);
276 }
277 Some(pubkey) => {
278 if !verify_signature(credential, &pubkey)? {
279 reasons.insert(AvcReasonCode::InvalidSignature);
280 }
281 }
282 }
283 }
284
285 if credential.issuer_did != credential.principal_did {
287 match &credential.authority_chain {
288 None => {
289 reasons.insert(AvcReasonCode::AuthorityChainMissing);
290 }
291 Some(chain_ref) => {
292 if !registry.authority_chain_valid(&chain_ref.chain_hash, &request.now) {
293 reasons.insert(AvcReasonCode::AuthorityChainInvalid);
294 }
295 }
296 }
297 }
298 enforce_registered_issuer_grant(credential, registry, &mut reasons);
299
300 if registry.is_revoked(&credential_id) {
302 reasons.insert(AvcReasonCode::Revoked);
303 }
304
305 for consent_ref in &credential.consent_refs {
307 if consent_ref.required && !registry.consent_ref_exists(&consent_ref.consent_id) {
308 reasons.insert(AvcReasonCode::ConsentMissing);
309 }
310 }
311 for policy_ref in &credential.policy_refs {
312 if policy_ref.required
313 && !registry.policy_ref_exists(&policy_ref.policy_id, policy_ref.policy_version)
314 {
315 reasons.insert(AvcReasonCode::PolicyMissing);
316 }
317 }
318
319 if let Some(action) = &request.action {
321 evaluate_action(
322 credential,
323 action,
324 &normalized_holder_did,
325 registry,
326 &request.now,
327 &mut reasons,
328 &mut human_approval_required,
329 )?;
330 }
331
332 let mut sorted: Vec<AvcReasonCode> = reasons.into_iter().collect();
333 let decision = if sorted.is_empty() {
334 sorted.push(AvcReasonCode::Valid);
335 AvcDecision::Allow
336 } else if human_approval_required
337 && reasons_are_only(&sorted, AvcReasonCode::HumanApprovalMissing)
338 {
339 AvcDecision::HumanApprovalRequired
340 } else {
341 AvcDecision::Deny
342 };
343
344 Ok(AvcValidationResult {
345 credential_id,
346 decision,
347 reason_codes: sorted,
348 normalized_holder_did,
349 valid_until: credential.expires_at,
350 receipt: None,
351 })
352}
353
354fn reasons_are_only(reasons: &[AvcReasonCode], expected: AvcReasonCode) -> bool {
355 reasons.len() == 1 && reasons[0] == expected
356}
357
358fn verify_signature(
359 credential: &AutonomousVolitionCredential,
360 pubkey: &PublicKey,
361) -> Result<bool, AvcError> {
362 let payload = credential.signing_payload()?;
367 Ok(crypto::verify(&payload, &credential.signature, pubkey))
368}
369
370fn enforce_registered_issuer_grant<R: AvcRegistryRead>(
371 credential: &AutonomousVolitionCredential,
372 registry: &R,
373 reasons: &mut BTreeSet<AvcReasonCode>,
374) {
375 let Some(granted_permissions) =
376 registry.resolve_issuer_permission_grant(&credential.issuer_did)
377 else {
378 return;
379 };
380 if granted_permissions.is_empty()
381 || credential
382 .authority_scope
383 .permissions
384 .iter()
385 .any(|permission| !granted_permissions.contains(permission))
386 {
387 reasons.insert(AvcReasonCode::ScopeWidening);
388 }
389}
390
391pub fn human_approval_signature_payload(
402 credential: &AutonomousVolitionCredential,
403 action: &AvcActionRequest,
404 approval: &AvcHumanApproval,
405) -> Result<Vec<u8>, AvcError> {
406 let credential_id = credential.id()?;
407 let payload = HumanApprovalSigningPayload {
408 domain: AVC_HUMAN_APPROVAL_SIGNING_DOMAIN,
409 schema_version: AVC_SCHEMA_VERSION,
410 credential_id: &credential_id,
411 action_id: &action.action_id,
412 actor_did: &action.actor_did,
413 requested_permission: &action.requested_permission,
414 tool: action.tool.as_ref(),
415 target_did: action.target_did.as_ref(),
416 data_class: action.data_class.as_ref(),
417 estimated_budget_minor_units: action.estimated_budget_minor_units,
418 estimated_risk_bp: action.estimated_risk_bp,
419 action_name: action.action_name.as_ref(),
420 approver_did: &approval.approver_did,
421 approved_at: &approval.approved_at,
422 expires_at: approval.expires_at.as_ref(),
423 };
424 let mut buf = Vec::new();
425 ciborium::ser::into_writer(&payload, &mut buf)?;
426 Ok(buf)
427}
428
429pub fn avc_action_signature_payload(
438 credential: &AutonomousVolitionCredential,
439 action: &AvcActionRequest,
440 validation_now: &Timestamp,
441) -> Result<Vec<u8>, AvcError> {
442 let credential_id = credential.id()?;
443 let payload = AvcActionSigningPayload {
444 domain: AVC_ACTION_SIGNING_DOMAIN,
445 schema_version: AVC_SCHEMA_VERSION,
446 credential_id: &credential_id,
447 action,
448 validation_now,
449 };
450 let mut buf = Vec::new();
451 ciborium::ser::into_writer(&payload, &mut buf)?;
452 Ok(buf)
453}
454
455pub fn avc_action_commitment_hash(
464 credential: &AutonomousVolitionCredential,
465 action: &AvcActionRequest,
466 validation_now: &Timestamp,
467) -> Result<Hash256, AvcError> {
468 let credential_id = credential.id()?;
469 let payload = AvcActionCommitmentPayload {
470 domain: AVC_ACTION_COMMITMENT_DOMAIN,
471 schema_version: AVC_SCHEMA_VERSION,
472 credential_id: &credential_id,
473 action,
474 validation_now,
475 };
476 hash_structured(&payload).map_err(AvcError::from)
477}
478
479pub fn avc_action_descriptor_hash(descriptor: &AvcActionDescriptor) -> Result<Hash256, AvcError> {
488 hash_structured(&AvcActionDescriptorPayload {
489 domain: AVC_ACTION_DESCRIPTOR_DOMAIN,
490 descriptor,
491 })
492 .map_err(AvcError::from)
493}
494
495pub fn avc_llm_usage_action_request(
501 evidence: &LlmUsageEvidence,
502) -> Result<AvcActionRequest, AvcError> {
503 validate_llm_usage_evidence(evidence)?;
504 Ok(AvcActionRequest {
505 action_id: evidence.action_id,
506 actor_did: evidence.actor_did.clone(),
507 requested_permission: Permission::Execute,
508 tool: Some(AVC_LLM_USAGE_EVIDENCE_DOMAIN.into()),
509 target_did: None,
510 data_class: Some(llm_usage_custody_data_class(evidence.custody_mode)),
511 estimated_budget_minor_units: evidence.usage.cost_minor_units,
512 estimated_risk_bp: None,
513 human_approval: None,
514 requires_human_approval: false,
515 action_name: Some(AVC_LLM_USAGE_ACTION_NAME.into()),
516 })
517}
518
519fn llm_usage_custody_data_class(custody_mode: LlmUsageCustodyMode) -> DataClass {
520 match custody_mode {
521 LlmUsageCustodyMode::ReceiptMinimized => DataClass::Internal,
522 LlmUsageCustodyMode::ExternalPayloadRef => DataClass::Confidential,
523 LlmUsageCustodyMode::DagDbCustody => DataClass::Restricted,
524 }
525}
526
527fn evaluate_action<R: AvcRegistryRead>(
528 credential: &AutonomousVolitionCredential,
529 action: &AvcActionRequest,
530 normalized_holder: &Did,
531 registry: &R,
532 now: &Timestamp,
533 reasons: &mut BTreeSet<AvcReasonCode>,
534 human_approval_required: &mut bool,
535) -> Result<(), AvcError> {
536 if action.actor_did != *normalized_holder && action.actor_did != credential.subject_did {
537 reasons.insert(AvcReasonCode::InvalidHolder);
538 }
539
540 if !credential
541 .authority_scope
542 .permissions
543 .contains(&action.requested_permission)
544 {
545 reasons.insert(AvcReasonCode::PermissionDenied);
546 }
547
548 enforce_tool(&credential.authority_scope, action, reasons);
549 enforce_data_class(&credential.authority_scope, action, reasons);
550 enforce_counterparty(&credential.authority_scope, action, reasons);
551 enforce_budget(&credential.constraints, action, reasons);
552 enforce_risk(
553 credential,
554 &credential.constraints,
555 action,
556 registry,
557 now,
558 reasons,
559 human_approval_required,
560 )?;
561 enforce_forbidden_action(&credential.constraints, action, reasons);
562 Ok(())
563}
564
565fn enforce_tool(
566 scope: &AuthorityScope,
567 action: &AvcActionRequest,
568 reasons: &mut BTreeSet<AvcReasonCode>,
569) {
570 let Some(tool) = &action.tool else {
571 return;
572 };
573 if scope.tools.is_empty() || !scope.tools.iter().any(|t| t == tool) {
574 reasons.insert(AvcReasonCode::ToolDenied);
575 }
576}
577
578fn enforce_data_class(
579 scope: &AuthorityScope,
580 action: &AvcActionRequest,
581 reasons: &mut BTreeSet<AvcReasonCode>,
582) {
583 let Some(class) = &action.data_class else {
584 return;
585 };
586 if !scope.data_classes.iter().any(|c| c == class) {
587 reasons.insert(AvcReasonCode::DataClassDenied);
588 }
589}
590
591fn enforce_counterparty(
592 scope: &AuthorityScope,
593 action: &AvcActionRequest,
594 reasons: &mut BTreeSet<AvcReasonCode>,
595) {
596 let Some(target) = &action.target_did else {
597 return;
598 };
599 if !scope.counterparties.is_empty() && !scope.counterparties.iter().any(|d| d == target) {
600 reasons.insert(AvcReasonCode::CounterpartyDenied);
601 }
602}
603
604fn enforce_budget(
605 constraints: &AvcConstraints,
606 action: &AvcActionRequest,
607 reasons: &mut BTreeSet<AvcReasonCode>,
608) {
609 if let (Some(cap), Some(estimate)) = (
610 constraints.max_budget_minor_units,
611 action.estimated_budget_minor_units,
612 ) {
613 if estimate > cap {
614 reasons.insert(AvcReasonCode::BudgetExceeded);
615 }
616 }
617}
618
619fn enforce_risk<R: AvcRegistryRead>(
620 credential: &AutonomousVolitionCredential,
621 constraints: &AvcConstraints,
622 action: &AvcActionRequest,
623 registry: &R,
624 now: &Timestamp,
625 reasons: &mut BTreeSet<AvcReasonCode>,
626 human_approval_required: &mut bool,
627) -> Result<(), AvcError> {
628 let risk_threshold_requires_approval = if let (Some(threshold), Some(estimate)) =
629 (constraints.approval_threshold_bp, action.estimated_risk_bp)
630 {
631 estimate >= threshold
632 } else {
633 false
634 };
635 if let (Some(cap), Some(estimate)) = (constraints.max_action_risk_bp, action.estimated_risk_bp)
636 {
637 if estimate > cap {
638 reasons.insert(AvcReasonCode::RiskExceeded);
639 }
640 }
641
642 let approval_required = constraints.human_approval_required || risk_threshold_requires_approval;
643 if approval_required {
644 *human_approval_required = true;
645 }
646 if approval_required || action.human_approval.is_some() {
647 match verify_human_approval(credential, action, registry, now)? {
648 Ok(()) => {}
649 Err(reason) => {
650 reasons.insert(reason);
651 }
652 }
653 }
654 Ok(())
655}
656
657fn verify_human_approval<R: AvcRegistryRead>(
658 credential: &AutonomousVolitionCredential,
659 action: &AvcActionRequest,
660 registry: &R,
661 now: &Timestamp,
662) -> Result<Result<(), AvcReasonCode>, AvcError> {
663 let Some(approval) = &action.human_approval else {
664 return Ok(Err(AvcReasonCode::HumanApprovalMissing));
665 };
666 if approval.signature.is_empty() || approval.approved_at > *now {
667 return Ok(Err(AvcReasonCode::HumanApprovalInvalid));
668 }
669 if let Some(expires_at) = approval.expires_at {
670 if expires_at <= approval.approved_at {
671 return Ok(Err(AvcReasonCode::HumanApprovalInvalid));
672 }
673 if expires_at <= *now {
674 return Ok(Err(AvcReasonCode::HumanApprovalExpired));
675 }
676 }
677
678 let Some(public_key) = registry.resolve_human_approval_key(&approval.approver_did) else {
679 return Ok(Err(AvcReasonCode::HumanApprovalInvalid));
680 };
681 let payload = human_approval_signature_payload(credential, action, approval)?;
682 if crypto::verify(&payload, &approval.signature, &public_key) {
683 Ok(Ok(()))
684 } else {
685 Ok(Err(AvcReasonCode::HumanApprovalInvalid))
686 }
687}
688
689fn enforce_forbidden_action(
690 constraints: &AvcConstraints,
691 action: &AvcActionRequest,
692 reasons: &mut BTreeSet<AvcReasonCode>,
693) {
694 let Some(name) = &action.action_name else {
695 return;
696 };
697 if constraints.forbidden_actions.iter().any(|a| a == name) {
698 reasons.insert(AvcReasonCode::ForbiddenAction);
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use exo_core::crypto::KeyPair;
705
706 use super::*;
707 use crate::{
708 credential::{
709 AVC_SCHEMA_VERSION, AuthorityChainRef, AvcConstraints, AvcDraft, AvcSubjectKind,
710 ConsentRef, PolicyRef, TimeWindow, issue_avc, test_support::*,
711 },
712 llm_usage_receipt::{EncryptedPayloadRef, ProviderUsageMetrics},
713 registry::{AvcRegistryWrite, InMemoryAvcRegistry},
714 revocation::{AvcRevocationReason, revoke_avc},
715 };
716
717 const ISSUER_SEED: [u8; 32] = [0x11; 32];
718 const HUMAN_APPROVER_SEED: [u8; 32] = [0x44; 32];
719
720 fn issuer_keypair() -> KeyPair {
721 KeyPair::from_secret_bytes(ISSUER_SEED).expect("valid seed")
722 }
723
724 fn human_approver_keypair() -> KeyPair {
725 KeyPair::from_secret_bytes(HUMAN_APPROVER_SEED).expect("valid seed")
726 }
727
728 struct Harness {
730 registry: InMemoryAvcRegistry,
731 }
732
733 impl Harness {
734 fn new() -> Self {
735 let mut registry = InMemoryAvcRegistry::new();
736 registry.put_public_key(did("issuer"), issuer_keypair().public);
737 Self { registry }
738 }
739
740 fn issue(&self, draft: AvcDraft) -> AutonomousVolitionCredential {
741 issue_avc(draft, |bytes| issuer_keypair().sign(bytes)).unwrap()
742 }
743 }
744
745 fn baseline_request(
746 cred: AutonomousVolitionCredential,
747 now: Timestamp,
748 ) -> AvcValidationRequest {
749 AvcValidationRequest {
750 credential: cred,
751 action: None,
752 now,
753 }
754 }
755
756 fn baseline_action(actor: Did) -> AvcActionRequest {
757 AvcActionRequest {
758 action_id: h256(0x55),
759 actor_did: actor,
760 requested_permission: Permission::Read,
761 tool: None,
762 target_did: None,
763 data_class: None,
764 estimated_budget_minor_units: None,
765 estimated_risk_bp: None,
766 human_approval: None,
767 requires_human_approval: false,
768 action_name: None,
769 }
770 }
771
772 fn baseline_llm_usage_evidence(custody_mode: LlmUsageCustodyMode) -> LlmUsageEvidence {
773 let mut evidence = LlmUsageEvidence {
774 schema_version: AVC_SCHEMA_VERSION,
775 tenant_id: "tenant-alpha".into(),
776 namespace: "default".into(),
777 actor_did: did("agent"),
778 provider: "openai".into(),
779 provider_endpoint: "responses".into(),
780 model_id: "gpt-test".into(),
781 provider_request_id_hash: Some(h256(0xA1)),
782 session_id_hash: Some(h256(0xA2)),
783 idempotency_key_hash: h256(0xA3),
784 action_id: h256(0xA4),
785 prompt_hash: h256(0xA5),
786 completion_hash: Some(h256(0xA6)),
787 tool_call_hash: None,
788 tool_result_hash: None,
789 usage: ProviderUsageMetrics {
790 input_tokens: 10,
791 output_tokens: 20,
792 total_tokens: 30,
793 cached_input_tokens: None,
794 reasoning_tokens: None,
795 cost_minor_units: Some(125),
796 cost_currency: Some("USD".into()),
797 usage_complete: true,
798 },
799 custody_mode,
800 encrypted_payload_refs: Vec::new(),
801 custody_policy_hash: h256(0xA7),
802 created_at: ts(1_600_000),
803 };
804 if custody_mode == LlmUsageCustodyMode::ExternalPayloadRef {
805 evidence.encrypted_payload_refs = vec![EncryptedPayloadRef {
806 ref_id_hash: h256(0xB1),
807 ciphertext_hash: h256(0xB2),
808 storage_policy_hash: h256(0xB3),
809 key_policy_hash: h256(0xB4),
810 payload_kind: "provider_exchange".into(),
811 byte_length: 128,
812 }];
813 }
814 evidence
815 }
816
817 fn attach_signed_human_approval(
818 credential: &AutonomousVolitionCredential,
819 action: &mut AvcActionRequest,
820 approver_did: Did,
821 approved_at: Timestamp,
822 expires_at: Option<Timestamp>,
823 approver_keypair: &KeyPair,
824 ) {
825 action.human_approval = Some(AvcHumanApproval {
826 approver_did,
827 approved_at,
828 expires_at,
829 signature: Signature::empty(),
830 });
831 let payload = human_approval_signature_payload(
832 credential,
833 action,
834 action
835 .human_approval
836 .as_ref()
837 .expect("approval placeholder"),
838 )
839 .expect("canonical approval payload");
840 action
841 .human_approval
842 .as_mut()
843 .expect("approval placeholder")
844 .signature = approver_keypair.sign(&payload);
845 }
846
847 #[test]
848 fn llm_usage_action_request_uses_execute_and_evidence_fields() {
849 let evidence = baseline_llm_usage_evidence(LlmUsageCustodyMode::ReceiptMinimized);
850 let action = avc_llm_usage_action_request(&evidence).expect("LYNK action");
851
852 assert_eq!(action.action_id, evidence.action_id);
853 assert_eq!(action.actor_did, evidence.actor_did);
854 assert_eq!(action.requested_permission, Permission::Execute);
855 assert_eq!(action.tool, Some(AVC_LLM_USAGE_EVIDENCE_DOMAIN.into()));
856 assert_eq!(action.data_class, Some(DataClass::Internal));
857 assert_eq!(action.estimated_budget_minor_units, Some(125));
858 assert_eq!(action.estimated_risk_bp, None);
859 assert_eq!(action.action_name, Some(AVC_LLM_USAGE_ACTION_NAME.into()));
860 assert!(!action.requires_human_approval);
861 assert!(action.human_approval.is_none());
862 }
863
864 #[test]
865 fn llm_usage_action_request_derives_data_class_from_custody_mode() {
866 let minimized = avc_llm_usage_action_request(&baseline_llm_usage_evidence(
867 LlmUsageCustodyMode::ReceiptMinimized,
868 ))
869 .expect("minimized action");
870 let external = avc_llm_usage_action_request(&baseline_llm_usage_evidence(
871 LlmUsageCustodyMode::ExternalPayloadRef,
872 ))
873 .expect("external action");
874 let dagdb = avc_llm_usage_action_request(&baseline_llm_usage_evidence(
875 LlmUsageCustodyMode::DagDbCustody,
876 ))
877 .expect("dagdb action");
878
879 assert_eq!(minimized.data_class, Some(DataClass::Internal));
880 assert_eq!(external.data_class, Some(DataClass::Confidential));
881 assert_eq!(dagdb.data_class, Some(DataClass::Restricted));
882 }
883
884 #[test]
885 fn llm_usage_action_request_fails_closed_on_invalid_evidence() {
886 let mut evidence = baseline_llm_usage_evidence(LlmUsageCustodyMode::ReceiptMinimized);
887 evidence.provider_endpoint = " ".into();
888
889 assert!(matches!(
890 avc_llm_usage_action_request(&evidence),
891 Err(AvcError::EmptyField {
892 field: "llm_usage.provider_endpoint"
893 })
894 ));
895 }
896
897 #[test]
898 fn valid_credential_allows() {
899 let h = Harness::new();
900 let cred = h.issue(baseline_draft());
901 let request = baseline_request(cred, ts(1_500_000));
902 let result = validate_avc(&request, &h.registry).unwrap();
903 assert_eq!(result.decision, AvcDecision::Allow);
904 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
905 }
906
907 #[test]
908 fn allows_credential_when_issuer_has_no_registered_grant() {
909 let h = Harness::new();
910 let mut draft = baseline_draft();
911 draft.authority_scope.permissions = vec![Permission::Read, Permission::Write];
912 let cred = h.issue(draft);
913
914 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
915
916 assert_eq!(result.decision, AvcDecision::Allow);
917 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
918 }
919
920 #[test]
921 fn allows_credential_scope_within_registered_issuer_grant() {
922 let mut h = Harness::new();
923 h.registry
924 .put_issuer_permission_grant(did("issuer"), vec![Permission::Read]);
925 let mut draft = baseline_draft();
926 draft.authority_scope.permissions = vec![Permission::Read];
927 let cred = h.issue(draft);
928
929 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
930
931 assert_eq!(result.decision, AvcDecision::Allow);
932 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
933 }
934
935 #[test]
936 fn allows_credential_scope_with_duplicate_registered_grant_entries() {
937 let mut h = Harness::new();
938 h.registry.put_issuer_permission_grant(
939 did("issuer"),
940 vec![Permission::Write, Permission::Read, Permission::Write],
941 );
942 let mut draft = baseline_draft();
943 draft.authority_scope.permissions = vec![Permission::Read, Permission::Write];
944 let cred = h.issue(draft);
945
946 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
947
948 assert_eq!(result.decision, AvcDecision::Allow);
949 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
950 }
951
952 #[test]
953 fn action_signature_payload_is_domain_separated_and_context_bound() {
954 let h = Harness::new();
955 let cred = h.issue(baseline_draft());
956 let action = baseline_action(cred.subject_did.clone());
957 let payload_one = avc_action_signature_payload(&cred, &action, &ts(1_500_000)).unwrap();
958 let payload_two = avc_action_signature_payload(&cred, &action, &ts(1_500_001)).unwrap();
959 let needle = AVC_ACTION_SIGNING_DOMAIN.as_bytes();
960
961 assert!(payload_one.windows(needle.len()).any(|w| w == needle));
962 assert_ne!(payload_one, payload_two);
963 }
964
965 #[test]
966 fn denies_unknown_issuer_key() {
967 let h = Harness::new();
968 let mut draft = baseline_draft();
969 draft.issuer_did = did("ghost");
970 draft.principal_did = did("ghost"); let cred = h.issue(draft);
972 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
973 assert_eq!(result.decision, AvcDecision::Deny);
974 assert!(result.reason_codes.contains(&AvcReasonCode::InvalidIssuer));
975 }
976
977 #[test]
978 fn denies_empty_signature() {
979 let h = Harness::new();
980 let mut cred = h.issue(baseline_draft());
981 cred.signature = Signature::empty();
982 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
983 assert_eq!(result.decision, AvcDecision::Deny);
984 assert!(
985 result
986 .reason_codes
987 .contains(&AvcReasonCode::InvalidSignature)
988 );
989 }
990
991 #[test]
992 fn denies_invalid_signature_when_payload_tampered() {
993 let h = Harness::new();
994 let mut cred = h.issue(baseline_draft());
995 cred.delegated_intent.purpose = "tampered".into();
997 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
998 assert_eq!(result.decision, AvcDecision::Deny);
999 assert!(
1000 result
1001 .reason_codes
1002 .contains(&AvcReasonCode::InvalidSignature)
1003 );
1004 }
1005
1006 #[test]
1007 fn denies_wrong_key_signature() {
1008 let h = Harness::new();
1009 let other = KeyPair::from_secret_bytes([0x99; 32]).unwrap();
1010 let mut cred = h.issue(baseline_draft());
1011 let payload = cred.signing_payload().unwrap();
1013 cred.signature = other.sign(&payload);
1014 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1015 assert_eq!(result.decision, AvcDecision::Deny);
1016 assert!(
1017 result
1018 .reason_codes
1019 .contains(&AvcReasonCode::InvalidSignature)
1020 );
1021 }
1022
1023 #[test]
1024 fn denies_expired_credential() {
1025 let h = Harness::new();
1026 let cred = h.issue(baseline_draft());
1027 let result = validate_avc(&baseline_request(cred, ts(3_000_000)), &h.registry).unwrap();
1028 assert_eq!(result.decision, AvcDecision::Deny);
1029 assert!(result.reason_codes.contains(&AvcReasonCode::Expired));
1030 }
1031
1032 #[test]
1033 fn denies_not_yet_valid_credential() {
1034 let h = Harness::new();
1035 let cred = h.issue(baseline_draft());
1036 let result = validate_avc(&baseline_request(cred, ts(0)), &h.registry).unwrap();
1037 assert_eq!(result.decision, AvcDecision::Deny);
1038 assert!(result.reason_codes.contains(&AvcReasonCode::NotYetValid));
1039 }
1040
1041 #[test]
1042 fn denies_outside_time_window() {
1043 let h = Harness::new();
1044 let mut draft = baseline_draft();
1045 draft.constraints.allowed_time_window = Some(TimeWindow {
1046 not_before: ts(1_400_000),
1047 not_after: ts(1_450_000),
1048 });
1049 let cred = h.issue(draft);
1050 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1051 assert!(
1052 result
1053 .reason_codes
1054 .contains(&AvcReasonCode::OutsideTimeWindow)
1055 );
1056 }
1057
1058 #[test]
1059 fn denies_revoked_credential() {
1060 let mut h = Harness::new();
1061 let cred = h.issue(baseline_draft());
1062 let id = cred.id().unwrap();
1063 h.registry.put_credential(cred.clone()).unwrap();
1064 let revocation = revoke_avc(
1065 id,
1066 did("issuer"),
1067 AvcRevocationReason::IssuerRevoked,
1068 ts(1_250_000),
1069 |bytes| issuer_keypair().sign(bytes),
1070 )
1071 .unwrap();
1072 h.registry.put_revocation(revocation).unwrap();
1073 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1074 assert_eq!(result.decision, AvcDecision::Deny);
1075 assert!(result.reason_codes.contains(&AvcReasonCode::Revoked));
1076 }
1077
1078 #[test]
1079 fn denies_missing_authority_chain_when_issuer_differs_from_principal() {
1080 let h = Harness::new();
1081 let mut draft = baseline_draft();
1082 draft.principal_did = did("principal");
1083 let cred = h.issue(draft);
1085 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1086 assert!(
1087 result
1088 .reason_codes
1089 .contains(&AvcReasonCode::AuthorityChainMissing)
1090 );
1091 }
1092
1093 #[test]
1094 fn denies_invalid_authority_chain_hash() {
1095 let h = Harness::new();
1096 let mut draft = baseline_draft();
1097 draft.principal_did = did("principal");
1098 draft.authority_chain = Some(AuthorityChainRef {
1099 chain_hash: h256(0xDE),
1100 });
1101 let cred = h.issue(draft);
1102 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1103 assert!(
1104 result
1105 .reason_codes
1106 .contains(&AvcReasonCode::AuthorityChainInvalid)
1107 );
1108 }
1109
1110 #[test]
1111 fn accepts_valid_authority_chain_hash() {
1112 let mut h = Harness::new();
1113 let mut draft = baseline_draft();
1114 draft.principal_did = did("principal");
1115 draft.authority_chain = Some(AuthorityChainRef {
1116 chain_hash: h256(0xDE),
1117 });
1118 h.registry.mark_authority_chain_valid(h256(0xDE));
1119 let cred = h.issue(draft);
1120 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1121 assert_eq!(result.decision, AvcDecision::Allow);
1122 }
1123
1124 #[test]
1125 fn denies_missing_required_consent_ref() {
1126 let h = Harness::new();
1127 let mut draft = baseline_draft();
1128 draft.consent_refs = vec![ConsentRef {
1129 consent_id: h256(0xC0),
1130 required: true,
1131 }];
1132 let cred = h.issue(draft);
1133 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1134 assert!(result.reason_codes.contains(&AvcReasonCode::ConsentMissing));
1135 }
1136
1137 #[test]
1138 fn allows_when_optional_consent_ref_missing() {
1139 let h = Harness::new();
1140 let mut draft = baseline_draft();
1141 draft.consent_refs = vec![ConsentRef {
1142 consent_id: h256(0xC0),
1143 required: false,
1144 }];
1145 let cred = h.issue(draft);
1146 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1147 assert_eq!(result.decision, AvcDecision::Allow);
1148 }
1149
1150 #[test]
1151 fn denies_missing_required_policy_ref() {
1152 let h = Harness::new();
1153 let mut draft = baseline_draft();
1154 draft.policy_refs = vec![PolicyRef {
1155 policy_id: h256(0xB1),
1156 policy_version: 2,
1157 required: true,
1158 }];
1159 let cred = h.issue(draft);
1160 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1161 assert!(result.reason_codes.contains(&AvcReasonCode::PolicyMissing));
1162 }
1163
1164 #[test]
1165 fn denies_actor_mismatch() {
1166 let h = Harness::new();
1167 let cred = h.issue(baseline_draft());
1168 let mut request = baseline_request(cred, ts(1_500_000));
1169 request.action = Some(baseline_action(did("imposter")));
1170 let result = validate_avc(&request, &h.registry).unwrap();
1171 assert!(result.reason_codes.contains(&AvcReasonCode::InvalidHolder));
1172 }
1173
1174 #[test]
1175 fn denies_permission_outside_scope() {
1176 let h = Harness::new();
1177 let cred = h.issue(baseline_draft());
1178 let actor = cred.subject_did.clone();
1179 let mut action = baseline_action(actor);
1180 action.requested_permission = Permission::Govern;
1181 let mut request = baseline_request(cred, ts(1_500_000));
1182 request.action = Some(action);
1183 let result = validate_avc(&request, &h.registry).unwrap();
1184 assert!(
1185 result
1186 .reason_codes
1187 .contains(&AvcReasonCode::PermissionDenied)
1188 );
1189 }
1190
1191 #[test]
1192 fn denies_credential_scope_wider_than_registered_issuer_grant() {
1193 let mut h = Harness::new();
1194 h.registry.put_issuer_permission_grant(
1195 did("issuer"),
1196 vec![
1197 Permission::Read,
1198 Permission::Write,
1199 Permission::Execute,
1200 Permission::Delegate,
1201 ],
1202 );
1203 let mut draft = baseline_draft();
1204 draft.authority_scope.permissions = vec![Permission::Govern];
1205 let cred = h.issue(draft);
1206 let actor = cred.subject_did.clone();
1207 let mut action = baseline_action(actor);
1208 action.requested_permission = Permission::Govern;
1209 let mut request = baseline_request(cred, ts(1_500_000));
1210 request.action = Some(action);
1211
1212 let result = validate_avc(&request, &h.registry).unwrap();
1213
1214 assert_eq!(result.decision, AvcDecision::Deny);
1215 assert!(
1216 result.reason_codes.contains(&AvcReasonCode::ScopeWidening),
1217 "root issuer grants must cap credential-declared permissions"
1218 );
1219 }
1220
1221 #[test]
1222 fn denies_all_credentials_from_issuer_capped_to_empty_permission_set() {
1223 let mut h = Harness::new();
1224 h.registry
1225 .put_issuer_permission_grant(did("issuer"), vec![]);
1226 let mut draft = baseline_draft();
1227 draft.authority_scope.permissions = vec![];
1228 let cred = h.issue(draft);
1229 let request = baseline_request(cred, ts(1_500_000));
1230
1231 let result = validate_avc(&request, &h.registry).unwrap();
1232
1233 assert_eq!(result.decision, AvcDecision::Deny);
1234 assert!(
1235 result.reason_codes.contains(&AvcReasonCode::ScopeWidening),
1236 "an issuer capped to no permissions must be able to sign nothing"
1237 );
1238 }
1239
1240 #[test]
1241 fn denies_any_credential_permission_outside_registered_issuer_grant() {
1242 let mut h = Harness::new();
1243 h.registry
1244 .put_issuer_permission_grant(did("issuer"), vec![Permission::Read]);
1245 let mut draft = baseline_draft();
1246 draft.authority_scope.permissions = vec![Permission::Read, Permission::Write];
1247 let cred = h.issue(draft);
1248
1249 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1250
1251 assert_eq!(result.decision, AvcDecision::Deny);
1252 assert!(
1253 result.reason_codes.contains(&AvcReasonCode::ScopeWidening),
1254 "any credential permission outside the issuer grant must fail closed"
1255 );
1256 }
1257
1258 #[test]
1259 fn denies_tool_outside_scope() {
1260 let h = Harness::new();
1261 let cred = h.issue(baseline_draft());
1262 let actor = cred.subject_did.clone();
1263 let mut action = baseline_action(actor);
1264 action.tool = Some("ungoverned".into());
1265 let mut request = baseline_request(cred, ts(1_500_000));
1266 request.action = Some(action);
1267 let result = validate_avc(&request, &h.registry).unwrap();
1268 assert!(result.reason_codes.contains(&AvcReasonCode::ToolDenied));
1269 }
1270
1271 #[test]
1272 fn empty_tool_scope_denies_any_tool_action() {
1273 let h = Harness::new();
1274 let mut draft = baseline_draft();
1275 draft.authority_scope.tools = vec![];
1276 let cred = h.issue(draft);
1277 let actor = cred.subject_did.clone();
1278 let mut action = baseline_action(actor);
1279 action.tool = Some("anything".into());
1280 let mut request = baseline_request(cred, ts(1_500_000));
1281 request.action = Some(action);
1282 let result = validate_avc(&request, &h.registry).unwrap();
1283 assert!(result.reason_codes.contains(&AvcReasonCode::ToolDenied));
1284 }
1285
1286 #[test]
1287 fn empty_tool_scope_allows_action_without_tool() {
1288 let h = Harness::new();
1289 let mut draft = baseline_draft();
1290 draft.authority_scope.tools = vec![];
1291 let cred = h.issue(draft);
1292 let actor = cred.subject_did.clone();
1293 let action = baseline_action(actor);
1294 let mut request = baseline_request(cred, ts(1_500_000));
1295 request.action = Some(action);
1296 let result = validate_avc(&request, &h.registry).unwrap();
1297 assert_eq!(result.decision, AvcDecision::Allow);
1298 }
1299
1300 #[test]
1301 fn denies_data_class_outside_scope() {
1302 let h = Harness::new();
1303 let cred = h.issue(baseline_draft());
1304 let actor = cred.subject_did.clone();
1305 let mut action = baseline_action(actor);
1306 action.data_class = Some(DataClass::SensitivePersonalData);
1307 let mut request = baseline_request(cred, ts(1_500_000));
1308 request.action = Some(action);
1309 let result = validate_avc(&request, &h.registry).unwrap();
1310 assert!(
1311 result
1312 .reason_codes
1313 .contains(&AvcReasonCode::DataClassDenied)
1314 );
1315 }
1316
1317 #[test]
1318 fn denies_counterparty_when_allowlist_present() {
1319 let h = Harness::new();
1320 let mut draft = baseline_draft();
1321 draft.authority_scope.counterparties = vec![did("approved-cp")];
1322 let cred = h.issue(draft);
1323 let actor = cred.subject_did.clone();
1324 let mut action = baseline_action(actor);
1325 action.target_did = Some(did("malicious-cp"));
1326 let mut request = baseline_request(cred, ts(1_500_000));
1327 request.action = Some(action);
1328 let result = validate_avc(&request, &h.registry).unwrap();
1329 assert!(
1330 result
1331 .reason_codes
1332 .contains(&AvcReasonCode::CounterpartyDenied)
1333 );
1334 }
1335
1336 #[test]
1337 fn empty_counterparty_list_allows_any_target() {
1338 let h = Harness::new();
1339 let cred = h.issue(baseline_draft());
1340 let actor = cred.subject_did.clone();
1341 let mut action = baseline_action(actor);
1342 action.target_did = Some(did("any"));
1343 let mut request = baseline_request(cred, ts(1_500_000));
1344 request.action = Some(action);
1345 let result = validate_avc(&request, &h.registry).unwrap();
1346 assert_eq!(result.decision, AvcDecision::Allow);
1347 }
1348
1349 #[test]
1350 fn denies_budget_exceeded() {
1351 let h = Harness::new();
1352 let mut draft = baseline_draft();
1353 draft.constraints.max_budget_minor_units = Some(1_000);
1354 let cred = h.issue(draft);
1355 let actor = cred.subject_did.clone();
1356 let mut action = baseline_action(actor);
1357 action.estimated_budget_minor_units = Some(2_000);
1358 let mut request = baseline_request(cred, ts(1_500_000));
1359 request.action = Some(action);
1360 let result = validate_avc(&request, &h.registry).unwrap();
1361 assert!(result.reason_codes.contains(&AvcReasonCode::BudgetExceeded));
1362 }
1363
1364 #[test]
1365 fn in_scope_action_at_budget_and_risk_caps_allows() {
1366 let h = Harness::new();
1367 let mut draft = baseline_draft();
1368 draft.authority_scope.counterparties = vec![did("approved-cp")];
1369 draft.constraints.max_budget_minor_units = Some(1_000);
1370 draft.constraints.max_action_risk_bp = Some(1_000);
1371 let cred = h.issue(draft);
1372 let actor = cred.subject_did.clone();
1373 let mut action = baseline_action(actor);
1374 action.tool = Some("alpha".into());
1375 action.data_class = Some(DataClass::Public);
1376 action.target_did = Some(did("approved-cp"));
1377 action.estimated_budget_minor_units = Some(1_000);
1378 action.estimated_risk_bp = Some(1_000);
1379 let mut request = baseline_request(cred, ts(1_500_000));
1380 request.action = Some(action);
1381 let result = validate_avc(&request, &h.registry).unwrap();
1382 assert_eq!(result.decision, AvcDecision::Allow);
1383 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1384 }
1385
1386 #[test]
1387 fn in_scope_action_with_allowed_tool_allows() {
1388 let h = Harness::new();
1389 let cred = h.issue(baseline_draft());
1390 let actor = cred.subject_did.clone();
1391 let mut action = baseline_action(actor);
1392 action.tool = Some("alpha".into());
1393 let mut request = baseline_request(cred, ts(1_500_000));
1394 request.action = Some(action);
1395 let result = validate_avc(&request, &h.registry).unwrap();
1396 assert_eq!(result.decision, AvcDecision::Allow);
1397 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1398 }
1399
1400 #[test]
1401 fn non_expiring_credential_allows_explicit_holder_action() {
1402 let h = Harness::new();
1403 let mut draft = baseline_draft();
1404 draft.holder_did = Some(did("holder"));
1405 draft.expires_at = None;
1406 let cred = h.issue(draft);
1407 let mut request = baseline_request(cred, ts(1_500_000));
1408 request.action = Some(baseline_action(did("holder")));
1409 let result = validate_avc(&request, &h.registry).unwrap();
1410 assert_eq!(result.decision, AvcDecision::Allow);
1411 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1412 assert_eq!(result.normalized_holder_did, did("holder"));
1413 assert_eq!(result.valid_until, None);
1414 }
1415
1416 #[test]
1417 fn subject_actor_remains_valid_when_holder_is_explicit() {
1418 let h = Harness::new();
1419 let mut draft = baseline_draft();
1420 draft.holder_did = Some(did("holder"));
1421 let cred = h.issue(draft);
1422 let mut request = baseline_request(cred, ts(1_500_000));
1423 request.action = Some(baseline_action(did("agent")));
1424 let result = validate_avc(&request, &h.registry).unwrap();
1425 assert_eq!(result.decision, AvcDecision::Allow);
1426 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1427 assert_eq!(result.normalized_holder_did, did("holder"));
1428 }
1429
1430 #[test]
1431 fn risk_at_approval_threshold_requires_human_approval() {
1432 let h = Harness::new();
1433 let mut draft = baseline_draft();
1434 draft.constraints.max_action_risk_bp = Some(10_000);
1435 draft.constraints.approval_threshold_bp = Some(5_000);
1436 let cred = h.issue(draft);
1437 let actor = cred.subject_did.clone();
1438 let mut action = baseline_action(actor);
1439 action.estimated_risk_bp = Some(5_000);
1440 let mut request = baseline_request(cred, ts(1_500_000));
1441 request.action = Some(action);
1442 let result = validate_avc(&request, &h.registry).unwrap();
1443 assert_eq!(result.decision, AvcDecision::HumanApprovalRequired);
1444 assert_eq!(
1445 result.reason_codes,
1446 vec![AvcReasonCode::HumanApprovalMissing]
1447 );
1448 }
1449
1450 #[test]
1451 fn denies_risk_exceeded() {
1452 let h = Harness::new();
1453 let mut draft = baseline_draft();
1454 draft.constraints.max_action_risk_bp = Some(1_000);
1455 let cred = h.issue(draft);
1456 let actor = cred.subject_did.clone();
1457 let mut action = baseline_action(actor);
1458 action.estimated_risk_bp = Some(5_000);
1459 let mut request = baseline_request(cred, ts(1_500_000));
1460 request.action = Some(action);
1461 let result = validate_avc(&request, &h.registry).unwrap();
1462 assert!(result.reason_codes.contains(&AvcReasonCode::RiskExceeded));
1463 }
1464
1465 #[test]
1466 fn risk_above_threshold_returns_human_approval_required() {
1467 let h = Harness::new();
1468 let mut draft = baseline_draft();
1469 draft.constraints.max_action_risk_bp = Some(10_000);
1470 draft.constraints.approval_threshold_bp = Some(5_000);
1471 let cred = h.issue(draft);
1472 let actor = cred.subject_did.clone();
1473 let mut action = baseline_action(actor);
1474 action.estimated_risk_bp = Some(7_500);
1475 let mut request = baseline_request(cred, ts(1_500_000));
1476 request.action = Some(action);
1477 let result = validate_avc(&request, &h.registry).unwrap();
1478 assert_eq!(result.decision, AvcDecision::HumanApprovalRequired);
1479 assert_eq!(
1480 result.reason_codes,
1481 vec![AvcReasonCode::HumanApprovalMissing]
1482 );
1483 }
1484
1485 #[test]
1486 fn risk_above_threshold_ignores_caller_approval_flag() {
1487 let h = Harness::new();
1488 let mut draft = baseline_draft();
1489 draft.constraints.max_action_risk_bp = Some(10_000);
1490 draft.constraints.approval_threshold_bp = Some(5_000);
1491 let cred = h.issue(draft);
1492 let actor = cred.subject_did.clone();
1493 let mut action = baseline_action(actor);
1494 action.estimated_risk_bp = Some(7_500);
1495 action.requires_human_approval = true;
1496 let mut request = baseline_request(cred, ts(1_500_000));
1497 request.action = Some(action);
1498 let result = validate_avc(&request, &h.registry).unwrap();
1499 assert_eq!(result.decision, AvcDecision::HumanApprovalRequired);
1500 assert_eq!(
1501 result.reason_codes,
1502 vec![AvcReasonCode::HumanApprovalMissing]
1503 );
1504 }
1505
1506 #[test]
1507 fn credential_human_approval_required_blocks_action_without_evidence() {
1508 let h = Harness::new();
1509 let mut draft = baseline_draft();
1510 draft.constraints.human_approval_required = true;
1511 let cred = h.issue(draft);
1512 let actor = cred.subject_did.clone();
1513 let action = baseline_action(actor);
1514 let mut request = baseline_request(cred, ts(1_500_000));
1515 request.action = Some(action);
1516 let result = validate_avc(&request, &h.registry).unwrap();
1517 assert_eq!(result.decision, AvcDecision::HumanApprovalRequired);
1518 assert_eq!(
1519 result.reason_codes,
1520 vec![AvcReasonCode::HumanApprovalMissing]
1521 );
1522 }
1523
1524 #[test]
1525 fn signed_human_approval_satisfies_credential_requirement() {
1526 let mut h = Harness::new();
1527 let approver_keypair = human_approver_keypair();
1528 let approver_did = did("human-approver");
1529 h.registry
1530 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1531 let mut draft = baseline_draft();
1532 draft.constraints.human_approval_required = true;
1533 let cred = h.issue(draft);
1534 let actor = cred.subject_did.clone();
1535 let mut action = baseline_action(actor);
1536 attach_signed_human_approval(
1537 &cred,
1538 &mut action,
1539 approver_did,
1540 ts(1_400_000),
1541 Some(ts(1_900_000)),
1542 &approver_keypair,
1543 );
1544 let mut request = baseline_request(cred, ts(1_500_000));
1545 request.action = Some(action);
1546 let result = validate_avc(&request, &h.registry).unwrap();
1547 assert_eq!(result.decision, AvcDecision::Allow);
1548 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1549 }
1550
1551 #[test]
1552 fn signed_human_approval_satisfies_risk_threshold() {
1553 let mut h = Harness::new();
1554 let approver_keypair = human_approver_keypair();
1555 let approver_did = did("human-approver");
1556 h.registry
1557 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1558 let mut draft = baseline_draft();
1559 draft.constraints.max_action_risk_bp = Some(10_000);
1560 draft.constraints.approval_threshold_bp = Some(5_000);
1561 let cred = h.issue(draft);
1562 let actor = cred.subject_did.clone();
1563 let mut action = baseline_action(actor);
1564 action.estimated_risk_bp = Some(7_500);
1565 attach_signed_human_approval(
1566 &cred,
1567 &mut action,
1568 approver_did,
1569 ts(1_400_000),
1570 None,
1571 &approver_keypair,
1572 );
1573 let mut request = baseline_request(cred, ts(1_500_000));
1574 request.action = Some(action);
1575 let result = validate_avc(&request, &h.registry).unwrap();
1576 assert_eq!(result.decision, AvcDecision::Allow);
1577 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1578 }
1579
1580 #[test]
1581 fn valid_optional_human_approval_evidence_allows_unrequired_action() {
1582 let mut h = Harness::new();
1583 let approver_keypair = human_approver_keypair();
1584 let approver_did = did("human-approver");
1585 h.registry
1586 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1587 let cred = h.issue(baseline_draft());
1588 let actor = cred.subject_did.clone();
1589 let mut action = baseline_action(actor);
1590 attach_signed_human_approval(
1591 &cred,
1592 &mut action,
1593 approver_did,
1594 ts(1_400_000),
1595 Some(ts(1_900_000)),
1596 &approver_keypair,
1597 );
1598 let mut request = baseline_request(cred, ts(1_500_000));
1599 request.action = Some(action);
1600 let result = validate_avc(&request, &h.registry).unwrap();
1601 assert_eq!(result.decision, AvcDecision::Allow);
1602 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1603 }
1604
1605 #[test]
1606 fn human_approval_from_untrusted_approver_is_invalid() {
1607 let h = Harness::new();
1608 let approver_keypair = human_approver_keypair();
1609 let approver_did = did("human-approver");
1610 let mut draft = baseline_draft();
1611 draft.constraints.human_approval_required = true;
1612 let cred = h.issue(draft);
1613 let actor = cred.subject_did.clone();
1614 let mut action = baseline_action(actor);
1615 attach_signed_human_approval(
1616 &cred,
1617 &mut action,
1618 approver_did,
1619 ts(1_400_000),
1620 Some(ts(1_900_000)),
1621 &approver_keypair,
1622 );
1623 let mut request = baseline_request(cred, ts(1_500_000));
1624 request.action = Some(action);
1625 let result = validate_avc(&request, &h.registry).unwrap();
1626 assert_eq!(result.decision, AvcDecision::Deny);
1627 assert_eq!(
1628 result.reason_codes,
1629 vec![AvcReasonCode::HumanApprovalInvalid]
1630 );
1631 }
1632
1633 #[test]
1634 fn issuer_public_key_alone_does_not_authorize_human_approval() {
1635 let h = Harness::new();
1636 let issuer_keypair = issuer_keypair();
1637 let mut draft = baseline_draft();
1638 draft.constraints.human_approval_required = true;
1639 let cred = h.issue(draft);
1640 let actor = cred.subject_did.clone();
1641 let mut action = baseline_action(actor);
1642 attach_signed_human_approval(
1643 &cred,
1644 &mut action,
1645 did("issuer"),
1646 ts(1_400_000),
1647 Some(ts(1_900_000)),
1648 &issuer_keypair,
1649 );
1650 let mut request = baseline_request(cred, ts(1_500_000));
1651 request.action = Some(action);
1652 let result = validate_avc(&request, &h.registry).unwrap();
1653 assert_eq!(result.decision, AvcDecision::Deny);
1654 assert_eq!(
1655 result.reason_codes,
1656 vec![AvcReasonCode::HumanApprovalInvalid]
1657 );
1658 }
1659
1660 #[test]
1661 fn optional_human_approval_evidence_must_still_verify() {
1662 let h = Harness::new();
1663 let approver_keypair = human_approver_keypair();
1664 let cred = h.issue(baseline_draft());
1665 let actor = cred.subject_did.clone();
1666 let mut action = baseline_action(actor);
1667 attach_signed_human_approval(
1668 &cred,
1669 &mut action,
1670 did("human-approver"),
1671 ts(1_400_000),
1672 Some(ts(1_900_000)),
1673 &approver_keypair,
1674 );
1675 let mut request = baseline_request(cred, ts(1_500_000));
1676 request.action = Some(action);
1677 let result = validate_avc(&request, &h.registry).unwrap();
1678 assert_eq!(result.decision, AvcDecision::Deny);
1679 assert_eq!(
1680 result.reason_codes,
1681 vec![AvcReasonCode::HumanApprovalInvalid]
1682 );
1683 }
1684
1685 #[test]
1686 fn human_approval_signature_binds_action_fields() {
1687 let mut h = Harness::new();
1688 let approver_keypair = human_approver_keypair();
1689 let approver_did = did("human-approver");
1690 h.registry
1691 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1692 let mut draft = baseline_draft();
1693 draft.constraints.max_action_risk_bp = Some(10_000);
1694 draft.constraints.approval_threshold_bp = Some(5_000);
1695 let cred = h.issue(draft);
1696 let actor = cred.subject_did.clone();
1697 let mut action = baseline_action(actor);
1698 action.estimated_risk_bp = Some(7_500);
1699 attach_signed_human_approval(
1700 &cred,
1701 &mut action,
1702 approver_did,
1703 ts(1_400_000),
1704 None,
1705 &approver_keypair,
1706 );
1707 action.estimated_risk_bp = Some(7_501);
1708 let mut request = baseline_request(cred, ts(1_500_000));
1709 request.action = Some(action);
1710 let result = validate_avc(&request, &h.registry).unwrap();
1711 assert_eq!(result.decision, AvcDecision::Deny);
1712 assert_eq!(
1713 result.reason_codes,
1714 vec![AvcReasonCode::HumanApprovalInvalid]
1715 );
1716 }
1717
1718 #[test]
1719 fn expired_human_approval_is_rejected() {
1720 let mut h = Harness::new();
1721 let approver_keypair = human_approver_keypair();
1722 let approver_did = did("human-approver");
1723 h.registry
1724 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1725 let mut draft = baseline_draft();
1726 draft.constraints.human_approval_required = true;
1727 let cred = h.issue(draft);
1728 let actor = cred.subject_did.clone();
1729 let mut action = baseline_action(actor);
1730 attach_signed_human_approval(
1731 &cred,
1732 &mut action,
1733 approver_did,
1734 ts(1_300_000),
1735 Some(ts(1_400_000)),
1736 &approver_keypair,
1737 );
1738 let mut request = baseline_request(cred, ts(1_500_000));
1739 request.action = Some(action);
1740 let result = validate_avc(&request, &h.registry).unwrap();
1741 assert_eq!(result.decision, AvcDecision::Deny);
1742 assert_eq!(
1743 result.reason_codes,
1744 vec![AvcReasonCode::HumanApprovalExpired]
1745 );
1746 }
1747
1748 #[test]
1749 fn human_approval_with_empty_signature_is_invalid() {
1750 let mut h = Harness::new();
1751 let approver_keypair = human_approver_keypair();
1752 let approver_did = did("human-approver");
1753 h.registry
1754 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1755 let mut draft = baseline_draft();
1756 draft.constraints.human_approval_required = true;
1757 let cred = h.issue(draft);
1758 let actor = cred.subject_did.clone();
1759 let mut action = baseline_action(actor);
1760 action.human_approval = Some(AvcHumanApproval {
1761 approver_did,
1762 approved_at: ts(1_400_000),
1763 expires_at: Some(ts(1_900_000)),
1764 signature: Signature::empty(),
1765 });
1766 let mut request = baseline_request(cred, ts(1_500_000));
1767 request.action = Some(action);
1768 let result = validate_avc(&request, &h.registry).unwrap();
1769 assert_eq!(result.decision, AvcDecision::Deny);
1770 assert_eq!(
1771 result.reason_codes,
1772 vec![AvcReasonCode::HumanApprovalInvalid]
1773 );
1774 }
1775
1776 #[test]
1777 fn human_approval_expiring_at_approval_time_is_invalid() {
1778 let mut h = Harness::new();
1779 let approver_keypair = human_approver_keypair();
1780 let approver_did = did("human-approver");
1781 h.registry
1782 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1783 let mut draft = baseline_draft();
1784 draft.constraints.human_approval_required = true;
1785 let cred = h.issue(draft);
1786 let actor = cred.subject_did.clone();
1787 let mut action = baseline_action(actor);
1788 attach_signed_human_approval(
1789 &cred,
1790 &mut action,
1791 approver_did,
1792 ts(1_400_000),
1793 Some(ts(1_400_000)),
1794 &approver_keypair,
1795 );
1796 let mut request = baseline_request(cred, ts(1_500_000));
1797 request.action = Some(action);
1798 let result = validate_avc(&request, &h.registry).unwrap();
1799 assert_eq!(result.decision, AvcDecision::Deny);
1800 assert_eq!(
1801 result.reason_codes,
1802 vec![AvcReasonCode::HumanApprovalInvalid]
1803 );
1804 }
1805
1806 #[test]
1807 fn human_approval_expiring_at_now_is_expired() {
1808 let mut h = Harness::new();
1809 let approver_keypair = human_approver_keypair();
1810 let approver_did = did("human-approver");
1811 h.registry
1812 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1813 let mut draft = baseline_draft();
1814 draft.constraints.human_approval_required = true;
1815 let cred = h.issue(draft);
1816 let actor = cred.subject_did.clone();
1817 let mut action = baseline_action(actor);
1818 attach_signed_human_approval(
1819 &cred,
1820 &mut action,
1821 approver_did,
1822 ts(1_400_000),
1823 Some(ts(1_500_000)),
1824 &approver_keypair,
1825 );
1826 let mut request = baseline_request(cred, ts(1_500_000));
1827 request.action = Some(action);
1828 let result = validate_avc(&request, &h.registry).unwrap();
1829 assert_eq!(result.decision, AvcDecision::Deny);
1830 assert_eq!(
1831 result.reason_codes,
1832 vec![AvcReasonCode::HumanApprovalExpired]
1833 );
1834 }
1835
1836 #[test]
1837 fn human_approval_with_future_approval_time_is_invalid() {
1838 let mut h = Harness::new();
1839 let approver_keypair = human_approver_keypair();
1840 let approver_did = did("human-approver");
1841 h.registry
1842 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1843 let mut draft = baseline_draft();
1844 draft.constraints.human_approval_required = true;
1845 let cred = h.issue(draft);
1846 let actor = cred.subject_did.clone();
1847 let mut action = baseline_action(actor);
1848 attach_signed_human_approval(
1849 &cred,
1850 &mut action,
1851 approver_did,
1852 ts(1_600_000),
1853 Some(ts(1_900_000)),
1854 &approver_keypair,
1855 );
1856 let mut request = baseline_request(cred, ts(1_500_000));
1857 request.action = Some(action);
1858 let result = validate_avc(&request, &h.registry).unwrap();
1859 assert_eq!(result.decision, AvcDecision::Deny);
1860 assert_eq!(
1861 result.reason_codes,
1862 vec![AvcReasonCode::HumanApprovalInvalid]
1863 );
1864 }
1865
1866 #[test]
1867 fn human_approval_expiring_before_approval_time_is_invalid() {
1868 let mut h = Harness::new();
1869 let approver_keypair = human_approver_keypair();
1870 let approver_did = did("human-approver");
1871 h.registry
1872 .put_human_approval_key(approver_did.clone(), approver_keypair.public);
1873 let mut draft = baseline_draft();
1874 draft.constraints.human_approval_required = true;
1875 let cred = h.issue(draft);
1876 let actor = cred.subject_did.clone();
1877 let mut action = baseline_action(actor);
1878 attach_signed_human_approval(
1879 &cred,
1880 &mut action,
1881 approver_did,
1882 ts(1_400_000),
1883 Some(ts(1_399_999)),
1884 &approver_keypair,
1885 );
1886 let mut request = baseline_request(cred, ts(1_500_000));
1887 request.action = Some(action);
1888 let result = validate_avc(&request, &h.registry).unwrap();
1889 assert_eq!(result.decision, AvcDecision::Deny);
1890 assert_eq!(
1891 result.reason_codes,
1892 vec![AvcReasonCode::HumanApprovalInvalid]
1893 );
1894 }
1895
1896 #[test]
1897 fn risk_below_approval_threshold_allows_without_human_approval() {
1898 let h = Harness::new();
1899 let mut draft = baseline_draft();
1900 draft.constraints.max_action_risk_bp = Some(10_000);
1901 draft.constraints.approval_threshold_bp = Some(5_000);
1902 let cred = h.issue(draft);
1903 let actor = cred.subject_did.clone();
1904 let mut action = baseline_action(actor);
1905 action.estimated_risk_bp = Some(4_999);
1906 let mut request = baseline_request(cred, ts(1_500_000));
1907 request.action = Some(action);
1908 let result = validate_avc(&request, &h.registry).unwrap();
1909 assert_eq!(result.decision, AvcDecision::Allow);
1910 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1911 }
1912
1913 #[test]
1914 fn risk_threshold_without_estimate_allows_without_human_approval() {
1915 let h = Harness::new();
1916 let mut draft = baseline_draft();
1917 draft.constraints.max_action_risk_bp = Some(10_000);
1918 draft.constraints.approval_threshold_bp = Some(5_000);
1919 let cred = h.issue(draft);
1920 let actor = cred.subject_did.clone();
1921 let action = baseline_action(actor);
1922 let mut request = baseline_request(cred, ts(1_500_000));
1923 request.action = Some(action);
1924 let result = validate_avc(&request, &h.registry).unwrap();
1925 assert_eq!(result.decision, AvcDecision::Allow);
1926 assert_eq!(result.reason_codes, vec![AvcReasonCode::Valid]);
1927 }
1928
1929 #[test]
1930 fn denies_forbidden_action_name() {
1931 let h = Harness::new();
1932 let mut draft = baseline_draft();
1933 draft.constraints.forbidden_actions = vec!["payment.execute".into()];
1934 let cred = h.issue(draft);
1935 let actor = cred.subject_did.clone();
1936 let mut action = baseline_action(actor);
1937 action.action_name = Some("payment.execute".into());
1938 let mut request = baseline_request(cred, ts(1_500_000));
1939 request.action = Some(action);
1940 let result = validate_avc(&request, &h.registry).unwrap();
1941 assert!(
1942 result
1943 .reason_codes
1944 .contains(&AvcReasonCode::ForbiddenAction)
1945 );
1946 }
1947
1948 #[test]
1949 fn reason_codes_are_sorted_and_deduped() {
1950 let h = Harness::new();
1951 let mut draft = baseline_draft();
1953 draft.principal_did = did("principal"); draft.authority_scope.tools = vec![];
1956 let cred = h.issue(draft);
1957 let actor = cred.subject_did.clone();
1958 let mut action = baseline_action(actor);
1959 action.tool = Some("forbidden".into());
1960 action.requested_permission = Permission::Govern; let mut request = baseline_request(cred, ts(3_000_000)); request.action = Some(action);
1963 let result = validate_avc(&request, &h.registry).unwrap();
1964 assert_eq!(result.decision, AvcDecision::Deny);
1965
1966 let mut sorted = result.reason_codes.clone();
1967 sorted.sort();
1968 assert_eq!(sorted, result.reason_codes, "reason codes must be sorted");
1969
1970 let mut deduped = result.reason_codes.clone();
1971 deduped.dedup();
1972 assert_eq!(deduped, result.reason_codes, "reason codes must be deduped");
1973 }
1974
1975 #[test]
1976 fn validation_does_not_consult_payment_state() {
1977 let h = Harness::new();
1979 let cred = h.issue(baseline_draft());
1980 let r1 = validate_avc(&baseline_request(cred.clone(), ts(1_500_000)), &h.registry).unwrap();
1981 let r2 = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
1982 assert_eq!(r1, r2);
1983 }
1984
1985 #[test]
1986 fn validation_request_round_trip_serializes() {
1987 let h = Harness::new();
1988 let cred = h.issue(baseline_draft());
1989 let request = baseline_request(cred, ts(1_500_000));
1990 let mut buf = Vec::new();
1991 ciborium::ser::into_writer(&request, &mut buf).unwrap();
1992 let decoded: AvcValidationRequest = ciborium::de::from_reader(buf.as_slice()).unwrap();
1993 assert_eq!(decoded, request);
1994 }
1995
1996 #[test]
1997 fn unsupported_subject_with_unknown_kind_still_allows() {
1998 let h = Harness::new();
1999 let mut draft = baseline_draft();
2000 draft.subject_kind = AvcSubjectKind::Unknown;
2001 let cred = h.issue(draft);
2002 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
2003 assert_eq!(result.decision, AvcDecision::Allow);
2004 }
2005
2006 #[test]
2007 fn validation_request_now_inside_window_is_inclusive() {
2008 let h = Harness::new();
2009 let mut draft = baseline_draft();
2010 draft.constraints.allowed_time_window = Some(TimeWindow {
2011 not_before: ts(1_500_000),
2012 not_after: ts(1_500_000_000),
2013 });
2014 let cred = h.issue(draft);
2015 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
2016 assert_eq!(result.decision, AvcDecision::Allow);
2017 }
2018
2019 #[test]
2020 fn confirms_schema_constant_is_one() {
2021 assert_eq!(AVC_SCHEMA_VERSION, 1);
2022 }
2023
2024 #[test]
2025 fn validation_with_only_constraints_passes_when_no_action() {
2026 let h = Harness::new();
2027 let mut draft = baseline_draft();
2028 draft.constraints = AvcConstraints {
2029 max_budget_minor_units: Some(1_000),
2030 currency_code: Some("USD".into()),
2031 max_action_risk_bp: Some(2_000),
2032 human_approval_required: false,
2033 approval_threshold_bp: Some(5_000),
2034 max_delegation_depth: 1,
2035 allowed_time_window: None,
2036 forbidden_actions: vec!["bad".into()],
2037 emergency_stop_refs: vec!["stop".into()],
2038 };
2039 let cred = h.issue(draft);
2040 let result = validate_avc(&baseline_request(cred, ts(1_500_000)), &h.registry).unwrap();
2041 assert_eq!(result.decision, AvcDecision::Allow);
2042 }
2043}