diff --git a/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode.md b/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode.md new file mode 100644 index 00000000..2a61fb0d --- /dev/null +++ b/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode.md @@ -0,0 +1,88 @@ +# PRD: FU-P12-T1-4 — Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode + +**Created:** 2026-02-19 +**Priority:** P2 +**Branch:** `codex/feature/FU-P12-T1-4-in-flight-shared-metrics` +**Status:** PLAN + +--- + +## 1. Problem Statement + +In shared SQLite metrics mode, `SharedMetricsStore.get_summary()` returns +`"in_flight": 0` unconditionally. This causes the Web UI KPI to be misleading +and prevents operators from seeing currently outstanding requests. + +--- + +## 2. Scope + +### In Scope +- Implement process-safe computation of `in_flight` for shared metrics mode. +- Ensure in-flight count increases on `record_request` and decreases after + matching `record_response` updates complete. +- Add unit tests for shared-store in-flight behavior, including multiple + concurrent outstanding requests. + +### Out of Scope +- Dashboard UI redesign. +- Changes to in-memory `MetricsCollector` in-flight behavior. +- Manual desktop verification of Xcode prompt workflows. + +--- + +## 3. Deliverables + +1. Shared metrics in-flight support +- `src/mcpbridge_wrapper/webui/shared_metrics.py` +- Replace hardcoded `in_flight: 0` with derived process-safe value. + +2. Test coverage +- `tests/unit/webui/test_shared_metrics.py` +- Add/adjust tests proving non-zero while outstanding and zero after response. + +3. Validation artifact +- `SPECS/INPROGRESS/FU-P12-T1-4_Validation_Report.md` + +--- + +## 4. Acceptance Criteria + +- [ ] `IN FLIGHT` KPI is greater than zero while requests are in progress and returns to zero after responses. +- [ ] Works correctly with multiple concurrent clients/processes using the shared metrics database. +- [ ] No regressions in existing dashboard metrics endpoints. +- [ ] `pytest` passes. +- [ ] `ruff check src/` passes. +- [ ] `mypy src/` passes. +- [ ] `pytest --cov` reports coverage >= 90%. + +--- + +## 5. Dependencies + +- P12-T1 ✅ + +--- + +## 6. Risks and Mitigations + +- **Risk:** Counting unresolved requests purely by `latency_ms IS NULL` may + include stale rows from abnormal process termination. + - **Mitigation:** Scope in-flight query to current summary window to avoid + indefinite inflation from old stale records. + +- **Risk:** Request ID reuse across processes could mismatch response updates. + - **Mitigation:** Keep existing update strategy unchanged and validate only + aggregate outstanding-count behavior at store level. + +--- + +## 7. Validation Plan + +1. Implement shared in-flight counting in `get_summary()`. +2. Add tests for outstanding requests before and after response updates. +3. Run required quality gates and record outcomes. + +--- +**Archived:** 2026-02-19 +**Verdict:** PASS diff --git a/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Validation_Report.md b/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Validation_Report.md new file mode 100644 index 00000000..9ed4a815 --- /dev/null +++ b/SPECS/ARCHIVE/FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/FU-P12-T1-4_Validation_Report.md @@ -0,0 +1,40 @@ +# Validation Report — FU-P12-T1-4 + +**Task:** FU-P12-T1-4 — Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode +**Date:** 2026-02-19 +**Verdict:** PASS + +## Scope + +- Replaced shared-mode hardcoded `in_flight: 0` with computed outstanding count + from unresolved request rows in the active metrics window. +- Added unit coverage for pending-request counting and cross-instance + aggregation against a shared SQLite database. + +## Files Changed + +- `src/mcpbridge_wrapper/webui/shared_metrics.py` +- `tests/unit/webui/test_shared_metrics.py` + +## Required Quality Gates + +- `pytest` + - Result: **PASS** (`588 passed, 5 skipped, 2 warnings`) +- `ruff check src/` + - Result: **PASS** (`All checks passed!`) +- `mypy src/` + - Result: **PASS** (`Success: no issues found in 18 source files`) +- `pytest --cov` + - Result: **PASS** (`588 passed, 5 skipped, 2 warnings`; total coverage **92.18%**, threshold 90%) + +## Acceptance Criteria Status + +- [x] `IN FLIGHT` KPI is greater than zero while requests are in progress and returns to zero after responses. +- [x] Works correctly with multiple concurrent clients/processes using the shared metrics database. +- [x] No regressions in existing dashboard metrics endpoints. +- [x] `pytest` suite remains green. + +## Notes + +- Existing third-party deprecation warnings from `websockets` / `uvicorn` were + observed during test runs and are unrelated to this task. diff --git a/SPECS/ARCHIVE/INDEX.md b/SPECS/ARCHIVE/INDEX.md index ecde042d..e0da166d 100644 --- a/SPECS/ARCHIVE/INDEX.md +++ b/SPECS/ARCHIVE/INDEX.md @@ -1,6 +1,6 @@ # mcpbridge-wrapper Tasks Archive -**Last Updated:** 2026-02-19 (REVIEW_fu_p12_t3_2_error_code_csv_export_archived) +**Last Updated:** 2026-02-19 (REVIEW_fu_p12_t1_4_in_flight_shared_metrics_archived) ## Archived Tasks @@ -118,6 +118,7 @@ | FU-P12-T1-1 | [FU-P12-T1-1_Remove_or_document_MCPInitializeParams_in_schemas/](FU-P12-T1-1_Remove_or_document_MCPInitializeParams_in_schemas/) | 2026-02-18 | PASS | | FU-P12-T1-2 | [FU-P12-T1-2_Add_code_comment_clarifying_stdin-only_client_capture_in_on_request/](FU-P12-T1-2_Add_code_comment_clarifying_stdin-only_client_capture_in_on_request/) | 2026-02-18 | PASS | | FU-P12-T1-3 | [FU-P12-T1-3_Show_multi-client_widgets_in_Web_UI_instead_of_single_overwritten_active_client/](FU-P12-T1-3_Show_multi-client_widgets_in_Web_UI_instead_of_single_overwritten_active_client/) | 2026-02-18 | PASS | +| FU-P12-T1-4 | [FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/](FU-P12-T1-4_Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode/) | 2026-02-19 | PASS | | FU-P12-T3-2 | [FU-P12-T3-2_Add_error_code_column_to_audit_CSV_export/](FU-P12-T3-2_Add_error_code_column_to_audit_CSV_export/) | 2026-02-19 | PASS | ## Historical Artifacts @@ -200,6 +201,7 @@ | [REVIEW_FU-P12-T1-1_mcpinitializeparams.md](_Historical/REVIEW_FU-P12-T1-1_mcpinitializeparams.md) | Review report for FU-P12-T1-1 | | [REVIEW_FU-P12-T1-2_stdin_capture_comment.md](_Historical/REVIEW_FU-P12-T1-2_stdin_capture_comment.md) | Review report for FU-P12-T1-2 | | [REVIEW_FU-P12-T1-3_multi_client_widgets.md](_Historical/REVIEW_FU-P12-T1-3_multi_client_widgets.md) | Review report for FU-P12-T1-3 | +| [REVIEW_FU-P12-T1-4_in_flight_shared_metrics.md](_Historical/REVIEW_FU-P12-T1-4_in_flight_shared_metrics.md) | Review report for FU-P12-T1-4 | | [REVIEW_FU-P12-T3-2_error_code_csv_export.md](_Historical/REVIEW_FU-P12-T3-2_error_code_csv_export.md) | Review report for FU-P12-T3-2 | ## Archive Log @@ -354,5 +356,7 @@ | 2026-02-18 | FU-P12-T1-2 | Archived REVIEW_FU-P12-T1-2_stdin_capture_comment report | | 2026-02-18 | FU-P12-T1-3 | Archived Show_multi-client_widgets_in_Web_UI_instead_of_single_overwritten_active_client (PASS) | | 2026-02-18 | FU-P12-T1-3 | Archived REVIEW_FU-P12-T1-3_multi_client_widgets report | +| 2026-02-19 | FU-P12-T1-4 | Archived Make_IN_FLIGHT_KPI_reflect_real_in_flight_requests_in_shared_metrics_mode (PASS) | +| 2026-02-19 | FU-P12-T1-4 | Archived REVIEW_FU-P12-T1-4_in_flight_shared_metrics report | | 2026-02-19 | FU-P12-T3-2 | Archived Add_error_code_column_to_audit_CSV_export (PASS) | | 2026-02-19 | FU-P12-T3-2 | Archived REVIEW_FU-P12-T3-2_error_code_csv_export report | diff --git a/SPECS/ARCHIVE/_Historical/REVIEW_FU-P12-T1-4_in_flight_shared_metrics.md b/SPECS/ARCHIVE/_Historical/REVIEW_FU-P12-T1-4_in_flight_shared_metrics.md new file mode 100644 index 00000000..29a8d319 --- /dev/null +++ b/SPECS/ARCHIVE/_Historical/REVIEW_FU-P12-T1-4_in_flight_shared_metrics.md @@ -0,0 +1,32 @@ +## REVIEW REPORT — FU-P12-T1-4 in-flight shared metrics + +**Scope:** origin/main..HEAD +**Files:** 7 + +### Summary Verdict +- [x] Approve +- [ ] Approve with comments +- [ ] Request changes +- [ ] Block + +### Critical Issues +- None. + +### Secondary Issues +- None. + +### Architectural Notes +- Shared-mode `in_flight` now reflects unresolved request rows in SQLite within + the active summary window, which is process-safe and aligns with existing + request/response persistence semantics. + +### Tests +- Quality gates rerun and passing: + - `pytest` (`588 passed, 5 skipped, 2 warnings`) + - `ruff check src/` (`All checks passed!`) + - `mypy src/` (`Success: no issues found in 18 source files`) + - `pytest --cov` (`92.18%`, threshold `>=90%`) + +### Next Steps +- No actionable follow-up items identified. +- FOLLOW-UP step can be skipped for this task. diff --git a/SPECS/INPROGRESS/next.md b/SPECS/INPROGRESS/next.md index 4efd3a3e..17a61b40 100644 --- a/SPECS/INPROGRESS/next.md +++ b/SPECS/INPROGRESS/next.md @@ -2,15 +2,15 @@ ## Recently Archived +- 2026-02-19 — FU-P12-T1-4: Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode (PASS) - 2026-02-19 — FU-P12-T3-2: Add `error_code` column to audit CSV export (PASS) - 2026-02-18 — FU-P12-T1-3: Show multi-client widgets in Web UI instead of single overwritten active client (PASS) - 2026-02-18 — FU-P12-T1-2: Add code comment clarifying stdin-only client capture in `on_request` (PASS) - 2026-02-18 — FU-P12-T1-1: Remove or document `MCPInitializeParams` in schemas (PASS) - 2026-02-18 — FU-BUG-T7-1: Cap `pending_methods` map to guard against unbounded growth (PASS) -- 2026-02-18 — FU-P13-T2-2: Move PID file write to after successful upstream launch (PASS) ## Suggested Next Tasks - P13-T5 follow-up — Complete interactive prompt verification in a desktop session (P1) -- FU-P12-T1-4 — Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode (P2) - FU-P12-T1-5 — Cap `_clients` dict and prune `client_identities` to prevent unbounded growth (P2) +- FU-P12-T1-6 — Uniform HTML escaping in `renderClientWidgets` (P3) diff --git a/SPECS/Workplan.md b/SPECS/Workplan.md index 1952150b..fdc2938d 100644 --- a/SPECS/Workplan.md +++ b/SPECS/Workplan.md @@ -2281,7 +2281,8 @@ Phase 9 Follow-up Backlog --- -#### FU-P12-T1-4: Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode +#### ✅ FU-P12-T1-4: Make `IN FLIGHT` KPI reflect real in-flight requests in shared-metrics mode +- **Status:** ✅ Completed (2026-02-19) - **Description:** In shared SQLite metrics mode, `/api/metrics` currently returns `in_flight: 0` unconditionally, so the `IN FLIGHT` widget is not informative. Add process-safe in-flight tracking so this KPI reports the true number of outstanding requests across active wrapper processes. - **Priority:** P2 - **Dependencies:** P12-T1 @@ -2292,10 +2293,10 @@ Phase 9 Follow-up Backlog - Updated `src/mcpbridge_wrapper/webui/server.py` metrics payload (if schema adjustments are needed) - Updated tests for shared-metrics in-flight behavior - **Acceptance Criteria:** - - [ ] `IN FLIGHT` KPI is greater than zero while requests are in progress and returns to zero after responses - - [ ] Works correctly with multiple concurrent clients/processes using the shared metrics database - - [ ] No regressions in existing dashboard metrics endpoints - - [ ] `pytest` suite remains green + - [x] `IN FLIGHT` KPI is greater than zero while requests are in progress and returns to zero after responses + - [x] Works correctly with multiple concurrent clients/processes using the shared metrics database + - [x] No regressions in existing dashboard metrics endpoints + - [x] `pytest` suite remains green --- diff --git a/src/mcpbridge_wrapper/webui/shared_metrics.py b/src/mcpbridge_wrapper/webui/shared_metrics.py index ba8b2a90..a2bcbed5 100644 --- a/src/mcpbridge_wrapper/webui/shared_metrics.py +++ b/src/mcpbridge_wrapper/webui/shared_metrics.py @@ -282,6 +282,14 @@ def get_summary(self, window_seconds: int = 3600) -> Dict[str, Any]: ).fetchone() rps = (row[0] or 0) / 60.0 + # Outstanding requests in the active window. + row = conn.execute( + """SELECT COUNT(*) FROM requests + WHERE timestamp > ? AND latency_ms IS NULL""", + (cutoff,), + ).fetchone() + in_flight = row[0] or 0 + # Error breakdown by code error_counts_by_code: Dict[int, int] = {} err_cursor = conn.execute( @@ -322,7 +330,7 @@ def get_summary(self, window_seconds: int = 3600) -> Dict[str, Any]: "tool_counts": tool_counts, "tool_errors": tool_errors, "tool_latency": tool_latency, - "in_flight": 0, # Can't track across processes easily + "in_flight": in_flight, "client_name": client_name, "client_version": client_version, "clients": clients, diff --git a/tests/unit/webui/test_shared_metrics.py b/tests/unit/webui/test_shared_metrics.py index 6c53b003..2029240a 100644 --- a/tests/unit/webui/test_shared_metrics.py +++ b/tests/unit/webui/test_shared_metrics.py @@ -44,6 +44,40 @@ def test_record_error(self, store): assert summary["total_errors"] == 1 assert summary["tool_errors"]["BuildProject"] == 1 + def test_in_flight_tracks_outstanding_requests(self, store): + """in_flight is non-zero while request is pending and zero after response.""" + store.record_request("BuildProject", request_id="123") + summary = store.get_summary() + assert summary["in_flight"] == 1 + + store.record_response("BuildProject", request_id="123", error=False, latency_ms=100.0) + summary = store.get_summary() + assert summary["in_flight"] == 0 + + def test_in_flight_aggregates_across_store_instances(self, tmp_path): + """Separate processes (store instances) share outstanding in-flight count.""" + db_path = tmp_path / "shared_in_flight.db" + store_a = SharedMetricsStore(db_path=db_path) + store_b = SharedMetricsStore(db_path=db_path) + store_a.reset() + + store_a.record_request("BuildProject", request_id="a1") + store_b.record_request("OpenFile", request_id="b1") + + summary = store_a.get_summary() + assert summary["in_flight"] == 2 + + store_a.record_response("BuildProject", request_id="a1", error=False, latency_ms=10.0) + summary = store_b.get_summary() + assert summary["in_flight"] == 1 + + store_b.record_response("OpenFile", request_id="b1", error=False, latency_ms=20.0) + summary = store_a.get_summary() + assert summary["in_flight"] == 0 + + store_a.close() + store_b.close() + def test_get_timeseries_format(self, store): """Test that get_timeseries returns correct format for frontend.""" # Record some test data