Skip to content
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 5 additions & 1 deletion SPECS/ARCHIVE/INDEX.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions SPECS/INPROGRESS/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
11 changes: 6 additions & 5 deletions SPECS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

---

Expand Down
10 changes: 9 additions & 1 deletion src/mcpbridge_wrapper/webui/shared_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/webui/test_shared_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down