fix(booth): templates were hot-reloading into a live service running older Python
19 of 25 live booths returned 500 with `UndefinedError: 'item_marks' is undefined`. Neither the old code nor the new code was broken — the service was running both at once. `booth.service` sets WorkingDirectory to this repo, so the repo IS the deployment root: no build step, no staging copy, the live service imports these files. Python is read once when the process starts. Jinja's FileSystemLoader re-reads a template on EVERY render. So the two halves of the service had different staleness rules, and editing booth.html deployed it instantly against Python from 22:03 that had never heard of the context the new markup wanted. The failure mode is worth naming precisely, because it is invisible to the suite by construction: the skew exists between a running process and the disk underneath it, so every test can pass against a tree that is simultaneously serving 500s. No amount of green catches this. The operator found it. Fixed at the source rather than with a reminder to restart. The template Environment is built here with auto_reload=False, so templates are cached at startup exactly like the Python, and there is ONE rule: nothing takes effect until you restart. The price is that template work needs a restart to see — that price is the entire point, and it is cheaper than a page of 500s while someone is reviewing. Building the Environment by hand means autoescape no longer comes from the Jinja2Templates constructor, so it is explicit and load-bearing: booth names, item names and mark text are all agent- or operator-authored strings that land in HTML. Verified escaped, not merely configured. Two tests hold the line — one on the snapshot property, one on the `dur` filter that is no longer incidental to the constructor. The environment is reachable at app.state.templates because a promise about the deployed service needs an assertion, and an assertion needs the env the app actually renders with. Also records the foot-gun in CLAUDE.md and persistent-memory: anyone editing this repo while the operator may be using the service is editing production. 244 tests. No version bump — the release tier for U2 is still the operator's call, and this rides with it.
This commit is contained in:
@@ -152,6 +152,42 @@ Ask which one you have before choosing a storage shape.
|
||||
- **Nothing deletes the operator's data on a timer** beyond the documented 24h
|
||||
TTL. Liveness is *flagged*, not enforced.
|
||||
|
||||
## ⚠ The repo IS the deployment root
|
||||
|
||||
`booth.service` runs uvicorn with `WorkingDirectory=/home/lkraven/development/booth`.
|
||||
There is no build step, no staging copy, and no separate deploy artifact: the
|
||||
live service on `:8090` imports **these files**. Two consequences, and the second
|
||||
one caused an outage.
|
||||
|
||||
1. **A Python edit does nothing until you restart.** Expected, and documented
|
||||
below.
|
||||
2. **A template edit used to take effect INSTANTLY.** Jinja's `FileSystemLoader`
|
||||
re-reads a template from disk on every render. So the two halves of the
|
||||
service had different staleness rules, and editing `booth.html` deployed it
|
||||
immediately against Python that had never heard of the context it wanted.
|
||||
|
||||
On 2026-09-21 that put **19 of 25 live booths at 500** —
|
||||
`UndefinedError: 'item_marks' is undefined` — with the Python from 22:03 and
|
||||
the templates from 23:40. Neither version was broken; the service was running
|
||||
both. The operator found it, not the suite, because no test can see a skew
|
||||
that only exists between a process and the disk under it.
|
||||
|
||||
Fixed at the source: the template `Environment` is now built with
|
||||
`auto_reload=False`, so templates are cached at startup exactly like the
|
||||
Python. **One rule now — nothing takes effect until you restart.** The price
|
||||
is that template work needs a restart to see, and that price is the point.
|
||||
`test_templates_do_not_hot_reload_from_disk` holds the line.
|
||||
|
||||
**So: after ANY edit here — Python or template — the live service is stale until
|
||||
you restart it.** If you are touching this repo while the operator may be using
|
||||
the service, either restart promptly or expect him to be looking at the old
|
||||
version. Never leave the tree in a state where a restart would 500.
|
||||
|
||||
⚠ The `Environment` is hand-built now, which means `autoescape` is explicit
|
||||
rather than inherited from the `Jinja2Templates` constructor. It is on
|
||||
(`select_autoescape(["html", "xml"])`) and it is load-bearing: booth names, item
|
||||
names and mark text are all agent- or operator-authored and land in HTML.
|
||||
|
||||
## Working in here
|
||||
|
||||
```sh
|
||||
|
||||
Reference in New Issue
Block a user