Skip to content

fix(leads): refuse create collisions without overwriting contacts - #29

Merged
debpalash merged 1 commit into
debpalash:mainfrom
rudycelekli:fix/lead-create-collision-20261002
Oct 2, 2026
Merged

debpalash merged 1 commit into
debpalash:mainfrom
rudycelekli:fix/lead-create-collision-20261002

Conversation

@rudycelekli

@rudycelekli rudycelekli commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What & why

Closes #15. POST /api/lead now refuses an existing company/city row with HTTP409 and its existing lead_id, instead of silently replacing its contact and clearing omitted enrichment fields. The existing data model has one lead per workspace/company/city. Explicit PUT remains the supported update/clear operation; collection callers retain the default upsert behavior. Both backend implementations map a concurrent unique-key collision to the same nonmutating error. The web API client propagates that failure; the Add Lead dialog shows the actionable message, preserves input and releases its loading state without reporting success.

The database-backed HTTP regression preserves exact existing row on rejected collision then verifies explicit PUT replacement/clearing. Six simultaneous creates have exactly one winner, five409replies and no contact replacement on both backends.

Type

  • Bug fix

Validation

uv run --frozen pytest tests/test_lead_create_collision.py tests/test_lead_store_sqlite.py tests/test_lead_job_tenancy.py tests/test_lead_utility_endpoints.py -q

before.log:2failed, real HTTP API returned200/sameID instead of refusing collision on rawSQLite and actual ORM store running SQLite.

after-final.log:19passed, four new parametercases plus existing store/job/utility suites; final docstrings added afterward, behavioral source unchanged.

Actual SQLite raw+SQLAlchemy stores and HTTP router tested; no native PostgreSQL/RLS claim. Schema/uniqueness unchanged. Create POST collisions intentionally now409; PUT explicit replacements/empty clears remain supported.

The native Bun API regression fails on unchanged main because an HTTP409 is returned as a successful creation. After the fix, both error and successful-creation cases pass (bun test apps/web/tests/api.lead-create.test.ts, Bun1.4.2). The dialog captures its form before the async call, clears/reset only on success, and retains inputs on refusal. The actual changed web app also passes bun run build (TypeScript build and Vite production build) and bun run lint with the locked shared dependencies. The complete backend suite also passed; the documentation-workspace build was not run.

Checklist

  • Focused backend regressions and related existing tests pass in locked uv development environment (Python3.14.4)
  • No scraping or credentialed provider added

Security-sensitive?

Same-workspace data correctness. Existing authentication, workspace/RLS selectors, outbound fetch guards, secrets and LLM handling are unchanged. No exploitable vulnerability claim.

Final validation

  • DCO sign-off and SSH-signed commit.
  • uv run --frozen pytest -q -m "not live and not postgres": 1845 passed, 131 skipped, 12 warnings in 64.13s (0:01:04).
  • Locked dependency environment, Python 3.14, fresh migrated SQLite, no live providers or production database. PostgreSQL/live integration markers were skipped; no native PostgreSQL claim.
  • Entire monorepo lint/build (documentation workspace not installed).

Prepared with AI assistance; reproduced locally and independently reviewed.

Frontend: web ESLint and TypeScript/Vite build passed on this patch. Documentation-workspace build was not run.

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
@rudycelekli
rudycelekli requested a review from debpalash as a code owner October 2, 2026 10:31
@debpalash
debpalash merged commit fcf7b59 into debpalash:main Oct 2, 2026
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.

bug(leads): creating another contact at the same company overwrites the lead and clears omitted fields

2 participants