Skip to content

Harden Edge de-dup with a partial unique index (close connectNodes TOCTOU) #25

@CuriouslyCory

Description

@CuriouslyCory

Context

PR #24 introduced the Edge model with service-level de-duplication (ADR-0005). The de-dup check in connectNodes uses findFirst to test for a duplicate active Edge, then creates it—but under READ COMMITTED isolation, a concurrent double-submit can slip two identical Edges in the window between the check and the write.

Why deferred

A database partial unique index is the only correct fix:

CREATE UNIQUE INDEX ... ON "Edge"(canvasNodeId, sourceId, targetId) WHERE "deletedAt" IS NULL

This allows soft-delete-then-recreate (a plain @@unique would wrongly block it) and closes the race. However, Prisma cannot express partial unique indexes declaratively—it requires switching from db:push to prisma migrate + raw SQL, a workflow change beyond the current slice.

The realistic trigger is narrow (one owner double-submitting the same Edge within milliseconds) and the blast radius (a duplicate active Edge) is recoverable via soft-delete.

Resolution

  1. Switch to prisma migrate workflow.
  2. Add the composite index: CREATE UNIQUE INDEX "idx_edge_dedup" ON "Edge"(canvasNodeId, sourceId, targetId) WHERE "deletedAt" IS NULL.
  3. In connectNodes, catch Prisma P2002 (unique constraint violation) and map it to ConflictError.
  4. Update ADR-0005's deferral note to point at this issue.

The non-unique composite index from PR #24 (@@index([canvasNodeId, sourceId, targetId, deletedAt])) is the fast-path precursor for the findFirst query.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an agent to implement AFK

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions