# Make absence-based deletions reversible: soft marking, grace windows and post-incident repair

A procedure for the case where a job has already deleted, archived or recreated records because a listing wrongly looked complete. Delay destructive absence actions behind a two-phase soft mark and a grace window sized to the source's own recovery time, record the observation that justified each action, and repair the damage afterwards by re-verifying against a healthy source rather than by blind rollback.

Exact reference: {"kind":"skill_version","skill_id":"skl_53jx9JrqPxFJTV-wjmcBOQ","version_id":"skv_hza7kpsBVyqsxYnFb0Us3A"}

Applicability: [{"constraint":"Jobs that delete, archive, revoke or recreate local records based on their absence from a remote listing, export or feed","technology":"data reconciliation and sync jobs","version_scheme":"unknown"},{"constraint":"Any system where a completeness or end-of-list signal from a dependency can be reported correctly but be false","technology":"distributed systems and API aggregation","version_scheme":"unknown"},{"constraint":"Repairing records already removed or duplicated by an automated absence-based decision","technology":"incident recovery and data repair","version_scheme":"unknown"}]

# Make absence-based deletions reversible: soft marking, grace windows and post-incident repair

## When to use

Use this when a job takes a destructive or conclusive action **because a record was missing** from something it read: deleting local rows absent from a remote listing, archiving accounts absent from a directory export, revoking access absent from a permissions feed, or creating a "new" record because a lookup found no prior one.

This is the companion to the rule that you must not act on an absence unless the source answered *and* its answer is confirmed complete. That rule is about the decision. This guidance is about the two things it does not cover:

- The completeness signal itself can be wrong. A source can report an end-of-list marker and still have served a truncated listing; a snapshot can be taken from a replica that had not caught up; a filter change can make records legitimately drop out of scope and then come back.
- Once wrong deletions have run, someone has to find and undo them, and a naive rollback usually makes it worse.

Assume your completeness check will eventually be wrong, and design so that being wrong is survivable.

## Rule

**An absence must be observed more than once, across a window longer than the source can plausibly take to recover, before anything irreversible happens — and every absence-based action must carry enough evidence to be undone.**

## Procedure

### 1. Split the action into a mark phase and an execute phase

The mark phase is cheap and reversible. It records a *candidate* for deletion; it does not delete. It stores, alongside the record:

- the identity of the source listing that did not contain the record,
- the position or watermark that bounded that listing (final cursor, snapshot id, export sequence),
- the time the observation was made,
- the completeness evidence that was accepted (end-of-list marker, exact count, snapshot id).

The execute phase runs later and independently. It deletes only records whose mark is still standing and which satisfy the confirmation conditions below.

The value of the split is that a mark is auditable while it is still harmless. A wrong completeness check produces a visible spike in marks *before* it produces data loss, which is the only cheap moment to catch it.

### 2. Require repeated, independent confirmation

Do not execute on a single observation, however complete it looked. Require that the record was absent from **at least two listings that do not share a failure mode**. Two consecutive pages of the same truncated export are one observation, not two. Useful separations:

- different fetches separated in time, so a transient truncation is unlikely to repeat identically,
- a different access path where one exists — for example a direct lookup of that specific identifier, rather than another full listing.

A direct per-record existence check is the strongest confirmation available and is usually cheap, because the number of marked candidates is small even when the listing is large. Prefer it whenever the source offers one. A listing says "I did not mention it"; a direct lookup says "it is not there", and only the second statement is about the record.

### 3. Size the grace window to the source's recovery time

The window between mark and execute must exceed the longest plausible time for the source to return to a correct answer. Derive it, do not round it:

- replication or index rebuild lag at its observed worst, not its median,
- retention of the source's own backfill or catch-up process,
- the interval at which the source itself is refreshed — a feed regenerated nightly cannot confirm anything twice within one night, so its window is at least two cycles.

A window shorter than one refresh cycle of the source provides no confirmation at all: it just re-reads the same stale artifact. This is the most common way a grace window becomes decorative.

### 4. Clear marks on reappearance, and treat clearing as a signal

If a marked record appears in a later listing, clear the mark and record that it was cleared. The clear rate is a free, continuous test of your completeness logic. A steady trickle is expected; a burst means a listing you accepted as complete was not, and the execute phase for that source should be paused before it drains the backlog.

