# PR-2 Apply Report: Backend Integration for Notification Center ## Status: COMPLETE All 5 tasks for PR-2 (NC-PR2-001 through NC-PR2-005) have been implemented, tested, and validated. ## What Was Implemented ### NC-PR2-001: Wire lifecycle_hooks.py to NotificationService **File:** `apps/api/src/services/lifecycle_hooks.py` - Imported `notification_service` singleton from `src.services.notification_service` - Added `_derive_title(event_type)` helper mapping lifecycle events to human-readable titles: - `instance.created` → "Container created" - `instance.started` → "Container started" - `instance.stopped` → "Container stopped" - `instance.restarted` → "Container restarted" - `instance.deleted` → "Container deleted" - `instance.error` → "Container error" - After `event_bus.publish(...)`, calls `notification_service.create_notification(...)` with: - `user_id = instance.owner_id` - `category = "instance"` - `severity = "error"` for `instance.error`, `"info"` for all others - `source_type = "tool_instances"`, `source_id = instance.id` - Wrapped in `try/except`; logs failure with `correlation_id` and continues - Event bus publish and audit row insert are unaffected by notification failure ### NC-PR2-002: Wire health_monitor.py to NotificationService **File:** `apps/api/src/services/health_monitor.py` - Imported `notification_service` singleton - After `self._event_bus.publish(event_type, payload)`, calls `notification_service.create_notification(...)` with: - `user_id = instance.owner_id` - `category = "instance"` for `new_status == "error"` - `category = "health"` for `instance.health_changed` - `severity` mapped: - `"error"` for crash - `"warning"` for unhealthy - `"info"` for recovery (running) - `title` mapped: - "Container failed" for error - "Container unhealthy" for unhealthy - "Container recovered" for running - Wrapped in `try/except`; logs failure with `correlation_id` and continues - Original event bus publish and health check insert are unaffected ### NC-PR2-003: Extend UserConfig schema for notification preferences **File:** `apps/api/src/api/user_config.py` - Added `notification_mute_categories: list[str] | None = None` to `UserConfigResponse` - Added `notification_toast_level: str | None = None` to `UserConfigResponse` - Added the same fields to `UserConfigUpdate` - Existing config keys are unaffected; new fields are optional with `None` defaults ### NC-PR2-004: Event producer integration tests (RED) **File:** `apps/api/tests/integration/test_notifications_lifecycle.py` *(new)* 6 integration tests covering: 1. `test_lifecycle_event_creates_notification` — lifecycle hook `instance.started` creates `severity="info"` notification for owner 2. `test_health_monitor_error_creates_notification` — simulated crash creates `severity="error"` notification for owner 3. `test_notification_failure_does_not_block_event_pipeline` — mocked `create_notification` raising `RuntimeError`; event still published, no exception escapes 4. `test_notification_ownership_matches_instance_owner` — notification `user_id` equals `instance.owner_id`, not the API caller 5. `test_lifecycle_error_creates_error_notification` — `instance.error` maps to `severity="error"`, title="Container error" 6. `test_health_monitor_unhealthy_creates_warning_notification` — tunnel failure creating `severity="warning"`, category="health" ### NC-PR2-005: Verify producer tests and clean up (GREEN / REFACTOR) - All 6 new integration tests pass - 13 unit tests for `NotificationService` pass (no regressions) - 10 integration tests for notifications API pass (no regressions) - 6 existing health monitor unit tests pass (no regressions) - 6 existing event integration tests pass (no regressions) - `ruff check` passes on all modified files ## TDD Cycle Evidence | Cycle | Task | Test File | RED | GREEN | Evidence | |-------|------|-----------|-----|-------|----------| | 1 | NC-PR2-004 (producer flow) | `tests/integration/test_notifications_lifecycle.py` | 4 tests written against unwired producers | Wired `lifecycle_hooks.py` and `health_monitor.py` | 4 passed | | 2 | NC-PR2-004 (severity mapping) | `tests/integration/test_notifications_lifecycle.py` | Added error + warning severity tests | Already green from implementation | 6 passed | | 3 | NC-PR2-003 (UserConfig schema) | `src/api/user_config.py` | Schema extended with new optional fields | PATCH/GET endpoints validate correctly | Verified manually | | 4 | NC-PR2-005 (REFACTOR) | All files | — | ruff clean, no regressions across 41 related tests | All pass | ## Changed Files 1. `apps/api/src/services/lifecycle_hooks.py` — Wired `NotificationService` after event bus publish 2. `apps/api/src/services/health_monitor.py` — Wired `NotificationService` after state change event publish 3. `apps/api/src/api/user_config.py` — Added `notification_mute_categories` and `notification_toast_level` to Pydantic schemas 4. `apps/api/tests/integration/test_notifications_lifecycle.py` *(new)* — 6 integration tests for event-to-notification flow ## Test Commands & Exit Codes ```bash # New integration tests for event producers (6 tests) cd apps/api && python -m pytest tests/integration/test_notifications_lifecycle.py -v # Exit: 0 — 6 passed # NotificationService unit tests (no regressions) cd apps/api && python -m pytest tests/unit/test_notification_service.py -v # Exit: 0 — 13 passed # Notifications API integration tests (no regressions) cd apps/api && python -m pytest tests/integration/test_notifications_api.py -v # Exit: 0 — 10 passed # Health monitor unit tests (no regressions) cd apps/api && python -m pytest tests/unit/test_health_monitor.py -v # Exit: 0 — 6 passed # Event integration tests (no regressions) cd apps/api && python -m pytest tests/integration/test_events.py -v # Exit: 0 — 6 passed # Combined relevant test suite cd apps/api && python -m pytest \ tests/unit/test_notification_service.py \ tests/integration/test_notifications_api.py \ tests/integration/test_notifications_lifecycle.py \ tests/unit/test_health_monitor.py \ tests/integration/test_events.py \ -v # Exit: 0 — 41 passed # Ruff linting on all modified files cd apps/api && python -m ruff check \ src/services/lifecycle_hooks.py \ src/services/health_monitor.py \ src/api/user_config.py \ tests/integration/test_notifications_lifecycle.py # Exit: 0 — All checks passed ``` ## Deviations from Design None. All mappings and behaviors match the design spec (section 1.3) and task requirements exactly. ## Surprises / Decisions 1. **Health monitor `test_health_monitor_unhealthy_creates_warning_notification` required `public_url`:** The health monitor only checks tunnel health when `instance.public_url` is truthy. Without setting it on the test fixture instance, `_derive_status` returned `"running"` instead of `"unhealthy"`, which created an `"info"` notification. Fixed by setting `test_instance.public_url` in the test before calling `_check_instance`. 2. **Patch target for failure test:** The `test_notification_failure_does_not_block_event_pipeline` patches `src.services.lifecycle_hooks.notification_service.create_notification`. This only works because `lifecycle_hooks.py` imports `notification_service` at module level, making the attribute resolvable by `unittest.mock.patch`. 3. **No schema migration needed for UserConfig:** Preferences are stored in the existing JSON `config` blob, consistent with the existing pattern (theme, editor, git identity). No Alembic migration required. ## Risks - **None:** All changes are additive. Event producers use `try/except` so notification failures cannot block the event pipeline. No existing test regressions introduced. ## PR Boundary This PR covers PR-2 only (NC-PR2-001 through NC-PR2-005). PR-3 (frontend core) and PR-4 (toast coordination) are out of scope.