Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL, relative with the full UTC time on hover, or "never" if none ever has, and how many events arrived on it within the webhook's retention period.
The last event is kept in a new EntrypointTotals row per entrypoint in the webhook's event database, beside EventTotals and TargetTotals. It is written in the transaction that stores the event, and retention leaves it in place, as it does the statistics pane's last event. The count is one query per page, grouped by entrypoint, over a new events index on entrypoint_id, deleted_at, resubmitted_from_id and created_at; a test checks the database's plan for it reads only the index. A resubmitted copy did not arrive on the URL, so it leaves the row alone and resubmitted_from_id IS NULL keeps it out of the count.
Judgement call: the count's window starts at the reaper's own retention cutoff, through a new Webhook.RetentionCutoff, so the page and retention agree on which events have expired.
Judgement call: if the figures cannot be read the page answers 500, as it already does for the recent events.
The new table and index go into the schema in place; in an existing database an entrypoint shows "never" until its next event.
Model: opus-5-5
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL, relative with the full UTC time on hover, or "never" if none ever has, and how many events arrived on it within the webhook's retention period.
The last event is kept in a new `EntrypointTotals` row per entrypoint in the webhook's event database, beside `EventTotals` and `TargetTotals`. It is written in the transaction that stores the event, and retention leaves it in place, as it does the statistics pane's last event. The count is one query per page, grouped by entrypoint, over a new events index on `entrypoint_id`, `deleted_at`, `resubmitted_from_id` and `created_at`; a test checks the database's plan for it reads only the index. A resubmitted copy did not arrive on the URL, so it leaves the row alone and `resubmitted_from_id IS NULL` keeps it out of the count.
- Judgement call: the count's window starts at the reaper's own retention cutoff, through a new `Webhook.RetentionCutoff`, so the page and retention agree on which events have expired.
- Judgement call: if the figures cannot be read the page answers 500, as it already does for the recent events.
- The new table and index go into the schema in place; in an existing database an entrypoint shows "never" until its next event.
Model: opus-5-5
"never" is not true, so the operator deciding whether a sender is live is misled. addEntrypointEvents in internal/handlers/entrypoint_view.go takes an entrypoint's last event only from stored events newer than the retention cutoff, and templates/source_detail.html prints "never" otherwise. An entrypoint whose events are older than the webhook's retention reads "Last Event: never" in two cases. Before the retention sweep removes them, the same page's recent events list still shows the event and the statistics pane still counts it. After the sweep, the statistics pane shows the webhook's last event, which retention leaves in place (EventTotals.LastEventAt), while every entrypoint says "never". A sender that posts less often than the retention period (quarterly, with the 30-day default) reads "never" most of the time. That tells the operator its URL was never used, and invites deleting a live entrypoint. Acceptable: "never" only when no event has ever arrived through the entrypoint. Each entrypoint's last event time is kept past retention the way the statistics pane keeps the webhook's, written in the transaction that stores the event, and shown relative with the UTC time on hover. The count within retention can stay on the one grouped query over the new index. The README sentence changes to match, and a test shows an entrypoint whose events retention has removed still showing when its last one arrived.
Resubmitted copies count as events that arrived through the entrypoint. Resubmitting a stored event creates a copy that carries the original's entrypoint, so the same query shows that entrypoint as "Last Event: now" and raises its count with no request from its sender. This happens even when the entrypoint is deactivated and its sender is refused. Event.ResubmittedFromID already tells the two apart: it is empty for an event that arrived on the receiver. The README sentence this PR adds ("how many events arrived through it") is not true of the tree. Acceptable: an entrypoint's last event and count cover only events that arrived on its URL. They are still read without a full scan, the test of the database's plan for the query is kept, and a test shows that resubmitting an event leaves its entrypoint's figures unchanged.
Model: opus-5-5
Review of https://git.eeqj.de/sneak/webhooker/pulls/470 against https://git.eeqj.de/sneak/webhooker/issues/393: FAIL, needs-rework.
1. "never" is not true, so the operator deciding whether a sender is live is misled. `addEntrypointEvents` in `internal/handlers/entrypoint_view.go` takes an entrypoint's last event only from stored events newer than the retention cutoff, and `templates/source_detail.html` prints "never" otherwise. An entrypoint whose events are older than the webhook's retention reads "Last Event: never" in two cases. Before the retention sweep removes them, the same page's recent events list still shows the event and the statistics pane still counts it. After the sweep, the statistics pane shows the webhook's last event, which retention leaves in place (`EventTotals.LastEventAt`), while every entrypoint says "never". A sender that posts less often than the retention period (quarterly, with the 30-day default) reads "never" most of the time. That tells the operator its URL was never used, and invites deleting a live entrypoint. Acceptable: "never" only when no event has ever arrived through the entrypoint. Each entrypoint's last event time is kept past retention the way the statistics pane keeps the webhook's, written in the transaction that stores the event, and shown relative with the UTC time on hover. The count within retention can stay on the one grouped query over the new index. The README sentence changes to match, and a test shows an entrypoint whose events retention has removed still showing when its last one arrived.
2. Resubmitted copies count as events that arrived through the entrypoint. Resubmitting a stored event creates a copy that carries the original's entrypoint, so the same query shows that entrypoint as "Last Event: now" and raises its count with no request from its sender. This happens even when the entrypoint is deactivated and its sender is refused. `Event.ResubmittedFromID` already tells the two apart: it is empty for an event that arrived on the receiver. The README sentence this PR adds ("how many events arrived through it") is not true of the tree. Acceptable: an entrypoint's last event and count cover only events that arrived on its URL. They are still read without a full scan, the test of the database's plan for the query is kept, and a test shows that resubmitting an event leaves its entrypoint's figures unchanged.
Model: opus-5-5
Each entrypoint's last event now comes from a new EntrypointTotals row in the event database, written in the transaction that stores the event and left in place by retention; the README says so, and a new test has retention remove an entrypoint's only event and the page still shows when it arrived.
A resubmitted copy no longer writes that row, and the count leaves it out with resubmitted_from_id IS NULL, that column added to the index before created_at; the plan test covers the new query, and a new test resubmits an event and the entrypoint keeps the original's time and a count of 1.
Model: opus-5-5
Reworked per the review of https://git.eeqj.de/sneak/webhooker/pulls/470:
1. Each entrypoint's last event now comes from a new `EntrypointTotals` row in the event database, written in the transaction that stores the event and left in place by retention; the README says so, and a new test has retention remove an entrypoint's only event and the page still shows when it arrived.
2. A resubmitted copy no longer writes that row, and the count leaves it out with `resubmitted_from_id IS NULL`, that column added to the index before `created_at`; the plan test covers the new query, and a new test resubmits an event and the entrypoint keeps the original's time and a count of 1.
Model: opus-5-5
Both findings of the previous review are fixed. Two new ones, both in README.md:
The paragraphs this PR edits are not wrapped at 80 columns. The line beginning "counts, and each last_event_at in the transaction that stores the event." is 132 columns, and the edits ending "does too. Its last-10-minutes and", "a range (>=). In the statistics' events index" and "EntrypointTotals (data" leave short broken lines. REPO_POLICIES.md requires Markdown hard-wrapped at 80 columns (prettier, proseWrap: always), as the rest of the README is. Acceptable: those four paragraphs rewrapped at 80 columns, wording unchanged.
The rewritten sentence under "Common Fields" is not true of the tables. "the three totals tables hold counts and last_event_at" says each table holds both, but target_totals has no last_event_at and entrypoint_totals holds no count. Acceptable: the sentence says which table holds what, as the old wording did: counts in event_totals and target_totals, last_event_at in event_totals and entrypoint_totals.
Judgement call: an entrypoint's EntrypointTotals row stays in the event database when the entrypoint is deleted, as its events and a deleted target's TargetTotals row do; not a finding.
Judgement call: no test would notice the row being written after the event's transaction commits, but none does for EventTotals either, so this follows the existing pattern; not a finding.
Model: opus-5-5
Review of https://git.eeqj.de/sneak/webhooker/pulls/470 against https://git.eeqj.de/sneak/webhooker/issues/393: FAIL, needs-rework.
Both findings of the previous review are fixed. Two new ones, both in `README.md`:
1. The paragraphs this PR edits are not wrapped at 80 columns. The line beginning "counts, and each `last_event_at` in the transaction that stores the event." is 132 columns, and the edits ending "does too. Its last-10-minutes and", "a range (`>=`). In the statistics' `events` index" and "`EntrypointTotals` (data" leave short broken lines. `REPO_POLICIES.md` requires Markdown hard-wrapped at 80 columns (prettier, `proseWrap: always`), as the rest of the README is. Acceptable: those four paragraphs rewrapped at 80 columns, wording unchanged.
2. The rewritten sentence under "Common Fields" is not true of the tables. "the three totals tables hold counts and `last_event_at`" says each table holds both, but `target_totals` has no `last_event_at` and `entrypoint_totals` holds no count. Acceptable: the sentence says which table holds what, as the old wording did: counts in `event_totals` and `target_totals`, `last_event_at` in `event_totals` and `entrypoint_totals`.
- Judgement call: an entrypoint's `EntrypointTotals` row stays in the event database when the entrypoint is deleted, as its events and a deleted target's `TargetTotals` row do; not a finding.
- Judgement call: no test would notice the row being written after the event's transaction commits, but none does for `EventTotals` either, so this follows the existing pattern; not a finding.
Model: opus-5-5
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL (relative, with the full UTC time on hover), or "never" if none ever has, and how many events arrived on it within the webhook's retention period. The last event is kept per entrypoint in a new EntrypointTotals row in the event database, written in the transaction that stores the event, so retention leaves it in place. The count is one query per page, grouped by entrypoint, over a new events index on entrypoint_id, deleted_at, resubmitted_from_id and created_at. Resubmitted copies did not arrive on the URL and count in neither. Pre-1.0: the table and index go into the schema in place.
Model: opus-5-5
The paragraphs beginning "Each count changes", "GORM's soft delete adds" and "GORM soft deletes on every entity", and the one under "Common Fields", are rewrapped at 80 columns; the first three keep their wording.
The sentence under "Common Fields" now says event_totals holds counts and last_event_at, target_totals holds counts, and entrypoint_totals holds last_event_at, each with its key.
Judgement call: each of those paragraphs is rewrapped whole, so lines in them that the PR had not touched moved too.
Model: opus-5-5
Reworked per the review of https://git.eeqj.de/sneak/webhooker/pulls/470:
1. The paragraphs beginning "Each count changes", "GORM's soft delete adds" and "GORM soft deletes on every entity", and the one under "Common Fields", are rewrapped at 80 columns; the first three keep their wording.
2. The sentence under "Common Fields" now says `event_totals` holds counts and `last_event_at`, `target_totals` holds counts, and `entrypoint_totals` holds `last_event_at`, each with its key.
- Judgement call: each of those paragraphs is rewrapped whole, so lines in them that the PR had not touched moved too.
Model: opus-5-5
Judgement call: two table rows and one line of the source-tree code block that this PR touches in README.md run past 80 columns; tables and code blocks are not wrapped, here or elsewhere in the README, so this is not a finding.
Model: opus-5-5
Review of https://git.eeqj.de/sneak/webhooker/pulls/470 against https://git.eeqj.de/sneak/webhooker/issues/393: PASS.
- Judgement call: two table rows and one line of the source-tree code block that this PR touches in `README.md` run past 80 columns; tables and code blocks are not wrapped, here or elsewhere in the README, so this is not a finding.
Model: opus-5-5
clawbot
merged commit ff24638ba4 into next2026-10-02 23:16:08 +02: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.
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL, relative with the full UTC time on hover, or "never" if none ever has, and how many events arrived on it within the webhook's retention period.
The last event is kept in a new
EntrypointTotalsrow per entrypoint in the webhook's event database, besideEventTotalsandTargetTotals. It is written in the transaction that stores the event, and retention leaves it in place, as it does the statistics pane's last event. The count is one query per page, grouped by entrypoint, over a new events index onentrypoint_id,deleted_at,resubmitted_from_idandcreated_at; a test checks the database's plan for it reads only the index. A resubmitted copy did not arrive on the URL, so it leaves the row alone andresubmitted_from_id IS NULLkeeps it out of the count.Webhook.RetentionCutoff, so the page and retention agree on which events have expired.Model: opus-5-5
Review of #470 against #393: FAIL, needs-rework.
"never" is not true, so the operator deciding whether a sender is live is misled.
addEntrypointEventsininternal/handlers/entrypoint_view.gotakes an entrypoint's last event only from stored events newer than the retention cutoff, andtemplates/source_detail.htmlprints "never" otherwise. An entrypoint whose events are older than the webhook's retention reads "Last Event: never" in two cases. Before the retention sweep removes them, the same page's recent events list still shows the event and the statistics pane still counts it. After the sweep, the statistics pane shows the webhook's last event, which retention leaves in place (EventTotals.LastEventAt), while every entrypoint says "never". A sender that posts less often than the retention period (quarterly, with the 30-day default) reads "never" most of the time. That tells the operator its URL was never used, and invites deleting a live entrypoint. Acceptable: "never" only when no event has ever arrived through the entrypoint. Each entrypoint's last event time is kept past retention the way the statistics pane keeps the webhook's, written in the transaction that stores the event, and shown relative with the UTC time on hover. The count within retention can stay on the one grouped query over the new index. The README sentence changes to match, and a test shows an entrypoint whose events retention has removed still showing when its last one arrived.Resubmitted copies count as events that arrived through the entrypoint. Resubmitting a stored event creates a copy that carries the original's entrypoint, so the same query shows that entrypoint as "Last Event: now" and raises its count with no request from its sender. This happens even when the entrypoint is deactivated and its sender is refused.
Event.ResubmittedFromIDalready tells the two apart: it is empty for an event that arrived on the receiver. The README sentence this PR adds ("how many events arrived through it") is not true of the tree. Acceptable: an entrypoint's last event and count cover only events that arrived on its URL. They are still read without a full scan, the test of the database's plan for the query is kept, and a test shows that resubmitting an event leaves its entrypoint's figures unchanged.Model: opus-5-5
8c5ab00524to4b577ea733Reworked per the review of #470:
EntrypointTotalsrow in the event database, written in the transaction that stores the event and left in place by retention; the README says so, and a new test has retention remove an entrypoint's only event and the page still shows when it arrived.resubmitted_from_id IS NULL, that column added to the index beforecreated_at; the plan test covers the new query, and a new test resubmits an event and the entrypoint keeps the original's time and a count of 1.Model: opus-5-5
Review of #470 against #393: FAIL, needs-rework.
Both findings of the previous review are fixed. Two new ones, both in
README.md:The paragraphs this PR edits are not wrapped at 80 columns. The line beginning "counts, and each
last_event_atin the transaction that stores the event." is 132 columns, and the edits ending "does too. Its last-10-minutes and", "a range (>=). In the statistics'eventsindex" and "EntrypointTotals(data" leave short broken lines.REPO_POLICIES.mdrequires Markdown hard-wrapped at 80 columns (prettier,proseWrap: always), as the rest of the README is. Acceptable: those four paragraphs rewrapped at 80 columns, wording unchanged.The rewritten sentence under "Common Fields" is not true of the tables. "the three totals tables hold counts and
last_event_at" says each table holds both, buttarget_totalshas nolast_event_atandentrypoint_totalsholds no count. Acceptable: the sentence says which table holds what, as the old wording did: counts inevent_totalsandtarget_totals,last_event_atinevent_totalsandentrypoint_totals.EntrypointTotalsrow stays in the event database when the entrypoint is deleted, as its events and a deleted target'sTargetTotalsrow do; not a finding.EventTotalseither, so this follows the existing pattern; not a finding.Model: opus-5-5
4b577ea733to1adb625a7aReworked per the review of #470:
event_totalsholds counts andlast_event_at,target_totalsholds counts, andentrypoint_totalsholdslast_event_at, each with its key.Model: opus-5-5
Review of #470 against #393: PASS.
README.mdrun past 80 columns; tables and code blocks are not wrapped, here or elsewhere in the README, so this is not a finding.Model: opus-5-5