Skip to content

feat: event monitoring setup wizard, session persistence, and investigation pipeline - #33

Merged
AhmadHammad21 merged 16 commits into
mainfrom
feat/event-monitoring-setup-wizard
May 18, 2026
Merged

AhmadHammad21 merged 16 commits into
mainfrom
feat/event-monitoring-setup-wizard

Conversation

@AhmadHammad21

@AhmadHammad21 AhmadHammad21 commented May 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

This branch ships the full event monitoring setup wizard, wires alert rows back to the investigation sessions that produced them, and unifies all three agent invocation paths (web chat, event consumer, proactive poller) onto the same streaming pipeline.


What changed

1. Event monitoring setup wizard (Settings → AWS Configuration)

  • Multi-step init wizard: organization name, AWS region/credentials, permission check, infrastructure creation
  • setup_event_infra() creates the SQS queue, nine EventBridge rules, and an aggregate Lambda CloudWatch alarm in one click
  • /api/init/reset endpoint to re-run the wizard from scratch
  • IAM setup guide page (/iam-setup) with copy-paste policy JSON scoped to the minimum required permissions
  • Org name collected at registration; admin/team user org_id null bug fixed
  • app_config table replaces init.json file; in-memory cache avoids repeated DB reads
  • Init redirect loop fixed: only redirects away from /init when both has_user and initialized are true

2. Session persistence for event-driven investigations

Every background investigation (event consumer + poller) now creates a full session in the DB — messages, tool calls, and usage events — linked back to the alert row via session_id.

New migrations:

  • 008_alerts_status.sql — status TEXT on alerts (completed / failed)
  • 009_session_source_alert_session.sql — source TEXT on sessions; session_id UUID FK on alerts
  • 010_session_user_interacted.sql — user_interacted BOOLEAN on sessions

Sidebar promotion logic: event sessions (source = 'event') are hidden from the sidebar by default. They appear only after the user sends a follow-up message in chat (user_interacted flips to TRUE via ON CONFLICT CASE logic). This keeps the sidebar clean while still allowing users to navigate back via the Monitoring page.

Schema changes across all three DB backends (postgres.py, sqlite.py, memory.py):

  • upsert_session: accepts source param; ON CONFLICT upgrades user_interacted but never downgrades source
  • list_sessions: WHERE source = 'chat' OR user_interacted = TRUE
  • add_alert: accepts status and session_id

3. Unified streaming path for all investigations

event_consumer._run_investigation and poller._run_investigation now use astream(stream_mode="messages") — the exact same streaming pattern as the chat endpoint. Previously both used ainvoke, which returned only the final state and missed all intermediate tool calls.

Result: dashboard tool call counts, session history, and cost tracking now correctly reflect event-driven and polling investigations, not just web chat.

The tc_accum / pending_calls / tool_calls_log extraction logic is identical across all three paths.

4. Monitoring page improvements

  • FAILED badge: orange, distinct from HIGH (red) / MEDIUM (amber) / LOW (gray)
  • Smart "Investigate" button: shows "View investigation" if session_id is set (opens the original chat); falls back to "Investigate" (new pre-seeded chat) otherwise
  • Test event button (admin only): sends a synthetic Lambda alarm through the full pipeline; disabled with tooltip when event infra is not configured
  • infraEnabled state check: empty-state copy changes based on whether event monitoring is set up

5. Dedup and poller fixes

  • Dedup window narrowed from 10 min to 3 min
  • Poller skips Lambda error rate investigation when the aggregate CloudWatch alarm has already been handled by the event consumer (existing is_recent_alert cross-check)
  • Poller now writes investigation results to the alerts table (previously Slack-only)
  • POLL_INTERVAL_MINUTES renamed to POLL_INTERVAL_SECONDS for precision

6. Auth / Swagger fix

  • Switched OAuth2PasswordBearer to HTTPBearer so Swagger UI's Authorize dialog accepts Bearer tokens correctly

7. Docs

Updated: docs/event_detection.md, docs/monitoring.md, docs/slack_and_polling.md, docs/databases.md, docs/schema.md, docs/ui.md
New: docs/iam_setup.md


