Supersession is authorized ad hoc at each call site. For calibrations, validate_superseded_score_calibration checks Action.READ, which passes for any published calibration. For score sets, create_score_set passes owner_or_contributor to fetch_score_set_by_urn, where the narrowing collapses against only_published. Neither spelling is the rule we want, and neither is reachable from the front end, so no supersede affordance can be rendered.
Action.UPDATE is not a substitute. _handle_update_action denies updates on published calibrations to everyone except admins, and only published calibrations may be superseded, so requiring UPDATE would make supersession admin-only.
Proposed behavior
Add SUPERSEDE to the Action enum and a _handle_supersede_action for both score_set and score_calibration.
Calibration predicate — permitted if any of:
- an active
admin role
entity.created_by_id == user_data.user.id
entity.investigator_provided and the user is a contributor to the score set
user_is_contributor_to_score_set keeps its existing definition in lib/permissions/score_calibration.py: the user's ORCID is in entity.score_set.contributors, or the user is the score set's created_by or modified_by.
Score set predicate — admin, or owner, or contributor.
A score set contributor may not supersede a community calibration on their own score set. Ranking is the team's tool for an unwanted community contribution; declaring their own calibration its successor asserts a scientific succession that isn't theirs to make.
Business invariants stay out of the permission handler and remain in the validators, since they hold regardless of who is asking:
- the target is published (
private is False) — a private calibration has no permitted superseder, including its owner and admins; edit it instead
- the target is not already superseded
- the target is on the same score set as the new calibration
Document one asymmetry in the handler's docstring: investigator_provided is frozen at creation while contributor status is evaluated per request, so adding someone to a score set grants them supersede rights over every investigator-provided calibration already on it, and removing them leaves their own calibrations superseded-able through the owner leg.
Acceptance criteria
permissions/user-is-permitted/{model_name}/{urn}/{action} answers supersede for score_set and score_calibration
validate_superseded_score_calibration asserts Action.SUPERSEDE instead of Action.READ
create_score_set asserts Action.SUPERSEDE on the superseded score set rather than relying on fetch_score_set_by_urn's owner_or_contributor argument
- a non-contributor is denied supersession of a published calibration and of a published score set
- a score set contributor is permitted on an investigator-provided calibration and denied on a community one
test_can_create_superseding_score_calibration_in_public_score_set_as_non_contributor is updated to assert denial
Supersession is authorized ad hoc at each call site. For calibrations,
validate_superseded_score_calibrationchecksAction.READ, which passes for any published calibration. For score sets,create_score_setpassesowner_or_contributortofetch_score_set_by_urn, where the narrowing collapses againstonly_published. Neither spelling is the rule we want, and neither is reachable from the front end, so no supersede affordance can be rendered.Action.UPDATEis not a substitute._handle_update_actiondenies updates on published calibrations to everyone except admins, and only published calibrations may be superseded, so requiring UPDATE would make supersession admin-only.Proposed behavior
Add
SUPERSEDEto theActionenum and a_handle_supersede_actionfor bothscore_setandscore_calibration.Calibration predicate — permitted if any of:
adminroleentity.created_by_id == user_data.user.identity.investigator_providedand the user is a contributor to the score setuser_is_contributor_to_score_setkeeps its existing definition inlib/permissions/score_calibration.py: the user's ORCID is inentity.score_set.contributors, or the user is the score set'screated_byormodified_by.Score set predicate — admin, or owner, or contributor.
A score set contributor may not supersede a community calibration on their own score set. Ranking is the team's tool for an unwanted community contribution; declaring their own calibration its successor asserts a scientific succession that isn't theirs to make.
Business invariants stay out of the permission handler and remain in the validators, since they hold regardless of who is asking:
private is False) — a private calibration has no permitted superseder, including its owner and admins; edit it insteadDocument one asymmetry in the handler's docstring:
investigator_providedis frozen at creation while contributor status is evaluated per request, so adding someone to a score set grants them supersede rights over every investigator-provided calibration already on it, and removing them leaves their own calibrations superseded-able through the owner leg.Acceptance criteria
permissions/user-is-permitted/{model_name}/{urn}/{action}answerssupersedeforscore_setandscore_calibrationvalidate_superseded_score_calibrationassertsAction.SUPERSEDEinstead ofAction.READcreate_score_setassertsAction.SUPERSEDEon the superseded score set rather than relying onfetch_score_set_by_urn'sowner_or_contributorargumenttest_can_create_superseding_score_calibration_in_public_score_set_as_non_contributoris updated to assert denial