AI Implementation feature(1089): Scheduled Execution with Cron and Retry Handling (#9)
This commit was merged in pull request #9.
This commit is contained in:
@@ -26,15 +26,7 @@ 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`.
|
||||
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 Mastodon status 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`. The scheduled wrapper executes this Python process as the unprivileged `bot` user.
|
||||
|
||||
# What You Should See
|
||||
|
||||
@@ -61,8 +53,8 @@ this is the **only** line you should see — the runner emits no
|
||||
| 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` |
|
||||
| Pipeline raises once, recovers | `retry_attempt`, then `run_complete` | `0` |
|
||||
| Pipeline keeps raising (budget exhausted) | `retry_attempt` events, then `retry_exhausted` with operation and error | `1` |
|
||||
| Config invalid | 1× `configuration_error` (extras describe what failed) | `2` |
|
||||
|
||||
> **Tester tip** — `docker compose ps` should report the container as
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
type: guide
|
||||
title: Runtime Verification Guide
|
||||
description: Tester workflow for validating root cron startup, unprivileged bot execution, retry events, and persistent data behavior.
|
||||
tags: [guide, tester, docker, runtime]
|
||||
timestamp: 2026-08-04T20:44:00Z
|
||||
---
|
||||
|
||||
# Setup
|
||||
|
||||
1. Copy `.env.example` to `.env` and provide valid required values.
|
||||
2. Build and start the service:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
3. Follow container output:
|
||||
|
||||
```bash
|
||||
docker compose logs -f bot
|
||||
```
|
||||
|
||||
# Expected Runtime Behavior
|
||||
|
||||
The container remains running with `cron -f` as its foreground process. Startup output includes the configured `RUN_AT` and `TZ`. The scheduled cron command invokes `/usr/local/bin/run-bot.sh`, which runs the Python bot as user `bot`, not root.
|
||||
|
||||
The host `./data` directory should contain the synchronized `blog/` clone and, after the state store is loaded, `posted.json`. Both are under the one `/app/data` mount.
|
||||
|
||||
# Manual Run
|
||||
|
||||
Run the same application command without waiting for cron:
|
||||
|
||||
```bash
|
||||
docker compose exec bot /usr/local/bin/python -m tenbackward
|
||||
```
|
||||
|
||||
A valid run exits `0`. A configuration failure exits `2`; an exhausted pipeline retry budget exits `1`.
|
||||
|
||||
# Retry Checks
|
||||
|
||||
To exercise transient failure handling, use a test repository or injected test double that fails once and then succeeds. Verify JSON events contain:
|
||||
|
||||
| Event | Expected fields |
|
||||
|---|---|
|
||||
| `retry_attempt` | `operation`, `attempt`, and the original `error` text. |
|
||||
| `retry_exhausted` | Final `operation`, total `attempts`, and `error` text when all attempts fail. |
|
||||
| `run_complete` | Counters and `posted_ids` only after a successful pass. |
|
||||
|
||||
With `MAX_RETRIES=0`, one attempt is still made and no sleep occurs. With a larger value, delays occur only between attempts and follow powers of two beginning at one second.
|
||||
|
||||
# Configuration Checks
|
||||
|
||||
Remove a required variable from `.env` and restart the service. The entrypoint should report the missing variable before cron starts. If the shell check is bypassed and the Python module runs directly, `configuration_error` should identify the missing key and return exit code `2` rather than silently substituting an application default.
|
||||
|
||||
# Persistence and Failure Safety
|
||||
|
||||
After a successful publication, inspect `/app/data/posted.json` and confirm new relative post paths appear under the `posted` list. If Mastodon publishing fails, the path must not be recorded; a later retry should be able to publish it again.
|
||||
|
||||
# Key Visual/Observable Elements
|
||||
|
||||
This service has no graphical UI. Test-facing outputs are:
|
||||
|
||||
* Container status and `docker compose logs` output.
|
||||
* JSON-per-line startup, retry, failure, and completion events.
|
||||
* Exit codes from manual runs.
|
||||
* `/app/data/blog` and `/app/data/posted.json` on the mounted host directory.
|
||||
|
||||
# Related
|
||||
|
||||
* [Daily Run Guide](/guides/daily-run.md)
|
||||
* [Job 1089 Runtime Changes](/architecture/runtime-changes.md)
|
||||
* [Environment Variable Setup](/operations/environment-setup.md)
|
||||
* [Cron Lifecycle](/operations/cron-lifecycle.md)
|
||||
Reference in New Issue
Block a user