Skip to content

[BE] Lock tagging records against deletion when a learner competency status is written #782

Open

Description

@thelmick-unicon

User Story

As a learner, I want the competency framework my earned mastery is recorded against to survive someone tidying up tags or taxonomies, so I keep the competencies I have demonstrated.

Acceptance Criteria

Scenario: Recording a learner's mastery locks the tag association it was evaluated through
  Given a learner has no recorded mastery for a competency
  When a mastery status is recorded for that learner against that competency
  Then the tag association the criterion was evaluated through is locked against deletion

Scenario: The locked association protects the tag and taxonomy above it too
  Given the tag association from the previous scenario is now locked
  When someone asks whether the tag, or its taxonomy, is safe to delete
  Then both report that they are depended upon, without either one carrying a lock of its own

Scenario: Deleting a protected tag archives it instead of removing it
  Given a learner has recorded mastery against a competency
  When an administrator deletes that competency
  Then the competency is archived rather than removed
  And the learner's recorded mastery still resolves through it

Scenario: Recording mastery for a second learner does not fail on an already-locked record
  Given a tag association already locked against deletion
  When a mastery status is recorded for another learner against the same competency
  Then the status is recorded
  And the association remains locked

Scenario: Content with no competency criteria locks nothing
  Given a learner is graded on content with no competency criteria associated with it
  When the grade is recorded
  Then no tagging record is locked against deletion

Scenario: A failed status write locks nothing
  Given a mastery status write that does not complete
  When the operation ends
  Then no tagging record is left locked against deletion

Description

openedx_tagging gains a way to mark a tag association as depended upon in #777, but nothing calls it, so no record is ever protected and every delete still removes rows outright. This ticket marks the tag association a criterion was evaluated through, so it can't be deleted, preserving the student competency status data recorded against it. Locking the association is also what protects the tag and taxonomy above it: #780's delete branch decides whether a tag or a taxonomy is safe to remove by asking whether any association beneath it is locked, so this one write is what the whole chain depends on.

The direction matters. openedx_tagging is a standalone library that must not know competency-based education exists, so the feature that creates the dependency is responsible for declaring it. Competency mastery is the only thing that creates a dependency on tagging data today.

Technical Details

This section is background and a suggested approach, not the ticket's source of truth. The User Story and Acceptance Criteria define what must be true when the work is done.

In short

When the lock is set. At the moment a learner competency status row is written. That row is the dependency: it is what would be orphaned if the tag association behind it were deleted. Setting the lock at write time rather than at delete time is what lets the tagging app answer the question without querying anything competency-related.

Only the association is locked; the tag and taxonomy are protected without a lock of their own. deletion_locked exists only on ObjectTag — #777 deliberately has no equivalent field or setter for Tag or Taxonomy, since neither has anything to set. A tag or a taxonomy is judged safe to delete by asking, through #777's reader functions, whether any association beneath it is locked. So locking the one association a criterion points at is enough: it is what the tag and taxonomy above it are actually asked about.

Locking rides along with the status write. The lock must not be able to exist without the status row that justifies it, and more importantly, the status row must not exist without the lock, since that would leave a window in which the record it depends on could be deleted. So the lock is set in the same transaction as the status write.

Locking is cheap and idempotent, so it does not need to be conditional. The lock function is bulk update and setting an already-set flag is a no-op, so there is no need to check first whether an association is already locked. Do not add a read to avoid a write here; the read costs as much as the write it saves.

Implementation specifics

Files to modify

File Nature of modification
src/openedx_learning/applets/cbe/api.py lock the tag association in the same transaction as the learner status write
tests/openedx_learning/applets/cbe/ lock-on-write, idempotency, no-criteria, query-count, rollback, and end-to-end delete tests

Context

Activity

  1. moved this from Needs additional details to Ready for Community Review in Competency Criteria and Student Progresson Sep 14, 2026
  2. moved this from Ready for Community Review to Final Axim Review in Competency Criteria and Student Progresson Sep 21, 2026
  3. thelmick-unicon commented on Sep 21, 2026

    @thelmick-unicon
    Author

    The discussion period for this ticket is now complete, and we appreciate all the feedback. We'll be moving forward with development based on the current ticket pending final Axim sign-off. That said, the conversation doesn't have to stop. If you still have feedback, please continue to add to this ticket. We'll review anything new and determine whether it fits within the current timeline or makes more sense as its own ticket for future work. Thanks again for helping make this better!

    @ormsbee This is ready for your final review. Thank you!

  4. ormsbee commented on Oct 9, 2026

    @ormsbee
    Contributor

    Approved.

  5. moved this from Final Axim Review to Todo in Competency Criteria and Student Progresson Oct 9, 2026
  6. moved this from In Progress to Unicon Code Review in Competency Criteria and Student Progresson Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions