## Verdict: **BLOCK — P1** - **P0:** 0 - **P1:** 2 - **P2:** 0 ### P1 findings 1. **Request-local cache does not preserve classification semantics for duplicate active canonical category names.** - `ClassificationLookupCache.category_rows()` returns all active categories without deterministic category-name resolution: `household_classification.py:366-371`. - Cached bank-interest resolution builds a name dictionary, selecting the last duplicate row, while the uncached SQL path selects a different row: `household_classification.py:760-787`. - Valid-schema focused probe with two active `Sonstige Einnahmen` categories returned: - uncached: `cat_other_income` - cached: `cat_duplicate_interest` - equality: `False` - `budget_categories.name` is not unique: `storage/migrations.py:544-555`. - This violates the required cache semantic parity and can change the category written at Confirm. 2. **Compact merchant matching now creates unsafe substring classifications for every token of six or more characters.** - `_rule_token_matches()` strips all separators and uses unrestricted substring containment: `household_classification.py:232-240`. - Since every public family token uses this helper at `household_classification.py:243-249`, unrelated merchant words can become automatic `proposal_ready` classifications. - Focused probes: - `Hospitality Supplies AG` → Health via `hospital` - `Sparkingshop GmbH` → Auto via `parking` - `Unfitness Coaching` → Hobbies/Selfcare via `fitness` - Compact matching should be narrowly allowlisted for known punctuation/glued-brand variants rather than applied to the entire taxonomy. ### Other reviewed gates - Conflicting confirmed history correctly outranks a stable public family: focused `Migros` conflict probe returned `conflicting_history`, `decision_needed`, no category. - Readiness maximum is consistently exactly **49** at `household_import.py:1247` and `:1270`. - Performance telemetry is added only after `preview_core` fingerprint construction and remains outside the fingerprint: `household_import.py:1295-1310`. - Preview/Confirm UI binds Confirm to the applied request and disables it for dirty drafts: `HouseholdImportsPage.vue:357-402`. - No new private runtime paths, secrets, or private merchant/rule names found in the production-source diff. ### Focused verification - Classification/cache/public-family tests: **9 passed** - Import read-only/performance tests: **2 passed** - Household UX tests: **19 passed** - `git diff --check`: passed earlier in review - No broad suite rerun, as requested. **Files created or modified:** none. **Issue encountered:** one initial focused pytest command referenced a nonexistent local `.venv`; rerun through the repository’s working Python environment succeeded.