• v0.40.0 31af8520ef

    joeyr released this 2026-08-24 09:29:42 -05:00 | 89 commits to main since this release

    Canvas is the fifth tenant, and the first whose credential changes under the
    running process.
    C1 of docs/canvas-plan.md: the seam, the four rotation rules, the
    preflight line naming which credential it booted with, and one tool. Not deployed yet.

    The institution has disabled personal access token generation on this account, which
    closes the Authorization: Bearer path every public Canvas MCP server assumes. What is
    left is the browser session cookie, and Canvas re-issues it as the session slides.

    Three of the four rules fail silently

    A superseded canvas_session stays valid until its own embedded clock runs out, so a
    client that breaks one of these keeps working for hours and then stops for reasons
    nothing connects to the break.

    • Rotate only on a clean 200, and only when a value came back. The point is not
      gaining a cookie but not losing one. A 200 carrying no Set-Cookie is the other
      half
      — Canvas re-issues only once the value carries some age, so a client that reads
      a missing cookie as failure is broken from its second request.
    • Follow no redirects. An expired session bounces to the login page, and a client
      that follows it gets 200 and an HTML body, which reads as success. The content type
      is checked BEFORE the rotation for the same reason.
    • One writer — one replica, and a lock inside it.
    • The previous value is kept, so a bad write is recoverable without a browser.

    Added

    • list_courses, and deliberately nothing else. C1 proves the credential in
      production rather than delivering a surface. It reports a partial page rather than
      returning one — Canvas defaults to ten per page, and a client that ignores Link:
      returns the first ten and reports success.
    • A keepalive, part of the tenant rather than an optimisation. Measured against
      superseded values of known age: alive at 10.7 hours, alive at 19.3, dead at 51.6.
      A coursework connector is exactly the thing nobody calls over a weekend.
    • docs/operations.md gained Re-seeding the Canvas session, before deployment
      rather than after the first expiry — including the trap that editing
      CANVAS_COOKIE_SEED does not re-seed a running tenant
      , because the jar wins over
      the seed whenever it holds anything.
    • deploy/nginx/canvas.conf. What is behind this hostname is an education record.

    Testing

    All ten new guards were proved by breaking them. The concurrency test caught itself
    out first: it asserted "every value sent had been stored at some point", which passed
    with the lock removed
    — a thread sending a stale cookie is still sending a real one.
    Rewritten to assert what the lock actually promises, it fails immediately.

    The transport is httpx.MockTransport driving real httpx. Rule 1 turns entirely on what
    Response.cookies does with a Set-Cookie header, so a hand-written fake would be
    testing the fake.

    1349 tests green.

    Downloads