feat(server): counters, readiness, drain mode and a version stamp

expvar counters for connections, rooms, games, submissions by rejection
reason, eliminations, chat, joins and bot moves, served on a separate
debug address so they never sit on the public mux, plus one structured
word_rejected log line per refused word carrying the normalized word and
its link. GET /readyz flips to 503 while draining; SIGTERM stops new
rooms, waits up to NOITU_DRAIN_TIMEOUT for live games, then shuts down.
GET /version and the startup log carry the build's git describe.

Fuzz targets for the frame decoder, the text sanitizer and Vietnamese
normalization; the last one found that composing before lowercasing
could leave a non-NFC result, now recomposed after lowering.

CI runs on dev as well as main, gates gofmt and golangci-lint, tracks the
buf major instead of an exact pin, and dependabot watches every
ecosystem. The lint findings that had been hidden by the default
per-issue cap are fixed.
This commit is contained in:
tiennm99 committed 2026-09-21 01:17:52 +07:00
1 parent 90cd679639
commit 8223f40b16
29 files changed
+1204 -66

No files matched your search

+27 -1
View File
@@ -10,7 +10,7 @@ name: ci
on:
push:
branches: [main]
branches: [main, dev]
pull_request:
release:
types: [published]
@@ -33,6 +33,28 @@ jobs:
run: go vet ./...
working-directory: server
# gofmt -l lists files that are not gofmt-clean; -d would only show the
# diff. Non-empty output is the failure signal, so it decides the exit
# code itself rather than leaning on the emptiness of a report nobody
# reads.
- name: Format check
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "not gofmt-clean:"
echo "$unformatted"
exit 1
fi
working-directory: server
# Default linters only: this is a signal every PR has to pass, not a
# style debate, so nothing beyond golangci-lint's own defaults is
# enabled here.
- name: Lint
uses: golangci/golangci-lint-action@v9
with:
working-directory: server
# -race because the whole transport layer is goroutines and timers, and a
# data race there is exactly the kind of defect that passes without it.
- name: Test
@@ -57,6 +79,10 @@ jobs:
run: npm run check
working-directory: web
- name: Lint
run: npm run lint
working-directory: web
# npm test builds first, so this also proves the bundle compiles and
# carries no wordlist.
- name: Test
+5 -2
View File
@@ -7,7 +7,7 @@ name: proto
on:
push:
branches: [main]
branches: [main, dev]
pull_request:
permissions:
@@ -22,9 +22,12 @@ jobs:
# buf breaking compares against main, which needs real history.
fetch-depth: 0
# A moving version rather than an exact pin, per house rule: updates
# arrive on the next run, and a breaking major would surface here before
# it could surprise a contributor's own machine.
- uses: bufbuild/buf-setup-action@v1
with:
version: 1.69.0
version: latest
- uses: actions/setup-go@v5
with: