# M13c — Live tail + DLQ + Audit + K8s + E2E

> Third and final sub-milestone of M13. Delivers the ops surface
> (live tail, DLQ, audit), the K8s manifests, and the Playwright
> E2E gate. After this lands, M13 v1 is done.

**Status:** planning (post-M13b)
**Target:** `M13_FRONTEND_SPEC.md` §4.3
**Estimate:** 7-9 days with one engineer

---

## 0. Recap — what M13c ships

- **Live tail** (SSE feed) with filter bar + drill-in modal.
- **DLQ surface** (replaces `cmd/admind/ui/dlq.html`).
- **Audit log viewer** (read-only).
- **K8s manifests** for `admind` and `authd` (carry from M12 W1).
- **Playwright E2E** happy path green in CI.
- `M13_VERIFICATION.md` with screenshots + run logs.
- SPEC.md `M13c` row flipped to ✅.

**What M13c is NOT:**
- Not SSO, not SLO dashboards, not Storybook, not visual regression —
  all v2.
- Not visual rule editor, not density toggle, not SLO dashboards in UI.

---

## 1. Workstreams

```
┌──────────────────────────┐    ┌──────────────────────────┐
│  W1: Live tail (SSE)     │    │  W2: DLQ surface         │
│  (filter bar, drill-in,  │───▶│  (replaces dlq.html,     │
│   pause-on-scroll)       │    │   replay + discard)      │
└──────────────┬───────────┘    └──────────────┬───────────┘
               │                               │
               ▼                               ▼
┌──────────────────────────────────────────────────────────────┐
│  W3: Audit log viewer (read-only, filterable)                │
└──────────────────────────────────────────────────────────────┘
                                               │
                                               ▼
┌──────────────────────────────────────────────────────────────┐
│  W4: K8s manifests (admind + authd) + ConfigMap + Secret    │
└──────────────────────────────────────────────────────────────┘
                                               │
                                               ▼
┌──────────────────────────────────────────────────────────────┐
│  W5: Playwright E2E + CI pipeline + M13_VERIFICATION.md     │
└──────────────────────────────────────────────────────────────┘
```

Five workstreams. W1 → W2 → W3 → W4 → W5. W4 (K8s) is
independent of W1-W3 and can start in parallel. W5 is the final
gate.

---

## 2. Workstream details

### W1: Live tail (SSE)

**Goal:** Real-time feed of inbound alerts. Filterable.
Drill-in modal.

**Scope:**
- Route: `/tail`.
- Feature folder: `web/src/features/tail/`.
- Components: `TailFeed`, `TailFilterBar`, `TailRow`, `TailDetailModal`,
  `SSEStatusBadge`.
