Skip to content

(SP:1) [SHOP] add DB guardrails for shipping/payment invariants#400

Merged
ViktorSvertoka merged 4 commits intodevelopfrom
lso/feat/wallets
Mar 13, 2026
Merged

(SP:1) [SHOP] add DB guardrails for shipping/payment invariants#400
ViktorSvertoka merged 4 commits intodevelopfrom
lso/feat/wallets

Conversation

@liudmylasovetovs
Copy link
Collaborator

@liudmylasovetovs liudmylasovetovs commented Mar 12, 2026

Description

Implements Batch 6 shipping DB guardrails to enforce order/shipment invariants at the database level.

This PR adds narrow database protections for terminal and non-shippable orders so invalid shipping pipeline states cannot be persisted. It also updates the Shop test fixtures/specs to reflect the new DB-enforced behavior and verifies that checkout, webhook, admin-shipping, and worker flows still behave correctly after the migration.


Related Issue

Issue: #<issue_number>


Changes

  • Added DB guardrails for shipping/order/payment invariants:
    • terminal orders cannot keep active shipping statuses
    • non-shippable orders cannot have processable queued/processing shipment rows
    • shipping pipeline is auto-closed when an order becomes terminal or otherwise non-shippable
  • Added and applied Batch 6 migration with backfill + constraint/trigger setup for orders and shipping_shipments
  • Updated Shop tests and test seeding strategy to create valid initial DB rows first, then transition them into blocked states where needed, so tests remain correct under the new DB rules

Database Changes (if applicable)

  • Schema migration required
  • Seed data updated
  • Breaking changes to existing queries
  • Transaction-safe migration
  • Migration tested locally on Neon

How Has This Been Tested?

  • Tested locally
  • Verified in development environment
  • Checked responsive layout (if UI-related)
  • Tested accessibility (keyboard / screen reader)

Additional verification performed:

  • Batch 6 guardrail tests passed
  • Updated Shop regression tests passed:
    • admin-shipping-payment-gate.test.ts
    • shipping-shipments-worker-phase5.test.ts
    • monobank-webhook-apply.test.ts
    • stripe-webhook-refund-full.test.ts
  • Full Shop Vitest suite passed locally: 126 passed / 461 passed
  • Build passed locally
  • Migration was applied and post-checks confirmed:
    • orders_terminal_shipping_status_chk
    • trg_orders_close_shipping_pipeline_guardrail
    • trg_shipping_shipments_require_shippable_order_guardrail

Screenshots (if applicable)

N/A


Checklist

Before submitting

  • Code has been self-reviewed
  • No TypeScript or console errors
  • Code follows project conventions
  • Scope is limited to this feature/fix
  • No unrelated refactors included
  • English used in code, commits, and docs
  • New dependencies discussed with team
  • Database migration tested locally (if applicable)
  • GitHub Projects card moved to In Review

Reviewers

Summary by CodeRabbit

  • Bug Fixes

    • Enforced consistency between payment, order, and shipping statuses to prevent invalid terminal state combinations.
  • Reliability Improvements

    • Added guardrails that block or close shipping workflows for non-shippable/terminal orders, mark affected shipments as needing attention, clear leases, and standardize error handling and backfills.
  • Tests

    • Added and updated tests and seeds to cover new guardrail behaviors, idempotency, and shipping-state transitions.

@vercel
Copy link
Contributor

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devlovers-net Ignored Ignored Preview Mar 13, 2026 0:26am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Warning

Rate limit exceeded

@liudmylasovetovs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a5c3885d-0d99-4ae2-a099-77f51485f9d5

📥 Commits

Reviewing files that changed from the base of the PR and between 4bddb62 and 86a4fcc.

📒 Files selected for processing (1)
  • frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts
📝 Walkthrough

Walkthrough

Adds DB-level guardrails and a terminal shipping CHECK constraint coordinating orders and shipping_shipments. Introduces two trigger functions and triggers to auto-cancel/mark shipments when orders become non-shippable, plus multiple test updates to reflect the new behaviors.

Changes

