Broad-Announce — Build Log (PROMPT)
Decisions, lessons, blockers. Append-only. Update as we go.
2026-06-13 — kickoff
Decided
- Repo at
git3.techno-world.net/lrosales/broad-announce (private).
- v1 stack: Go, PostgreSQL + Timescale, ClickHouse, NATS JetStream,
Redis, EMQX (MQTT), Prometheus + Grafana, Loki. Deploy v1 = Docker
Compose. v2 = K8s.
- Multi-tenancy = shared infrastructure, strict app-level isolation.
No row-level security in v1; tenant filter on every query.
- FCM = single shared project for v1, schema supports per-company
FCM project later (
companies.fcm_shared).
- Telegram is a first-class delivery channel + a management UI
(the bot is how users mute, subscribe, acknowledge).
- Severity taxonomy:
info | warning | critical | inminent_colapse.
Only inminent_colapse bypasses quiet hours.
- Dedupe: 60s window per
(source_id, dedupe_key), attach
dedupe_count so user sees "×N in 60s" not N pushes.
- v1 capacity target in docker-compose: 5k alerts/sec sustained.
50k/sec is the design ceiling; the K8s + multi-broker work
is what unlocks it.
Open (was) → Resolved 2026-06-13
Bot ↔ individual linking → admin-invites only. Flow: admin
creates individuals row + generates one-time invite code;
user runs /start <invite_code> in the Telegram bot; bot
matches the code, links telegram_chat_id to the
pre-existing individual, and burns the code. Stricter path:
prevents drive-by bot self-registration, keeps
individuals auditable.
Localized titles → source-localized, pass through. v1
does NOT translate. Sources send pre-localized
title/body strings (or use the locale on the
subscription to key into their own lookup table before
calling our API). Alert schema carries locale and
title/body already-resolved. We can add a translation
layer in v2 if customers ask.
- mTLS: schema supports it, but the docker-compose profile won't
terminate client certs in v1. Documented as opt-in for
enterprise sources.
Lessons (already)
- It's much cheaper to answer "what can this be?" with 30
questions than to refactor later. Most of the SPEC's weight is
in §11 (security) and §6 (recipient resolution) — those are
the parts that are expensive to change after launch.
- For multi-tenant at 10k companies, the one design choice
that compounds is the subject layout in the broker.
alerts.<company_id> is fine; alerts.<company_id>.<source_id>
would let us scale router consumer groups per source. Locked
in §6 of ARCHITECTURE.md.
2026-06-13 — added gRPC ingest (option a)
- Decision: ship gRPC bidi-streaming as the fourth ingest
protocol, scoped to internal high-volume sources only.
Public SaaS webhooks stay on HTTP POST, browsers stay on
WebSocket, IoT stays on MQTT.
- Rationale: typed schemas, HTTP/2 + protobuf, native backpressure,
no reconnect-loop code, generated Go/Java/Python/Node clients.
Cost: one more protocol to operate + a
buf generate build step.
- Gated on M11, after M10 (load test). M10 now caps at
5k/s on docker-compose (not 50k/s) — 50k/s is the design
ceiling that requires K8s + multi-broker (M12 in v2).
StreamAlerts(Alert) → Ack carries the same dedupe_count
contract as HTTP/WS/MQTT.
- Proto lives at
proto/broadannounce/v1/ingest.proto; server
stub in internal/grpcserver/, reusable client in
internal/grpcclient/. Auth = API key in metadata + optional
mTLS. Per-stream rate limit + 256 in-flight cap = natural
backpressure.