Skip to content

Conversation

@ktyagiapphelix2u
Copy link
Contributor

@ktyagiapphelix2u ktyagiapphelix2u commented Jan 14, 2026

Description

Instead of deleting user retirement records completely, this PR updates the system to replace personal information (name, email, username) with safe placeholder values while keeping the records in the database.

Private Ticket

https://2u-internal.atlassian.net/browse/BOMS-293

@pdpinch
Copy link
Contributor

pdpinch commented Jan 14, 2026

Maybe this is out of scope for this PR, but I'm concerned about the explicit reference to Jenkins. I understand the desirability of recording the job id, but not everyone uses Jenkins as their runner.

@robrap
Copy link
Contributor

robrap commented Jan 14, 2026

@pdpinch: @ktyagiapphelix2u is out until Friday, but we will be removing references to 2U and Jenkins. Thanks.

@ktyagiapphelix2u
Copy link
Contributor Author

ktyagiapphelix2u commented Jan 16, 2026

@pdpinch I agreed I am removing jenkins name reference. Thanks.

@robrap
Copy link
Contributor

robrap commented Jan 20, 2026

@ktyagiapphelix2u: Also, I think we should change this to a breaking change PR. That means updating the title to fix!: .... Also, we should open a Fast Track DEPR (i.e automatically accepted) for this as an inform.

We should note that switching from deleting records to redacting records is not a breaking change from the point of view of user retirement, because in either case the sensitive data has been safely taken care of. The breaking change is that these records will no longer be deleted, so any operator scripts that run against the table after retirement, that also relied on the fact that data was being deleted, would need to be updated.

@ktyagiapphelix2u ktyagiapphelix2u changed the title fix: redacting user retirement data in lms fix!: redacting user retirement data in lms Jan 21, 2026
@ktyagiapphelix2u
Copy link
Contributor Author

ktyagiapphelix2u commented Jan 21, 2026

@robrap I was thinking of updating archive cleanup script for Open edX. Is it expected to update it there as well, or should it be left as is? as they have moved to CI workflows rather than jenkins

@bmtcril
Copy link
Contributor

bmtcril commented Jan 21, 2026

Hi all, can we get a description of the problem this is trying to address since we can't see the internal ticket? I have some (very small) amount of concern about this table growing given how often it's called, but more want to understand why this change is necessary.

Since this is a breaking change in the API I think it should be versioned to a V2 endpoint as part of the depr.

@fghaas I know you've been involved in tutor-contrib-retirement so wanted to give you a heads up as well

@robrap
Copy link
Contributor

robrap commented Jan 26, 2026

@bmtcril: In thinking through explaining the issue to you, I may have come up with a backward-compatible version of this change, so that is what I am going to propose we do.

The reason we wanted this change is because in Snowflake, we've used different technologies for sync, but all of them treat the status record deletes as soft-deletes, which then requires an additional custom job to clear out the soft deleted data to fully remove the sensitive data. We decided that redacting here, just like we do everywhere else, would avoid the custom jobs and ongoing maintenance.

I just realized that we could have the best of both worlds, and have this api first redact, and then delete. That way it will still be backward-compatible with the deletes, but we wouldn't have to clean up the soft-deletes, because those records would already be redacted.

@ktyagiapphelix2u: Can you please implement this. You'll need to ensure that the redacted data gets saved to the DB before the delete. I'm not certain exactly how to ensure this, but maybe working with Dave W. to ensure that the soft-deleted records contain the redacted data? Hopefully neither mysql nor Django nor the sync code tries to be too smart.

@robrap
Copy link
Contributor

robrap commented Jan 26, 2026

@ktyagiapphelix2u: Once we ensure we can get everything working with redact + delete, we'd be able to close the DEPR:

Copy link
Contributor

@robrap robrap left a comment

Choose a reason for hiding this comment

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

I'd start with either assertNumQueries (see other comment) or possibly the Django debug toolbar to ensure you are getting the update query before the delete query.

Once confirmed, you could also consider using https://www.mysqltutorial.org/mysql-administration/mysql-binary-logs/ locally to confirm the update is seen before the delete.

Copy link
Contributor

@robrap robrap left a comment

Choose a reason for hiding this comment

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

Looks great. Minor comments. Thank you.


for update_query in update_queries:
sql = update_query['sql']
assert custom_username in sql, f"UPDATE query missing custom username '{custom_username}': {sql}"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure of the exact syntax, but can you update custom_username to something like f"original_username='{custom_username}'" so we verify that the correct field is getting the correct value?

Copy link
Contributor

Choose a reason for hiding this comment

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

@ktyagiapphelix2u: You either missed this comment, or didn't fully understand it. If you updated the code to:

 for retirement in retirements:
                retirement.original_username = redacted_email
                retirement.original_email = redacted_name
                retirement.original_name = redacted_username

Where we are setting the wrong field with each input, I think your test would still pass, right? I was trying to get you to assert that the correct field was being set.

@robrap robrap changed the title fix!: redacting user retirement data in lms fix: redacting user retirement data in lms Jan 30, 2026
assert 'original_username' in sql_lower or 'original_email' in sql_lower, (
f"UPDATE query doesn't appear to update retirement fields: {sql_lower}"
)

def test_simple_success(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think there is any difference between what is being tested here, and what is tested in test_redaction_before_deletion, right? It's the same test, but you are just using two different names and asserting on different things. I think I would just delete test_simple_success and rename test_redaction_before_deletion to test_default_redacted_values. I think test_default_redacted_values already has all the assertions you need. Thoughts?

Copy link
Contributor

@robrap robrap left a comment

Choose a reason for hiding this comment

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

Also, I remove the breaking change "!" from the title, because this is no longer a breaking change. Thanks.

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.

6 participants