• v0.1.5 c2d913da37

    v0.1.5
    All checks were successful
    CI / cargo (push) Successful in 38s
    CI / docker (push) Successful in 21s
    Stable

    jlxq0 released this 2026-08-28 01:55:05 +00:00 | 14 commits to main since this release

    Stops the initialize rate limiter livelocking the fleet. Every new MCP session
    was refused for hours on 2026-08-28 and the server recorded nothing about it.

    • A refused request still spent a token: check() charged the per-bearer bucket
      and then tested the per-subject bucket, so retries consumed every refill
      without reaching the two tokens one connection needs. It now charges exactly
      one bucket.
    • Burst 8 to 32. MAX_SESSIONS, the actual flood defence, is untouched at 256.
    • The refill is no longer anchored to SESSION_KEEP_ALIVE: one token per minute
      rather than one per thirty, because the old anchor tied recovery to session
      lifetime and made one connection cost an hour of fleet silence.
    • Adds carddav_mcp_initialize_rejected_total{bucket} and a warn line. Nothing
      recorded a rejection before, which is why the outage had to be reconstructed
      from what succeeded and from the silence after it.

    Acceptance, and what it does not show

    Pod carddav-mcp-7bf7b8ff4d-nw6kl, sha256:85da0deb…, up 02:07:22Z from a
    zero baseline. Three connections from the three agent sessions that hold a
    carddav mount, inside four minutes:

    refused MCP initialize    0
    create new session        6
    client initialized        3      2:1, no orphans
    authenticated requests   23
    distinct token_hash       1
    xff_entries / hops       23 of 23 at 2 / 2 / resolved
    

    This window would have passed on v0.1.4 as well. Six charges against the old
    burst of eight, and a pod replacement resets the buckets either way, so it
    evidences the rollout rather than the fix. The charge-order path never executed:
    zero rejections means the changed code did not run.

    The fix is evidenced by mutation, not in the field. Restoring the old order
    turns a_refused_initialize_spends_nothing red with left: Err(Bearer), right: Err(Subject) — the rejected request having spent its own token. Reverting the
    burst or the replenish period each kills only its own test.

    The field case cannot be constructed with three mounts: distinguishing the
    images needs five connections in one window, or a drained bucket observed
    recovering.

    v0.1.4's trusted-proxy-hop fix survives the upgrade unchanged, which is the
    regression nobody thinks to check.

    Downloads