Alert on the clear rate, not only on listing errors. Listing errors are the case you already handle; wrongly-confident successes are the case that hurts, and the clear rate is the only place they surface early.

### 5. Make the execute phase recoverable rather than destructive

Prefer an action that can be reversed without reconstructing state: a soft-delete flag, a move to a quarantine area, a revocation that can be re-granted. Keep the reversible form for at least as long as it would take a human to notice the problem — which is typically far longer than the grace window, because nobody looks until a user complains.

Whatever form it takes, each executed action writes a repair record containing the record identity, the full prior state (or a pointer to a retained copy), both justifying observations, and the execution time. Without the justifying observations you cannot scope a repair later; you will only be able to ask "what did this job delete", not "what did it delete *because of the incident*", and the difference is between repairing a hundred records and reviewing a million.

### 6. Repair by re-verification, not by rollback

After an incident where absence-based actions ran on bad data, do not restore everything the job touched during the incident window. Some of those deletions were correct, and restoring them resurrects records that were genuinely removed at the source — which then get deleted again on the next run, producing a confusing flap, or worse, get treated as new.

Instead:

1. **Scope by evidence, not by time.** Select repair records whose justifying observations came from the affected source and overlap the incident, using the recorded listing identity and watermark. Time alone over-selects, because unrelated correct deletions ran in the same period.
2. **Wait for the source to be demonstrably healthy.** Repairing against a source that is still degraded reproduces the original bug. Require a fresh listing that passes the completeness check *and* whose size is consistent with the pre-incident baseline. A listing that is complete by its own marker but a third smaller than yesterday's is still broken.
3. **Re-verify each candidate individually.** Direct lookup per record beats another listing. Records that exist at the source were wrongly removed and should be restored; records genuinely absent should stay removed, with the repair record annotated so the same candidate is not re-examined on a later pass.
4. **Restore idempotently under stable identity.** Restore keyed on the source's own identifier, not on a locally generated one, so that a repair run interrupted halfway and restarted does not create second copies.

### 7. Repairing wrongful creation is a different job

The mirror-image failure — a lookup returned nothing because it was incomplete, so the job created a record that already existed — does not repair by deleting the new record. The new record may have accumulated its own references, activity or child rows since it was created.

Repairing that is a merge: pick a surviving identity, re-point references, then retire the other. It needs a stable natural key to match the pair, which is exactly what was missing when the duplicate was created. If your create path has no such key, the duplicate is not automatically repairable at all, and the mark-and-confirm discipline above matters more, because there is no cheap undo waiting at the end.

## Boundary: what this does not make safe

Reversibility ends at your own storage. An action with an external effect — a notification sent, a payment issued, an external account deleted at a third party, a webhook delivered — cannot be repaired by this procedure, because the repair record can only restore your side. For those, confirmation must be complete *before* the action, and the grace window is not a safety net but the entire safety mechanism.

Similarly, the procedure assumes the absence decision is at worst *wrong*, not *adversarial*. A source that can be induced to omit records by whoever benefits from their deletion is an authorization problem, and no grace window addresses it.

## Reasoned walkthroughs

These are worked examples, not executed tests. They are included because the failure modes only become visible when the timings are made concrete.

**A window shorter than the refresh cycle.** A directory export is regenerated once every twenty-four hours. A job marks absent accounts and executes after a six-hour grace window, re-reading the export twice more in that time. All three reads return the same file. One night the export is generated during a partial outage and omits a department. Six hours later, with "three independent confirmations", every account in that department is deleted. The window looked conservative and confirmed nothing, because all three observations came from one artifact. Two full cycles plus a direct lookup per candidate would have caught it at the first lookup.

**Scoping a repair by time instead of evidence.** A reconciliation job deletes roughly two hundred records a day in normal operation. During a four-hour incident it deletes nine thousand. Restoring everything deleted in a window that spans the incident restores the correct deletions from that period too. Those records are absent at the source, so the next run marks them again and deletes them again — and any downstream consumer that saw them reappear now has a spurious create-and-delete pair. Scoping by the recorded listing identity selects the wrongly deleted set and leaves the correct deletions alone.

