[Retention Policy] Keep record marks for indirect-permission subset deletes#8469
Draft
onbuyuka wants to merge 1 commit into
Draft
[Retention Policy] Keep record marks for indirect-permission subset deletes#8469onbuyuka wants to merge 1 commit into
onbuyuka wants to merge 1 commit into
Conversation
…eletes Retention policy subset deletion (specific marked records, e.g. an Email Log or Shopify Log retention policy that is not "apply to all") deleted nothing for tables that require indirect delete permission. Cause: in "Apply Retention Policy Impl.".DeleteExpiredRecords the code took RecordRefDuplicate := RecordRef.Duplicate() and raised OnApplyRetentionPolicyIndirectPermissionRequired with that duplicate. RecordRef.Duplicate() copies the filters and the MarkedOnly flag but NOT the individual record marks, so the subscriber received a marked-only view with zero marks and its DeleteAll(true) deleted nothing. Worse, "Reten. Pol. Delete. Impl.".DeleteRecords had already Close()d the original RecordRef (which still held the marks) before the event ran. Fix: - DeleteRecords: only Close() the RecordRef on the direct-permission path (where it does the DeleteAll itself). For the indirect path leave it open so the marks survive for the caller. - DeleteExpiredRecords: pass the original marked RecordRef to the event, compute RecordCountAfter from it (remaining marked records), then Close() it. The direct path is unchanged (still counts via the duplicate, which is also used for telemetry/caption in both paths). All four subscribers (Email Logs, Retention Policy Logs, Performance Profiler, Shopify Logs) rely on MarkedOnly()/filters + DeleteAll(true) and none close the ref, so passing the original ref is safe. Fixes AB#626315 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Retention-policy subset deletion (specific marked records — i.e. a policy that is not "apply to all records") deleted nothing for tables that require indirect delete permission (Email Logs, Retention Policy Logs, Performance Profiler, Shopify Logs). IcM 21000000010094.
Root cause
In
"Apply Retention Policy Impl.".DeleteExpiredRecords:RecordRef.Duplicate()copies the filters and theMarkedOnlyflag but not the individual record marks. So the subscriber received a marked-only view with zero marks, and itsDeleteAll(true)deleted nothing (the subscribers gate onMarkedOnly()/filters). Meanwhile"Reten. Pol. Delete. Impl.".DeleteRecordshad alreadyClose()d the originalRecordRef— which still held the marks — before the event ran.Fix
RetenPolDeleteImpl.DeleteRecords—Close()theRecordRefonly on the direct-permission path (where this codeunit does theDeleteAllitself). On the indirect path leave it open so the marks survive for the caller.ApplyRetentionPolicyImpl.DeleteExpiredRecords— pass the original markedRecordRefto the event, computeRecordCountAfterfrom it (remaining marked records), thenClose()it. The direct path is unchanged (still counts via the duplicate; the duplicate is still used for telemetry caption/name/number in both paths).Counting note
On the indirect path,
RecordCountAfternow counts the original ref after the subscriber'sDeleteAll, i.e. the marked records still remaining (0 when all were deleted) →NumberOfRecordsDeleted = RecordCountBefore. The previous code counted the markless duplicate (always 0), which combined with deleting nothing under-deleted while still loggingRecordCountBeforedeleted — a misleading count this fix also corrects.Subscriber safety
All four subscribers of
OnApplyRetentionPolicyIndirectPermissionRequireduse the same template (MarkedOnly()/GetFilters()check →RecRef.DeleteAll(true)→ setHandled) and none of them close the ref, so passing the original ref is safe for every consumer.Provenance / status
The previously-attempted ADO PR 243754 (submodule-pointer bump) was abandoned; this implements the fix directly in BCApps. Posting as draft.
Verification⚠️
DeleteExpiredRecords,DeleteRecords, all four subscribers, and theReten. Pol. Filteringmark logic.Linked work item: AB#626315
🤖 Generated with Claude Code