Cohort / File(s) Summary
Schema & Migrations
frontend/db/schema/shop.ts, frontend/drizzle/0031_lean_nebula.sql, frontend/drizzle/0032_shipping_guardrails_followup.sql, frontend/drizzle/meta/_journal.json
Adds orders_terminal_shipping_status_chk CHECK constraint; adds two PL/pgSQL guardrail functions and triggers (shop_orders_close_shipping_pipeline_guardrail / trg_orders_close_shipping_pipeline_guardrail, shop_shipping_shipments_require_shippable_order_guardrail / trg_shipping_shipments_require_shippable_order_guardrail); adds migration journal entries and backfill/update SQL for shipments.
New Tests
frontend/lib/tests/shop/shipping-db-guardrails-batch6.test.ts
New test suite validating guardrail behavior, state transitions, and side-effects on orders and shipping_shipments.
Test Updates — Shipping/Payment Gate
frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts, frontend/lib/tests/shop/shipping-shipments-worker-phase5.test.ts
Shifted seeding to include post-insert state mutations; added handling for blocked transitions; broadened shippingStatus/shipmentStatus options (cancelled, needs_attention) and adjusted expectations.
Test Updates — Webhooks & Refunds
frontend/lib/tests/shop/stripe-webhook-refund-full.test.ts, frontend/lib/tests/shop/monobank-webhook-apply.test.ts
Removed automatic queued-shipment seeding, renamed helper (insertPendingOrderWithQueuedShipmentinsertPendingOrder), updated assertions to check absence/length of shipments and adjusted test descriptions.
Test Utilities / Replay
frontend/lib/tests/shop/monobank-google-pay-submit-route.test.ts
Consolidated replay token generation to reuse a single replayToken across initial and replayed requests for idempotency testing.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Orders as Orders Table
    participant OrdersTrig as Trigger:\nshop_orders_close_shipping_pipeline_guardrail
    participant Shipments as Shipping_Shipments Table
    participant ShipTrig as Trigger:\nshop_shipping_shipments_require_shippable_order_guardrail

    App->>Orders: UPDATE payment_status/status/inventory_status/shipping_required
    Orders->>OrdersTrig: BEFORE UPDATE fires
    OrdersTrig->>OrdersTrig: evaluate is_shippable / is_terminal
    alt transitioned to non-shippable or terminal
        OrdersTrig->>Orders: SET shipping_status = 'cancelled' (when applicable)
        OrdersTrig->>Shipments: UPDATE related shipments → status='needs_attention', clear leases, set error fields
    end
    App->>Shipments: INSERT/UPDATE shipment (status='queued'/'processing')
    Shipments->>ShipTrig: BEFORE INSERT/UPDATE fires
    ShipTrig->>Orders: SELECT ... FOR UPDATE to verify shippable (shipping_required, paid, PAID, inventory reserved)
    alt order is shippable
        ShipTrig-->>Shipments: allow operation
    else
        ShipTrig-->>Shipments: RAISE EXCEPTION (constraint violation)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰
I hop where triggers softly tread,
I nudge sad shipments from their bed,
Constraints in place, the carrots stay,
Orders and parcels find their way,
A tiny hop — and all is fed.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding database guardrails for shipping/payment invariants. It accurately summarizes the core objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lso/feat/wallets
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d90c3fe88b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts (1)

70-83: Mirror the full terminal predicate in the seeding helper.

requiresPostInsertBlockedTransition only special-cases refunded and CANCELED, but orders_terminal_shipping_status_chk also treats failed and INVENTORY_FAILED as terminal. The next test that seeds either path will fail during setup instead of at the assertion site.

♻️ Minimal alignment
-  const requiresPostInsertBlockedTransition =
-    args.paymentStatus === 'refunded' || args.orderStatus === 'CANCELED';
+  const requiresPostInsertBlockedTransition =
+    args.paymentStatus === 'failed' ||
+    args.paymentStatus === 'refunded' ||
+    args.orderStatus === 'CANCELED' ||
+    args.orderStatus === 'INVENTORY_FAILED';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts` around lines 70
- 83, The seeding helper's terminal predicate
requiresPostInsertBlockedTransition only checks for 'refunded' and 'CANCELED'
but must mirror the DB constraint (orders_terminal_shipping_status_chk) which
also treats payment statuses 'failed' and order/inventory statuses
'INVENTORY_FAILED' as terminal; update the logic that computes
requiresPostInsertBlockedTransition to include args.paymentStatus === 'failed'
and args.orderStatus === 'INVENTORY_FAILED' (or the appropriate casing used
elsewhere) so seedPaymentStatus, seedOrderStatus, and seedInventoryStatus are
set to the non-terminal seeded values only when truly non-terminal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/drizzle/0031_lean_nebula.sql`:
- Around line 1-20: The migration backfill and the guardrail function
shop_orders_close_shipping_pipeline_guardrail() only touch statuses 'queued' and
'processing', but claimQueuedShipmentsForProcessing treats 'failed' as runnable
— update both the SQL backfill (update shipping_shipments ...) and the guardrail
to also include rows with status = 'failed' (or at minimum clear next_attempt_at
and lease fields for failed rows) so failed shipments are not re-claimed; locate
the SQL UPDATE statement and the shop_orders_close_shipping_pipeline_guardrail()
implementation and apply the same status changes (status -> 'needs_attention',
lease_owner/lease_expires_at/next_attempt_at -> null, last_error_code/message
and updated_at) or at least ensure next_attempt_at is null for status =
'failed'.
- Around line 125-139: The shippability check uses an unlocked EXISTS on orders
which can race with concurrent updates; change the validation to select and lock
the parent order row by querying orders with WHERE id = new.order_id AND ... FOR
UPDATE (or use SELECT 1 FOR UPDATE / PERFORM with the same predicate) so the row
is locked during the trigger's check; preserve the same error raise (constraint
shipping_shipments_shippable_order_chk and message) when the locked-select
returns no row, ensuring the check references new.order_id and the same columns
(shipping_required, payment_status, status, inventory_status).

