Tiše zahozené porušení constraintu u error-as-value DB klientů
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Webhook platební brány při chargebacku označuje objednávku stavem disputed. Handler doběhne, vrátí 200, activity log dostane záznam — vše vypadá zdravě. Objednávka ale v DB zůstane paid a může se normálně expedovat. Nikde žádná chyba.
Root cause
Sekce “Root cause”Dvě věci se potkaly:
- DB check constraint neznal hodnotu, kterou kód zapisoval. Constraint vznikl dřív, hodnota
disputedse do kódu přidala později a migrace se nedopsala (schéma driftovalo mimo repo). - Klient vrací chyby jako hodnotu, ne výjimku. PostgREST/supabase-js vzor:
await db.from('orders') .update({ payment_status: 'disputed' }) .eq('order_number', orderNumber);// ŽÁDNÁ kontrola { error } → constraint violation zmizí beze stopyawait proběhne v pohodě, { error } v návratové hodnotě nikdo nečte, handler pokračuje. Na rozdíl od ORM s výjimkami tu neexistuje ŽÁDNÝ signál — ani crash, ani log.
Fix
Sekce “Fix”const { error: updErr } = await db.from('orders') .update({ payment_status: 'disputed' }) .eq('order_number', orderNumber);if (updErr) console.error('failed to flag order disputed:', updErr.message);// + rozšířit constraint o chybějící hodnotu (aditivní ALTER, se zálohou)// + kritická notifikace adminovi nezávisle na DB zápisu (chargeback = člověk hned)Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection: grep zápisů bez destrukturace erroru:
await .*\.(update|insert|delete)\(na řádcích, kde výsledek není přiřazen, nebo přiřazení nedestrukturujeerror. Zkřížit hodnoty stavových sloupců v kódu (.update({ status:/payment_status:) protipg_get_constraintdefživé DB. - Anti-pattern:
await db.from(...).update(...)jako statement (výsledek zahozen) v kritické cestě; enum/status hodnoty definované jen v kódu bez odpovídajícího constraintu v migracích. - Lepší přístup: tenký wrapper
mustUpdate()který na{error}hází; CI check porovnávající status hodnoty v kódu se schema dumpem; u stavů s penězi navíc out-of-band alert (e-mail), který nezávisí na úspěchu DB zápisu.
Sister bugs / společníci
Sekce “Sister bugs / společníci”Stejný vzor zabíjí: audit logy (insert s neexistujícím sloupcem = tichý no-op), usage-checky před mazáním (select na neexistující tabulku → “0 výskytů” → smaž), backfilly. Viz též [[rate-limit-guard-filtered-by-guessed-value]] — jiný druh “kód vypadá, že chrání, ale nikdy se nespustí”.