# Open Bugs & Known Issues

Active bugs and warnings. Resolved bugs are in `completed/BUGS.md`.

---

- Bug 13 moved to `docs/planning/completed/BUGS.md` (resolved on 2026-02-16).
- Bug 14 moved to `docs/planning/completed/BUGS.md` (resolved on 2026-02-16).
- Bug 15 moved to `docs/planning/completed/BUGS.md` (resolved on 2026-02-16).
- Bug 17 moved to `docs/planning/completed/BUGS.md` (resolved on 2026-02-17).

---

## Bug 16: SnapTrade User Secret Deleted in AWS Secrets Manager

**Status:** Verified resolved
**Severity:** High
**Date:** 2026-02-16

**Symptom:** Portfolio MCP calls fail with:
```
Failed to retrieve SnapTrade user secret from AWS Secrets Manager:
InvalidRequestException: You can't perform this operation on the secret because it was marked for deletion.
```

**Root cause:** The AWS secret `snaptrade/user_secret/hc@henrychien.com` was scheduled for deletion (unknown when/how). AWS keeps secrets in a "pending deletion" state for a recovery window before purging. During this window, `GetSecretValue` calls fail with `InvalidRequestException` rather than `ResourceNotFoundException`.

**Resolution:** Restored via `aws secretsmanager restore-secret --secret-id "snaptrade/user_secret/hc@henrychien.com" --region us-east-1`.

**Preventive measures:**
1. Add a health check that verifies secret accessibility on startup
2. Consider storing the user secret in `.env` as a fallback (less secure but more resilient)
3. Investigate what triggered the deletion — could be an accidental CLI call, a cleanup script, or AWS console action

**Key file:** `snaptrade_loader.py` — `get_snaptrade_user_secret()`

**Audit evidence (2026-02-16):**
- Command:
  - `python3 - <<'PY' ... get_snaptrade_user_secret('hc@henrychien.com') ... PY`
- Result:
  - Secret read succeeded (`ok True len 36`)
  - No `InvalidRequestException` observed

---

## Bug 18: min_months config drift in performance metrics adapter

**Status:** Fixed
**Severity:** Low
**Date:** 2026-02-16

**Symptom:** `test_adapter_keeps_default_min_months_for_longer_windows` fails — expects `min_months == 12` but gets `11`.

**Test file:** `tests/core/test_performance_metrics_engine.py:477`

**Root cause:** A prior commit changed the default `min_months` value or the adapter logic that computes it, but the test wasn't updated to match.

**Fix implemented (2026-02-16):**
- Updated regression expectation to match current default behavior (`min_months == 11`).

**Files changed:**
- `tests/core/test_performance_metrics_engine.py`

**Validation:**
- `pytest -q tests/core/test_performance_metrics_engine.py::test_adapter_keeps_default_min_months_for_longer_windows`
- Result: `1 passed`

---

## Bug 19: Brokerage alias test failure

**Status:** Fixed
**Severity:** Low
**Date:** 2026-02-16

**Symptom:** `test_get_positions_brokerage_alias` fails.

**Test file:** `tests/mcp_tools/test_brokerage_aliases.py`

**Root cause (confirmed):** Test monkeypatched removed symbol `mcp_tools.positions.get_default_user`; tool now resolves users through `resolve_user_email(...)`.

**Fix implemented (2026-02-16):**
- Updated test monkeypatch target to `resolve_user_email`.

**Files changed:**
- `tests/mcp_tools/test_brokerage_aliases.py`

**Validation:**
- `pytest -q tests/mcp_tools/test_brokerage_aliases.py`
- Result: `9 passed`

---

## Bug 20: get_default_user monkeypatch target missing

**Status:** Resolved (already fixed in codebase)
**Severity:** Low
**Date:** 2026-02-16

**Symptom:** `test_load_portfolio_for_performance_threads_dates_to_portfolio_data` fails with `AttributeError: <module 'mcp_tools.performance'> has no attribute 'get_default_user'`.

**Root cause:** The test monkeypatches `perf.get_default_user` but that function was moved or renamed. The test's monkeypatch target is stale.

**Test file:** `tests/mcp_tools/test_performance.py:551`

**Audit evidence (2026-02-16):**
- `mcp_tools/performance.py` currently exports/uses `get_default_user` compatibility seam.
- Validation commands:
  - `pytest -q tests/mcp_tools/test_performance.py::test_load_portfolio_for_performance_threads_dates_to_portfolio_data`
  - `pytest -q tests/mcp_tools/test_performance.py`
- Result: target test passed; full file `11 passed`.

---

## Bug 21: Intermittent Schwab credential detection during live provider calls

**Status:** Fixed
**Severity:** Medium
**Date:** 2026-02-17

**Symptom:** Live Schwab-backed calls are intermittently failing with:
`ValueError: Missing SCHWAB_APP_KEY or SCHWAB_APP_SECRET in environment`

Observed during real-function routing checks (`run_positions`, `run_trading_analysis`, and direct `PositionService` / `fetch_transactions_for_source` calls). Same flows may succeed in one run and fail in another.

