pub struct ReleaseId(/* private fields */);Expand description
A validated opaque release identifier owned by the consumer.
Implementations§
Source§impl ReleaseId
impl ReleaseId
Sourcepub fn new(value: impl Into<String>) -> Result<Self>
pub fn new(value: impl Into<String>) -> Result<Self>
Creates a release identifier after rejecting empty or unsafe control-character input.
§Errors
Returns Error::InvalidInput when the identifier is empty or
contains a control character.
Examples found in repository?
examples/receipts.rs (line 53)
35fn build(
36 new: &[u8],
37 old: Option<&[u8]>,
38) -> Result<(PathBuf, InstallPlan), Box<dyn std::error::Error>> {
39 let base = temp_root("eggup-receipts");
40 let inputs = base.join("inputs");
41 let root = base.join("install");
42 fs::create_dir_all(&inputs)?;
43 fs::create_dir_all(root.join("bin"))?;
44 fs::write(inputs.join("app"), new)?;
45 if let Some(old) = old {
46 fs::write(root.join("bin/app"), old)?;
47 }
48 let digest = eggup_core::hash_file(&inputs.join("app"))?;
49 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
50 .with_integrity(IntegrityRequirement::Sha256(digest));
51 let plan = InstallPlan::new(
52 ProductId::new("demo")?,
53 ReleaseId::new("5.0.0")?,
54 &root,
55 ArtifactSet::single(member)?,
56 )?;
57 // Leak the base via the plan's root; caller cleans `root.parent()`.
58 Ok((base, plan))
59}More examples
examples/custom_validator.rs (line 58)
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}examples/one_member.rs (line 46)
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}examples/ownership.rs (line 38)
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}examples/multi_member.rs (line 48)
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}examples/stale_lock_recovery.rs (line 96)
89fn main() -> Result<(), Box<dyn std::error::Error>> {
90 let root =
91 std::env::temp_dir().join(format!("eggup-stale-lock-example-{}", std::process::id()));
92 let _ = std::fs::remove_dir_all(&root);
93 std::fs::create_dir_all(&root)?;
94
95 let product = ProductId::new("example-daemon")?;
96 let release = ReleaseId::new("2026.10.05")?;
97 let lock_path = root.join(".eggup-mutation.lock");
98
99 // A crashed updater left this record behind.
100 std::fs::write(
101 &lock_path,
102 "pid=4242 nonce=9 product=example-daemon release=2026.10.05\n",
103 )?;
104
105 println!("default acquisition never recovers:");
106 let error = MutationLock::acquire(&root, &product, &release).expect_err("record exists");
107 println!(" {error}\n");
108
109 // The consumer supervises pid 4242, can no longer see it, and the deployment
110 // is declared idle. That combination is its proof.
111 let conclusive = DeploymentVerifier {
112 evidence: DeploymentEvidence {
113 product: product.clone(),
114 supervised: vec![4242],
115 alive: Vec::new(),
116 declared_idle: true,
117 },
118 };
119 match MutationLock::acquire_with_recovery(&root, &product, &release, &conclusive) {
120 Ok(lock) => {
121 println!("recovered after caller proof:");
122 println!(" lock record: {}", lock.path().display());
123 println!(" our own record is removed when it is dropped");
124 }
125 Err(error) => println!("retained the record: {error}"),
126 }
127
128 // Contrast: the same record without the deployment's out-of-band statement is
129 // ambiguous, so it is left exactly where it is.
130 let _ = std::fs::remove_file(&lock_path);
131 std::fs::write(
132 &lock_path,
133 "pid=5150 nonce=1 product=example-daemon release=2026.10.05\n",
134 )?;
135 let cautious = DeploymentVerifier {
136 evidence: DeploymentEvidence {
137 product: product.clone(),
138 supervised: vec![5150],
139 alive: Vec::new(),
140 declared_idle: false,
141 },
142 };
143 let error = MutationLock::acquire_with_recovery(&root, &product, &release, &cautious)
144 .expect_err("no out-of-band proof");
145 println!("\nwithout out-of-band proof:");
146 println!(" {error}");
147 println!(" record still present: {}", lock_path.exists());
148
149 // And a record belonging to a different deployment is never claimed.
150 let _ = std::fs::remove_file(&lock_path);
151 std::fs::write(
152 &lock_path,
153 "pid=6060 nonce=1 product=somebody-else release=2026.10.05\n",
154 )?;
155 let foreign = DeploymentVerifier {
156 evidence: DeploymentEvidence {
157 product: ProductId::new("example-daemon")?,
158 supervised: vec![6060],
159 alive: Vec::new(),
160 declared_idle: true,
161 },
162 };
163 let error = MutationLock::acquire_with_recovery(&root, &product, &release, &foreign)
164 .expect_err("foreign record");
165 println!("\nfor another deployment's record:");
166 println!(" {error}");
167 println!(" record still present: {}", lock_path.exists());
168
169 let _ = std::fs::remove_dir_all(&root);
170 Ok(())
171}Trait Implementations§
impl Eq for ReleaseId
impl StructuralPartialEq for ReleaseId
Auto Trait Implementations§
impl Freeze for ReleaseId
impl RefUnwindSafe for ReleaseId
impl Send for ReleaseId
impl Sync for ReleaseId
impl Unpin for ReleaseId
impl UnsafeUnpin for ReleaseId
impl UnwindSafe for ReleaseId
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more