Files
headquarter/openspec/changes/container-monitoring-notifications/tasks.md
T
alex 4a7f24348c feat: container monitoring backend core (PR-1)
- Add instance_events and health_checks tables with Alembic migration
- InstanceEventBus: typed pub/sub singleton with wildcard support
- HealthMonitor: async background loop polling containers every 15s
- SSE endpoint GET /events/stream with auth and connection limits
- Lifecycle hooks in tool_instances.py (create/start/stop/restart/delete)
- Structured JSON logging with correlation IDs
- 15 new unit tests (EventBus, HealthMonitor, MonitoringModels)

Quality gates: pytest 15 new passed, ruff clean
2026-05-29 10:25:00 +02:00

659 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDD Tasks: Container Monitoring & Notification System
## Review Workload Forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | ~2,100 total (PR-1 ~1,000; PR-2 ~700; PR-3 ~400) |
| 400-line budget risk | High |
| Chained PRs recommended | Yes |
| Suggested split | PR 1 (Backend Core) → PR 2 (Frontend UI) → PR 3 (Integration + Polish) |
| Delivery strategy | auto-chain |
| Chain strategy | stacked-to-main |
```
Decision needed before apply: No
Chained PRs recommended: Yes
Chain strategy: stacked-to-main
400-line budget risk: High
```
> **Note:** PR-1 and PR-2 exceed the 400-line review budget. Within each PR, tasks are grouped into autonomous work units that can be reviewed independently. If review fanout is available, consider splitting PR-1 into (a) DB + EventBus + SSE and (b) HealthMonitor + Lifecycle Hooks + Logging. PR-2 can be split into (a) useEvents + ToastProvider and (b) Badge updates + Polling removal.
---
## PR-1: Backend Core
**Goal:** Establish the backend infrastructure for real-time container monitoring: database schema, in-memory event bus, background health monitor, SSE endpoint, structured logging, and lifecycle instrumentation.
**Estimated Lines:** ~1,000
**Review Risk:** High
---
### MON-PR1-001: Create Alembic migration for monitoring tables
**Description:**
Write a single Alembic revision that creates `instance_events` and `health_checks` with all columns, constraints, and indexes defined in the spec.
**Files to modify:**
- `apps/api/alembic/versions/2026_05_28_add_monitoring_tables.py` *(new)*
**Acceptance criteria:**
- [ ] Migration creates `instance_events` table with columns: `id`, `instance_id`, `event_type`, `status`, `message`, `created_by`, `metadata`, `created_at`.
- [ ] Migration creates `health_checks` table with columns: `id`, `instance_id`, `container_status`, `container_healthy`, `tunnel_healthy`, `exit_code`, `probe_status`, `probe_output`, `checked_at`.
- [ ] All 5 indexes from the spec are created.
- [ ] `upgrade()` and `downgrade()` are both implemented and pass `alembic upgrade head` / `alembic downgrade -1`.
- [ ] Migration depends on current `head` revision.
**Estimated effort:** Small (23 hours)
**Dependencies:** None
---
### MON-PR1-002: Create SQLAlchemy models for InstanceEvent and HealthCheck
**Description:**
Add SQLAlchemy models matching the migration schema, following the existing `UUIDPrimaryKeyMixin` + `Base` pattern (no `TimestampMixin` on `InstanceEvent`; `created_at` uses `server_default`).
**Files to modify:**
- `apps/api/src/models/instance_event.py` *(new)*
- `apps/api/src/models/health_check.py` *(new)*
- `apps/api/src/models/__init__.py`
**Acceptance criteria:**
- [ ] `InstanceEvent` model matches spec schema with correct FKs (`ON DELETE CASCADE` / `SET NULL`).
- [ ] `HealthCheck` model matches spec schema with correct FK (`ON DELETE CASCADE`).
- [ ] Both models exported in `models/__init__.py`.
- [ ] `alembic revision --autogenerate` produces no drift against the hand-written migration.
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR1-001
---
### MON-PR1-003: Add correlation ID context variable and middleware
**Description:**
Implement an async context variable `CORRELATION_ID` and a FastAPI middleware that reads `X-Request-ID` or generates a new UUID on every request. This must be available before structured logging and event publishing.
**Files to modify:**
- `apps/api/src/services/correlation.py` *(new)*
- `apps/api/src/main.py`
**Acceptance criteria:**
- [ ] `CORRELATION_ID: contextvars.ContextVar[str]` exists with `get_correlation_id()` helper.
- [ ] `CorrelationIdMiddleware` sets the context var from `X-Request-ID` header or `uuid.uuid4()`.
- [ ] Middleware is registered in `main.py` before all routes.
- [ ] Calling `get_correlation_id()` inside a request handler returns the same ID for the full request lifecycle.
**Estimated effort:** Small (23 hours)
**Dependencies:** None
---
### MON-PR1-004: Refactor API logging to structured JSON format
**Description:**
Replace the plain-text formatter in `logging_config.py` with a JSON formatter that includes `timestamp`, `level`, `logger`, `message`, `correlation_id`, `instance_id`, and `event_type`. Add a `logging.Filter` that reads from `CORRELATION_ID`.
**Files to modify:**
- `apps/api/src/logging_config.py`
**Acceptance criteria:**
- [ ] Log output is valid JSON lines with required fields.
- [ ] `correlation_id` is populated automatically from the context var.
- [ ] `instance_id` and `event_type` are included when passed as `extra=` to the logger.
- [ ] Request/response middleware logs remain functional but now emit JSON.
- [ ] Unhandled exception middleware logs tracebacks as JSON.
- [ ] `uvicorn.access` stays at `WARNING` to reduce noise.
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR1-003
---
### MON-PR1-005: Implement InstanceEventBus singleton with typed pub/sub
**Description:**
Create the in-memory event bus as a module-level singleton following the `TerminalManager` pattern. Support typed subscription, unsubscribe, and exception-isolated delivery.
**Files to modify:**
- `apps/api/src/services/event_bus.py` *(new)*
**Acceptance criteria:**
- [ ] `InstanceEventBus` is a singleton (`__new__` + lock).
- [ ] `subscribe(event_type, callback)` returns an unsubscribe callable.
- [ ] `publish(event_type, payload)` delivers to all subscribers in the same event loop iteration.
- [ ] If a subscriber raises, the exception is logged with `correlation_id` and delivery continues to remaining subscribers.
- [ ] `InstanceEventPayload` TypedDict matches the spec schema exactly.
- [ ] `unsubscribe_all(event_type)` exists for test teardown.
**Estimated effort:** Small (34 hours)
**Dependencies:** MON-PR1-003
---
### MON-PR1-006: Implement HealthMonitor background polling task
**Description:**
Build the background monitor that polls Docker + tunnel health every 15 seconds, compares against in-memory state, and only writes to DB / publishes events on actual state changes.
**Files to modify:**
- `apps/api/src/services/health_monitor.py` *(new)*
- `apps/api/src/services/docker.py` *(read-only; reuse `get_container_status`)*
**Acceptance criteria:**
- [ ] `HealthMonitor` accepts `event_bus: InstanceEventBus` and is a singleton-style service.
- [ ] `start()` is idempotent; creates an asyncio task for `_poll_loop()`.
- [ ] `stop()` cancels the task and clears `_last_known_state`.
- [ ] Poll interval is `15.0` seconds (configurable for tests).
- [ ] Queries all instances where `status NOT IN ("pending", "stopped", "error")`.
- [ ] Per instance: calls `get_container_status()`, then HTTP HEAD/GET to `public_url` if present.
- [ ] On state change: updates `tool_instances.status`, inserts `health_checks` row, publishes `instance.health_changed` or `instance.error`.
- [ ] On no change: skips all DB writes and event publication.
- [ ] Per-instance exceptions are caught, logged as structured JSON, and the loop continues.
- [ ] `_last_known_state` is a `dict[UUID, HealthSnapshot]` dataclass.
**Estimated effort:** Medium (57 hours)
**Dependencies:** MON-PR1-002, MON-PR1-005
---
### MON-PR1-007: Implement SSE streaming endpoint with auth and connection limits
**Description:**
Create `/events/stream` using FastAPI `StreamingResponse` with `text/event-stream`. Enforce authentication and a max of 5 concurrent connections per user.
**Files to modify:**
- `apps/api/src/api/events.py` *(new)*
- `apps/api/src/api/__init__.py`
**Acceptance criteria:**
- [ ] `GET /events/stream` returns `401` before stream start if auth is missing/invalid.
- [ ] Returns `429` if user already has 5 open SSE connections.
- [ ] Sends SSE `event:` and `data:` lines formatted per spec.
- [ ] Sends `:ping` comment every 30 seconds.
- [ ] Per-connection `asyncio.Queue(maxsize=100)` drops oldest events if client is slow.
- [ ] On disconnect (`asyncio.CancelledError` or client close), unsubscribes from `InstanceEventBus` and releases the connection slot.
- [ ] Router is exported from `api/__init__.py`.
**Estimated effort:** Medium (46 hours)
**Dependencies:** MON-PR1-005
---
### MON-PR1-008: Instrument lifecycle hooks in tool_instances.py
**Description:**
Add event publishing and audit-row writes at all lifecycle transition points in `tool_instances.py`. Create a thin `lifecycle_hooks.py` service to keep `tool_instances.py` readable.
**Files to modify:**
- `apps/api/src/services/lifecycle_hooks.py` *(new)*
- `apps/api/src/api/tool_instances.py`
**Acceptance criteria:**
- [ ] After DB commit on `POST /instances``instance.created` event + `instance_events` row.
- [ ] After DB commit on start begins → `instance.started` event + row.
- [ ] After probe success → `instance.health_changed` (`running`) event + row.
- [ ] After container exits during start → `instance.error` event + row.
- [ ] After DB commit on stop → `instance.stopped` event + row.
- [ ] After DB commit on restart → `instance.restarted` event + row.
- [ ] After DB commit on delete → `instance.deleted` event + row.
- [ ] `created_by` is set to `current_user.id` for user actions; `NULL` for system-detected transitions.
- [ ] `correlation_id` from the request context is propagated into the event payload.
**Estimated effort:** Medium (46 hours)
**Dependencies:** MON-PR1-002, MON-PR1-005, MON-PR1-003
---
### MON-PR1-009: Wire up HealthMonitor, EventBus, and events router in application startup
**Description:**
Register the new events router and start/stop the `HealthMonitor` within FastAPI lifespan events.
**Files to modify:**
- `apps/api/src/main.py`
**Acceptance criteria:**
- [ ] `events_router` is included in the main FastAPI app with appropriate prefix.
- [ ] `HealthMonitor` is instantiated with the global `InstanceEventBus` and started during app startup.
- [ ] `HealthMonitor.stop()` is called during app shutdown.
- [ ] No import cycles introduced.
- [ ] App boots and passes a smoke test (`GET /health` still works).
**Estimated effort:** Small (12 hours)
**Dependencies:** MON-PR1-006, MON-PR1-007
---
### MON-PR1-010: Backend unit tests — EventBus
**Description:**
Write pytest unit tests for `InstanceEventBus` covering pub/sub, exception isolation, and unsubscribe.
**Files to modify:**
- `tests/unit/test_event_bus.py` *(new)*
**Acceptance criteria:**
- [ ] `test_publish_delivers_to_all_subscribers`: 3 callbacks registered, all receive payload.
- [ ] `test_subscriber_exception_isolation`: callback A raises, B still receives event.
- [ ] `test_unsubscribe_removes_callback`: after unsubscribe, callback is not called.
- [ ] `test_publish_to_empty_subscriber_list`: no error raised.
- [ ] Tests use a fresh `InstanceEventBus` instance (reset singleton state in fixture).
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR1-005
---
### MON-PR1-011: Backend unit tests — HealthMonitor
**Description:**
Write pytest unit tests for `HealthMonitor` state-transition logic using mocked Docker and HTTP responses.
**Files to modify:**
- `tests/unit/test_health_monitor.py` *(new)*
**Acceptance criteria:**
- [ ] `test_detects_container_crash`: mock `get_container_status``exited`, `exit_code=137`; asserts DB status becomes `error`, event published, `health_checks` row inserted.
- [ ] `test_detects_tunnel_failure`: mock tunnel HEAD → 502; asserts status → `unhealthy`, `tunnel_healthy=false` in DB.
- [ ] `test_detects_recovery`: mock running + tunnel 200 after unhealthy; asserts status → `running`, `health_checks` row inserted.
- [ ] `test_skips_writes_when_no_state_change`: two identical polls; asserts only one `health_checks` row.
- [ ] `test_docker_exception_resilience`: mock raises `CalledProcessError`; asserts no exception propagates, loop continues.
- [ ] Uses `db_session` and `event_bus` fixtures; mocks poll interval to `0.1s`.
**Estimated effort:** Medium (45 hours)
**Dependencies:** MON-PR1-006, MON-PR1-010
---
### MON-PR1-012: Backend integration tests — SSE endpoint
**Description:**
Write integration tests for the SSE endpoint covering auth, streaming, connection limits, and disconnect cleanup.
**Files to modify:**
- `tests/integration/test_sse_endpoint.py` *(new)*
**Acceptance criteria:**
- [ ] `test_sse_requires_auth`: `GET /events/stream` without cookie → `401`.
- [ ] `test_sse_streams_event`: authenticated client connects; backend publishes event; client receives valid SSE line within 1s.
- [ ] `test_sse_enforces_connection_limit`: open 6 connections; 6th returns `429`.
- [ ] `test_sse_disconnect_unsubscribes`: connect, close client, publish event; assert subscriber count is 0 and no error logged.
- [ ] Uses `authenticated_client` fixture.
**Estimated effort:** Medium (45 hours)
**Dependencies:** MON-PR1-007
---
## PR-2: Frontend UI
**Goal:** Build the frontend event consumption layer: SSE client hook, toast notification system, and real-time status badge updates.
**Estimated Lines:** ~700
**Review Risk:** High
---
### MON-PR2-001: Install sonner and create event TypeScript types
**Description:**
Add `sonner` to the frontend dependencies and create the `InstanceEventPayload` TypeScript interface that mirrors the backend spec.
**Files to modify:**
- `apps/web/package.json`
- `apps/web/src/types/events.ts` *(new)*
**Acceptance criteria:**
- [ ] `sonner` is added to `dependencies` (not `devDependencies`).
- [ ] `InstanceEventPayload` interface includes all required fields: `event`, `instance_id`, `status`, `message`, `metadata`, `timestamp`, `correlation_id`.
- [ ] `metadata` sub-type includes optional fields: `exit_code`, `tunnel_url`, `probe_output`, `error_type`, `previous_status`.
- [ ] `pnpm install` (or equivalent) succeeds and lockfile updated.
**Estimated effort:** Small (12 hours)
**Dependencies:** PR-1 merged (backend SSE endpoint must exist)
---
### MON-PR2-002: Implement useEvents() SSE hook with reconnect backoff
**Description:**
Create a React hook that opens an `EventSource` to `/events/stream`, handles reconnections with exponential backoff + jitter, and exposes parsed events.
**Files to modify:**
- `apps/web/src/hooks/use-events.ts` *(new)*
**Acceptance criteria:**
- [ ] Hook connects to `${API_BASE_URL}/events/stream` with credentials included.
- [ ] Parsed events are returned in a reactive list/array.
- [ ] `connected` boolean reflects `EventSource` ready state.
- [ ] On error/disconnect: waits `delay = min(30000, 1000 * 2^attempts) * (0.8 + Math.random() * 0.4)` before reconnect.
- [ ] On `401` response: stops reconnecting and redirects to login.
- [ ] On `429` response: adds extra 5s penalty before next retry.
- [ ] Hook cleans up `EventSource` on unmount.
- [ ] `reconnectCount` is exposed for debugging.
**Estimated effort:** Medium (45 hours)
**Dependencies:** MON-PR2-001
---
### MON-PR2-003: Implement toast rules and deduplication logic
**Description:**
Create a pure module that maps SSE event types to toast configurations and deduplicates rapid duplicate events.
**Files to modify:**
- `apps/web/src/components/toast-rules.ts` *(new)*
**Acceptance criteria:**
- [ ] `instance.started``info` toast, message `"Container starting..."`, duration 3s.
- [ ] `instance.health_changed` to `running``success` toast, message `"Container running"`, duration 3s.
- [ ] `instance.health_changed` to `unhealthy``warning` toast, message `"Container unhealthy"`, duration 5s.
- [ ] `instance.error``error` toast, uses event `message` + `metadata.exit_code` if present, duration 10s (or persistent if sonner supports it).
- [ ] Deduplication: same `(instance_id, event_type)` within 1s produces only one toast.
- [ ] Function is pure and testable without React rendering.
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR2-001
---
### MON-PR2-004: Implement ToastProvider component
**Description:**
Build a global toast provider that wraps `sonner`'s `<Toaster />`, consumes `useEvents()`, and renders toasts via the rules module.
**Files to modify:**
- `apps/web/src/components/toast-provider.tsx` *(new)*
- `apps/web/src/components/app-shell.tsx`
**Acceptance criteria:**
- [ ] `<ToastProvider />` mounts `<Toaster />` and calls `useEvents()`.
- [ ] Incoming events are passed through `toast-rules.ts` mapping.
- [ ] Mounted inside `AppShell` so it is active on every authenticated page.
- [ ] Deduplication state is managed internally (e.g., `Map<string, number>` of last toast timestamp).
- [ ] Does not cause re-renders of the entire app on every SSE event (uses narrow subscription or memoization).
**Estimated effort:** Small (34 hours)
**Dependencies:** MON-PR2-002, MON-PR2-003
---
### MON-PR2-005: Replace health polling with real-time SSE updates in instance list
**Description:**
Remove the 30-second health polling loop from `instance-list.tsx` and `session-card.tsx`. Consume `useEvents()` to update status badges in real time. Retain a 60-second lightweight list refresh.
**Files to modify:**
- `apps/web/src/components/instance-list.tsx`
- `apps/web/src/components/session-card.tsx`
- `apps/web/src/api/sessions.ts`
**Acceptance criteria:**
- [ ] `setInterval` health polling (every 30s) is removed from `instance-list.tsx`.
- [ ] `session-card.tsx` badge colors map to statuses: `running` → green, `starting`/`probing` → blue, `unhealthy` → amber, `error` → red, `stopped` → gray.
- [ ] Badge text and color update within 1s of receiving the matching SSE event.
- [ ] `api/sessions.ts` still exports `checkInstanceHealth` for on-demand use (do not delete the function).
- [ ] A 60s list refresh poll remains for resilience (full list re-fetch, not per-instance health).
- [ ] Multiple instances update independently (no global refresh on single-instance event).
**Estimated effort:** Medium (45 hours)
**Dependencies:** MON-PR2-002
---
### MON-PR2-006: Frontend unit tests — useEvents hook
**Description:**
Write tests for the `useEvents` hook using mocked `EventSource` to verify reconnect logic and event parsing.
**Files to modify:**
- `apps/web/src/hooks/use-events.test.ts` *(new)*
**Acceptance criteria:**
- [ ] `test_reconnects_with_backoff`: simulate `EventSource` error; assert reconnect delay follows exponential pattern up to 30s cap.
- [ ] `test_parses_sse_event`: simulate incoming `message` event with JSON payload; assert hook state contains parsed event.
- [ ] `test_stops_on_401`: simulate 401; assert `EventSource` is closed and reconnect stops.
- [ ] `test_cleans_up_on_unmount`: unmount component; assert `EventSource.close()` called.
**Estimated effort:** Small (34 hours)
**Dependencies:** MON-PR2-002
---
### MON-PR2-007: Frontend unit tests — toast rules
**Description:**
Write tests for `toast-rules.ts` covering mapping correctness and deduplication.
**Files to modify:**
- `apps/web/src/components/toast-rules.test.ts` *(new)*
**Acceptance criteria:**
- [ ] `test_maps_error_event_to_error_toast`: asserts type, message includes exit code, duration.
- [ ] `test_maps_running_health_change_to_success_toast`: asserts type, message, duration.
- [ ] `test_deduplicates_within_one_second`: two identical events at t=0 and t=0.5 → one toast call.
- [ ] `test_allows_duplicate_after_one_second`: two identical events at t=0 and t=1.1 → two toast calls.
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR2-003
---
## PR-3: Integration + Polish
**Goal:** Validate the end-to-end event flow, add cross-stack integration tests, tune performance, update documentation, and ensure zero regression.
**Estimated Lines:** ~400
**Review Risk:** Medium
---
### MON-PR3-001: Integration tests — lifecycle event flow
**Description:**
Write backend integration tests that exercise real lifecycle endpoints and assert both DB audit rows and event bus publications.
**Files to modify:**
- `tests/integration/test_lifecycle_hooks.py` *(new)*
**Acceptance criteria:**
- [ ] `test_start_publishes_started_event`: call start endpoint; assert `instance_events` row with `event_type="started"` and event bus subscriber receives `instance.started`.
- [ ] `test_stop_publishes_stopped_event`: call stop endpoint; assert `event_type="stopped"` row and subscriber receives `instance.stopped`.
- [ ] `test_restart_publishes_restarted_event`: call restart endpoint; assert `event_type="restarted"`.
- [ ] `test_delete_publishes_deleted_event`: call delete endpoint; assert `event_type="deleted"`.
- [ ] `test_created_by_set_to_user_id`: user-initiated actions have `created_by` populated.
- [ ] Uses `authenticated_client`, `db_session`, and a test subscriber on `InstanceEventBus`.
**Estimated effort:** Medium (45 hours)
**Dependencies:** PR-1 merged, PR-2 merged
---
### MON-PR3-002: End-to-end tests — container start to toast
**Description:**
Write an E2E test (Playwright or Cypress) that starts a container and verifies the toast sequence in the browser.
**Files to modify:**
- `tests/e2e/container_monitoring.spec.ts` *(new)*
**Acceptance criteria:**
- [ ] User clicks Start on an instance.
- [ ] Toast "Container starting..." appears within 3s.
- [ ] After readiness probe passes, toast "Container running" appears within 10s.
- [ ] No manual page refresh is performed between steps.
- [ ] Test is tagged `@monitoring` for selective CI runs.
**Estimated effort:** Medium (46 hours)
**Dependencies:** PR-1 merged, PR-2 merged
---
### MON-PR3-003: End-to-end tests — container crash detection
**Description:**
Write an E2E test that kills a running container externally and verifies the error toast + badge update.
**Files to modify:**
- `tests/e2e/container_monitoring.spec.ts`
**Acceptance criteria:**
- [ ] Start a container and wait for "running" state.
- [ ] Kill the container via Docker CLI (or API call) from the test setup.
- [ ] Error toast appears within 5s.
- [ ] Status badge changes from green "running" to red "error" without page refresh.
- [ ] `instance_events` table contains `event_type="error"` with `exit_code`.
**Estimated effort:** Medium (46 hours)
**Dependencies:** MON-PR3-002
---
### MON-PR3-004: Performance tuning — connection limits and queue bounds
**Description:**
Verify and harden performance constraints: SSE queue cap, heartbeat ping, and connection-per-user limit.
**Files to modify:**
- `apps/api/src/api/events.py`
- `apps/web/src/hooks/use-events.ts`
**Acceptance criteria:**
- [ ] Per-connection `asyncio.Queue` is capped at 100 events; oldest dropped on overflow.
- [ ] SSE ping (`:ping`) is sent every 30s and confirmed with a test.
- [ ] Max 5 connections per user is enforced and load-tested (even 10 rapid tab opens).
- [ ] Frontend reconnect jitter prevents thundering herd (simulate 50 clients disconnect/reconnect).
- [ ] Document any latency findings; no regressions in existing terminal WS.
**Estimated effort:** Small (23 hours)
**Dependencies:** PR-1 merged, PR-2 merged
---
### MON-PR3-005: Documentation updates
**Description:**
Add user-facing and developer-facing documentation for the monitoring system.
**Files to modify:**
- `docs/features/container-monitoring.md` *(new)*
- `docs/api/events.md` *(new)*
- `docs/architecture/event-bus.md` *(new)*
**Acceptance criteria:**
- [ ] `docs/features/container-monitoring.md` explains real-time status, toasts, and health history to users.
- [ ] `docs/api/events.md` documents `GET /events/stream` auth, headers, reconnection strategy, and event payload schema.
- [ ] `docs/architecture/event-bus.md` documents the in-memory bus design, health monitor loop, and state machine.
- [ ] README or nav index updated with links to new docs.
**Estimated effort:** Small (23 hours)
**Dependencies:** PR-1 merged, PR-2 merged
---
### MON-PR3-006: Final cleanup and regression validation
**Description:**
Run the full test suite, fix any flakes, remove debug logging, and verify no existing functionality is broken.
**Files to modify:**
- Any files with temporary debug code or TODOs introduced in PR-1/PR-2.
**Acceptance criteria:**
- [ ] `pytest` passes (unit + integration) with no failures.
- [ ] Frontend build passes with no TypeScript errors.
- [ ] Existing terminal WebSocket functionality verified manually or via existing E2E tests.
- [ ] Existing instance CRUD (create, start, stop, restart, delete) works end-to-end.
- [ ] Tunnel creation and recreation still function.
- [ ] No `console.log` or debug `logger.debug` left from development.
- [ ] All TODO comments resolved or converted to tracked issues.
- [ ] CHANGELOG or release notes entry added if project maintains one.
**Estimated effort:** Small (23 hours)
**Dependencies:** MON-PR3-001, MON-PR3-002, MON-PR3-003, MON-PR3-004
---
## Dependency Graph (PR Level)
```
PR-1: Backend Core
├─► MON-PR1-001 ──► MON-PR1-002
├─► MON-PR1-003 ──► MON-PR1-004
│ └─► MON-PR1-008
├─► MON-PR1-005 ──► MON-PR1-006 ──► MON-PR1-009
│ │
│ └─► MON-PR1-007 ──► MON-PR1-012
├─► MON-PR1-010
└─► MON-PR1-011
PR-2: Frontend UI (depends on PR-1 merged)
├─► MON-PR2-001 ──► MON-PR2-002 ──► MON-PR2-004
│ │
│ └─► MON-PR2-005
├─► MON-PR2-003 ──► MON-PR2-004
├─► MON-PR2-006
└─► MON-PR2-007
PR-3: Integration + Polish (depends on PR-1 + PR-2 merged)
├─► MON-PR3-001
├─► MON-PR3-002 ──► MON-PR3-003
├─► MON-PR3-004
├─► MON-PR3-005
└─► MON-PR3-006
```
---
## Task Summary
| PR | Task ID | Description | Effort |
|----|---------|-------------|--------|
| 1 | MON-PR1-001 | Alembic migration for monitoring tables | S |
| 1 | MON-PR1-002 | SQLAlchemy models for InstanceEvent and HealthCheck | S |
| 1 | MON-PR1-003 | Correlation ID context variable and middleware | S |
| 1 | MON-PR1-004 | Structured JSON logging refactor | S |
| 1 | MON-PR1-005 | InstanceEventBus singleton | S |
| 1 | MON-PR1-006 | HealthMonitor background polling task | M |
| 1 | MON-PR1-007 | SSE streaming endpoint | M |
| 1 | MON-PR1-008 | Lifecycle hook instrumentation | M |
| 1 | MON-PR1-009 | Wire up startup/shutdown and router registration | S |
| 1 | MON-PR1-010 | Unit tests — EventBus | S |
| 1 | MON-PR1-011 | Unit tests — HealthMonitor | M |
| 1 | MON-PR1-012 | Integration tests — SSE endpoint | M |
| 2 | MON-PR2-001 | Install sonner + TypeScript event types | S |
| 2 | MON-PR2-002 | useEvents() SSE hook | M |
| 2 | MON-PR2-003 | Toast rules and deduplication | S |
| 2 | MON-PR2-004 | ToastProvider component | S |
| 2 | MON-PR2-005 | Real-time badge updates + polling removal | M |
| 2 | MON-PR2-006 | Unit tests — useEvents hook | S |
| 2 | MON-PR2-007 | Unit tests — toast rules | S |
| 3 | MON-PR3-001 | Integration tests — lifecycle event flow | M |
| 3 | MON-PR3-002 | E2E tests — container start to toast | M |
| 3 | MON-PR3-003 | E2E tests — container crash detection | M |
| 3 | MON-PR3-004 | Performance tuning (limits, queue, jitter) | S |
| 3 | MON-PR3-005 | Documentation updates | S |
| 3 | MON-PR3-006 | Final cleanup and regression validation | S |
**Total tasks:** 25
**Total estimated effort:** ~95 hours (backend ~55h, frontend ~25h, integration ~15h)