pub struct TransactionReceipt { /* private fields */ }Expand description
Evidence describing the terminal result of a transaction attempt.
Implementations§
Source§impl TransactionReceipt
impl TransactionReceipt
Sourcepub fn disposition(&self) -> TransactionDisposition
pub fn disposition(&self) -> TransactionDisposition
Returns the terminal disposition.
Examples found in repository?
46fn main() -> Result<(), Box<dyn std::error::Error>> {
47 let base = temp_root("eggup-custom-validator");
48 let inputs = base.join("inputs");
49 let root = base.join("install");
50 fs::create_dir_all(&inputs)?;
51 fs::create_dir_all(root.join("bin"))?;
52 fs::write(inputs.join("app"), b"release-marker-42")?;
53 let digest = eggup_core::hash_file(&inputs.join("app"))?;
54 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
55 .with_integrity(IntegrityRequirement::Sha256(digest));
56 let plan = InstallPlan::new(
57 ProductId::new("demo")?,
58 ReleaseId::new("3.0.0")?,
59 &root,
60 ArtifactSet::single(member)?,
61 )?;
62 let validator = ContainsMarker {
63 member: MemberId::new("main")?,
64 marker: b"marker-42".to_vec(),
65 };
66 let receipt = plan
67 .prepare()?
68 .verify_integrity()?
69 .validate(&validator)?
70 .commit(CommitOwnership::new(
71 &ExistingAsOwnedVerifier,
72 AbsentPolicy::AllowCreate,
73 ))?;
74 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
75 let _ = fs::remove_dir_all(&base);
76 println!("custom validator commit: {:?}", receipt.disposition());
77 Ok(())
78}More examples
30fn main() -> Result<(), Box<dyn std::error::Error>> {
31 let base = temp_root("eggup-quickstart");
32 let (inputs, root) = (base.join("inputs"), base.join("install"));
33 fs::create_dir_all(inputs.join("bin"))?;
34 fs::create_dir_all(root.join("bin"))?;
35 fs::write(inputs.join("app"), b"new-app")?;
36
37 // 1. Describe one member, with the SHA-256 you expect to verify.
38 let source = inputs.join("app");
39 let digest = eggup_core::hash_file(&source)?;
40 let member = ArtifactMember::new(MemberId::new("main")?, source, "bin/app")?
41 .with_integrity(IntegrityRequirement::Sha256(digest));
42
43 // 2. Name the product, release, install root, and artifact set.
44 let plan = InstallPlan::new(
45 ProductId::new("demo")?,
46 ReleaseId::new("1.0.0")?,
47 &root,
48 ArtifactSet::single(member)?,
49 )?;
50
51 // 3. Stage, verify, validate, then commit with proven ownership.
52 let receipt = plan
53 .prepare()?
54 .verify_integrity()?
55 .validate(&AllValidators::new())?
56 .commit(CommitOwnership::new(
57 &ExistingAsOwnedVerifier,
58 AbsentPolicy::AllowCreate,
59 ))?;
60
61 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
62 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
63 let _ = fs::remove_dir_all(&base);
64 println!("committed: {:?}", receipt.disposition());
65 Ok(())
66}24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-ownership");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("app"), b"new-app")?;
31 fs::write(root.join("bin/app"), b"old-app")?;
32 let new_digest = eggup_core::hash_file(&inputs.join("app"))?;
33 let old_digest = eggup_core::hash_file(&root.join("bin/app"))?;
34 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
35 .with_integrity(IntegrityRequirement::Sha256(new_digest));
36 let plan = InstallPlan::new(
37 ProductId::new("demo")?,
38 ReleaseId::new("4.0.0")?,
39 &root,
40 ArtifactSet::single(member)?,
41 )?;
42 // Prove the live file is the expected old deployment; deny creation of
43 // absent destinations (replacement-only policy).
44 let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main")?, old_digest)]);
45 let receipt = plan
46 .prepare()?
47 .verify_integrity()?
48 .validate(&AllValidators::new())?
49 .commit(CommitOwnership::new(&verifier, AbsentPolicy::DenyCreate))?;
50 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
51 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
52 let _ = fs::remove_dir_all(&base);
53 println!("ownership-verified commit: {:?}", receipt.disposition());
54 Ok(())
55}24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-multi-member");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("main"), b"new-main")?;
31 fs::write(inputs.join("helper"), b"new-helper")?;
32 fs::write(root.join("bin/main"), b"old-main")?;
33 fs::write(root.join("bin/helper"), b"old-helper")?;
34 let main_digest = eggup_core::hash_file(&inputs.join("main"))?;
35 let helper_digest = eggup_core::hash_file(&inputs.join("helper"))?;
36 let members = ArtifactSet::new(vec![
37 ArtifactMember::new(MemberId::new("main")?, inputs.join("main"), "bin/main")?
38 .with_integrity(IntegrityRequirement::Sha256(main_digest)),
39 ArtifactMember::new(
40 MemberId::new("helper")?,
41 inputs.join("helper"),
42 "bin/helper",
43 )?
44 .with_integrity(IntegrityRequirement::Sha256(helper_digest)),
45 ])?;
46 let plan = InstallPlan::new(
47 ProductId::new("bundle")?,
48 ReleaseId::new("2.0.0")?,
49 &root,
50 members,
51 )?;
52 let receipt = plan
53 .prepare()?
54 .verify_integrity()?
55 .validate(&AllValidators::new())?
56 .commit(CommitOwnership::new(
57 &ExistingAsOwnedVerifier,
58 AbsentPolicy::AllowCreate,
59 ))?;
60 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
61 assert_eq!(fs::read(root.join("bin/main"))?, b"new-main");
62 assert_eq!(fs::read(root.join("bin/helper"))?, b"new-helper");
63 let _ = fs::remove_dir_all(&base);
64 println!("multi-member commit: {:?}", receipt.disposition());
65 Ok(())
66}61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 // 1. Committed: success.
63 let (base, plan) = build(b"new", Some(b"old"))?;
64 let receipt = plan
65 .prepare()?
66 .verify_integrity()?
67 .validate(&AllValidators::new())?
68 .commit(CommitOwnership::new(
69 &ExistingAsOwnedVerifier,
70 AbsentPolicy::AllowCreate,
71 ))?;
72 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73 assert!(receipt.failure().is_none());
74 println!("committed: {:?}", receipt.disposition());
75 let _ = fs::remove_dir_all(&base);
76
77 // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78 let (base, plan) = build(b"new", Some(b"old"))?;
79 let root = base.join("install");
80 let receipt = plan
81 .prepare()?
82 .verify_integrity()?
83 .validate(&AllValidators::new())?
84 .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85 assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86 assert!(receipt.rollback_verified());
87 let failure = receipt.failure().expect("rolled back carries a cause");
88 println!(
89 "rolled back: phase={} category={} detail={}",
90 failure.phase().as_str(),
91 failure.category().as_str(),
92 failure.detail()
93 );
94 assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95 assert_ne!(
96 receipt.disposition(),
97 TransactionDisposition::Committed,
98 "rollback is not success"
99 );
100 let _ = fs::remove_dir_all(&base);
101
102 // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103 // prove either generation is coherent. It is produced by rollback failure
104 // or unclean finalization; operators must inspect `recovery_path` and the
105 // preserved lock. See `docs/transaction.md` for the full contract.
106 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107
108 // 4. A failed post-commit check can keep the coherent new generation,
109 // while remaining machine-readable as a failed check.
110 let (base, plan) = build(b"new", Some(b"old"))?;
111 let receipt = plan
112 .prepare()?
113 .verify_integrity()?
114 .validate(&AllValidators::new())?
115 .commit_with_post_commit(
116 CommitOwnership::new(&ExistingAsOwnedVerifier, AbsentPolicy::AllowCreate),
117 PostCommitFailurePolicy::KeepInstalled,
118 || Err("example health check failed"),
119 )?;
120 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
121 assert!(receipt.post_commit_failure().is_some());
122 println!(
123 "kept installed after failed check: {:?}",
124 receipt.disposition()
125 );
126 let _ = fs::remove_dir_all(base);
127 Ok(())
128}Sourcepub fn rollback_performed(&self) -> bool
pub fn rollback_performed(&self) -> bool
Returns whether rollback was attempted.
Sourcepub fn rollback_verified(&self) -> bool
pub fn rollback_verified(&self) -> bool
Returns whether every restoration check passed.
This describes restoration of live destinations only. A rolled-back
transaction whose restoration fully succeeded stays true even when
the backup set could not be removed; that cleanup problem is reported
in Self::failure with a FailurePhase::Finalize phase and
false is never reported without a Self::rollback_failure or a
recorded cleanup failure to explain it.
Examples found in repository?
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 // 1. Committed: success.
63 let (base, plan) = build(b"new", Some(b"old"))?;
64 let receipt = plan
65 .prepare()?
66 .verify_integrity()?
67 .validate(&AllValidators::new())?
68 .commit(CommitOwnership::new(
69 &ExistingAsOwnedVerifier,
70 AbsentPolicy::AllowCreate,
71 ))?;
72 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73 assert!(receipt.failure().is_none());
74 println!("committed: {:?}", receipt.disposition());
75 let _ = fs::remove_dir_all(&base);
76
77 // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78 let (base, plan) = build(b"new", Some(b"old"))?;
79 let root = base.join("install");
80 let receipt = plan
81 .prepare()?
82 .verify_integrity()?
83 .validate(&AllValidators::new())?
84 .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85 assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86 assert!(receipt.rollback_verified());
87 let failure = receipt.failure().expect("rolled back carries a cause");
88 println!(
89 "rolled back: phase={} category={} detail={}",
90 failure.phase().as_str(),
91 failure.category().as_str(),
92 failure.detail()
93 );
94 assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95 assert_ne!(
96 receipt.disposition(),
97 TransactionDisposition::Committed,
98 "rollback is not success"
99 );
100 let _ = fs::remove_dir_all(&base);
101
102 // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103 // prove either generation is coherent. It is produced by rollback failure
104 // or unclean finalization; operators must inspect `recovery_path` and the
105 // preserved lock. See `docs/transaction.md` for the full contract.
106 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107
108 // 4. A failed post-commit check can keep the coherent new generation,
109 // while remaining machine-readable as a failed check.
110 let (base, plan) = build(b"new", Some(b"old"))?;
111 let receipt = plan
112 .prepare()?
113 .verify_integrity()?
114 .validate(&AllValidators::new())?
115 .commit_with_post_commit(
116 CommitOwnership::new(&ExistingAsOwnedVerifier, AbsentPolicy::AllowCreate),
117 PostCommitFailurePolicy::KeepInstalled,
118 || Err("example health check failed"),
119 )?;
120 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
121 assert!(receipt.post_commit_failure().is_some());
122 println!(
123 "kept installed after failed check: {:?}",
124 receipt.disposition()
125 );
126 let _ = fs::remove_dir_all(base);
127 Ok(())
128}Sourcepub fn cleanup(&self) -> CleanupDisposition
pub fn cleanup(&self) -> CleanupDisposition
Returns how transaction-owned temporary evidence was handled.
Sourcepub fn recovery_path(&self) -> Option<&Path>
pub fn recovery_path(&self) -> Option<&Path>
Returns retained backup/lock evidence when manual recovery is required.
When present, the path always refers to real retained evidence. A
cleanup failure after an otherwise successful commit — or after a fully
verified rollback — retains the real backup root and reports the cleanup
problem in Self::failure; no synthetic non-existent path is ever
returned.
Sourcepub fn failure(&self) -> Option<&FailureReport>
pub fn failure(&self) -> Option<&FailureReport>
Returns the structured report for the triggering failure, if any.
A rolled-back transaction remains a first-class terminal result, but the caller can answer “what failed?” from this report without scraping logs. Successful commits with cleanup trouble also report the finalize problem here, as does a fully verified rollback whose backup set could not be cleaned up.
Examples found in repository?
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 // 1. Committed: success.
63 let (base, plan) = build(b"new", Some(b"old"))?;
64 let receipt = plan
65 .prepare()?
66 .verify_integrity()?
67 .validate(&AllValidators::new())?
68 .commit(CommitOwnership::new(
69 &ExistingAsOwnedVerifier,
70 AbsentPolicy::AllowCreate,
71 ))?;
72 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73 assert!(receipt.failure().is_none());
74 println!("committed: {:?}", receipt.disposition());
75 let _ = fs::remove_dir_all(&base);
76
77 // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78 let (base, plan) = build(b"new", Some(b"old"))?;
79 let root = base.join("install");
80 let receipt = plan
81 .prepare()?
82 .verify_integrity()?
83 .validate(&AllValidators::new())?
84 .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85 assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86 assert!(receipt.rollback_verified());
87 let failure = receipt.failure().expect("rolled back carries a cause");
88 println!(
89 "rolled back: phase={} category={} detail={}",
90 failure.phase().as_str(),
91 failure.category().as_str(),
92 failure.detail()
93 );
94 assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95 assert_ne!(
96 receipt.disposition(),
97 TransactionDisposition::Committed,
98 "rollback is not success"
99 );
100 let _ = fs::remove_dir_all(&base);
101
102 // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103 // prove either generation is coherent. It is produced by rollback failure
104 // or unclean finalization; operators must inspect `recovery_path` and the
105 // preserved lock. See `docs/transaction.md` for the full contract.
106 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107
108 // 4. A failed post-commit check can keep the coherent new generation,
109 // while remaining machine-readable as a failed check.
110 let (base, plan) = build(b"new", Some(b"old"))?;
111 let receipt = plan
112 .prepare()?
113 .verify_integrity()?
114 .validate(&AllValidators::new())?
115 .commit_with_post_commit(
116 CommitOwnership::new(&ExistingAsOwnedVerifier, AbsentPolicy::AllowCreate),
117 PostCommitFailurePolicy::KeepInstalled,
118 || Err("example health check failed"),
119 )?;
120 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
121 assert!(receipt.post_commit_failure().is_some());
122 println!(
123 "kept installed after failed check: {:?}",
124 receipt.disposition()
125 );
126 let _ = fs::remove_dir_all(base);
127 Ok(())
128}Sourcepub fn rollback_failure(&self) -> Option<&FailureReport>
pub fn rollback_failure(&self) -> Option<&FailureReport>
Returns the rollback-phase failure when restoration itself failed.
When present, Self::failure still carries the original triggering
cause; this field carries the distinct recovery failure.
Sourcepub fn post_commit_failure(&self) -> Option<&FailureReport>
pub fn post_commit_failure(&self) -> Option<&FailureReport>
Returns the failed post-commit check, if one occurred.
Examples found in repository?
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 // 1. Committed: success.
63 let (base, plan) = build(b"new", Some(b"old"))?;
64 let receipt = plan
65 .prepare()?
66 .verify_integrity()?
67 .validate(&AllValidators::new())?
68 .commit(CommitOwnership::new(
69 &ExistingAsOwnedVerifier,
70 AbsentPolicy::AllowCreate,
71 ))?;
72 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73 assert!(receipt.failure().is_none());
74 println!("committed: {:?}", receipt.disposition());
75 let _ = fs::remove_dir_all(&base);
76
77 // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78 let (base, plan) = build(b"new", Some(b"old"))?;
79 let root = base.join("install");
80 let receipt = plan
81 .prepare()?
82 .verify_integrity()?
83 .validate(&AllValidators::new())?
84 .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85 assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86 assert!(receipt.rollback_verified());
87 let failure = receipt.failure().expect("rolled back carries a cause");
88 println!(
89 "rolled back: phase={} category={} detail={}",
90 failure.phase().as_str(),
91 failure.category().as_str(),
92 failure.detail()
93 );
94 assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95 assert_ne!(
96 receipt.disposition(),
97 TransactionDisposition::Committed,
98 "rollback is not success"
99 );
100 let _ = fs::remove_dir_all(&base);
101
102 // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103 // prove either generation is coherent. It is produced by rollback failure
104 // or unclean finalization; operators must inspect `recovery_path` and the
105 // preserved lock. See `docs/transaction.md` for the full contract.
106 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107
108 // 4. A failed post-commit check can keep the coherent new generation,
109 // while remaining machine-readable as a failed check.
110 let (base, plan) = build(b"new", Some(b"old"))?;
111 let receipt = plan
112 .prepare()?
113 .verify_integrity()?
114 .validate(&AllValidators::new())?
115 .commit_with_post_commit(
116 CommitOwnership::new(&ExistingAsOwnedVerifier, AbsentPolicy::AllowCreate),
117 PostCommitFailurePolicy::KeepInstalled,
118 || Err("example health check failed"),
119 )?;
120 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
121 assert!(receipt.post_commit_failure().is_some());
122 println!(
123 "kept installed after failed check: {:?}",
124 receipt.disposition()
125 );
126 let _ = fs::remove_dir_all(base);
127 Ok(())
128}