Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# FU-P7-T3-2 — Exclude broker-owned dashboard listeners from foreign port-conflict guidance

## Objective Summary

`FU-P7-T3-1` fixed the case where a live broker PID and a foreign process on
the requested dashboard port were collapsed into broker-only remediation.
Review of that change found one remaining ownership gap: the mixed-state branch
currently treats any listener on the dashboard port as foreign, even when the
listener belongs to the same broker daemon named by the PID file. When backend
probes degrade while the broker still owns the port, startup and `--doctor`
can incorrectly tell users to stop an "existing listener" or use
`--web-ui-restart`, even though the right guidance is still the broker-health
path.

This follow-up should keep the mixed-state foreign-listener behavior from
`FU-P7-T3-1`, but make it ownership-aware. The implementation should filter out
the broker daemon PID before choosing the foreign-listener/port-occupied path,
while leaving single-blocker behavior unchanged for genuine foreign listeners
and broker-without-dashboard states.

## Deliverables

- Update `src/mcpbridge_wrapper/__main__.py` so mixed-state startup guidance
only treats listener PIDs that differ from the running broker PID as foreign
port conflicts.
- Update `src/mcpbridge_wrapper/doctor.py` so mixed-state diagnostics use the
same broker-owned-listener exclusion before returning `port-occupied`.
- Extend `tests/unit/test_main.py` and `tests/unit/test_doctor.py` with
broker-owned-listener regression coverage alongside the existing
foreign-listener tests.
- Produce `SPECS/INPROGRESS/FU-P7-T3-2_Validation_Report.md` with targeted and
full quality-gate results.

## Success Criteria

- `--broker-console` and `--broker-daemon --web-ui` only surface
foreign-listener occupied-port guidance when `listener_pids` contains at
least one PID other than the running broker PID.
- `--doctor` does not classify a broker-owned dashboard listener plus degraded
probes as "stop the existing listener"; it stays on broker-health guidance.
- Regression tests cover both the foreign-listener and broker-owned-listener
mixed states so future refactors cannot reintroduce self-conflict messaging.

## Test-First Plan

1. Extend the existing startup tests for `_run_broker_console()` and
`main()`’s `--broker-daemon --web-ui` flow with a same-PID listener case,
asserting the error stays on the broker-reset/broker-health path and does
not mention "existing listener" or `--web-ui-restart`.
2. Extend the mixed-state doctor classification coverage with a dashboard
listener list that contains the local broker PID, asserting the report stays
in the `broker-without-dashboard` family instead of `port-occupied`.
3. Implement the smallest production change needed to derive a foreign-listener
subset before selecting user-facing remediation.
4. Run `pytest`, `ruff check src/`, `mypy src/`, and `pytest --cov`.

## Execution Plan

### Phase 1: Pin the self-listener contract

Inputs:
- `tests/unit/test_main.py`
- `tests/unit/test_doctor.py`
- existing foreign-listener regressions from `FU-P7-T3-1`

Outputs:
- failing tests for broker-owned listener mixed states in startup and doctor

Verification:
- the new tests fail against the current branch because self-owned listeners
still route through foreign-listener guidance

### Phase 2: Filter broker-owned listener PIDs

Inputs:
- `_report_requested_dashboard_unavailable()` and `_run_broker_console()` in
`src/mcpbridge_wrapper/__main__.py`
- `classify_doctor_report()` in `src/mcpbridge_wrapper/doctor.py`

Outputs:
- a minimal ownership-aware listener filter shared by startup and doctor logic
- unchanged messaging for genuine foreign listeners and plain
broker-without-dashboard states

Verification:
- mixed-state occupied-port guidance only appears when a non-broker PID still
owns the dashboard port

### Phase 3: Validate and archive

Inputs:
- updated production code
- targeted and full test suites

Outputs:
- passing targeted regressions and full quality gates
- validation report ready for ARCHIVE

Verification:
- coverage remains at or above the repository threshold
- review can focus on ownership precision rather than broad behavior changes

## Acceptance Tests

- `pytest tests/unit/test_main.py`
- `pytest tests/unit/test_doctor.py`
- `pytest`
- `ruff check src/`
- `mypy src/`
- `pytest --cov`

## Decision Points

- Prefer filtering the listener PID set over rewriting the message hierarchy;
the user-visible behavior from `FU-P7-T3-1` should remain intact for genuine
foreign listeners.
- If multiple listener PIDs are present and one matches the broker PID, the
code should still report a port conflict when any remaining PID is foreign.

## Notes

- No documentation updates are expected unless stderr wording changes beyond
the existing broker-health and foreign-listener guidance surfaces.
- Review subject name for this task: `broker_owned_listener_guidance`.

