fix(balance): scope v16 abort guard to convertible accounts (#228) #229

Open
maximus wants to merge 1 commit from issue-228-v16-guard-convertible-scope into main
Owner

Fixes #228

Bug

La garde d'abort de la migration v16 (_v16_guard) clé sur a.symbol IS NOT NULL au lieu de la convertibilité. Un compte sous catégorie simple portant un symbole résiduel (recatégorisation priced→simple ; AccountForm rend le champ symbole inconditionnellement) a des lignes quantity NULL par construction → elles satisfont le prédicat de garde → la garde insère 0 → CHECK(ok = 1) échoue → toute la migration v16 abort → l'app ne démarre plus pour ce profil (pas de perte de données, blocage de lancement).

Fix

JOIN balance_categories + AND c.asset_type IS NOT NULL dans la sous-requête de garde, dans les 3 copies : Migration { version: 16 }, const V16_SQL, et V16_CORRUPT (test d'abort injecté, gardé statement-équivalent — son abort reste piloté par la ligne convertible). La logique de conversion (steps 1-3) était déjà correcte.

Test

Nouveau migration_v16_leaves_simple_account_with_residual_symbol_intact : seed compte simple + symbole résiduel + ligne qty-NULL → v16 applique sans abort, compte intact (aucun security/holding, qty/value préservés, kind reste simple, detailed_since NULL). Échoue avant le fix (CHECK constraint failed: ok = 1), passe après. Suite complète cargo test : 98 passed, 0 failed.

Note — immutabilité des migrations

Modifier v16 en place est sûr et conforme à l'esprit de la règle : v16 n'a jamais été dans une release taggée (v0.9.1 s'arrêtait à v13 ; v14-v16 sont sous [Unreleased], mergées via #219-#227). Aucun profil persistant en prod n'a son checksum. Les profils dev ayant déjà appliqué v16 verront un mismatch checksum SHA-384 → reset du profil dev attendu.

Critères d'acceptation

  • Garde v16 scopée sur c.asset_type IS NOT NULL (Migration v16 + V16_SQL)
  • Test : compte simple + symbole résiduel + ligne qty-NULL → v16 sans abort, compte intact
  • cargo test vert (98)
Fixes #228 ## Bug La garde d'abort de la migration v16 (`_v16_guard`) clé sur `a.symbol IS NOT NULL` au lieu de la convertibilité. Un compte sous catégorie **simple** portant un symbole résiduel (recatégorisation priced→simple ; AccountForm rend le champ symbole inconditionnellement) a des lignes `quantity NULL` par construction → elles satisfont le prédicat de garde → la garde insère 0 → `CHECK(ok = 1)` échoue → **toute la migration v16 abort → l'app ne démarre plus** pour ce profil (pas de perte de données, blocage de lancement). ## Fix JOIN `balance_categories` + `AND c.asset_type IS NOT NULL` dans la sous-requête de garde, dans les **3 copies** : `Migration { version: 16 }`, const `V16_SQL`, et `V16_CORRUPT` (test d'abort injecté, gardé statement-équivalent — son abort reste piloté par la ligne convertible). La logique de conversion (steps 1-3) était déjà correcte. ## Test Nouveau `migration_v16_leaves_simple_account_with_residual_symbol_intact` : seed compte simple + symbole résiduel + ligne qty-NULL → v16 applique sans abort, compte intact (aucun security/holding, qty/value préservés, `kind` reste `simple`, `detailed_since` NULL). Échoue avant le fix (`CHECK constraint failed: ok = 1`), passe après. Suite complète `cargo test` : **98 passed, 0 failed**. ## Note — immutabilité des migrations Modifier v16 en place est sûr et conforme à l'esprit de la règle : v16 n'a jamais été dans une release taggée (v0.9.1 s'arrêtait à v13 ; v14-v16 sont sous [Unreleased], mergées via #219-#227). Aucun profil persistant en prod n'a son checksum. Les profils **dev** ayant déjà appliqué v16 verront un mismatch checksum SHA-384 → reset du profil dev attendu. ## Critères d'acceptation - [x] Garde v16 scopée sur `c.asset_type IS NOT NULL` (Migration v16 + V16_SQL) - [x] Test : compte simple + symbole résiduel + ligne qty-NULL → v16 sans abort, compte intact - [x] cargo test vert (98)
maximus added 1 commit 2026-06-10 01:32:37 +00:00
fix(balance): scope v16 abort guard to convertible accounts (#228)
All checks were successful
PR Check / rust (pull_request) Successful in 23m8s
PR Check / frontend (pull_request) Successful in 2m29s
cf3e06c910
The v16 belt-and-suspenders abort guard keyed on `a.symbol IS NOT NULL`
instead of convertibility. A simple-category account carrying a residual
symbol (left by a priced→simple recategorization; AccountForm renders the
symbol field unconditionally) has quantity-NULL lines by construction;
they satisfied the over-broad predicate, so the guard inserted 0, the
CHECK(ok = 1) failed, and the whole v16 migration aborted — the app no
longer started for that profile.

JOIN balance_categories and require `c.asset_type IS NOT NULL` in the guard
subquery, in all three copies (Migration v16, V16_SQL mirror, and the
V16_CORRUPT injected-failure test, kept statement-equivalent). Add a
regression test seeding a simple account with a residual symbol plus a
quantity-NULL line: v16 now applies without abort and leaves the account
intact (not converted, no security/holding, qty/value preserved).

Modifying v16 in place is safe: it has never shipped in a tagged release
(v0.9.1 stopped at v13; v14-v16 are unreleased), so no persisted profile
carries its checksum yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maximus added the
status:review
label 2026-06-10 01:33:04 +00:00
All checks were successful
PR Check / rust (pull_request) Successful in 23m8s
PR Check / frontend (pull_request) Successful in 2m29s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin issue-228-v16-guard-convertible-scope:issue-228-v16-guard-convertible-scope
git checkout issue-228-v16-guard-convertible-scope
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#229
No description provided.