Small Go backend for casino / sports brands.
Other systems send player actions (deposit, bet). EngagePulse updates that player’s VIP status, welcome bonus, and warning flags, and may add a reward balance of 100 once.
Try it: engagepulse-topaz.vercel.app — open Pulse Arena and click Ignite live demo.
Ignite needs the Go API online (Postgres + a Kafka-compatible broker). The public demo API is already hosted: https://api-production-2ef9b.up.railway.app (Postgres + Apache Kafka on Railway; local Compose still uses Redpanda). If the API is down, the console shows a clear offline banner and links to the Arena demo mp4.
Use the app on your machine. Start only Postgres + Redpanda in Docker.
The app listens on 127.0.0.1 by default (avoids localhost → IPv6 confusion).
cp .env.example .env
docker compose up -d postgres redpanda
make migrate
make runIn another terminal:
make demomake demo explains each step, sends traffic for two brands, then prints both player results.
If the Compose app image fails to build (module download blocked in Docker), that is fine. Local
make runis the normal path.
Latest release v0.3.0 — web console (Pulse Arena):
| Clip | What you see |
|---|---|
| Arena guided demo (mp4) | Ignite → Acme VIP + Nova velocity payoff |
| Architecture live (mp4) | Event-path diagram + live metrics poll |
| Full tour (mp4) | Arena run then Architecture page |
| Arena preview (gif) | Short silent preview |
Posters: arena · architecture
CLI demo (still on v0.2.1):
- Video: engagepulse-demo-v0.2.1.mp4
- Text recording: engagepulse-demo-v0.2.1.cast
(asciinema play engagepulse-demo-v0.2.1.cast)
Run the CLI demo yourself:
docker compose up -d postgres redpanda
make migrate
make run
# other terminal:
make demo| Brand | Meant to show | Look for |
|---|---|---|
acme-casino |
Welcome bonus + VIP going up | welcome_bonus, VIP silver or gold, balance: 100 |
nova-sports |
Betting too fast | welcome_bonus, integrity_flag: velocity, balance: 100 |
make replay is the contract for those two brands: it applies testdata/replay/ through the worker and fails if the player JSON drifts. make replay-kafka applies the same packs through signed ingest, outbox, Kafka, and the consumer, and still credits once on a second run. make demo is the guided tour of the same outcomes against a running API.
make rulepatch prints an advisory diff for a request like raise velocity threshold and re-runs replay before/after. It does not write internal/rules. Copy the diff yourself if you want it. CI uses the mock proposer; PROPOSER=openai without OPENAI_API_KEY errors instead of falling back to mock.
make shadow scores recorded replay bursts against the velocity rule and prints a disagreement board (pack, rule, model, agree). Mock is the default: flag when the pack has at least VelocityBetLimit bets. The 1-minute rule can disagree (see testdata/replay/sparse-bets/). Shadow does not credit or set VIP. CI uses the mock scorer; SHADOW_SCORER=openai without OPENAI_API_KEY errors instead of falling back to mock.
| Idea | Where |
|---|---|
| Words like VIP / welcome / velocity | docs/concepts.md |
| How messages move through the app | docs/architecture.md |
| Two brands, separate data | migrations/, internal/store |
| Signed event intake | internal/ingest, POST /v1/events |
| Save first, send to stream later | internal/store/outbox.go, internal/outbox |
| Message stream + failed-message topic | internal/kafka |
| Worker mark + state + credit in one DB tx | internal/worker, internal/store, internal/ledger |
| The three rules | internal/rules |
| Advisory rule patch | cmd/rulepatch, internal/rulepatch |
| Shadow flag vs velocity | cmd/shadow, internal/shadow |
| Bonus credit without double-pay | internal/ledger |
| Read player over HTTP / gRPC | internal/api/httpapi, internal/api/grpcapi |
| HMAC / API-key / tenant isolation tests | internal/api/httpapi/security_test.go |
| What | Path |
|---|---|
| Send event | POST /v1/events (header X-Signature, optional X-Key-Id; schema_version omitted or 1; response X-Trace-Id) |
| Rotate HMAC | POST /v1/hmac/rotate (header X-API-Key; overlap for that brand only) |
| Read player | GET /v1/players/{id} (header X-API-Key) |
| List dead letters | GET /v1/dlq (header X-API-Key) |
| Tool call | POST /v1/tools/{name} (header X-API-Key; ingest also X-Signature) |
| Read player (gRPC) | GetPlayer (metadata x-api-key) |
| Live | GET /livez (process up; /healthz is the same) |
| Ready | GET /readyz (Postgres ping + Kafka dial; 503 if either is down) |
| Counters | GET /metrics |
| Command | What it does |
|---|---|
make up |
Start Postgres + Redpanda |
make migrate |
Create / update tables (+ seed brands) |
make loadgen |
Send signed events |
make soak |
Local only: unique first deposits plus duplicate deliveries; credits Δ equals unique ids, each balance 100. Do not point at Railway. |
make demo |
Guided end-to-end run |
make replay |
Replay pack vs expected player JSON (needs DATABASE_URL) |
make replay-kafka |
Same packs through ingest → outbox → Kafka → consumer (needs Kafka) |
make rulepatch |
Print an advisory rule diff and replay traces (not applied) |
make shadow |
Print shadow vs velocity disagreement board (no credits) |
make test |
Tests (set DATABASE_URL for DB tests) |
Creative dual-tenant arena UI in web/ — Next.js BFF over the live Go API.
Hosted: engagepulse-topaz.vercel.app (points at the Railway API above).
Local UI (API already up via make run):
cd web && npm install && npm run devOpen http://localhost:3000 and click Ignite live demo.
See web/README.md for pages, env vars, and hosting notes.
CI runs gofmt, go vet, golangci-lint, go test ./... -p 1, make replay, and make replay-kafka on main / develop.