Files
MangaCircus/docs/reading-limiter.md
MaashkinoozheandClaude Opus 5.5 b2bb34174c Fold the sentinel into the scraper and drop the sentinel service (BL-0046)
The stall watchdog now runs inside the scraper (ScraperService.checkStalls,
every 30 s from the work loop): same rule as before, one checkpoint-safe
restart, quarantine on the second stall, missing checkpoint is no evidence,
and paused time no longer counts. Stalls are recorded as scraper incidents
(failure.kind 'stall'). The private /api/recovery endpoint is gone.

Dropped with the service: reader/scraper-unreachable incidents (supervisor
and Docker handle crashes; the health page shows "Scraper: unreachable"),
duplicate job-failure incidents and the unused /metrics endpoint.
The stack is now reader + scraper (+ optional peer-gateway).
The supervisor test moved to runtime/test (npm run test:runtime stays).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:58:03 +02:00

84 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment-controlled reading hours and quiz
The reader enforces reading hours on both `GET /api/chapter` and chapter page-image
requests under `/lib/` (HTTP 423 outside hours). The dashboard, manga metadata,
covers, progress, reading-time summaries, scraper and administrator pages remain
available. A reader tab already open when a window ends checks access every 15
seconds and returns to the gate. Chapter images use `Cache-Control: no-store` when
the limiter is configured. Already downloaded images in a browser or files available
directly on disk cannot be recalled; this is a self-imposed server access rule, not
DRM. There is no schedule-changing API or UI. Signed-in readers and admins can
view the effective weekly schedule and timezone under **Settings → Reading
hours**; that page is read-only.
## Docker configuration
Set these deployment variables in the Compose `.env` file next to
`docker-compose.yml`, then recreate **only the reader**. The `reader` service
passes them through to the container. `READER_IMAGE` selects a reader-only
release while `APP_IMAGE` continues to select the scraper image.
Leaving `READING_WINDOWS_JSON` empty disables the limiter. Day keys
are `0` Sunday through `6` Saturday. Each day has any number of `[start,end]`
intervals; an end earlier than its start carries into the next day. `24:00` is
valid only as an end. Intervals are start-inclusive and end-exclusive, resolved
using the IANA timezone, including daylight-saving changes.
```dotenv
READING_WINDOWS_JSON='{"0":[["08:00","01:00"]],"1":[["07:00","12:00"],["13:00","24:00"]],"2":[["07:00","24:00"]],"3":[["07:00","24:00"]],"4":[["07:00","24:00"]],"5":[["07:00","24:00"]],"6":[["08:00","01:00"]]}'
READING_TIMEZONE=Europe/Berlin
READING_QUIZ_UNLOCK_MINUTES=60
READING_QUIZ_CATALOG_PATH=/data/reading-quiz.json
```
The Monday split above illustrates multiple on/off periods; replace it with
`[["07:00","24:00"]]` for an uninterrupted weekday. The Saturday and Sunday
windows extend to 01:00 on the following mornings. A malformed timezone,
schedule, unlock length or catalog stops the reader at startup rather than
silently removing the restriction. `READING_QUIZ_UNLOCK_MINUTES` accepts 1–1440.
The scraper is independent and must not be restarted for this configuration.
Until a reviewed catalog is installed at `READING_QUIZ_CATALOG_PATH`, the
outside-hours gate cannot offer a quiz. It does not relax the schedule.
## Question bank and progress watermark
The bank is a deployment-managed JSON file, separate from `manga.json` and from
the scraper. See [`config/reading-quiz.example.json`](../config/reading-quiz.example.json).
Its `manga` keys are **exact library folder IDs**. Each question has a stable ID,
prompt, accepted answer variants, an evidence citation, and at least one
`requires` chapter ID. The chapter IDs are the reader's IDs (for example `1/12`,
not the on-disk `v01/c0012`). Every required chapter must be marked completed
for that user before the question enters their pool. This supports a single
watermark or a set of specific sections. Five eligible questions are needed to
offer a quiz; an empty bank simply leaves outside-hours reading locked. A
question's answers and evidence never go to the browser.
The challenge randomly samples five progress-eligible questions across *all*
library manga, not only the one being opened. It asks them sequentially, accepts
unlimited retries, and has no answer timer. Accepted variants are normalized
for case, punctuation and accents; one typo is tolerated for answers of five
or more characters, two for answers of eleven or more. Starting a new visit
invalidates the previous challenge, and leaving the page abandons it. Passing
all five grants that user reading access across manga for the configured number
of minutes, persisted in `db.json` across reader restarts. Questions are not
shown before their watermark; this depends on the reader's existing
self-reported completion records.
Validate a proposed file with
`node tools/validate-reading-quiz.js <catalog.json>` before pointing the reader
at it. For an eventual research agent: write **drafts** with source citations,
aliases and proposed chapter watermarks, then have a human verify identity,
answer and spoiler boundary. Only a reviewed catalog should be copied to the
configured path and loaded by restarting the reader. No wiki or transcript
fetching is performed by the reader itself.
## Testing and rollback
`node tools/test-compose.js --reading-limit-ui` creates a disposable Compose
fixture with two progress-qualified sample manga, tests chapter and image
denial, quiz flow and the one-hour unlock, then tears it down. Add `--keep`
to leave an isolated playable instance locked for manual review; its printed
URL and fixture path identify it. Main data and the running main scraper are
not mounted. To roll back a future main deployment, restore the previous
reader image and configuration, recreate only the reader, and preserve current
`db.json` and library. The additional `readingAccess` field is backward-safe.