Skip to content

Build1 publisher2 min readPublished

Freezing the digest rules table as data, not the window object, stops an agent defaulting to UTC and inclusive bounds

A dev.to case study builds the timezone calculator and its pytest suite first, then lets a model write the mail worker that may only call it, with 8 March 2026 pinned as the fixture no regeneration may delete.

The Engineer · Build desk

Illustration accompanying Freezing the digest rules table as data, not the window object, stops an agent defaulting to UTC and inclusive bounds

What happened

  • A dev.to case study rewrites a vague daily-digest request as a timezone-strict calculator plus a pytest suite, both of which run green before any model is allowed to write the mail worker.
  • The author reads "send each tenant a daily digest of yesterday's events" as hiding three decisions: which timezone defines yesterday, whether the end bound is exclusive, and where late rows go.
  • Left unconstrained, according to the case study, an agent picks UTC, filters with inclusive SQL between, and then backfills the late events into a second mail.
  • The tests pin 8 March 2026, the US spring-forward date, because that local Sunday lasts twenty-three hours and so blocks any attempt to compute the window by epoch division.
  • The calculator stays in a single module with no HTTP, no database and no SMTP client, which is the boundary the exercise exists to demonstrate.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision A rule that lives only in a prompt gets simplified away by the next generation, so the decision table has to be committed to git before the first chat with a coding model opens.
  • precedent A generated change that deletes the DST fixture is rejected even when the happy-path test is green. Test files become review surface.
  • constraint Acceptance for generated code moves to call-graph shape. A worker whose output happens to be right still fails review if it skips the calculator and does its own local date arithmetic.
  • exposure Production event rows stay out of the assistant until the window math is proven, so the debugging loop runs on fixtures and a tenant's event history never enters a prompt.

04:10 UTC breaks a UTC default without any help. In America/New_York it reads 23:10 on the previous local day while the zone is on standard time, and 00:10 on the same local day once daylight time starts [1]. A filter that groups by UTC calendar day files that row under one date all year. One tenant and one event are enough to show it. The case study stays that small [5].

The second decision is the bound. The committed table says the window is half-open: `start_utc <= event < end_utc` [8]. The author writes that an unconstrained agent reaches for inclusive SQL `between` instead [4]. That is documented behaviour, and it is the wrong default here: two consecutive inclusive windows share their boundary instant, so a row landing exactly on the boundary is selected by both days' queries, while half-open bounds select it once [4]. Events at or after `end_utc` are labeled `late_for_this_window` and never silently merged [9].

A civil date keeps a generated change honest. US clocks spring forward on 8 March 2026, so that local Sunday lasts 23 hours [12]. Call `window_for_local_date` for America/New_York and 2026-03-08 and you get `start_utc` at 05:00 on the 8th and `end_utc` at 04:00 on the 9th [2]. An implementation that adds 24 hours to the start lands at 05:00 on the 9th and drags in the first local hour of Monday [3]. An event at 04:10 UTC on 9 March sits ten minutes past the real boundary, so the calculator calls it late, while a UTC-day filter counts it as the 9th [6].

The fail-closed path is short. `require_timezone` rejects None, the empty string and whitespace, then raises `MissingTimezoneError` [17], which subclasses `ValueError` [18]. `membership` raises on a naive datetime before it compares anything [20]. `Window` is a frozen dataclass, so a caller cannot move `end_utc` after the query has started [16].

The module holds no HTTP client, database handle or SMTP client [14]. The case study says agents fold date math into the worker, and asks reviewers to read the import graph as strictly as the date arithmetic [15]. A generated worker passes when it imports the calculator [22] and fails when it inlines local date arithmetic of its own [26].

The code is short enough to copy, but the published listing stops partway through `membership`, so the comparison logic and the pytest suite are left to the reader [27]. digest-window is labeled an exercise, not a production incident [2]. `window_id` ports cleanly into an existing system; for this tenant and date it is `America/New_York:2026-03-08` [5], and the table requires it in the log line and in the mail header [11].

What to watch

  • Whether the pytest suite and the finished membership comparison are published, since the listing stops mid-function.
  • Whether a fall-back fixture joins the 8 March one; the published material pins only the spring-forward date.
  • Whether the worker drafted by a free coding model holds the import boundary, or reintroduces local date math.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories