Commit Graph
291 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 854869ba53 Coordinate Craft App release polling across the fleet through Redis (#77)
New releases took up to ~30 min to reach \`GET
/v1/craft_apps/release_info\` (ArtCraft Launcher 0.2.0 needed 17–29
min), pods disagreed with each other, and every pod polled GitHub on its
own, which uses up the unauthenticated quota (60/h per IP, and 304s
count) and risks backing off for up to 4 h.

The pods now share their results through Redis. There's no lock:
- **One Redis hash per app** (\`craftApps:releases:app:<key>\`, 7 d
TTL). It holds the latest release, the ETag and \`fetched_ms\`. Before
asking GitHub about an app, a pod reads its entry. If it's fresher than
\`interval − 30 s\` (a clock-skew margin), the pod takes it from Redis
and doesn't call GitHub.
- **Last write wins, guarded by timestamp.** A Lua script never lets an
entry go back in time. A 304 moves only \`fetched_ms\`, and only while
the stored ETag still matches.
- **A soft per-app claim** (60 s, no owner, it just lapses), so a fleet
that ticks every 30 s rarely fetches the same app twice.
- **Backoff shared by the whole fleet**
(\`craftApps:releases:backoff\`), so a rate limit slows everyone down.
Per-app failure retry is 60 s, doubling.
- **The endpoint is unchanged.** It still serves each pod's in-memory
copy. If Redis is down, a pod falls back to polling on its own and warns
at most every 10 minutes.

**The GitHub token is optional.** With
\`CRAFT_APPS_RELEASE_GITHUB_TOKEN\` set, the default interval is 120 s
(min 60). Without it (unset or blank), calls go out unauthenticated with
no Authorization header, at 1800 s (min 300), and startup and polling
are never blocked. \`CRAFT_APPS_RELEASE_COORDINATION_ENABLED=false\`
switches back to the old per-pod polling.

**Tests:** \`cargo test -p storyteller-web --bin storyteller-web craft\`
(49 pass) and \`cargo test -p redis_schema craft\` (4 pass). The Redis
tests passed against a local redis-server with
\`CRAFT_APPS_TEST_REDIS_URL=… -- --include-ignored --skip live_round\`
(51 pass).

**Follow-ups after review:**
- **The poller has its own Redis pool** (2 connections; connects within
3 s; every read and write, including the pool's `PING` check, times out
after 5 s). Before this, it shared the handlers' pool, which has no read
timeout, so a Redis that stopped answering could leave the poller
holding every connection while handlers waited 30 s for one. A test
against a fake Redis that goes silent after the handshake fails without
the timeouts and passes with them.
- **When Redis won't run scripts**, writes fall back to
`WATCH`/`MULTI`/`EXEC`: read and decode the guarding fields, decide, and
write in a transaction that Redis drops if another pod wrote in between.
This switch only happens when Redis refuses the script itself. Being
unreachable, busy, out of memory or read-only doesn't trigger it. It was
tested against a real Redis user denied `@scripting`.
- **New pods load from Redis at startup**, before the startup jitter, so
they serve the fleet's releases right away without asking GitHub.
- **`Cache-Control: no-store` until every app has been fetched**
(`last_fetched` set), so Cloudflare never caches a cold pod's "not
fetched yet" answer. The response body is unchanged.

**Tests:** `cargo test -p storyteller-web --bin storyteller-web craft --
--include-ignored --skip live_round` against a local Redis 8.6: 63 pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:05:06 -04:00
Brandon Thomas c3d72fb321 Generate WebP thumbnails for videos instead of Gif (except: MacOS 10.15)
MacOS Catalina 10.15 released October 7, 2019 can't do gifs in
safari/webkit.
2026-09-28 00:52:07 -04:00
Brandon Thomas 55ab800542 more performant list user files endpoint 2026-09-27 15:02:19 -04:00
Brandon Thomas b6a184bcb0 enum variants 2026-09-26 02:36:24 -07:00
Brandon Thomas d0e1d113fe break out queries 2026-09-26 02:13:23 -07:00
Brandon Thomas 1facb312dc tests 2026-09-26 01:57:16 -07:00
Brandon Thomas 4f1c9134ee WIP SSO OTP bridge 2026-09-25 23:23:00 -07:00
Brandon Thomas 3e6d759fe2 fix thumbnail job latency 2026-09-20 10:12:38 -04:00
Brandon Thomas 734b757ef9 gpt image 2.5: new qualities 2026-09-18 14:12:34 -04:00
Brandon Thomas 92bc1e24c4 wan 3.0 + prime 2026-09-18 13:40:50 -04:00
Brandon Thomas 7dd5331232 hide behind feature flag 2026-09-15 18:48:36 -04:00
Brandon Thomas ef2710fcc8 wip 2026-09-14 15:40:33 -04:00
Brandon Thomas cb1bea9d0a remove the desktop app from the repo 2026-09-12 12:23:46 -04:00
Brandon Thomas 7b88737c45 fix tauri for omni apis (#1941) 2026-09-11 01:03:53 -04:00
Brandon ThomasandHanashi 7fb81500a7 rename CLAUDE.md to AGENTS.md (#1940)
Co-authored-by: Hanashi <shared2@storyteller.ai>
2026-09-10 21:55:17 -04:00
Brandon ThomasandHanashi 74207e3520 gpt-image-2.5 (#1937)
Co-authored-by: Hanashi <shared2@storyteller.ai>
2026-09-09 16:37:35 -04:00
hanashi 0fa098dd38 more providers (#1922) 2026-09-01 10:45:02 -04:00
hanashi 44ff9ce338 mcp sessions (#1917) 2026-08-27 13:05:55 -04:00
hanashi f563b06614 Cleanup dead enums (#1878) 2026-08-10 01:50:46 -04:00
hanashi c62a269638 testing omni gen (#1875) 2026-08-09 22:54:25 -04:00
hanashi f767fc9638 cleanup (#1872) 2026-08-08 22:33:46 -04:00
hanashi 534337aa3a seedance 2.5 ultra (#1870) 2026-08-08 21:46:14 -04:00
hanashi 9646a791aa minimax feature (#1865) 2026-08-08 16:34:49 -04:00
hanashi c888a494b9 seedance 2.5 bindings (#1862) 2026-08-07 23:41:44 -04:00
hanashi a7a32a3862 minimax worker endpoints (#1855) 2026-08-07 01:24:20 -04:00
hanashi 9020f0aa5b minimax turbo (#1852) 2026-08-06 18:21:28 -04:00
hanashi a473be78bd new endpoint for media files by job token (#1846) 2026-08-04 23:13:30 -04:00
hanashi 36cfba0000 media files - record job token (#1845) 2026-08-04 22:09:08 -04:00
hanashi a5f59d7995 Flux3 (#1844) 2026-08-04 15:12:32 -04:00
hanashi 37b7f3e88f Minimax (#1840) 2026-08-04 05:31:17 -04:00
hanashi 60b4148179 web + API for omni gen endpoints (#1836) 2026-08-03 16:22:16 -04:00
hanashi a71a4b9613 seedance2p5 (#1825) 2026-07-31 18:12:27 -04:00
hanashi 9737f0ef88 rename client (#1824) 2026-07-31 15:10:30 -04:00
hanashi 17c12836ad seedream 5 plus (#1822) 2026-07-30 18:25:19 -04:00
Brandon Thomas 0003cad179 seedream5 (#1784) 2026-07-13 11:10:21 -04:00
Brandon Thomas 348a603cae folderless endpoint (#1779) 2026-07-12 15:23:58 -04:00
Brandon Thomas 7dffc5fb5c list meshes and splats (#1774) 2026-07-12 09:32:35 +07:00
Brandon Thomas 9cc9d4b9cf Fix cover images (#1773) 2026-07-11 15:32:15 -04:00
Brandon Thomas dc5a4f8dd9 backup for cover images when absent 2026-07-10 17:14:13 -04:00
Brandon Thomas 73b40d321e fal 3d fixes (#1771) 2026-07-10 16:41:59 -04:00
Brandon Thomas cb6d0fe27c new tags system (replacing the old one) (#1770) 2026-07-10 05:21:49 -04:00
Brandon Thomas 698c330734 remove old tags code (#1767) 2026-07-10 02:31:37 -04:00
Brandon Thomas 1dfb62454a 8 new persistence endpoints, 2 for each type (#1766) 2026-07-10 01:32:23 -04:00
Brandon Thomas 87f35f3e6f Remove endpoints for old Rust / Bevy game engine (#1765) 2026-07-09 23:52:16 -04:00
Brandon Thomas df0a82cbdb triposplat support (#1764) 2026-07-09 21:00:32 -04:00
Brandon Thomas c5071d4f2e list project media files endpoint (#1763) 2026-07-09 20:08:59 -04:00
Brandon Thomas 57c77cbbc2 stop writing Dimensional for Mesh/Splat types (#1762) 2026-07-09 17:59:10 -04:00
Brandon Thomas 98c4fad729 add metadata to project files; remove legacy 3d editor endpoint (#1761) 2026-07-09 17:23:09 -04:00
Brandon Thomas f50bfa9ee7 forgot to commit this omg 2026-07-09 16:25:38 -04:00
Brandon Thomas d90d446643 new field for media files, splat/mesh split (#1760) 2026-07-09 00:43:54 -04:00