• v0.29.4 029db76b63

    0.29.4 — every tenant serves its healthcheck
    All checks were successful
    CI / gate (push) Successful in 1m26s
    CI / image (push) Successful in 14s
    Stable

    joeyr released this 2026-08-21 23:05:03 -05:00 | 16 commits to main since this release

    The Gmail tenant served no /healthz, so its container never started. Third defect
    found by deploying rather than by testing, and the one that named itself least.

    Fixed

    • build_gmail_mcp registered no healthcheck route. The image carries
      HEALTHCHECK CMD ... urlopen('http://127.0.0.1:8787/healthz'), so the request 404'd,
      failed three times, and Swarm killed the task before it reached RUNNING — over and over.

      Nothing in the symptom named the healthcheck. The boot log was flawless: tenant=gmail,
      all seven preflight checks passed, check=imap detail=10 folder(s) readable, serving | tenant=gmail path=/gmail port=8787. Portainer showed the task stuck in starting
      forever. nginx returned 502, because an unhealthy task is never added to the service VIP,
      so there was nothing behind the proxy to reach. Three layers, three plausible wrong
      diagnoses, and the actual cause invisible from all of them.

      Preflight cannot catch this: it runs before the app is built and asks about IMAP and
      Forgejo, not about our own routing table.

    • subscriptions had the identical gap, since 0.21.0. The only reason it was not the
      tenant that failed is that it has never been deployed. Fixed in the same commit.

    Added

    • tests/test_healthcheck.py — every tenant in TENANTS, asserted against the built
      app's route table. Written as a loop rather than a test beside each tenant, because the
      failure mode is a tenant being forgotten, and a per-tenant test is forgotten in exactly
      the same motion as the route it would have checked. A fifth tenant fails here on the day
      it is added.

    Notes

    • Three defects, three layers, none reachable from the one below. The gate proved the
      code; G1's live pass drove the tool layer and found the unread overcount; the first boot
      found the tenant dispatch; the first deploy found this. Each needed the layer above to
      exist before it could fail — which is the argument for standing a thing up rather than
      declaring it done when the suite is green.
    Downloads