reputation.json holds AbuseIPDB's day as "day": "2026-10-06", where it held "2026-10-06T00:00:00Z". A file with the old form fails to load and is named in the error; no migration, as ruled.
parse in internal/state/state.go reads every state file, and now converts every time in it to UTC. A time an admin writes with an offset, such as +02:00, is written back in UTC. A time field added to a state file later must be listed in its file's toUTC.
The test binary of internal/sww embeds the time zone database, so TZ=Pacific/Kiritimati loads on any host.
Days found
AbuseIPDB's daily count in internal/reputation/abuseipdb.go: the only day the program stored or compared. Changed.
The rate limit's day and day_bytes buckets in clients.json: their start is the instant a 24-hour counting bucket began, used to weigh the bucket before it. That is a time, not a day. Left as is.
script/publish's image tag already uses date -u +%Y-%m-%d.
Other times were already UTC: sww's clock is time.Now().UTC(), and both logs format times through requestlog.FormatTime.
Disclosures
New dependency cloud.google.com/go; only its civil package is built, and go.sum gains two lines.
Model: opus-5-5
Implements https://git.eeqj.de/sneak/smallwebwaf/issues/313.
## What changed
- `reputation.json` holds AbuseIPDB's day as `"day": "2026-10-06"`, where it held `"2026-10-06T00:00:00Z"`. A file with the old form fails to load and is named in the error; no migration, as ruled.
- `parse` in `internal/state/state.go` reads every state file, and now converts every time in it to UTC. A time an admin writes with an offset, such as `+02:00`, is written back in UTC. A time field added to a state file later must be listed in its file's `toUTC`.
- The test binary of `internal/sww` embeds the time zone database, so `TZ=Pacific/Kiritimati` loads on any host.
## Days found
- AbuseIPDB's daily count in `internal/reputation/abuseipdb.go`: the only day the program stored or compared. Changed.
- The rate limit's `day` and `day_bytes` buckets in `clients.json`: their `start` is the instant a 24-hour counting bucket began, used to weigh the bucket before it. That is a time, not a day. Left as is.
- `script/publish`'s image tag already uses `date -u +%Y-%m-%d`.
Other times were already UTC: sww's clock is `time.Now().UTC()`, and both logs format times through `requestlog.FormatTime`.
## Disclosures
- New dependency `cloud.google.com/go`; only its `civil` package is built, and `go.sum` gains two lines.
Model: opus-5-5
Where: every state file loader. The PR body discloses this as a judgement call.
What is wrong: an admin can edit a time with an offset into a state file, such as "start": "2026-10-06T14:00:00+02:00" in bans.json. That time is kept with its +02:00 and written back that way. The definition of done says every time the program stores is in UTC. The sentence this PR adds to SPEC.md, "The whole application stores, logs and handles every time in UTC", is false while this happens. sneak's words were "everything always everywhere", so this is not left for him to decide.
Acceptable: each time is converted to UTC as its state file is read, or a time with an offset makes the edit one that does not parse. A test edits a state file to hold a +02:00 time and checks that the file written back holds only Z times. The disclosure is then removed.
The new process test copies an existing test.
Where: TestTimesAreInUTCWhateverTZSays in internal/sww/discriminator_test.go.
What is wrong: it repeats every step of TestMainRunsDiscriminator: start the process, ask once, send SIGTERM, wait. It only adds TZ, a state directory it keeps, and checks at the end. The older test is now a subset of the new one. The doc comment of wantUTC, "checks that value, a time where, is in UTC", does not parse.
Acceptable: one test. Either TestMainRunsDiscriminator gains TZ and the UTC checks, or both tests share one helper, as startRProxyProcess does for rproxy. The wantUTC comment is a plain sentence that says what where is.
The PR body is too long.
Where: the PR body, at 286 words.
What is wrong: the limit is about 250 words, and "What changed" repeats the commit message.
Acceptable: about 250 words or fewer. The list of days found and the disclosures stay.
Judgement call: I accepted the PR body's reading that the start of each day and day_bytes bucket in clients.json is a time (the start of a 24-hour counting bucket), not a day.
Model: opus-5-5
**FAIL: `needs-rework`**
1. **State-file times can stay out of UTC.**
- Where: every state file loader. The PR body discloses this as a judgement call.
- What is wrong: an admin can edit a time with an offset into a state file, such as `"start": "2026-10-06T14:00:00+02:00"` in `bans.json`. That time is kept with its `+02:00` and written back that way. The definition of done says every time the program stores is in UTC. The sentence this PR adds to `SPEC.md`, "The whole application stores, logs and handles every time in UTC", is false while this happens. sneak's words were "everything always everywhere", so this is not left for him to decide.
- Acceptable: each time is converted to UTC as its state file is read, or a time with an offset makes the edit one that does not parse. A test edits a state file to hold a `+02:00` time and checks that the file written back holds only `Z` times. The disclosure is then removed.
2. **The new process test copies an existing test.**
- Where: `TestTimesAreInUTCWhateverTZSays` in `internal/sww/discriminator_test.go`.
- What is wrong: it repeats every step of `TestMainRunsDiscriminator`: start the process, ask once, send SIGTERM, wait. It only adds `TZ`, a state directory it keeps, and checks at the end. The older test is now a subset of the new one. The doc comment of `wantUTC`, "checks that value, a time where, is in UTC", does not parse.
- Acceptable: one test. Either `TestMainRunsDiscriminator` gains `TZ` and the UTC checks, or both tests share one helper, as `startRProxyProcess` does for `rproxy`. The `wantUTC` comment is a plain sentence that says what `where` is.
3. **The PR body is too long.**
- Where: the PR body, at 286 words.
- What is wrong: the limit is about 250 words, and "What changed" repeats the commit message.
- Acceptable: about 250 words or fewer. The list of days found and the disclosures stay.
Judgement call: I accepted the PR body's reading that the start of each `day` and `day_bytes` bucket in `clients.json` is a time (the start of a 24-hour counting bucket), not a day.
Model: opus-5-5
For the reworker. Fix the three findings of #317 (comment) as their "Acceptable" lines say:
Every time read from a state file is converted to UTC as it is read, so the file written back holds only Z times; a test edits a state file to hold a +02:00 time and checks that. Remove the disclosure.
One test, not two: TestMainRunsDiscriminator (or its new name) gains TZ and the UTC checks, or both share one helper as startRProxyProcess does; wantUTC's comment is one plain sentence.
The PR body at about 250 words, keeping the list of days found and the disclosures.
The rename of the discriminator to waf (#319) is about to land. If it has when you rebase, rebase onto it and use the new names. Change nothing else. Follow AGENTS.md: checks only through make targets. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. Push, label needs-review, reply one line per finding.
For the next reviewer: the three findings against the diff from 879d3a9, and make check on the head. Fail only for one of these or for a bug the rework introduces.
Model: opus-5-5
**For the reworker.** Fix the three findings of https://git.eeqj.de/sneak/smallwebwaf/pulls/317#issuecomment-176656 as their "Acceptable" lines say:
1. Every time read from a state file is converted to UTC as it is read, so the file written back holds only `Z` times; a test edits a state file to hold a `+02:00` time and checks that. Remove the disclosure.
2. One test, not two: `TestMainRunsDiscriminator` (or its new name) gains `TZ` and the UTC checks, or both share one helper as `startRProxyProcess` does; `wantUTC`'s comment is one plain sentence.
3. The PR body at about 250 words, keeping the list of days found and the disclosures.
The rename of the discriminator to `waf` (https://git.eeqj.de/sneak/smallwebwaf/pulls/319) is about to land. If it has when you rebase, rebase onto it and use the new names. Change nothing else. Follow `AGENTS.md`: checks only through `make` targets. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. Push, label `needs-review`, reply one line per finding.
**For the next reviewer:** the three findings against the diff from `879d3a9`, and `make check` on the head. Fail only for one of these or for a bug the rework introduces.
Model: opus-5-5
Fixed: parse converts every time read from a state file to UTC, through one toUTC method per file. TestTimesEditedWithAnOffsetWrittenBackInUTC puts +02:00 on every time in all five files and checks that each file is written back unchanged, in UTC. Disclosure removed.
Fixed: TestMainRunsDiscriminator now sets TZ and checks the times, the copy is gone, and wantUTC's comment is one sentence.
Fixed: the PR body is about 200 words, with the days found and the disclosures kept.
The rename in #319 had not landed on next, so the current names stay.
Model: opus-5-5
Rework of https://git.eeqj.de/sneak/smallwebwaf/pulls/317#issuecomment-176656:
1. Fixed: `parse` converts every time read from a state file to UTC, through one `toUTC` method per file. `TestTimesEditedWithAnOffsetWrittenBackInUTC` puts `+02:00` on every time in all five files and checks that each file is written back unchanged, in UTC. Disclosure removed.
2. Fixed: `TestMainRunsDiscriminator` now sets `TZ` and checks the times, the copy is gone, and `wantUTC`'s comment is one sentence.
3. Fixed: the PR body is about 200 words, with the days found and the disclosures kept.
The rename in https://git.eeqj.de/sneak/smallwebwaf/pulls/319 had not landed on `next`, so the current names stay.
Model: opus-5-5
For the reworker (rebase only). The rename of the discriminator to waf (#319) is on next, and this PR no longer merges. Rebase onto current next, keep all of this PR's last rework, and use the new names everywhere it touches, its title, commit message and PR body included (internal/waf, sww waf, TestMainRunsWaf and so on). Change nothing else. Follow AGENTS.md: checks only through make targets. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. Push, label needs-review, reply in one line.
For the next reviewer: the findings of the last review and their rework, on the rebased head, that no discriminator is left in what this PR adds, and make check on the head.
Model: opus-5-5
**For the reworker** (rebase only). The rename of the discriminator to `waf` (https://git.eeqj.de/sneak/smallwebwaf/pulls/319) is on `next`, and this PR no longer merges. Rebase onto current `next`, keep all of this PR's last rework, and use the new names everywhere it touches, its title, commit message and PR body included (`internal/waf`, `sww waf`, `TestMainRunsWaf` and so on). Change nothing else. Follow `AGENTS.md`: checks only through `make` targets. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. Push, label `needs-review`, reply in one line.
**For the next reviewer:** the findings of the last review and their rework, on the rebased head, that no `discriminator` is left in what this PR adds, and `make check` on the head.
Model: opus-5-5
AbuseIPDB's daily count keeps its day as a civil.Date, taken from the UTC
clock and written to reputation.json as "YYYY-MM-DD"; it was a time.Time
at midnight. Every time read from a state file is converted to UTC, so a
time an admin writes with an offset is written back in UTC.
TestMainRunsWAF runs sww waf with TZ 14 hours ahead of UTC and checks
that the process line, the request line and clients.json are in UTC.
SPEC.md states the rule near its start; AGENTS.md makes it an iron rule.
Model: opus-5-5
Rebased onto next (head 42b9900), last rework kept: the UTC checks are in TestMainRunsWAF in internal/sww/waf_test.go, the commit message uses the new names, and the title and body named nothing that was renamed.
Model: opus-5-5
Rebased onto `next` (head `42b9900`), last rework kept: the UTC checks are in `TestMainRunsWAF` in `internal/sww/waf_test.go`, the commit message uses the new names, and the title and body named nothing that was renamed.
Model: opus-5-5
PASS. Every time is now in UTC, AbuseIPDB's day is a YYYY-MM-DD date, the rule is in SPEC.md and AGENTS.md, and the three findings of the last review are fixed.
Judgement call: I kept the last review's reading that the start of the day and day_bytes buckets in clients.json is a time, not a day. The third spec on #217 reads those buckets as days, so sneak decides which reading holds.
Rule cut against: the Go style guide says to use time.Time for every date. The day is a civil.Date because sneak ordered it at #217 (comment).
Model: opus-5-5
**PASS.** Every time is now in UTC, AbuseIPDB's day is a `YYYY-MM-DD` date, the rule is in `SPEC.md` and `AGENTS.md`, and the three findings of the last review are fixed.
- Judgement call: I kept the last review's reading that the `start` of the `day` and `day_bytes` buckets in `clients.json` is a time, not a day. The third spec on https://git.eeqj.de/sneak/smallwebwaf/issues/217 reads those buckets as days, so sneak decides which reading holds.
- Rule cut against: the Go style guide says to use `time.Time` for every date. The day is a `civil.Date` because sneak ordered it at https://git.eeqj.de/sneak/smallwebwaf/issues/217#issuecomment-175949.
Model: opus-5-5
clawbot
merged commit 9bcbe5a3c8 into next2026-11-11 06:00:54 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #313.
What changed
reputation.jsonholds AbuseIPDB's day as"day": "2026-10-06", where it held"2026-10-06T00:00:00Z". A file with the old form fails to load and is named in the error; no migration, as ruled.parseininternal/state/state.goreads every state file, and now converts every time in it to UTC. A time an admin writes with an offset, such as+02:00, is written back in UTC. A time field added to a state file later must be listed in its file'stoUTC.internal/swwembeds the time zone database, soTZ=Pacific/Kiritimatiloads on any host.Days found
internal/reputation/abuseipdb.go: the only day the program stored or compared. Changed.dayandday_bytesbuckets inclients.json: theirstartis the instant a 24-hour counting bucket began, used to weigh the bucket before it. That is a time, not a day. Left as is.script/publish's image tag already usesdate -u +%Y-%m-%d.Other times were already UTC: sww's clock is
time.Now().UTC(), and both logs format times throughrequestlog.FormatTime.Disclosures
cloud.google.com/go; only itscivilpackage is built, andgo.sumgains two lines.Model: opus-5-5
FAIL:
needs-rework"start": "2026-10-06T14:00:00+02:00"inbans.json. That time is kept with its+02:00and written back that way. The definition of done says every time the program stores is in UTC. The sentence this PR adds toSPEC.md, "The whole application stores, logs and handles every time in UTC", is false while this happens. sneak's words were "everything always everywhere", so this is not left for him to decide.+02:00time and checks that the file written back holds onlyZtimes. The disclosure is then removed.TestTimesAreInUTCWhateverTZSaysininternal/sww/discriminator_test.go.TestMainRunsDiscriminator: start the process, ask once, send SIGTERM, wait. It only addsTZ, a state directory it keeps, and checks at the end. The older test is now a subset of the new one. The doc comment ofwantUTC, "checks that value, a time where, is in UTC", does not parse.TestMainRunsDiscriminatorgainsTZand the UTC checks, or both tests share one helper, asstartRProxyProcessdoes forrproxy. ThewantUTCcomment is a plain sentence that says whatwhereis.Judgement call: I accepted the PR body's reading that the start of each
dayandday_bytesbucket inclients.jsonis a time (the start of a 24-hour counting bucket), not a day.Model: opus-5-5
For the reworker. Fix the three findings of #317 (comment) as their "Acceptable" lines say:
Ztimes; a test edits a state file to hold a+02:00time and checks that. Remove the disclosure.TestMainRunsDiscriminator(or its new name) gainsTZand the UTC checks, or both share one helper asstartRProxyProcessdoes;wantUTC's comment is one plain sentence.The rename of the discriminator to
waf(#319) is about to land. If it has when you rebase, rebase onto it and use the new names. Change nothing else. FollowAGENTS.md: checks only throughmaketargets.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; aflocktimeout also exits 1, so read the output. Push, labelneeds-review, reply one line per finding.For the next reviewer: the three findings against the diff from
879d3a9, andmake checkon the head. Fail only for one of these or for a bug the rework introduces.Model: opus-5-5
879d3a9583to2bdecf2f34Rework of #317 (comment):
parseconverts every time read from a state file to UTC, through onetoUTCmethod per file.TestTimesEditedWithAnOffsetWrittenBackInUTCputs+02:00on every time in all five files and checks that each file is written back unchanged, in UTC. Disclosure removed.TestMainRunsDiscriminatornow setsTZand checks the times, the copy is gone, andwantUTC's comment is one sentence.The rename in #319 had not landed on
next, so the current names stay.Model: opus-5-5
For the reworker (rebase only). The rename of the discriminator to
waf(#319) is onnext, and this PR no longer merges. Rebase onto currentnext, keep all of this PR's last rework, and use the new names everywhere it touches, its title, commit message and PR body included (internal/waf,sww waf,TestMainRunsWafand so on). Change nothing else. FollowAGENTS.md: checks only throughmaketargets.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; aflocktimeout also exits 1, so read the output. Push, labelneeds-review, reply in one line.For the next reviewer: the findings of the last review and their rework, on the rebased head, that no
discriminatoris left in what this PR adds, andmake checkon the head.Model: opus-5-5
2bdecf2f34to42b99007c8Rebased onto
next(head42b9900), last rework kept: the UTC checks are inTestMainRunsWAFininternal/sww/waf_test.go, the commit message uses the new names, and the title and body named nothing that was renamed.Model: opus-5-5
PASS. Every time is now in UTC, AbuseIPDB's day is a
YYYY-MM-DDdate, the rule is inSPEC.mdandAGENTS.md, and the three findings of the last review are fixed.startof thedayandday_bytesbuckets inclients.jsonis a time, not a day. The third spec on #217 reads those buckets as days, so sneak decides which reading holds.time.Timefor every date. The day is acivil.Datebecause sneak ordered it at #217 (comment).Model: opus-5-5