This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-WEBHOOK-EVENTS-PANEL — recent webhook events lens (stub)#109
Merged
rafe-walker merged 1 commit intoMay 22, 2026
Merged
Conversation
Operator-facing observability for public-port traffic on /api/webhooks/* once the daemon deploys to Fly. Joshua needs to see "what's hitting the public port" — verified events, dead-lettered HMAC failures, rate-limited bursts — without `flyctl logs`. Stub-then-real per the proven CC#2 pattern (HB-PANEL #103, KR-MCP-3 #106). CC#3 will wire per-event chain-event recording OR a substrate webhook_events table in a follow-on — blocked on the substrate-team coord ask for the dead-letter ledger shape. Until then the stub:true flag keeps the FE banner visible. Branch base: feature/phase2-upgrades per bucket §0. SECURITY: source_ip values are OCTET-MASKED in the response (e.g. "54.203.x.x" never "54.203.99.142") — operator gets the geolocation hint without full PII exposure on the panel. Three-layer contract from KR-MCP-3 (#106 b34b268) applied here too: 1. Backend payload — bucket §3 stub uses masked literals ("54.203.x.x", "203.0.113.x", "198.51.100.x") that are RFC5737/example-IP-shaped to be unambiguously not-real 2. TS interface — source_ip: string (no value-level type info, but the wire contract is enforced by the backend test) 3. Backend tests — two regex guards: * each event's source_ip matches the octet-mask shape (must contain literal "x" octet) * walk-the-whole-payload guard rejects any 4-octet IPv4 appearing anywhere (catches future drift that adds a "real_source_ip" diagnostic field, embeds a full IP in details, etc.) CC#3 will enforce the same masking when real data flips in. §2 K-DG verifications: * kora_cli/listeners/webhooks.py + webhook_dead_letter.py confirmed (CC#3 KR-D-DAEMON ST3 PR #104) * No collision with existing /api/webhooks/* listener routes — the new GET /api/webhooks/events/recent is a sibling read endpoint; POST /slack/events + POST /email/inbound are untouched * Dashboard layout currently lg:grid-cols-3 (2-row symmetry) — adding a 7th card pushes row 2 to 7 cards; keep cols-3 for visual stability (3 rows of 3 with the last row half-full looks cleaner than cols-7 single-row narrowness) Backend (kora_cli/web_server.py): * GET /api/webhooks/events/recent — stub returns 4 representative events per bucket §3 verbatim: - 2 verified slack events (message, url_verification) - 1 dead-letter email (hmac_invalid, signature_mismatch) - 1 rate-limited slack (event_type:null per "request never reached the handler" semantics) stub:true + total_recent_24h:4 + generated_at. Frontend: * pages/WebhookEventsPanel.tsx — - Stats strip: total_recent_24h + per-status counts + italic "Source IPs octet-masked for PII" reminder - Filter pills: All / Verified / Dead Letter / Rate Limited / Handler Error (FE-only scoping; operator narrows fetched data without re-hitting backend) - Timeline rows newest-first: status icon + status badge + short endpoint (/slack/events vs /api/webhooks/slack/events) + event_type + Globe-icon masked source_ip + Clock-icon relative timestamp - Expandable detail: id + full endpoint + absolute received_at + source_ip with italic "(octet-masked for PII)" note + pretty-printed details JSON - STUB banner with CC#3 follow-on note (mentions substrate-team coord ask blocker) - Empty state guides operator: "No webhook events yet. Public webhook plane is on port 9118; verify daemon is running." * lib/api.ts — WebhookEventStatus type alias + WebhookEvent + WebhookEventsResponse interfaces + getRecentWebhookEvents client * App.tsx — /webhook-events route + nav entry (Inbox icon) between /heartbeat and /mcp-clients (operator scans public-port activity near service-health cluster) * DashboardPage.tsx — new Webhook Events card on row 2 (7th card; grid stays lg:grid-cols-3 for visual symmetry). Card body: total_24h headline + per-status pills (verified/dead-letter/ rate-limited/handler-error when non-zero). Headline tone goes destructive when dead_letter > 5 in 24h per bucket §3(c) operator-attention contract — card visually screams from dashboard glance. ALL_SOURCES extended → footer stubbed count adds 1. Tests: tests/kora_cli/test_web_server_webhook_events.py — 10 tests covering all 6 §4 scenarios plus security/contract guards: * 4-event-pin (bucket §3 canonical stub list) * Status diversity guard (3 statuses must be present so FE color rendering is exercised) * Per-entry shape + null-event_type-for-rate_limited contract * SECURITY: source_ip octet-mask regex (must contain "x" octet) * SECURITY: walk-all guard rejects any full IPv4 anywhere in payload * Dead-letter event carries reason field * Endpoints reference real CC#3 ST3 routes only * Cron-regression sanity 178/178 admin-panel suite green (was 168 + 10 new). tsc -b + vite build clean. PII redaction in `details` body is OUT OF SCOPE here (per bucket §5 — flagged for follow-on when CC#3 wires real data and the substrate-team coord ask lands). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Operator-facing observability for public-port traffic on `/api/webhooks/*` once the daemon deploys to Fly. When Joshua needs to see "what's hitting the public port" — verified events, dead-lettered HMAC failures, rate-limited bursts — without `flyctl logs`.
Stub-then-real per the proven CC#2 pattern. CC#3 will wire per-event chain-event recording (or a substrate `webhook_events` table) in a follow-on, blocked on the substrate-team coord ask for the dead-letter ledger shape. Until then `stub:true` keeps the FE banner visible.
Branch base: `feature/phase2-upgrades` per bucket §0. Bucket spec: `kora_docs/17_cc_bucket_prompts/KR-WEBHOOK-EVENTS-PANEL_stub.md`.
SECURITY contract — 3-layer pattern from KR-MCP-3 #106
`source_ip` values are OCTET-MASKED on the wire (e.g. `"54.203.x.x"` never `"54.203.99.142"`) — operator gets geolocation hint without full PII exposure.
CC#3 will enforce the same masking when real data flips in.
Backend
`GET /api/webhooks/events/recent` — stub returns 4 representative events per bucket §3 verbatim:
`stub: true` + `total_recent_24h: 4`.
Frontend
`pages/WebhookEventsPanel.tsx`:
`lib/api.ts` — `WebhookEventStatus` type + `WebhookEvent` + `WebhookEventsResponse` + `getRecentWebhookEvents` client.
`App.tsx` — `/webhook-events` route + nav entry (Inbox icon) between `/heartbeat` and `/mcp-clients` (operator scans public-port activity near service-health cluster).
`DashboardPage.tsx` — new Webhook Events card on row 2 (7th card; grid stays `lg:grid-cols-3` for visual symmetry). Card body: `total_24h` headline + per-status pills (only non-zero shown). Headline tone goes destructive when `dead_letter > 5` in 24h per bucket §3(c) operator-attention contract — card visually screams from dashboard glance.
Test plan
Flip-over plan
When CC#3 wires per-event recording (substrate-team coord ask unblocks), the endpoint body swaps to project from real chain events or a webhook_events table. Payload shape pinned, FE auto-renders real data, STUB banner auto-disappears, dashboard card destructive-tone trigger fires when dead_letter actually exceeds 5/24h.
Known follow-on (out of scope)
Per bucket §5: PII redaction in `details` body is out of scope here — stub uses representative fake data. Real implementation will need a redaction pass on the details payload (slack channel IDs, email subjects, etc.) when CC#3 wires real recording. Flagged here so it doesn't get lost.
🤖 Generated with Claude Code