Files
mangaBookmark/docs/adr/0012-persisted-lane-state.md
T
sulthan fc8a40cfc6 fix(latest,web): finish the atomic deletion and honest empty states (#145)
Review fixes on top of cb2b104:
- Delete the write-only in-memory refuseUntil map and setRefusalBackoff now
  that status.go is gone: the pass gate reads the durable stamp, so the map
  was half-deleted dead state (spec review C-1).
- An outcome-query failure no longer blanks the Lane table into the false
  'no data yet': rows render with 'none observed' chips instead (I-1).
- due-query and eligible-count skips get their own sentences instead of the
  merged 'check failed' (I-2).
- ADR-0012 constrain wording corrected and trailing newline added (M-1)
2026-08-21 20:31:50 +07:00

44 lines
2.1 KiB
Markdown

# ADR-0012: Persisted lane state
Date: 2026-08-21
Status: accepted
Supersedes the in-memory lane snapshot carried by `latest`'s `LaneState`/`Status`
and the `web.LaneReporter` seam (ADR-0010 wrote the durable rows this page now
reads).
## Decision
The admin Lanes page stops reading the poller's in-memory Lane state and
becomes a read of `poll_passes`/`poll_lanes` in Postgres. There is no
`LaneReporter` interface: `web/admin_lanes.go` walks `store.LatestLanePasses()`
into one row per Site and adds the window's outcome sums from
`store.LanePassOutcomes()`. The `latest` package's `LaneState`/`Status` snapshot
and its `web.LaneReporter` seam are deleted.
The browser is a deployment configuration fact plus a reachability derived
from the pass log: `BROWSER_WS_URL` set means "configured", and the browser is
"reachable" unless a recent browser-Site pass inside `latest.RefuseBackoff` is
a sidecar loss, a missing fetcher, or an interrupted read. A skip reason is
the whole difference between a Lane resting and a Lane stuck: a skipped pass
prints its sentence, and only an empty skip with Series due and none read
draws the true-stall fault. Sleep skips never count toward `Attention`.
## Why
The old page lived on a poller snapshot. Because that state was in memory, a
deploy erased it: the page read zeroes until a fresh pass ran, and browser
reachability came through a reporter interface only a live poller could
serve. Making the page answer from the database means a restart is complete
the instant the store is up, the browser fact survives a poller restart, and
a Lane that has not yet gathered figures shows a placeholder rather than a
confident zero.
## Constraints
The poller still owns the writes: each pass exit records one row (ADR-0010),
and a pass that returns before gathering figures carries the previous pass's
numbers forward instead of recording zeroes. A skip is a stable wire string;
`asleep` never counts toward `Attention`. When polling is switched off
(`LATEST_CHAPTER_POLL_ENABLED` unset) the page must say so, and the browser
statusline appears only when polling is switched on.