M0_VERIFICATION.md 5.6 KB

M0 Verification

How to prove the M0 milestone is done.

What "M0 done" means

SPEC §23: "Skeleton + docker-compose up — Postgres + NATS + Redis up, all 4 services start, /health green."

Concretely, the M0 exit criteria are:

  1. docker compose up -d brings up the data tier (postgres, redis, nats, emqx, clickhouse) cleanly.
  2. ingestd, routerd, deliverd, admind all start, log "http listening", expose /health returning 200.
  3. curl -X POST /v1/ingest with a properly HMAC-signed alert returns 202 with {alert_id, dedupe_count, received_at}.
  4. The same dedupe_key sent twice within 60s returns the same alert_id shape but with dedupe_count=2 on the second call.
  5. loadgen-http --mode normal --rate 50 --duration 30s runs cleanly against ingestd with no errors.
  6. Prometheus on :9090 shows ba_ingestd_alerts_received_total with result="accepted" incrementing.

Step-by-step

1. Bring up the stack

cd /root/.openclaw/workspace/broad-announce
docker compose up -d
docker compose ps      # all services "Up (healthy)"

Expected: postgres, redis, nats, emqx, clickhouse come up healthy in ~30s. The four app services depend on them and start last.

2. Check /health

curl -fsS http://localhost:8800/health
curl -fsS http://localhost:8801/health
curl -fsS http://localhost:8802/health
curl -fsS http://localhost:8803/health

Expected: all four return {"status":"ok","service":"..."}.

3. Signed webhook end-to-end

From a host that can reach ingestd (localhost:8800 if running docker compose on the same host):

SECRET="s3cret-acme"
BODY='{"company_id":"acme-001","source_id":"prom-prod","severity":"critical","category":"storage","title":"Disk full on db-prod-03","body":"92% used","data":{"host":"db-prod-03"},"dedupe_key":"disk:db-prod-03:full"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')

curl -fsS -X POST http://localhost:8800/v1/ingest \
  -H "Content-Type: application/json" \
  -H "X-BA-Signature: t=$TS,v1=$SIG" \
  --data "$BODY"

Expected:

{"alert_id":"018f...","dedupe_count":1,"received_at":"2026-06-13T..."}

Run the same call again — dedupe_count should be 2.

4. Bad signature

Same call but with v1=deadbeef:

curl -i -X POST http://localhost:8800/v1/ingest \
  -H "X-BA-Signature: t=$TS,v1=deadbeef" \
  -H "Content-Type: application/json" \
  --data "$BODY"

Expected: 401 {"error":"bad_signature"}.

5. Payload too large

python3 -c 'import sys; sys.stdout.write("{\"x\":\"" + "a"*300000 + "\"}")' \
  | curl -i -X POST http://localhost:8800/v1/ingest \
      -H "Content-Type: application/json" \
      --data-binary @-

Expected: 413 {"error":"payload_too_large"}.

6. Schema invalid

curl -i -X POST http://localhost:8800/v1/ingest \
  -H "Content-Type: application/json" \
  --data '{"company_id":"acme-001","source_id":"prom-prod","severity":"BOGUS","title":"x"}'

Expected: 400 {"error":"invalid","detail":"invalid alert: [severity: ...]"}.

7. loadgen-http

docker compose --profile loadgen up loadgen-http

Expected: 30 seconds of progress lines, then a "done" summary with failed=0. The ba_ingestd_alerts_received_total{result="accepted"} counter in Prometheus should be ~1500 (= 30s × 50/s).

8. Metrics

curl -fsS http://localhost:8800/metrics | grep ba_ingestd

Expected output:

# HELP ba_ingestd_alerts_received_total Number of inbound alerts by result.
# TYPE ba_ingestd_alerts_received_total counter
ba_ingestd_alerts_received_total{result="accepted",service="ingestd"} 1500
ba_ingestd_alerts_received_total{result="deduped",service="ingestd"} 450
...

If something fails

symptom first thing to check
connection refused on /health docker compose ps — is the service running?
nats:4222: dial: connection refused nats container unhealthy; docker compose logs nats
redis:6379: dial: connection refused redis container unhealthy
401 bad_signature on a "valid" call openssl vs Go HMAC mismatch; re-check the printf '%s.%s' format
404 on /v1/ingest wrong path; must be POST /v1/ingest (the handler is registered with POST method)
nats: no stream broker.Connect failed; logs will say so
nothing in alerts.* subject did EnsureStreams run? Check the ingestd log on first start

What's NOT in M0 (M0 honest scope)

These all work (return correct HTTP code, log correctly) but return no-ops. M-stuff that lands them is called out:

  • routerd: starts, /health works, but does not consume alerts.* or resolve recipients. M2.
  • deliverd: starts, /health works, but does not consume deliveries.* or call any sink. M3 (FCM), M5 (Telegram).
  • admind: starts, /health + /v1/ping work, no tenant CRUD yet. M8.
  • Multi-tenant isolation: source registry is env-based. M2 swaps for DB and adds WHERE company_id = $1 everywhere.
  • Per-IP concurrency cap (SPEC §22 layer 2): not yet wired. M5 with WS.
  • Circuit breaker (layer 6), quarantine (layer 7): M9.
  • Schema-based protection is a single Validate() call today. Future: per-source allowlist, JSON Schema files.
  • gRPC, MQTT, WebSocket ingest: not yet. M4, M5, M11.
  • ClickHouse archive job: not yet. M7.
  • Real FCM: fakes only. M3.
  • Source registry in DB: env-only. M2.

If M0 verification passes, M1 is unlocked: add a no-op deliverd-fcm worker that consumes deliveries.fcm.<company_id> and writes to the testfakes FCM server.