1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::crypto::{hash, Hash, PublicKey, SecretKey, Sig};
11use crate::error::{Error, Result};
12use crate::event::{EventId, ResourceId};
13
14use super::hitl::Severity;
15use super::principal::PrincipalId;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct OutcomeAttestation {
20 action_event_id: EventId,
22 outcome: ActionOutcome,
24 evidence: Vec<Evidence>,
26 attestor: Attestor,
28 observed_at: i64,
30 signature: Sig,
32}
33
34impl OutcomeAttestation {
35 pub fn builder() -> OutcomeAttestationBuilder {
37 OutcomeAttestationBuilder::new()
38 }
39
40 pub fn action_event_id(&self) -> EventId {
42 self.action_event_id
43 }
44
45 pub fn outcome(&self) -> &ActionOutcome {
47 &self.outcome
48 }
49
50 pub fn evidence(&self) -> &[Evidence] {
52 &self.evidence
53 }
54
55 pub fn attestor(&self) -> &Attestor {
57 &self.attestor
58 }
59
60 pub fn observed_at(&self) -> i64 {
62 self.observed_at
63 }
64
65 pub fn signature(&self) -> &Sig {
67 &self.signature
68 }
69
70 pub fn canonical_bytes(&self) -> Vec<u8> {
72 let mut data = Vec::new();
73 data.extend_from_slice(self.action_event_id.0.as_bytes());
74
75 let outcome_json = serde_json::to_vec(&self.outcome).unwrap_or_default();
76 data.extend_from_slice(&outcome_json);
77
78 for evidence in &self.evidence {
79 let evidence_json = serde_json::to_vec(evidence).unwrap_or_default();
80 data.extend_from_slice(&evidence_json);
81 }
82
83 let attestor_json = serde_json::to_vec(&self.attestor).unwrap_or_default();
84 data.extend_from_slice(&attestor_json);
85
86 data.extend_from_slice(&self.observed_at.to_le_bytes());
87
88 data
89 }
90
91 pub fn verify_signature(&self, public_key: &PublicKey) -> Result<()> {
97 let message = self.canonical_bytes();
98 public_key.verify(&message, &self.signature)
99 }
100
101 pub fn verify_against_attestor(&self) -> Result<()> {
111 let public_key = self.attestor.public_key().ok_or_else(|| {
112 Error::invalid_input(
113 "attestor type does not carry a public key; \
114 use external verification for HumanObserver/CryptographicProof",
115 )
116 })?;
117 self.verify_signature(public_key)
118 }
119
120 pub fn is_evidence_sufficient(&self, severity: Severity) -> bool {
122 match severity {
123 Severity::Low => {
124 true
126 }
127 Severity::Medium => {
128 self.evidence.iter().any(|e| e.is_external())
130 }
131 Severity::High => {
132 let external_count = self.evidence.iter().filter(|e| e.is_external()).count();
134 external_count >= 2
135 }
136 Severity::Critical => {
137 let has_third_party = self
142 .evidence
143 .iter()
144 .any(|e| matches!(e, Evidence::ThirdPartyAttestation { .. }));
145
146 let has_human = matches!(self.attestor, Attestor::HumanObserver { .. });
148
149 let has_receipt = self
152 .evidence
153 .iter()
154 .any(|e| matches!(e, Evidence::Receipt { .. }));
155 let external_count = self.evidence.iter().filter(|e| e.is_external()).count();
156 let has_corroborated_receipt = has_receipt && external_count >= 2;
157
158 has_third_party || has_human || has_corroborated_receipt
159 }
160 }
161 }
162
163 pub fn is_self_attestation(&self) -> bool {
165 matches!(self.attestor, Attestor::SelfAttestation { .. })
166 }
167}
168
169#[derive(Debug, Default)]
171pub struct OutcomeAttestationBuilder {
172 action_event_id: Option<EventId>,
173 outcome: Option<ActionOutcome>,
174 evidence: Vec<Evidence>,
175 attestor: Option<Attestor>,
176 observed_at: Option<i64>,
177}
178
179impl OutcomeAttestationBuilder {
180 pub fn new() -> Self {
182 Self::default()
183 }
184
185 pub fn action_event_id(mut self, id: EventId) -> Self {
187 self.action_event_id = Some(id);
188 self
189 }
190
191 pub fn outcome(mut self, outcome: ActionOutcome) -> Self {
193 self.outcome = Some(outcome);
194 self
195 }
196
197 pub fn evidence(mut self, evidence: Evidence) -> Self {
199 self.evidence.push(evidence);
200 self
201 }
202
203 pub fn evidence_list(mut self, evidence: Vec<Evidence>) -> Self {
205 self.evidence = evidence;
206 self
207 }
208
209 pub fn attestor(mut self, attestor: Attestor) -> Self {
211 self.attestor = Some(attestor);
212 self
213 }
214
215 pub fn observed_at(mut self, timestamp: i64) -> Self {
217 self.observed_at = Some(timestamp);
218 self
219 }
220
221 pub fn observed_now(mut self) -> Self {
223 self.observed_at = Some(chrono::Utc::now().timestamp_millis());
224 self
225 }
226
227 pub fn sign(self, key: &SecretKey) -> Result<OutcomeAttestation> {
229 let action_event_id = self
230 .action_event_id
231 .ok_or_else(|| Error::invalid_input("action_event_id is required"))?;
232
233 let outcome = self
234 .outcome
235 .ok_or_else(|| Error::invalid_input("outcome is required"))?;
236
237 let attestor = self
238 .attestor
239 .ok_or_else(|| Error::invalid_input("attestor is required"))?;
240
241 let observed_at = self
242 .observed_at
243 .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
244
245 let mut attestation = OutcomeAttestation {
247 action_event_id,
248 outcome,
249 evidence: self.evidence,
250 attestor,
251 observed_at,
252 signature: Sig::empty(), };
254
255 let message = attestation.canonical_bytes();
257 attestation.signature = key.sign(&message);
258
259 Ok(attestation)
260 }
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(tag = "status", rename_all = "snake_case")]
266pub enum ActionOutcome {
267 Success {
269 result: serde_json::Value,
271 result_hash: Hash,
273 },
274 PartialSuccess {
276 completed: Vec<String>,
278 failed: Vec<String>,
280 result: serde_json::Value,
282 },
283 Failure {
285 error: String,
287 error_code: Option<String>,
289 recoverable: bool,
291 },
292 Pending {
294 expected_completion: Option<i64>,
296 },
297 RolledBack {
299 rollback_reason: String,
301 rollback_event_id: EventId,
303 },
304}
305
306impl ActionOutcome {
307 pub fn success(result: serde_json::Value) -> Self {
309 let result_hash = hash(result.to_string().as_bytes());
310 Self::Success {
311 result,
312 result_hash,
313 }
314 }
315
316 pub fn success_with_hash(result: serde_json::Value, result_hash: Hash) -> Self {
318 Self::Success {
319 result,
320 result_hash,
321 }
322 }
323
324 pub fn partial_success(
326 completed: Vec<String>,
327 failed: Vec<String>,
328 result: serde_json::Value,
329 ) -> Self {
330 Self::PartialSuccess {
331 completed,
332 failed,
333 result,
334 }
335 }
336
337 pub fn failure(error: impl Into<String>, recoverable: bool) -> Self {
339 Self::Failure {
340 error: error.into(),
341 error_code: None,
342 recoverable,
343 }
344 }
345
346 pub fn failure_with_code(
348 error: impl Into<String>,
349 error_code: impl Into<String>,
350 recoverable: bool,
351 ) -> Self {
352 Self::Failure {
353 error: error.into(),
354 error_code: Some(error_code.into()),
355 recoverable,
356 }
357 }
358
359 pub fn pending(expected_completion: Option<i64>) -> Self {
361 Self::Pending {
362 expected_completion,
363 }
364 }
365
366 pub fn rolled_back(reason: impl Into<String>, rollback_event_id: EventId) -> Self {
368 Self::RolledBack {
369 rollback_reason: reason.into(),
370 rollback_event_id,
371 }
372 }
373
374 pub fn is_success(&self) -> bool {
376 matches!(self, ActionOutcome::Success { .. })
377 }
378
379 pub fn is_failure(&self) -> bool {
381 matches!(self, ActionOutcome::Failure { .. })
382 }
383
384 pub fn is_pending(&self) -> bool {
386 matches!(self, ActionOutcome::Pending { .. })
387 }
388
389 pub fn is_final(&self) -> bool {
391 !matches!(self, ActionOutcome::Pending { .. })
392 }
393
394 pub fn is_recoverable(&self) -> bool {
396 match self {
397 ActionOutcome::Failure { recoverable, .. } => *recoverable,
398 _ => false,
399 }
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
405#[serde(tag = "type", rename_all = "snake_case")]
406pub enum Evidence {
407 DataHash {
409 resource: ResourceId,
411 hash: Hash,
413 size: u64,
415 },
416 ExternalConfirmation {
418 system: String,
420 confirmation_id: String,
422 timestamp: i64,
424 },
425 Receipt {
427 issuer: String,
429 receipt: Vec<u8>,
431 },
432 Visual {
434 hash: Hash,
436 description: String,
438 },
439 LogEntries {
441 source: String,
443 entries: Vec<String>,
445 hash: Hash,
447 },
448 ThirdPartyAttestation {
450 attestor: PublicKey,
452 attestation: Vec<u8>,
454 },
455}
456
457impl Evidence {
458 pub fn data_hash(resource: ResourceId, hash: Hash, size: u64) -> Self {
460 Self::DataHash {
461 resource,
462 hash,
463 size,
464 }
465 }
466
467 pub fn external_confirmation(
469 system: impl Into<String>,
470 confirmation_id: impl Into<String>,
471 timestamp: i64,
472 ) -> Self {
473 Self::ExternalConfirmation {
474 system: system.into(),
475 confirmation_id: confirmation_id.into(),
476 timestamp,
477 }
478 }
479
480 pub fn receipt(issuer: impl Into<String>, receipt: Vec<u8>) -> Self {
482 Self::Receipt {
483 issuer: issuer.into(),
484 receipt,
485 }
486 }
487
488 pub fn visual(hash: Hash, description: impl Into<String>) -> Self {
490 Self::Visual {
491 hash,
492 description: description.into(),
493 }
494 }
495
496 pub fn log_entries(source: impl Into<String>, entries: Vec<String>) -> Self {
498 let entries_json = serde_json::to_string(&entries).unwrap_or_default();
499 let hash = hash(entries_json.as_bytes());
500 Self::LogEntries {
501 source: source.into(),
502 entries,
503 hash,
504 }
505 }
506
507 pub fn third_party_attestation(attestor: PublicKey, attestation: Vec<u8>) -> Self {
509 Self::ThirdPartyAttestation {
510 attestor,
511 attestation,
512 }
513 }
514
515 pub fn is_external(&self) -> bool {
517 matches!(
518 self,
519 Evidence::ExternalConfirmation { .. }
520 | Evidence::Receipt { .. }
521 | Evidence::ThirdPartyAttestation { .. }
522 )
523 }
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(tag = "type", rename_all = "snake_case")]
529pub enum Attestor {
530 SelfAttestation {
532 agent: PublicKey,
534 },
535 ExecutionSystem {
537 system_id: String,
539 system_key: PublicKey,
541 },
542 Monitor {
544 monitor_id: String,
546 monitor_key: PublicKey,
548 },
549 HumanObserver {
551 principal: PrincipalId,
553 },
554 CryptographicProof {
556 proof_type: String,
558 },
559}
560
561impl Attestor {
562 pub fn self_attestation(agent: PublicKey) -> Self {
564 Self::SelfAttestation { agent }
565 }
566
567 pub fn execution_system(system_id: impl Into<String>, system_key: PublicKey) -> Self {
569 Self::ExecutionSystem {
570 system_id: system_id.into(),
571 system_key,
572 }
573 }
574
575 pub fn monitor(monitor_id: impl Into<String>, monitor_key: PublicKey) -> Self {
577 Self::Monitor {
578 monitor_id: monitor_id.into(),
579 monitor_key,
580 }
581 }
582
583 pub fn human_observer(principal: PrincipalId) -> Self {
585 Self::HumanObserver { principal }
586 }
587
588 pub fn cryptographic_proof(proof_type: impl Into<String>) -> Self {
590 Self::CryptographicProof {
591 proof_type: proof_type.into(),
592 }
593 }
594
595 pub fn public_key(&self) -> Option<&PublicKey> {
597 match self {
598 Attestor::SelfAttestation { agent } => Some(agent),
599 Attestor::ExecutionSystem { system_key, .. } => Some(system_key),
600 Attestor::Monitor { monitor_key, .. } => Some(monitor_key),
601 Attestor::HumanObserver { .. } => None,
602 Attestor::CryptographicProof { .. } => None,
603 }
604 }
605}
606
607#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
609pub struct IdempotencyKey {
610 agent: PublicKey,
612 action_type: String,
614 client_key: String,
616}
617
618impl IdempotencyKey {
619 pub fn new(
621 agent: PublicKey,
622 action_type: impl Into<String>,
623 client_key: impl Into<String>,
624 ) -> Self {
625 Self {
626 agent,
627 action_type: action_type.into(),
628 client_key: client_key.into(),
629 }
630 }
631
632 pub fn agent(&self) -> &PublicKey {
634 &self.agent
635 }
636
637 pub fn action_type(&self) -> &str {
639 &self.action_type
640 }
641
642 pub fn client_key(&self) -> &str {
644 &self.client_key
645 }
646
647 pub fn hash(&self) -> Hash {
649 let mut data = Vec::new();
650 data.extend_from_slice(&self.agent.as_bytes());
651 data.extend_from_slice(self.action_type.as_bytes());
652 data.extend_from_slice(self.client_key.as_bytes());
653 hash(&data)
654 }
655}
656
657impl std::fmt::Display for IdempotencyKey {
658 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
659 write!(
660 f,
661 "{}:{}:{}",
662 hex::encode(self.agent.as_bytes()),
663 self.action_type,
664 self.client_key
665 )
666 }
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct IdempotencyRecord {
672 key: IdempotencyKey,
674 original_event_id: EventId,
676 outcome: ActionOutcome,
678 created_at: i64,
680 expires_at: i64,
682}
683
684impl IdempotencyRecord {
685 pub fn new(
687 key: IdempotencyKey,
688 original_event_id: EventId,
689 outcome: ActionOutcome,
690 ttl_ms: i64,
691 ) -> Self {
692 let now = chrono::Utc::now().timestamp_millis();
693 Self {
694 key,
695 original_event_id,
696 outcome,
697 created_at: now,
698 expires_at: now + ttl_ms,
699 }
700 }
701
702 pub fn key(&self) -> &IdempotencyKey {
704 &self.key
705 }
706
707 pub fn original_event_id(&self) -> EventId {
709 self.original_event_id
710 }
711
712 pub fn outcome(&self) -> &ActionOutcome {
714 &self.outcome
715 }
716
717 pub fn created_at(&self) -> i64 {
719 self.created_at
720 }
721
722 pub fn expires_at(&self) -> i64 {
724 self.expires_at
725 }
726
727 pub fn is_expired(&self) -> bool {
729 chrono::Utc::now().timestamp_millis() > self.expires_at
730 }
731
732 pub fn is_valid(&self) -> bool {
734 !self.is_expired()
735 }
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct OutcomeDispute {
741 disputed_attestation_event_id: EventId,
743 disputant: Attestor,
745 reason: String,
747 counter_evidence: Vec<Evidence>,
749 filed_at: i64,
751 status: DisputeStatus,
753}
754
755impl OutcomeDispute {
756 pub fn new(
758 disputed_attestation_event_id: EventId,
759 disputant: Attestor,
760 reason: impl Into<String>,
761 ) -> Self {
762 Self {
763 disputed_attestation_event_id,
764 disputant,
765 reason: reason.into(),
766 counter_evidence: Vec::new(),
767 filed_at: chrono::Utc::now().timestamp_millis(),
768 status: DisputeStatus::Pending,
769 }
770 }
771
772 pub fn with_evidence(mut self, evidence: Evidence) -> Self {
774 self.counter_evidence.push(evidence);
775 self
776 }
777
778 pub fn disputed_attestation_event_id(&self) -> EventId {
780 self.disputed_attestation_event_id
781 }
782
783 pub fn disputant(&self) -> &Attestor {
785 &self.disputant
786 }
787
788 pub fn reason(&self) -> &str {
790 &self.reason
791 }
792
793 pub fn counter_evidence(&self) -> &[Evidence] {
795 &self.counter_evidence
796 }
797
798 pub fn filed_at(&self) -> i64 {
800 self.filed_at
801 }
802
803 pub fn status(&self) -> &DisputeStatus {
805 &self.status
806 }
807
808 pub fn set_status(&mut self, status: DisputeStatus) {
810 self.status = status;
811 }
812}
813
814#[derive(Debug, Clone, Serialize, Deserialize)]
816#[serde(tag = "status", rename_all = "snake_case")]
817pub enum DisputeStatus {
818 Pending,
820 UnderReview {
822 reviewer: PrincipalId,
824 started_at: i64,
826 },
827 RejectedOriginalStands {
829 reason: String,
831 resolution_event_id: EventId,
833 },
834 UpheldOriginalInvalidated {
836 reason: String,
838 corrected_outcome: Option<Box<ActionOutcome>>,
840 resolution_event_id: EventId,
842 },
843}
844
845impl DisputeStatus {
846 pub fn is_pending(&self) -> bool {
848 matches!(
849 self,
850 DisputeStatus::Pending | DisputeStatus::UnderReview { .. }
851 )
852 }
853
854 pub fn is_resolved(&self) -> bool {
856 !self.is_pending()
857 }
858}
859
860#[derive(Debug, Default)]
866pub struct IdempotencyStore {
867 records: HashMap<Hash, IdempotencyRecord>,
868}
869
870impl IdempotencyStore {
871 pub fn new() -> Self {
873 Self::default()
874 }
875
876 pub fn insert(&mut self, record: IdempotencyRecord) {
878 let key_hash = record.key().hash();
879 self.records.insert(key_hash, record);
880 }
881
882 pub fn lookup(&self, key: &IdempotencyKey) -> Option<&IdempotencyRecord> {
886 self.records.get(&key.hash()).filter(|r| !r.is_expired())
887 }
888
889 pub fn cleanup(&mut self) -> usize {
891 let before = self.records.len();
892 self.records.retain(|_, r| !r.is_expired());
893 before - self.records.len()
894 }
895
896 pub fn len(&self) -> usize {
898 self.records.len()
899 }
900
901 pub fn is_empty(&self) -> bool {
903 self.records.is_empty()
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910 use crate::event::ResourceKind;
911
912 fn test_event_id() -> EventId {
913 EventId(hash(b"test-event"))
914 }
915
916 fn test_key() -> SecretKey {
917 SecretKey::generate()
918 }
919
920 fn test_resource_id() -> ResourceId {
921 ResourceId::new(ResourceKind::File, "/tmp/test.txt")
922 }
923
924 #[test]
927 fn outcome_success() {
928 let outcome = ActionOutcome::success(serde_json::json!({"result": "ok"}));
929 assert!(outcome.is_success());
930 assert!(!outcome.is_failure());
931 assert!(outcome.is_final());
932 }
933
934 #[test]
935 fn outcome_failure() {
936 let outcome = ActionOutcome::failure("Something went wrong", true);
937 assert!(outcome.is_failure());
938 assert!(outcome.is_recoverable());
939
940 let non_recoverable = ActionOutcome::failure("Fatal error", false);
941 assert!(!non_recoverable.is_recoverable());
942 }
943
944 #[test]
945 fn outcome_partial_success() {
946 let outcome = ActionOutcome::partial_success(
947 vec!["step1".to_string(), "step2".to_string()],
948 vec!["step3".to_string()],
949 serde_json::json!({}),
950 );
951 assert!(!outcome.is_success());
952 assert!(!outcome.is_failure());
953 }
954
955 #[test]
956 fn outcome_pending() {
957 let outcome = ActionOutcome::pending(Some(chrono::Utc::now().timestamp_millis() + 60000));
958 assert!(outcome.is_pending());
959 assert!(!outcome.is_final());
960 }
961
962 #[test]
963 fn outcome_rolled_back() {
964 let outcome = ActionOutcome::rolled_back("User cancelled", test_event_id());
965 assert!(outcome.is_final());
966 }
967
968 #[test]
971 fn evidence_data_hash() {
972 let evidence = Evidence::data_hash(test_resource_id(), hash(b"data"), 1024);
973 assert!(!evidence.is_external());
974 }
975
976 #[test]
977 fn evidence_external_confirmation() {
978 let evidence = Evidence::external_confirmation("github", "pr-123", 1000);
979 assert!(evidence.is_external());
980 }
981
982 #[test]
983 fn evidence_receipt() {
984 let evidence = Evidence::receipt("blockchain", vec![1, 2, 3, 4]);
985 assert!(evidence.is_external());
986 }
987
988 #[test]
989 fn evidence_visual() {
990 let evidence = Evidence::visual(hash(b"screenshot"), "Shows successful deployment");
991 assert!(!evidence.is_external());
992 }
993
994 #[test]
995 fn evidence_log_entries() {
996 let evidence = Evidence::log_entries("server.log", vec!["INFO: Started".to_string()]);
997 assert!(!evidence.is_external());
998 }
999
1000 #[test]
1001 fn evidence_third_party() {
1002 let key = test_key();
1003 let evidence = Evidence::third_party_attestation(key.public_key(), vec![1, 2, 3]);
1004 assert!(evidence.is_external());
1005 }
1006
1007 #[test]
1010 fn attestor_self() {
1011 let key = test_key();
1012 let attestor = Attestor::self_attestation(key.public_key());
1013 assert!(attestor.public_key().is_some());
1014 }
1015
1016 #[test]
1017 fn attestor_execution_system() {
1018 let key = test_key();
1019 let attestor = Attestor::execution_system("docker-runtime", key.public_key());
1020 assert!(attestor.public_key().is_some());
1021 }
1022
1023 #[test]
1024 fn attestor_human() {
1025 let principal = PrincipalId::user("user@example.com").unwrap();
1026 let attestor = Attestor::human_observer(principal);
1027 assert!(attestor.public_key().is_none());
1028 }
1029
1030 #[test]
1033 fn attestation_build_and_sign() {
1034 let key = test_key();
1035 let attestation = OutcomeAttestation::builder()
1036 .action_event_id(test_event_id())
1037 .outcome(ActionOutcome::success(serde_json::json!({})))
1038 .attestor(Attestor::self_attestation(key.public_key()))
1039 .observed_now()
1040 .sign(&key)
1041 .unwrap();
1042
1043 assert!(attestation.verify_signature(&key.public_key()).is_ok());
1044 }
1045
1046 #[test]
1047 fn attestation_requires_action_event_id() {
1048 let key = test_key();
1049 let result = OutcomeAttestation::builder()
1050 .outcome(ActionOutcome::success(serde_json::json!({})))
1051 .attestor(Attestor::self_attestation(key.public_key()))
1052 .sign(&key);
1053 assert!(result.is_err());
1054 }
1055
1056 #[test]
1057 fn attestation_requires_outcome() {
1058 let key = test_key();
1059 let result = OutcomeAttestation::builder()
1060 .action_event_id(test_event_id())
1061 .attestor(Attestor::self_attestation(key.public_key()))
1062 .sign(&key);
1063 assert!(result.is_err());
1064 }
1065
1066 #[test]
1067 fn attestation_requires_attestor() {
1068 let key = test_key();
1069 let result = OutcomeAttestation::builder()
1070 .action_event_id(test_event_id())
1071 .outcome(ActionOutcome::success(serde_json::json!({})))
1072 .sign(&key);
1073 assert!(result.is_err());
1074 }
1075
1076 #[test]
1079 fn evidence_sufficiency_low() {
1080 let key = test_key();
1081 let attestation = OutcomeAttestation::builder()
1082 .action_event_id(test_event_id())
1083 .outcome(ActionOutcome::success(serde_json::json!({})))
1084 .attestor(Attestor::self_attestation(key.public_key()))
1085 .sign(&key)
1086 .unwrap();
1087
1088 assert!(attestation.is_evidence_sufficient(Severity::Low));
1090 }
1091
1092 #[test]
1093 fn evidence_sufficiency_medium() {
1094 let key = test_key();
1095
1096 let attestation = OutcomeAttestation::builder()
1098 .action_event_id(test_event_id())
1099 .outcome(ActionOutcome::success(serde_json::json!({})))
1100 .attestor(Attestor::self_attestation(key.public_key()))
1101 .sign(&key)
1102 .unwrap();
1103 assert!(!attestation.is_evidence_sufficient(Severity::Medium));
1104
1105 let attestation = OutcomeAttestation::builder()
1107 .action_event_id(test_event_id())
1108 .outcome(ActionOutcome::success(serde_json::json!({})))
1109 .attestor(Attestor::self_attestation(key.public_key()))
1110 .evidence(Evidence::external_confirmation("ci", "build-123", 1000))
1111 .sign(&key)
1112 .unwrap();
1113 assert!(attestation.is_evidence_sufficient(Severity::Medium));
1114 }
1115
1116 #[test]
1117 fn evidence_sufficiency_high() {
1118 let key = test_key();
1119
1120 let attestation = OutcomeAttestation::builder()
1122 .action_event_id(test_event_id())
1123 .outcome(ActionOutcome::success(serde_json::json!({})))
1124 .attestor(Attestor::self_attestation(key.public_key()))
1125 .evidence(Evidence::external_confirmation("ci", "build-123", 1000))
1126 .sign(&key)
1127 .unwrap();
1128 assert!(!attestation.is_evidence_sufficient(Severity::High));
1129
1130 let attestation = OutcomeAttestation::builder()
1132 .action_event_id(test_event_id())
1133 .outcome(ActionOutcome::success(serde_json::json!({})))
1134 .attestor(Attestor::self_attestation(key.public_key()))
1135 .evidence(Evidence::external_confirmation("ci", "build-123", 1000))
1136 .evidence(Evidence::receipt("notary", vec![1, 2, 3]))
1137 .sign(&key)
1138 .unwrap();
1139 assert!(attestation.is_evidence_sufficient(Severity::High));
1140 }
1141
1142 #[test]
1143 fn evidence_sufficiency_critical() {
1144 let key = test_key();
1145
1146 let attestation = OutcomeAttestation::builder()
1148 .action_event_id(test_event_id())
1149 .outcome(ActionOutcome::success(serde_json::json!({})))
1150 .attestor(Attestor::self_attestation(key.public_key()))
1151 .evidence(Evidence::external_confirmation("ci", "build-123", 1000))
1152 .sign(&key)
1153 .unwrap();
1154 assert!(!attestation.is_evidence_sufficient(Severity::Critical));
1155
1156 let attestation = OutcomeAttestation::builder()
1158 .action_event_id(test_event_id())
1159 .outcome(ActionOutcome::success(serde_json::json!({})))
1160 .attestor(Attestor::self_attestation(key.public_key()))
1161 .evidence(Evidence::third_party_attestation(
1162 key.public_key(),
1163 vec![1, 2, 3],
1164 ))
1165 .sign(&key)
1166 .unwrap();
1167 assert!(attestation.is_evidence_sufficient(Severity::Critical));
1168
1169 let principal = PrincipalId::user("admin@example.com").unwrap();
1171 let attestation = OutcomeAttestation::builder()
1172 .action_event_id(test_event_id())
1173 .outcome(ActionOutcome::success(serde_json::json!({})))
1174 .attestor(Attestor::human_observer(principal))
1175 .sign(&key)
1176 .unwrap();
1177 assert!(attestation.is_evidence_sufficient(Severity::Critical));
1178 }
1179
1180 #[test]
1183 fn verify_against_attestor_rejects_key_mismatch() {
1184 let real_key = test_key();
1187 let fake_key = test_key();
1188
1189 let attestation = OutcomeAttestation::builder()
1190 .action_event_id(test_event_id())
1191 .outcome(ActionOutcome::success(serde_json::json!({})))
1192 .attestor(Attestor::self_attestation(fake_key.public_key())) .observed_now()
1194 .sign(&real_key) .unwrap();
1196
1197 assert!(attestation.verify_signature(&real_key.public_key()).is_ok());
1199
1200 assert!(attestation.verify_against_attestor().is_err());
1202 }
1203
1204 #[test]
1205 fn verify_against_attestor_accepts_matching_key() {
1206 let key = test_key();
1207
1208 let attestation = OutcomeAttestation::builder()
1209 .action_event_id(test_event_id())
1210 .outcome(ActionOutcome::success(serde_json::json!({})))
1211 .attestor(Attestor::self_attestation(key.public_key()))
1212 .observed_now()
1213 .sign(&key)
1214 .unwrap();
1215
1216 assert!(attestation.verify_against_attestor().is_ok());
1217 }
1218
1219 #[test]
1220 fn verify_against_attestor_for_human_observer_returns_error() {
1221 let key = test_key();
1224 let principal = PrincipalId::user("admin@example.com").unwrap();
1225
1226 let attestation = OutcomeAttestation::builder()
1227 .action_event_id(test_event_id())
1228 .outcome(ActionOutcome::success(serde_json::json!({})))
1229 .attestor(Attestor::human_observer(principal))
1230 .observed_now()
1231 .sign(&key)
1232 .unwrap();
1233
1234 let result = attestation.verify_against_attestor();
1235 assert!(result.is_err());
1236 }
1237
1238 #[test]
1239 fn verify_against_attestor_for_execution_system() {
1240 let system_key = test_key();
1241
1242 let attestation = OutcomeAttestation::builder()
1243 .action_event_id(test_event_id())
1244 .outcome(ActionOutcome::success(serde_json::json!({})))
1245 .attestor(Attestor::execution_system(
1246 "docker",
1247 system_key.public_key(),
1248 ))
1249 .observed_now()
1250 .sign(&system_key)
1251 .unwrap();
1252
1253 assert!(attestation.verify_against_attestor().is_ok());
1254 }
1255
1256 #[test]
1257 fn verify_against_attestor_for_monitor() {
1258 let monitor_key = test_key();
1259
1260 let attestation = OutcomeAttestation::builder()
1261 .action_event_id(test_event_id())
1262 .outcome(ActionOutcome::success(serde_json::json!({})))
1263 .attestor(Attestor::monitor("prometheus", monitor_key.public_key()))
1264 .observed_now()
1265 .sign(&monitor_key)
1266 .unwrap();
1267
1268 assert!(attestation.verify_against_attestor().is_ok());
1269 }
1270
1271 #[test]
1272 fn verify_against_attestor_for_cryptographic_proof_returns_error() {
1273 let key = test_key();
1275
1276 let attestation = OutcomeAttestation::builder()
1277 .action_event_id(test_event_id())
1278 .outcome(ActionOutcome::success(serde_json::json!({})))
1279 .attestor(Attestor::cryptographic_proof("blockchain-anchor"))
1280 .observed_now()
1281 .sign(&key)
1282 .unwrap();
1283
1284 assert!(attestation.verify_against_attestor().is_err());
1285 }
1286
1287 #[test]
1290 fn idempotency_key_hash() {
1291 let key = test_key();
1292 let idem_key1 = IdempotencyKey::new(key.public_key(), "file_write", "request-123");
1293 let idem_key2 = IdempotencyKey::new(key.public_key(), "file_write", "request-123");
1294 assert_eq!(idem_key1.hash(), idem_key2.hash());
1295
1296 let idem_key3 = IdempotencyKey::new(key.public_key(), "file_write", "request-456");
1297 assert_ne!(idem_key1.hash(), idem_key3.hash());
1298 }
1299
1300 #[test]
1301 fn idempotency_key_display() {
1302 let key = test_key();
1303 let idem_key = IdempotencyKey::new(key.public_key(), "file_write", "request-123");
1304 let display = format!("{}", idem_key);
1305 assert!(display.contains("file_write"));
1306 assert!(display.contains("request-123"));
1307 }
1308
1309 #[test]
1312 fn idempotency_record_valid() {
1313 let key = test_key();
1314 let idem_key = IdempotencyKey::new(key.public_key(), "file_write", "request-123");
1315 let record = IdempotencyRecord::new(
1316 idem_key,
1317 test_event_id(),
1318 ActionOutcome::success(serde_json::json!({})),
1319 60000, );
1321
1322 assert!(record.is_valid());
1323 assert!(!record.is_expired());
1324 }
1325
1326 #[test]
1327 fn idempotency_record_expired() {
1328 let key = test_key();
1329 let idem_key = IdempotencyKey::new(key.public_key(), "file_write", "request-123");
1330 let record = IdempotencyRecord::new(
1331 idem_key,
1332 test_event_id(),
1333 ActionOutcome::success(serde_json::json!({})),
1334 -1, );
1336
1337 assert!(!record.is_valid());
1338 assert!(record.is_expired());
1339 }
1340
1341 #[test]
1344 fn idempotency_store_insert_and_lookup() {
1345 let mut store = IdempotencyStore::new();
1346 let key = test_key();
1347 let idem_key = IdempotencyKey::new(key.public_key(), "write", "req-1");
1348 let record = IdempotencyRecord::new(
1349 idem_key.clone(),
1350 test_event_id(),
1351 ActionOutcome::success(serde_json::json!({})),
1352 60000,
1353 );
1354
1355 let expected_event_id = record.original_event_id();
1356 store.insert(record);
1357 let found = store.lookup(&idem_key);
1358 assert!(found.is_some());
1359 assert_eq!(found.unwrap().original_event_id(), expected_event_id);
1360 }
1361
1362 #[test]
1363 fn idempotency_store_returns_none_for_unknown() {
1364 let store = IdempotencyStore::new();
1365 let key = test_key();
1366 let idem_key = IdempotencyKey::new(key.public_key(), "write", "unknown");
1367 assert!(store.lookup(&idem_key).is_none());
1368 }
1369
1370 #[test]
1371 fn idempotency_store_expired_records_not_returned() {
1372 let mut store = IdempotencyStore::new();
1373 let key = test_key();
1374 let idem_key = IdempotencyKey::new(key.public_key(), "write", "req-1");
1375 let record = IdempotencyRecord::new(
1376 idem_key.clone(),
1377 test_event_id(),
1378 ActionOutcome::success(serde_json::json!({})),
1379 -1, );
1381
1382 store.insert(record);
1383 assert!(store.lookup(&idem_key).is_none()); }
1385
1386 #[test]
1387 fn idempotency_store_cleanup_removes_expired() {
1388 let mut store = IdempotencyStore::new();
1389 let key = test_key();
1390
1391 for i in 0..3 {
1393 let idem_key = IdempotencyKey::new(key.public_key(), "write", format!("expired-{}", i));
1394 store.insert(IdempotencyRecord::new(
1395 idem_key,
1396 test_event_id(),
1397 ActionOutcome::success(serde_json::json!({})),
1398 -1, ));
1400 }
1401
1402 for i in 0..2 {
1404 let idem_key = IdempotencyKey::new(key.public_key(), "write", format!("valid-{}", i));
1405 store.insert(IdempotencyRecord::new(
1406 idem_key,
1407 test_event_id(),
1408 ActionOutcome::success(serde_json::json!({})),
1409 60000, ));
1411 }
1412
1413 assert_eq!(store.len(), 5);
1414 let removed = store.cleanup();
1415 assert_eq!(removed, 3);
1416 assert_eq!(store.len(), 2);
1417 }
1418
1419 #[test]
1420 fn idempotency_store_overwrite_existing() {
1421 let mut store = IdempotencyStore::new();
1422 let key = test_key();
1423 let idem_key = IdempotencyKey::new(key.public_key(), "write", "req-1");
1424
1425 let record1 = IdempotencyRecord::new(
1426 idem_key.clone(),
1427 test_event_id(),
1428 ActionOutcome::success(serde_json::json!({"version": 1})),
1429 60000,
1430 );
1431 store.insert(record1);
1432
1433 let event2 = EventId(hash(b"second-event"));
1434 let record2 = IdempotencyRecord::new(
1435 idem_key.clone(),
1436 event2,
1437 ActionOutcome::success(serde_json::json!({"version": 2})),
1438 60000,
1439 );
1440 store.insert(record2);
1441
1442 assert_eq!(store.len(), 1);
1443 let found = store.lookup(&idem_key).unwrap();
1444 assert_eq!(found.original_event_id(), event2);
1445 }
1446
1447 #[test]
1448 fn idempotency_store_is_empty() {
1449 let store = IdempotencyStore::new();
1450 assert!(store.is_empty());
1451 assert_eq!(store.len(), 0);
1452 }
1453
1454 #[test]
1457 fn dispute_creation() {
1458 let key = test_key();
1459 let dispute = OutcomeDispute::new(
1460 test_event_id(),
1461 Attestor::self_attestation(key.public_key()),
1462 "Outcome was not as described",
1463 );
1464
1465 assert!(dispute.status().is_pending());
1466 assert!(!dispute.status().is_resolved());
1467 }
1468
1469 #[test]
1470 fn dispute_with_evidence() {
1471 let key = test_key();
1472 let dispute = OutcomeDispute::new(
1473 test_event_id(),
1474 Attestor::self_attestation(key.public_key()),
1475 "Incorrect outcome",
1476 )
1477 .with_evidence(Evidence::log_entries(
1478 "server.log",
1479 vec!["ERROR: Failed".to_string()],
1480 ));
1481
1482 assert_eq!(dispute.counter_evidence().len(), 1);
1483 }
1484
1485 #[test]
1486 fn dispute_status_transitions() {
1487 let principal = PrincipalId::user("reviewer@example.com").unwrap();
1488
1489 let pending = DisputeStatus::Pending;
1490 assert!(pending.is_pending());
1491
1492 let under_review = DisputeStatus::UnderReview {
1493 reviewer: principal.clone(),
1494 started_at: chrono::Utc::now().timestamp_millis(),
1495 };
1496 assert!(under_review.is_pending());
1497
1498 let rejected = DisputeStatus::RejectedOriginalStands {
1499 reason: "Evidence insufficient".to_string(),
1500 resolution_event_id: test_event_id(),
1501 };
1502 assert!(rejected.is_resolved());
1503
1504 let upheld = DisputeStatus::UpheldOriginalInvalidated {
1505 reason: "Clear evidence of error".to_string(),
1506 corrected_outcome: Some(Box::new(ActionOutcome::failure("Actual failure", false))),
1507 resolution_event_id: test_event_id(),
1508 };
1509 assert!(upheld.is_resolved());
1510 }
1511
1512 #[test]
1515 fn evidence_receipt_alone_insufficient_for_critical() {
1516 let key = test_key();
1517 let attestation = OutcomeAttestation::builder()
1518 .action_event_id(test_event_id())
1519 .outcome(ActionOutcome::success(serde_json::json!({})))
1520 .attestor(Attestor::self_attestation(key.public_key()))
1521 .evidence(Evidence::receipt("self-system", vec![1, 2, 3]))
1522 .sign(&key)
1523 .unwrap();
1524
1525 assert!(!attestation.is_evidence_sufficient(Severity::Critical));
1526 }
1527
1528 #[test]
1529 fn evidence_third_party_attestation_satisfies_critical() {
1530 let key = test_key();
1531 let third_party_key = SecretKey::generate();
1532 let attestation = OutcomeAttestation::builder()
1533 .action_event_id(test_event_id())
1534 .outcome(ActionOutcome::success(serde_json::json!({})))
1535 .attestor(Attestor::self_attestation(key.public_key()))
1536 .evidence(Evidence::third_party_attestation(
1537 third_party_key.public_key(),
1538 vec![1, 2, 3],
1539 ))
1540 .sign(&key)
1541 .unwrap();
1542
1543 assert!(attestation.is_evidence_sufficient(Severity::Critical));
1544 }
1545
1546 #[test]
1547 fn evidence_human_observer_satisfies_critical() {
1548 let key = test_key();
1549 let principal = PrincipalId::user("admin@example.com").unwrap();
1550 let attestation = OutcomeAttestation::builder()
1551 .action_event_id(test_event_id())
1552 .outcome(ActionOutcome::success(serde_json::json!({})))
1553 .attestor(Attestor::human_observer(principal))
1554 .sign(&key)
1555 .unwrap();
1556
1557 assert!(attestation.is_evidence_sufficient(Severity::Critical));
1558 }
1559
1560 #[test]
1561 fn evidence_receipt_plus_external_confirmation_satisfies_critical() {
1562 let key = test_key();
1563 let attestation = OutcomeAttestation::builder()
1564 .action_event_id(test_event_id())
1565 .outcome(ActionOutcome::success(serde_json::json!({})))
1566 .attestor(Attestor::self_attestation(key.public_key()))
1567 .evidence(Evidence::receipt("notary-service", vec![1, 2, 3]))
1568 .evidence(Evidence::external_confirmation(
1569 "monitoring",
1570 "check-456",
1571 chrono::Utc::now().timestamp_millis(),
1572 ))
1573 .sign(&key)
1574 .unwrap();
1575
1576 assert!(attestation.is_evidence_sufficient(Severity::Critical));
1577 }
1578
1579 #[test]
1580 fn evidence_external_confirmation_alone_insufficient_for_critical() {
1581 let key = test_key();
1582 let attestation = OutcomeAttestation::builder()
1583 .action_event_id(test_event_id())
1584 .outcome(ActionOutcome::success(serde_json::json!({})))
1585 .attestor(Attestor::self_attestation(key.public_key()))
1586 .evidence(Evidence::external_confirmation(
1587 "monitoring",
1588 "check-789",
1589 chrono::Utc::now().timestamp_millis(),
1590 ))
1591 .sign(&key)
1592 .unwrap();
1593
1594 assert!(!attestation.is_evidence_sufficient(Severity::Critical));
1597 }
1598
1599 #[test]
1600 fn evidence_no_evidence_insufficient_for_critical() {
1601 let key = test_key();
1602 let attestation = OutcomeAttestation::builder()
1603 .action_event_id(test_event_id())
1604 .outcome(ActionOutcome::success(serde_json::json!({})))
1605 .attestor(Attestor::self_attestation(key.public_key()))
1606 .sign(&key)
1607 .unwrap();
1608
1609 assert!(!attestation.is_evidence_sufficient(Severity::Critical));
1610 }
1611
1612 #[test]
1613 fn evidence_low_severity_always_sufficient() {
1614 let key = test_key();
1615 let attestation = OutcomeAttestation::builder()
1616 .action_event_id(test_event_id())
1617 .outcome(ActionOutcome::success(serde_json::json!({})))
1618 .attestor(Attestor::self_attestation(key.public_key()))
1619 .sign(&key)
1620 .unwrap();
1621
1622 assert!(attestation.is_evidence_sufficient(Severity::Low));
1624 }
1625
1626 #[test]
1627 fn evidence_medium_requires_external() {
1628 let key = test_key();
1629
1630 let attestation = OutcomeAttestation::builder()
1632 .action_event_id(test_event_id())
1633 .outcome(ActionOutcome::success(serde_json::json!({})))
1634 .attestor(Attestor::self_attestation(key.public_key()))
1635 .sign(&key)
1636 .unwrap();
1637 assert!(!attestation.is_evidence_sufficient(Severity::Medium));
1638
1639 let attestation2 = OutcomeAttestation::builder()
1641 .action_event_id(test_event_id())
1642 .outcome(ActionOutcome::success(serde_json::json!({})))
1643 .attestor(Attestor::self_attestation(key.public_key()))
1644 .evidence(Evidence::external_confirmation(
1645 "ci",
1646 "run-123",
1647 chrono::Utc::now().timestamp_millis(),
1648 ))
1649 .sign(&key)
1650 .unwrap();
1651 assert!(attestation2.is_evidence_sufficient(Severity::Medium));
1652 }
1653}