Skip to content

fix(import): the later backup decides a message's mark and text, unsent part included (#1741, #1804) - #1983

Merged
mbeisser1 merged 9 commits into
mainfrom
fix/1741-later-backup-decides
Oct 8, 2026
Merged

mbeisser1 merged 9 commits into
mainfrom
fix/1741-later-backup-decides

Conversation

@mbeisser1

Copy link
Copy Markdown
Member

Bug Description

Expected: Between two copies of one message from one source, the copy from the later backup decides the message's mark (Deleted in the source app or Unsent, mark or no mark) and its text, parts, earlier versions and unsent marks, across imports and within one import, in any file order. A file without a backup date keeps the rules for files without one: a mark adds and is never cleared, and the copy whose newest earlier version is newer gives the text.

Actual (before #1924): A mark was only ever added, never cleared, and within one import the first file staged decided the mark, because staging_messages is unique on (account_id, source, guid) and the insert is ON CONFLICT DO NOTHING (#1741). An append took a later edit only when its newest earlier version was newer, so a later backup whose only change was an unsent part, or one edit after an unsend, kept the older text (#1804).

Root Cause

The conversation file did not say when its backup was made, so the import could not tell which copy was newer and used rules of thumb: marks add, and version times compare.

Fix Description

#1924 (commit 7a8435e, PR #1966) already built both decisions. It added export.backup_taken_at_unix_ms to the conversation file, staging_messages.backup_taken_at and messages.backup_taken_at, and the one rule later_backup_sql / later_backup in crates/server/server/src/db/staging.rs. promote_deletion_marks lets the later backup set or clear the mark across imports; write_edit_map takes the later backup's text and versions whatever their times say, and keeps later_edit_sql only as the fallback for undated (or equally dated) copies; add_staged_copy in imports_api/staging.rs handles the second copy that ON CONFLICT DO NOTHING skips, so the second file of one import gives its mark and text. docs/architecture/contacts-identities-and-messages.md, the common-message page and the user import page say the rule and that a file without a date keeps marks additive. Its tests in imports_api/tests/backup_dates.rs cover the #1741 criteria in every order (together, together reversed, apart, apart reversed), the swapped dates, the undated fallback, and the #1804 text case.

What was left, and what this PR adds:

No code change was needed.

How to Reproduce (Before Fix)

  1. Check out the commit before 7a8435e (or disable the date comparison in later_backup_sql and later_backup).
  2. Run cargo test -p message-crate-server --lib backup_dates::the_later_backup_decides_an_unsent_part.
  3. It fails on the first order: the message holds A's text and versions and the earlier backup's date.

How to Verify (After Fix)

  1. cargo test -p message-crate-server --lib backup_dates (10 tests pass).
  2. With the date comparison disabled (AND 0 in later_backup_sql, && false in both later_backup arms), the new test fails with together: left: Held { text: "zqharbor zqyonder", … } right: Held { text: "zqbeacon", … }. Checked locally, then reverted.
  3. ./scripts/check-pr.sh and cargo test -p message-crate-server pass.

Impact Assessment

Regression Risk

None in behaviour: this PR adds one test and two CHANGELOG entries.

Checklist

  • Root cause identified and documented above
  • Fix addresses the root cause (not just symptoms)
  • Added test to prevent regression
  • Existing tests pass locally
  • Tested the specific reproduction steps

Related Issues

Closes #1741
Closes #1804

Built on #1924 (PR #1966).

🤖 Generated with Claude Code

mbeisser1 and others added 2 commits October 7, 2026 23:49
The #1924 tests ran the #1804 scenario without its unsent part: backup B
carried no mark. The new test gives B the Unsent mark its unsent part
brings, and checks every order, one import of both files in either order
included: with B the later backup the message holds B's text, versions and
mark and search finds neither copy's text; with the dates swapped it holds
A's with no mark and search finds A's text; with no dates the version
times pick A's text and B's mark adds, as #1801 left them.

Part of #1741, #1804.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two Fixes entries for the 0.11.0 Importing list: a mark is cleared by a
newer backup that no longer has it, and the second file of one import no
longer loses its mark (#1741); a newer backup that unsent a part gives the
message its text whatever the edit times say (#1804).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review of 42be2b2 (the PR head, up to date with main). Standards 6, Spec 3, Correctness 2. Three findings with no line in the diff follow in a top-level comment. Each is answered in its thread.

Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs Outdated
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs Outdated
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs Outdated
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs Outdated
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs
Comment thread crates/server/server/src/imports_api/tests/backup_dates.rs
@mbeisser1

Copy link
Copy Markdown
Member Author

Findings of the review of 42be2b2 with no line in the diff. Each is answered by a later marked comment quoting it.

  1. Spec · judgement (crates/server/server/src/imports_api/tests/backup_dates.rs, the_later_backup_decides_the_text_whatever_the_version_times_say, outside the diff). The new test now does everything this one does, plus the mark, yet this one's doc comment still calls itself "The scenario of An append keeps the older text when a later backup only unsent a part #1804". Fix: delete it, or retitle it as the case without a mark.

  2. Spec · judgement (crates/server/server/src/db/staging.rs, the doc comment on later_edit_sql, on main already). It still says "The one rule for which of two copies of a message is the later backup", which contradicts later_backup_sql. Fix: say it is the fallback for undated copies, here or in a follow-up issue.

  3. Correctness · bug (crates/server/server/src/db/staging.rs, add_staged_copy / take_later_staged_copy / add_staged_copy_mark, on main already). When one import holds a dated file and an undated one, the stored backup date depends on which file comes first, and a later import then gives different results. Import 1 holds A (dated EARLIER, no mark) and B (no date, Unsent). Order A,B: the staged row keeps EARLIER and adds B's mark. Order B,A: the staged row keeps NULL, because later_backup returns Undecided and neither take_later_staged_copy nor add_staged_copy_mark sets backup_taken_at. Import 2 is C (dated LATER, no mark): after A,B the mark is cleared; after B,A it stays unsent, because promote_deletion_marks falls back to marks-add. The module comment and add_staged_copy's doc say one import stores the same in either file order (One import of two backups holding a new message stores both rows' earlier versions #1806, One import of two backups drops the attachments and reactions of the copy staged second #1837); that holds for the mark and text but not for the date that decides later imports. Fix: in the Undecided case have the staged row keep a date when either copy has one, with a test; or decide on purpose that a mixed import stores no date and test that.

mbeisser1 and others added 5 commits October 8, 2026 00:00
The fix landed with #1966, whose Features entry already describes it.
Drop the two Fixes bullets that repeated it under a later date.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fold the unsent-part test into the_later_backup_decides_the_text_whatever_the_version_times_say:
backup B now carries the Unsent mark, and the test checks search and the
undated files there. It also counts earlier versions in
message_versions_fts, which keeps them for an Unsent message, so a stale
version of backup A or a missing one of backup B fails the test.

every_order returns each order's name and database with what it holds,
so no test copies its order names or database path. text_hits sits with
the other helpers and replaces the inline count in the searchable test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…her order

When one import held a dated file and an undated one, the staged message
kept the date only when the dated file was read first. Read second, the
dates could not decide, nothing set the date, and a later import fell
back to adding marks: a backup made later without the mark left the
message Unsent. The staged message now takes the dated copy's date when
it has none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Its doc comment still called it the one rule for which copy is the later
backup, which later_backup_sql now is. Also part later_backup_sql's two
paragraphs, which ran together.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e in either order"

This reverts commit 6c96678. Giving the staged row the dated copy's
date whenever an undated copy also contributed makes the dated copy's
date decide for the undated copy too at promotion, which drops a mark
or an edit the undated copy brought when the stored message came from a
later backup. Neither rule stores what two separate imports store in
every order, so the order-dependence of a mixed import is filed as its
own issue rather than fixed here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mbeisser1
mbeisser1 marked this pull request as ready for review October 8, 2026 04:22

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Re-review of the fix commits (Standards 3, Correctness 1), posted after the push at d92d4b2. Each is answered in its thread.

All four findings are about commit 6c96678, which 6562383 reverts, so their lines are no longer in the diff and each is listed and answered here.

  1. Standards · rule (docs/architecture/, the backup date rule). The architecture doc's "backup_taken_at keeps the date of the backup that decided the stored copy" no longer matched 6c96678: a mixed import stored the dated copy's date beside the undated copy's mark.
    Answered by the revert 6562383: the attempted fix is withdrawn and the order-dependence is Deferred to #1989.

  2. Standards · judgement (crates/server/server/src/imports_api/staging.rs). 6c96678 pushed a doc comment line to about 115 characters.
    Answered by the revert 6562383: the attempted fix is withdrawn and the order-dependence is Deferred to #1989.

  3. Standards · judgement (crates/server/server/src/imports_api/tests/backup_dates.rs). The test 6c96678 added copied the order names instead of using every_order, and left out the two "apart" orders.
    Answered by the revert 6562383: the attempted fix is withdrawn and the order-dependence is Deferred to #1989.

  4. Correctness · bug (crates/server/server/src/db/staging.rs, with 6c96678). With a stored message from a LATER backup, one import of an undated Unsent copy plus an EARLIER dated copy got the date EARLIER, and the Unsent mark was dropped at promotion, where two separate imports of the same files keep the message Unsent.
    Answered by the revert 6562383: the attempted fix is withdrawn and the order-dependence is Deferred to #1989.

@mbeisser1

Copy link
Copy Markdown
Member Author

Answers to the findings in #1983 (comment).

  1. Spec · judgement … The new test now does everything this one does, plus the mark, yet this one's doc comment still calls itself "The scenario of An append keeps the older text when a later backup only unsent a part #1804".

Fixed in 28350fd: the two tests are now one. the_later_backup_decides_the_text_whatever_the_version_times_say gives backup B the Unsent mark and is the only test of the #1804 scenario.

  1. Spec · judgement … later_edit_sql … still says "The one rule for which of two copies of a message is the later backup", which contradicts later_backup_sql.

Fixed in 75218e2: the doc comment on later_edit_sql now says it is the fallback for copies that are undated or have equal dates, and later_backup_sql is the rule.

  1. Correctness · bug … When one import holds a dated file and an undated one, the stored backup date depends on which file comes first, and a later import then gives different results.

Deferred: #1989. A fix was tried (6c96678) and reverted in 6562383: giving the staged row the dated copy's date lets that date decide for the undated copy's mark and text at promotion, which drops a mark separate imports keep. The issue records both scenarios.

@mbeisser1

Copy link
Copy Markdown
Member Author

Review summary for 42be2b2, pushed as d92d4b2 (the PR with main merged in twice, conflict-free).

Every acceptance box of #1741 and #1804 was verified against the tests from #1966 plus the one merged here. No commit was needed for CI and no user thread is open. CI run 37727199790 on d92d4b2: success.

@mbeisser1
mbeisser1 merged commit 0f7f36a into main Oct 8, 2026
28 checks passed
@mbeisser1
mbeisser1 deleted the fix/1741-later-backup-decides branch October 8, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant