1use crate::bindings::golem::permissions::{
59 derive as raw_derive, inspect as raw_inspect, kernel_introspection as raw_kernel,
60 revoke as raw_revoke, types as raw, wallet as raw_wallet,
61};
62use crate::schema::wit::GuestPermissionCardHandle;
63use crate::schema::wit::direct::{
64 WireError, WirePreflight, WireReader, WireSchemaBuilder, WireWriter,
65};
66use crate::schema::wit::wire;
67use crate::{AgentId, FromSchema, FromWire, IntoSchema, IntoWire, Uuid, WireSchema};
68use std::collections::HashMap;
69use std::sync::Arc;
70#[cfg(feature = "export_golem_agentic")]
71use std::sync::Weak;
72use std::time::Duration;
73
74#[cfg(feature = "export_golem_agentic")]
75use crate::schema::{
76 FromSchemaError, PermissionCardSpec, SchemaBuilder, SchemaType, SchemaValue, TypeId,
77};
78
79pub struct PermissionCard {
104 handle: GuestPermissionCardHandle,
105}
106
107#[derive(Debug)]
109pub enum PersistentCard {
110 Concrete(PermissionCard),
111 Template(PolymorphicPermissionCard),
112}
113
114impl PersistentCard {
115 fn from_raw(raw: raw::PersistentCard) -> Self {
116 match raw {
117 raw::PersistentCard::Concrete(card) => Self::Concrete(PermissionCard::from_raw(card)),
118 raw::PersistentCard::Template(card) => {
119 Self::Template(PolymorphicPermissionCard::from_raw(card))
120 }
121 }
122 }
123
124 pub fn id(&self) -> CardId {
126 match self {
127 Self::Concrete(card) => card.id(),
128 Self::Template(card) => card.id(),
129 }
130 }
131
132 pub fn parents(&self) -> Vec<CardId> {
134 match self {
135 Self::Concrete(card) => card.parents(),
136 Self::Template(card) => card.parents(),
137 }
138 }
139
140 pub fn expires_at(&self) -> Option<Timestamp> {
142 match self {
143 Self::Concrete(card) => card.expires_at(),
144 Self::Template(card) => card.expires_at(),
145 }
146 }
147
148 pub fn inspect(&self) -> Result<CardView, PermissionError> {
150 match self {
151 Self::Concrete(card) => card.inspect(),
152 Self::Template(card) => card.inspect(),
153 }
154 }
155
156 pub fn derive(
158 &self,
159 bounds: &Bounds,
160 lifespan: Option<&Lifespan>,
161 ) -> Result<Self, PermissionError> {
162 match self {
163 Self::Concrete(card) => card.derive(bounds, lifespan),
164 Self::Template(card) => card.derive(bounds, lifespan),
165 }
166 }
167
168 pub fn revoke(self) -> Result<u32, PermissionError> {
170 match self {
171 Self::Concrete(card) => card.revoke(),
172 Self::Template(card) => card.revoke(),
173 }
174 }
175
176 pub fn install(self, target: &Holder) -> Result<(), PermissionError> {
178 match self {
179 Self::Concrete(card) => card.install(target),
180 Self::Template(card) => card.install(target),
181 }
182 }
183}
184
185const CARD_MOVED: &str = "permission card has already been moved (revoked, installed, or \
186 transferred to another agent) and can no longer be used";
187
188impl PermissionCard {
189 fn from_raw(raw: wire::PermissionCard) -> Self {
190 Self {
191 handle: GuestPermissionCardHandle::new(raw),
192 }
193 }
194
195 fn with_raw<R>(&self, f: impl FnOnce(&wire::PermissionCard) -> R) -> R {
196 with_raw_card(&self.handle, f)
197 }
198
199 pub fn id(&self) -> CardId {
206 CardId::from_raw(self.with_raw(raw::id))
207 }
208
209 pub fn parents(&self) -> Vec<CardId> {
216 card_parents(&self.handle)
217 }
218
219 pub fn expires_at(&self) -> Option<Timestamp> {
226 self.with_raw(raw::expires_at).map(Timestamp::from)
227 }
228
229 pub fn inspect(&self) -> Result<CardView, PermissionError> {
235 inspect_card(&self.handle)
236 }
237
238 pub fn derive(
249 &self,
250 bounds: &Bounds,
251 lifespan: Option<&Lifespan>,
252 ) -> Result<PersistentCard, PermissionError> {
253 derive_child(&self.handle, bounds, lifespan)
254 }
255
256 pub fn revoke(self) -> Result<u32, PermissionError> {
265 revoke_card(&self.handle)
266 }
267
268 pub fn install(self, target: &Holder) -> Result<(), PermissionError> {
280 install_card(&self.handle, target)
281 }
282}
283
284impl std::fmt::Debug for PermissionCard {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "PermissionCard(<{}>)", handle_state(&self.handle))
287 }
288}
289
290pub struct PolymorphicPermissionCard {
313 handle: GuestPermissionCardHandle,
314}
315
316impl PolymorphicPermissionCard {
317 fn from_raw(raw: wire::PermissionCard) -> Self {
318 Self {
319 handle: GuestPermissionCardHandle::new(raw),
320 }
321 }
322
323 fn with_raw<R>(&self, f: impl FnOnce(&wire::PermissionCard) -> R) -> R {
324 with_raw_card(&self.handle, f)
325 }
326
327 pub fn id(&self) -> CardId {
334 CardId::from_raw(self.with_raw(raw::id))
335 }
336
337 pub fn parents(&self) -> Vec<CardId> {
344 card_parents(&self.handle)
345 }
346
347 pub fn expires_at(&self) -> Option<Timestamp> {
354 self.with_raw(raw::expires_at).map(Timestamp::from)
355 }
356
357 pub fn inspect(&self) -> Result<CardView, PermissionError> {
364 inspect_card(&self.handle)
365 }
366
367 pub fn derive(
377 &self,
378 bounds: &Bounds,
379 lifespan: Option<&Lifespan>,
380 ) -> Result<PersistentCard, PermissionError> {
381 derive_child(&self.handle, bounds, lifespan)
382 }
383
384 pub fn revoke(self) -> Result<u32, PermissionError> {
393 revoke_card(&self.handle)
394 }
395
396 pub fn install(self, target: &Holder) -> Result<(), PermissionError> {
407 install_card(&self.handle, target)
408 }
409}
410
411impl std::fmt::Debug for PolymorphicPermissionCard {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 write!(
414 f,
415 "PolymorphicPermissionCard(<{}>)",
416 handle_state(&self.handle)
417 )
418 }
419}
420
421fn handle_state(handle: &GuestPermissionCardHandle) -> &'static str {
422 if handle.is_present() {
423 "present"
424 } else {
425 "moved"
426 }
427}
428
429fn with_raw_card<R>(
430 handle: &GuestPermissionCardHandle,
431 f: impl FnOnce(&wire::PermissionCard) -> R,
432) -> R {
433 handle
434 .with_handle(f)
435 .unwrap_or_else(|| panic!("{CARD_MOVED}"))
436}
437
438fn take_raw_card(handle: &GuestPermissionCardHandle) -> wire::PermissionCard {
439 handle.take().unwrap_or_else(|| panic!("{CARD_MOVED}"))
440}
441
442fn card_parents(handle: &GuestPermissionCardHandle) -> Vec<CardId> {
443 with_raw_card(handle, raw::parents)
444 .into_iter()
445 .map(CardId::from_raw)
446 .collect()
447}
448
449fn inspect_card(handle: &GuestPermissionCardHandle) -> Result<CardView, PermissionError> {
450 with_raw_card(handle, raw_inspect::inspect_card)
451 .map(CardView::from)
452 .map_err(PermissionError::from)
453}
454
455fn derive_child(
456 handle: &GuestPermissionCardHandle,
457 bounds: &Bounds,
458 lifespan: Option<&Lifespan>,
459) -> Result<PersistentCard, PermissionError> {
460 let bounds = RawBounds::from(bounds);
461 let lifespan = lifespan.map(Lifespan::to_raw);
462 with_raw_card(handle, |card| {
463 raw_derive::derive(
464 card,
465 &bounds.lower_positive,
466 &bounds.lower_negative,
467 &bounds.upper_positive,
468 &bounds.upper_negative,
469 lifespan.as_ref(),
470 )
471 })
472 .map(PersistentCard::from_raw)
473 .map_err(PermissionError::from)
474}
475
476fn revoke_card(handle: &GuestPermissionCardHandle) -> Result<u32, PermissionError> {
477 raw_revoke::revoke_card(take_raw_card(handle)).map_err(PermissionError::from)
478}
479
480fn install_card(
481 handle: &GuestPermissionCardHandle,
482 target: &Holder,
483) -> Result<(), PermissionError> {
484 let target = target.to_raw();
485 raw_wallet::install_card(take_raw_card(handle), &target).map_err(PermissionError::from)
486}
487
488#[derive(Debug)]
493pub enum WalletCard {
494 Concrete(PermissionCard),
496 Template(PolymorphicPermissionCard),
499 Scope(ScopeCard),
503}
504
505impl WalletCard {
506 fn from_raw(raw: raw::WalletCard) -> Self {
507 match raw {
508 raw::WalletCard::Concrete(card) => Self::Concrete(PermissionCard::from_raw(card)),
509 raw::WalletCard::Template(card) => {
510 Self::Template(PolymorphicPermissionCard::from_raw(card))
511 }
512 raw::WalletCard::Scope(card) => Self::Scope(ScopeCard {
513 raw: Arc::new(card),
514 }),
515 }
516 }
517
518 pub fn id(&self) -> CardId {
525 match self {
526 Self::Concrete(card) => card.id(),
527 Self::Template(card) => card.id(),
528 Self::Scope(card) => card.id(),
529 }
530 }
531}
532
533pub fn wallet() -> Vec<WalletCard> {
542 raw_wallet::self_wallet()
543 .into_iter()
544 .map(WalletCard::from_raw)
545 .collect()
546}
547
548pub fn wallet_version() -> WalletVersionToken {
551 raw_wallet::self_version().into()
552}
553
554pub fn derive_from_wallet(
566 bounds: &Bounds,
567 lifespan: Option<&Lifespan>,
568) -> Result<PersistentCard, PermissionError> {
569 let bounds = RawBounds::from(bounds);
570 let lifespan = lifespan.map(Lifespan::to_raw);
571 raw_derive::derive_from_wallet(
572 &bounds.lower_positive,
573 &bounds.lower_negative,
574 &bounds.upper_positive,
575 &bounds.upper_negative,
576 lifespan.as_ref(),
577 )
578 .map(PersistentCard::from_raw)
579 .map_err(PermissionError::from)
580}
581
582pub fn derive_scope(
595 parents: &[ScopeParent<'_>],
596 bounds: &Bounds,
597) -> Result<ScopeCard, PermissionError> {
598 let bounds = RawBounds::from(bounds);
599 with_raw_parents(parents, |parents| {
600 raw_derive::derive_scope(
601 parents,
602 &bounds.lower_positive,
603 &bounds.lower_negative,
604 &bounds.upper_positive,
605 &bounds.upper_negative,
606 )
607 })
608 .map(|raw| ScopeCard { raw: Arc::new(raw) })
609 .map_err(PermissionError::from)
610}
611
612#[derive(Clone, Copy, Debug)]
615pub enum ScopeParent<'a> {
616 Card(&'a PermissionCard),
617 Template(&'a PolymorphicPermissionCard),
618 Scope(&'a ScopeCard),
619}
620
621impl<'a> From<&'a PermissionCard> for ScopeParent<'a> {
622 fn from(card: &'a PermissionCard) -> Self {
623 Self::Card(card)
624 }
625}
626
627impl<'a> From<&'a PersistentCard> for ScopeParent<'a> {
628 fn from(card: &'a PersistentCard) -> Self {
629 match card {
630 PersistentCard::Concrete(card) => Self::Card(card),
631 PersistentCard::Template(card) => Self::Template(card),
632 }
633 }
634}
635
636impl<'a> From<&'a PolymorphicPermissionCard> for ScopeParent<'a> {
637 fn from(card: &'a PolymorphicPermissionCard) -> Self {
638 Self::Template(card)
639 }
640}
641
642impl<'a> From<&'a WalletCard> for ScopeParent<'a> {
643 fn from(card: &'a WalletCard) -> Self {
644 match card {
645 WalletCard::Concrete(card) => Self::Card(card),
646 WalletCard::Template(card) => Self::Template(card),
647 WalletCard::Scope(card) => Self::Scope(card),
648 }
649 }
650}
651
652impl<'a> From<&'a ScopeCard> for ScopeParent<'a> {
653 fn from(card: &'a ScopeCard) -> Self {
654 Self::Scope(card)
655 }
656}
657
658pub struct ScopeCard {
675 raw: Arc<wire::PermissionCard>,
676}
677
678impl ScopeCard {
679 pub fn id(&self) -> CardId {
681 CardId::from_raw(raw::id(&self.raw))
682 }
683
684 pub fn parents(&self) -> Vec<CardId> {
686 raw::parents(&self.raw)
687 .into_iter()
688 .map(CardId::from_raw)
689 .collect()
690 }
691
692 pub fn expires_at(&self) -> Option<Timestamp> {
695 raw::expires_at(&self.raw).map(Timestamp::from)
696 }
697
698 pub fn inspect(&self) -> Result<CardView, PermissionError> {
700 raw_inspect::inspect_card(&self.raw)
701 .map(CardView::from)
702 .map_err(PermissionError::from)
703 }
704
705 #[cfg(feature = "export_golem_agentic")]
706 pub(crate) fn raw(&self) -> &wire::PermissionCard {
707 &self.raw
708 }
709}
710
711impl std::fmt::Debug for ScopeCard {
712 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
713 f.write_str("ScopeCard(..)")
714 }
715}
716
717pub fn list_modules() -> Vec<KernelModule> {
719 raw_kernel::list_modules()
720 .into_iter()
721 .map(KernelModule::from)
722 .collect()
723}
724
725pub fn validate_grant(grant: &PatternGrant) -> Result<(), PermissionError> {
727 raw_kernel::validate_grant(&grant.to_raw()).map_err(PermissionError::from)
728}
729
730#[cfg(feature = "export_golem_agentic")]
731const DEFAULT_SCOPE_CARD_DROPPED: &str = "the client's default scope card was dropped; clear \
732 the default before dropping the scope card";
733
734#[doc(hidden)]
740#[cfg(feature = "export_golem_agentic")]
741#[derive(Clone, Default)]
742pub struct __DefaultScopeCard(Option<Weak<wire::PermissionCard>>);
743
744#[cfg(feature = "export_golem_agentic")]
745impl __DefaultScopeCard {
746 pub fn set(&mut self, card: &ScopeCard) {
747 self.0 = Some(Arc::downgrade(&card.raw));
748 }
749
750 pub fn clear(&mut self) {
751 self.0 = None;
752 }
753
754 pub fn resolve(&self, scope_card: Option<&ScopeCard>) -> Option<ScopeCard> {
764 match (scope_card, &self.0) {
765 (Some(card), _) => Some(ScopeCard {
766 raw: card.raw.clone(),
767 }),
768 (None, Some(default)) => Some(ScopeCard {
769 raw: default
770 .upgrade()
771 .unwrap_or_else(|| panic!("{DEFAULT_SCOPE_CARD_DROPPED}")),
772 }),
773 (None, None) => None,
774 }
775 }
776}
777
778#[cfg(feature = "export_golem_agentic")]
779impl std::fmt::Debug for __DefaultScopeCard {
780 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781 let state = if self.0.is_some() { "set" } else { "unset" };
782 write!(f, "DefaultScopeCard(<{state}>)")
783 }
784}
785
786#[doc(hidden)]
791#[cfg(feature = "export_golem_agentic")]
792pub fn __async_invoke_and_await(
793 rpc: &crate::golem_agentic::golem::agent::host::WasmRpc,
794 method_name: &str,
795 input: wire::SchemaValueTree,
796 scope_card: Option<&ScopeCard>,
797) -> crate::golem_agentic::golem::agent::host::AsyncInvocationWithMetadata {
798 rpc.async_invoke_and_await(method_name, input, scope_card.map(ScopeCard::raw))
799}
800
801fn with_raw_parents<R>(
806 parents: &[ScopeParent<'_>],
807 f: impl FnOnce(&[&wire::PermissionCard]) -> R,
808) -> R {
809 type WithRawCards<'a, R> = Box<dyn FnOnce(&[&wire::PermissionCard]) -> R + 'a>;
810
811 fn go<R>(
812 parents: &[ScopeParent<'_>],
813 first_index: &HashMap<*const (), usize>,
814 acc: &[&wire::PermissionCard],
815 f: WithRawCards<'_, R>,
816 ) -> R {
817 let Some((parent, rest)) = parents.split_first() else {
818 return f(acc);
819 };
820 let handle = match parent {
821 ScopeParent::Scope(scope) => {
822 let mut next = acc.to_vec();
823 next.push(scope.raw.as_ref());
824 return go(rest, first_index, &next, f);
825 }
826 ScopeParent::Card(card) => &card.handle,
827 ScopeParent::Template(card) => &card.handle,
828 };
829 let index = first_index[&handle.cell_id()];
830 if index < acc.len() {
831 let mut next = acc.to_vec();
832 next.push(acc[index]);
833 return go(rest, first_index, &next, f);
834 }
835 with_raw_card(handle, |raw| {
836 let mut next = acc.to_vec();
837 next.push(raw);
838 go(rest, first_index, &next, f)
839 })
840 }
841
842 let mut first_index = HashMap::new();
843 for (index, parent) in parents.iter().enumerate() {
844 let handle = match parent {
845 ScopeParent::Card(card) => &card.handle,
846 ScopeParent::Template(card) => &card.handle,
847 ScopeParent::Scope(_) => continue,
848 };
849 first_index.entry(handle.cell_id()).or_insert(index);
850 }
851 go(parents, &first_index, &[], Box::new(f))
852}
853
854#[derive(Clone, PartialEq, Eq, Hash)]
862pub struct CardId {
863 uuid: Uuid,
864}
865
866impl CardId {
867 fn from_raw(raw: wire::CardId) -> Self {
868 Self {
869 uuid: raw.uuid.into(),
870 }
871 }
872}
873
874const REDACTED_CARD_ID: &str = "<redacted card id>";
875
876impl std::fmt::Debug for CardId {
877 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
878 write!(f, "CardId({REDACTED_CARD_ID})")
879 }
880}
881
882impl std::fmt::Display for CardId {
883 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
884 f.write_str(REDACTED_CARD_ID)
885 }
886}
887
888#[cfg(feature = "export_golem_agentic")]
889impl crate::serde::Serialize for CardId {
890 fn serialize<S: crate::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
891 serializer.serialize_str(REDACTED_CARD_ID)
892 }
893}
894
895#[derive(
897 Clone, Debug, PartialEq, Eq, Hash, IntoSchema, FromSchema, IntoWire, FromWire, WireSchema,
898)]
899#[schema(named = "golem.permissions.PatternGrant")]
900pub struct PatternGrant {
901 pub class: String,
902 pub owner: String,
903 pub recipient: String,
904 pub verb: String,
905 pub resource_id: String,
906}
907
908impl PatternGrant {
909 pub fn new(
910 class: impl Into<String>,
911 owner: impl Into<String>,
912 recipient: impl Into<String>,
913 verb: impl Into<String>,
914 resource_id: impl Into<String>,
915 ) -> Self {
916 Self {
917 class: class.into(),
918 owner: owner.into(),
919 recipient: recipient.into(),
920 verb: verb.into(),
921 resource_id: resource_id.into(),
922 }
923 }
924
925 fn to_raw(&self) -> raw::PatternGrant {
926 raw::PatternGrant {
927 class: self.class.clone(),
928 owner: self.owner.clone(),
929 recipient: self.recipient.clone(),
930 verb: self.verb.clone(),
931 resource_id: self.resource_id.clone(),
932 }
933 }
934}
935
936impl From<raw::PatternGrant> for PatternGrant {
937 fn from(value: raw::PatternGrant) -> Self {
938 Self {
939 class: value.class,
940 owner: value.owner,
941 recipient: value.recipient,
942 verb: value.verb,
943 resource_id: value.resource_id,
944 }
945 }
946}
947
948#[derive(
950 Clone, Debug, Default, PartialEq, Eq, IntoSchema, FromSchema, IntoWire, FromWire, WireSchema,
951)]
952#[schema(named = "golem.permissions.CardView")]
953pub struct CardView {
954 pub lower_positive: Vec<PatternGrant>,
955 pub lower_negative: Vec<PatternGrant>,
956 pub upper_positive: Vec<PatternGrant>,
957 pub upper_negative: Vec<PatternGrant>,
958}
959
960impl From<raw::CardView> for CardView {
961 fn from(value: raw::CardView) -> Self {
962 let convert =
963 |grants: Vec<raw::PatternGrant>| grants.into_iter().map(PatternGrant::from).collect();
964 Self {
965 lower_positive: convert(value.lower_positive),
966 lower_negative: convert(value.lower_negative),
967 upper_positive: convert(value.upper_positive),
968 upper_negative: convert(value.upper_negative),
969 }
970 }
971}
972
973#[derive(Clone, Debug, Default, PartialEq, Eq)]
980pub struct Bounds {
981 pub lower_positive: Vec<PatternGrant>,
982 pub lower_negative: Vec<PatternGrant>,
983 pub upper_positive: Vec<PatternGrant>,
984 pub upper_negative: Vec<PatternGrant>,
985}
986
987struct RawBounds {
988 lower_positive: Vec<raw::PatternGrant>,
989 lower_negative: Vec<raw::PatternGrant>,
990 upper_positive: Vec<raw::PatternGrant>,
991 upper_negative: Vec<raw::PatternGrant>,
992}
993
994impl From<&Bounds> for RawBounds {
995 fn from(value: &Bounds) -> Self {
996 let convert = |grants: &[PatternGrant]| grants.iter().map(PatternGrant::to_raw).collect();
997 Self {
998 lower_positive: convert(&value.lower_positive),
999 lower_negative: convert(&value.lower_negative),
1000 upper_positive: convert(&value.upper_positive),
1001 upper_negative: convert(&value.upper_negative),
1002 }
1003 }
1004}
1005
1006#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1009pub struct Lifespan {
1010 iso_8601: String,
1011}
1012
1013impl Lifespan {
1014 pub fn iso8601(value: impl Into<String>) -> Result<Self, LifespanError> {
1017 let iso_8601 = value.into();
1018 if !iso_8601.starts_with('P') {
1019 return Err(LifespanError(format!(
1020 "ISO-8601 duration must start with 'P': {iso_8601:?}"
1021 )));
1022 }
1023 crate::schema::canonical::duration::from_text(&iso_8601)
1024 .map_err(|error| LifespanError(format!("{iso_8601:?}: {error}")))?;
1025 Ok(Self { iso_8601 })
1026 }
1027
1028 pub fn as_iso8601(&self) -> &str {
1030 &self.iso_8601
1031 }
1032
1033 fn to_raw(&self) -> raw::Duration {
1034 raw::Duration {
1035 iso_8601: self.iso_8601.clone(),
1036 }
1037 }
1038}
1039
1040impl TryFrom<Duration> for Lifespan {
1041 type Error = LifespanError;
1042
1043 fn try_from(value: Duration) -> Result<Self, Self::Error> {
1046 if value.is_zero() {
1047 return Err(LifespanError("duration must be positive".to_string()));
1048 }
1049 let nanoseconds = i64::try_from(value.as_nanos()).map_err(|_| {
1050 LifespanError(format!("duration {value:?} exceeds the supported range"))
1051 })?;
1052 Ok(Self {
1053 iso_8601: crate::schema::canonical::duration::to_text(
1054 &crate::schema::DurationValuePayload { nanoseconds },
1055 ),
1056 })
1057 }
1058}
1059
1060#[derive(Clone, Debug, PartialEq, Eq)]
1062pub struct LifespanError(String);
1063
1064impl std::fmt::Display for LifespanError {
1065 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1066 write!(f, "invalid lifespan: {}", self.0)
1067 }
1068}
1069
1070impl std::error::Error for LifespanError {}
1071
1072#[derive(
1074 Clone,
1075 Copy,
1076 Debug,
1077 PartialEq,
1078 Eq,
1079 PartialOrd,
1080 Ord,
1081 Hash,
1082 IntoSchema,
1083 FromSchema,
1084 IntoWire,
1085 FromWire,
1086 WireSchema,
1087)]
1088#[schema(named = "golem.permissions.Timestamp")]
1089pub struct Timestamp {
1090 pub seconds: i64,
1091 pub nanoseconds: u32,
1092}
1093
1094impl From<raw::Timestamp> for Timestamp {
1095 fn from(value: raw::Timestamp) -> Self {
1096 Self {
1097 seconds: value.seconds,
1098 nanoseconds: value.nanoseconds,
1099 }
1100 }
1101}
1102
1103#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1105pub struct WalletVersionToken {
1106 pub wallet_id_hash: Vec<u8>,
1108 pub generation: u64,
1110}
1111
1112impl From<raw::WalletVersionToken> for WalletVersionToken {
1113 fn from(value: raw::WalletVersionToken) -> Self {
1114 Self {
1115 wallet_id_hash: value.wallet_id_hash,
1116 generation: value.generation,
1117 }
1118 }
1119}
1120
1121#[derive(Clone, Debug, PartialEq, Eq, IntoSchema, FromSchema, IntoWire, FromWire, WireSchema)]
1123#[schema(named = "golem.permissions.KernelModule")]
1124pub struct KernelModule {
1125 pub class_name: String,
1126 pub verbs: Vec<String>,
1127}
1128
1129impl From<raw_kernel::KernelModule> for KernelModule {
1130 fn from(value: raw_kernel::KernelModule) -> Self {
1131 Self {
1132 class_name: value.class_name,
1133 verbs: value.verbs,
1134 }
1135 }
1136}
1137
1138#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1142pub enum Holder {
1143 Account(Uuid),
1144 App(Uuid),
1145 Agent(AgentId),
1146}
1147
1148impl Holder {
1149 fn to_raw(&self) -> raw::Holder {
1150 match self {
1151 Self::Account(uuid) => raw::Holder::Account((*uuid).into()),
1152 Self::App(uuid) => raw::Holder::App((*uuid).into()),
1153 Self::Agent(agent_id) => {
1154 raw::Holder::Agent(crate::schema_agent_id_to_wire(agent_id.clone()))
1155 }
1156 }
1157 }
1158}
1159
1160#[derive(Clone, Debug, PartialEq, Eq, IntoSchema, FromSchema, IntoWire, FromWire, WireSchema)]
1164#[schema(named = "golem.permissions.PermissionError")]
1165pub enum PermissionError {
1166 NotPermitted(String),
1167 LowerBoundTooBroad(String),
1168 UpperBoundTooBroad(String),
1169 WalletMultiSourceRequired(String),
1170 CardRevoked(String),
1171 CardExpired(String),
1172 InstallRecipientMismatch(String),
1173 UnknownResourceClass(String),
1174 InvalidPattern(String),
1175 InvalidVerb(String),
1176 InvalidOwner(String),
1177 InvalidRecipient(String),
1178}
1179
1180impl PermissionError {
1181 pub fn message(&self) -> &str {
1183 match self {
1184 Self::NotPermitted(message)
1185 | Self::LowerBoundTooBroad(message)
1186 | Self::UpperBoundTooBroad(message)
1187 | Self::WalletMultiSourceRequired(message)
1188 | Self::CardRevoked(message)
1189 | Self::CardExpired(message)
1190 | Self::InstallRecipientMismatch(message)
1191 | Self::UnknownResourceClass(message)
1192 | Self::InvalidPattern(message)
1193 | Self::InvalidVerb(message)
1194 | Self::InvalidOwner(message)
1195 | Self::InvalidRecipient(message) => message,
1196 }
1197 }
1198
1199 fn kind(&self) -> &'static str {
1200 match self {
1201 Self::NotPermitted(_) => "not-permitted",
1202 Self::LowerBoundTooBroad(_) => "lower-bound-too-broad",
1203 Self::UpperBoundTooBroad(_) => "upper-bound-too-broad",
1204 Self::WalletMultiSourceRequired(_) => "wallet-multi-source-required",
1205 Self::CardRevoked(_) => "card-revoked",
1206 Self::CardExpired(_) => "card-expired",
1207 Self::InstallRecipientMismatch(_) => "install-recipient-mismatch",
1208 Self::UnknownResourceClass(_) => "unknown-resource-class",
1209 Self::InvalidPattern(_) => "invalid-pattern",
1210 Self::InvalidVerb(_) => "invalid-verb",
1211 Self::InvalidOwner(_) => "invalid-owner",
1212 Self::InvalidRecipient(_) => "invalid-recipient",
1213 }
1214 }
1215}
1216
1217impl From<raw::PermissionError> for PermissionError {
1218 fn from(value: raw::PermissionError) -> Self {
1219 match value {
1220 raw::PermissionError::NotPermitted(message) => Self::NotPermitted(message),
1221 raw::PermissionError::LowerBoundTooBroad(message) => Self::LowerBoundTooBroad(message),
1222 raw::PermissionError::UpperBoundTooBroad(message) => Self::UpperBoundTooBroad(message),
1223 raw::PermissionError::WalletMultiSourceRequired(message) => {
1224 Self::WalletMultiSourceRequired(message)
1225 }
1226 raw::PermissionError::CardRevoked(message) => Self::CardRevoked(message),
1227 raw::PermissionError::CardExpired(message) => Self::CardExpired(message),
1228 raw::PermissionError::InstallRecipientMismatch(message) => {
1229 Self::InstallRecipientMismatch(message)
1230 }
1231 raw::PermissionError::UnknownResourceClass(message) => {
1232 Self::UnknownResourceClass(message)
1233 }
1234 raw::PermissionError::InvalidPattern(message) => Self::InvalidPattern(message),
1235 raw::PermissionError::InvalidVerb(message) => Self::InvalidVerb(message),
1236 raw::PermissionError::InvalidOwner(message) => Self::InvalidOwner(message),
1237 raw::PermissionError::InvalidRecipient(message) => Self::InvalidRecipient(message),
1238 }
1239 }
1240}
1241
1242impl std::fmt::Display for PermissionError {
1243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1244 write!(f, "{}: {}", self.kind(), self.message())
1245 }
1246}
1247
1248impl std::error::Error for PermissionError {}
1249
1250macro_rules! card_codecs {
1254 ($card:ident, $type_id:literal, polymorphic: $polymorphic:literal) => {
1255 impl WireSchema for $card {
1256 fn wire_type_id() -> String {
1257 $type_id.to_string()
1258 }
1259
1260 fn append_schema(builder: &mut WireSchemaBuilder) -> i32 {
1261 builder.push(wire::SchemaTypeBody::PermissionCardType(
1262 wire::PermissionCardSpec {
1263 polymorphic: $polymorphic,
1264 },
1265 ))
1266 }
1267 }
1268
1269 impl FromWire for $card {
1270 fn read_wire(reader: &mut WireReader, index: i32) -> Result<Self, WireError> {
1274 GuestPermissionCardHandle::read_wire(reader, index)?
1275 .take()
1276 .map(Self::from_raw)
1277 .ok_or(WireError::ConsumedResource("permission-card"))
1278 }
1279 }
1280
1281 impl IntoWire for $card {
1282 fn preflight(&self, resources: &mut WirePreflight) -> Result<(), WireError> {
1283 self.handle.preflight(resources)
1284 }
1285
1286 fn write_wire(&self, writer: &mut WireWriter) -> Result<i32, WireError> {
1287 self.handle.write_wire(writer)
1288 }
1289 }
1290
1291 #[cfg(feature = "export_golem_agentic")]
1292 impl IntoSchema for $card {
1293 fn type_id() -> TypeId {
1294 TypeId::new($type_id)
1295 }
1296
1297 fn register_in(_builder: &mut SchemaBuilder) -> SchemaType {
1298 SchemaType::permission_card(PermissionCardSpec {
1299 polymorphic: $polymorphic,
1300 })
1301 }
1302
1303 fn to_value(&self) -> SchemaValue {
1308 SchemaValue::PermissionCard(self.handle.clone())
1309 }
1310 }
1311
1312 #[cfg(feature = "export_golem_agentic")]
1313 impl FromSchema for $card {
1314 fn from_value(value: &SchemaValue) -> Result<Self, FromSchemaError> {
1317 match value {
1318 SchemaValue::PermissionCard(handle) => {
1319 handle.take().map(Self::from_raw).ok_or_else(|| {
1320 FromSchemaError::custom(
1321 "permission card has already been moved out of this value",
1322 )
1323 })
1324 }
1325 other => Err(FromSchemaError::shape_mismatch(
1326 "permission-card",
1327 format!("{other:?}"),
1328 stringify!($card),
1329 )),
1330 }
1331 }
1332 }
1333
1334 #[cfg(feature = "export_golem_agentic")]
1335 impl crate::serde::Serialize for $card {
1336 fn serialize<S: crate::serde::Serializer>(
1337 &self,
1338 _serializer: S,
1339 ) -> Result<S::Ok, S::Error> {
1340 Err(crate::serde::ser::Error::custom(
1341 "permission cards cannot be serialized; pass them as agent method arguments or results",
1342 ))
1343 }
1344 }
1345
1346 #[cfg(feature = "export_golem_agentic")]
1347 impl<'de> crate::serde::Deserialize<'de> for $card {
1348 fn deserialize<D: crate::serde::Deserializer<'de>>(
1349 _deserializer: D,
1350 ) -> Result<Self, D::Error> {
1351 Err(crate::serde::de::Error::custom(
1352 "permission cards cannot be deserialized or forged from data",
1353 ))
1354 }
1355 }
1356 };
1357}
1358
1359card_codecs!(PermissionCard, "golem.core.PermissionCard", polymorphic: false);
1360card_codecs!(
1361 PolymorphicPermissionCard,
1362 "golem.core.PolymorphicPermissionCard",
1363 polymorphic: true
1364);
1365
1366#[cfg(feature = "export_golem_agentic")]
1367impl crate::serde::Serialize for ScopeCard {
1368 fn serialize<S: crate::serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
1369 Err(crate::serde::ser::Error::custom(
1370 "scope cards cannot be serialized; they are valid only during the invocation that derived them",
1371 ))
1372 }
1373}
1374
1375#[cfg(test)]
1376mod tests {
1377 use super::*;
1378 use crate::schema::wit::direct;
1379 use test_r::test;
1380
1381 fn fake_card(handle: u32) -> PermissionCard {
1382 PermissionCard::from_raw(unsafe { wire::PermissionCard::from_handle(handle) })
1383 }
1384
1385 fn forget(card: PermissionCard) -> Option<u32> {
1387 card.handle.take().map(|raw| raw.take_handle())
1388 }
1389
1390 fn fake_template(handle: u32) -> PolymorphicPermissionCard {
1391 PolymorphicPermissionCard::from_raw(unsafe { wire::PermissionCard::from_handle(handle) })
1392 }
1393
1394 fn forget_template(card: PolymorphicPermissionCard) -> Option<u32> {
1395 card.handle.take().map(|raw| raw.take_handle())
1396 }
1397
1398 fn fake_scope(handle: u32) -> ScopeCard {
1399 ScopeCard {
1400 raw: Arc::new(unsafe { wire::PermissionCard::from_handle(handle) }),
1401 }
1402 }
1403
1404 fn forget_scope(scope: ScopeCard) -> u32 {
1407 Arc::try_unwrap(scope.raw)
1408 .unwrap_or_else(|_| panic!("scope handle is still shared"))
1409 .take_handle()
1410 }
1411
1412 #[cfg(feature = "export_golem_agentic")]
1415 fn forget_scope_shared_handle(scope: ScopeCard) -> u32 {
1416 assert!(Arc::strong_count(&scope.raw) > 1);
1417 scope.raw.handle()
1418 }
1419
1420 fn panic_message(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
1421 let payload = std::panic::catch_unwind(f).expect_err("operation must panic");
1422 payload
1423 .downcast_ref::<String>()
1424 .cloned()
1425 .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
1426 .unwrap_or_default()
1427 }
1428
1429 #[test]
1430 fn permission_error_covers_every_wit_case() {
1431 let cases = [
1432 (
1433 raw::PermissionError::NotPermitted("a".into()),
1434 PermissionError::NotPermitted("a".into()),
1435 "not-permitted",
1436 ),
1437 (
1438 raw::PermissionError::LowerBoundTooBroad("b".into()),
1439 PermissionError::LowerBoundTooBroad("b".into()),
1440 "lower-bound-too-broad",
1441 ),
1442 (
1443 raw::PermissionError::UpperBoundTooBroad("c".into()),
1444 PermissionError::UpperBoundTooBroad("c".into()),
1445 "upper-bound-too-broad",
1446 ),
1447 (
1448 raw::PermissionError::WalletMultiSourceRequired("d".into()),
1449 PermissionError::WalletMultiSourceRequired("d".into()),
1450 "wallet-multi-source-required",
1451 ),
1452 (
1453 raw::PermissionError::CardRevoked("e".into()),
1454 PermissionError::CardRevoked("e".into()),
1455 "card-revoked",
1456 ),
1457 (
1458 raw::PermissionError::CardExpired("f".into()),
1459 PermissionError::CardExpired("f".into()),
1460 "card-expired",
1461 ),
1462 (
1463 raw::PermissionError::InstallRecipientMismatch("g".into()),
1464 PermissionError::InstallRecipientMismatch("g".into()),
1465 "install-recipient-mismatch",
1466 ),
1467 (
1468 raw::PermissionError::UnknownResourceClass("h".into()),
1469 PermissionError::UnknownResourceClass("h".into()),
1470 "unknown-resource-class",
1471 ),
1472 (
1473 raw::PermissionError::InvalidPattern("i".into()),
1474 PermissionError::InvalidPattern("i".into()),
1475 "invalid-pattern",
1476 ),
1477 (
1478 raw::PermissionError::InvalidVerb("j".into()),
1479 PermissionError::InvalidVerb("j".into()),
1480 "invalid-verb",
1481 ),
1482 (
1483 raw::PermissionError::InvalidOwner("k".into()),
1484 PermissionError::InvalidOwner("k".into()),
1485 "invalid-owner",
1486 ),
1487 (
1488 raw::PermissionError::InvalidRecipient("l".into()),
1489 PermissionError::InvalidRecipient("l".into()),
1490 "invalid-recipient",
1491 ),
1492 ];
1493 assert_eq!(cases.len(), 12);
1496 let mut kinds = std::collections::HashSet::new();
1497 for (raw_error, expected, kind) in cases {
1498 let message = expected.message().to_string();
1499 let converted = PermissionError::from(raw_error);
1500 assert_eq!(converted, expected);
1501 assert_eq!(converted.to_string(), format!("{kind}: {message}"));
1502 assert!(kinds.insert(kind));
1503 }
1504 }
1505
1506 #[test]
1507 fn lifespan_from_iso8601_accepts_only_days_and_time_units() {
1508 for value in ["PT1H", "P1DT2H", "PT30.5S", "P2D"] {
1509 assert_eq!(Lifespan::iso8601(value).unwrap().as_iso8601(), value);
1510 }
1511 for value in ["P1Y", "P1M", "P1W", "PT", "1h", "5s", "", "-PT1S"] {
1512 assert!(
1513 Lifespan::iso8601(value).is_err(),
1514 "{value} must be rejected"
1515 );
1516 }
1517 }
1518
1519 #[test]
1520 fn lifespan_from_duration_is_a_fixed_iso8601_duration() {
1521 let lifespan = |duration| Lifespan::try_from(duration).unwrap();
1522 assert!(Lifespan::try_from(Duration::ZERO).is_err());
1523 assert_eq!(
1524 lifespan(Duration::from_secs(90_061)).as_iso8601(),
1525 "P1DT1H1M1S"
1526 );
1527 assert_eq!(
1528 lifespan(Duration::from_millis(2_500)).as_iso8601(),
1529 "PT2.5S"
1530 );
1531 assert_eq!(
1532 lifespan(Duration::from_nanos(1)).as_iso8601(),
1533 "PT0.000000001S"
1534 );
1535 assert!(Lifespan::try_from(Duration::from_secs(u64::MAX)).is_err());
1536 }
1537
1538 #[test]
1539 fn moved_card_fails_loudly() {
1540 let card = fake_card(7);
1541 let alias = PermissionCard {
1542 handle: card.handle.clone(),
1543 };
1544 assert_eq!(forget(card), Some(7));
1545
1546 assert_eq!(format!("{alias:?}"), "PermissionCard(<moved>)");
1547 let message = panic_message(std::panic::AssertUnwindSafe(|| {
1548 let _ = alias.id();
1549 }));
1550 assert!(message.contains("already been moved"), "{message}");
1551 let message = panic_message(std::panic::AssertUnwindSafe(|| {
1552 let _ = alias.inspect();
1553 }));
1554 assert!(message.contains("already been moved"), "{message}");
1555 let message = panic_message(std::panic::AssertUnwindSafe(|| {
1556 let _ = alias.derive(&Bounds::default(), None);
1557 }));
1558 assert!(message.contains("already been moved"), "{message}");
1559 let message = panic_message(std::panic::AssertUnwindSafe(|| {
1560 let _ = derive_scope(&[(&alias).into()], &Bounds::default());
1561 }));
1562 assert!(message.contains("already been moved"), "{message}");
1563
1564 let revoked = PermissionCard {
1565 handle: alias.handle.clone(),
1566 };
1567 let message = panic_message(std::panic::AssertUnwindSafe(move || {
1568 let _ = revoked.revoke();
1569 }));
1570 assert!(message.contains("already been moved"), "{message}");
1571 let installed = PermissionCard {
1572 handle: alias.handle.clone(),
1573 };
1574 let message = panic_message(std::panic::AssertUnwindSafe(move || {
1575 let _ = installed.install(&Holder::Account(Uuid::nil()));
1576 }));
1577 assert!(message.contains("already been moved"), "{message}");
1578 }
1579
1580 #[test]
1581 fn transferred_card_is_moved_and_cannot_be_encoded_twice() {
1582 let card = fake_card(11);
1583 let tree = direct::encode(&card).unwrap();
1584 assert_eq!(format!("{card:?}"), "PermissionCard(<moved>)");
1585 assert!(matches!(
1586 direct::encode(&card),
1587 Err(WireError::ConsumedResource(_))
1588 ));
1589
1590 let received = direct::decode::<PermissionCard>(tree).unwrap();
1591 assert_eq!(format!("{received:?}"), "PermissionCard(<present>)");
1592 assert_eq!(forget(received), Some(11));
1593 }
1594
1595 #[test]
1596 fn aliased_card_is_rejected_before_any_handle_moves() {
1597 let card = fake_card(13);
1598 let alias = PermissionCard {
1599 handle: card.handle.clone(),
1600 };
1601 let pair = (card, alias);
1602 assert!(matches!(
1603 direct::encode(&pair),
1604 Err(WireError::AliasedResource(_))
1605 ));
1606 assert_eq!(format!("{:?}", pair.1), "PermissionCard(<present>)");
1607 assert_eq!(forget(pair.0), Some(13));
1608 }
1609
1610 #[cfg(feature = "export_golem_agentic")]
1611 #[test]
1612 fn card_cannot_be_serialized_or_deserialized() {
1613 let card = fake_card(17);
1614 let error = crate::serde_json::to_string(&card).unwrap_err().to_string();
1615 assert!(error.contains("cannot be serialized"), "{error}");
1616 let error = crate::serde_json::from_str::<PermissionCard>("17")
1617 .unwrap_err()
1618 .to_string();
1619 assert!(error.contains("cannot be deserialized"), "{error}");
1620 assert_eq!(forget(card), Some(17));
1621 }
1622
1623 #[cfg(feature = "export_golem_agentic")]
1624 #[test]
1625 fn scope_card_cannot_be_serialized() {
1626 let scope = fake_scope(18);
1627 let error = crate::serde_json::to_string(&scope)
1628 .unwrap_err()
1629 .to_string();
1630 assert!(error.contains("cannot be serialized"), "{error}");
1631 assert_eq!(format!("{scope:?}"), "ScopeCard(..)");
1632 assert_eq!(forget_scope(scope), 18);
1633 }
1634
1635 #[cfg(feature = "export_golem_agentic")]
1636 #[test]
1637 fn decoding_a_card_takes_its_handle() {
1638 let card = fake_card(23);
1639 let value = card.to_value();
1640 let decoded = PermissionCard::from_value(&value).unwrap();
1641 assert_eq!(format!("{card:?}"), "PermissionCard(<moved>)");
1642 assert!(PermissionCard::from_value(&value).is_err());
1643 assert!(PermissionCard::from_value(&SchemaValue::Bool(true)).is_err());
1644 assert_eq!(forget(decoded), Some(23));
1645 }
1646
1647 #[cfg(feature = "export_golem_agentic")]
1648 #[test]
1649 fn default_scope_card_is_borrowed_and_per_call_card_wins() {
1650 let default_scope = fake_scope(19);
1651 let per_call = fake_scope(20);
1652 let mut default = __DefaultScopeCard::default();
1653 assert!(default.resolve(None).is_none());
1654 default.set(&default_scope);
1655 let handle = |card: Option<ScopeCard>| card.map(forget_scope_shared_handle);
1656 assert_eq!(handle(default.resolve(None)), Some(19));
1657 assert_eq!(handle(default.resolve(Some(&per_call))), Some(20));
1658
1659 assert_eq!(forget_scope(default_scope), 19);
1661 let message = panic_message(std::panic::AssertUnwindSafe(|| {
1662 let _ = default.resolve(None);
1663 }));
1664 assert!(
1665 message.contains("default scope card was dropped"),
1666 "{message}"
1667 );
1668 assert_eq!(handle(default.resolve(Some(&per_call))), Some(20));
1669
1670 default.clear();
1671 assert!(default.resolve(None).is_none());
1672 assert_eq!(forget_scope(per_call), 20);
1673 }
1674
1675 #[test]
1676 fn scope_parents_are_each_locked_once() {
1677 let first = fake_card(21);
1678 let second = fake_card(22);
1679 let template = fake_template(26);
1680 let scope = fake_scope(24);
1681 let handles = with_raw_parents(
1682 &[
1683 (&first).into(),
1684 (&template).into(),
1685 (&scope).into(),
1686 (&second).into(),
1687 (&template).into(),
1688 (&first).into(),
1689 ],
1690 |raw| raw.iter().map(|card| card.handle()).collect::<Vec<_>>(),
1691 );
1692 assert_eq!(handles, vec![21, 26, 24, 22, 26, 21]);
1693 assert_eq!(forget(first), Some(21));
1694 assert_eq!(forget(second), Some(22));
1695 assert_eq!(forget_template(template), Some(26));
1696 assert_eq!(forget_scope(scope), 24);
1697 }
1698
1699 #[test]
1700 fn moved_template_card_fails_loudly() {
1701 let card = fake_template(31);
1702 let alias = PolymorphicPermissionCard {
1703 handle: card.handle.clone(),
1704 };
1705 assert_eq!(forget_template(card), Some(31));
1706
1707 assert_eq!(format!("{alias:?}"), "PolymorphicPermissionCard(<moved>)");
1708 for operation in [
1709 Box::new(|card: &PolymorphicPermissionCard| {
1710 let _ = card.id();
1711 }) as Box<dyn Fn(&PolymorphicPermissionCard)>,
1712 Box::new(|card| {
1713 let _ = card.parents();
1714 }),
1715 Box::new(|card| {
1716 let _ = card.expires_at();
1717 }),
1718 Box::new(|card| {
1719 let _ = card.inspect();
1720 }),
1721 Box::new(|card| {
1722 let _ = card.derive(&Bounds::default(), None);
1723 }),
1724 Box::new(|card| {
1725 let _ = derive_scope(&[card.into()], &Bounds::default());
1726 }),
1727 ] {
1728 let message = panic_message(std::panic::AssertUnwindSafe(|| operation(&alias)));
1729 assert!(message.contains("already been moved"), "{message}");
1730 }
1731
1732 let revoked = PolymorphicPermissionCard {
1733 handle: alias.handle.clone(),
1734 };
1735 let message = panic_message(std::panic::AssertUnwindSafe(move || {
1736 let _ = revoked.revoke();
1737 }));
1738 assert!(message.contains("already been moved"), "{message}");
1739 let installed = PolymorphicPermissionCard {
1740 handle: alias.handle.clone(),
1741 };
1742 let message = panic_message(std::panic::AssertUnwindSafe(move || {
1743 let _ = installed.install(&Holder::Account(Uuid::nil()));
1744 }));
1745 assert!(message.contains("already been moved"), "{message}");
1746 }
1747
1748 #[test]
1749 fn transferred_template_card_is_moved_and_cannot_be_encoded_twice() {
1750 let card = fake_template(32);
1751 let tree = direct::encode(&card).unwrap();
1752 assert_eq!(format!("{card:?}"), "PolymorphicPermissionCard(<moved>)");
1753 assert!(matches!(
1754 direct::encode(&card),
1755 Err(WireError::ConsumedResource(_))
1756 ));
1757
1758 let received = direct::decode::<PolymorphicPermissionCard>(tree).unwrap();
1759 assert_eq!(
1760 format!("{received:?}"),
1761 "PolymorphicPermissionCard(<present>)"
1762 );
1763 assert_eq!(forget_template(received), Some(32));
1764 }
1765
1766 #[test]
1767 fn aliased_template_card_is_rejected_before_any_handle_moves() {
1768 let card = fake_template(33);
1769 let alias = PolymorphicPermissionCard {
1770 handle: card.handle.clone(),
1771 };
1772 let other = fake_card(34);
1773 let triple = (other, card, alias);
1774 assert!(matches!(
1775 direct::encode(&triple),
1776 Err(WireError::AliasedResource(_))
1777 ));
1778 assert_eq!(format!("{:?}", triple.0), "PermissionCard(<present>)");
1779 assert_eq!(
1780 format!("{:?}", triple.2),
1781 "PolymorphicPermissionCard(<present>)"
1782 );
1783 assert_eq!(forget(triple.0), Some(34));
1784 assert_eq!(forget_template(triple.1), Some(33));
1785 }
1786
1787 #[test]
1788 fn decoding_a_shared_wire_snapshot_twice_does_not_alias_the_template_card() {
1789 let card = fake_template(35);
1790 let tree = direct::encode(&card).unwrap();
1791 let snapshot = std::rc::Rc::new(direct::WireSnapshot::new(tree.value_nodes));
1792 let first =
1793 PolymorphicPermissionCard::read_wire(&mut snapshot.reader(), tree.root).unwrap();
1794 assert!(matches!(
1795 PolymorphicPermissionCard::read_wire(&mut snapshot.reader(), tree.root),
1796 Err(WireError::ConsumedResource(_))
1797 ));
1798 assert_eq!(forget_template(first), Some(35));
1799 }
1800
1801 #[test]
1802 fn card_types_declare_their_polymorphic_flag_on_the_wire() {
1803 let polymorphic =
1804 |graph: wire::SchemaGraph| match &graph.type_nodes[graph.root as usize].body {
1805 wire::SchemaTypeBody::PermissionCardType(spec) => spec.polymorphic,
1806 other => panic!("expected a permission-card type, found {other:?}"),
1807 };
1808 assert!(!polymorphic(direct::schema::<PermissionCard>()));
1809 assert!(polymorphic(direct::schema::<PolymorphicPermissionCard>()));
1810 }
1811
1812 #[cfg(feature = "export_golem_agentic")]
1813 #[test]
1814 fn template_card_cannot_be_serialized_or_deserialized() {
1815 let card = fake_template(36);
1816 let error = crate::serde_json::to_string(&card).unwrap_err().to_string();
1817 assert!(error.contains("cannot be serialized"), "{error}");
1818 let error = crate::serde_json::from_str::<PolymorphicPermissionCard>("36")
1819 .unwrap_err()
1820 .to_string();
1821 assert!(error.contains("cannot be deserialized"), "{error}");
1822 assert_eq!(forget_template(card), Some(36));
1823 }
1824
1825 #[cfg(feature = "export_golem_agentic")]
1826 #[test]
1827 fn decoding_a_template_card_takes_its_handle() {
1828 let card = fake_template(37);
1829 let value = card.to_value();
1830 let decoded = PolymorphicPermissionCard::from_value(&value).unwrap();
1831 assert_eq!(format!("{card:?}"), "PolymorphicPermissionCard(<moved>)");
1832 assert!(PolymorphicPermissionCard::from_value(&value).is_err());
1833 assert!(PolymorphicPermissionCard::from_value(&SchemaValue::Bool(true)).is_err());
1834 assert_eq!(forget_template(decoded), Some(37));
1835 }
1836
1837 #[cfg(feature = "export_golem_agentic")]
1838 #[test]
1839 fn card_types_declare_their_polymorphic_flag_in_the_schema() {
1840 #[derive(IntoSchema)]
1841 struct Cards {
1842 concrete: PermissionCard,
1843 template: PolymorphicPermissionCard,
1844 }
1845
1846 let schema = crate::schema::try_into_schema_graph::<Cards>().unwrap();
1847 let resolve = |ty| schema.resolve_ref(ty).unwrap();
1848 let SchemaType::Record { fields, .. } = resolve(&schema.root) else {
1849 panic!("expected a record");
1850 };
1851 let flags = fields
1852 .iter()
1853 .map(|field| match resolve(&field.body) {
1854 SchemaType::PermissionCard { spec, .. } => spec.polymorphic,
1855 other => panic!("expected a permission-card type, found {other:?}"),
1856 })
1857 .collect::<Vec<_>>();
1858 assert_eq!(flags, vec![false, true]);
1859 }
1860
1861 #[test]
1862 fn wallet_cards_keep_the_kind_the_host_reported() {
1863 let raw_card = |handle| unsafe { wire::PermissionCard::from_handle(handle) };
1864 let cards = [
1865 raw::WalletCard::Concrete(raw_card(41)),
1866 raw::WalletCard::Template(raw_card(42)),
1867 raw::WalletCard::Scope(raw_card(43)),
1868 ]
1869 .into_iter()
1870 .map(WalletCard::from_raw)
1871 .collect::<Vec<_>>();
1872 let handles = cards
1873 .into_iter()
1874 .map(|card| match card {
1875 WalletCard::Concrete(card) => ("concrete", forget(card).unwrap()),
1876 WalletCard::Template(card) => ("template", forget_template(card).unwrap()),
1877 WalletCard::Scope(card) => ("scope", forget_scope(card)),
1878 })
1879 .collect::<Vec<_>>();
1880 assert_eq!(
1881 handles,
1882 vec![("concrete", 41), ("template", 42), ("scope", 43)]
1883 );
1884 }
1885
1886 #[test]
1887 fn card_id_is_opaque_but_comparable() {
1888 let raw_id = || wire::CardId {
1889 uuid: Uuid::from_u128(42).into(),
1890 };
1891 let id = CardId::from_raw(raw_id());
1892 assert_eq!(id, CardId::from_raw(raw_id()));
1893 assert_ne!(
1894 id,
1895 CardId::from_raw(wire::CardId {
1896 uuid: Uuid::from_u128(43).into()
1897 })
1898 );
1899 assert_eq!(format!("{id:?}"), "CardId(<redacted card id>)");
1900 assert_eq!(format!("{id:#?}"), "CardId(<redacted card id>)");
1901 assert_eq!(id.to_string(), "<redacted card id>");
1902 #[cfg(feature = "export_golem_agentic")]
1903 assert_eq!(
1904 crate::serde_json::to_string(&vec![id.clone()]).unwrap(),
1905 r#"["<redacted card id>"]"#
1906 );
1907 assert_eq!(id.uuid, Uuid::from_u128(42));
1908 }
1909
1910 #[test]
1911 fn decoding_a_shared_wire_snapshot_twice_does_not_alias_the_card() {
1912 let card = fake_card(25);
1913 let tree = direct::encode(&card).unwrap();
1914 let snapshot = std::rc::Rc::new(direct::WireSnapshot::new(tree.value_nodes));
1915 let first = PermissionCard::read_wire(&mut snapshot.reader(), tree.root).unwrap();
1916 assert!(matches!(
1917 PermissionCard::read_wire(&mut snapshot.reader(), tree.root),
1918 Err(WireError::ConsumedResource(_))
1919 ));
1920 assert_eq!(forget(first), Some(25));
1921 }
1922}