---
**Archived:** 2026-03-07
**Verdict:** PASS
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# FU-P7-T3-2 Validation Report

**Task:** FU-P7-T3-2 — Exclude broker-owned dashboard listeners from foreign port-conflict guidance
**Date:** 2026-03-07
**Verdict:** PASS

## Summary

Implemented the `FU-P7-T3-2` follow-up by:

- updating `src/mcpbridge_wrapper/__main__.py` so mixed-state startup guidance
filters out the running broker PID before treating dashboard listeners as a
foreign port conflict
- updating `src/mcpbridge_wrapper/doctor.py` so doctor classification applies
the same ownership filter before returning `port-occupied`
- extending startup and doctor regressions so foreign-listener cases still
trigger occupied-port guidance, while broker-owned-listener cases stay on the
broker-health reset path

## Files Validated

- `src/mcpbridge_wrapper/__main__.py`
- `src/mcpbridge_wrapper/doctor.py`
- `tests/unit/test_main.py`
- `tests/unit/test_doctor.py`

## Targeted Verification

```bash
pytest tests/unit/test_main.py tests/unit/test_doctor.py -k 'same_pid_listener or broker_owned_listener or mixed_broker_and_foreign_listener_prefers_port_conflict or mixed_state_mentions_foreign_listener'
```

- Result: `6 passed`
- Observed outcome: foreign-listener mixed states still surface occupied-port
guidance, while same-PID listener states stay on broker-health guidance.

```bash
pytest tests/unit/test_main.py tests/unit/test_doctor.py
```

- Result: `122 passed`

## Required Quality Gates

```bash
pytest
```

- Result: `894 passed, 5 skipped in 7.95s`

```bash
ruff check src/
```

- Result: `All checks passed!`

```bash
mypy src/
```

- Result: `Success: no issues found in 20 source files`

```bash
pytest --cov
```

- Result: `894 passed, 5 skipped in 8.75s`
- Coverage: `91.78%`

## Acceptance Criteria Evidence

- [x] `--broker-console` and `--broker-daemon --web-ui` only surface
foreign-listener occupied-port guidance when `listener_pids` contains at
least one PID other than the running broker PID.
- Evidence:
`tests/unit/test_main.py::TestBrokerConsoleHelpers::test_run_broker_console_mixed_state_mentions_foreign_listener`,
`tests/unit/test_main.py::TestBrokerConsoleHelpers::test_run_broker_console_same_pid_listener_uses_broker_reset_guidance`,
`tests/unit/test_main.py::TestMainBrokerWebUIFlowCoverage::test_main_broker_daemon_webui_mixed_state_mentions_foreign_listener`,
and
`tests/unit/test_main.py::TestMainBrokerWebUIFlowCoverage::test_main_broker_daemon_webui_same_pid_listener_uses_broker_reset_guidance`
all passed.
- [x] `--doctor` does not classify a broker-owned dashboard listener plus
degraded probes as "stop the existing listener"; it stays on broker-health
guidance.
- Evidence:
`tests/unit/test_doctor.py::TestClassifyDoctorReport::test_classify_broker_owned_listener_uses_broker_without_dashboard`
passed with `report.code == "broker-without-dashboard"`, while
`test_classify_mixed_broker_and_foreign_listener_prefers_port_conflict`
still passed with `report.code == "port-occupied"`.
- [x] Regression tests cover both the foreign-listener and broker-owned-listener
mixed states so future refactors cannot reintroduce self-conflict messaging.
- Evidence: `tests/unit/test_main.py` and `tests/unit/test_doctor.py` now
cover mixed sets containing both the broker PID and a foreign PID, plus the
same-PID-only path.

## Notes

- Existing `websockets` / `uvicorn` deprecation warnings remain in the suite
and are unrelated to this task.
4 changes: 4 additions & 0 deletions SPECS/ARCHIVE/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

| Task ID | Folder | Archived | Verdict |
|---------|--------|----------|---------|
| FU-P7-T3-2 | [FU-P7-T3-2_Exclude_broker-owned_dashboard_listeners_from_foreign_port-conflict_guidance/](FU-P7-T3-2_Exclude_broker-owned_dashboard_listeners_from_foreign_port-conflict_guidance/) | 2026-03-07 | PASS |
| FU-P7-T3-1 | [FU-P7-T3-1_Prioritize_foreign_port-owner_guidance_in_mixed_broker-dashboard_conflicts/](FU-P7-T3-1_Prioritize_foreign_port-owner_guidance_in_mixed_broker-dashboard_conflicts/) | 2026-03-07 | PASS |
| FU-P7-T1-1 | [FU-P7-T1-1_Normalize_KeyboardInterrupt_handling_when_broker-console_reuses_an_existing_host/](FU-P7-T1-1_Normalize_KeyboardInterrupt_handling_when_broker-console_reuses_an_existing_host/) | 2026-03-07 | PASS |
| P7-T3 | [P7-T3_Auto-recover_or_guide_on_dashboard_port_ownership_conflicts/](P7-T3_Auto-recover_or_guide_on_dashboard_port_ownership_conflicts/) | 2026-03-07 | PASS |
Expand Down Expand Up @@ -201,6 +202,7 @@

