1#[derive(
23 ::std::clone::Clone,
24 ::std::fmt::Debug,
25 ::std::cmp::PartialEq,
26 ::std::cmp::Eq,
27 ::std::hash::Hash,
28 ::serde::Serialize,
29 ::serde::Deserialize,
30)]
31pub struct CredentialRef(::std::string::String);
32
33impl CredentialRef {
34 pub fn new(s: ::std::string::String) -> Self {
36 Self(s)
37 }
38
39 pub fn as_str(&self) -> &str {
41 &self.0
42 }
43}
44
45#[derive(
53 ::std::clone::Clone,
54 ::std::fmt::Debug,
55 ::std::cmp::PartialEq,
56 ::std::cmp::Eq,
57 ::serde::Serialize,
58 ::serde::Deserialize,
59)]
60#[serde(tag = "kind", rename_all = "snake_case")]
61pub enum AccessIntent {
62 OneShot,
64 Session {
66 ttl_seconds: u64,
68 },
69 Persistent {
71 mandate_id: ::std::string::String,
73 },
74}
75
76#[derive(
84 ::std::fmt::Debug,
85 ::std::clone::Clone,
86 ::std::cmp::PartialEq,
87 ::std::cmp::Eq,
88 ::serde::Serialize,
89 ::serde::Deserialize,
90)]
91#[serde(rename_all = "camelCase", deny_unknown_fields)]
92pub struct CredentialAccessNotarizationEnvelope {
93 pub aph_version: ::std::string::String,
95 #[serde(rename = "@context")]
97 pub context: ::std::vec::Vec<::std::string::String>,
98 #[serde(rename = "type")]
101 pub r#type: ::std::vec::Vec<::std::string::String>,
102 pub id: ::std::string::String,
104 pub issuer: ::std::string::String,
106 pub valid_from: ::std::string::String,
108 pub valid_until: ::std::string::String,
110 pub credential_access_subject: CredentialAccessSubject,
112 #[serde(default)]
114 pub linked_mandate: ::std::option::Option<crate::envelope::LinkedMandate>,
115 pub proof: crate::envelope::EnvelopeProof,
123}
124
125#[derive(
133 ::std::fmt::Debug,
134 ::std::clone::Clone,
135 ::std::cmp::PartialEq,
136 ::std::cmp::Eq,
137 ::serde::Serialize,
138 ::serde::Deserialize,
139)]
140#[serde(rename_all = "camelCase", deny_unknown_fields)]
141pub struct CredentialAccessSubject {
142 pub human_principal: crate::envelope::HumanPrincipalRef,
145 pub agent: crate::envelope::AgentRef,
147 pub credential_ref: CredentialRef,
150 pub access_intent: AccessIntent,
153 pub policy: crate::envelope::PolicyDescriptor,
155 pub notarization: crate::envelope::NotarizationMetadata,
157}
158
159#[cfg(test)]
160mod tests {
161 fn make_envelope() -> super::CredentialAccessNotarizationEnvelope {
167 super::CredentialAccessNotarizationEnvelope {
168 aph_version: ::std::string::String::from("0.1"),
169 context: ::std::vec![::std::string::String::from(
170 "https://www.w3.org/ns/credentials/v2",
171 )],
172 r#type: ::std::vec![
173 ::std::string::String::from("VerifiableCredential"),
174 ::std::string::String::from("AgentCredentialAccessAuthorization"),
175 ],
176 id: ::std::string::String::from("urn:uuid:00000000-0000-4000-8000-000000000000"),
177 issuer: ::std::string::String::from("did:web:notary.example"),
178 valid_from: ::std::string::String::from("2026-05-29T00:00:00Z"),
179 valid_until: ::std::string::String::from("2026-05-29T01:00:00Z"),
180 credential_access_subject: super::CredentialAccessSubject {
181 human_principal: crate::envelope::HumanPrincipalRef {
182 id: ::std::string::String::from("did:key:zUser"),
183 display_name: ::std::string::String::from("Scott"),
184 },
185 agent: crate::envelope::AgentRef {
186 id: ::std::string::String::from("did:web:agent.example"),
187 agent_card_uri: ::std::option::Option::None,
188 display_name: ::std::string::String::from("TestAgent"),
189 version: ::std::string::String::from("0.1.0"),
190 },
191 credential_ref: super::CredentialRef::new(
192 ::std::string::String::from("cred-id-1"),
193 ),
194 access_intent: super::AccessIntent::OneShot,
195 policy: crate::envelope::PolicyDescriptor {
196 decision: crate::envelope::PolicyDecision::AlwaysAllow,
197 matched_scope: ::std::string::String::from("per-credential"),
198 delegation_mandate_id: ::std::option::Option::None,
199 act_chain: ::std::vec::Vec::new(),
200 attestation_mode: ::std::option::Option::None,
201 delegation_mandate: ::std::option::Option::None,
202 },
203 notarization: crate::envelope::NotarizationMetadata {
204 notary_service: crate::envelope::NotaryServiceRef {
205 id: ::std::string::String::from("did:web:notary.example"),
206 name: ::std::string::String::from("Squillo Notary Service"),
207 version: ::std::string::String::from("0.1.0"),
208 attested_digest: ::std::option::Option::None,
209 attestation_uri: ::std::option::Option::None,
210 },
211 decision_timestamp: ::std::string::String::from("2026-05-29T00:00:00Z"),
212 decision_latency_ms: 0,
213 },
214 },
215 linked_mandate: ::std::option::Option::None,
216 proof: crate::envelope::EnvelopeProof {
217 r#type: ::std::string::String::from("DataIntegrityProof"),
218 cryptosuite: ::std::option::Option::Some(::std::string::String::from(
219 "eddsa-jcs-2022",
220 )),
221 verification_method: ::std::string::String::from("did:key:zNotary#zNotary"),
222 created: ::std::string::String::from("2026-05-29T00:00:00Z"),
223 proof_purpose: ::std::string::String::from("assertionMethod"),
224 proof_value: ::std::string::String::from("placeholder"),
225 id: ::std::option::Option::None,
226 previous_proof: ::std::option::Option::None,
227 },
228 }
229 }
230
231 #[test]
232 fn type_array_contains_credential_access_marker() {
233 let env = make_envelope();
237 assert_eq!(env.r#type.len(), 2);
238 assert!(env
239 .r#type
240 .iter()
241 .any(|t| t == "AgentCredentialAccessAuthorization"));
242 }
243
244 #[test]
245 fn envelope_round_trip_json_serde() {
246 let env = make_envelope();
250 let s = ::serde_json::to_string(&env).expect("serialize");
251 let env2: super::CredentialAccessNotarizationEnvelope =
252 ::serde_json::from_str(&s).expect("deserialize");
253 assert_eq!(env, env2);
254
255 assert!(s.contains("@context"));
257 assert!(s.contains("VerifiableCredential"));
258 assert!(s.contains("AgentCredentialAccessAuthorization"));
259 assert!(s.contains("credentialAccessSubject"));
261 assert!(s.contains("credentialRef"));
264 assert!(s.contains("accessIntent"));
265 }
266
267 #[test]
268 fn envelope_rejects_unknown_fields() {
269 let env = make_envelope();
274 let mut value = ::serde_json::to_value(&env).expect("serialize");
275 if let ::serde_json::Value::Object(map) = &mut value {
276 map.insert(
277 ::std::string::String::from("bogusFieldNotInSchema"),
278 ::serde_json::Value::String(::std::string::String::from("should-fail")),
279 );
280 } else {
281 panic!("envelope serializes to a JSON object");
282 }
283 let s = ::serde_json::to_string(&value).expect("re-serialize");
284 let result: ::std::result::Result<
285 super::CredentialAccessNotarizationEnvelope,
286 ::serde_json::Error,
287 > = ::serde_json::from_str(&s);
288 assert!(
289 result.is_err(),
290 "deny_unknown_fields MUST reject bogus extra field"
291 );
292 }
293
294 #[test]
295 fn cross_crate_credential_ref_in_envelope_round_trips() {
296 let env = make_envelope();
301 let s = ::serde_json::to_string(&env).expect("serialize");
302 let env2: super::CredentialAccessNotarizationEnvelope =
303 ::serde_json::from_str(&s).expect("deserialize");
304 assert_eq!(
305 env.credential_access_subject.credential_ref,
306 env2.credential_access_subject.credential_ref,
307 );
308 assert_eq!(
309 env.credential_access_subject.access_intent,
310 env2.credential_access_subject.access_intent,
311 );
312 }
313}