Skip to content

Story 7-3: KF-004 — update() no-op short-circuit (closes #4) - #51

Merged
guycorbaz merged 3 commits into
mainfrom
story/7-3-kf-004-no-op-shortcircuit
Apr 29, 2026
Merged

guycorbaz merged 3 commits into
mainfrom
story/7-3-kf-004-no-op-shortcircuit

Conversation

@guycorbaz

Copy link
Copy Markdown
Owner

Summary

  • KF-004 closed : 9 repositories patchés avec helper is_no_op_change + court-circuit pattern uniforme — update() no-op ne bump plus version, n'écrit plus dans audit_log, ne touche plus updated_at.
  • 2 passes code review (cycle Sonnet → Haiku, contextes frais orthogonaux) → critère d'arrêt CLAUDE.md atteint (0 findings > LOW).
  • 6 tests E2E HTTP (kf004_no_op_e2e.rs) + 18 tests sqlx ajoutés (no-op + partial change + reorder + edge cases FY closed/account inactif).
  • Refactoring Variant C → A pour companies et users (ajout SELECT before).
  • Doc pattern : docs/optimistic-locking-patterns.md créé.

Commits inclus

  • 646658c — Story 7-3: KF-004 — update() no-op short-circuit (closes [KF-004] update() bump version même sur no-op #4) — implémentation T1-T13
  • 647f0a2 — Story 7-3: code review Pass 1 — Sonnet×3, 1H+5M+9L → 5 patches (2M+3L), 12 rejected
  • c37d791 — Story 7-3: code review Pass 2 + closure — Haiku×3, 26→0 patches+26 rejected, status done

Code Review (2 passes)

Pass 1 — Sonnet 4.6 × 3 reviewers parallèles

17 findings bruts → triage : 5 patches (2 MEDIUM + 3 LOW), 12 rejected.

ID Sévérité Sujet
CR-M1 MEDIUM AC #21 absent → ajout test E2E put_invoice_no_op_returns_200_unchanged_version
CR-M2 MEDIUM Reclassement AC #29 → smoke test séquentiel + issue follow-up KF-021 #50 pour test deterministe race REPEATABLE READ
CR-L1 LOW Typologie Variant A/C corrigée dans optimistic-locking-patterns.md
CR-L2 LOW Assertion updatedAt user B dans test concurrent (les 2 invariants AC #22)
CR-L3 LOW updatedAt string compare — accepté v0.1 (pattern uniforme codebase)

Rejet HIGH (CR-H1) : régression sémantique NotFound→OptimisticLockConflict dans companies/users branche défensive rows_affected==0 = alignement intentionnel sur contacts.rs Variant A par spec T7.2 (pattern codebase-wide cohérent).

Pass 2 — Haiku 4.5 × 3 reviewers parallèles

26 findings bruts → triage : 0 patch, 26 rejected (tous faux positifs vérifiés ou doublons P1).

  • Acceptance Auditor Haiku : 0 findings, conformité spec confirmée AC-by-AC.
  • Faux positifs vérifiés empiriquement :
  • Doublons Pass 1 : race condition v0.1 documentée + alignement Variant A.

Critère d'arrêt CLAUDE.md atteint : 0 findings > LOW, cycle LLM Sonnet → Haiku, convergence orthogonale.

Issues follow-up

Test plan

  • cargo test -p kesh-db --tests -- --test-threads=1 → 216 tests OK (198 pré-existants + 18 nouveaux)
  • cargo test -p kesh-api --test kf004_no_op_e2e → 6/6 OK
  • cargo test -p kesh-api → 194 E2E OK (tous E2E pré-existants verts)
  • cargo clippy --all-targets -- -D warnings → green
  • cargo fmt --all --check → clean
  • CI pipeline (3 status checks) — sera validé sur la PR
  • Review humaine

Documentation modifiée

  • docs/known-failures.md — KF-004 status open → closed (Story 7-3, 2026-04-29)
  • docs/optimistic-locking-patterns.md — créé (récap pattern, 9 cibles, race v0.1, mitigation Epic 8)

🤖 Generated with Claude Code

guycorbaz and others added 3 commits April 29, 2026 14:59
Implémentation pattern compare-then-skip dans 9 repositories `update()`
user-form du crate kesh-db. Helper privé `is_no_op_change(before, changes)`
co-localisé qui compare les champs métier ; si aucun ne diffère, la fonction
court-circuite la mutation (`tx.rollback() + Ok(before)`) après la
version-check applicative et tous les guards (status, fiscal_year, accounts
actifs).

Repositories patchés : contacts, products, invoices, accounts, bank_accounts
(branche Some), companies, company_invoice_settings, journal_entries, users.
Refactoring variant C → A pour companies::update et users::update_role_and_active
(ajout SELECT before + version-check applicative avant le no-op check).

Tests : 18 tests sqlx ajoutés (no-op + partial change + reorder + edge cases
FY clos / account inactif), 5 tests E2E HTTP dans kf004_no_op_e2e.rs (PUT
no-op transparent, concurrence 200/200, vrai conflit 409, race séquentielle).
216 tests kesh-db serial + 194 tests kesh-api E2E verts, 0 régression.

Race REPEATABLE READ + plain SELECT documentée et acceptée v0.1 (variants A
et C, sauf bank_accounts/journal_entries qui ont déjà SELECT FOR UPDATE).
Issue follow-up #49 [KF-020] créée pour invoices::update SELECT FOR UPDATE
(Epic 8 prerequisite). Comportement observable testé en mode séquentiel
(régression detector).

Doc : docs/optimistic-locking-patterns.md créé. KF-004 fermée dans
docs/known-failures.md (archive).

Pas de modifications API, frontend, migration DB, i18n, CI ou README
(refactor interne, cf. CLAUDE.md règle Sync README).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…), 12 rejected

3 reviewers Sonnet 4.6 parallèles (Blind Hunter + Edge Case Hunter + Acceptance
Auditor) sur commit 646658c. 17 findings bruts triés → 5 patches appliqués + 12
rejets (faux positifs structurels ou pré-existants project-wide).

Patches MEDIUM :
- CR-M1 : ajout test E2E `put_invoice_no_op_returns_200_unchanged_version`
  (AC #21 manquant, le module-doc le revendiquait sans implémentation).
- CR-M2 : reclassement AC #29 — le test séquentiel actuel ne peut servir de
  régression detector pour la migration SELECT FOR UPDATE. Issue
  [KF-021 #50](#50) créée pour le
  test deterministe via tokio::join. Spec amendée + doc-comment du test
  réécrit pour expliciter le smoke test séquentiel v0.1.

Patches LOW :
- CR-L1 : optimistic-locking-patterns.md — typologie Variant A/C corrigée
  (companies/users sont Variant C, journal_entries est Variant A protégé).
- CR-L2 : assertion `updatedAt` ajoutée pour user B dans le test concurrent
  (les 2 invariants observables AC #22 — version ET updatedAt).
- CR-L3 : `updatedAt` comparé en string brute — accepté v0.1 (pattern
  uniforme codebase, sérialisation figée).

Reject HIGH (CR-H1) : régression NotFound→OptimisticLockConflict dans
companies/users branche défensive `rows_affected==0` = alignement
intentionnel sur contacts.rs Variant A (spec T7.2 explicite, codebase-wide).

Tests : 6/6 kf004_no_op_e2e green (vs 5/5 avant patch), clippy clean,
fmt clean.

Critère d'arrêt CLAUDE.md à confirmer Pass 2 (LLM ≠ Sonnet, contexte frais).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…jected, status done

Pass 2 lancée immédiatement après commit 647f0a2 (Pass 1 patches). Cycle
CLAUDE.md respecté : Sonnet (P1) → Haiku (P2). Trois reviewers Haiku 4.5
parallèles, contextes frais orthogonaux à la session principale + à Pass 1.

Findings remontés (26 bruts) :
- Blind Hunter Haiku : 0C/3H/5M/5L = 13 findings
- Edge Case Hunter Haiku : 0C/4H/6M/4L = 14 findings
- Acceptance Auditor Haiku : 0 findings — clean review, conformité spec
  confirmée AC-by-AC (AC #1-#29 + #28-bis), patches Pass 1 (#21, #22, #29)
  vérifiés.

Triage Pass 2 : 0 patches, 26 rejected — tous faux positifs vérifiés
empiriquement ou doublons P1 :

- Decimal scale-sensitive (4 findings) → vérifié `cargo run` :
  rust_decimal::Decimal::eq est scale-insensitive
  (`dec!(150.00) == dec!(150.0000)` retourne true).
- journal_entries `before_lines` ordre (2 findings) → vérifié grep :
  SELECT l.649 → no-op check l.669 → DELETE l.678. Correct.
- Line ordering (3 findings) → vérifié : `zip().all()` est order-sensitive,
  AC #7 valide ce comportement (test reorder bumps version).
- Race condition v0.1 (5 findings) → doublons P1 décision documentée
  §race-condition + commentaires inline + issue follow-up #49.
- Régression NotFound→OptimisticLockConflict (3 findings) → doublons P1
  alignement intentionnel sur contacts.rs Variant A par spec T7.2.
- Spéculatif (5 findings) → enum future variants, Unicode IBAN, ContactType
  case drift — pas de repro concrète, hors scope KF-004.
- Whitespace handler-side (2 findings) → spec §reportés-v0.2 explicite.
- Hors scope (2 findings) → IDOR pré-existant (idor_multi_tenant_e2e.rs
  dédié), code dup YAGNI explicite.

Critère d'arrêt CLAUDE.md atteint :
- 0 findings > LOW restants après triage Pass 2.
- 2 LLMs différents (Sonnet P1 + Haiku P2), contextes frais.
- Cycle Opus(orchestrateur) → Sonnet → Haiku respecté.
- Convergence orthogonale : Acceptance Auditor 0 findings.
- Aucun reclassement en dette technique requis (toutes dettes déjà tracées
  via #49 et #50 en Pass 1).

Sprint status : 7-3-kf-004-version-bump-optimization `review` → `done`.
Story spec : Status `review` → `done`. Change Log Pass 2 ajouté.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@guycorbaz
guycorbaz merged commit 7529204 into main Apr 29, 2026
3 checks passed
@guycorbaz
guycorbaz deleted the story/7-3-kf-004-no-op-shortcircuit branch April 29, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[KF-004] update() bump version même sur no-op

1 participant