Files
10Backward/docs/guides/daily-run.md
T
2026-08-04 18:57:40 +00:00

134 lines
5.5 KiB
Markdown
Raw 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.
---
type: guide
title: Daily Run Guide
description: Operator- and tester-focused walkthrough of one daily cron tick — repository synchronization, anniversary matching, deduplication, logging, and retry behaviour.
tags: [guide, run, daily, tester]
timestamp: 2026-08-04T17:51:00Z
---
# Purpose
A tester or operator needs to know **what should happen** each time
the cron tick fires. This guide describes the contract from the
outside of the container, in observable terms: log lines, exit codes,
files appearing on the volume, and the "silent on no matches"
behaviour that the runner enforces.
# Triggering a Run Manually
```bash
docker compose exec tenbackward /usr/local/bin/python -m tenbackward
```
This is the same command the container's cron entry issues once per
day at `RUN_AT`. Use it to verify behaviour without waiting for the
scheduled tick.
# What the Run Does
Before scanning, the process ensures the configured blog repository exists
under `/app/data/blog` and is fast-forwarded from `BLOG_REPO_URL`. It then
walks `_posts/blog/**/*.md`, finds posts whose date is exactly ten years
before today, skips IDs already present in `posted.json`, composes a
single German-language Mastodon status (default prefix
`Heute vor 10 Jahren:`) listing each new match's title and canonical URL
followed by the configured hashtags, and — only after the Mastodon API
call succeeds — records the relative paths of the published posts in
`posted.json`.
# What You Should See
The bot uses a **JSON-per-line** logger that writes to
`/app/data/cron.log` (because the cron line redirects with
`>> /app/data/cron.log 2>&1`). Inspecting the file with
`docker compose exec tenbackward tail -n 100 /app/data/cron.log`
should show output similar to:
```json
{"ts": "2026-08-04T09:00:00+00:00", "level": "INFO", "logger": "tenbackward", "message": "startup", "event": "startup", "version": "0.1.0", "site_url": "https://blog.example.com", "run_at": "09:00", "tz": "Europe/Berlin", "hashtags": "#throwback,#10backward", "throwback_prefix": "Heute vor 10 Jahren:"}
```
On the current scaffold (`_iter_candidates` is intentionally empty),
this is the **only** line you should see — the runner emits no
`run_complete` when no candidates were found. This is the
"silent on no matches" contract.
# Expected Container Behaviour
| Scenario | Lines in `cron.log` | Container exit |
|-------------------------------------------|----------------------------------------------------------------|------------------|
| Config valid, no anniversary matches | 1× `startup` | `0` |
| Blog clone or pull fails | 1× `blog_repo_error` | `1` |
| Config valid, matching IDs are new | 1× `startup`, then 1× `run_complete` with `posted=N` | `0` |
| Config valid, all matches already posted | 1× `startup`, then 1× `run_complete` with `posted=0` | `0` |
| Pipeline raises once, recovers | 1× `pipeline_error`, then 1× `run_complete` | `0` |
| Pipeline keeps raising (budget exhausted) | `MAX_RETRIES + 1` × `pipeline_error`, then 1× `pipeline_failed` | `1` |
| Config invalid | 1× `configuration_error` (extras describe what failed) | `2` |
> **Tester tip** — `docker compose ps` should report the container as
> `running` after a `pipeline_failed` exit *only* if cron has not yet
> fired again. A single failed pipeline tick does not kill the container
> itself; cron re-runs it the next day.
# Files the Tester Should Look For
| Path (inside container) | When it appears |
|-------------------------|--------------------------------------------|
| `/app/data/cron.log` | Always (cron appends stdout/stderr here). |
| `/app/data/posted.json` | Created when the matcher or a state check first loads the store; rewritten only when a new ID is posted. The file contains relative Jekyll paths under a `posted` list. |
To verify these from the host:
```bash
docker compose exec tenbackward ls -la /app/data
docker compose exec tenbackward cat /app/data/cron.log
```
# Visual Elements
This is a server-side bot, so there is no UI. The "UI" consists of:
* **Log lines on stdout** — one JSON object per `INFO`/`ERROR` event.
* **Exit code** — `0` (healthy), `1` (pipeline exhausted), or `2` (config).
* **`posted.json`** — JSON state file; its mtime changes whenever a new id is persisted.
* **`/app/data/blog`** — synchronized local clone containing the Jekyll posts scanned for anniversaries.
# Examples
## Healthy scaffold tick
```bash
$ docker compose exec tenbackward python -m tenbackward
{"ts":"...","event":"startup",...}
$ echo $?
0
```
## Bad `RUN_AT`
```bash
$ RUN_AT=25:99 docker compose exec tenbackward python -m tenbackward
ERROR: RUN_AT='25:99' must be in HH:MM (24-hour) format
$ docker compose ps
tenbackward Exit 1
```
(The error message lands on stderr in the entrypoint path and is
captured by `cron.log`.)
## Missing required env var
Unset `MAX_RETRIES`, restart the container:
```bash
$ docker compose up --build
tenbackward | ERROR: missing required environment variable(s): MAX_RETRIES
tenbackward exited with code 1
```
# Related
* [Environment Variable Setup](/operations/environment-setup.md)
* [Pipeline Runner](/architecture/pipeline-runner.md)
* [Logging & Run Summary](/architecture/logging.md)