| File | Description |
|------|-------------|
| [REVIEW_broker_owned_listener_guidance.md](_Historical/REVIEW_broker_owned_listener_guidance.md) | Review report for FU-P7-T3-2 |
| [REVIEW_mixed_dashboard_conflict_guidance.md](_Historical/REVIEW_mixed_dashboard_conflict_guidance.md) | Review report for FU-P7-T3-1 |
| [REVIEW_broker_console_keyboardinterrupt_reuse.md](_Historical/REVIEW_broker_console_keyboardinterrupt_reuse.md) | Review report for FU-P7-T1-1 |
| [REVIEW_dashboard_port_ownership_conflicts.md](_Historical/REVIEW_dashboard_port_ownership_conflicts.md) | Review report for P7-T3 |
Expand Down Expand Up @@ -345,6 +347,8 @@

| Date | Task ID | Action |
|------|---------|--------|
| 2026-03-07 | FU-P7-T3-2 | Archived REVIEW_broker_owned_listener_guidance report |
| 2026-03-07 | FU-P7-T3-2 | Archived Exclude_broker-owned_dashboard_listeners_from_foreign_port-conflict_guidance (PASS) |
| 2026-03-07 | FU-P7-T3-1 | Archived REVIEW_mixed_dashboard_conflict_guidance report |
| 2026-03-07 | FU-P7-T3-1 | Archived Prioritize_foreign_port-owner_guidance_in_mixed_broker-dashboard_conflicts (PASS) |
| 2026-03-07 | FU-P7-T1-1 | Archived REVIEW_broker_console_keyboardinterrupt_reuse report |
Expand Down
39 changes: 39 additions & 0 deletions SPECS/ARCHIVE/_Historical/REVIEW_broker_owned_listener_guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## REVIEW REPORT — broker_owned_listener_guidance

**Scope:** origin/main..HEAD
**Files:** 9

### Summary Verdict
- [x] Approve
- [ ] Approve with comments
- [ ] Request changes
- [ ] Block

### Critical Issues

- None.

### Secondary Issues

- None.

### Architectural Notes

- The change stays scoped to PID ownership classification instead of rewriting
the broader mixed-state guidance flow introduced in `FU-P7-T3-1`.
- Startup and doctor now share the same core rule: only listener PIDs that
differ from the broker PID should trigger foreign port-conflict guidance.

### Tests

- `pytest tests/unit/test_main.py tests/unit/test_doctor.py -k 'same_pid_listener or broker_owned_listener or mixed_broker_and_foreign_listener_prefers_port_conflict or mixed_state_mentions_foreign_listener'` passed (`6 passed`)
- `pytest tests/unit/test_main.py tests/unit/test_doctor.py` passed (`122 passed`)
- `pytest` passed (`894 passed, 5 skipped`)
- `ruff check src/` passed
- `mypy src/` passed
- `pytest --cov` passed with `91.78%` coverage

### Next Steps

- No follow-up tasks required from this review.
- Archive the review artifact and continue to PR creation.
22 changes: 11 additions & 11 deletions SPECS/INPROGRESS/next.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# Next Task: FU-P7-T3-2Exclude broker-owned dashboard listeners from foreign port-conflict guidance
# Next Task: P7-T4Add direct local-status fallback for TUI when dashboard API is unavailable

**Priority:** P1
**Phase:** Phase 7: Broker UX and Diagnostics
**Effort:** 2-3 hours
**Dependencies:** FU-P7-T3-1
**Effort:** 4-5 hours
**Dependencies:** P6-T2
**Status:** Ready

## Description

Refine the mixed broker/dashboard conflict classifier so it distinguishes the
broker daemon's own dashboard listener from a foreign process on the same port.
When degraded probes occur against a broker-owned listener, startup and
diagnostics should keep users on broker-health guidance instead of reporting a
foreign port owner.
Reduce TUI dependence on the Web UI API by letting it fall back to local broker
state when the dashboard endpoint is unavailable. The TUI should still provide
useful diagnostics from PID/socket/version files and any directly accessible
broker status sources, while clearly indicating that live dashboard-backed
controls are unavailable.

## Recently Archived

