- **Tree verified:** `HEAD` is exactly `17ad9ad9eba002b74ef18664fa1a008d926bf5fd`; worktree remained clean and read-only. ### Delayed findings 1. **Same-day canonical projection conflicts — already fixed/stale** - `preview_postfinance_import()` checks: - `positions_snapshot` by depot account and snapshot date: `postfinance_service.py:322-325` - canonical `cash_balances` by cash account, balance date, and `source_type='postfinance_official_import'`: `postfinance_service.py:326-330` - either collision sets `canonical_projection_date_conflict`: `postfinance_service.py:331-332` - Existing regression covers the positions path: `tests/test_postfinance_sprint13.py:400-412`. - **Coverage gap:** no checked-in test independently seeds a same-day canonical cash row. A read-only in-memory probe confirmed the cash path returns `conflict=True` with the canonical projection conflict reason. 2. **Canonical UPDATE/DELETE protection — already fixed/stale** - Conditional SQLite triggers protect official rows: - `positions_snapshot`: `postfinance_schema.py:202-207` - `cash_balances`: `postfinance_schema.py:208-213` - `transactions`: `postfinance_schema.py:214-219` - Regression iterates over all three tables and verifies both UPDATE and DELETE rejection: `tests/test_postfinance_sprint13.py:454-463`. - Focused test passed. 3. **`/api/postfinance/summary` identifier leakage — already fixed/stale** - Route directly returns the service’s projected summary: `api/routers/postfinance.py:38-40`. - Service uses explicit projections: - latest snapshot: `postfinance_service.py:866-878`, strips `snapshot_id` at `937` - positions: explicit safe dictionary at `879-905`; query selects names/ticker but not instrument IDs or ISINs - cash: `906-919` - imports: explicit columns without batch/account/snapshot/document identifiers at `926-936` - Runtime synthetic probe found only the five top-level keys `latest_snapshot`, `positions`, `cash`, `event_counts`, and `imports`, with no forbidden identifier keys. - Existing assertions cover absence of synthetic account/ISIN values plus snapshot, instrument, and batch IDs: `tests/test_postfinance_sprint13.py:259-264`. - **Coverage gap:** this is service-level coverage only; there is no direct `GET /api/postfinance/summary` contract test. The response schema is also permissive (`dict[str, Any]`) at `api/schemas/postfinance.py:67-72` and is not attached as a route response model. ### Global aggregation regression - **Reproduced:** `portfolio_service._latest_truewealth_value()` globally orders all matching account snapshots and applies one `LIMIT 1`, rather than selecting the latest value per active account and summing them: `portfolio_service.py:13-29`. - `get_overview()` then treats that single value as the entire TrueWealth allocation and total component: `portfolio_service.py:50-66`. - In-memory two-account probe: expected global TrueWealth value `300.00`; actual `truewealth_value_chf` and total were `200.00`. - Current test coverage uses only one managed account: `tests/unit/test_cash_truewealth_management.py:121-136`. Sprint-13 tests never call `get_overview()`. - Missing regression should cover multiple active managed accounts, latest-per-account selection, inactive/older snapshots, and final portfolio total without double counting. ### Verification - `tests/test_postfinance_sprint13.py`: **6 passed** - Focused conflict/immutability tests: **2 passed** - Cash-conflict synthetic probe: **passed** - Global aggregation counterexample: **reproduced** - No files or databases were modified. Initial local pytest lacked `pypdf`; verification succeeded using an isolated `uv` environment.