Production evening seal recorded full-site snapshot as success with elapsed=0 / already_completed=true while hourly sync was overlapping. Mutual skip between snapshot and snapshot_recent was recorded as ok=true, then next_run advanced to the next day — so the real seal was missed until a manual --force.
Later: post-seed restart forced a daytime full scrape (clone punched early) and left the evening 23:15 seal skipped. Root cause: intent lived only in in-memory next_run.
## Background
Production evening seal recorded full-site `snapshot` as success with `elapsed=0` / `already_completed=true` while hourly sync was overlapping. Mutual skip between `snapshot` and `snapshot_recent` was recorded as `ok=true`, then `next_run` advanced to the next day — so the real seal was missed until a manual `--force`.
Later: post-seed restart forced a daytime full scrape (clone punched early) and left the evening 23:15 seal skipped. Root cause: intent lived only in in-memory `next_run`.
## Changes (!7 original)
1. **Full-site snapshot never skips** because another task is running (removed mutex).
2. **Quiet window 23:00–01:00 Asia/Shanghai** for discovery / snapshot_recent / candidate_cleanup.
3. **Fans** short-path (no quiet deferral / no mutex with snapshot).
4. **Skip ≠ success** (`ok=false` + `skipped=true`).
5. **Retry / makeup** via `TaskControl.next_run` (~5 min; seed ~10 min).
6. System UI + `/api/system` next_runs aligned.
7. **`snapshot-seed` CLI**.
8. Seed deferral when snapshot running / `no_source`.
## Review follow-ups (kept)
1. snapshot-seed: check `completed_key` before `no_source`.
2. `ok=False` → `scheduler.task_fail` (no false `task_ok`).
3. candidate_cleanup failure → ~5 min retry.
4. (superseded by #8) no daytime force-seal just because seeded.
## #8 on this branch — task state persistence
Implements https://git.ofcss.com/kcloud/re-xxm/issues/8 on tip `f408da93c20237bb7bcfc36e74346452fd02ac0f`.
- New `reaction_board/task_state.py`: kv `task:state:{name}` with `status` / `due_at` / `stat_date` / `progress` / `updated_at`.
- Scheduler startup uses `startup_next_run` (recover stale `running`, honor future `due_at`); **no** daily `next_run=0`.
- Seed success enqueues evening seal (`ensure_pending` with `due_at=snapshot_at`); daytime restart does not force-scrape.
- Seal path: `run_videos(..., force=True, keep_scraped=True)` punches `src=seed`, keeps non-seed ok=1 as progress; CLI `--force` still full refetch.
- Fail/skip/quiet → `failed`/`deferred` + future `due_at`; success → `done` + next wall/interval due.
- Web system next_runs prefer `task:state.due_at`.
## Test plan
- `uv run python tests/test_task_state.py`
- `uv run python tests/test_cli.py`
- `uv run python tests/test_scheduler.py`
- `uv run python tests/test_snapshot.py`
- `uv run python tests/test_timeutil.py`
- `uv run python tests/test_web_queries.py`
Do not merge until reviewed.
Incident: evening full snapshot could be recorded as ok with
already_completed/elapsed=0 after mutual skip with hourly sync, then
next_run jumped to the next day.
- Full-site snapshot never skips because another task is running
- 23:00-01:00 Asia/Shanghai: defer discovery / snapshot_recent /
candidate_cleanup to after 01:00; fans never defers
- Skip records ok=false + skipped=true (not success / not completed)
- Skip/fail reschedule soon (~5m); daily success still next snapshot_at
- TaskControl return value drives next_run; system UI shows skip fields
Add `python app.py snapshot-seed [--date]` (offline clone, Debian /
docker compose exec friendly). When seed fires while full-site snapshot
is running, or last_completed is missing (no_source), record skip as
non-success and retry in 10 minutes until a real seed succeeds; fans
and quiet-window rules unchanged.
Schedule snapshot_seed/snapshot/fans to the next wall-clock seed_at /
snapshot_at on process start (optionally from last task record), instead
of next_run=0. Force-seal of seed shells only after today's snapshot_at
(or CLI --force), so a daytime restart cannot scrape the whole site.
Replace process-start next_run=0 guesses with kv task:state records
(status, due_at, stat_date, progress). Startup recovers stale running,
honors future due_at for delayed retries, and resumes the same
stat_date. Seed only enqueues evening seal; seal force keeps non-seed
ok=1 as progress (keep_scraped). Keeps !7 fail-logging and cleanup retry.
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.
Background
Production evening seal recorded full-site
snapshotas success withelapsed=0/already_completed=truewhile hourly sync was overlapping. Mutual skip betweensnapshotandsnapshot_recentwas recorded asok=true, thennext_runadvanced to the next day — so the real seal was missed until a manual--force.Later: post-seed restart forced a daytime full scrape (clone punched early) and left the evening 23:15 seal skipped. Root cause: intent lived only in in-memory
next_run.Changes (!7 original)
ok=false+skipped=true).TaskControl.next_run(~5 min; seed ~10 min)./api/systemnext_runs aligned.snapshot-seedCLI.no_source.Review follow-ups (kept)
completed_keybeforeno_source.ok=False→scheduler.task_fail(no falsetask_ok).#8 on this branch — task state persistence
Implements #8 on tip
f408da93c20237bb7bcfc36e74346452fd02ac0f.reaction_board/task_state.py: kvtask:state:{name}withstatus/due_at/stat_date/progress/updated_at.startup_next_run(recover stalerunning, honor futuredue_at); no dailynext_run=0.ensure_pendingwithdue_at=snapshot_at); daytime restart does not force-scrape.run_videos(..., force=True, keep_scraped=True)punchessrc=seed, keeps non-seed ok=1 as progress; CLI--forcestill full refetch.failed/deferred+ futuredue_at; success →done+ next wall/interval due.task:state.due_at.Test plan
uv run python tests/test_task_state.pyuv run python tests/test_cli.pyuv run python tests/test_scheduler.pyuv run python tests/test_snapshot.pyuv run python tests/test_timeutil.pyuv run python tests/test_web_queries.pyDo not merge until reviewed.
@michal 测例 NEED-CHANGES 已补,请对 tip
dcf37c4a1d1afc05394de43c0f6a2c6e3fad84b3(dcf37c4)复审。H7
test_fail_or_defer_preserves_progress:先写入非空 progress,再mark_failed/mark_deferred(progress=None),load_state断言 progress +stat_date保留,只改 status +due_at。H8
test_defer_snapshot_seed_schedules_ten_minutes:task:statestatus=deferred,due_at ≈ finished+600test_quiet_window_defer_persists_deferred_state:discovery 静默窗 defer →deferred+due_at == next_after_snapshot_quiet_window,且不写成功完成类 kvSoft
test_seeded_before_snapshot_at_does_not_force_run:due_at 刻意用 22:00 ≠ 墙钟 23:15test_candidate_cleanup_failure_retries_soon:兼读task:statefailed + due_at本地:
uv run python tests/test_task_state.py/uv run python tests/test_cli.py均 OK。未 merge。View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.