eggup-core
eggup-core is the policy-neutral local substrate for verified, multi-artifact
updates. It is intentionally transport-neutral: callers acquire artifacts,
prove destination ownership, and choose release and service policies; the core
provides bounded local validation, private staging, SHA-256 integrity
verification, bounded candidate validation, locked ownership and staged-digest
revalidation, and synchronous commit/rollback with structured receipts.
Ownership uses Absent | Owned | Foreign | Unknown. Destructive replacement
requires Owned, or Absent with explicit creation authorization;
Foreign and Unknown fail closed. No destination parent is created
automatically. Staged bytes are re-hashed under lock before any live mutation.
Terminal receipts preserve failure phase, category, member, post-commit check
failure, and real recovery evidence. commit_with_post_commit retains the
mutation lock and rollback set until one caller check succeeds or its explicit
KeepInstalled | RollBack policy is resolved.
A program can replace the executable it is currently running through the same
transaction model: InstallPlan::for_current_executable binds the canonical
live image, plans StagePlacement::InsideInstallationRoot so no write authority
above that directory is ever needed, and re-proves the image's identity under
the lock immediately before the first live rename. An invocation symlink is
followed to its real target and never overwritten; a hard-linked or non-regular
destination is refused. On Windows the previous generation stays mapped until the
process exits, so KeepInstalled reports
CleanupDisposition::DeferredToProcessExit and schedules its removal, rather
than calling it cleaned or stranded.
Locks are fail-closed by default: MutationLock::acquire never recovers a stale
record and inspect is read-only. Recovery is opt-in through
MutationLock::acquire_with_recovery, which displaces a record only when a
caller-supplied StaleLockVerifier returns StaleLockDecision::ProvenStale
for the exact observation Core reported. That opt-in API returns
RecoveryResult<T>: ordinary failures arrive as RecoveryError::Core(Error),
and retained evidence as RecoveryError::RecoveryRequired, which carries the
real path via RecoveryError::evidence(). Default acquire keeps returning
Result<T, Error>. Core never decides staleness from PID
liveness, record age, executable name, or service state; see
examples/stale_lock_recovery.rs.
Integrity is checksum evidence only. No authenticity or signature verification exists in this crate.
Bounded candidate execution clears the inherited environment, bounds stdout and
stderr per stream, and enforces an unconditional wall-clock deadline that kills
and reaps the child. A caller may attach a bounded stdin payload with
CommandSpec::stdin_data; the payload is bounded by MAX_STDIN_BYTES
inclusively, an oversized payload is rejected before any child is spawned, and a
write that fails before the deadline fails closed with a typed Error::Io whose
operation is "writing bounded stdin". A write that fails at or after the
deadline is reported by the deadline outcome instead — Ok with timed_out
set — because the deadline already decided it; the write's own observation
instant decides which of the two applies, never the runner's polling phase. The
operation strings "spawning bounded candidate", "polling bounded candidate", and "writing bounded stdin" are a stable classification surface a
downstream adapter may branch on.