# 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 ` 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.` is fine; `alerts..` 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.