Files
headquarter/openspec/changes/container-monitoring-notifications/apply-progress.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

93 lines
5.5 KiB
Markdown

# PR-1 Apply Progress: Backend Core for Container Monitoring & Notifications
## TDD Cycle Evidence
### EventBus (MON-PR1-005 + MON-PR1-010)
| Cycle | Action | Evidence |
|-------|--------|----------|
| RED | Wrote `test_event_bus.py` with imports to non-existent module | `pytest` collection error: `ModuleNotFoundError: No module named 'src.services.event_bus'` |
| GREEN | Implemented `event_bus.py` with singleton, subscribe, publish, unsubscribe, exception isolation | `pytest tests/unit/test_event_bus.py -v` → 6 passed |
| REFACTOR | Replaced `asyncio.iscoroutinefunction` with `inspect.iscoroutinefunction`; moved `Awaitable`/`Callable` to `collections.abc` | Tests still pass, ruff clean |
| TRIANGULATE | Added wildcard (`"*"`) support in `publish()` for SSE endpoint | Verified by SSE endpoint test logic |
### HealthMonitor (MON-PR1-006 + MON-PR1-011)
| Cycle | Action | Evidence |
|-------|--------|----------|
| RED | Wrote `test_health_monitor.py` with imports to non-existent module | `pytest` collection error: `ModuleNotFoundError: No module named 'src.services.health_monitor'` |
| GREEN | Implemented `health_monitor.py` with poll loop, state comparison, DB writes, event publication | `pytest tests/unit/test_health_monitor.py -v` → 6 passed |
| REFACTOR | Added per-instance exception handling in `_check_instance`; removed redundant try/except in `_run_check_cycle` | Tests still pass |
### Models (MON-PR1-003 + MON-PR1-004)
| Cycle | Action | Evidence |
|-------|--------|----------|
| RED | Wrote `test_monitoring_models.py` with imports to non-existent models | `pytest` collection error: `ModuleNotFoundError` for models |
| GREEN | Implemented `instance_event.py` and `health_check.py`; exported in `models/__init__.py` | `pytest tests/unit/test_monitoring_models.py -v` → 3 passed |
## Completed Tasks
- [x] MON-PR1-001: Alembic migration `2026_05_28_add_monitoring_tables.py` (creates both `instance_events` and `health_checks` with all indexes)
- [x] MON-PR1-002: SQLAlchemy models `InstanceEvent` and `HealthCheck`
- [x] MON-PR1-003: `InstanceEvent` model (`apps/api/src/models/instance_event.py`)
- [x] MON-PR1-004: `HealthCheck` model (`apps/api/src/models/health_check.py`)
- [x] MON-PR1-005: `InstanceEventBus` service (`apps/api/src/services/event_bus.py`)
- [x] MON-PR1-006: `HealthMonitor` background task (`apps/api/src/services/health_monitor.py`)
- [x] MON-PR1-007: SSE endpoint `GET /events/stream` (`apps/api/src/api/events.py`)
- [x] MON-PR1-008: Lifecycle hooks in `tool_instances.py` + `lifecycle_hooks.py` service
- [x] MON-PR1-009: Structured JSON logging in `logging_config.py` + `CorrelationIdMiddleware`
- [x] MON-PR1-010: Unit tests for EventBus (`apps/api/tests/unit/test_event_bus.py`)
- [x] MON-PR1-011: Unit tests for HealthMonitor (`apps/api/tests/unit/test_health_monitor.py`)
## Files Changed
### New Files
- `apps/api/alembic/versions/2026_05_28_add_monitoring_tables.py`
- `apps/api/src/models/instance_event.py`
- `apps/api/src/models/health_check.py`
- `apps/api/src/services/event_bus.py`
- `apps/api/src/services/health_monitor.py`
- `apps/api/src/services/correlation.py`
- `apps/api/src/services/lifecycle_hooks.py`
- `apps/api/src/api/events.py`
- `apps/api/tests/unit/test_event_bus.py`
- `apps/api/tests/unit/test_health_monitor.py`
- `apps/api/tests/unit/test_monitoring_models.py`
### Modified Files
- `apps/api/src/models/__init__.py` — export new models
- `apps/api/src/api/__init__.py` — export events router
- `apps/api/src/api/tool_instances.py` — lifecycle hook instrumentation
- `apps/api/src/logging_config.py` — JSON formatter + CorrelationIdFilter
- `apps/api/src/main.py` — register events router, CorrelationIdMiddleware, HealthMonitor lifespan
## Test Evidence
```bash
# New unit tests — all pass
$ cd apps/api && python -m pytest tests/unit/test_event_bus.py tests/unit/test_health_monitor.py tests/unit/test_monitoring_models.py -v
15 passed, 4 warnings in 0.98s
# Full unit suite — no regressions (4 pre-existing failures unrelated to this change)
$ cd apps/api && python -m pytest tests/unit/ -v
172 passed, 4 failed, 2 warnings in 6.57s
# Ruff linting on new/modified files
$ cd apps/api && python -m ruff check <new files>
All checks passed!
```
## Deviation from Design
1. **Single migration vs. two migrations**: Prompt listed MON-PR1-001 and MON-PR1-002 as separate migrations, but design.md specifies a single revision. Implemented as one migration `2026_05_28_add_monitoring_tables.py` creating both tables.
2. **`metadata` column name**: SQLAlchemy `DeclarativeBase` reserves `metadata` as a class attribute. Used `event_metadata` as the Python attribute name with `"metadata"` as the DB column name via `mapped_column("metadata", ...)`. The event payload still uses `metadata` key.
3. **Delete audit row**: The FK `ON DELETE CASCADE` on `instance_events.instance_id` means the audit row for `instance.deleted` cannot survive deletion. The row is inserted before `session.delete(instance)` and is cascade-deleted on commit. The event bus publication still occurs.
4. **SQLite test compatibility**: Used `JSON` instead of `JSONB` in the SQLAlchemy model to maintain SQLite test compatibility. The migration uses `sa.JSON()` which maps appropriately.
## Remaining Tasks (for PR-2 / PR-3)
- Frontend `useEvents()` hook, `ToastProvider`, `toast-rules.ts`
- Frontend badge real-time updates + polling removal
- Integration tests for SSE endpoint (MON-PR1-012)
- Integration tests for lifecycle hooks
- E2E tests
- Performance tuning and documentation