Commit graph

14 commits

Author SHA1 Message Date
jedarden
941f8bd2c9 fix(db): add LIMITs to unbounded queries to prevent OOM
- Add LIMIT 1000 to fetchChampionshipBracket (was unbounded)
- Reduce fetchSeries from LIMIT 5000 to LIMIT 1000
- Reduce fetchLineage from LIMIT 50000 to LIMIT 10000
- Reduce fetchFeedback from LIMIT 5000 to LIMIT 1000
- Reduce fetchRatingHistory from LIMIT 10000 to LIMIT 5000

The acb-index-builder pod has been in CrashLoopBackOff with OOMKill
(exit code 137) for 45 days with 4713 restarts. These unbounded queries
were loading too much data into memory, causing the kernel to kill the
process before any logs could be written.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 00:38:55 -04:00
jedarden
4b7d81db45 feat(index-builder): bundle warm-set replays as static Pages assets
Per bead bf-3e60: instead of copying B2->R2 (warm cache), bundle warm-set
replays, thumbnails, cards, and evolution live.json directly into the Pages
deploy directory as static assets (dist/data/). This serves replays same-origin,
eliminating R2 dependency and 404 errors.

Changes:
- Add B2Client interface for testable B2 operations
- Add bundleWarmReplays(): copies replays/*.json.gz from B2 to dist/data/replays/
- Add bundleWarmThumbnails(): copies thumbnails/*.png from B2 to dist/data/thumbnails/
- Add bundleWarmCards(): copies cards/*.png from B2 to dist/data/cards/
- Add bundleEvolutionLive(): copies evolution/live.json from B2 to dist/data/evolution/
- Replace promoteRecentReplaysForCycle() with bundleWarmAssetsForCycle()
- Remove R2 pruning logic from main loop (no longer needed)
- Add unit tests for all bundling functions with mock B2 client

Replays are served gzipped (as-is from B2) to keep deploy size under Pages'
25MB file limit. Frontend will gunzip client-side (separate bead bf-5cwi).

All tests pass (go test ./...).

Closes: bf-3e60
2026-05-26 07:35:48 -04:00
jedarden
c397e66239 fix(index-builder): run wrangler from /app/web to pick up functions/ bundle
_worker.js static file approach fails — Cloudflare rejects it when uploaded
as a static asset. Instead, copy web/functions/ into the image and set
wrangler CWD to /app/web/ so it discovers functions/ and uploads the Pages
Functions bundle correctly on every deploy cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 12:39:57 -04:00
jedarden
41c7223f8a refactor(web): promote app.html to index.html as homepage
The main leaderboard SPA is now served at / (index.html) and the
standalone replay viewer lives at /replay.html. This removes the
_redirects workaround in index-builder that patched over the inverted
entry points.

- Rename web/app.html → web/index.html (main SPA)
- Rename web/index.html → web/replay.html (standalone viewer)
- Update vite.config.ts: main→index.html, replay→replay.html
- Remove _redirects injection from deploy.go verifyMergedOutput
- Update pages.json routes and README dev URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 08:51:52 -04:00
jedarden
5d89fbd885 fix(index-builder): add _redirects to route / → app.html
The replay viewer was baked into index.html (served at /) while the
leaderboard app was at /app.html. Add a _redirects file so visitors
landing on / get redirected to the main leaderboard app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 07:22:06 -04:00
jedarden
88bd70640a fix(types): add missing ReplayPlayer import and type annotation for transcript feature
- Add ReplayPlayer to type imports in replay-viewer.ts
- Add explicit type annotation for entry parameter in replay.ts transcript map
- Fixes TypeScript compilation errors for §15.3 screen reader transcript feature
2026-04-22 18:20:56 -04:00
jedarden
89560e5ec4 feat(index): implement match thumbnail PNG generation (§7.2, §14.3)
Implementation complete:

1. engine/thumbnail.go - New thumbnail rendering package
   - GenerateMatchThumbnail() creates 640x360 PNG thumbnails
   - Renders grid, bots, cores, walls, energy with player colors
   - SelectThumbnailTurn() chooses most interesting turn
   - Pure Go stdlib image rendering (no canvas required)

2. cmd/acb-worker - Upload thumbnails to B2 alongside replays
   - uploadThumbnail() generates PNG and uploads to B2
   - Key: thumbnails/{match_id}.png, content-type: image/png
   - Called after match completion, non-blocking on failure

3. cmd/acb-index-builder/deploy.go - Promote thumbnails to R2
   - promoteRecentReplays() copies both replays AND thumbnails from B2 to R2
   - Thumbnails promoted to warm cache alongside replay promotion

4. cmd/acb-index-builder/generator.go - Populate thumbnail URLs
   - buildPlaylistMatch() now includes thumbnail_url field
   - URL pattern: https://r2.aicodebattle.com/thumbnails/{match_id}.png
   - Enables playlist cards and embed OG tags to show preview images

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:42:36 -04:00
jedarden
7f2407ed00 feat: add Prometheus metrics instrumentation across services
Add metrics server startup and HTTP middleware to acb-api, generation
counter metric to evolver, and R2 cache size metric to index builder.

Also remove dead measureR2CacheSize reference from index builder main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:16:03 -04:00
jedarden
be3843d9ac fix(index-builder): correct series/season exempt queries, optimize playlist curation
- Fix deploy.go to query actual table names (series_games not series_matches,
  join through series_games for seasons instead of non-existent season_matches)
- Add playlist_matches table to exempt match IDs from R2 pruning
- Pre-build lookup maps for O(1) playlist match filtering instead of O(n²)
- Enhance home page featured replay to prefer AI-enriched matches
- Add enrichment test coverage (shouldEnrich criteria validation)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 16:47:13 -04:00
jedarden
b1121ed6f8 feat(playlists): add playlist curation and rebuild logic per §10, with series/seasons/enrichment
Implement auto-curated playlists in the index builder: 12 playlist types
(closest finishes, upsets, comebacks, marathons, rivalry classics, etc.)
with weekly highlight curation. Add DB persistence, R2 pruning exemptions,
frontend pages, and AI commentary enrichment pipeline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 16:11:27 -04:00
jedarden
dcdca5147f fix(index-builder): stream-copy web assets to avoid large-file OOM
Replace os.ReadFile+os.WriteFile with io.Copy so large files (e.g. the 21MB
demo-replay-v2-6p.json) are never fully loaded into RAM during copyWebAssets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:35:42 -04:00
jedarden
82d5ecba1f fix(index-builder): set wrangler CWD to /tmp for Pages deploy
wrangler creates .wrangler/tmp relative to its working directory.
The container runs as non-root user acb with WORKDIR=/app (root-owned),
so mkdir /app/.wrangler/tmp fails with EACCES. Setting cmd.Dir=/tmp
gives wrangler a writable CWD while keeping the /data output path
(absolute) unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:03:20 -04:00
jedarden
21308dce05 Implement S3 functions for R2/B2 integration in acb-index-builder
- Add s3.go with AWS SDK v2 S3Client wrapper for R2/B2 operations
- Implement listObjects, deleteObject, objectExists, uploadFile, copyObject, downloadObject
- Add s3_test.go with MockS3Client and comprehensive tests
- Wire promoteRecentReplaysForCycle() into build cycle in main.go
- Add fetchRecentMatchIDs() to query recent matches from PostgreSQL
- Add fetchExemptMatchIDs() to protect series/season/playlist matches from pruning
- Implement pruneR2CacheWithDB() for 10GB cap enforcement with exemptions
- Update go.mod with AWS SDK v2 dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 09:09:29 -04:00
jedarden
70bde20472 Add Go index builder (cmd/acb-index-builder)
Per plan §11.1, the index builder reads PostgreSQL and generates all JSON
index files for Cloudflare Pages deployment:

- main.go: Build cycle orchestration with configurable timeout, self-restart
- config.go: Environment-based configuration with sensible defaults
- db.go: PostgreSQL data fetching for bots, matches, series, seasons, predictions
- generator.go: JSON index generation (leaderboard, bots, matches, playlists)
- deploy.go: Cloudflare Pages deployment via wrangler, R2 warm cache pruning
- Dockerfile: Multi-stage build with Go + Node.js + wrangler CLI
- main_test.go: Tests for config, index generation, playlists

Index builder runs on 15-minute cycles, deploys to Pages every ~90 minutes,
and prunes R2 warm cache weekly to stay within 10GB free tier.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 03:15:47 -04:00