- Transport: native `EventSource` with `?access_token=***` query
  param (workaround for `EventSource`'s lack of custom headers).
- Heartbeat: green dot when SSE healthy, red on disconnect,
  auto-reconnect with exponential backoff (1s, 2s, 4s, 8s, max 30s).
- Pause-on-scroll-up: when user scrolls up, pause auto-scroll and
  show a "go to bottom" button. Resume when scrolled to bottom or
  button clicked.
- Filter bar: company (super), severity (≥ X), category, source,
  text search on title_key.
- Drill-in modal: full alert JSON (data, dedupe history, raw NATS
  subject).
- HTTP fallback: if SSE blocked by proxy, the filter bar shows a
  "use polling" toggle that hits `/v1/tail/recent?since=<ts>` every
  5s.

**Exit criteria:**
- [ ] `loadgen` sends 1 alert → appears in live tail within 2s.
- [ ] Burst of 100 alerts → all 100 visible in order.
- [ ] Filter by company hides other companies' alerts.
- [ ] Filter by severity hides lower severities.
- [ ] Pause on scroll up, "go to bottom" button works.
- [ ] SSE reconnect on network blip (close laptop, reopen, still
      streaming).
- [ ] HTTP fallback works when SSE blocked (manual test with
      curl).
- [ ] Tenant-admin sees only their company's alerts.
- [ ] Bundle: live-tail feature code-split (< 40 KB gzipped).

**Estimated:** 2-3 days.

---

### W2: DLQ surface (replaces `dlq.html`)

**Goal:** Replace `cmd/admind/ui/dlq.html` with the new SPA. Same
list/replay/discard flow, plus filters.

**Scope:**
- Routes: `/dlq`, `/dlq/:id`.
- Feature folder: `web/src/features/dlq/`.
- Components: `DLQList`, `DLQDetail`, `DLQReplayDialog`,
  `DLQDiscardDialog` (typed confirmation).
- List: filter by company (super), channel, status, time range.
- Detail: full alert payload + delivery history timeline (each
  attempt's error, response, latency).
- Replay: confirmation modal showing "this will redeliver to
  N recipients", audit row written.
- Discard: typed confirmation (`type "discard" to confirm`), audit
  row written.
- Optimistic updates for replay/discard (instant UI, rollback on
  error).
- **Delete `cmd/admind/ui/dlq.html`** and remove the old `GET /dlq`
  handler from `cmd/admind/main.go` (SPA fallback now serves the
  new DLQ page).

**Exit criteria:**
- [ ] DLQ list: all M8 rows visible, paginated, filterable.
- [ ] Replay: alert re-delivered (verified via deliverd metrics:
      `ba_deliverd_attempts_total{result=ok}` increments).
- [ ] Discard: row hidden from default list, `status=discarded`.
- [ ] Tenant-admin sees only their company's DLQ rows.
- [ ] Old `dlq.html` file deleted from `cmd/admind/ui/`.
- [ ] Old `GET /dlq` handler in `admind` removed.
- [ ] Bundle: dlq feature code-split (< 30 KB gzipped).

**Estimated:** 2-3 days.

---

### W3: Audit log viewer

**Goal:** Read-only table of `audit_log` rows, filterable.

**Scope:**
- Route: `/audit`.
- Feature folder: `web/src/features/audit/`.
- Components: `AuditList`, `AuditDetailModal` (expanded JSON
  metadata).
- List: filter by actor (user picker), action, entity_type, time
  range, free text on metadata.
- Read-only. No actions from this page.
- New backend endpoint: `GET /v1/audit` in `cmd/admind/main.go`.
  Reads from the `audit_log` table that M9 already created.
- New SQL: `SELECT ... FROM audit_log WHERE company_id = $1 ORDER BY
  ts DESC LIMIT $2 OFFSET $3` (super-admin passes company_id query
  param; tenant-admin forced to own).

**Exit criteria:**
- [ ] Audit list: rows from `ingestd`, `routerd`, `deliverd`,
      `admind` actions all visible.
- [ ] Filter by actor narrows the list.
- [ ] Filter by action narrows the list.
- [ ] Time range filter works.
- [ ] AuditDetailModal shows full metadata JSON pretty-printed.
- [ ] Tenant-admin sees only their company's audit rows.
- [ ] Bundle: audit feature code-split (< 20 KB gzipped).

**Estimated:** 1-2 days.

---

### W4: K8s manifests (admind + authd)

**Goal:** K8s manifests that bring up `admind` and `authd` alongside
the rest of the M12 stack. Carry-over from M12 W1 with M13-specific
additions (authd Service, JWT secret Secret).

**Scope:**
- `deploy/k8s/base/admind/`:
  - `deployment.yaml` — 2 replicas, resource limits, env from
    ConfigMap + Secret.
  - `service.yaml` — ClusterIP, ports 8803 (HTTP) + 9090 (gRPC,
    unused by frontend but kept for parity).
  - `configmap.yaml` — BA_HTTP_ADDR, BA_PG_*, BA_NATS_*.
  - `pdb.yaml` — minAvailable: 1.
- `deploy/k8s/base/authd/`:
  - `deployment.yaml` — 2 replicas, env from Secret.
  - `service.yaml` — ClusterIP, port 8804.
  - `configmap.yaml` — BA_AUTHD_HTTP_ADDR, BA_AUTH_SMTP_*.
  - `secret.yaml.example` — BA_AUTH_JWT_SECRET (real one gitignored).
  - `pdb.yaml` — minAvailable: 1.
- `deploy/k8s/base/kustomization.yaml` updated to include both.
- `deploy/k8s/README.md` — quickstart, secret-rotation note.
- Carry from M12 W1: namespace, postgres, nats, redis, clickhouse,
  emqx, ingestd, routerd, deliverd-*, archiverd, prometheus, grafana.

**Exit criteria:**
- [ ] `kubectl apply -k deploy/k8s/base/` brings up `admind` and
      `authd` alongside the rest.
- [ ] `kubectl get pods -n broad-announce` shows both Running +
      Ready.
- [ ] Kustomize output passes `kubeconform` (or
      `kubectl apply --dry-run=server`).
- [ ] Single `admind` binary size < 60 MB.
- [ ] `deploy/k8s/README.md` documents the quickstart and the
      JWT-secret rotation procedure.

**Estimated:** 1 day (most of the templates already exist from
M12 W1).

---

### W5: Playwright E2E + CI pipeline + final verification

**Goal:** Single Playwright happy-path test that runs in CI.
Final `M13_VERIFICATION.md` with screenshots.

**Scope:**
- `web/tests/e2e/happy-path.spec.ts`:
  1. Login as super-admin.
  2. Create a company.
  3. Create a source (capture one-time secrets from response).
  4. Send 1 alert via curl to ingestd (Playwright runs the curl in
     a subprocess).
  5. See the alert in Live Tail within 2s.
  6. Open DLQ, replay a row, verify deliverd metrics incremented.
  7. Open Audit, see all 6+ audit rows.
  8. Logout.
  9. Login as tenant-admin (created during the test).
  10. Cross-tenant 403 attempt: try to GET another company's
      sources → expect 403.
- `web/playwright.config.ts` — chromium only, base URL from env.
- `scripts/m13_e2e.sh` — wraps the Playwright run, brings up
  docker-compose, runs, teardown (no `-v`, learned from M11).
- `.github/workflows/m13-e2e.yml` (or GitLab CI equivalent) — runs
  the smoke on every PR that touches `cmd/**` or `web/**`.
- `M13_VERIFICATION.md`:
  - Screenshots of all 6 modules.
  - Playwright run log.
  - Tenant-isolation 403 test.
  - Binary size.
  - Bundle breakdown.
  - CI link.

**Exit criteria:**
- [ ] Playwright happy-path test green on local docker-compose.
- [ ] Playwright happy-path test green in CI for 3 consecutive
      runs.
- [ ] CI workflow runs on every PR; green required to merge.
- [ ] `M13_VERIFICATION.md` published.
- [ ] SPEC.md `M13a`, `M13b`, `M13c` rows all flipped to
      **✅ shipped YYYY-MM-DD**.

**Estimated:** 1-2 days.

---

## 3. Sequencing

```
W1 (2-3d) ──┐
            ├──▶ W2 (2-3d) ──▶ W3 (1-2d) ──▶ W5 (1-2d)
            │
W4 (1d) ────┴── (parallel with W1-W3, joins W5 at the end)
```

- **W1 alone:** 2-3 days.
- **W2 after W1:** 2-3 days.
- **W3 after W2:** 1-2 days.
- **W4 in parallel:** 1 day.
- **W5 last:** 1-2 days.
- **Total: ~7-11 days, target 7-9 with overlap.**

---

## 4. Risks specific to M13c

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| SSE through corporate proxy | Medium | Medium | HTTP fallback (`/v1/tail/recent`) is in the API; UI has a "use polling" toggle |
| Playwright flakes in CI | Medium | Medium | Retry once on failure; isolate test data per run (unique company slug with timestamp) |
| `dlq.html` removal breaks a customer who's bookmarked it | Low | Low | Note in release notes; old URL → new SPA `/dlq` route |
| Audit log volume overwhelms the list | Medium | Low | Server-side pagination (25/50/100); filters required for >1k rows |
| K8s manifest drift from M12 W1 | Medium | Low | Reuse M12 templates; W4 PR diff'd against M12 W1 to catch unintended changes |
| Single binary size > 60 MB | Low | Low | Per-feature code splitting; gzip embed; CI size gate |

---

## 5. Definition of done — M13c

- [ ] All 5 workstreams have their per-workstream exit criteria
      checked.
- [ ] `cmd/admind/ui/dlq.html` removed.
- [ ] Old `GET /dlq` handler in `admind` removed.
- [ ] `admind` binary size < 60 MB.
- [ ] K8s manifests apply cleanly, both `admind` and `authd`
      Running.
- [ ] Playwright happy-path green in CI for 3 consecutive runs.
- [ ] Tenant-isolation 403 test green in CI.
- [ ] `M13_VERIFICATION.md` published.
- [ ] SPEC.md `M13a`, `M13b`, `M13c` rows all flipped to
      **✅ shipped YYYY-MM-DD**.

---

## 6. Definition of done — whole M13 (final check)

- [ ] All 3 sub-milestones (M13a, M13b, M13c) DoD checked.
- [ ] Demo to a real customer (or internal stakeholder) and signed
      off.
- [ ] SPEC.md `M13` row (top-level) updated to **✅ shipped
      YYYY-MM-DD** with a one-line summary linking to M13a/b/c
      VERIFICATION docs.

---

**Next step:** M13c W1 (Live tail). W4 (K8s) can start in parallel
from a separate branch.