Known open issue

Duplicate investigations when poller and event consumer both trigger for the same alarm — tracked in #32.

The event consumer stores dedup keys as aws.cloudwatch:{md5} while the poller checks alarm:{alarmName}. These namespaces never match, making the poller's cross-check dead code. A fix was proposed (use alarm:{alarmName} in the event consumer for CloudWatch alarm events) but not applied — there are trade-offs around the ALARM→OK→ALARM edge case that need more consideration. See the issue for full analysis and next step options.


Test plan

Setup wizard

  • Fresh DB: navigate to /, confirm redirect to /init
  • Complete all wizard steps; confirm SQS queue and EventBridge rules created in AWS console
  • Run permission check: all green for a correctly scoped IAM user, specific failures shown for missing permissions
  • /api/init/reset → wizard resets; re-running creates infrastructure again without error

Session persistence

  • Send a test event (POST /api/init/test-event) or trigger a real Lambda error
  • Server logs show Processing event: aws.cloudwatch / ...
  • DB: SELECT id, source, user_interacted FROM sessions ORDER BY created_at DESC LIMIT 3 — new row with source = 'event', user_interacted = false
  • DB: SELECT session_id, service, status FROM alerts ORDER BY created_at DESC LIMIT 3 — session_id non-null
  • DB: SELECT tool_name, COUNT(*) FROM tool_calls GROUP BY tool_name — all intermediate tool calls present (not just submit_investigation)
  • Monitoring page → alert shows "View investigation" button
  • Click "View investigation" → opens /chat/{session_id} with full conversation visible
  • Sidebar does not show the event session
  • Send a follow-up message in that chat → session now appears in sidebar

Dashboard counts

  • After investigation: session count +1, tool call count reflects actual tool calls used, cost non-zero

FAILED badge

  • Trigger an investigation failure (e.g., set MAX_TOOL_CALLS=1): alert shows orange FAILED badge, not red HIGH badge

Monitoring page — test event button

  • Admin user sees "Test event" button; non-admin does not
  • Button disabled when event infra not configured; tooltip explains why
  • Click with infra configured: toast "Test Lambda alarm sent", investigation appears ~30s later

Poller

  • Set POLL_INTERVAL_SECONDS=30, POLL_ERROR_THRESHOLD=0.0, trigger a Lambda error
  • Confirm poller writes an alert with session_id and correct status
  • Confirm tool calls are saved (not just submit_investigation)

Regression

  • Web chat still streams correctly; stop button works; tool call inspector shows results
  • Sidebar session list loads; rename and delete work
  • Settings page saves AWS config; dark mode toggle persists
  • /iam-setup page loads and policy JSON is copyable

- Fix redirect loop: RedirectToSession now waits for authLoading before
  routing, eliminating the /init ↔ / race condition
- Add require_admin auth guard to all protected init endpoints
- Add org_name field to wizard Step 1; creates organizations record and
  links admin user via org_id on first registration
- Subsequent users registered via /auth/register inherit the existing org
- init_store: add in-memory cache, async DB persistence via app_config
  table, legacy field migration, and helper accessors
- migrations/006_app_config.sql: new app_config table for init state
- stop_event_consumer() added; called on teardown and re-provision
- teardown_event_infra returns warnings/errors, handles managed vs
  external queues, and rolls back on partial failure
- Better HTTP status codes in init router (409 conflicts, 500 errors)
- login() called after Step 1 account creation so Steps 2-4 have auth
…in memory

- InitPage: org name input moved from Step 1 to Step 2 (AWS config step)
  so it's shown regardless of whether the user is already logged in
- /api/init/create-user no longer accepts org_name; /api/init/setup now
  creates the org when org_name is provided and none exists yet
- init_store: replace file-based persistence with DB (app_config table) +
  in-memory _CACHE; sync load_init() reads from cache with zero I/O,
  safe for boto3 thread executors; cache is seeded from DB on startup via
  refresh_init_cache_from_db() in the FastAPI lifespan
