Completes the Go backend so it can fully replace Python in production:
tracker-go website layer (serves the unchanged frontend):
- static file serving + SPA fallback + /icons (website.go)
- login/logout with itsdangerous cookie ISSUING (bcrypt, Python-interop) and the
/me handler (auth.go issueSessionCookie + website.go)
- admin user CRUD (website_admin.go) and the issue-board write side (website_issues.go)
- request-scoped user context + requireAdmin (auth.go)
cutover ingest (gated off during the parallel run, required for a clean cutover):
- inventory forwarding: full_inventory -> /process-inventory, inventory_delta ->
item POST/DELETE, per-character serialized, fire-and-forget (inventory_forward.go)
- death/idle Discord alerts via DISCORD_ACLOG_WEBHOOK (aclog.go)
- SKIP_SCHEMA_INIT so write mode against the prod DBs runs no DDL (tracker-go + inventory-go)
two bugs found live and fixed:
- coerceNum: the plugin sends kills_per_hour/deaths/total_deaths/prismatic_taper_count
as STRINGS; pydantic coerced them, Go's number helpers wrote null/0 (reads.go/ingest.go)
- telemetry is broadcast TYPELESS so the browser ignores it and uses the /live poll;
broadcasting it typed flapped the per-player counters 0<->value (ingest.go stripType)
docker-compose.cutover.yml: reversible override flipping the Go services to write
mode against the production DBs and repointing the Discord bot at the Go /ws/live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- share.go: cross-machine vital sharing (share_subscribe/unsubscribe/share_*),
faithful port of the peer-state snapshot + plugin fan-out + /vital-sharing/peers.
The last ingest handler — the Go tracker now handles every plugin event type.
- shadow consumer: drop the outbound keepalive ping (the firehose is never idle)
and tighten the read-deadline watchdog to 12s for faster reconnect after the
upstream's periodic eviction (full-firehose browser clients get evicted ~every
90s; the watchdog recovers it, ~90% duty cycle). Production-bound /ws/position
is unaffected (plugins connect to us; no eviction).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the plugin event handlers (the /ws/position write logic) as a shared
Ingestor, validated against real traffic by replaying Python's /ws/live firehose
into an isolated dereth_go DB (no production write, no plugin stolen).
- ingest.go: faithful ports of telemetry (kill-delta -> char_stats, server
received_at stamp), rare (rare_stats/rare_stats_sessions/rare_events), portal
(coord upsert), character_stats (stats_data JSONB subset + upsert), spawn, and
the memory-only handlers (vitals/quest/equipment_cantrip/nearby/dungeon). In
-memory live state + read-side overlay accessors.
- shadow.go: coder/websocket consumer of /ws/live -> Ingestor.dispatch (telemetry
matched by shape since its broadcast has no type field).
- main.go/store.go: ingest mode (READ_ONLY=false + SHADOW_INGEST_WS) wires the
ingestor; read handlers (/character-stats, /equipment-cantrip, /quest-status)
now consult the live overlay first, like Python.
- compose: shadow instance ingests ws://dereth-tracker:8765/ws/live.
Validated live: dereth_go has 73 distinct telemetry chars; shadow /live online
set == production (73=73); character_stats 5/5 exact byte-match (0 mismatch);
char_stats kill-deltas + portals accumulating. compare/compare_ingest.py.
Deferred to next pass: combat_stats (delta/merge), share_*, the /ws/position +
/ws/live servers (for cutover).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the rest of the read-side endpoints to the Go tracker, all parity-checked
against the live Python service:
- DB reads: /stats/{c}, /portals, /spawns/heatmap, /server-health,
/character-stats/{c} (stats_data JSONB merged to top level),
/combat-stats[/{c}], /inventories, /inventory/{c}/search.
- 5-minute totals cache + /total-rares, /total-kills.
- Ingest-only state returned as Python's empty/default shapes (/quest-status,
/vital-sharing/peers, /equipment-cantrip-state/{c}); /issues (flat file),
/me (401 until cookie verification lands).
- Streaming reverse proxy to inventory-service (/inventory/{c},
/inventory-characters, /search/*, /sets/list, /inv/{path...} incl. the SSE
suitbuilder stream).
- compare/compare_endpoints.py: structural parity for all read endpoints +
exact-match check for /character-stats and /combat-stats on OFFLINE chars
(online chars legitimately differ — Python serves a richer live overlay that
Phase-1 Go lacks until ingest).
Verified live: 14/14 endpoints structural-match, 8/8 rich offline chars
exact-match on /character-stats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>