The dailyprog puzzle safety net
摘要
文章说明 dailyprog 需要每日新 puzzle,先手动撰写,后建 LLM 生成器验证 sandbox 执行。CI 作业在 22:00 UTC 检查明日 puzzle 是否存在,若缺失则运行 gen:puzzle 脚本(默认 DeepSeek v4 Flash,medium 难度,扫描最近 7 个 puzzle 的 technique tags 避免重复),通过 schema validation、reference solution 测试、brute-force collapse proof 等 gates 后写入文件。之后运行 puzzles.test.ts(加载所有 .solution.ts 并测试 visible / hidden cases)和 puzzle-quality.test.ts(guardrails 如 no duplicate technique、无连续 hard days、runtime budget),测试通过后以 puzzle-bot 提交、推送 main 并 dispatch deploy workflow(SSH 到 VPS pull、--no-cache rebuild、up)。生成后有两小时 lead time 供人工审查替换;手动与自动路径共用相同测试检查。
荐读理由
它给出了 Forgejo CI 每晚检查缺失 puzzle 后调用 LLM 生成器、跑 schema/测试/多样性约束、再部署的完整 workflow,你可以直接迁移到需要可靠每日/定期内容生成的 AI 项目里
原文
https://blog.lvmbdv.dev/posts/the-puzzle-safety-net/
The dailyprog puzzle safety net
dailyprog needs a new puzzle every day. I wrote the first few drafts by hand, then built a generator that writes them with an LLM and validates the output against real sandbox execution. The generator works, but it still needs someone to run it. Pick a pattern, kick off the script, review the result, commit. Fifteen minutes on a good day. More if the model gets stuck in a repair loop and I need to nudge it.

Photo by Basile Morin, cropped (CC BY-SA 4.0)
dailyprog needs a new puzzle every day. I wrote the first few drafts by hand, then built a generator that writes them with an LLM and validates the output against real sandbox execution. The generator works, but it still needs someone to run it. Pick a pattern, kick off the script, review the result, commit. Fifteen minutes on a good day. More if the model gets stuck in a repair loop and I need to nudge it.
Some days I don’t have fifteen minutes. Some days I forget. Before last week, either of those meant the site went dark at midnight UTC and stayed dark until I woke up and fixed it. Now it doesn’t.
At 22:00 UTC every night, two hours before the puzzle day rolls over, a CI job runs on the Forgejo runner. It checks whether tomorrow’s puzzle exists in the catalogue. If it does, nothing happens. If it doesn’t, the job generates one, validates it, commits it, and deploys it. The site never goes dark.
The job is a single workflow file. The check step is four lines of bash:
TOMORROW=$(date -u -d '+1 day' +%Y-%m-%d)
if [ -f "workspaces/app/content/puzzles/$TOMORROW.json" ]; then
echo "puzzle for $TOMORROW is already authored — nothing to do"
fi
If the file is missing, the rest of the job runs: install toolchain, install dependencies, call the same gen:puzzle script I run manually. The model defaults to DeepSeek v4 Flash. Medium difficulty, no pattern hint. The diversity constraint (scan the last 7 puzzles’ technique tags, block repeats) keeps the model from producing three binary search puzzles in a row. It usually closes in 2 attempts.
The generation is the easy part. The interesting thing is what comes after.
The generator script only writes files to disk when it passes every gate: schema validation, reference solution against all test cases, brute-force collapse proof, technique diversity. That’s the first safety net. But the CI job runs a second one anyway. After the generator finishes, it fires the full catalogue test suite:
pnpm run check:ci
pnpm --filter @dailyprog/app exec vitest run lib/puzzles.test.ts lib/puzzle-quality.test.ts
puzzles.test.ts loads every .solution.ts file in the catalogue and runs the reference solution against every visible and hidden test case. If the generator produced a solution that doesn’t pass its own tests, this catches it. puzzle-quality.test.ts runs the guardrails: no duplicate technique in the recent window, no back-to-back hard days, every language produces output within the runtime budget. These are the same tests that run on every push to main. The generated puzzle goes through them twice.
If the tests pass, the job commits as puzzle-bot and pushes straight to main:
feat: add puzzle 47 - staggered delivery
Auto-generated by the nightly safety-net workflow.
Co-Authored-By: DeepSeek V4 Flash <noreply@deepseek.com>
Forgejo skips CI on pushes made with the run’s own token (standard loop prevention), so the commit doesn’t trigger another build. The puzzle is in git but not on the site. One more step: the job POSTs to Forgejo’s API to dispatch the deploy workflow. The deploy workflow SSHes into the VPS, pulls the new commit, rebuilds the container image with --no-cache, and brings it up. The new puzzle goes live about 10 minutes after generation started.
The two-hour lead time matters. I check the site most nights. If the model produced something weird (a puzzle where the English prompt is technically correct but the story doesn’t make sense, or one that passes every gate but just feels lazy), I have two hours to write a replacement. The safety net isn’t a substitute for human review. It’s a guarantee that the worst case is “a puzzle I didn’t author” rather than “no puzzle at all.”
The app doesn’t know any of this exists. It reads JSON files from content/puzzles/. The CI runner lives on the same VPS as the site, as an unprivileged user with no special access. The Deepseek API key is a repo secret scoped to the workflow. If the runner goes down, the site still serves puzzles from whatever is already in the catalogue.
The manual path and the automatic path converge on the same checks. I author a puzzle, run the tests, commit, deploy. CI generates one, runs the same tests, commits, deploys. The tests don’t care who wrote the JSON. If CI can’t pass them, the puzzle doesn’t ship. If it can, the puzzle is safe to serve.
There’s no handoff. No separate “auto-generated” code path with weaker validation. No flag that marks a puzzle as machine-authored and lets it skip a gate. The safety net is just the manual workflow running unattended.
Most days I still write the puzzle myself. But the days I can’t, nobody notices :^)
这条对你有帮助吗?