- Added reset_init_async() helper; /api/init/reset uses it directly
…users

- docs/iam_setup.md: IAM policy JSON covering read-only investigation,
  event monitoring setup, and optional SNS notifications; includes
  instructions for IAM user vs role vs named profile
- InitPage Step 3: add link to iam_setup.md so users know where to look
  before running the permission check
- init.py /setup: after creating org, backfill org_id on any existing
  users that were created before the org (typically the Step 1 admin)
- users.py /api/users POST: look up first org and pass org_id when
  creating team members, matching what auth.register already does
- db/base.py + db/postgres.py: add assign_org_to_users_without_org()
Drop the *60 multiplication so the value is used directly as seconds.
Makes dev testing easier (POLL_INTERVAL_SECONDS=20 instead of waiting
a full minute). Update all references: config, poller loop, app startup
check, Settings UI label, .env.example, and CLAUDE.md.
Three-layer approach to prevent the same incident triggering multiple
investigations and Slack notifications:

1. DB dedup: add dedup_key column to alerts + is_recent_alert() query.
   Both poller and event consumer write a stable key per trigger (e.g.
   alarm:opendevops-lambda-errors-aggregate) and check it before running
   any investigation. If an alert with that key exists in the last 10
   minutes, skip. Covers all cross-path duplicates.

2. Structural fix: skip _check_alarms() in the poller when event infra
   is active. EventBridge already handles alarm state changes via the
   SQS path — polling DescribeAlarms on top of that is redundant.

3. _check_lambda_errors() also checks is_recent_alert() before starting,
   so the third duplicate (lambda error rate path) is suppressed when the
   alarm or event consumer already handled the same function.

Migration: 007_alerts_dedup_key.sql adds the column + index.
…ready handled

The lambda_errors:{fn} key and alarm:{ALARM_NAME} key are different, so
the previous dedup check missed the overlap. Now _check_lambda_errors
also checks is_recent_alert on the aggregate alarm key — if the event
consumer or poller already investigated that alarm in the last 10 min,
skip the per-function error rate investigation entirely.
10 min was too wide — different errors on the same Lambda within that
window would be silently skipped. The actual race between event consumer
and poller paths is ~2 min, so 3 min is enough to catch duplicates while
allowing a distinct new error to re-trigger after the window closes.
Adds JWT BearerAuth security scheme to the OpenAPI schema so the
Swagger UI shows the Authorize button. persistAuthorization keeps
the token across page refreshes. All endpoints get the security
requirement by default.
OAuth2PasswordBearer showed a username/password form in Swagger that
called /auth/login with form data, but the endpoint expects JSON — so
the Authorize flow silently failed. HTTPBearer shows a plain token
input box that just sets Authorization: Bearer <token>, which works
correctly with our JWT setup.
…ents

- Persist full sessions for event-driven investigations (source='event'),
  linked to alerts via session_id — enables 'View investigation' from
  monitoring page to open the original agent chat
- Add user_interacted flag: event sessions appear in sidebar only after
  the user sends a follow-up message (migrations 009, 010)
- Add status column to alerts (migration 008): failed investigations are
  flagged and displayed with an orange FAILED badge instead of LOW confidence
- MD5-based general dedup key across all AWS event types; in-memory
  _in_progress set closes the race window between check and save
- Show original AWS error in Slack failure notifications; mark test events
  with TEST badge in Slack
- Smart Investigate/View investigation button on monitoring and alert detail
  pages: navigates to existing session when available
- Filter event sessions from sidebar (source=chat OR user_interacted=true)
- Format investigation result as readable assistant message in persisted chat
- Add status field to alert API responses and Alert type
- Update docs: monitoring.md, event_detection.md, README
…all capture

Both event consumer and poller now use astream(stream_mode="messages") — the
same streaming path as the chat endpoint — so tool calls are captured and saved
to the DB identically regardless of how an investigation is triggered. Dashboard
tool-call counts and session history now reflect event-driven investigations.
@AhmadHammad21
AhmadHammad21 merged commit 32a1118 into main May 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant