• v0.9.2 d2e11d1dbb

    v0.9.2
    All checks were successful
    CI / gate (push) Successful in 1m9s
    CI / image (push) Successful in 53s
    Stable

    joeyr released this 2026-08-10 21:07:03 -05:00 | 88 commits to main since this release

    The answers-short-without-erroring group, which is the defect class this project keeps
    finding: three of the four live defects found so far were in it. Also the OAuth regression
    coverage that had been outstanding since before 0.9.0. Phase 2 of docs/audit.md.

    Added

    • truncated and events_found in the read envelopes. MAX_EVENTS (default 500)
      truncates in two places — per calendar during expansion, and again across the merged
      list. Both logged a warning; neither reached the caller. So list_events over a busy
      month returned count: 500 and a clean envelope, and the model reported that as the
      user's whole month. A request that failed, reporting success.

      FetchResult now carries whether anything was dropped and how many there were, counted
      before the limit rather than inferred from the length that came back — which cannot tell
      a window holding exactly the limit from one holding ten times it.

      The find_free_time wording is deliberately harder than list_events', because the
      consequence inverts. Dropping events from a listing shortens the answer. Dropping them
      from an availability search removes busy time, so the tool invents free slots and
      offers to book over meetings that exist — the same argument
      _reject_unknown_calendars already makes about unknown calendar names. Both tools log
      truncated_from too, so a short count has one more explanation on the same line.

    • The browser half of the sign-in flow is regression-tested. Dynamic registration,
      /authorize, the consent screen and the hand-off to Forgejo now run in the suite
      against the real ASGI stack. It pins the things that fail silently: the upstream
      redirect URI is this server's /auth/callback rather than Claude's (the
      redirect_uri_mismatch in the operations table), PKCE is forwarded under a second
      challenge of the proxy's own, declining consent never reaches Forgejo, a registered
      client survives a restart, tokens are checked by introspection with no required scope,
      and the signing key is derived from configuration rather than generated per process —
      the last of which presents only as everyone being signed out for no visible reason.

      Forgejo's own answer and the token exchange still need a network. Neither has ever been
      where this broke.

    Fixed

    • Two calendars sharing a display name shared a cache entry. fetch_events keys its
      results by index, with a comment saying why: two collections on one account can share a
      name, and collapsing them would drop a calendar's events while reporting success. The
      resource cache key, seven lines later, was the name. First request, both missed and both
      searched — correct, by luck. Any request inside EVENT_CACHE_TTL, both indices hit
      whichever was written second: one calendar's events returned twice, the other's gone,
      nothing in unavailable. A follow-up question about the same week is exactly the
      traffic the cache exists to serve. Keyed by collection URL now, which is unique, stable,
      and stays out of the log line because it carries the account DSID.

    • A basic-format end date silently excluded its own day. parse_when decided a value
      was date-only with len(text) == 10, which is true of 2026-08-07 and false of
      20260807 — a spelling datetime.fromisoformat accepts perfectly happily on 3.11+. So
      end="20260807" parsed clean to midnight, skipped the end-of-day widening, and dropped
      everything on the 7th with no error. The tool description asks for YYYY-MM-DD, but it
      also accepts "a full ISO 8601 datetime", and basic format is ISO 8601. Decided from the
      parse now, which cannot drift from what was actually parsed.

    Downloads