use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::ids;
use crate::types::{ContradictionResolution, SupportAdmissionMethod, SupportState};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportReceipt {
pub receipt_version: String,
pub export_receipt_id: String,
pub operation: String,
pub input_refs: Vec<String>,
pub input_digests: std::collections::HashMap<String, String>,
pub output_ref: Option<String>,
pub output_digest: Option<String>,
pub recorded_time: DateTime<Utc>,
pub attempt_id: String,
pub status: String,
pub degradation: Vec<String>,
pub digest_semantics: String,
}
impl ExportReceipt {
pub fn new(operation: &str, input_refs: Vec<String>, attempt_id: String) -> Self {
let input_refs_str: Vec<&str> = input_refs.iter().map(|s| s.as_str()).collect();
let export_receipt_id = ids::export_receipt_id(operation, &input_refs_str);
Self {
receipt_version: "ExportReceiptV1".to_string(),
export_receipt_id,
operation: operation.to_string(),
input_refs,
input_digests: std::collections::HashMap::new(),
output_ref: None,
output_digest: None,
recorded_time: Utc::now(),
attempt_id,
status: "pending".to_string(),
degradation: Vec::new(),
digest_semantics: "sha256-output-bytes".to_string(),
}
}
pub fn bind_output(&mut self, output_ref: String, output_digest: String) {
self.output_ref = Some(output_ref);
self.output_digest = Some(output_digest);
}
pub fn mark_success(&mut self) {
self.status = "success".to_string();
}
pub fn mark_failed(&mut self, reason: &str) {
self.status = format!("failed: {}", reason);
}
pub fn add_degradation(&mut self, item: &str) {
self.degradation.push(item.to_string());
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupportAdmissionReceipt {
pub receipt_version: String,
pub support_admission_receipt_id: String,
pub claim_id: String,
pub previous_support_judgment_ref: String,
pub new_support_judgment_ref: String,
pub method: SupportAdmissionMethod,
pub admitted_support_state: SupportState,
pub rationale: String,
pub operator_ref: Option<String>,
pub proof_payload: Option<crate::types::SupportProofPayload>,
pub proof_debt_waiver: Option<String>,
pub recorded_time: DateTime<Utc>,
}
impl SupportAdmissionReceipt {
#[allow(clippy::too_many_arguments)]
pub fn new(
claim_id: &str,
previous_ref: &str,
new_ref: &str,
method: SupportAdmissionMethod,
admitted_state: SupportState,
rationale: &str,
) -> Self {
Self {
receipt_version: "SupportAdmissionReceiptV1".to_string(),
support_admission_receipt_id: ids::support_admission_receipt_id(
claim_id,
previous_ref,
new_ref,
),
claim_id: claim_id.to_string(),
previous_support_judgment_ref: previous_ref.to_string(),
new_support_judgment_ref: new_ref.to_string(),
method,
admitted_support_state: admitted_state,
rationale: rationale.to_string(),
operator_ref: None,
proof_payload: None,
proof_debt_waiver: None,
recorded_time: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LedgerAppendReceipt {
pub receipt_version: String,
pub ledger_append_receipt_id: String,
pub ledger_ref: String,
pub sequence: u64,
pub previous_entry_digest: Option<String>,
pub entry_digest: String,
pub recorded_time: DateTime<Utc>,
}
impl LedgerAppendReceipt {
pub fn new(
ledger_ref: &str,
sequence: u64,
previous_entry_digest: Option<String>,
entry_digest: String,
) -> Self {
Self {
receipt_version: "LedgerAppendReceiptV1".to_string(),
ledger_append_receipt_id: ids::ledger_append_receipt_id(
ledger_ref,
sequence,
&entry_digest,
),
ledger_ref: ledger_ref.to_string(),
sequence,
previous_entry_digest,
entry_digest,
recorded_time: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContradictionResolutionReceipt {
pub receipt_version: String,
pub contradiction_resolution_receipt_id: String,
pub contradiction_id: String,
pub previous_status: String,
pub resolution: ContradictionResolution,
pub rationale: String,
pub affected_support_judgment_refs: Vec<String>,
pub supersession_receipt_refs: Vec<String>,
pub review_queue_refs: Vec<String>,
pub recorded_time: DateTime<Utc>,
}
impl ContradictionResolutionReceipt {
pub fn new(
contradiction_id: &str,
previous_status: &str,
resolution: ContradictionResolution,
rationale: &str,
) -> Self {
Self {
receipt_version: "ContradictionResolutionReceiptV1".to_string(),
contradiction_resolution_receipt_id: ids::contradiction_resolution_receipt_id(
contradiction_id,
previous_status,
&format!("{:?}", resolution),
),
contradiction_id: contradiction_id.to_string(),
previous_status: previous_status.to_string(),
resolution,
rationale: rationale.to_string(),
affected_support_judgment_refs: Vec::new(),
supersession_receipt_refs: Vec::new(),
review_queue_refs: Vec::new(),
recorded_time: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupersessionReceipt {
pub receipt_version: String,
pub supersession_receipt_id: String,
pub superseded_ref: String,
pub superseding_ref: String,
pub rationale: String,
pub recorded_time: DateTime<Utc>,
}
impl SupersessionReceipt {
pub fn new(superseded_ref: &str, superseding_ref: &str, rationale: &str) -> Self {
Self {
receipt_version: "SupersessionReceiptV1".to_string(),
supersession_receipt_id: ids::supersession_receipt_id(superseded_ref, superseding_ref),
superseded_ref: superseded_ref.to_string(),
superseding_ref: superseding_ref.to_string(),
rationale: rationale.to_string(),
recorded_time: Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn export_receipt_new_has_id() {
let receipt = ExportReceipt::new(
"bundle_export",
vec!["/path/to/bundle.json".to_string()],
"attempt-001".to_string(),
);
assert!(receipt.export_receipt_id.starts_with("xpt_"));
assert_eq!(receipt.operation, "bundle_export");
assert_eq!(receipt.status, "pending");
}
#[test]
fn export_receipt_bind_output() {
let mut receipt = ExportReceipt::new(
"testify_answer",
vec!["in.json".to_string()],
"attempt-001".to_string(),
);
receipt.bind_output("/path/to/out.json".to_string(), "abc123".to_string());
assert_eq!(receipt.output_ref, Some("/path/to/out.json".to_string()));
assert_eq!(receipt.output_digest, Some("abc123".to_string()));
}
#[test]
fn ledger_append_receipt_sequence() {
let receipt =
LedgerAppendReceipt::new("/ledger.jsonl", 1, None, "entryhash123".to_string());
assert_eq!(receipt.sequence, 1);
assert!(receipt.previous_entry_digest.is_none());
}
#[test]
fn supersession_receipt_new() {
let receipt = SupersessionReceipt::new("old-id", "new-id", "superseded by newer judgment");
assert!(receipt.supersession_receipt_id.starts_with("ssr_"));
assert_eq!(receipt.superseded_ref, "old-id");
assert_eq!(receipt.superseding_ref, "new-id");
}
}