Files
headquarter/openspec/changes/notification-center/apply-pr2.md
alex 6085859874 feat: notification center backend integration (PR-2)
- Wire lifecycle_hooks.py to NotificationService after event bus publish
- Wire health_monitor.py to NotificationService after state changes
- Category/severity mapping: instance.* → info, error → error, unhealthy → warning
- Extend UserConfig API with notification_mute_categories and notification_toast_level
- 6 integration tests for event-to-notification flow
- All producer calls wrapped in try/except — failures logged, pipeline continues

Quality gates: pytest 41 passed (monitoring + lifecycle), ruff clean
2026-05-29 12:40:15 +02:00

7.8 KiB

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_notificationinstance.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

# 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.