Skip to content

fix(core): money & inventory integrity hardening — server-authoritative pricing, atomic writes, ledger discipline - #90

Merged
bighadj22 merged 1 commit into
mainfrom
fix/money-inventory-integrity
Sep 6, 2026
Merged

fix(core): money & inventory integrity hardening — server-authoritative pricing, atomic writes, ledger discipline#90
bighadj22 merged 1 commit into
mainfrom
fix/money-inventory-integrity

Conversation

@bighadj22

Copy link
Copy Markdown
Owner

Summary

A probe-first audit of every money and inventory path in the platform. Each issue was proven red on real D1 (Miniflare + real migrations, no mocks for the flows) before fixing, and each fix is locked by a regression test at the same seam.

Fixed issues

🔴 Storefront pricing & delivery

Issue Proof Fix
Client-supplied price forging — a shopper could buy a 50 000 DZD product at 1 DZD; the forged price flowed into price, codAmount, customer totalSpent, driver pendingCash forged 1 DZD order persisted on real D1 createStoreOrder resolves prices server-side from the catalog (product row / variant rows); order price = true Σ line totals; pricePerUnit is display-only
Silent free shipping — orders to wilayas without a delivery rule (or a disabled delivery type) charged 0 DZD getDeliveryFee returned 0 for uncovered wilaya returns null → handler refuses with DELIVERY_NOT_AVAILABLE (422) before creating the customer row

🔴 Orders / stock

Issue Proof Fix
Oversell floor — qty 5 with stock 2 silently sold 2, order said 5 ledger logged -2 vs qty-5 line guarded subselect ledger writes inside the atomic batch → whole order rolls back on insufficient stock
Concurrency race — two orders both read qty=1 ledger logged -2 against 1 unit, inventory 0 guarded inventory >= qty WHERE → exactly one order commits (proven with racing pair)
Delete left gutted orders — order delete failed on company_api_logs/webhook_events FKs after committing partial state FK failure with customer stats already decremented, phantom restock explicit cleanup of FK-blocked children + one atomic batch
Cancel-then-delete double-decrement of customer totalSpent 5000 → 3000 instead of 5000 deleteOrder skips the spend rollback for orders whose spend cancel/return already rolled back
Deleting a delivered order left phantom driver cash pendingCash/totalEarnings/totalDelivered stayed inflated forever driver credit reversed for unsettled delivered orders; settled ones keep counters (payment history is append-only truth)
Stale codAmount after carrier amount update carrier 12 600 vs DB 9 600 syncOrderAfterCarrierUpdate recomputes codAmount = price + deliveryFee

🟠 Drivers & stock ledger

Issue Proof Fix
Non-atomic driver settlement — fault between insert/link/counters left settled orders + inflated pendingCash, retry blocked by settled guard fault-injection probe createDriverPayment = one db.batch (payment + links + counters)
Invisible driver cash drift (no reconciliation existed) injected drift undetectable getDriverById reports cashReconciliation (pendingCash vs real unsettled-orders sum); dashboard DriverDetail shows a warning alert when drift ≠ 0 (ar/en/fr)
Catalog inventory edits bypassed the stock ledger variant 10→15 with zero movement rows updateProduct/updateVariant log ADJUSTMENT_* movements with true deltas (atomic); createVariant logs opening stock; deleteVariant follows the FK cascade so Σ(movements) stays reconciled
adjustStock race + lying labels concurrent adjusts lost deltas; ADJUSTMENT_ADD accepted negative delta sign-coherence validation + guarded atomic batch (movement + inventory + delta >= 0 WHERE)

