Skip to content

Honor expectedVersion on ordinary card upserts - #23

Draft
CoreyRDean wants to merge 1 commit into
browser-use:mainfrom
CoreyRDean:atomic-publication-guard
Draft

CoreyRDean wants to merge 1 commit into
browser-use:mainfrom
CoreyRDean:atomic-publication-guard

Conversation

@CoreyRDean

@CoreyRDean CoreyRDean commented Sep 17, 2026

Copy link
Copy Markdown

What

POST /api/ideas honored expectedVersion only in blocked-replacement mode. The ordinary upsert path ran INSERT ... ON CONFLICT(dedupe_key) DO UPDATE unconditionally: publishing an existing dedupe key replaced the card's content and bumped its version with no compare-and-set, so a programmatic write could silently overwrite a card a user was mid-click on.

Change

The ordinary path is now a single-statement compare-and-set: the DO UPDATE clause carries WHERE ideas.version = ?, bound to the request's expectedVersion (a safe integer, or null when absent).

  • Existing key with a matching expectedVersion: card updated, version bumped - unchanged behavior.
  • Existing key with a missing or stale expectedVersion: SQLite skips the update, nothing is written, and the route returns 409 with { error, storedVersion } so the caller can re-read the card and retry.
  • New dedupe key: ingests normally without expectedVersion - unchanged behavior.
  • Blocked-replacement mode is untouched.

Test

node --test scripts/ideas-upsert-guard.test.mjs runs the real route (Workers env stubbed, D1-shaped adapter over in-memory SQLite) and covers the new-key 201 path, the missing and mismatched expectedVersion 409 paths - including that the stored row is left untouched - and the matching-version update path.


Summary by cubic

Makes the ordinary POST /api/ideas upsert a compare-and-set so an agent write can no longer silently overwrite a card a user is acting on.

  • Existing dedupe keys now only update when the request's expectedVersion matches the stored version; otherwise SQLite skips the write and the route returns 409 with the stored version.
  • New dedupe keys still ingest without expectedVersion, and blocked-replacement mode is untouched.
  • Adds scripts/ideas-upsert-guard.test.mjs covering the new-key 201 path, missing and mismatched expectedVersion 409 paths, and the matching-version update path.

Written for commit a7aeaf7. Summary will update on new commits.

Review in cubic

The ordinary POST /api/ideas upsert of an existing dedupe key now only
replaces the card when the request carries the stored version. A missing
or stale expectedVersion skips the write entirely and returns 409 with
the stored version, so an agent upsert can no longer silently overwrite
a card a user is acting on. New keys keep ingesting without it.
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