**Observed behavior:** In some processes, `os.getenv("SCHWAB_APP_KEY")` and `os.getenv("SCHWAB_APP_SECRET")` start empty, then become populated after importing `settings`, suggesting runtime credential bootstrapping side effects.

**Root cause (confirmed):** `providers/routing.py` checked provider enablement/availability with `os.getenv(...)` only, while other codepaths (including `settings`) support `.env` fallback. This made routing sensitive to runtime env bootstrapping/import timing.

**Key files:** `settings.py`, `providers/routing.py`, `schwab_client.py`, `trading_analysis/data_fetcher.py`

**Fix implemented (2026-02-16):**
- Updated routing checks to use `settings._read_env_or_dotenv(...)` for:
  - provider flags (`SCHWAB_ENABLED`, `IBKR_ENABLED`)
  - credential checks (`SCHWAB_APP_KEY`, `SCHWAB_APP_SECRET`, IBKR Flex creds)
  - `SCHWAB_TOKEN_PATH`
- Added regression tests validating dotenv-backed routing behavior.

**Files changed:**
- `providers/routing.py`
- `tests/providers/test_provider_switching.py`
- `tests/providers/test_routing.py` (test isolation for env/dotenv behavior)

**Validation:**
- Repro evidence of bootstrap drift:
  - `python3 - <<'PY' ... print(os.getenv('SCHWAB_APP_KEY')) before/after importing settings ... PY`
  - Result: empty before import, populated after import.
- Regression suites:
  - `pytest -q tests/providers/test_provider_switching.py`
  - `pytest -q tests/providers/test_routing.py tests/providers/test_provider_switching.py`
- Result: all passing.

---

## Investigation 22: Low data coverage in realized performance (29-33%)

**Status:** Resolved (informational / expected data-window limitation)
**Severity:** Low (informational — does not affect tax harvest or trading analysis)
**Date:** 2026-02-17

**Symptom:** Realized performance shows 29% coverage (Schwab only) or 33% coverage (all sources). 10-17 positions have no transaction lots, causing synthetic opening positions and `HIGH CONFIDENCE GATE FAILED` warnings.

**Positions without lots (all sources):** CPPMF, ENB, GLBE, IGIC, KINS, LIFFF, NVDA, PCTY, TKO, V

**Root cause:** These positions were purchased before the transaction history lookback window of each provider (Schwab ~1yr, Plaid ~1-2yr, IBKR Flex 365d). No buy transaction exists on record.

**Impact:** Only affects realized performance NAV accuracy. Tax harvest and trading analysis work fine — positions without lots are simply excluded from lot-level analysis.

**Possible improvements:**
1. Backfill file — manually provide buy date/price for older positions (`backfill_path` param on `get_performance`)
2. Check if Schwab API supports fetching older transaction history (beyond default window)
3. Coverage naturally improves over time as new trades accumulate

**Key files:** `core/realized_performance_analysis.py`, `mcp_tools/performance.py`

**Audit evidence (2026-02-16):**
- Ran:
  - `python3 tests/diagnostics/diagnose_realized.py --source all --email hc@henrychien.com`
  - `python3 tests/diagnostics/diagnose_realized.py --source all --email hc@henrychien.com 2>/dev/null | rg -n "Current positions|Synthetic positions|Fully synthetic|NO HISTORY"`
- Observed:
  - `Current positions: 24`
  - `Synthetic positions: 19`
  - Fully synthetic tickers include `IGIC, KINS, NVDA, TKO, V`
  - Coverage gaps are consistent with missing historical buys outside provider lookback.

---

## Bug 23: Schwab dividend descriptions not resolving to ticker symbols

**Status:** Deferred (handled by separate solution track)
**Severity:** Medium
**Date:** 2026-02-17

**Symptom:** 4 "ENBRIDGE INC F" dividend transactions ($611.99 total) appear as `UNRESOLVED_DIVIDEND` in trading analysis income breakdown. Other dividends (DSU, CBL, MSCI, etc.) resolve correctly because they have matching trade rows in the description map.

**Root cause:** Schwab `DIVIDEND_OR_INTEREST` transactions have **no instrument/symbol field** — only a company name in the `description` field (e.g., "ENBRIDGE INC F"). The current two-pass resolution does exact matching:
1. Pass 1 (`description_map`): built from trade rows' descriptions → only works if a matching trade exists
2. Pass 2 (`schwab_security_lookup`): built from position names → Schwab positions report `name='ENB'` (ticker, not company name), so the lookup key is `'enb'`, which doesn't match `'enbridge inc f'`

ENB has no trade rows in the lookback window (bought before history), so pass 1 fails. The position name is just the ticker symbol, so pass 2 also fails.

