## Verdict: **BLOCK / not release-ready** Snapshot reviewed: branch `Sprint16/smart-household-inbox-real-data-onboarding-v2`, base `852aac4a8060d2dfec716fc630560a11d297abde`, including four untracked candidate files. ### P0 — release blockers - **Raiffeisen parser can silently discard legitimate transactions.** `household_source_analysis.py:108-126` automatically deduplicates rows sharing account/date/amount/value-date when description-token similarity is ≥0.85. A synthetic pair `COOP BASEL 1` / `COOP BASEL 2` was reduced from two records to one. This happens before candidate persistence and audit, creating an unrecoverable data-loss risk. Similar rows must become review candidates, not be suppressed. - **Source mappings do not enforce account semantics.** `configure_source_mapping()` accepts any active linked account. A synthetic probe successfully mapped `visa_credit_card` to a `cash` account. This violates the documented onboarding invariant that VISA must map to a credit-card liability and could invert or misstate household assets and income/expenses. Add server-side source/account-type validation and negative tests. - **A transaction-specific real-data exception is committed as product logic.** `household_classification.py:131-145` hard-codes provider, exact date, exact amount, and merchant tokens for the Swisslos/TWINT case; the golden test repeats them. This contradicts the inventory’s claim that production-specific corrections are not hard-coded and exposes transaction-level detail in Git. Represent the decision through an audited exception/rule contract using synthetic test values. ### P1 - **Authorized account onboarding has no release artifact.** The diff adds multi-mapping support but no audited/idempotent account-creation workflow or copy-first onboarding runbook for the new Raiffeisen cash account and VISA liability account. Mapping audit does not audit creation of the canonical and budget accounts. Real onboarding therefore depends on undocumented/manual database changes. - **Schema-47 copy evidence is asserted, not reproducible from the candidate.** The no-DDL/no-schema-48 decision itself is technically sound: all used relations and columns already exist in schema 47, and fresh schema application was idempotent with `integrity_check=ok` and empty `foreign_key_check`. However, there is no schema-47 copy-upgrade/digest/restore test or sanitized release-evidence artifact backing the production-copy claim. - **Deploy toolchain compatibility is insufficiently pinned.** The lockfile introduces dependencies requiring Node `^20.19 || ^22.13 || >=23.5`, while CI pins only Node major `22`, `package.json` uses `latest`, and no deployment-runtime preflight is present. `npm ci` and build passed locally on Node 24, but that does not prove compatibility with the actual deployment builder. ### P2 - **Lockfile hardening is broader and less reproducible than described.** The package-lock-only change is 160 additions/106 deletions and includes alpha `@emnapi/*` plus broad Rolldown/platform-binding churn. Production audit is clean, but six high-severity dev-tool findings remain. Pin direct versions in `package.json` rather than relying on `latest`, and document the reviewed transitive delta. - Missing focused coverage for dedup-collision preservation, invalid source/account-type mappings, caller-owned transaction rollback, and a real schema-47-copy no-change gate. ### Evidence run - Household predecessor + Sprint 16 tests: **49 passed** - Frontend focused tests: **10 passed** - Frontend typecheck: **passed** - Production build with Vite 8.0.16: **passed**; one existing >500 kB chunk warning - `npm ci --ignore-scripts`: **passed** - `npm audit --omit=dev`: **0 findings** - Full npm audit: **6 high, dev-only** - `git diff --check`: **passed** - Schema probe: version **47**, second application idempotent, integrity **ok**, FK check empty No source files were edited or committed, and no real financial files or production database were accessed. Build/install created only ignored local artifacts (`node_modules`/`dist`); tracked status remained unchanged.