Skip to content

Duplicate record id: silent overwrite in MemoryAdapter, unmapped engine error in the SQL adapters #120

Description

@cuibonobo

#55 promises "an id that already exists → 409 StackConflictError, never a silent overwrite." Stack.create() pre-checks via getRecord (stack.ts:748-753), so the common path is safe — but the raw adapter createRecord doesn't uniformly enforce it, so a direct adapter call (or a race between Stack's pre-check and its write) doesn't get the promised contract:

  • MemoryAdapter.createRecord (testing.ts:50-54) does records.set(id, …) and order.push(id) unconditionally → a duplicate id silently overwrites the existing record and appends a duplicate order entry (corrupting pagination).
  • SQL adaptersSharedSqlRecordLogic.createRecord (record-logic.ts:133-162) INSERTs against the records PK; a duplicate throws a raw SQLite unique-constraint error that is not mapped to StackConflictError. isUniqueConstraintViolation is consulted only in saveVersion (record-logic.ts:447-455), not here — so a raw-adapter duplicate surfaces as an unmapped engine exception instead of the typed error the wire and ID authority under server topology: clients mint record IDs and the server trusts them #55 promise.

Fix

  • MemoryAdapter.createRecord: throw StackConflictError if the id already exists (before mutating records/order).
  • SharedSqlRecordLogic.createRecord: wrap the INSERT and map a unique-constraint violation to StackConflictError, reusing isUniqueConstraintViolation (already imported).

Both bring the raw adapters in line with Stack.create()'s pre-check and the #55 contract, and pin the "never a silent overwrite" invariant at the storage layer rather than relying solely on the Stack-level pre-check.

Tests

  • Raw adapter.createRecord with a duplicate id throws StackConflictError on MemoryAdapter and both SQLite adapters
  • MemoryAdapter order has no duplicate entry after a rejected create

Refs

#55 (ID authority / never-silent-overwrite), #48 (the saveVersion collision-mapping precedent this mirrors). From docs/design-assessment-2026-07.md §F7.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions