Skip to content

fix: 4 critical bugs — data corruption, data loss, and silent state mutation#6

Draft
cursor[bot] wants to merge 5 commits intomainfrom
cursor/critical-bug-inspection-d225
Draft

fix: 4 critical bugs — data corruption, data loss, and silent state mutation#6
cursor[bot] wants to merge 5 commits intomainfrom
cursor/critical-bug-inspection-d225

Conversation

@cursor
Copy link
Copy Markdown

@cursor cursor Bot commented Apr 27, 2026

Summary

Automated deep inspection found 4 critical bugs that cause data corruption, data loss, or silent state mutation in production paths.

Bugs Fixed

1. Operator precedence bug silently corrupts contract saves (lib/api/contracts.ts)

Bug: isLegacyContractConstraintOrSchemaError had || lower.includes('status') without parentheses, which — due to && binding tighter than || — matched any Postgres error containing the word "status". This sent unrelated errors (RLS violations, permission errors, other constraint failures) through the legacy fallback path, silently saving contracts with a reduced schema (missing milestones, clauses, template_type, etc.).

Trigger: Any contract create/update that fails with an error message containing "status" (e.g. RLS policy mentioning status, enum validation errors).

Fix: Removed the overly broad includes('status') clause. The remaining two patterns (contracts_status_check and column … contracts … does not exist) are specific enough.

2. Contract delete unlinks invoices even on non-FK errors (lib/api/contracts.ts)

Bug: deleteContract ran the invoice-unlink fallback (contract_id = null) for any first-delete error, not just foreign-key constraint violations. If the delete failed due to RLS, permissions, or transient errors, invoices would still have their contract_id cleared — even though the contract was never deleted.

Trigger: Delete fails for any reason other than FK constraints (e.g. RLS denies delete but allows invoice update).

Fix: Added FK-specific error pattern matching. Non-FK errors are now thrown immediately without running the unlink.

3. Share link generation resets paid invoices to unpaid (lib/api/invoices.ts)

Bug: generateInvoiceShareLink unconditionally set status: 'unpaid' when generating a share link. Sharing a paid or archived invoice silently reverted its payment status, corrupting income tracking data.

Trigger: User generates/refreshes a share link for a paid invoice.

Fix: Changed initial query from UPDATE to SELECT (read-only). The UPDATE only runs in the fallback path when a share_token needs to be generated, and no longer touches status.

4. Guest invoices lost on signup (lib/migration/migrateGuestData.ts)

Bug: migrateGuestData migrated clients, contracts, and time entries, then called clearAll() which wiped all guest data including invoices — but invoices were never migrated. Any guest user who created invoices before signing up would silently lose them.

Trigger: Guest creates invoices, then signs up. All invoices are permanently deleted.

Fix: Added invoice migration step (step 3) with client_id remapping and computed tax_amount, following the same pattern as the other entity migrations.

Validation

  • All 23 tests pass (5 suites), including 6 new tests for the operator precedence fix
  • TypeScript strict mode passes (tsc --noEmit)
  • ESLint passes on all changed files
  • No existing behavior is altered beyond the bug fixes
Open in Web View Automation 

cursoragent and others added 5 commits April 27, 2026 11:18
The isLegacyContractConstraintOrSchemaError function had a missing
parentheses issue where `lower.includes('status')` alone (without
the && conditions) would match almost any Postgres error containing
the word 'status'. This caused unrelated errors (RLS, permission,
constraint) to be silently routed through the legacy fallback path,
potentially saving contracts with a reduced/corrupted schema.

Removed the overly broad `includes('status')` check entirely. The
remaining conditions (contracts_status_check constraint name, and
missing-column pattern) are precise enough for the intended fallback.

Co-authored-by: Siddhartha Dwivedi <info@siddart.net>
The deleteContract fallback path ran invoice unlink (setting
contract_id to null) for ANY first-delete error, not just FK
constraint violations. This could clear invoice-contract links
even when the contract was not actually deleted (e.g. RLS denied
the delete, or a transient error occurred).

Now the fallback only triggers when the error message matches
foreign-key constraint patterns. Other errors are thrown immediately.

Co-authored-by: Siddhartha Dwivedi <info@siddart.net>
…eneration

generateInvoiceShareLink was unconditionally setting status='unpaid'
when generating or refreshing a share link. This silently reverted
paid or archived invoices back to unpaid — corrupting invoice state
and breaking income tracking.

Changed to read-only SELECT to get the existing share_token and only
UPDATE with a new token when one is missing, without touching status.

Co-authored-by: Siddhartha Dwivedi <info@siddart.net>
Guest invoices were never migrated to Supabase during sign-up.
migrateGuestData migrated clients, contracts, and time entries,
then called clearAll() which wiped all guest data including
invoices. Any guest who created invoices before signing up would
silently lose them.

Added invoice migration step (step 3) with client_id remapping
and computed tax_amount, matching the pattern used by the other
entity migrations.

Co-authored-by: Siddhartha Dwivedi <info@siddart.net>
Verifies the operator-precedence fix: ensures the function does NOT
match generic errors containing 'status' in non-constraint contexts,
while still correctly matching the two legitimate legacy patterns.

Co-authored-by: Siddhartha Dwivedi <info@siddart.net>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

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

Project Deployment Actions Updated (UTC)
stacklite-live Ready Ready Preview, Comment Apr 27, 2026 11:20am

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