Skip to main content

InstallPlan

Struct InstallPlan 

Source
pub struct InstallPlan { /* private fields */ }
Expand description

An explicit installation root and a coherent artifact set ready for private preparation.

Implementations§

Source§

impl InstallPlan

Source

pub fn new( product: ProductId, release: ReleaseId, installation_root: impl Into<PathBuf>, artifacts: ArtifactSet, ) -> Result<Self>

Validates an installation root, destination containment, sources, and member uniqueness.

Examples found in repository?
examples/receipts.rs (lines 51-56)
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
Hide additional examples
examples/one_member.rs (lines 35-40)
25fn main() -> Result<(), Box<dyn std::error::Error>> {
26    let base = temp_root("eggup-one-member");
27    let inputs = base.join("inputs");
28    let root = base.join("install");
29    fs::create_dir_all(inputs.join("bin"))?;
30    fs::create_dir_all(root.join("bin"))?;
31    fs::write(inputs.join("app"), b"new-app")?;
32    let digest = eggup_core::hash_file(&inputs.join("app"))?;
33    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
34        .with_integrity(IntegrityRequirement::Sha256(digest));
35    let plan = InstallPlan::new(
36        ProductId::new("demo")?,
37        ReleaseId::new("1.0.0")?,
38        &root,
39        ArtifactSet::single(member)?,
40    )?;
41    let receipt = plan
42        .prepare()?
43        .verify_integrity()?
44        .validate(&AllValidators::new())?
45        .commit(CommitOwnership::new(
46            &ExistingAsOwnedVerifier,
47            AbsentPolicy::AllowCreate,
48        ))?;
49    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
50    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
51    let _ = fs::remove_dir_all(&base);
52    println!("one-member commit: {:?}", receipt.disposition());
53    Ok(())
54}
examples/custom_validator.rs (lines 56-61)
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/ownership.rs (lines 36-41)
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 (lines 46-51)
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}
Source

pub fn product(&self) -> &ProductId

Returns the consumer-owned product identity.

Source

pub fn release(&self) -> &ReleaseId

Returns the consumer-owned release identity.

Source

pub fn installation_root(&self) -> &Path

Returns the exact installation root used for destination resolution.

Source

pub fn artifacts(&self) -> &ArtifactSet

Returns the validated artifact set.

Source

pub fn destination(&self, member: &MemberId) -> Result<PathBuf>

Resolves a member destination beneath the exact installation root.

Source

pub fn prepare(self) -> Result<PreparedTransaction>

Copies every member into private owned stage state without mutating a destination.

Examples found in repository?
examples/one_member.rs (line 42)
25fn main() -> Result<(), Box<dyn std::error::Error>> {
26    let base = temp_root("eggup-one-member");
27    let inputs = base.join("inputs");
28    let root = base.join("install");
29    fs::create_dir_all(inputs.join("bin"))?;
30    fs::create_dir_all(root.join("bin"))?;
31    fs::write(inputs.join("app"), b"new-app")?;
32    let digest = eggup_core::hash_file(&inputs.join("app"))?;
33    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
34        .with_integrity(IntegrityRequirement::Sha256(digest));
35    let plan = InstallPlan::new(
36        ProductId::new("demo")?,
37        ReleaseId::new("1.0.0")?,
38        &root,
39        ArtifactSet::single(member)?,
40    )?;
41    let receipt = plan
42        .prepare()?
43        .verify_integrity()?
44        .validate(&AllValidators::new())?
45        .commit(CommitOwnership::new(
46            &ExistingAsOwnedVerifier,
47            AbsentPolicy::AllowCreate,
48        ))?;
49    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
50    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
51    let _ = fs::remove_dir_all(&base);
52    println!("one-member commit: {:?}", receipt.disposition());
53    Ok(())
54}
More examples
Hide additional examples
examples/custom_validator.rs (line 67)
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/ownership.rs (line 46)
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 53)
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/receipts.rs (line 65)
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}
Source

pub fn prepare_with_bound_sources( self, bound: BoundSources, ) -> Result<PreparedTransaction>

Copies every member into private owned stage state, sourcing bound members from their already-open objects without any pathname lookup.

Each member identity present in bound is staged by reading the moved open object from byte zero; its recorded source path is treated as advisory diagnostics only and is never opened, even if it now names foreign state. Members absent from bound stage from their recorded source paths exactly as in prepare.

Every bound identity must name a member of this plan: leftover handles fail closed and no staged byte is ever obtained by reopening a member name as fallback. A failed bound stage copy never falls back to the recorded path.

Trait Implementations§

Source§

impl Clone for InstallPlan

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for InstallPlan

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for InstallPlan

Source§

impl PartialEq for InstallPlan

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for InstallPlan

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.