## Summary
- `master` already contains the 124 public-page nginx (crawler files, exact HTML paths, 404 page, security headers) and the shortlink redirect that published port 8082.
- This branch merges that `master` and keeps one change: `absolute_redirect off` on `/f`, `/h`, `/i`, `/l`, `/r`, `/x`, and `/y`, including an optional trailing slash.
- One deploy after merge. No separate 124 deploy. `chat_web_app` is unchanged.
## Test plan
- [ ] Merge, then apply the web-static role on a prod host.
- [ ] `curl -sI https://hesychia.ai/y` returns `302` and `Location: /?utm_source=heycatch&utm_campaign=y` (no `:8082`).
- [ ] Same check for `/f`, `/h`, `/i`, `/l`, `/r`, and `/x`.
- [ ] `https://hesychia.ai/robots.txt` still returns the crawler file, not the app shell.
Reviewed-on: #46
## Summary
- `chat_web_app` nginx now has exact locations for `/robots.txt`, `/sitemap.xml`, and `/llms.txt`.
- Each location serves the static file or returns 404. It does not fall through to `index.html`.
- Other static apps are unchanged. The flag is `exact_root_files` on `chat_web_app` only.
- Merged current `master`, so the Stripe webhook proxy stays in place next to the new locations.
- Closes#43.
The file contents ship in the chat_web_app public-site PR.
## Test plan
- [ ] Render the web-static nginx template and confirm only `chat_web_app` gets the three exact locations
- [ ] Webhook location `/api/finance/webhooks/` is still present
- [ ] After deploy, `curl -I https://hesychia.ai/robots.txt` is `text/plain`, and a missing file would be 404 rather than HTML
Reviewed-on: #44
## Summary
- Closes#41.
- Stripe live endpoint `we_1TzDNHDV0RPXOyxGCOftavHV` posts to `https://hesychia.ai/api/finance/webhooks/stripe/`. On 2026-10-09 the `chat_web_app` prod vhost returned HTTP 405 fifteen times (`request_time` 0.000, User-Agent Stripe). `pending_webhooks` stayed 1.
- `try_files` falls through to `index.html`, and nginx rejects POST on that static file. The Django handler on `chat_backend` never sees the body.
- `chat_web_app` now proxies `/api/finance/webhooks/` to the same-env `chat_backend` port (`8003` prod, `8013` beta) via `host.docker.internal`. `Host` is the backend public hostname so `ALLOWED_HOSTS` matches. Other static apps are unchanged.
## Test plan
- [ ] `ansible-playbook playbooks/site.yml --syntax-check` (or the web-static portion) renders without error
- [ ] Redeploy web-static (`scripts/deploy.sh` path that runs the web-static role) so `nginx.conf` reloads
- [ ] `curl -i -X POST http://127.0.0.1:8082/api/finance/webhooks/stripe/` from an app host reaches Django (400 invalid signature or 503 missing secret), not 405
- [ ] Stripe Dashboard: resend a failed event to the Hesychia.ai endpoint and confirm 2xx
Reviewed-on: #42