## Outcome **Sprint 11 is not release-gated yet.** Current baseline is healthy, but several mandated scenarios are either absent or contradicted by current behavior. ### Verified baseline - Backend full suite: **659 passed** in 150.70s. - Migration gate: **PASS** - Empty DB → schema **43** - Sprint-5 schema **40 → 43** - Integrity and seed-preservation checks passed. - `git diff --check`: passed. - Worktree remained clean and unchanged. - Frontend suite could not be run locally because `frontend/node_modules` is absent; CI installs it with `npm ci`. ## Coverage audit | Mandate | Current coverage | Assessment | |---|---|---| | Full suites | Backend full suite and frontend CI job exist | Good, but backend CI hard-pins **659** | | GET side-effect safety | Quote/chart GETs are cache-only; analytics/policy GETs check read-only behavior | **Critical gap:** `GET /api/equity/{id}/candles?refresh=true` calls yfinance and writes cache rows | | 22-position pagination | Sprint-9 activation asserts 22 mappings/positions | **Gap:** quote refresh uses one SQL `LIMIT`; frontend hardcodes `limit=10`; no 22-item refresh regression | | Rate-limit/auth classification | Implementation recognizes FMP 401/403/429 | **Gap:** no tests; auth currently collapses to `missing`, unlike explicit `rate_limited` | | Historical fallback cutoff/currency | Exact/prior date, seven-day cutoff, future rejection, and mapping-currency fallback are partly covered | **Partial:** no composite-fallback test proving historical requests never accept a provider’s current quote | | Apple 190 / Alphabet 96 | Apple CHF 190 exists in backend and Apple 190 appears in frontend fixture | **Partial:** no single portfolio completeness regression covering both Apple 190 and Alphabet 96 | | Policy inactive | Backend checks historical `is_active=false`; frontend shows historical version read-only without actions | Already adequately covered | | Responsive drawer | Drawer has responsive Tailwind classes; equity selection is tested | **Gap:** no dedicated drawer open/close/responsive/accessibility contract | ## Exact tests/files to add ### Backend — six newly collected cases 1. **`tests/unit/test_market_quotes_detail_charts_v1.py`** - `test_market_get_endpoints_are_cache_only_and_storage_free` - Monkeypatch all providers to fail if called. - Exercise status, quote, chart, candles, and positions GETs. - Compare `total_changes`, market/cache table counts, and audit rows before/after. - Remove or reject the mutating `refresh=true` GET contract; provider refresh should be an explicit POST. 2. **`tests/unit/test_market_quotes_detail_charts_v1.py`** - `test_equity_batch_refresh_covers_all_22_mappings_across_bounded_pages` - Seed 22 mapped active instruments. - Assert all 22 are visited exactly once across page boundaries such as `10 + 10 + 2`. - Assert `total=22`, `updated=22`, no duplicate provider calls, and no silent first-page truncation. 3. **`tests/unit/test_provider_integration_v2.py`** - Parameterized `test_fmp_http_failure_classification` - Two collected cases: - 401/403 → `auth_failed` - 429 → `rate_limited` - Assert sanitized stable reason codes, with no API key or raw response leakage. 4. **`tests/unit/test_provider_integration_v2.py`** - `test_historical_composite_fallback_respects_cutoff_and_mapping_currency` - Historical primary returns no eligible row. - A fallback capable only of current quotes must not satisfy the historical request. - Eligible prior-close fallback must be at/before cutoff and use confirmed mapping currency when payload currency is absent. 5. **`tests/unit/test_fx_frankfurter_primary_hotfix.py`** - `test_postfinance_apple_190_and_alphabet_96_are_both_valued_in_chf` - Seed both real synthetic ISIN mappings. - Assert exact Decimal-derived CHF values: - Apple: `190.00` - Alphabet: `96.00` - Also assert both remain present in the returned position list. ### Frontend — two test declarations 6. **Extend `frontend/src/pages/EquityPageHotfix.test.ts`** - `refreshes the complete 22-position portfolio instead of the first 10` - Assert explicit refresh uses the complete server-side batch contract and displays `22` processed. - Prevent regression to `updateEquityQuotes('auto', 10)`. 7. **Create `frontend/src/components/DetailDrawer.test.ts`** - `provides a responsive, closable detail drawer contract` - Assert closed/open rendering, mobile bottom-sheet classes, desktop/static classes, accessible name/close control, and close emission. - Ideally also assert Escape and focus restoration if those behaviors are added. ### Existing policy gates to retain unchanged - `tests/unit/test_portfolio_policy_foundation.py::test_policy_detail_returns_requested_historical_and_active_versions_read_only` - `frontend/src/components/policy/PortfolioPolicyManager.test.ts` historical read-only scenario ## CI and migration implications ### Backend count The proposed six backend cases change the collection count: - Current: **659** - Sprint 11 target: **665** Update `.github/workflows/portfolio-phase3-integration.yml`: - Job label: `Backend – 665 tests (single run)` - Count assertion: `grep -Eq '(^|[^0-9])665 passed' ...` Do not hide the new scenarios inside the existing Sprint-9 helper runner merely to preserve 659; visible collection gives clearer release evidence. ### CI quality scope The controls job’s Ruff allowlist currently omits the main Sprint 11 market surfaces. Add: - `src/jarvis_finance/api/routers/market.py` - `src/jarvis_finance/api/schemas/market.py` - `src/jarvis_finance/market_data/prices.py` - `src/jarvis_finance/services/market_service.py` - `tests/unit/test_market_quotes_detail_charts_v1.py` - `tests/unit/test_provider_integration_v2.py` - `tests/unit/test_fx_frankfurter_primary_hotfix.py` Rename the stale workflow step description from **“Empty and Sprint-5-to-41 migration gates”** to **“…Sprint-5-to-43…”**. Frontend CI already runs the complete Vitest suite, typecheck, and production build; it does not hard-pin a count. Static inventory is currently approximately **150 test declarations**, becoming **152** with the proposed additions. ### Migration **No schema migration should be required.** Pagination, error classification, historical-provider eligibility, cache-only GET behavior, and responsive UI are service/API/frontend concerns. Keep: - `MIGRATION_VERSION = 43` - `EXPECTED_SCHEMA = 43` Do not create migration 44 unless implementation introduces persisted batch cursors or durable refresh-run state, neither of which is necessary for this sprint. ## Files modified - **None** — audit was read-only.