## P1 findings 1. **Sensitive source/file/logical hashes are persisted unencrypted and truncated derivatives are exposed by Preview.** - `src/jarvis_finance/storage/migrations.py:2332-2383` stores `source_reference_hash`, `file_fingerprint`, `source_row_fingerprint`, and `logical_fingerprint` as plaintext columns. - `src/jarvis_finance/storage/migrations.py:2397-2402` additionally persists source/logical hashes on ordinary budget candidates. - `src/jarvis_finance/services/household_import.py:417-418,488-495,531-556` derives public `file_token`/`row_token` values directly from those hashes, enabling deterministic cross-preview correlation. - **Probe:** confirmed a synthetic batch and queried the database; full 64-character hashes were present in `household_account_source_mappings`, `household_import_batches`, `household_import_files`, `household_import_items`, and `budget_transaction_candidates`. - This violates the stated boundary prohibiting account-reference/source/file/logical hashes outside encrypted raw storage. 2. **The new duplicate engine does not reconcile against legacy candidates or ledger transactions.** - `src/jarvis_finance/services/household_import.py:319-323` builds duplicate sets exclusively from Sprint-15 `household_import_files/items`; existing legacy `budget_transaction_candidates` and `budget_transactions` are ignored. - `src/jarvis_finance/services/household_import.py:433-447` consequently classifies an exact legacy overlap as new unless it already has Sprint-15 lineage. - **Probe:** inserted a legacy Raiffeisen candidate matching the incoming account, date, signed amount, currency, and description exactly. Preview returned: `{'disposition': 'candidate', 'classification': 'expense_candidate', 'confirmable': True}`. - A later review-confirm can therefore create a second productive ledger expense for an already imported legacy booking. 3. **Migros cross-batch linking fails once the linked candidate has become a productive transaction.** - `src/jarvis_finance/services/household_import.py:326-347` returns both the original candidate and its confirmed transaction as independent Migros money movements; it does not collapse them by `source_candidate_id`/lineage or exclude consumed candidates. - `src/jarvis_finance/services/household_import.py:469-476` then sees two exact matches, refuses to choose one, and marks the receipt `review`. - **Probe:** imported a CHF Migros card candidate, confirmed it through the review action as an expense, then previewed the matching receipt in a later batch. Result: `{'receipt_status': 'review', 'candidate_matches': 1, 'transaction_matches': 1}`. - No second expense was created, but the required cross-batch enrichment link was lost. ## Verified gates - Focused household/schema tests: **25 passed**. - Portfolio/reconciliation/performance regression tests: **42 passed**. - Disposable real schema-46 → 47 migration probe: `integrity_check=ok`, protected-table digests unchanged, second migration application was a no-op. - Preview read-only, stale-baseline rejection, mapping mismatch, transfer neutrality/four-class behavior, pending/final collapse, review Preview→Confirm, atomic rollback, and ordinary repeated-confirm idempotency passed focused tests. - `git diff --check` passed. - **Files modified:** none. ## Verdict **NOT APPROVED — 3 P1 blockers, no P0 found.**