Errors

  • Unknown errors now return "An unexpected error occurred" + requestId in context for log correlation. The [variable]-redaction message soup is gone (that was this bug's original symptom report).

Test plan

  • New real-D1 suites: money-integrity-probes (6), orders.delete-e2e (2), orders.stock-lifecycle-e2e (14), flagged-fixes (18) — includes fault-injection atomicity probes and racing-order probes
  • cod-server: 117 files / 1705 tests green, tsc --noEmit clean
  • cod-client-astro: typecheck clean, 142 tests green (incl. three-locale i18n guard for the new drift-warning keys)
  • E2E migration application batched through d1.batch — fixes the Miniflare loopback-port exhaustion that flaked the full suite
  • Deployed and smoke-verified on the production Worker before opening this PR

Notes

  • Dashboard-facing domain docs (store/CONTEXT.md, stock/CONTEXT.md) updated: Client-Supplied Price → Server-Authoritative Pricing; delivery availability is server-enforced; catalog-edit ledger discipline.
  • Known cosmetic follow-up (not money): the storefront rates map still displays fees for disabled delivery types — the server refuses those orders, but the customer sees the fee before rejection. Needs a small theme01 change.

Every fix in this change was first proven red on real D1 (miniflare +
real migrations, no mocks for the flows), then fixed and locked with a
regression test at the same seam.

Storefront / pricing
- Server-authoritative pricing: createStoreOrder resolves unit prices
  from the catalog (product row / variant rows) instead of trusting the
  client-sent pricePerUnit; order price = true sum of line totals. A
  forged 1 DZD price no longer propagates into codAmount, customer
  totalSpent, or driver pendingCash.
- Delivery availability is enforced: a wilaya with no rule under the
  default profile (or a disabled delivery type) refuses the order with
  DELIVERY_NOT_AVAILABLE before the customer row is created, instead of
  silently charging 0.

Orders / stock
- createOrder deducts stock via guarded subselect ledger writes inside
  the atomic batch: insufficient stock rolls back the whole order (no
  silent floor-at-zero), and concurrent orders can no longer double-sell
  the last unit (proven: two racing qty-1 orders → exactly one commits).
- deleteOrder reverses driver credit (totalDelivered, totalEarnings,
  pendingCash) for unsettled delivered orders; settled orders keep
  counters so append-only payment history stays reconciled.
- deleteOrder skips the totalSpent decrement when cancel/return already
  rolled the spend back (cancel + delete no longer double-decrements).
- syncOrderAfterCarrierUpdate recomputes codAmount = price + deliveryFee
  so settlement follows carrier amount updates.

Drivers
- createDriverPayment settles in one atomic batch (payment row, order
  links, driver counters) — a mid-sequence failure can no longer leave
  settled orders with inflated pendingCash and no retry path.
- getDriverById now reports cashReconciliation: pendingCash vs the real
  sum of delivered-unsettled orders, with drift surfaced in the
  dashboard DriverDetail (warning alert, ar/en/fr).

Stock / ledger discipline
- Catalog inventory edits write movements: updateProduct and
  updateVariant log ADJUSTMENT_ADD/REMOVE with true deltas, batched with
  the write; createVariant logs opening stock; deleteVariant relies on
  the variant FK cascade so the ledger sum stays reconciled.
- adjustStock validates movement-type/delta sign coherence, keeps the
  friendly INSUFFICIENT_STOCK 422, and applies the change as a guarded
  atomic batch (movement + inventory update) — no lost deltas, no
  ledger divergence.

Errors
- Unknown errors return a clean envelope: "An unexpected error
  occurred" + requestId in context for log correlation. The [variable]
  redaction soup is gone.

Tests
- New real-D1 suites: money-integrity-probes, orders.delete-e2e,
  orders.stock-lifecycle-e2e, flagged-fixes (39 tests over the seams
  above, including fault-injection atomicity probes).
- All e2e files batch migrations through d1.batch — cuts the Miniflare
  proxy fetch storm that exhausted loopback ports and flaked the suite.

Verified: cod-server 117 files / 1705 tests green, typecheck clean;
cod-client-astro typecheck + 142 tests green.
@bighadj22
bighadj22 merged commit 2448766 into main Sep 6, 2026
3 checks passed
@bighadj22
bighadj22 deleted the fix/money-inventory-integrity branch September 6, 2026 14:49
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.

1 participant