astrid-runtime/astrid · error

second probe blocks

Error message

second probe blocks

What it means

Test assertion in `RedeemRateLimiter`: after a first successful `check(ip, interval)` returns `None` (allowed), the second call within the window must return `Some(wait)` — the remaining wait duration. The `expect("second probe blocks")` fires when the second probe was unexpectedly allowed (`None`), meaning the rate limiter failed to block a repeat request inside the interval.

Solutions

  1. Verify `check` records the IP's last-probe timestamp on the first call and compares `elapsed < interval` on the second.
  2. Confirm `RedeemRateLimiter::default()` actually initializes its map (not left empty after each check).
  3. Check the interval arithmetic uses a monotonic clock (`Instant`) rather than system time.
  4. Ensure tests don't share a global limiter state that another test reset.

Example fix

// before
let wait = limiter.check(ip, interval).expect("second probe blocks");
// after
let wait = limiter.check(ip, interval).unwrap_or_else(|| {
    panic!("rate limiter allowed a second probe within {interval:?}; entries: {:?}", limiter.entries())
});
Defensive patterns

Strategy: validation

Validate before calling

// Rust: assert limiter state before second probe
assert!(limiter.last_probe(&ip).is_some(), "first probe must be recorded");

Try / catch

// Rust: treat a None on a blocked-expected probe as a test failure with context
match limiter.check(ip, interval) {
    Some(wait) => assert!(wait > Duration::ZERO),
    None => panic!("second probe within {interval:?} was not blocked"),
}

Prevention

When it happens

Trigger: Calling `limiter.check(ip, interval)` twice for the same IP within a `Duration::from_mins(1)` window and getting `None` on the second call — the limiter did not record the first probe or reset its window incorrectly.

Common situations: A regression in `RedeemRateLimiter::default()` or `check` (e.g. keying by the wrong field, using wall-clock instead of instant comparison, or clearing entries per call); also system clock changes breaking interval math.

Related errors


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

Appendix: source

Thrown at crates/astrid-gateway/src/state.rs:499

        Ok(
            crate::bus_kernel::BusKernelClient::new(bus, caller.principal.clone(), session_id.0)
                .with_device_key_id(caller.device_key_id.clone()),
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rate_limiter_blocks_within_window() {
        let mut limiter = RedeemRateLimiter::default();
        let ip: IpAddr = "127.0.0.1".parse().unwrap();
        let interval = Duration::from_mins(1);

        assert!(limiter.check(ip, interval).is_none());
        let wait = limiter.check(ip, interval).expect("second probe blocks");
        assert!(wait > Duration::from_secs(0));
    }

    #[test]
    fn rate_limiter_zero_interval_never_blocks() {
        let mut limiter = RedeemRateLimiter::default();
        let ip: IpAddr = "127.0.0.1".parse().unwrap();
        let interval = Duration::from_secs(0);
        // Zero interval: every probe should be free regardless.
        assert!(limiter.check(ip, interval).is_none());
        assert!(limiter.check(ip, interval).is_none());
    }

    #[test]
    fn signing_material_round_trips() {
        use ed25519_dalek::{Signer, Verifier};
        let s = SigningMaterial::fresh();
        let msg = b"hello world";

View on GitHub (pinned to affd8760f4)