Repository navigation
fix(db): KF-020 SELECT FOR UPDATE in invoices::update (closes #49) - #64
Merged
Merged
Conversation
Ferme la race no-op résiduelle KF-004 documentée Story 7-3 §race-condition. Sous REPEATABLE READ + plain SELECT, une tx parallèle qui commit entre BEGIN et le no-op check pouvait laisser fuiter un snapshot stale en `200 OK`. Avec FOR UPDATE le X-lock attend la fin de la tx concurrente et la lecture post-lock retourne v=N+1 → la version-check applicative déclenche le 409 attendu. Scope chirurgical : 1 ligne SQL (`invoices::update` SELECT initial), mêmes patterns que `delete()`, `validate_invoice()`, `mark_as_paid()`. Hors scope : autres entités variant A (cf. issue #49 « Hors scope »). Tests : - Nouveau `test_update_concurrent_no_op_vs_mutation_no_stale_snapshot_kf020` dans `crates/kesh-db/src/repositories/invoices.rs` : 30 itérations tokio::join (mutation A vs no-op B même expected_version=N) avec pool dédié 6 connexions. Asserts les invariants : A=Ok(v=N+1), B=Ok(v=N) ou OptimisticLockConflict, DB final cohérent. Tolère Sqlx(deadlock) rare avec trace eprintln (H-1 review remediation). - Docstring AC #29 mise à jour dans `crates/kesh-api/tests/kf004_no_op_e2e.rs` pour refléter la fermeture v0.1 (KF-021 #50 reste ouvert pour les autres entités variant A). Code review (1 passe Sonnet adversariale, contexte frais) : - 0 CRITICAL, 1 HIGH (H-1 panic test brittle), 3 MEDIUM (M-1 lock-order doc manquant, M-2 DRY pré-existant `delete`, M-3 max_connections=4 serré), 4 LOW. - Patches appliqués : H-1 (assertion relax avec trace), M-1 (docblock lock-ordering ajouté à `update`), M-3 (4 → 6 connexions), L-1/L-2 (cosmétique). - Defers : M-2 (pré-existant, hors scope), L-3 (date OK), L-4 (autre fonction). - Verdict reviewer : GO. Tests verts (release) : 39/39 kesh-db invoices, 6/6 kf004 e2e. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Item #2 du prep sprint Epic 8 (validé Guy 2026-05-01). Ferme la race no-op résiduelle KF-004 documentée Story 7-3 §race-condition.
Scope chirurgical : 1 ligne SQL dans
invoices::update(SELECT→SELECT ... FOR UPDATE), même pattern quedelete(),validate_invoice(),mark_as_paid()déjà en place dans le même fichier. Hors scope : les autres entités variant A (contacts, products, companies, accounts, users, company_invoice_settings, bank_accounts) — cf. issue #49 « Hors scope ».Race fermée
Sous REPEATABLE READ + plain SELECT, une tx parallèle qui commit entre BEGIN et le no-op check (
is_no_op_change) pouvait laisser fuiter un snapshot stale en200 OK. Avec FOR UPDATE :Tests
test_update_concurrent_no_op_vs_mutation_no_stale_snapshot_kf020danscrates/kesh-db/src/repositories/invoices.rs: 30 itérations tokio::join (mutation A vs no-op B mêmeexpected_version=N) avec pool dédié 6 connexions. Asserts les invariants : A=Ok(v=N+1), B=Ok(v=N) ou OptimisticLockConflict, DB final cohérent. Tolère Sqlx(deadlock) rare avec trace eprintln.crates/kesh-api/tests/kf004_no_op_e2e.rs.Tests verts (release) : 39/39 kesh-db invoices, 6/6 kf004 e2e.
Code review (1 passe Sonnet adversariale)
Approche option B (validée Guy) : 1 passe code review LLM différent de l'implémenteur Opus, contexte frais, sans story BMAD complète (l'issue #49 = la spec).
unwrap_or_else(panic!)masque deadlock MariaDB rareupdate()delete()SQL inlinemax_connections=4serré…vs..., phrasing)validate_invoicelock commentVerdict reviewer : GO.
Test plan
closes #49Suite — prep sprint Epic 8
Items restants :
Créer(PR docs(epic-8): create planning artifact + open CR-009/CR-010 #63 mergée)epic-8.mdkesh-importcrate design🤖 Generated with Claude Code