Follow-up to #1163. App-side half of synonymdev/bitkit-core#134.
Problem
ActivityRepo.restoreFromBackup applies the three Core-owned slices of an activity backup inside a single runCatching:
coreService.activity.upsertList(payload.activities)
coreService.activity.upsertTags(payload.activityTags)
coreService.activity.upsertClosedChannelList(payload.closedChannels)
Core fails a bulk write as a whole, so one unusable record aborts the remaining calls. A single tag whose parent activity is missing therefore costs the activities and the closed channels too.
This was reproduced on regtest while building #1163, before that PR stopped routing hardware tags through activity_tags:
Failed to restore activity backup [errorDetails=Failed to insert tag: FOREIGN KEY constraint failed]
activity_tags went from 1 row to 0 and closed channels were skipped entirely, from one bad record.
Why it still matters after #1163
#1163 removes the hardware-wallet trigger by carrying those tags as pre-activity metadata, so this is no longer easy to hit. It is not impossible: the activity backup is assembled from three separate Core reads rather than one snapshot, so a tag written against a brand-new activity between the first and third read lands in the envelope without its parent, in the default wallet scope with no hardware wallet involved.
val activities = activityRepo.getActivities()... // read 1
val closedChannels = activityRepo.getClosedChannels()... // read 2
val activityTags = activityRepo.getAllActivitiesTags()... // read 3
The window is narrow, but the blast radius is not: an entire restored category is lost for one tag.
Requested change
Apply each slice independently so one rejected record cannot discard the others, while still reporting failure overall.
Reporting failure matters: BackupRepo gates the VSS rewrite on restoreFromBackup(...) succeeding, so a partial restore must not be treated as authoritative or it will overwrite a good backup with incomplete state.
Acceptance criteria
- A failing slice does not prevent the other slices from being applied.
- Each slice failure is logged with the slice it came from.
- The overall result is still a failure when any slice failed, so no VSS rewrite is triggered.
- Tests cover a payload where one slice fails and assert the others were still applied and the result is a failure.
Notes
synonymdev/bitkit-core#134 asks Core to keep the valid records within a single bulk write. The two are complementary: Core limits the loss to the bad record, this issue limits the loss to the affected slice. Neither depends on the other.
Follow-up to #1163. App-side half of synonymdev/bitkit-core#134.
Problem
ActivityRepo.restoreFromBackupapplies the three Core-owned slices of an activity backup inside a singlerunCatching:Core fails a bulk write as a whole, so one unusable record aborts the remaining calls. A single tag whose parent activity is missing therefore costs the activities and the closed channels too.
This was reproduced on regtest while building #1163, before that PR stopped routing hardware tags through
activity_tags:activity_tagswent from 1 row to 0 and closed channels were skipped entirely, from one bad record.Why it still matters after #1163
#1163 removes the hardware-wallet trigger by carrying those tags as pre-activity metadata, so this is no longer easy to hit. It is not impossible: the activity backup is assembled from three separate Core reads rather than one snapshot, so a tag written against a brand-new activity between the first and third read lands in the envelope without its parent, in the default wallet scope with no hardware wallet involved.
The window is narrow, but the blast radius is not: an entire restored category is lost for one tag.
Requested change
Apply each slice independently so one rejected record cannot discard the others, while still reporting failure overall.
Reporting failure matters:
BackupRepogates the VSS rewrite onrestoreFromBackup(...)succeeding, so a partial restore must not be treated as authoritative or it will overwrite a good backup with incomplete state.Acceptance criteria
Notes
synonymdev/bitkit-core#134 asks Core to keep the valid records within a single bulk write. The two are complementary: Core limits the loss to the bad record, this issue limits the loss to the affected slice. Neither depends on the other.