#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![doc = "Policy-neutral local mechanics for verified multi-artifact updates."]
#![doc = ""]
#![doc = "The core crate deliberately does not fetch bytes, manage services, or choose release policy."]
#![doc = "Callers acquire artifacts and prove destination ownership; the core"]
#![doc = "provides validated preparation, SHA-256 integrity verification, bounded"]
#![doc = "candidate validation, locked ownership revalidation, staged-digest"]
#![doc = "revalidation, and atomic-feeling multi-artifact commit with rollback."]
#![doc = ""]
#![doc = "Integrity here is checksum evidence only. No authenticity or signature"]
#![doc = "claim is made: there is no authenticity verifier in this crate."]
#![doc = ""]
#![doc = "Runnable flows live in `examples/`: one-member, multi-member, custom"]
#![doc = "validator, ownership verifier, and receipt interpretation."]
mod candidate;
mod domain;
mod error;
mod integrity;
mod lock;
mod stage;
mod transaction;
pub use candidate::{
run_bounded, AllValidators, CandidateValidator, CommandOutput, CommandSpec,
CrossMemberAgreementValidator, ExactIdentityValidator, ValidatedTransaction,
};
pub use domain::{
AbsentOnlyVerifier, AbsentPolicy, ArtifactMember, ArtifactSet, CommitOwnership,
ExactDigestVerifier, ExistingAsOwnedVerifier, FileKind, InstallPlan, IntegrityRequirement,
MemberId, Ownership, OwnershipVerifier, PermissionsIntent, ProductId, ReleaseId,
};
pub use error::{Error, Result};
pub use integrity::{
hash_file, parse_sha256_sidecar, verify_file, IntegrityResult, IntegrityStatus, Sha256Manifest,
VerifiedTransaction,
};
pub use lock::{LockStatus, MutationLock};
pub use stage::PreparedTransaction;
pub use transaction::{
CleanupDisposition, FailureCategory, FailurePhase, FailureReport, TransactionDisposition,
TransactionReceipt,
};
#[cfg(test)]
mod test_support;
#[cfg(test)]
mod tests {
use super::test_support::{FailureInjector, FailurePoint, InstallationRoot};
use super::transaction::CommitFault;
use super::{
AbsentOnlyVerifier, AbsentPolicy, AllValidators, ArtifactMember, ArtifactSet,
CleanupDisposition, CommitOwnership, Error, ExactDigestVerifier, ExactIdentityValidator,
ExistingAsOwnedVerifier, InstallPlan, MemberId, MutationLock, Ownership, OwnershipVerifier,
ProductId, ReleaseId, TransactionDisposition,
};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::time::Duration;
struct FixedVerifier(Ownership);
impl std::fmt::Debug for FixedVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("FixedVerifier").field(&self.0).finish()
}
}
impl OwnershipVerifier for FixedVerifier {
fn verify(&self, _m: &MemberId, _d: &Path) -> Ownership {
self.0
}
}
fn allow_create() -> ExistingAsOwnedVerifier {
ExistingAsOwnedVerifier
}
fn commit_verified(
prepared: super::PreparedTransaction,
verifier: &dyn OwnershipVerifier,
absent: AbsentPolicy,
) -> super::Result<super::TransactionReceipt> {
let verified = prepared.verify_integrity()?;
let validated = verified.validate(&AllValidators::new())?;
validated.commit(CommitOwnership::new(verifier, absent))
}
fn commit_verified_with_fault(
prepared: super::PreparedTransaction,
verifier: &dyn OwnershipVerifier,
absent: AbsentPolicy,
fault: CommitFault,
) -> super::Result<super::TransactionReceipt> {
let verified = prepared.verify_integrity()?;
let validated = verified.validate(&AllValidators::new())?;
validated.commit_with_fault(CommitOwnership::new(verifier, absent), fault)
}
#[test]
fn fixture_writes_and_reads_exact_bytes() {
let fixture = InstallationRoot::new().expect("fixture");
fixture
.write_file("bin/eggup", b"exact bytes")
.expect("write");
assert_eq!(
fixture.read_file("bin/eggup").expect("read"),
b"exact bytes"
);
assert!(fixture.path().join("bin/eggup").is_file());
}
#[test]
fn fixture_rejects_escape_paths() {
let fixture = InstallationRoot::new().expect("fixture");
let error = fixture.write_file("../outside", b"nope").unwrap_err();
assert_eq!(error.kind(), std::io::ErrorKind::PermissionDenied);
assert!(!fixture.path().parent().unwrap().join("outside").exists());
}
#[test]
fn failure_injector_defaults_to_no_failure() {
let mut injector = FailureInjector::default();
assert!(injector.check(FailurePoint::Prepare).is_ok());
assert!(injector.check(FailurePoint::Commit).is_ok());
}
#[test]
fn prepares_a_multi_member_bundle_without_touching_destinations() {
let inputs = InstallationRoot::new().expect("inputs");
let install = InstallationRoot::new().expect("install");
inputs.write_file("egress", b"egress").expect("write");
inputs.write_file("helper", b"helper").expect("write");
let product = ProductId::new("egress").expect("product");
let release = ReleaseId::new("2026.09.22").expect("release");
let members = ArtifactSet::new(vec![
ArtifactMember::new(
MemberId::new("main").expect("member"),
inputs.path().join("egress"),
"bin/egress",
)
.expect("artifact"),
ArtifactMember::new(
MemberId::new("helper").expect("member"),
inputs.path().join("helper"),
"bin/helper",
)
.expect("artifact"),
])
.expect("members");
let prepared = InstallPlan::new(product, release, install.path(), members)
.expect("plan")
.prepare()
.expect("prepare");
assert!(!install.path().join("bin/egress").exists());
assert_eq!(
fs::read(
prepared
.staged_path(&MemberId::new("main").unwrap())
.unwrap()
)
.unwrap(),
b"egress"
);
}
#[test]
fn preparation_failure_cleans_private_stage() {
let inputs = InstallationRoot::new().expect("inputs");
let install = InstallationRoot::new().expect("install");
inputs.write_file("eggup", b"bytes").expect("write");
let plan = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(
ArtifactMember::new(
MemberId::new("main").unwrap(),
inputs.path().join("eggup"),
"eggup",
)
.unwrap(),
)
.unwrap(),
)
.unwrap();
assert!(plan
.clone()
.prepare_with_failure(FailurePoint::StageCreate)
.is_err());
let result = plan.prepare_with_failure(FailurePoint::StageCopy);
assert!(result.is_err());
assert!(!install.path().join("eggup").exists());
let stage_prefix = format!(
".eggup-stage-{}-",
install.path().file_name().unwrap().to_string_lossy()
);
assert!(install
.path()
.parent()
.unwrap()
.read_dir()
.unwrap()
.filter_map(std::result::Result::ok)
.all(|entry| !entry
.file_name()
.to_string_lossy()
.starts_with(&stage_prefix)));
}
#[test]
fn rejects_duplicate_normalized_destinations() {
let inputs = InstallationRoot::new().expect("inputs");
let install = InstallationRoot::new().expect("install");
inputs.write_file("one", b"one").expect("write");
inputs.write_file("two", b"two").expect("write");
let members = ArtifactSet::new(vec![
ArtifactMember::new(
MemberId::new("one").unwrap(),
inputs.path().join("one"),
"bin/./app",
)
.unwrap(),
ArtifactMember::new(
MemberId::new("two").unwrap(),
inputs.path().join("two"),
"bin/app",
)
.unwrap(),
])
.unwrap();
let result = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
members,
);
assert!(result.is_err());
}
#[test]
fn rejects_empty_sets_and_escaping_destinations() {
assert!(ArtifactSet::new(Vec::new()).is_err());
assert!(ArtifactMember::new(
MemberId::new("main").unwrap(),
"/tmp/acquired",
"../outside",
)
.is_err());
assert!(ArtifactMember::new(
MemberId::new("main").unwrap(),
"/tmp/acquired",
"/absolute/path",
)
.is_err());
}
#[cfg(unix)]
#[test]
fn rejects_symlink_sources_and_preserves_executable_intent_in_stage() {
use std::os::unix::fs::symlink;
let inputs = InstallationRoot::new().expect("inputs");
let install = InstallationRoot::new().expect("install");
inputs.write_file("real", b"real").expect("write");
symlink(inputs.path().join("real"), inputs.path().join("link")).expect("symlink");
let symlink_member = ArtifactMember::new(
MemberId::new("link").unwrap(),
inputs.path().join("link"),
"bin/link",
)
.unwrap();
assert!(InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(symlink_member).unwrap(),
)
.is_err());
let member = ArtifactMember::new(
MemberId::new("real").unwrap(),
inputs.path().join("real"),
"bin/real",
)
.unwrap()
.with_permissions(super::PermissionsIntent::Executable);
let prepared = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(member).unwrap(),
)
.unwrap()
.prepare()
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(
prepared
.staged_path(&MemberId::new("real").unwrap())
.unwrap(),
)
.unwrap()
.permissions()
.mode();
assert_ne!(mode & 0o111, 0);
assert_eq!(mode & 0o777, 0o700);
}
}
fn prepared_bundle(
existing: bool,
) -> (
InstallationRoot,
InstallationRoot,
super::PreparedTransaction,
) {
let inputs = InstallationRoot::new().expect("inputs");
let install = InstallationRoot::new().expect("install");
let main_src = inputs.write_file("main", b"new-main").expect("write");
let helper_src = inputs.write_file("helper", b"new-helper").expect("write");
let main_digest = super::hash_file(&main_src).unwrap();
let helper_digest = super::hash_file(&helper_src).unwrap();
if existing {
install
.write_file("bin/main", b"old-main")
.expect("old main");
install
.write_file("bin/helper", b"old-helper")
.expect("old helper");
} else {
fs::create_dir_all(install.path().join("bin")).unwrap();
}
let members = ArtifactSet::new(vec![
ArtifactMember::new(
MemberId::new("main").unwrap(),
inputs.path().join("main"),
"bin/main",
)
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(main_digest)),
ArtifactMember::new(
MemberId::new("helper").unwrap(),
inputs.path().join("helper"),
"bin/helper",
)
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(helper_digest)),
])
.unwrap();
let prepared = InstallPlan::new(
ProductId::new("bundle").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
members,
)
.unwrap()
.prepare()
.unwrap();
(inputs, install, prepared)
}
#[test]
fn commits_a_complete_multi_member_generation_and_releases_lock() {
let (_inputs, install, prepared) = prepared_bundle(true);
let verifier = allow_create();
let receipt =
commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).expect("commit");
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
assert_eq!(receipt.cleanup(), CleanupDisposition::Cleaned);
assert!(receipt.failure().is_none());
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"new-main"
);
assert_eq!(
fs::read(install.path().join("bin/helper")).unwrap(),
b"new-helper"
);
assert!(!install.path().join(".eggup-mutation.lock").exists());
assert!(install
.path()
.read_dir()
.unwrap()
.filter_map(std::result::Result::ok)
.all(|entry| !entry
.file_name()
.to_string_lossy()
.starts_with(".eggup-backup-")));
}
#[test]
fn partial_commit_failure_restores_every_old_member() {
let (_inputs, install, prepared) = prepared_bundle(true);
let verifier = allow_create();
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::Commit(MemberId::new("helper").unwrap()),
)
.expect("receipt");
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert!(receipt.rollback_performed());
assert!(receipt.rollback_verified());
let failure = receipt.failure().expect("failure report");
assert_eq!(failure.phase(), super::FailurePhase::Commit);
assert_eq!(failure.member(), Some(&MemberId::new("helper").unwrap()));
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"old-main"
);
assert_eq!(
fs::read(install.path().join("bin/helper")).unwrap(),
b"old-helper"
);
}
#[test]
fn rollback_removes_new_members_that_were_absent_before_commit() {
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let main_src = inputs.write_file("main", b"new-main").unwrap();
let helper_src = inputs.write_file("helper", b"new-helper").unwrap();
let main_digest = super::hash_file(&main_src).unwrap();
let helper_digest = super::hash_file(&helper_src).unwrap();
install.write_file("bin/main", b"old-main").unwrap();
let prepared = InstallPlan::new(
ProductId::new("bundle").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::new(vec![
ArtifactMember::new(
MemberId::new("main").unwrap(),
inputs.path().join("main"),
"bin/main",
)
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(main_digest)),
ArtifactMember::new(
MemberId::new("helper").unwrap(),
inputs.path().join("helper"),
"bin/helper",
)
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(helper_digest)),
])
.unwrap(),
)
.unwrap()
.prepare()
.unwrap();
let verifier = allow_create();
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::Commit(MemberId::new("helper").unwrap()),
)
.unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"old-main"
);
assert!(!install.path().join("bin/helper").exists());
}
#[test]
fn lock_contention_and_malformed_lock_fail_closed() {
let (_inputs, install, prepared) = prepared_bundle(false);
let product = ProductId::new("bundle").unwrap();
let release = ReleaseId::new("r1").unwrap();
let _lock = MutationLock::acquire(install.path(), &product, &release).unwrap();
let verifier = allow_create();
let error = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap_err();
assert!(matches!(error, Error::UpdateInProgress { .. }));
drop(_lock);
let (_inputs, install2, prepared) = prepared_bundle(false);
let lock = install2.path().join(".eggup-mutation.lock");
fs::write(&lock, b"malformed").unwrap();
let error = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap_err();
assert!(matches!(error, Error::UpdateInProgress { .. }));
assert!(lock.exists());
}
#[test]
fn rollback_failure_returns_recovery_required_and_retains_evidence() {
let (_inputs, install, prepared) = prepared_bundle(true);
let verifier = allow_create();
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::CommitThenRollback(
MemberId::new("helper").unwrap(),
MemberId::new("main").unwrap(),
),
)
.unwrap();
assert_eq!(
receipt.disposition(),
TransactionDisposition::RecoveryRequired
);
assert!(!receipt.rollback_verified());
assert!(receipt.recovery_path().is_some());
assert!(receipt.failure().is_some());
assert!(receipt.rollback_failure().is_some());
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::Commit
);
assert_eq!(
receipt.rollback_failure().unwrap().phase(),
super::FailurePhase::Rollback
);
assert!(install.path().join(".eggup-mutation.lock").exists());
let recovery = receipt.recovery_path().unwrap();
assert!(recovery.exists());
let _ = fs::remove_dir_all(recovery);
let _ = fs::remove_file(install.path().join(".eggup-mutation.lock"));
}
#[test]
fn precommit_and_backup_failures_restore_old_state() {
let (_inputs, install, prepared) = prepared_bundle(true);
let verifier = allow_create();
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::BeforeFirstCommit,
)
.unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert!(receipt.failure().is_some());
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"old-main"
);
let (_inputs, install, prepared) = prepared_bundle(true);
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::Backup(MemberId::new("helper").unwrap()),
)
.unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::Backup
);
assert_eq!(
fs::read(install.path().join("bin/helper")).unwrap(),
b"old-helper"
);
}
#[test]
fn injected_lock_creation_failure_performs_no_mutation() {
let (_inputs, install, prepared) = prepared_bundle(true);
let verifier = allow_create();
let error = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::LockCreation,
)
.unwrap_err();
assert!(matches!(error, Error::InvalidInput(_)));
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"old-main"
);
assert!(!install.path().join(".eggup-mutation.lock").exists());
}
#[cfg(unix)]
#[test]
fn destination_links_fail_immediately_before_backup() {
use std::fs::hard_link;
use std::os::unix::fs::symlink;
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let src = inputs.write_file("main", b"new-main").unwrap();
let digest = super::hash_file(&src).unwrap();
install.write_file("bin/real", b"old-main").unwrap();
symlink(install.path().join("real"), install.path().join("bin/main")).unwrap();
let prepared = InstallPlan::new(
ProductId::new("bundle").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(
ArtifactMember::new(
MemberId::new("main").unwrap(),
inputs.path().join("main"),
"bin/main",
)
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(digest)),
)
.unwrap(),
)
.unwrap()
.prepare()
.unwrap();
let verifier = allow_create();
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::Ownership
);
assert_eq!(
fs::read(install.path().join("bin/real")).unwrap(),
b"old-main"
);
let (_inputs, install, prepared) = prepared_bundle(true);
hard_link(
install.path().join("bin/main"),
install.path().join("bin/linked"),
)
.unwrap();
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
fs::read(install.path().join("bin/main")).unwrap(),
b"old-main"
);
}
#[test]
fn hashes_known_bytes_and_parses_strict_sidecars() {
let fixture = InstallationRoot::new().unwrap();
let path = fixture.write_file("abc", b"abc").unwrap();
let manifest = super::parse_sha256_sidecar(
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD abc\n",
)
.unwrap();
assert_eq!(manifest.filename(), Some("abc"));
assert_eq!(
super::verify_file(&path, &manifest).unwrap(),
*manifest.digest()
);
assert!(super::parse_sha256_sidecar("abc\ndef").is_err());
assert!(super::parse_sha256_sidecar(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad other"
)
.and_then(|manifest| super::verify_file(&path, &manifest))
.is_err());
assert!(super::parse_sha256_sidecar("not-a-digest abc").is_err());
}
#[test]
fn integrity_verification_precedes_candidate_validation() {
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let source = inputs.write_file("main", b"verified bytes").unwrap();
let digest = super::hash_file(&source).unwrap();
let member = ArtifactMember::new(MemberId::new("main").unwrap(), source, "bin/main")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(digest));
let verified = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(member).unwrap(),
)
.unwrap()
.prepare()
.unwrap()
.verify_integrity()
.unwrap();
assert_eq!(
verified
.integrity(&MemberId::new("main").unwrap())
.unwrap()
.status(),
super::IntegrityStatus::Verified
);
let bad_source = inputs.write_file("bad", b"bad bytes").unwrap();
let bad_member = ArtifactMember::new(MemberId::new("bad").unwrap(), bad_source, "bin/bad")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(digest));
assert!(matches!(
InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(bad_member).unwrap(),
)
.unwrap()
.prepare()
.unwrap()
.verify_integrity(),
Err(Error::VerificationFailed(_))
));
let no_requirement = ArtifactMember::new(
MemberId::new("none").unwrap(),
inputs.write_file("none", b"no declaration").unwrap(),
"bin/none",
)
.unwrap();
let verified = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(no_requirement).unwrap(),
)
.unwrap()
.prepare()
.unwrap()
.verify_integrity()
.unwrap();
let validator = ExactIdentityValidator::new(MemberId::new("none").unwrap(), "anything");
assert!(matches!(
verified.validate(&validator),
Err(Error::VerificationFailed(_))
));
}
#[cfg(unix)]
#[test]
fn bounded_candidates_are_exact_and_environment_is_cleared() {
use std::os::unix::fs::PermissionsExt;
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let script = inputs
.write_file("main", b"#!/bin/sh\nprintf 'eggup 1.2.3\\n'\n")
.unwrap();
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();
let digest = super::hash_file(&script).unwrap();
let member = ArtifactMember::new(MemberId::new("main").unwrap(), script, "bin/main")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(digest));
fs::create_dir_all(install.path().join("bin")).unwrap();
let verified = InstallPlan::new(
ProductId::new("eggup").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(member).unwrap(),
)
.unwrap()
.prepare()
.unwrap()
.verify_integrity()
.unwrap();
let validator =
ExactIdentityValidator::new(MemberId::new("main").unwrap(), "eggup 1.2.3\n");
let validated = verified.validate(&validator).unwrap();
let verifier = allow_create();
assert_eq!(
validated
.commit(CommitOwnership::new(&verifier, AbsentPolicy::AllowCreate))
.unwrap()
.disposition(),
TransactionDisposition::Committed
);
let secret_script = inputs
.write_file(
"secret",
b"#!/bin/sh\nprintf '%s' \"${EGGUP_SECRET-unset}\"\n",
)
.unwrap();
fs::set_permissions(&secret_script, fs::Permissions::from_mode(0o755)).unwrap();
let output = super::run_bounded(
&super::CommandSpec::new(secret_script).timeout(Duration::from_secs(1)),
)
.unwrap();
assert!(output.success());
assert_eq!(output.stdout(), b"unset");
}
#[cfg(unix)]
#[test]
fn bounded_runner_kills_timeouts_and_limits_output() {
use std::os::unix::fs::PermissionsExt;
let fixture = InstallationRoot::new().unwrap();
let timeout_script = fixture
.write_file("timeout", b"#!/bin/sh\nsleep 2\n")
.unwrap();
let noisy_script = fixture
.write_file("noisy", b"#!/bin/sh\nprintf '1234567890'\n")
.unwrap();
for path in [&timeout_script, &noisy_script] {
fs::set_permissions(path, fs::Permissions::from_mode(0o755)).unwrap();
}
let timed_out = super::run_bounded(
&super::CommandSpec::new(timeout_script).timeout(Duration::from_millis(20)),
)
.unwrap();
assert!(timed_out.timed_out());
assert!(!timed_out.success());
let limited =
super::run_bounded(&super::CommandSpec::new(noisy_script).max_output_bytes(4)).unwrap();
assert!(limited.output_limited());
assert!(!limited.success());
}
#[cfg(unix)]
#[test]
fn cross_member_identity_requires_bundle_agreement() {
use std::os::unix::fs::PermissionsExt;
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let main = inputs
.write_file("main", b"#!/bin/sh\nprintf 'bundle 9\\n'\n")
.unwrap();
let helper = inputs
.write_file("helper", b"#!/bin/sh\nprintf 'bundle 9\\n'\n")
.unwrap();
for path in [&main, &helper] {
fs::set_permissions(path, fs::Permissions::from_mode(0o755)).unwrap();
}
let main_digest = super::hash_file(&main).unwrap();
let helper_digest = super::hash_file(&helper).unwrap();
let verified = InstallPlan::new(
ProductId::new("bundle").unwrap(),
ReleaseId::new("r9").unwrap(),
install.path(),
ArtifactSet::new(vec![
ArtifactMember::new(MemberId::new("main").unwrap(), main, "bin/main")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(main_digest)),
ArtifactMember::new(MemberId::new("helper").unwrap(), helper, "bin/helper")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(helper_digest)),
])
.unwrap(),
)
.unwrap()
.prepare()
.unwrap()
.verify_integrity()
.unwrap();
let validator = super::CrossMemberAgreementValidator::new(
[
MemberId::new("main").unwrap(),
MemberId::new("helper").unwrap(),
],
"bundle 9\n",
);
assert!(verified.validate(&validator).is_ok());
}
fn single_verified(
new_bytes: &[u8],
old_bytes: Option<&[u8]>,
with_bin_dir: bool,
) -> (
InstallationRoot,
InstallationRoot,
super::PreparedTransaction,
[u8; 32],
) {
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
let src = inputs.write_file("main", new_bytes).unwrap();
let digest = super::hash_file(&src).unwrap();
if let Some(old) = old_bytes {
install.write_file("bin/main", old).unwrap();
} else if with_bin_dir {
fs::create_dir_all(install.path().join("bin")).unwrap();
}
let member = ArtifactMember::new(MemberId::new("main").unwrap(), src, "bin/main")
.unwrap()
.with_integrity(super::IntegrityRequirement::Sha256(digest));
let prepared = InstallPlan::new(
ProductId::new("p").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(member).unwrap(),
)
.unwrap()
.prepare()
.unwrap();
(inputs, install, prepared, digest)
}
#[test]
fn ownership_owned_allows_replacement_and_reports_no_failure() {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let old_digest = super::hash_file(&install.path().join("bin/main")).unwrap();
let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main").unwrap(), old_digest)]);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::DenyCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
assert_eq!(fs::read(install.path().join("bin/main")).unwrap(), b"new");
}
#[test]
fn ownership_foreign_and_unknown_fail_closed_with_zero_mutation() {
for fixed in [Ownership::Foreign, Ownership::Unknown] {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verifier = FixedVerifier(fixed);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::Ownership
);
assert_eq!(fs::read(install.path().join("bin/main")).unwrap(), b"old");
}
}
#[test]
fn ownership_absent_create_policy_is_enforced() {
let (_i, install, prepared, _) = single_verified(b"new", None, true);
let verifier = AbsentOnlyVerifier;
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
assert_eq!(fs::read(install.path().join("bin/main")).unwrap(), b"new");
let (_i, install, prepared, _) = single_verified(b"new", None, true);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::DenyCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert!(!install.path().join("bin/main").exists());
}
#[test]
fn ownership_flapping_between_preflight_and_lock_fails() {
use std::cell::Cell;
#[derive(Debug)]
struct Flap {
first: Ownership,
second: Ownership,
calls: Cell<usize>,
}
impl OwnershipVerifier for Flap {
fn verify(&self, _m: &MemberId, _d: &Path) -> Ownership {
let n = self.calls.get();
self.calls.set(n + 1);
if n == 0 {
self.first
} else {
self.second
}
}
}
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verifier = Flap {
first: Ownership::Owned,
second: Ownership::Foreign,
calls: Cell::new(0),
};
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(fs::read(install.path().join("bin/main")).unwrap(), b"old");
}
#[test]
fn missing_parent_fails_without_creating_directories() {
let (_i, install, prepared, _) = single_verified(b"new", None, false);
assert!(!install.path().join("bin").exists());
let verifier = allow_create();
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert!(!install.path().join("bin").exists());
assert!(!install.path().join("bin/main").exists());
}
#[cfg(unix)]
#[test]
fn symlinked_parent_fails_closed() {
use std::os::unix::fs::symlink;
let (_i, install, prepared, _) = single_verified(b"new", None, false);
fs::create_dir_all(install.path().join("real-bin")).unwrap();
symlink(install.path().join("real-bin"), install.path().join("bin")).unwrap();
let verifier = allow_create();
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
}
#[test]
fn staged_mutation_after_validation_fails_before_live_mutation() {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verified = prepared.verify_integrity().unwrap();
let validated = verified.validate(&AllValidators::new()).unwrap();
let staged = validated
.staged_path(&MemberId::new("main").unwrap())
.unwrap();
fs::write(&staged, b"tampered").unwrap();
let verifier = allow_create();
let receipt = validated
.commit(CommitOwnership::new(&verifier, AbsentPolicy::AllowCreate))
.unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::StageRevalidation
);
assert_eq!(fs::read(install.path().join("bin/main")).unwrap(), b"old");
}
#[test]
fn unverified_integrity_none_cannot_commit() {
let inputs = InstallationRoot::new().unwrap();
let install = InstallationRoot::new().unwrap();
fs::create_dir_all(install.path().join("bin")).unwrap();
let src = inputs.write_file("main", b"bytes").unwrap();
let member = ArtifactMember::new(MemberId::new("main").unwrap(), src, "bin/main").unwrap();
let prepared = InstallPlan::new(
ProductId::new("p").unwrap(),
ReleaseId::new("r1").unwrap(),
install.path(),
ArtifactSet::single(member).unwrap(),
)
.unwrap()
.prepare()
.unwrap();
let verified = prepared.verify_integrity().unwrap();
assert!(verified.validate(&AllValidators::new()).is_err());
}
#[test]
fn cleanup_failure_reports_real_backup_root() {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verifier = allow_create();
let receipt = commit_verified_with_fault(
prepared,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::Finalize,
)
.unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
assert_eq!(receipt.cleanup(), CleanupDisposition::RetainedForRecovery);
let path = receipt.recovery_path().unwrap();
assert!(path.exists());
assert!(!path.to_string_lossy().contains("cleanup-failed"));
assert_eq!(
receipt.failure().unwrap().phase(),
super::FailurePhase::Finalize
);
let _ = fs::remove_dir_all(path);
let _ = fs::remove_file(install.path().join(".eggup-mutation.lock"));
}
#[cfg(unix)]
#[test]
fn transaction_owned_state_is_owner_private() {
use std::os::unix::fs::PermissionsExt;
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let stage_mode = fs::metadata(prepared.stage_root())
.unwrap()
.permissions()
.mode()
& 0o777;
assert_eq!(stage_mode, 0o700);
let staged = prepared
.staged_path(&MemberId::new("main").unwrap())
.unwrap();
let file_mode = fs::metadata(&staged).unwrap().permissions().mode() & 0o777;
assert_eq!(file_mode, 0o600);
let verifier = allow_create();
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
let (_i2, install2, prepared2, _) = single_verified(b"n2", Some(b"o2"), false);
let receipt = commit_verified_with_fault(
prepared2,
&verifier,
AbsentPolicy::AllowCreate,
CommitFault::Finalize,
)
.unwrap();
let lock_mode = fs::metadata(install2.path().join(".eggup-mutation.lock"))
.unwrap()
.permissions()
.mode()
& 0o777;
assert_eq!(lock_mode, 0o600);
let _ = fs::remove_dir_all(receipt.recovery_path().unwrap());
let _ = fs::remove_file(install2.path().join(".eggup-mutation.lock"));
let _ = install;
}
#[test]
fn lock_inspect_never_deletes_and_reports_status() {
let install = InstallationRoot::new().unwrap();
assert_eq!(
MutationLock::inspect(install.path()).unwrap(),
super::LockStatus::Available
);
let product = ProductId::new("p").unwrap();
let release = ReleaseId::new("r").unwrap();
let lock = MutationLock::acquire(install.path(), &product, &release).unwrap();
match MutationLock::inspect(install.path()).unwrap() {
super::LockStatus::Held { contents, .. } => assert!(contents.is_some()),
other => panic!("expected held, got {other:?}"),
}
drop(lock);
assert_eq!(
MutationLock::inspect(install.path()).unwrap(),
super::LockStatus::Available
);
fs::write(install.path().join(".eggup-mutation.lock"), b"malformed").unwrap();
match MutationLock::inspect(install.path()).unwrap() {
super::LockStatus::Held { .. } | super::LockStatus::Malformed { .. } => {}
other => panic!("expected held/malformed, got {other:?}"),
}
assert!(install.path().join(".eggup-mutation.lock").exists());
}
#[test]
fn oversized_lock_record_is_malformed_not_deleted() {
let install = InstallationRoot::new().unwrap();
let big = vec![b'x'; 8192];
fs::write(install.path().join(".eggup-mutation.lock"), &big).unwrap();
match MutationLock::inspect(install.path()).unwrap() {
super::LockStatus::Malformed { .. } => {}
other => panic!("expected malformed, got {other:?}"),
}
assert!(install.path().join(".eggup-mutation.lock").exists());
}
#[test]
fn exact_digest_verifier_proves_ownership() {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let live_digest = super::hash_file(&install.path().join("bin/main")).unwrap();
let verifier =
ExactDigestVerifier::new(vec![(MemberId::new("main").unwrap(), live_digest)]);
assert_eq!(
verifier.verify(
&MemberId::new("main").unwrap(),
&install.path().join("bin/main")
),
Ownership::Owned
);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::DenyCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
}
#[test]
fn absent_only_verifier_never_authorizes_replacement() {
let (_i, install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verifier = AbsentOnlyVerifier;
assert_eq!(
verifier.verify(
&MemberId::new("main").unwrap(),
&install.path().join("bin/main")
),
Ownership::Foreign
);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
}
#[test]
fn failure_reports_carry_phase_category_and_member() {
let (_i, _install, prepared, _) = single_verified(b"new", Some(b"old"), false);
let verifier = FixedVerifier(Ownership::Foreign);
let receipt = commit_verified(prepared, &verifier, AbsentPolicy::AllowCreate).unwrap();
let failure = receipt.failure().unwrap();
assert_eq!(failure.phase(), super::FailurePhase::Ownership);
assert!(!failure.detail().is_empty());
assert!(failure.detail().len() <= 512);
}
#[test]
fn checksum_only_state_is_explicit_in_docs() {
let desc = env!("CARGO_PKG_DESCRIPTION");
let _ = desc;
}
#[allow(dead_code)]
fn _unused() {
let _m: HashMap<MemberId, [u8; 32]> = HashMap::new();
}
}