# Archive-first cleanup and recovery

Use when the runner requests clearing source saves/bookmarks after import, or
resumes authorized cleanup. Keep every archived copy. Never use delete_my_data
for source cleanup, or any platform's Clear all. Never post, comment, like,
follow or change account settings.

## Drain small batches

Use one scan per platform/account across batches.

1. Open the source saved list from its top; verify account. Capture at most 50
   cards, expand content, finish import and record inventory per [sync.md](sync.md).
   Missing identity/text, unsupported types and inaccessible folders are explicit
   exceptions; never silently skip them.
2. `plan_source_removal(scan_id, account, limit)` returns archive-backed canonical
   identities plus blocked rows. Verified batches can drain while coverage is
   partial; that does not prove the whole source has been scanned.
3. Open each exact canonical post. Verify current account, source id and saved
   state through fresh UI. Call `get_item(id)` with planned item_id; compare its
   text, attribution and source link to the visible original. Truncation or
   mismatch blocks removal. Retain source saves for incomplete archives.
4. Inspect fresh DOM/accessibility state. Use the supported browser click API on
   that post's Unsave / Remove bookmark control. Never use a generic toggle with
   unknown state. Never reuse row indices after removals.
5. Verify Save / Bookmark is now offered, or an explicit removal confirmation.
   Call `record_source_removal(scan_id, account, item_id, result, evidence)`
   immediately. result: removed, already_absent (verified before clicking), failed.
   Disappearing rows, deleted/unavailable posts and loading errors are not proof
   of removal. Archive remains intact.
6. Refresh source from top and repeat, including every folder/section. If a
   previously removed item reappears, inspect whether it was saved again and start
   a new scan; archive dedupe must not suppress it. Failed items remain pending.

Evidence: observed labels, canonical id, UTC time, counts and local screenshot
reference when available. Exclude credentials, signed URLs, private post text
and unrelated account data. Receipts are caller observations, not independent
server verification. If interrupted after clicking, inspect current state before
retrying; never toggle it back on. Retry transient failures at most twice per run,
preserve exceptions, continue independent work.

## Final reconciliation

Reload both sources and inspect every saved-content section/folder. Require
explicit empty state, not blank/loading/error pages. New saves trigger another
capture/import/removal batch. No reliable end/empty signal means unverified coverage.

Once all observed items have verified removed/already_absent outcomes and the
source is visibly empty, call
`complete_source_scan(scan_id, account, expected_count, unresolved, end_evidence, evidence)`
with empty_state, zero unresolved and cumulative unique inventory count. Then
`verify_source_empty(scan_id, account, remaining_count, evidence)` with zero
remaining and fresh reload evidence. Repeat independently for both platforms.

Report platform, account, archived, removed, already absent, failed/pending,
source remaining, coverage and verification time. Say "none left" only when both
sources have fresh explicit empty-state evidence, zero unresolved exceptions and
zero pending removals. Historical receipts do not establish today's source state.

## Resume

`source_scan_status(scan_id)` restores progress.
`plan_source_removal(scan_id, account, limit)` returns unresolved candidates;
completed outcomes do not repeat. Failed outcomes can become verified success;
duplicate success receipts are idempotent. After 24 hours, capture a new scan
before planning more removals. Account mismatch stops that source only. Reconnect
expired auth; never bypass login or browser restrictions. Outstanding work stays
visible, with exact reason and next action.
