astrid-runtime/astrid · error

signed channel lifetime exceeds the maximum for its channel

Error message

signed channel lifetime exceeds the maximum for its channel

What it means

Each channel has a maximum allowed pointer lifetime: stable 30 days, dev 7 days, nightly 2 days. Even if expiry is after publication, a pointer valid longer than its channel's cap is rejected, because long-lived pointers undermine the short-expiry trust model (a compromised file would remain usable too long).

Solutions

  1. Set `expires_at = published_at + max_lifetime` for the channel (30d stable, 7d dev, 2d nightly) and republish
  2. Parameterize the publishing script's expiry per channel instead of sharing one constant
  3. If the pointer came from upstream and is oversized, report/fix the release automation and use a freshly generated pointer
  4. Shorten the lifetime and re-sign the metadata

Example fix

// before: nightly published with stable-style expiry
let expires = published + Duration::days(30);
// after
let expires = match channel {
    UpdateChannel::Nightly => published + Duration::days(2),
    UpdateChannel::Dev => published + Duration::days(7),
    UpdateChannel::Stable => published + Duration::days(30),
};
Defensive patterns

Strategy: validation

Validate before calling

fn lifetime_within_cap(p: &ChannelPointer, channel: UpdateChannel) -> bool {
    let days = match channel { UpdateChannel::Stable => 30, UpdateChannel::Dev => 7, UpdateChannel::Nightly => 2 };
    p.expires_at.signed_duration_since(p.published_at) <= chrono::Duration::days(days)
}

Type guard

fn lifetime_ok(p: &ChannelPointer, channel: &UpdateChannel) -> bool {
    let max = match channel {
        UpdateChannel::Stable => chrono::Duration::days(30),
        UpdateChannel::Dev => chrono::Duration::days(7),
        UpdateChannel::Nightly => chrono::Duration::days(2),
    };
    p.expires_at.signed_duration_since(p.published_at) <= max
}

Try / catch

match parse_channel(&bytes, channel, now) {
    Err(e) if e.to_string().contains("exceeds the maximum") => {
        anyhow::bail!("publisher misconfigured expiry for this channel; report to feed operator")
    }
    other => other?,
}

Prevention

When it happens

Trigger: parse_channel or enforce_continuity reads a pointer where `expires_at - published_at` exceeds the cap for `expected_channel`, e.g. a nightly with expires 30 days after publish.

Common situations: Publisher script reusing the stable expiry (30d) constant for nightly/dev builds; hand-edited expiry extended to 'keep updates working'; template files copied between channels without adjusting lifetimes.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/c7c7f54d5f2d3a8c. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/update_channel.rs:497

    let published = canonical_time(&pointer.published_at, "published-at")?;
    let expires = canonical_time(&pointer.expires_at, "expires-at")?;
    ensure!(expires > published, "signed channel lifetime is invalid");
    if let Some(now) = now {
        ensure!(now <= expires, "signed channel metadata has expired");
        let latest_reasonable_publication = now
            .checked_add_signed(chrono::Duration::seconds(MAX_FUTURE_SKEW_SECS))
            .context("channel publication skew overflowed the clock")?;
        ensure!(
            published <= latest_reasonable_publication,
            "signed channel published-at is unreasonably far in the future"
        );
    }
    let max_lifetime = match expected_channel {
        UpdateChannel::Stable => chrono::Duration::days(30),
        UpdateChannel::Dev => chrono::Duration::days(7),
        UpdateChannel::Nightly => chrono::Duration::days(2),
    };
    ensure!(
        expires.signed_duration_since(published) <= max_lifetime,
        "signed channel lifetime exceeds the maximum for its channel"
    );
    let version = canonical_version(&pointer.release.version)?;
    let nightly_commit = nightly_source_commit(&version);
    match expected_channel {
        UpdateChannel::Nightly => ensure!(
            nightly_commit.is_some() && version.build.is_empty(),
            "nightly channel must point to an exact nightly prerelease"
        ),
        UpdateChannel::Stable | UpdateChannel::Dev => ensure!(
            version.pre.is_empty() && version.build.is_empty(),
            "stable and dev channels must point to canonical releases"
        ),
    }
    ensure!(
        pointer.release.tag == format!("v{version}"),
        "signed channel release tag does not match its version"

View on GitHub (pinned to affd8760f4)