- `2026-03-07` — `FU-P7-T3-2` archived with verdict `PASS`
- `2026-03-07` — `FU-P7-T3-1` archived with verdict `PASS`
- `2026-03-07` — `FU-P7-T1-1` archived with verdict `PASS`
- `2026-03-07` — `P7-T3` archived with verdict `PASS`

## Next Step

Create the `FU-P7-T3-2` PRD in `SPECS/INPROGRESS/`, then implement and validate
the broker-owned-listener exclusion in startup and doctor mixed-state guidance.
Create the `P7-T4` PRD in `SPECS/INPROGRESS/`, then implement and validate the
local broker-status fallback path for TUI.
9 changes: 5 additions & 4 deletions SPECS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,8 @@ Add new tasks using the canonical template in [TASK_TEMPLATE.md](TASK_TEMPLATE.m
- [x] `--doctor` does not hide the foreign listener behind a generic broker-without-dashboard diagnosis in the same mixed state
- [x] Regression tests cover the mixed-state conflict and prevent reordering back to broker-only guidance

#### ⬜️ FU-P7-T3-2: Exclude broker-owned dashboard listeners from foreign port-conflict guidance
#### ✅ FU-P7-T3-2: Exclude broker-owned dashboard listeners from foreign port-conflict guidance
- **Status:** ✅ Completed (2026-03-07)
- **Description:** Refine the mixed broker/dashboard conflict classifier so it distinguishes the broker daemon's own dashboard listener from a foreign process on the same port. When degraded probes occur against a broker-owned listener, startup and diagnostics should keep users on broker-health guidance instead of reporting a foreign port owner.
- **Priority:** P1
- **Dependencies:** FU-P7-T3-1
Expand All @@ -553,9 +554,9 @@ Add new tasks using the canonical template in [TASK_TEMPLATE.md](TASK_TEMPLATE.m
- `src/mcpbridge_wrapper/doctor.py` mixed-state diagnostic guidance with the same broker-owned listener exclusion
- `tests/unit/test_main.py` and `tests/unit/test_doctor.py` regression coverage for foreign-listener and broker-owned-listener mixed states
- **Acceptance Criteria:**
- [ ] Mixed-state foreign-port guidance triggers only when the dashboard listener PID differs from the running broker PID
- [ ] Broker-owned listeners with degraded dashboard probes do not tell users to stop an "existing listener" or use restart guidance meant for foreign ownership
- [ ] Regression tests cover both foreign-listener and broker-owned-listener mixed states in startup and doctor paths
- [x] Mixed-state foreign-port guidance triggers only when the dashboard listener PID differs from the running broker PID
- [x] Broker-owned listeners with degraded dashboard probes do not tell users to stop an "existing listener" or use restart guidance meant for foreign ownership
- [x] Regression tests cover both foreign-listener and broker-owned-listener mixed states in startup and doctor paths

#### ⬜️ P7-T4: Add direct local-status fallback for TUI when dashboard API is unavailable
- **Description:** Reduce TUI dependence on the Web UI API by letting it fall back to local broker state when the dashboard endpoint is unavailable. The TUI should still provide useful diagnostics from PID/socket/version files and any directly accessible broker status sources, while clearly indicating that live dashboard-backed controls are unavailable.
Expand Down
13 changes: 11 additions & 2 deletions src/mcpbridge_wrapper/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,13 @@ def _format_listener_pid_summary(listener_pids: Set[int]) -> str:
return f"listener {label} {joined}"


def _foreign_listener_pids(listener_pids: Set[int], running_broker_pid: Optional[int]) -> Set[int]:
"""Return listener PIDs that do not belong to the running broker."""
if running_broker_pid is None:
return set(listener_pids)
return {pid for pid in listener_pids if pid != running_broker_pid}


def _report_requested_dashboard_unavailable(
*,
runtime: Any,
Expand All @@ -610,11 +617,13 @@ def _report_requested_dashboard_unavailable(
listener_pids: Set[int],
) -> int:
"""Print one explicit remediation path for an unusable requested dashboard."""
if port is not None and listener_pids and running_broker_pid is not None:
foreign_listener_pids = _foreign_listener_pids(listener_pids, running_broker_pid)

if port is not None and foreign_listener_pids and running_broker_pid is not None:
print(
"Error: Broker daemon is already running "
f"(PID {running_broker_pid}), but Web UI port {port} is already occupied by "
f"{_format_listener_pid_summary(listener_pids)}, so no broker-backed "
f"{_format_listener_pid_summary(foreign_listener_pids)}, so no broker-backed "
f"dashboard is reachable at {runtime.base_url}. Stop the existing listener or "
f"retry startup with {_broker_console_restart_command()}. If the port "
f"becomes free and the dashboard is still unavailable, reset the dedicated "
Expand Down
Loading