Consumes the Python tracker's /ws/live firehose (subscribed to rare+chat),
classifies rares common/great, posts embeds + relays allegiance chat.
- classify.go: the 74-name common-rares set, extracted verbatim from the Python
COMMON_RARES_PATTERN (not hand-transcribed). go test runs at build time; a
server-side dump-rares vs the Python regex confirms the sets are IDENTICAL.
- poster.go: a `poster` interface with a real discordgo impl (REST sends by
channel id; gold/blue embeds, location/time fields, icon attachment) and a
dry-run log impl.
- ws.go: coder/websocket client to /ws/live with subscribe, ping keepalive,
exponential-backoff reconnect; rare/chat dispatch incl. vortex-warning + the
MONITOR_CHARACTER filter.
- SAFE BY DEFAULT: dry-run unless a token AND DRY_RUN=0 are set, so it can never
double-post to production. Deployed via the compose override
(discord-rare-monitor-go), running dry-run against the same live firehose.
Validated on the server: connects, subscribes, relays a real chat in dry-run;
classifier parity 74/74 vs the Python regex.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replicates main.py's AuthMiddleware so /go/ can be exposed safely:
- internal-trust: private source IP AND no X-Forwarded-For => skip auth
(loopback/compose callers; nginx adds XFF to all internet traffic).
- session cookie: byte-compatible itsdangerous URLSafeTimedSerializer verify
(HMAC-SHA1, django-concat key derivation sha1("itsdangerous"+"signer"+key),
Unix-epoch timestamp, urlsafe-b64 no pad, optional zlib payload), keyed on the
same SECRET_KEY. 30-day max-age. Public allowlist (/login,/logout,login assets,
/icons/,/health); 302->/login for html, 401 JSON otherwise.
Validated on the server: internal-trust loopback 200; external no-cookie 401;
html 302; valid cookie 200; tampered 401; /health public 200; and the SAME
Python-issued cookie authenticates BOTH services (cross-compat proof).
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>
Parallel Go reimplementation of the dereth-tracker read side, deployed
loopback-only (:8770) and reading the dereth TimescaleDB read-only. The live
Python stack is untouched (added via a compose override, not by editing the
tracked docker-compose.yml).
- Phase 0 scaffold: stdlib net/http server (Go 1.22+ method+path routing),
/health + /api-version, multi-stage distroless Docker build, and
go-services/docker-compose.go.yml override (loopback :8770).
- Phase 1: pgx v5 pool forced into read-only transactions, a 5s /live + /trails
cache loop using the exact main.py:837 SQL, and Python-isoformat timestamps
so output matches FastAPI's jsonable_encoder.
- compare/compare_live.py: parity harness vs the live Python service. Uses the
server-stamped received_at to prove same-row full-field equality and to make
the online-set diff boundary-aware.
Verified on live traffic (73 players): identical online set + 23-key schema,
identity/type parity for all, every same-row pair matches on every field, and
diff-row pairs differ only by the ~6s two-cache refresh skew.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>