Spec #137: per-Series poll failure state, completion hint, and outbound owner notification #174

Merged
sulthan merged 30 commits from spec-137 into main 2026-08-23 11:31:11 +07:00
4 changed files with 19 additions and 4 deletions
Showing only changes of commit 6af8859c70 - Show all commits
+3 -3
View File
@@ -99,14 +99,14 @@ DISCORD_REDIRECT_URI=
# request whose Host header isn't an IP or "localhost", which silently breaks # request whose Host header isn't an IP or "localhost", which silently breaks
# every kagane poll. Left unset here on purpose — a wrong default would poll a # every kagane poll. Left unset here on purpose — a wrong default would poll a
# stranger's address, and "no browser" is a safe, self-announcing state. # stranger's address, and "no browser" is a safe, self-announcing state.
# BROWSER_WS_URL=ws://100.x.y.z:9222
#
# Discord webhook for owner notices (outbound alerting when a poll Lane # Discord webhook for owner notices (outbound alerting when a poll Lane
# stalls). Unset means the whole path is off — a local stack needs no webhook, # stalls). Unset means the whole path is off — a local stack needs no webhook,
# exactly as the browser URL behaves. The address is a secret in the class of # exactly as the browser URL behaves. The address is a secret in the class of
# TOKEN_KEY: never commit it, never paste it anywhere public. # TOKEN_KEY: never commit it, never paste it anywhere public.
# DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
#
# Zone the backend stamps its log lines in. Cosmetic only. Nothing else in
# Zone the backend stamps its log lines in. Cosmetic only. Nothing else in # Zone the backend stamps its log lines in. Cosmetic only. Nothing else in
# the service has a zone: bookmark timestamps are unix ms, and the two real # the service has a zone: bookmark timestamps are unix ms, and the two real
# time columns are timestamptz. Defaults to Asia/Jakarta; set to UTC for the # time columns are timestamptz. Defaults to Asia/Jakarta; set to UTC for the
+9
View File
@@ -159,6 +159,15 @@ failures. The flag decays and they probe again.
- Browser Lanes wake Chrome only when 5+ Series are due or one has waited 15m, - Browser Lanes wake Chrome only when 5+ Series are due or one has waited 15m,
and cover work runs in the background so a slow CDN can't eat a Lane's gap. and cover work runs in the background so a slow CDN can't eat a Lane's gap.
### Owner notices — `internal/notify`, `latest.Fault`, `latest.Notifier`, `latest.FaultsFrom`
The poller's outbound owner-notice path (issue #171): one condition today
(the stall), judged from the durable pass log alone so the poller and any
future reader of the same judgement cannot disagree. The webhook address is a
secret in the class of TOKEN_KEY — never logged, never rendered, never
carried in an error. The `owner_notices` suppression table (one row per
condition + site) is the only state; every threshold is derived, not stored.
### Covers — `Store.OnSeriesCreated`, `latest.Acquirer`, `latest.CoverBytesFetcher`, `Store.SetSeriesCover` ### Covers — `Store.OnSeriesCreated`, `latest.Acquirer`, `latest.CoverBytesFetcher`, `Store.SetSeriesCover`
Acquired once when the first Bookmark of a Series is created, then served from Acquired once when the first Bookmark of a Series is created, then served from
+7
View File
@@ -21,6 +21,13 @@ func TestLoadLatestPollDefaults(t *testing.T) {
} }
} }
func TestLoadConfigReadsCoverDirectory(t *testing.T) {
t.Setenv("COVER_DIR", "/covers")
if got := loadConfig().CoverDir; got != "/covers" {
t.Fatalf("CoverDir = %q, want /covers", got)
}
}
func TestLoadConfigReadsDiscordWebhook(t *testing.T) { func TestLoadConfigReadsDiscordWebhook(t *testing.T) {
// The address is read, never defaulted: unset stays empty (the whole // The address is read, never defaulted: unset stays empty (the whole
// path is off), set flows into the Config for the poller's notifier. // path is off), set flows into the Config for the poller's notifier.
-1
View File
@@ -80,7 +80,6 @@ services:
# runs exactly as it does today. An env var not listed here never # runs exactly as it does today. An env var not listed here never
# reaches the container. # reaches the container.
DISCORD_WEBHOOK_URL: ${DISCORD_WEBHOOK_URL:-} DISCORD_WEBHOOK_URL: ${DISCORD_WEBHOOK_URL:-}
depends_on:
depends_on: depends_on:
# The migration runner is the first thing the binary does, so a Postgres # The migration runner is the first thing the binary does, so a Postgres
# that is still initialising means a crash-loop until it is not. # that is still initialising means a crash-loop until it is not.