Skip to content

Fix delete-resurrection: atomic tombstone check on sync apply#133

Merged
Considus merged 2 commits into
mainfrom
fix/delete-resurrection-guard
Jul 23, 2026
Merged

Fix delete-resurrection: atomic tombstone check on sync apply#133
Considus merged 2 commits into
mainfrom
fix/delete-resurrection-guard

Conversation

@Considus

Copy link
Copy Markdown
Owner

Summary

  • Closes the re-opened delete-resurrection race: a Take deleted while a sync pull is mid-flight could be resurrected (and its tombstone cleared) because the pull loop's guard reads a pull-start tombstone snapshot.
  • New TakeStore.applyRemote(_:) applies a remote Take unless a live tombstone supersedes it — checked atomically with the write inside the store's serial queue, so a concurrent delete cannot interleave. Deletion wins ties; a remote edit strictly after the deletion still resurrects (edit-wins preserved).
  • The 2026-07-20 log-only diagnostics (pull-window breadcrumbs, DailiesViewModel probes) are superseded and removed.

Tests

  • Contract triplet on applyRemote runs against BOTH stores (InMemory + EncryptedTakeStore via the contract-suite subclass).
  • SyncEngine regression test injects a delete mid-pull via a wrapper store — confirmed to fail against the previous upsert path and pass with the fix.
  • coreverify mirrors the store-level checks. Full package suite: 361 tests green locally.

🤖 Generated with Claude Code

Considus and others added 2 commits July 20, 2026 17:17
The Obie widget/intent opens a blank Obie draft in the inline editor;
tapping the background with nothing typed persisted an empty pinned Obie.
`saveInlineEdit`'s blank-discard test carried `&& !t.isObie`, which forced
`isBlank` false for any Obie so it always saved. Drop that term so an Obie
is discarded like any other never-saved blank Take — matching the locked-
capture path (`saveLockedCapture`), which already discarded a blank Obie.
An existing Obie whose text is cleared is still kept via `storedHadContent`.

Device-confirmed on both the home-screen widget and the lock-screen control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h the write

The pull loop's resurrection guard reads a tombstone snapshot taken at
pull-start, so a user delete landing MID-PULL was invisible to it — the
.takeRemote branch then re-upserted the Take and cleared its fresh
tombstone (the re-opened delete-resurrection bug, 2026-07-20).

TakeStore gains applyRemote(_:) — apply-unless-a-live-tombstone-supersedes,
deletion wins ties, remote edits strictly after the deletion still land.
EncryptedTakeStore runs the check and the write inside one queue.sync so a
concurrent main-thread delete cannot interleave; InMemoryTakeStore mirrors
the contract. SyncEngine's .takeRemote branch now routes through it.

The 2026-07-20 log-only instrumentation (SyncEngine pull-window breadcrumbs
+ DailiesViewModel resurrection probes, both uncommitted) is superseded by
the fix and not carried forward.

Tests: store-level contract triplet (runs against BOTH stores via
TakeStoreContractTests inheritance), a SyncEngine regression test driving a
mid-pull delete through a wrapper store (verified to FAIL on the old upsert
path), and mirrored coreverify checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Considus
Considus merged commit d63eaf9 into main Jul 23, 2026
4 checks passed
@Considus
Considus deleted the fix/delete-resurrection-guard branch July 23, 2026 18:06
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.

1 participant