## Checks worth running before relying on this

- Make a listing return an end-of-list marker on a deliberately truncated result. Confirm that candidates are marked, that nothing is executed, and that the mark rate is visible somewhere a human would see it.
- Restore the truncated records before the window expires. Confirm every mark clears and that the clear burst raises an alert.
- Let the window expire with the truncation still in place, with per-record direct lookups enabled. Confirm the lookups override the listing and nothing is deleted.
- Run a repair pass twice against the same incident. The second pass should change nothing — no duplicate restores, no re-examination of candidates already confirmed genuinely absent.
- Interrupt a repair pass midway and restart it. Confirm restoration is keyed on source identity and produces no second copies.

## Pitfalls

- A grace window measured in wall-clock time while the source refreshes on its own slower cycle, so repeated reads observe one artifact.
- Counting retries of the same failed fetch as independent confirmations.
- Hard-deleting immediately and relying on database backups as the repair path. Backups restore a whole point in time, not a selected set, and restoring one is usually more disruptive than the original loss.
- Repair records that store only the identifier and not the prior state, so the repair can identify the damage but not undo it.
- Treating a filter or scope change as a data outage. Records that legitimately left scope will mark and never clear; if you cannot tell the two apart, a configuration change silently drains the backlog. Version the filter definition in the mark, and invalidate outstanding marks when it changes.
- Running the execute phase from the same process and schedule as the listing fetch, so a bad fetch and its consequences land in the same run with nothing in between to inspect.

## Evidence level

This guidance is reasoned design analysis. No tests, reproductions, incident data or benchmarks were executed for it, and it cites no external sources. The walkthroughs above are constructed examples used to expose the timing failures, not observed incidents. The suggested checks are proposed verification steps for adopters; they have not been run.

## Supporting basis and limitations

The basis is reasoning only. No test, reproduction, benchmark, incident review or production observation was executed for this skill, and no external sources are cited. The two walkthroughs (a six-hour grace window over a twenty-four-hour export cycle, and a repair scoped by time across an incident that deleted nine thousand records against a two-hundred-per-day baseline) are constructed examples chosen to make the timing failures concrete; the numbers are illustrative, not measured. The checks listed under "Checks worth running" are proposed verification steps for adopters and have not been run by the author. The gap being filled was identified by reading published guidance on preserving partial results and distinguishing empty data from an unavailable dependency: that guidance covers per-source status modeling, a completeness flag separate from status, and a guard that blocks absence-based actions, and its own basis note states that the recovery procedure (two-phase marking, grace windows, repairing absence-based actions after an outage) was deliberately left out of its scope. A further search for guidance on post-outage repair of absence-based deletions returned no skill covering it. This skill is written to sit beside that guidance without restating its state model: it assumes the prevention rule is in place and addresses only the case where the completeness signal was accepted and turned out to be false. No conversation is attached as a source, and no reviewer has assessed this content.

## Change and rationale

New standalone skill covering the recovery half of the partial-results problem: what to do once absence-based deletions or creations have already run on a listing that wrongly appeared complete. Specifies a two-phase mark-then-execute split, independent repeated confirmation with per-record direct lookup preferred over a second listing, grace windows derived from the source's own refresh and catch-up cycle, mark-clear rate as an early signal that a completeness check is wrong, repair records carrying the justifying observations, and a repair procedure that re-verifies per record against a demonstrably healthy source instead of rolling back a time window. Covers the mirror case of wrongful creation as a merge problem, and bounds the guidance at external side effects and adversarial sources.

Existing guidance on partial results stops at the decision boundary: it says never act on an absence unless the source answered and the answer is confirmed complete. That leaves two gaps. First, the completeness signal can itself be wrong — a truncated export with an end-of-list marker passes the check — so a rule that trusts the check has no defense in depth. Second, nothing covers what to do after wrong deletions have already run, which is when people actually need a procedure. The non-obvious parts are that a grace window shorter than the source's own refresh cycle confirms nothing while appearing conservative, that the mark-clear rate is a continuous free test of completeness logic, and that repairing by restoring a time window resurrects correct deletions and causes a second incident. These are narrow and procedural enough to stand alone rather than enlarge the existing skill, whose scope is detection and prevention.
