Skip to content

Bugfix/estelle/706/unique constraint to replaces#738

Open
EstelleDa wants to merge 5 commits into
release-2026.2.4from
bugfix/estelle/706/uniqueConstraintToReplacesId
Open

Bugfix/estelle/706/unique constraint to replaces#738
EstelleDa wants to merge 5 commits into
release-2026.2.4from
bugfix/estelle/706/uniqueConstraintToReplacesId

Conversation

@EstelleDa
Copy link
Copy Markdown
Member

No description provided.

@EstelleDa EstelleDa requested a review from bencap May 15, 2026 02:55
@bencap bencap changed the base branch from release-2026.2.3 to release-2026.2.4 May 21, 2026 18:10
Copy link
Copy Markdown
Collaborator

@bencap bencap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on this Estelle. A couple comments and a broad point about deployment. We'll need a deployment rule here where we clean production of these duplicate entries. I think we have one case right now where the duplicates are clearly junk score sets, and we can just delete those and retain the published successor that is meant to be the correct superseder. No action on this PR, but I want to make sure we persist that rule here so that we know to do it on deployment.

The other two comments are about clarity in our responses and error messages. I think we don't want to assume the user wants to supersede the head-- we should do what they say and raise a clear error if we cannot supersede the score set because of the constraint. Ultimately the root of this is that score set creation isn't necessarily atomic, but I think that is something we should handle separately in #759. Here, I think as long as we return a clear error to the user we can eliminate the confusing and unintentional behavior of having multiple superseders for the same score set.

Comment on lines 1603 to +1627
if item_create.superseded_score_set_urn is not None:
superseded_score_set = await fetch_score_set_by_urn(
current_superseded = await fetch_score_set_by_urn(
db, item_create.superseded_score_set_urn, user_data, user_data, True
)

if superseded_score_set is None:
if current_superseded is None:
logger.info(
msg="Failed to create score set; The requested superseded score set does not exist.",
extra=logging_context(),
)
raise HTTPException(
status_code=404,
detail="The requested superseded score set does not exist",
)
superseded_score_set: Optional[ScoreSet] = find_superseded_score_set_tail(current_superseded, Action.READ, user_data)
if superseded_score_set is None or superseded_score_set.private:
logger.info(
msg="Failed to create score set; The newest version of the requested superseded score set is not accessible.",
extra=logging_context(),
)
raise HTTPException(
status_code=404,
detail="The newest version of the requested superseded score set is not accessible. "
"It may be private and multiple score sets supersede the same score set.",
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat resistant here to do tail resolution on the score set that a user specifically requested as being the one they want to supersede. I think that instead, if a user requests to supersede a score set that already has been superseded, we should return a 409 Conflict with a clear message they cannot do that. Our logic simplifies to:

If the user requests to supersede a score set:

  1. Check if the superseded score set exists and is accessible. If not, return a 404.
  2. Check if the superseded score set already has a superseding score set. If yes, return a 409.
  3. If we pass the above checks, we can supersede the score set and do so.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your suggestion is better. I only considered that the users supersede a wrong one, but didn't consider some potential problems.

Comment thread src/mavedb/routers/score_sets.py Outdated
Comment on lines 1882 to 1883
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I also think we should add a try/catch for a potential race condition with the unique constraint. If a user sent off two almost concurrent requests for superseding the same score set, they might race past our checks above and fail here. That would look something like:

try:
     db.add(item)
     db.commit()
 except IntegrityError as e:
     db.rollback()
     if is_replaces_id_unique_violation(e):
         raise HTTPException(
             status_code=409,
             detail="The requested score set has already been superseded.",
         )
     raise

We'd need a new helper function to check if the integrity error is the one we expect.

@EstelleDa
Copy link
Copy Markdown
Member Author

I found that only one score set has multiple private superseding score sets. I think we can delete the old superseding score set manually.

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.

Enforce unique supersession: replaces_id should have a unique constraint

2 participants