Status: shipped 2026-06-14
Branch: master
Commits: see git log --oneline | grep M5
This milestone adds the third ingest transport (WebSocket) on top
of HTTP (M0) and MQTT (M4), plus the SPEC §22 "layer 2" per-IP
concurrency cap, plus an in-process live-tail endpoint for
operators (GET /v1/tail/ws).
| Surface | Method | Path | Auth | Notes |
|---|---|---|---|---|
| Ingest | GET (WS upgrade) |
/v1/ingest/ws |
first frame {api_key} |
text-only, per-IP cap 32 |
| Live tail | GET (WS upgrade) |
/v1/tail/ws |
?token= / Authorization: Bearer / X-BA-Tail-Token |
?company_id= filter |
| Metrics | GET |
/metrics |
n/a | ba_ingestd_ws_*, ba_ingestd_tail_*, ba_ingestd_connection_rejected_total |
| Var | Default | Purpose |
|---|---|---|
BA_INGESTD_TAIL_TOKEN |
empty (disables) | static token for /v1/tail/ws |
BA_INGESTD_MAX_CONCURRENT_PER_IP |
32 | layer-2 per-IP cap for WS ingest |
migrations/005_ws.up.sql # adds max_concurrent_connections to sources/companies
migrations/005_ws.down.sql
internal/concurrency/perip.go # sync.Map[ip]atomic.Int64 + janitor
internal/concurrency/perip_test.go # race-tested
internal/tailhub/hub.go # in-process pub/sub, drop-on-full
internal/tailhub/hub_test.go # fan-out + filter + drops
internal/wsclient/client.go # thin gorilla wrapper
internal/wsclient/client_test.go
loadgen/cmd/ws/main.go # loadgen-ws binary
loadgen/cmd/m5drivers/tail/main.go # /tmp/m5-tail-test driver
cmd/ingestd/ws.go # WS ingest endpoint
cmd/ingestd/wstail.go # WS tail endpoint
cmd/ingestd/process.go # tail.Publish fan-out (shared by all transports)
cmd/ingestd/main.go # env wiring, deps composition
internal/observability/metrics.go # 5 new metric vectors
docker-compose.yml # env vars on ingestd
Client opens a WS, sends {api_key} auth frame, receives
{result:"ready"}, sends one alert envelope, receives
{alert_id, result:"accepted"}. The downstream router fans out
to Alice (fcm) and Alice (telegram) → 2 deliveries in Postgres.
Asserted:
ba_ingestd_ws_messages_total{result="received"} +1ba_ingestd_ws_messages_total{result="accepted"} +1deliveries +2Same source (acme-001:prom-prod), 5 alerts, default normal
profile (30% dedupe means ~2 of 5 share a dedupe_key). Alice
subscribes at min_severity=info; Bob at critical. So the
~3 new alerts all hit Alice, the critical subset also hits Bob.
With 2 channels each, expected ≥ 8 deliveries (often 10 if all
5 are new or all 2 are deduped on the same key).
Asserted:
ba_ingestd_ws_messages_total{result="received"} +5ba_ingestd_ws_messages_total{result="accepted"} ≥3deliveries ≥8Auth passes (valid key), but the envelope auth string contains
a wrong v1 HMAC. ingestd's HMAC verify returns bad_signature,
the alert is rejected, no delivery, the counter ticks.
Asserted:
ba_ingestd_ws_messages_total{result="bad_signature"} +1deliveries +0A test driver opens 35 concurrent WS connections from the same
IP. The concurrency.PerIP gate allows the first 32, the next
3 are rejected at the upgrade. Each rejected conn is counted in
ba_ingestd_connection_rejected_total{transport="ws"}. A
closed_per_ip_cap state is also bumped on the connection
lifecycle counter.
Asserted:
ok=30..32 rejected=2..3ba_ingestd_connection_rejected_total{transport="ws"} +2..3ba_ingestd_ws_connections_total{state="closed_per_ip_cap"} +2..3A test driver connects to /v1/tail/ws?token=... (no company
filter). The handler:
cmd/ingestd/wstail.go for the race that motivated this
ordering)After tail_subscribers reaches 1, the test sends 1 alert via
WS. The hub fans out, the tail handler writes the JSON frame
back, the test driver receives it.
Asserted:
ba_ingestd_tail_subscribers = 1 while connectedFRAME: lineba_ingestd_tail_subscribers returns to 0 after disconnectSame as Step 6 but with ?company_id=globex-002. The test sends
one acme alert (should be filtered out) and one globex alert
(should pass).
Asserted:
cd /root/.openclaw/workspace/broad-announce
bash scripts/m5_smoke.sh
Exit code = number of failed checks (0 on success).
# Tail
wscat -c "ws://localhost:8800/v1/tail/ws?token=tail-dev-token-please-change-in-prod"
# Ingest (from another shell)
curl -i -X POST http://localhost:8800/v1/ingest \
-H "X-BA-Key: acme-001:prom-prod:s3cret-acme" \
-H "X-BA-Signature: t=1700000000,v1=$(echo -n '{"company_id":"acme-001","source_id":"prom-prod","severity":"info","title":"curl-test"}' | openssl dgst -sha256 -hmac s3cret-acme | awk '{print $2}')" \
-H "Content-Type: application/json" \
-d '{"company_id":"acme-001","source_id":"prom-prod","severity":"info","title":"curl-test"}'
POST /v1/ingest per-IP cap — applied in M10 (the "M5-bump" item from SPEC §22).