---

Nitpick comments:
In `@frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts`:
- Around line 70-83: The seeding helper's terminal predicate
requiresPostInsertBlockedTransition only checks for 'refunded' and 'CANCELED'
but must mirror the DB constraint (orders_terminal_shipping_status_chk) which
also treats payment statuses 'failed' and order/inventory statuses
'INVENTORY_FAILED' as terminal; update the logic that computes
requiresPostInsertBlockedTransition to include args.paymentStatus === 'failed'
and args.orderStatus === 'INVENTORY_FAILED' (or the appropriate casing used
elsewhere) so seedPaymentStatus, seedOrderStatus, and seedInventoryStatus are
set to the non-terminal seeded values only when truly non-terminal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b67276c-91b7-4dea-8cfe-83a5b090a482

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc67b8 and d90c3fe.

📒 Files selected for processing (10)
  • frontend/db/schema/shop.ts
  • frontend/drizzle/0031_lean_nebula.sql
  • frontend/drizzle/meta/0031_snapshot.json
  • frontend/drizzle/meta/_journal.json
  • frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts
  • frontend/lib/tests/shop/monobank-google-pay-submit-route.test.ts
  • frontend/lib/tests/shop/monobank-webhook-apply.test.ts
  • frontend/lib/tests/shop/shipping-db-guardrails-batch6.test.ts
  • frontend/lib/tests/shop/shipping-shipments-worker-phase5.test.ts
  • frontend/lib/tests/shop/stripe-webhook-refund-full.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts`:
- Around line 130-137: The returned seed currently doesn't reflect the DB
guardrail change: when requiresPostInsertBlockedTransition is true and
state.shipmentStatus exists you must return 'needs_attention' instead of the
original status; update the return block around shipmentId/shippingStatus to set
shipmentStatus to requiresPostInsertBlockedTransition && state.shipmentStatus ?
'needs_attention' : state.shipmentStatus. Also extend the Seeded type
(referenced near line 41) to include 'needs_attention' as an allowed
shipmentStatus value so the type system accepts this new status.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe205aa1-f8ea-4507-8195-6177abf3060d

📥 Commits

Reviewing files that changed from the base of the PR and between d90c3fe and 03dcdd1.

📒 Files selected for processing (4)
  • frontend/drizzle/0032_shipping_guardrails_followup.sql
  • frontend/drizzle/meta/0032_snapshot.json
  • frontend/drizzle/meta/_journal.json
  • frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/drizzle/meta/_journal.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts`:
- Around line 70-86: The current requiresPostInsertBlockedTransition only marks
terminal targets (failed/refunded/CANCELED/INVENTORY_FAILED); update its logic
so it treats any non-shippable target as requiring the two-step (guardrail) path
— e.g. invert the shippable condition instead of listing only terminal states so
cases like pending are included; adjust the same predicate used to derive
seedPaymentStatus, seedOrderStatus and seedInventoryStatus (the variables
requiresPostInsertBlockedTransition, seedPaymentStatus, seedOrderStatus,
seedInventoryStatus) so non-shippable targets always route through the
post-insert blocked transition and mirror the persisted DB state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5789e8e3-6b30-4e36-bcc0-fd65521742cb

📥 Commits

Reviewing files that changed from the base of the PR and between 03dcdd1 and 4bddb62.

📒 Files selected for processing (1)
  • frontend/lib/tests/shop/admin-shipping-payment-gate.test.ts

@ViktorSvertoka ViktorSvertoka merged commit ac0525c into develop Mar 13, 2026
7 checks passed
@ViktorSvertoka ViktorSvertoka deleted the lso/feat/wallets branch March 13, 2026 05:09
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.

2 participants