**Possible fixes:**
1. **FMP company search fallback**: For unresolved descriptions, call `fmp_search("ENBRIDGE")` → returns ENB. Adds API calls but is robust.
2. **Enriched security lookup**: When building `schwab_security_lookup`, fetch company names via FMP for each position ticker, creating a `'enbridge inc' → ENB` mapping alongside `'enb' → ENB`.
3. **Word-prefix matching**: Check if any security lookup key is a prefix of the description (fragile).
4. **Static override map**: Manual `{'enbridge inc': 'ENB'}` map for known cases (simple but doesn't scale).

**Key files:** `providers/normalizers/schwab.py` — `_resolve_income_symbol()`, `build_schwab_security_lookup()`

**Reproduction (2026-02-16):**
- Ran:
  - `python3 - <<'PY' ... SchwabNormalizer(schwab_security_lookup={'enb':'ENB'}) ... description='ENBRIDGE INC F' ... PY`
- Result:
  - `symbol UNRESOLVED_DIVIDEND` (reproduced)

**Tracking note:** Implementation is being handled in a separate solution stream; no code change applied in this audit pass.

---

# Audit Run Metadata
- Date/time: 2026-02-16 23:02:46 EST
- Host/OS: Darwin macOS 26.2 (arm64)
- Language/toolchain versions (node/python/go/etc): Python 3.13.2, pytest 8.4.1, Node v22.18.0, npm 10.9.3, Playwright 1.54.1, Jest 30.0.4
- Commands used to setup/run:
  - `python3 -m pytest tests/ -v`
  - `make test`
  - `npm run test`
  - `npm run test:js`
  - `npm run test:e2e`
  - `python3 tests/ai_test_orchestrator.py --full -v`
  - `python3 -m pytest -v --maxfail=1 <suite>` for each major `tests/*` suite
  - CLI smoke batches: `python3 <entrypoint> --help` and `python3 <entrypoint> --version` (with timeout wrapper where needed)
  - API smoke: `uvicorn app:app ...` + `curl` against discovered routes
  - MCP smoke: JSON-RPC `initialize`, `tools/list`, and minimal `tools/call`
- Any missing prerequisites:
  - Auth/session cookie unavailable for protected API endpoints (expected 401 paths cannot be fully functionally validated)
  - Admin token unavailable for `/admin/*` and `/generate_key` positive-path validation
  - Some CLIs require brokerage/API credentials and/or external services (Plaid/SnapTrade/IBKR/FMP)
  - E2E flows require frontend on `:3000`, backend on `:5001`, and auth bootstrap artifacts

# Inventory
## CLIs
(table: name | entrypoint file | how to run | purpose | prerequisites | smoke status)
| name | entrypoint file | how to run | purpose | prerequisites | smoke status |
| `clear_dividend_cache.py` | `admin/clear_dividend_cache.py` | `python3 admin/clear_dividend_cache.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `clear_price_cache.py` | `admin/clear_price_cache.py` | `python3 admin/clear_price_cache.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `manage_reference_data.py` | `admin/manage_reference_data.py` | `python3 admin/manage_reference_data.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `manage_security_types.py` | `admin/manage_security_types.py` | `python3 admin/manage_security_types.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `migrate_reference_data.py` | `admin/migrate_reference_data.py` | `python3 admin/migrate_reference_data.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Nonstandard: both executed (check side effects) |
| `test_factor_intelligence.py` | `admin/test_factor_intelligence.py` | `python3 admin/test_factor_intelligence.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `verify_proxies.py` | `admin/verify_proxies.py` | `python3 admin/verify_proxies.py` | Admin/maintenance CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `app.py` | `app.py` | `python3 app.py` | Backend server entrypoint | DATABASE_URL + API env vars | Failure on smoke flags |
| `run_migration.py` | `database/run_migration.py` | `python3 database/run_migration.py` | Primary operational CLI | Python deps + .env/DB as needed | Failure on smoke flags |
| `run_cache.py` | `run_cache.py` | `python3 run_cache.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `run_factor_intelligence.py` | `run_factor_intelligence.py` | `python3 run_factor_intelligence.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `run_ibkr_data.py` | `run_ibkr_data.py` | `python3 run_ibkr_data.py` | Primary operational CLI | IBKR Gateway + IBKR env | Help OK; no version flag |
| `run_plaid.py` | `run_plaid.py` | `python3 run_plaid.py` | Primary operational CLI | Plaid credentials + DB | Help OK; no version flag |
| `run_portfolio_risk.py` | `run_portfolio_risk.py` | `python3 run_portfolio_risk.py` | Primary operational CLI | Python deps + .env/DB as needed | Nonstandard: both executed (check side effects) |
| `run_positions.py` | `run_positions.py` | `python3 run_positions.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `run_risk.py` | `run_risk.py` | `python3 run_risk.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `run_schwab.py` | `run_schwab.py` | `python3 run_schwab.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `run_snaptrade.py` | `run_snaptrade.py` | `python3 run_snaptrade.py` | Primary operational CLI | SnapTrade credentials + DB | Help OK; no version flag |
| `run_trading_analysis.py` | `run_trading_analysis.py` | `python3 run_trading_analysis.py` | Primary operational CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `backfill_fmp_tickers.py` | `scripts/backfill_fmp_tickers.py` | `python3 scripts/backfill_fmp_tickers.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Failure on smoke flags |
| `clean_cli_samples.py` | `scripts/clean_cli_samples.py` | `python3 scripts/clean_cli_samples.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Nonstandard: both executed (check side effects) |
| `collect_all_schemas.py` | `scripts/collect_all_schemas.py` | `python3 scripts/collect_all_schemas.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Timed out/non-idempotent on smoke flags |
| `collect_api_direct.py` | `scripts/collect_api_direct.py` | `python3 scripts/collect_api_direct.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Nonstandard: both executed (check side effects) |
| `explore_transactions.py` | `scripts/explore_transactions.py` | `python3 scripts/explore_transactions.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Nonstandard: both executed (check side effects) |
| `fetch_ibkr_trades.py` | `scripts/fetch_ibkr_trades.py` | `python3 scripts/fetch_ibkr_trades.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `get_api_json.py` | `scripts/get_api_json.py` | `python3 scripts/get_api_json.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Failure on smoke flags |
| `validate_adapters.py` | `scripts/validate_adapters.py` | `python3 scripts/validate_adapters.py` | Dev/ops script CLI | Python deps + .env/DB as needed | Help OK; no version flag |
| `ai_test_orchestrator.py` | `tests/ai_test_orchestrator.py` | `python3 tests/ai_test_orchestrator.py` | Test harness/diagnostic CLI | PYTHONPATH/repo root + test deps | Help OK; no version flag |
| `diagnose_realized.py` | `tests/diagnostics/diagnose_realized.py` | `python3 tests/diagnostics/diagnose_realized.py` | Test harness/diagnostic CLI | PYTHONPATH/repo root + test deps | Help OK; no version flag |
| `create_test_session.py` | `tests/utils/create_test_session.py` | `python3 tests/utils/create_test_session.py` | Test harness/diagnostic CLI | PYTHONPATH/repo root + test deps | Failure on smoke flags |
| `run_portfolio_crud_tests.py` | `tests/utils/run_portfolio_crud_tests.py` | `python3 tests/utils/run_portfolio_crud_tests.py` | Primary operational CLI | PYTHONPATH/repo root + test deps | Help OK; no version flag |
| `show_db_data.py` | `tests/utils/show_db_data.py` | `python3 tests/utils/show_db_data.py` | Test harness/diagnostic CLI | PYTHONPATH/repo root + test deps | Nonstandard: both executed (check side effects) |
| `test_claude_functions.py` | `tests/utils/test_claude_functions.py` | `python3 tests/utils/test_claude_functions.py` | Test harness/diagnostic CLI | PYTHONPATH/repo root + test deps | Failure on smoke flags |
| `backfill_subindustry_peers.py` | `tools/backfill_subindustry_peers.py` | `python3 tools/backfill_subindustry_peers.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Help OK; no version flag |
| `check_dependencies.py` | `tools/check_dependencies.py` | `python3 tools/check_dependencies.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `check_parameter_alignment.py` | `tools/check_parameter_alignment.py` | `python3 tools/check_parameter_alignment.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Parser error on --help |
| `clean_plaid_trace.py` | `tools/clean_plaid_trace.py` | `python3 tools/clean_plaid_trace.py` | Codebase analysis/tracing utility | Plaid credentials + DB | Nonstandard: both executed (check side effects) |
| `code_tracer.py` | `tools/code_tracer.py` | `python3 tools/code_tracer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `demo_tracer.py` | `tools/demo_tracer.py` | `python3 tools/demo_tracer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `enhanced_code_tracer.py` | `tools/enhanced_code_tracer.py` | `python3 tools/enhanced_code_tracer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `fullstack_code_tracer.py` | `tools/fullstack_code_tracer.py` | `python3 tools/fullstack_code_tracer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `fullstack_living_map.py` | `tools/fullstack_living_map.py` | `python3 tools/fullstack_living_map.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `fullstack_real_dependencies.py` | `tools/fullstack_real_dependencies.py` | `python3 tools/fullstack_real_dependencies.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `js_analyzer.py` | `tools/js_analyzer.py` | `python3 tools/js_analyzer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `living_code_map.py` | `tools/living_code_map.py` | `python3 tools/living_code_map.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `living_code_map_clean.py` | `tools/living_code_map_clean.py` | `python3 tools/living_code_map_clean.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `real_dependency_tracer.py` | `tools/real_dependency_tracer.py` | `python3 tools/real_dependency_tracer.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `test_all_interfaces.py` | `tools/test_all_interfaces.py` | `python3 tools/test_all_interfaces.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `trace_plaid.py` | `tools/trace_plaid.py` | `python3 tools/trace_plaid.py` | Codebase analysis/tracing utility | Plaid credentials + DB | Nonstandard: both executed (check side effects) |
| `view_alignment.py` | `tools/view_alignment.py` | `python3 tools/view_alignment.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Nonstandard: both executed (check side effects) |
| `watch_and_update.py` | `tools/watch_and_update.py` | `python3 tools/watch_and_update.py` | Codebase analysis/tracing utility | Python deps; many require repo root context | Timed out/non-idempotent on smoke flags |
| `main.py` | `trading_analysis/main.py` | `python3 trading_analysis/main.py` | Trading analysis runner | Python deps + .env/DB as needed | Failure on smoke flags |

## MCP
(table: component | start command | purpose | prerequisites | smoke status)
| component | start command | purpose | prerequisites | smoke status |
| `portfolio-mcp` | `RISK_MODULE_USER_EMAIL=<email> python3 mcp_server.py` | Portfolio analysis, risk, performance, trading tools | DB + user email env + provider creds for some tools | `initialize` OK, `tools/list` OK (`20` tools), `tools/call get_mcp_context` OK |
| `fmp-mcp` | `python3 fmp_mcp_server.py` | FMP market/fundamental tools | FMP API key for live data endpoints | `initialize` OK, `tools/list` OK (`17` tools), `tools/call fmp_list_endpoints` OK |
| `ibkr-mcp` | `python3 ibkr_mcp_server.py` | IBKR market/account tools | IBKR Gateway/session for live account data | `initialize` OK, `tools/list` OK (`6` tools), `tools/call get_ibkr_account` OK |

## API Endpoints
(table: method | path | source file | auth | expected | status)
| method | path | source file | auth | expected | status |
| `GET` | `/api/health` | `app.py` | None or request-body based | 200 health JSON | `200` |
| `POST` | `/api/analyze` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/direct/portfolio` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/risk-score` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/performance` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/interpret` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/portfolio-analysis` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/what-if` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/min-variance` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/max-return` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/portfolios` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/portfolio/refresh-prices` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/portfolios/test-portfolio` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `DELETE` | `/api/portfolios/test-portfolio` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/portfolios` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `PUT` | `/api/portfolios/test-portfolio` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `PATCH` | `/api/portfolios/test-portfolio` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/expected-returns` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/expected-returns` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/risk-settings` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/risk-settings` | `app.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/direct/stock` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/direct/what-if` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/direct/optimize/min-variance` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/direct/optimize/max-return` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/direct/performance` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `POST` | `/api/direct/interpret` | `app.py` | Session/API key required | 422 with empty payload | `422` |
| `GET` | `/auth/status` | `routes/auth.py` | None or request-body based | 200 auth status | `200` |
| `POST` | `/auth/google` | `routes/auth.py` | None or request-body based | 422 without token payload | `422` |
| `POST` | `/auth/logout` | `routes/auth.py` | None or request-body based | 200 success envelope | `200` |
| `POST` | `/auth/cleanup` | `routes/auth.py` | None or request-body based | 200 success envelope | `200` |
| `POST` | `/api/log-frontend` | `routes/frontend_logging.py` | None or request-body based | 200 health/log ack | `200` |
| `GET` | `/api/log-frontend/health` | `routes/frontend_logging.py` | None or request-body based | 200 health/log ack | `200` |
| `GET` | `/plaid/connections` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/plaid/create_link_token` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/plaid/exchange_public_token` | `routes/plaid.py` | Session/API key required | 401 without auth session | `422` |
| `GET` | `/plaid/holdings` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/plaid/connection_status` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/plaid/poll_completion` | `routes/plaid.py` | Session/API key required | 401 without auth session | `422` |
| `POST` | `/plaid/webhook` | `routes/plaid.py` | None or request-body based | 422 without required body | `422` |
| `DELETE` | `/plaid/connections/test-inst` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `DELETE` | `/plaid/user` | `routes/plaid.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/snaptrade/register` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/snaptrade/create-connection-url` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/snaptrade/connections` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/snaptrade/holdings` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `DELETE` | `/api/snaptrade/connections/test-auth` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `DELETE` | `/api/snaptrade/user` | `routes/snaptrade.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/snaptrade/webhook` | `routes/snaptrade.py` | None or request-body based | 422 without required body | `422` |
| `GET` | `/api/provider-routing/institution-support/charles_schwab` | `routes/provider_routing_api.py` | None | 200 data | `200` |
| `GET` | `/api/provider-routing/supported-institutions` | `routes/provider_routing_api.py` | None | 200 data | `200` |
| `GET` | `/api/provider-routing/status` | `routes/provider_routing_api.py` | None | 200 data | `200` |
| `GET` | `/api/provider-routing/metrics` | `routes/provider_routing_api.py` | None | 200 data | `200` |
| `POST` | `/api/claude_chat` | `routes/claude.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/claude_chat_stream` | `routes/claude.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-intelligence/correlations` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-intelligence/performance` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-intelligence/recommendations` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-intelligence/portfolio-recommendations` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/factor-groups` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/factor-groups/test-group` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-groups` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `PUT` | `/api/factor-groups/test-group` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `DELETE` | `/api/factor-groups/test-group` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/api/factor-groups/test-group/validate` | `routes/factor_intelligence.py` | Session/API key required | 401 without auth session | `401` |
| `GET` | `/api/positions/monitor` | `routes/positions.py` | Session/API key required | 401 without auth session | `401` |
| `POST` | `/generate_key` | `routes/admin.py` | admin_token query required | 422/401 without admin_token | `422` |
| `GET` | `/admin/usage_summary` | `routes/admin.py` | admin_token query required | 422/401 without admin_token | `422` |
| `GET` | `/admin/cache_status` | `routes/admin.py` | admin_token query required | 422/401 without admin_token | `422` |
| `POST` | `/admin/clear_cache` | `routes/admin.py` | admin_token query required | 422/401 without admin_token | `422` |

## Tests
(table: suite | command | scope | status | notes)
| suite | command | scope | status | notes |
| Python full (tests folder) | `python3 -m pytest tests/ -v` | Canonical Python tests from `package.json` | Fail | Collection interrupted by 2 errors (`routes.api` import missing; duplicate `test_snaptrade_integration` module name collision) |
| Python full (repo-wide via Make) | `make test` (`pytest -v`) | Unscoped repo-wide collection | Fail | 53 collection/import mismatches across `archive/`, `backup/`, `risk_module_secrets/`, plus canonical tests |
| npm full | `npm run test` | Python + JS wrapper | Fail | Stops in Python stage with same 2 errors |
| JS Jest suite | `npm run test:js` | Frontend and frontend-related tests | Fail | React/ReactDOM version mismatch and missing module import paths |
| E2E suite | `npm run test:e2e` | Playwright e2e (30 tests) | Fail | 28 failed, 2 passed; frontend/backend endpoints unavailable (`ERR_EMPTY_RESPONSE`, `ECONNREFUSED`) |
| AI orchestrator full | `python3 tests/ai_test_orchestrator.py --full -v` | Multi-phase test orchestrator | Fail | 24 total: 3 passed, 10 failed, 11 skipped; API/integration skipped due server readiness failures |
| `tests/api` | `python3 -m pytest -v --maxfail=1 tests/api` | API unit/integration files | Fail | `ModuleNotFoundError: routes.api` |
| `tests/core` | `python3 -m pytest -v --maxfail=1 tests/core` | Core analytics | Fail | `test_adapter_keeps_default_min_months_for_longer_windows` expects 12, got 11 |
| `tests/services` | `python3 -m pytest -v --maxfail=1 tests/services` | Service layer | Pass | 52 passed |
| `tests/providers` | `python3 -m pytest -v --maxfail=1 tests/providers` | Provider interfaces/normalizers/routing | Pass | 98 passed |
| `tests/mcp_tools` | `python3 -m pytest -v --maxfail=1 tests/mcp_tools` | MCP tool tests | Fail | `AttributeError` monkeypatch target missing: `mcp_tools.positions.get_default_user` |
| `tests/trading_analysis` | `python3 -m pytest -v --maxfail=1 tests/trading_analysis` | Trading analysis | Pass | 28 passed |
| `tests/ibkr` | `python3 -m pytest -v --maxfail=1 tests/ibkr` | IBKR adapters/market/flex/cache | Pass | 88 passed |
| `tests/snaptrade` | `python3 -m pytest -v --maxfail=1 tests/snaptrade` | SnapTrade tests | Pass with skip | 3 passed, 1 skipped |
| `tests/unit` | `python3 -m pytest -v --maxfail=1 tests/unit` | Unit tests | Pass | 16 passed |
| `tests/utils` | `python3 -m pytest -v --maxfail=1 tests/utils` | Utility tests | Pass with warnings | 8 passed; several `PytestReturnNotNoneWarning` |
| `tests/fmp` | `python3 -m pytest -v --maxfail=1 tests/fmp` | FMP-specific tests | Pass | 9 passed |
| `tests/factor_intelligence` | `python3 -m pytest -v --maxfail=1 tests/factor_intelligence` | Factor intelligence | Pass with warnings | 12 passed; pydantic v1 validator deprecation warnings |
| `tests/performance` | `python3 -m pytest -v --maxfail=1 tests/performance` | Performance directory | No tests collected | 0 tests |
| `tests/diagnostics` | `python3 -m pytest -v --maxfail=1 tests/diagnostics` | Diagnostics directory | No tests collected | 0 tests |
| Frontend unit npm alias | `npm run test:unit` | Jest unit pattern | Fail | Same react version mismatch + path import errors |
| Frontend integration npm alias | `npm run test:integration` | `frontend/test-runner.js` | Fail | `MODULE_NOT_FOUND` for `frontend/test-runner.js` |
| Adapter validation scripts | `npm run test:adapters`, `npm run validate:adapters` | Adapter-model contract checks | Pass | 9/9 adapters valid |

# Issues Catalog

## [ISSUE-001] Repo-wide pytest collection is broken by mixed legacy trees
- Area: Tests
- Severity: High
- Status: New
- Repro steps:
  1) Run `make test` from repo root.
- Expected:
  - Canonical test run should collect only intended active test suites and execute without import path collisions.
- Actual:
  - Pytest collects across `archive/`, `backup/`, `risk_module_secrets/` and fails with 53 collection errors before execution.
- Evidence:
  - command(s): `make test`
  - logs/snippets (trimmed but sufficient): `collected 1180 items / 53 errors`; many `ImportPathMismatchError` and duplicate module-name collisions.
- Suspected location (file paths / functions): `Makefile` test target (`pytest -v` unscoped)
- Notes / hypotheses (optional, no fixes): Unscoped discovery in a mono-worktree with historical copies is incompatible with default pytest import behavior.

## [ISSUE-002] Canonical Python suite fails during collection (`routes.api` + duplicate test module name)
- Area: Tests
- Severity: High
- Status: New
- Repro steps:
  1) Run `python3 -m pytest tests/ -v`.
- Expected:
  - `tests/` collection should complete and run tests.
- Actual:
  - Collection aborts with:
    - `ModuleNotFoundError: No module named 'routes.api'` (`tests/api/test_portfolio_api_crud.py`)
    - `import file mismatch` for `test_snaptrade_integration` (same basename under `tests/api` and `tests/snaptrade`).
- Evidence:
  - command(s): `python3 -m pytest tests/ -v`, `npm run test`
  - logs/snippets (trimmed but sufficient): `collected 970 items / 2 errors`, interrupted at collection.
- Suspected location (file paths / functions): `tests/api/test_portfolio_api_crud.py`, `tests/snaptrade/test_snaptrade_integration.py`, test naming/layout
- Notes / hypotheses (optional, no fixes): Legacy Flask route import contract remains in tests after FastAPI migration.

## [ISSUE-003] Frontend Jest suite fails due React package version mismatch and stale import paths
- Area: Tests
- Severity: High
- Status: New
- Repro steps:
  1) Run `npm run test:js` (or `npm run test:unit`).
- Expected:
  - Jest suites should start and run assertions.
- Actual:
  - Multiple suites fail at startup:
    - `Incompatible React versions ... react 19.1.1 vs react-dom 19.1.0`
    - missing module paths in tests (e.g., `frontend/src/store/AppStore`, `frontend/src/hooks/useAuthFlow`).
- Evidence:
  - command(s): `npm run test:js`, `npm run test:unit`
  - logs/snippets (trimmed but sufficient): 10 failed suites in `test:js`; 6 failed suites in `test:unit`.
- Suspected location (file paths / functions): `frontend/package.json`, lockfiles, `tests/frontend/**`
- Notes / hypotheses (optional, no fixes): Dependency drift + test path assumptions out of sync with current frontend structure.

## [ISSUE-004] `npm run test:integration` points to missing runner file
- Area: Tests
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `npm run test:integration`.
- Expected:
  - Integration runner should execute.
- Actual:
  - Node exits with `MODULE_NOT_FOUND: /frontend/test-runner.js`.
- Evidence:
  - command(s): `npm run test:integration`
  - logs/snippets (trimmed but sufficient): `Error: Cannot find module '/Users/henrychien/Documents/Jupyter/risk_module/frontend/test-runner.js'`.
- Suspected location (file paths / functions): `frontend/package.json` scripts, missing `frontend/test-runner.js`
- Notes / hypotheses (optional, no fixes): Script entry in package config references a deleted/renamed file.

## [ISSUE-005] E2E command does not ensure required services are up, causing near-total failure
- Area: Tests
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `npm run test:e2e` with no prestarted frontend/backend.
- Expected:
  - Either bootstrap services automatically or fail fast with explicit prerequisite error.
- Actual:
  - 28/30 tests fail with `ERR_EMPTY_RESPONSE` (`localhost:3000`) and `ECONNREFUSED` (`localhost:5001`).
- Evidence:
  - command(s): `npm run test:e2e`
  - logs/snippets (trimmed but sufficient): Playwright failures across user-journey/component suites; only 2 tests passed.
- Suspected location (file paths / functions): Playwright setup/config and npm e2e script orchestration
- Notes / hypotheses (optional, no fixes): The command assumes pre-running services but does not enforce or validate prerequisites before full execution.

## [ISSUE-006] Documented `python app.py` startup path is broken by circular import
- Area: API
- Severity: High
- Status: New
- Repro steps:
  1) Run `python3 app.py`.
- Expected:
  - Backend server starts (README states this is an alternative startup path).
- Actual:
  - Startup aborts: `ImportError: cannot import name 'plaid_router' from partially initialized module 'routes.plaid'`.
- Evidence:
  - command(s): `python3 app.py`, `python3 app.py --help`
  - logs/snippets (trimmed but sufficient): circular import trace through `routes/plaid.py` importing from `app` while `app` imports `plaid_router`.
- Suspected location (file paths / functions): `app.py`, `routes/plaid.py`
- Notes / hypotheses (optional, no fixes): `uvicorn app:app` works, so script-mode import order is specifically problematic.

## [ISSUE-007] Multiple scripts treat `--help`/`--version` as real execution paths (unsafe smoke behavior)
- Area: CLI
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `python3 scripts/explore_transactions.py --help` (or `--version`).
  2) Run `python3 scripts/clean_cli_samples.py --help`.
  3) Run `python3 scripts/collect_api_direct.py --help`.
- Expected:
  - Help/version flags should not perform external calls, DB writes, or filesystem output generation.
- Actual:
  - Commands execute real workflows: brokerage/API fetches, schema/sample generation, data cleaning outputs.
- Evidence:
  - command(s): as above (plus timed smoke matrix in inventory)
  - logs/snippets (trimmed but sufficient): transaction fetches and sample generation output observed during help/version invocation.
- Suspected location (file paths / functions): `scripts/explore_transactions.py`, `scripts/clean_cli_samples.py`, `scripts/collect_api_direct.py`, similar non-argparse utilities
- Notes / hypotheses (optional, no fixes): Non-argparse scripts interpret first arg as business input and do not reserve flag semantics.

## [ISSUE-008] Several CLI entrypoints fail when run directly due import-path assumptions
- Area: CLI
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `python3 scripts/backfill_fmp_tickers.py --help`.
  2) Run `python3 trading_analysis/main.py --help`.
  3) Run `python3 tests/utils/test_claude_functions.py --help`.
- Expected:
  - Entry scripts should import project modules correctly when invoked from repo root.
- Actual:
  - Import failures, e.g.:
    - `ModuleNotFoundError: No module named 'database'`
    - `ModuleNotFoundError: No module named 'fmp'`
    - `ModuleNotFoundError: No module named 'services.claude'`
- Evidence:
  - command(s): above + `python3 tests/utils/create_test_session.py --help`
  - logs/snippets (trimmed but sufficient): direct import failures before parser/help output.
- Suspected location (file paths / functions): affected script imports; module path bootstrap logic
- Notes / hypotheses (optional, no fixes): Scripts depend on implicit `PYTHONPATH`/package layout not guaranteed in direct execution mode.

## [ISSUE-009] `/api/provider-routing/metrics` returns mock/static analytics values
- Area: API
- Severity: Medium
- Status: New
- Repro steps:
  1) Start backend with `uvicorn app:app`.
  2) Call `GET /api/provider-routing/metrics`.
  3) Inspect implementation in `routes/provider_routing_api.py`.
- Expected:
  - Metrics endpoint should return real routing telemetry or explicitly marked synthetic values.
- Actual:
  - Endpoint returns hardcoded values (`total_routes=1250`, `success_rate=0.94`, static institution list).
- Evidence:
  - command(s): `curl http://127.0.0.1:5001/api/provider-routing/metrics`
  - logs/snippets (trimmed but sufficient): 200 response with fixed values; code comments: “For now, return mock data...”.
- Suspected location (file paths / functions): `routes/provider_routing_api.py` (`_get_total_route_count`, `_get_provider_route_count`, `_calculate_*`, `_get_popular_institutions`)
- Notes / hypotheses (optional, no fixes): Response shape is production-like but semantics are synthetic, risking operator misinterpretation.

## [ISSUE-010] MCP documentation drift: portfolio server tool count mismatch
- Area: Docs
- Severity: Low
- Status: New
- Repro steps:
  1) Read `MCP_SERVERS.md` / `mcp_tools/README.md` (portfolio tool count documented as 19).
  2) Run MCP handshake and `tools/list` against `mcp_server.py`.
- Expected:
  - Docs and runtime tool counts should match.
- Actual:
  - Runtime exposes `20` tools for `portfolio-mcp`.
- Evidence:
  - command(s): programmatic MCP JSON-RPC handshake (`initialize`, `tools/list`)
  - logs/snippets (trimmed but sufficient): `portfolio: init_ok=True tools=20`.
- Suspected location (file paths / functions): `MCP_SERVERS.md`, `mcp_tools/README.md`, `mcp_server.py` registration list
- Notes / hypotheses (optional, no fixes): Likely recent tool addition without docs update.

## [ISSUE-011] Core suite regression: expected `min_months` default no longer matches implementation
- Area: Tests
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `python3 -m pytest -v --maxfail=1 tests/core`.
- Expected:
  - `test_adapter_keeps_default_min_months_for_longer_windows` should pass with expected `min_months == 12`.
- Actual:
  - Assertion failure: actual `min_months == 11`.
- Evidence:
  - command(s): above
  - logs/snippets (trimmed but sufficient): `assert 11 == 12` in `tests/core/test_performance_metrics_engine.py::...`.
- Suspected location (file paths / functions): performance metrics adapter path in `portfolio_risk.py` / related service glue
- Notes / hypotheses (optional, no fixes): Either test expectation stale or business-rule default changed without test alignment.

## [ISSUE-012] MCP tool test regression: missing monkeypatch target in positions module
- Area: Tests
- Severity: Medium
- Status: New
- Repro steps:
  1) Run `python3 -m pytest -v --maxfail=1 tests/mcp_tools`.
- Expected:
  - `tests/mcp_tools/test_brokerage_aliases.py` should monkeypatch existing symbol successfully.
- Actual:
  - `AttributeError`: `mcp_tools.positions` has no attribute `get_default_user`.
- Evidence:
  - command(s): above
  - logs/snippets (trimmed but sufficient): failure in `test_get_positions_brokerage_alias` at monkeypatch line.
- Suspected location (file paths / functions): `mcp_tools/positions.py`, `tests/mcp_tools/test_brokerage_aliases.py`
- Notes / hypotheses (optional, no fixes): Test contract predates refactor in user-resolution helper path.
