1use serde::{Deserialize, Serialize};
25use sha2::{Digest as _, Sha256};
26use std::cmp::Ordering;
27use std::fmt::Write as _;
28
29use crate::ValidationError;
30use crate::team_context::{
31 ContentDigest, Ed25519Signature, KeyId, NonceDigest, ReasonCode, RecordDigest, TeamId,
32 TeamTimestampV1, timestamp_cmp,
33};
34
35pub const ORG_POLICY_V2_ARTIFACT_SCHEMA_ID: &str = "leanctx.org-policy/v2";
37pub const ORG_POLICY_VERIFY_REQUEST_SCHEMA_ID: &str = "leanctx.org-policy-verify-request/v1";
38pub const ORG_POLICY_VERIFY_RESPONSE_SCHEMA_ID: &str = "leanctx.org-policy-verify-response/v1";
39pub const ORG_POLICY_VERIFY_REQUEST_DIGEST_DOMAIN: &str = "leanctx.org-policy-verify-request.v1\n";
40pub const ORG_POLICY_VERIFY_RESPONSE_SIGNATURE_DOMAIN: &str =
41 "leanctx.org-policy-verify-response.v1\n";
42pub const ORG_REQUIRED_MARKER_SCHEMA_ID: &str = "leanctx.org-required-marker/v1";
43pub const ORG_REQUIRED_MARKER_DIGEST_DOMAIN: &str = "leanctx.org-required-marker.v1\n";
44pub const MAX_ORG_POLICY_VERIFIED_PACK_BYTES: usize = 1024 * 1024;
46pub const MAX_ORG_REQUIRED_MARKER_ENTRIES: usize = 1024;
48pub const MAX_ORG_REQUIRED_MARKER_BYTES: usize = 512 * 1024;
50
51const MAX_SEQUENCE: u64 = 9_007_199_254_740_991;
52
53fn hex_digest(domain: &str, bytes: &[u8]) -> String {
54 let mut hasher = Sha256::new();
55 hasher.update(domain.as_bytes());
56 hasher.update(bytes);
57 let mut value = String::from("sha256:");
58 for byte in hasher.finalize() {
59 write!(&mut value, "{byte:02x}").expect("writing to String cannot fail");
60 }
61 value
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct OrgPolicyVerifyRequestV1 {
68 pub schema: String,
69 pub organization_id: TeamId,
70 pub artifact_digest: ContentDigest,
72 pub trust_digest: Option<ContentDigest>,
74 pub marker_digest: Option<ContentDigest>,
76 pub nonce: NonceDigest,
78 pub requested_at: TeamTimestampV1,
79 pub deadline: TeamTimestampV1,
81}
82
83impl OrgPolicyVerifyRequestV1 {
84 pub fn validate(&self) -> Result<(), ValidationError> {
85 if self.schema != ORG_POLICY_VERIFY_REQUEST_SCHEMA_ID {
86 return Err(ValidationError::new(
87 "org policy verify request schema is unknown",
88 ));
89 }
90 if timestamp_cmp(&self.requested_at, &self.deadline) != Ordering::Less {
91 return Err(ValidationError::new(
92 "org policy verify request deadline is not after its request time",
93 ));
94 }
95 Ok(())
96 }
97
98 pub fn canonical_bytes(&self) -> Result<Vec<u8>, ValidationError> {
99 self.validate()?;
100 serde_json::to_vec(self)
101 .map_err(|_| ValidationError::new("org policy verify request cannot be encoded"))
102 }
103
104 pub fn digest(&self) -> Result<RecordDigest, ValidationError> {
105 RecordDigest::new(hex_digest(
106 ORG_POLICY_VERIFY_REQUEST_DIGEST_DOMAIN,
107 &self.canonical_bytes()?,
108 ))
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
116pub enum OrgPolicyVerifyOutcomeV1 {
117 Enforce {
118 pack_toml: String,
119 policy_digest: RecordDigest,
120 sequence: u64,
121 valid_until: TeamTimestampV1,
126 },
127 NotRequired {
128 unpin_digest: RecordDigest,
129 sequence: u64,
130 },
131 Refuse {
132 reason: ReasonCode,
133 },
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(deny_unknown_fields)]
139pub struct OrgPolicyVerifyResponseV1 {
140 pub schema: String,
141 pub request_digest: RecordDigest,
142 pub organization_id: TeamId,
143 pub artifact_digest: ContentDigest,
144 pub trust_digest: Option<ContentDigest>,
145 pub marker_digest: Option<ContentDigest>,
146 pub nonce: NonceDigest,
147 pub answered_at: TeamTimestampV1,
148 pub verifier_key_id: KeyId,
149 pub outcome: OrgPolicyVerifyOutcomeV1,
150}
151
152impl OrgPolicyVerifyResponseV1 {
153 pub fn validate(&self) -> Result<(), ValidationError> {
154 if self.schema != ORG_POLICY_VERIFY_RESPONSE_SCHEMA_ID {
155 return Err(ValidationError::new(
156 "org policy verify response schema is unknown",
157 ));
158 }
159 match &self.outcome {
160 OrgPolicyVerifyOutcomeV1::Enforce {
161 pack_toml,
162 sequence,
163 ..
164 } => {
165 if pack_toml.is_empty() || pack_toml.len() > MAX_ORG_POLICY_VERIFIED_PACK_BYTES {
166 return Err(ValidationError::new(
167 "org policy verify response pack size is out of range",
168 ));
169 }
170 valid_sequence(*sequence)
171 }
172 OrgPolicyVerifyOutcomeV1::NotRequired { sequence, .. } => valid_sequence(*sequence),
173 OrgPolicyVerifyOutcomeV1::Refuse { .. } => Ok(()),
174 }
175 }
176
177 pub fn canonical_bytes(&self) -> Result<Vec<u8>, ValidationError> {
178 self.validate()?;
179 serde_json::to_vec(self)
180 .map_err(|_| ValidationError::new("org policy verify response cannot be encoded"))
181 }
182
183 pub fn signing_bytes(&self) -> Result<Vec<u8>, ValidationError> {
185 let mut bytes = ORG_POLICY_VERIFY_RESPONSE_SIGNATURE_DOMAIN
186 .as_bytes()
187 .to_vec();
188 bytes.extend(self.canonical_bytes()?);
189 Ok(bytes)
190 }
191
192 pub fn binds(
203 &self,
204 request: &OrgPolicyVerifyRequestV1,
205 now: &TeamTimestampV1,
206 ) -> Result<(), ValidationError> {
207 self.validate()?;
208 if self.request_digest != request.digest()?
209 || self.organization_id != request.organization_id
210 || self.artifact_digest != request.artifact_digest
211 || self.trust_digest != request.trust_digest
212 || self.marker_digest != request.marker_digest
213 || self.nonce != request.nonce
214 {
215 return Err(ValidationError::new(
216 "org policy verify response does not answer this request",
217 ));
218 }
219 if timestamp_cmp(&self.answered_at, &request.requested_at) == Ordering::Less
223 || timestamp_cmp(&self.answered_at, &request.deadline) == Ordering::Greater
224 || timestamp_cmp(now, &self.answered_at) == Ordering::Less
225 || timestamp_cmp(now, &request.deadline) == Ordering::Greater
226 {
227 return Err(ValidationError::new(
228 "org policy verify response is outside the request window",
229 ));
230 }
231 if let OrgPolicyVerifyOutcomeV1::Enforce { valid_until, .. } = &self.outcome
235 && (timestamp_cmp(&self.answered_at, valid_until) != Ordering::Less
236 || timestamp_cmp(now, valid_until) != Ordering::Less)
237 {
238 return Err(ValidationError::new(
239 "org policy verify response is past the validity of its finding",
240 ));
241 }
242 Ok(())
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248#[serde(deny_unknown_fields)]
249pub struct OrgPolicyVerifyEnvelopeV1 {
250 pub response: OrgPolicyVerifyResponseV1,
251 pub signature: Ed25519Signature,
252}
253
254fn valid_sequence(sequence: u64) -> Result<(), ValidationError> {
255 if sequence == 0 || sequence > MAX_SEQUENCE {
256 return Err(ValidationError::new("org policy sequence is out of range"));
257 }
258 Ok(())
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
264pub enum OrgRequiredMarkerStateV1 {
265 Required { policy_digest: RecordDigest },
267 Unpinned { unpin_digest: RecordDigest },
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
272#[serde(deny_unknown_fields)]
273pub struct OrgRequiredMarkerEntryV1 {
274 pub sequence: u64,
275 pub recorded_at: TeamTimestampV1,
276 pub state: OrgRequiredMarkerStateV1,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(deny_unknown_fields)]
283pub struct OrgRequiredMarkerV1 {
284 pub schema: String,
285 pub organization_id: TeamId,
286 pub entries: Vec<OrgRequiredMarkerEntryV1>,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum OrgRequiredMarkerAdvance {
293 Appended,
295 Unchanged,
297}
298
299impl OrgRequiredMarkerV1 {
300 pub fn first(
302 organization_id: TeamId,
303 entry: OrgRequiredMarkerEntryV1,
304 ) -> Result<Self, ValidationError> {
305 let marker = Self {
306 schema: ORG_REQUIRED_MARKER_SCHEMA_ID.into(),
307 organization_id,
308 entries: vec![entry],
309 };
310 marker.validate()?;
311 Ok(marker)
312 }
313
314 pub fn validate(&self) -> Result<(), ValidationError> {
315 if self.schema != ORG_REQUIRED_MARKER_SCHEMA_ID {
316 return Err(ValidationError::new(
317 "org required marker schema is unknown",
318 ));
319 }
320 if self.entries.is_empty() || self.entries.len() > MAX_ORG_REQUIRED_MARKER_ENTRIES {
321 return Err(ValidationError::new(
322 "org required marker entry count is out of range",
323 ));
324 }
325 valid_sequence(self.entries[0].sequence)?;
326 for pair in self.entries.windows(2) {
327 valid_sequence(pair[1].sequence)?;
328 if !follows(&pair[0], &pair[1]) {
329 return Err(ValidationError::new(
330 "org required marker history does not move forward",
331 ));
332 }
333 }
334 Ok(())
335 }
336
337 pub fn current(&self) -> &OrgRequiredMarkerEntryV1 {
339 self.entries
340 .last()
341 .expect("a validated marker has at least one entry")
342 }
343
344 pub fn is_required(&self) -> bool {
346 matches!(
347 self.current().state,
348 OrgRequiredMarkerStateV1::Required { .. }
349 )
350 }
351
352 pub fn advance(
358 &mut self,
359 organization_id: &TeamId,
360 entry: OrgRequiredMarkerEntryV1,
361 ) -> Result<OrgRequiredMarkerAdvance, ValidationError> {
362 self.validate()?;
363 valid_sequence(entry.sequence)?;
364 if organization_id != &self.organization_id {
365 return Err(ValidationError::new(
366 "org required marker belongs to another organization",
367 ));
368 }
369 let current = self.current();
370 if entry.sequence == current.sequence && entry.state == current.state {
371 return Ok(OrgRequiredMarkerAdvance::Unchanged);
372 }
373 if !follows(current, &entry) {
374 return Err(ValidationError::new(
375 "org required marker refuses a state that does not move forward",
376 ));
377 }
378 if self.entries.len() >= MAX_ORG_REQUIRED_MARKER_ENTRIES {
379 return Err(ValidationError::new("org required marker history is full"));
380 }
381 self.entries.push(entry);
382 Ok(OrgRequiredMarkerAdvance::Appended)
383 }
384
385 pub fn canonical_bytes(&self) -> Result<Vec<u8>, ValidationError> {
386 self.validate()?;
387 serde_json::to_vec(self)
388 .map_err(|_| ValidationError::new("org required marker cannot be encoded"))
389 }
390
391 pub fn parse(bytes: &[u8]) -> Result<Self, ValidationError> {
393 if bytes.len() > MAX_ORG_REQUIRED_MARKER_BYTES {
394 return Err(ValidationError::new("org required marker is too large"));
395 }
396 let marker: Self = serde_json::from_slice(bytes)
397 .map_err(|_| ValidationError::new("org required marker is malformed"))?;
398 if marker.canonical_bytes()? != bytes {
399 return Err(ValidationError::new(
400 "org required marker is not in canonical form",
401 ));
402 }
403 Ok(marker)
404 }
405
406 pub fn digest(&self) -> Result<RecordDigest, ValidationError> {
407 RecordDigest::new(hex_digest(
408 ORG_REQUIRED_MARKER_DIGEST_DOMAIN,
409 &self.canonical_bytes()?,
410 ))
411 }
412}
413
414fn follows(previous: &OrgRequiredMarkerEntryV1, next: &OrgRequiredMarkerEntryV1) -> bool {
417 match next.sequence.cmp(&previous.sequence) {
418 Ordering::Greater => true,
419 Ordering::Less => false,
420 Ordering::Equal => matches!(
421 (&previous.state, &next.state),
422 (
423 OrgRequiredMarkerStateV1::Required { .. },
424 OrgRequiredMarkerStateV1::Unpinned { .. }
425 )
426 ),
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433
434 fn content(fill: char) -> ContentDigest {
435 ContentDigest::new(format!("sha256:{}", fill.to_string().repeat(64))).unwrap()
436 }
437
438 fn record(fill: char) -> RecordDigest {
439 RecordDigest::new(format!("sha256:{}", fill.to_string().repeat(64))).unwrap()
440 }
441
442 fn at(value: &str) -> TeamTimestampV1 {
443 TeamTimestampV1::new(value).unwrap()
444 }
445
446 fn org(name: &str) -> TeamId {
447 TeamId::new(name).unwrap()
448 }
449
450 fn request() -> OrgPolicyVerifyRequestV1 {
451 OrgPolicyVerifyRequestV1 {
452 schema: ORG_POLICY_VERIFY_REQUEST_SCHEMA_ID.into(),
453 organization_id: org("acme"),
454 artifact_digest: content('a'),
455 trust_digest: Some(content('b')),
456 marker_digest: Some(content('c')),
457 nonce: NonceDigest::new(format!("sha256:{}", "d".repeat(64))).unwrap(),
458 requested_at: at("2026-10-10T09:00:00Z"),
459 deadline: at("2026-10-10T09:00:02Z"),
460 }
461 }
462
463 fn response(request: &OrgPolicyVerifyRequestV1) -> OrgPolicyVerifyResponseV1 {
464 OrgPolicyVerifyResponseV1 {
465 schema: ORG_POLICY_VERIFY_RESPONSE_SCHEMA_ID.into(),
466 request_digest: request.digest().unwrap(),
467 organization_id: request.organization_id.clone(),
468 artifact_digest: request.artifact_digest.clone(),
469 trust_digest: request.trust_digest.clone(),
470 marker_digest: request.marker_digest.clone(),
471 nonce: request.nonce.clone(),
472 answered_at: at("2026-10-10T09:00:01Z"),
473 verifier_key_id: KeyId::new("key:verifier-test").unwrap(),
474 outcome: OrgPolicyVerifyOutcomeV1::Enforce {
475 pack_toml: "name = \"floor\"\n".into(),
476 policy_digest: record('e'),
477 sequence: 7,
478 valid_until: at("2026-11-01T00:00:00Z"),
479 },
480 }
481 }
482
483 fn enforce_until(
484 request: &OrgPolicyVerifyRequestV1,
485 valid_until: &str,
486 ) -> OrgPolicyVerifyResponseV1 {
487 let mut response = response(request);
488 response.outcome = OrgPolicyVerifyOutcomeV1::Enforce {
489 pack_toml: "name = \"floor\"\n".into(),
490 policy_digest: record('e'),
491 sequence: 7,
492 valid_until: at(valid_until),
493 };
494 response
495 }
496
497 #[test]
498 fn an_enforce_finding_is_refused_at_and_after_its_own_limit() {
499 let request = request();
502 let limited = enforce_until(&request, "2026-10-10T09:00:01.500Z");
503 assert!(
504 limited
505 .binds(&request, &at("2026-10-10T09:00:01.499Z"))
506 .is_ok()
507 );
508 assert!(
509 limited
510 .binds(&request, &at("2026-10-10T09:00:01.500Z"))
511 .is_err(),
512 "the limit itself is outside"
513 );
514 assert!(
515 limited
516 .binds(&request, &at("2026-10-10T09:00:01.900Z"))
517 .is_err(),
518 "still before the request deadline, past the finding"
519 );
520 let already_over = enforce_until(&request, "2026-10-10T09:00:01Z");
522 assert!(
523 already_over
524 .binds(&request, &at("2026-10-10T09:00:01Z"))
525 .is_err()
526 );
527 assert_ne!(
529 limited.signing_bytes().unwrap(),
530 enforce_until(&request, "2026-10-10T09:00:02Z")
531 .signing_bytes()
532 .unwrap()
533 );
534 }
535
536 #[test]
537 fn an_answer_binds_only_the_request_it_was_given_for() {
538 let request = request();
539 let now = at("2026-10-10T09:00:01.500Z");
540 assert!(response(&request).binds(&request, &now).is_ok());
541
542 let mut other_org = response(&request);
543 other_org.organization_id = org("other");
544 assert!(other_org.binds(&request, &now).is_err());
545
546 let mut other_artifact = response(&request);
547 other_artifact.artifact_digest = content('f');
548 assert!(other_artifact.binds(&request, &now).is_err());
549
550 let mut no_trust = response(&request);
551 no_trust.trust_digest = None;
552 assert!(no_trust.binds(&request, &now).is_err());
553
554 let mut other_marker = response(&request);
555 other_marker.marker_digest = Some(content('f'));
556 assert!(other_marker.binds(&request, &now).is_err());
557
558 let mut replayed = response(&request);
559 replayed.nonce = NonceDigest::new(format!("sha256:{}", "9".repeat(64))).unwrap();
560 assert!(replayed.binds(&request, &now).is_err());
561
562 let mut later = request.clone();
565 later.deadline = at("2026-10-10T09:00:03Z");
566 assert!(response(&later).binds(&request, &now).is_err());
567 }
568
569 #[test]
570 fn an_answer_outside_the_window_is_refused() {
571 let request = request();
572 let in_time = at("2026-10-10T09:00:02Z");
573 assert!(response(&request).binds(&request, &in_time).is_ok());
574
575 let late_use = at("2026-10-10T09:00:02.001Z");
576 assert!(response(&request).binds(&request, &late_use).is_err());
577
578 let mut answered_late = response(&request);
579 answered_late.answered_at = at("2026-10-10T09:00:03Z");
580 assert!(answered_late.binds(&request, &in_time).is_err());
581
582 let mut answered_early = response(&request);
583 answered_early.answered_at = at("2026-10-10T08:59:59Z");
584 assert!(answered_early.binds(&request, &in_time).is_err());
585
586 let before_request = at("2026-10-10T08:59:59Z");
590 assert!(response(&request).binds(&request, &before_request).is_err());
591 let before_answer = at("2026-10-10T09:00:00.999Z");
592 assert!(response(&request).binds(&request, &before_answer).is_err());
593 let at_answer = at("2026-10-10T09:00:01Z");
594 assert!(response(&request).binds(&request, &at_answer).is_ok());
595 }
596
597 #[test]
598 fn request_and_response_refuse_malformed_content() {
599 let mut inverted = request();
600 inverted.deadline = inverted.requested_at.clone();
601 assert!(inverted.validate().is_err());
602
603 let request = request();
604 let mut empty_pack = response(&request);
605 empty_pack.outcome = OrgPolicyVerifyOutcomeV1::Enforce {
606 pack_toml: String::new(),
607 policy_digest: record('e'),
608 sequence: 7,
609 valid_until: at("2026-11-01T00:00:00Z"),
610 };
611 assert!(empty_pack.validate().is_err());
612
613 let mut zero = response(&request);
614 zero.outcome = OrgPolicyVerifyOutcomeV1::NotRequired {
615 unpin_digest: record('e'),
616 sequence: 0,
617 };
618 assert!(zero.validate().is_err());
619
620 let unknown = br#"{"schema":"leanctx.org-policy-verify-request/v1","extra":1}"#;
621 assert!(serde_json::from_slice::<OrgPolicyVerifyRequestV1>(unknown).is_err());
622 }
623
624 #[test]
625 fn signing_bytes_cover_the_outcome() {
626 let request = request();
627 let enforce = response(&request);
628 let mut refuse = response(&request);
629 refuse.outcome = OrgPolicyVerifyOutcomeV1::Refuse {
630 reason: ReasonCode::new("policy_expired").unwrap(),
631 };
632 assert_ne!(
633 enforce.signing_bytes().unwrap(),
634 refuse.signing_bytes().unwrap()
635 );
636 assert!(
637 enforce
638 .signing_bytes()
639 .unwrap()
640 .starts_with(ORG_POLICY_VERIFY_RESPONSE_SIGNATURE_DOMAIN.as_bytes())
641 );
642 }
643
644 fn required(sequence: u64, fill: char) -> OrgRequiredMarkerEntryV1 {
645 OrgRequiredMarkerEntryV1 {
646 sequence,
647 recorded_at: at("2026-10-10T09:00:00Z"),
648 state: OrgRequiredMarkerStateV1::Required {
649 policy_digest: record(fill),
650 },
651 }
652 }
653
654 fn unpinned(sequence: u64, fill: char) -> OrgRequiredMarkerEntryV1 {
655 OrgRequiredMarkerEntryV1 {
656 sequence,
657 recorded_at: at("2026-10-10T10:00:00Z"),
658 state: OrgRequiredMarkerStateV1::Unpinned {
659 unpin_digest: record(fill),
660 },
661 }
662 }
663
664 #[test]
665 fn the_marker_only_moves_forward() {
666 let acme = org("acme");
667 let mut marker = OrgRequiredMarkerV1::first(acme.clone(), required(3, 'a')).unwrap();
668 assert!(marker.is_required());
669
670 assert_eq!(
671 marker.advance(&acme, required(3, 'a')).unwrap(),
672 OrgRequiredMarkerAdvance::Unchanged
673 );
674 assert!(marker.advance(&acme, required(2, 'b')).is_err(), "lower");
675 assert!(
676 marker.advance(&acme, required(3, 'b')).is_err(),
677 "equal sequence, other content"
678 );
679 assert!(
680 marker.advance(&org("other"), required(4, 'b')).is_err(),
681 "another organization"
682 );
683 assert_eq!(marker.entries.len(), 1, "refusals change nothing");
684
685 assert_eq!(
686 marker.advance(&acme, required(4, 'b')).unwrap(),
687 OrgRequiredMarkerAdvance::Appended
688 );
689 assert_eq!(marker.current().sequence, 4);
690 }
691
692 #[test]
693 fn a_signed_unpin_keeps_the_history() {
694 let acme = org("acme");
695 let mut marker = OrgRequiredMarkerV1::first(acme.clone(), required(3, 'a')).unwrap();
696 assert_eq!(
697 marker.advance(&acme, unpinned(3, 'd')).unwrap(),
698 OrgRequiredMarkerAdvance::Appended
699 );
700 assert!(!marker.is_required());
701 assert_eq!(marker.entries.len(), 2, "the requirement stays on record");
702 assert_eq!(marker.entries[0], required(3, 'a'));
703
704 assert!(
705 marker.advance(&acme, required(3, 'a')).is_err(),
706 "the lifted requirement cannot be re-entered at its own sequence"
707 );
708 assert!(marker.advance(&acme, unpinned(2, 'd')).is_err());
709 assert_eq!(
710 marker.advance(&acme, required(4, 'b')).unwrap(),
711 OrgRequiredMarkerAdvance::Appended
712 );
713 assert!(marker.is_required());
714 assert_eq!(marker.entries.len(), 3);
715 }
716
717 #[test]
718 fn a_stored_marker_is_strict_and_canonical() {
719 let acme = org("acme");
720 let mut marker = OrgRequiredMarkerV1::first(acme.clone(), required(3, 'a')).unwrap();
721 marker.advance(&acme, unpinned(3, 'd')).unwrap();
722 let bytes = marker.canonical_bytes().unwrap();
723 assert_eq!(OrgRequiredMarkerV1::parse(&bytes).unwrap(), marker);
724 assert_ne!(
725 marker.digest().unwrap(),
726 OrgRequiredMarkerV1::first(acme, required(3, 'a'))
727 .unwrap()
728 .digest()
729 .unwrap()
730 );
731
732 let mut spaced = bytes.clone();
733 spaced.push(b' ');
734 assert!(OrgRequiredMarkerV1::parse(&spaced).is_err());
735 assert!(OrgRequiredMarkerV1::parse(b"{}").is_err());
736
737 let mut backwards = marker.clone();
739 backwards.entries.swap(0, 1);
740 assert!(backwards.validate().is_err());
741 assert!(serde_json::to_vec(&backwards).is_ok());
742 assert!(OrgRequiredMarkerV1::parse(&serde_json::to_vec(&backwards).unwrap()).is_err());
743 }
744
745 #[test]
746 fn a_full_history_refuses_to_advance() {
747 let acme = org("acme");
748 let mut marker = OrgRequiredMarkerV1::first(acme.clone(), required(1, 'a')).unwrap();
749 for sequence in 2..=MAX_ORG_REQUIRED_MARKER_ENTRIES as u64 {
750 marker.advance(&acme, required(sequence, 'a')).unwrap();
751 }
752 assert!(
753 marker
754 .advance(
755 &acme,
756 required(MAX_ORG_REQUIRED_MARKER_ENTRIES as u64 + 1, 'a')
757 )
758 .is_err()
759 );
760 }
761}