jdx/mise · error

lockfile generation would downgrade recorded provenance…

Error message

lockfile generation would downgrade recorded provenance; previous files were preserved

What it means

mise's lockfile records cryptographic provenance (e.g. SLSA attestations) for each locked artifact. During `lockfile generate`, `ensure_no_downgrade` compares the existing recorded provenance with the newly computed one; if the new provenance is weaker or absent (including the packslip-signer-replaces-provenance case), generation aborts so previously written lockfiles are preserved untouched. This is a trust-safety guard, not a bug.

Solutions

  1. Check why the new provenance is weaker: verify the packslip manifest/attestations are still published for the target version
  2. Re-enable attestation settings (e.g. `github_attestations`) that were disabled in settings.toml
  3. Regenerate with the backend that produced the original provenance (e.g. packslip:) instead of a fallback backend
  4. If the downgrade is intentional, delete/review the existing lockfile entry first so generation starts from a clean trusted state

Example fix

# before: attestations disabled, regeneration downgrades provenance
[settings.ruby]
github_attestations = false
# after
[settings.ruby]
github_attestations = true
Defensive patterns

Strategy: validation

Validate before calling

let old = existing_entry.provenance.as_ref();
let new = computed_entry.provenance.as_ref();
if new.is_none() || new.map(|n| n.kind()) != old.map(|o| o.kind()) {
    // provenance would be downgraded — regenerate with attesting backend first
}

Prevention

When it happens

Trigger: Running lockfile generation (`mise lock` / internal `generate`) for a tool whose existing lockfile entry has provenance (or a packslip signer) recorded, but the regenerated entry's provenance is missing or weaker per `provenance_is_downgrade` — e.g. the backend stopped serving attestations, the packslip manifest no longer includes them, or settings for attestations were disabled.

Common situations: Regenerating a lockfile after a registry/backend change dropped attestation data; switching a backend from packslip to a non-attesting backend; CI regenerating lockfiles after attestations were temporarily unavailable upstream; toggling `github_attestations` settings off.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/7e80a9afb266cd73. Report an issue: GitHub.

Appendix: source

Thrown at src/lockfile/generate.rs:783

        lockfile.set_uv_lock(&ba.short, &tv.version, &backend_name, &options, graph)?;
    }
    Ok(())
}

fn ensure_no_downgrade(old: &PlatformInfo, new: &PlatformInfo, backend: &str) -> Result<()> {
    // A verified Packslip signer is the replacement trust baseline for an
    // artifact authenticated by its signed release manifest. Older incremental
    // lock updates could carry detected GitHub provenance into a Packslip entry,
    // but complete generation intentionally does not persist that unverified
    // link. Without a signer, retain the ordinary provenance ratchet.
    let packslip_signer_replaces_provenance =
        backend.starts_with("packslip:") && new.signer.is_some();
    if provenance_is_downgrade(
        old.provenance.as_ref(),
        new.provenance.as_ref(),
        packslip_signer_replaces_provenance,
    ) {
        bail!(
            "lockfile generation would downgrade recorded provenance; previous files were preserved"
        );
    }
    if let Some(signer) = &old.signer
        && (new.signer.as_ref() != Some(signer) || new.attested_by != old.attested_by)
    {
        bail!(
            "lockfile generation would change the recorded signer; previous files were preserved"
        );
    }
    // Preserve identities across reordering, then pair replaced URLs in their
    // configured order so version upgrades retain the previous trust baseline.
    let mut replacements = new.additional_artifacts.iter().filter(|artifact| {
        !old.additional_artifacts
            .iter()
            .any(|old| old.url == artifact.url)
    });
    for artifact in &old.additional_artifacts {

View on GitHub (pinned to 533346cc37)