## Verdict **PR1 is a high-risk transactional change and must not reuse the existing group-action contract.** The base provides good preview/fingerprint, cursor-version, audit, and savepoint patterns, but the required duplicate, card-settlement, and confirmed-transaction correction workflows do not yet exist safely. ### P0 release blockers / design risks 1. **Legacy group actions mutate every open row sharing a classification.** - `preview_household_review_action()` and `confirm_household_review_action()` resolve `group_key → classification → all rows`. - The legacy `duplicate`/`ignore` branches only set status; they do not establish evidence or an original relationship. - PR1 should either retire these routes for row menus or make them reject all item-specific special actions. 2. **Current UI advertises non-executable actions.** - `HouseholdReviewPage.vue` shows Transfer/Split/Duplicate/Ignore menu entries that merely open an informational drawer. - This directly contradicts the Sprint 17B.0 requirement that offered actions be executable. 3. **Duplicate integrity is insufficient.** - `budget_transaction_candidates.duplicate_of_transaction_id` exists and is reusable. - Existing household group-confirm does not set it, validate the original, display bilateral evidence, or prove zero financial effect. - `reopen_transaction_candidate()` leaves existing linkage untouched; an audited reopen must explicitly revoke/clear the effective duplicate decision without deleting source lineage. 4. **Confirmed-category editing is currently an unsafe direct write.** - `POST /budget/transactions/{id}/update` calls `update_budget_transaction()` directly and commits. - It has no preview fingerprint, baseline/version binding, idempotent replay, transaction-type compatibility check, or protection for transfers, refunds, duplicates, card settlements, and Migros details. 5. **No durable credit-card-settlement contract exists.** - Credit-card payments are classified as special cases, but there is no complete item-specific Preview→Confirm model carrying account/card source, counterpost, purchase coverage, completeness, and `partial` status. - Neutrality must flow through the canonical financial-effects reader; merely relabeling a candidate as transfer is insufficient. 6. **Atomicity must be preserved deliberately.** - The newer household batch flow correctly uses one `BEGIN IMMEDIATE`/savepoint and same-transaction audit. - Several legacy candidate/update helpers commit internally. PR1 must not compose those helpers unchanged. 7. **Item/version binding needs strengthening.** - Existing `item_token` is `SHA256(candidate_id)[:16]`; it identifies a row but is not a keyed capability. - New previews should bind item token, candidate ID internally, candidate `updated_at`, action, target relation/category, review `data_version`, complete baseline, and contract version. - Confirm must revalidate after acquiring the write lock and return `409` on any mismatch. ## Regression matrix for Sprint 17B.0 / PR1 Recommended primary backend file: `tests/unit/test_sprint17b_review_corrections.py`; frontend file: `frontend/src/pages/HouseholdCorrections.test.ts`. | Requirement | Concrete regression | |---|---| | One-row special action | Seed two candidates with the same classification; preview/confirm token A; assert only A, its audit, review count, and version change. B remains byte-equivalent/open. | | No accidental group confirm | Call legacy group endpoints for `duplicate`, `ignore`, and card settlement; require `409/410` or prove they cannot be reached from the row UI. | | Preview read-only | Compare deterministic full synthetic DB digest, open count, and audit count before/after every new preview. | | Complete binding | Alter action, token, target category/original, baseline, candidate `updated_at`, or `data_version` between preview and confirm; each must return `409` with zero writes. | | Idempotent replay | Identical confirm returns the original decision/audit identity and performs no additional writes; altered replay conflicts. | | Atomic rollback | Inject failure after candidate/relation update but before audit; assert all candidate, transaction, relation, aggregate, and audit changes roll back, including under a caller-owned outer transaction. | | “Keine Dublette” | Possible duplicate + compatible category → exactly one productive economic event, candidate resolved, chosen category applied, one bounded audit event. | | Exclude duplicate | Require an existing eligible original; preview displays both sanitized sides and reason classes; confirm sets unique `duplicate_of`, preserves source rows, and yields zero incremental effect. | | Reject weak duplicate evidence | Similar merchant/history count without a concrete eligible original must not be confirmable. Counterprobe self-link, missing/terminal original, amount/date/source mismatch, and competing original. | | Reopen duplicate | Audited reopen restores exactly one review item, revokes effective duplicate status/link safely, changes cursor/data version, and does not create a transaction. Replay is idempotent. | | Card settlement complete | AKB debit + deterministic VISA account/counterpost + covered purchases → settlement is neutral; purchase expenses remain exactly once; income/net/budget do not change. | | Card settlement incomplete | Missing/ambiguous counterpost or incomplete purchase coverage → preview and resulting quality contract are `partial`, require explicit confirmation, and name the missing-import next step. | | No normal confirm for special cases | Duplicate, settlement, transfer, linked refund, and Migros-detail items expose only their dedicated executable capability; ordinary category dropdown/confirm is absent or disabled server-side and client-side. | | Confirmed transaction drawer | Category detail, household list, and cockpit entry open the same canonical detail contract with dates, amount/currency, masked source, semantics, status, links, and collapsed audit history. | | Category-change preview | Preview exposes old/new category and both category deltas while total financial effect remains identical; preview is read-only and fingerprint-bound. | | Category type/security | Expense→income category and income→expense category fail; inactive/missing categories fail; amount, currency, source identity, and linked semantics remain immutable. | | Special-case reclassification | Transfer/settlement/duplicate/Migros detail cannot use ordinary correction. Linked refund redirects to or changes the origin category under the dedicated relation contract. | | Aggregate invalidation | After synthetic confirm, overview/month/year/category/budget/forecast readers all reflect the new category with unchanged total income/expense/net. | | Cursor invalidation | Obtain review and transaction cursors, confirm one correction, then require old cursors to return `409`; fresh page has updated totals/version and no mixed snapshot. | | Audit privacy | Audit contains action/version, immutable internal IDs, old/new category or relation, timestamp/user, and expected/actual writes—but no raw descriptions, filenames, source references, or public token/digest prefixes. | | Responsive 820 fix | Real browser at 1440/820/390: budget status uses cards at 820, controlled title wrapping, full-page detail, zero page overflow, no colliding columns, every interactive target ≥44×44 px, keyboard/focus/escape behavior intact. | ### Existing regression evidence worth retaining - `test_review_inbox_batch_is_fingerprint_bound_stale_safe_and_idempotent` - `test_review_summary_is_canonical_filtered_and_cursor_paginated` - `test_review_expense_preview_confirm_is_bound_atomic_and_idempotent` - `test_card_settlement_three_day_edge_is_ambiguous_while_two_days_is_safe` - `test_financial_semantics_reconcile_refunds_transfers_categories_and_net` - Household frontend stale-response, cursor-restart, category compatibility, privacy, and focus-trap tests These are predecessor regressions only; they do **not** prove the new PR1 contracts. ## Migration/versioning findings - Current schema is **47**, migration name `047_household_import_v1`. - Migration history is compressed: compatibility builders run at startup, while recorded rows chiefly represent versions 1 and 47. - The recorded v47 checksum hashes the migration **name**, not executable migration logic. - If PR1 adds durable correction/settlement/decision structures, require an explicit **47→48** migration: - upgrade a real schema-47 synthetic copy; - unchanged pre-existing business digest; - `integrity_check=ok`; - empty `foreign_key_check`; - exact indexes/FKs/triggers; - second application is a no-op; - backup and independently restored-copy equality. - Update all version constants, especially `scripts/ci_portfolio_phase3_gate.py::EXPECTED_SCHEMA`. ## CI/release risks and acceptance gates ### CI gaps - Backend collection is **830**, but workflow name/floor remains **667**; deletion protection is materially stale. - Frontend has **198 tests**, but CI has no numeric floor. - `make verify` omits Ruff and the custom migration gate. - CI Ruff checks a fixed historical file list; new PR1 modules/tests will be unlinted unless explicitly added or Ruff scope is widened. - CI triggers on PR/manual dispatch only—no post-merge `push` signal. - No tracked browser/responsive suite exists; jsdom cannot verify 820 px overflow or 44 px geometry. - Restart scripts are operational helpers, not an exact-SHA deployment pipeline with backup, migration, rollback, and post-start evidence. ### Mandatory PR1 gates 1. New focused backend contract/security/rollback tests. 2. New frontend executable-action, stale-preview, special-case, and correction tests. 3. Full backend suite with floor raised to at least the frozen **830** baseline plus new tests. 4. Full frontend suite with floor frozen at **198** plus new tests. 5. Explicit schema 47→48 copy/idempotency/digest/FK gate if schema changes. 6. API write-security tests for every new route, including disabled/remote/local/test modes. 7. Typecheck, production build, full changed-surface Ruff, compileall, Git-safety, candidate/untracked scan, and `git diff --check`. 8. Independent final-HEAD P0/P1/P2 review. 9. Real browser matrix at 1440/820/390; fail on overflow, console/page errors, failed requests, sub-44 px controls, inaccessible dialog/focus behavior. 10. Deployment only from verified merged `origin/main` SHA, after private backup/restore and integrity/FK checks. 11. Production UAT remains preview-only: Europa Park, VISECA, and reclassification previews bracketed by complete DB/table digests and strict no-write sentinels. ## Executed evidence - Backend collection: **830 tests collected**. - Focused synthetic backend/security set: **8 passed**. - Frontend full suite: **198 passed across 49 files**. - Focused Household frontend/API suite: **33 passed**. - Existing migration gate: **PASS**, schema 40→47, integrity OK, digest stable, second run no-op. - Final repository state: exact SHA `785e18b20219b4fe734576a8126d23c453050ce9`, clean, zero untracked files, `git diff --check` clean. **Files modified:** none. A transient generated `uv.lock` was removed; all test/migration artifacts remained under `/tmp`. **Issues encountered:** target worktree lacked local Python/frontend dependencies, so read-only verification used dependency environments from the sibling exact-source Sprint 17A worktree while loading code/tests from the requested PR1 worktree. No production database or private financial source was opened or changed.