-
v0.57.0 Stable
released this
2026-09-21 19:01:08 -05:00 | 48 commits to main since this releaseTenant #7, weather, W1. A tenant whose correctness claim is about time rather than
about content: every other one here answers "what does the provider hold", and this one
answers "what does the provider hold, and when was that true". It exists because a NOAA
raw METAR text file came back dated eight months earlier under a clean HTTP 200, with
nothing in the response to say so.It ships code-only and undeployed, deliberately, and that is written down rather than
discovered later. The Forgejo OAuth app, the stack service and the proxy host are the
actual gate and none of them is in this repo — so the A8-equivalent live pass is
impossible until they exist.subscriptionshas been in that state for thirty-six
releases; the difference between "parked" and "forgotten" is whether somebody said which.The W0 probe that ran during the review invalidated the plan's centrepiece, and the
build is shaped around the answer. Counted by hours covered rather than by entry count,
one live gridpoint'swindDirectionseries was 17% native andwindSpeed9% —
so nine tenths of any transition found in the grid is an artifact of how it is encoded.
The grid cannot be the surface a wind-shift scan runs over, andget_forecast_gridis
built to say so in the response rather than to hide it.Added
mcp_gateway/weather/— the seventh tenant, 1,614 lines across four modules.
envelope.py(the staleness wrapper every tool returns through),client.py(four
upstreams, per-host headers, typed errors, the size cap and coordinate redaction),
grid.py(ISO 8601 durations, native-versus-filled, the unit layer) andtools.py.get_forecast_grid— the NWS raw forecast grid for any US coordinate. It returns
the grid's blocks, never an hourly expansion, and reportsnative_shareper
parameter so a caller can see how much of a window is a forecast for that hour and how
much is one value stretched across an afternoon.- The staleness envelope. One wrapper, not a field each tool remembers to populate:
source,issued,fetched,cached,age_minutes,age_unknown,stale. Age
comes from the payload's own issuance time and is measured from now rather than from
when the bytes arrived, so a cached body does not report the age it had when it landed.
An absent issuance time givesstale: null— neverfalse. - Two thresholds per endpoint, and only the second raises.
stale_afteris a
freshness judgement;absurd_afteris a sanity ceiling set against the eight-month
METAR. Putting the ceiling where the threshold belongs would turn "this station reports
hourly" into a tool error. tests/test_no_pii.pyrefuses a coordinate-shaped literal, allowlisted rather than
blocklisted — a rule that looked for coordinates near a named region would have to
write that region down, in the public file whose whole job is not writing it down, and
would still pass on a coordinate from wherever the operator was travelling. Ships with
a negative control.deploy/nginx/weather.conf, written and parked. The sixth copy;proxy_read_timeout 120s, matchingcanvas.conf.- Five settings:
WEATHER_CONTACT_EMAIL(required — NWS publishes no rate limit and
enforces its contact-address courtesy by blocking),WEATHER_MODEL_CONUS,
WEATHER_MODEL_GLOBAL,WEATHER_TIMEOUT_SECONDS,WEATHER_MAX_RESPONSE_BYTES. There
is no location, no station list and no alias table, and that is the design: every
tool takes a coordinate as an argument. - 82 tests — 78 in
tests/test_weather.py, two for the coordinate guard and two from
the healthcheck loop. Thirteen deliberate mutations were run against them and every
one was caught, including the first-pass native rule, thewmoUnit:km_hspelling, an
index-based expansion,generatedAtread as the issuance time, an unredacted log URL
and a fall-through in the healthcheck builder.
Fixed
tests/test_healthcheck.pywas testing the calendar tenant three times. Its
per-tenant builder was a chain ofifs ending in a fall-through tobuild_app, and
canvasandespnffwere never added — so from the day each shipped, a loop written
precisely to catch a forgotten tenant built the calendar app for them, found/healthz
on it, and passed. A loop overTENANTSthat silently tests one tenant twice is worse
than no loop, because the file's own docstring promises the opposite. It now names
every tenant and raises on the fall-through. Both were fine; nothing was broken by this,
which is the only reason it survived two releases.__main__'s tenant dispatch ended in a bareelsethat built the iCloud calendar
app. A tenant added toTENANTSand forgotten there did not fail — it became the
calendar tenant, booting cleanly, passing its healthcheck and serving somebody else's
tools on the new hostname. Now an explicit branch per tenant with anelsethat logs
and returns 1.Config.from_envdemanded an Apple ID by default.required=not (mail or subs or gmail or canvas or espnff)meant "the calendar tenant" and said so only by listing
everything else, so every tenant added after it inherited the demand and the only thing
in the way was somebody remembering to extend anot (...). Both credential conditions
are named positively now; behaviour is unchanged.docs/operations.mdstep 2 said "the 300s timeouts" and it is two numbers.
icloudcalendar.conf,icloudmail.confandgmail.confuse 300s;
subscriptions.conf,canvas.confandweather.confuse 120s. The split tracks what
is behind the location — a credential and a protocol that can walk a mailbox, versus one
or two HTTPS GETs — so the checklist now says to copy the conf of the tenant yours
resembles and read its comment.
Changed
force_refreshis floored rather than refused inside the TTL. The plan specified
that a forced refresh inside the endpoint's TTL should return the cached value, which
reads cleanly and would have made the argument do nothing at all — the cache only ever
holds entries inside their TTL. It is refused outright on the two deterministic
mappings, and elsewhere honoured at most once per 60 seconds per key, saying so when it
declines.CLAUDE.mdgained A tenant whose correctness claim is about time — five of the
plan's seven→ CLAUDE.mdpieces, which leftdocs/weather-plan.mdin the same commit
that finished them. The plan went from 819 lines to 430 and holds only W2 onward..env.example,README.mdanddeploy/README.mdall carry the seventh tenant.
0.55.4 shipped to fix a stale conf count in the last of those, so it is a known miss
rather than a hypothetical one.
Not built, and named so it is not mistaken for an oversight
- The Open-Meteo model-slug preflight check. Both slugs are configuration as of this
release and no code path reads them until the model tool exists in W2, so a boot check
on them would dial a third-party API to validate a setting nothing uses. - An hourly expansion in a tool response.
grid.expandexists for the consumers that
need hours — the wind-shift scan, the fill statistics — and every sample it yields
carriesnative. Putting its output in a response would hand a caller twelve identical
rows that read as twelve forecasts.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads