Skip to content

fix(android): preserve SAF download replacements - #434

Open
veryCrunchy wants to merge 7 commits into
mainfrom
fix/android-owned-publication-recovery
Open

fix(android): preserve SAF download replacements#434
veryCrunchy wants to merge 7 commits into
mainfrom
fix/android-owned-publication-recovery

Conversation

@veryCrunchy

Copy link
Copy Markdown
Member

What changed

  • stage and flush SAF downloads before replacing the visible local item
  • protect an existing file or directory with an app-owned backup until publication is verified
  • persist recovery ownership before mutating the document provider
  • reconcile interrupted publication and retry backup cleanup after restart
  • hide only durably owned stage and backup documents from sync scans
  • use the same staged publication path for directory-to-file and file-to-directory replacements
  • preserve UUID-shaped user files that are not recorded as app-owned recovery state

Validation

  • focused AndroidSafDownloadPublicationTest
  • focused AndroidFileSyncEngineInvariantTest
  • :androidApp:compileDebugKotlin
  • bash tools/check-kotlin-architecture.sh
  • bash tools/check-repository.sh after binding this PR number in the changelog fragment

Limits

  • validation used deterministic unit tests and Android compilation on the Linux build host
  • live SAF provider and device process-death validation remain follow-up evidence

Found by the code-first reliability audit.

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 19:35 Destroyed
@obiente-cloud

obiente-cloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

Obiente preview

NC Native · b1681c6083d2 · Ready

Open preview

View in Obiente

Obiente updates this comment as the preview changes.

@veryCrunchy
veryCrunchy marked this pull request as ready for review September 1, 2026 19:36
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 19:36 Destroyed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T22:39:28.478804Z b1681c6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a8c0e0147

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 20:13 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c0faeedc4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 20:35 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 544b61b1e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 21:01 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d40a0c839

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #434 / NC Native September 1, 2026 21:30 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3c120ddd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1681c6083

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 313 to +317
require(current?.entry?.revision == expectedLocalRevision) {
"The local file changed after the sync scan."
}
require(current.entry.kind == SyncEntryKind.File) { "The local item changed type." }
}
val replacementSnapshot = current?.let(::replacementSnapshot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve scan-time content identity before replacement

When a SAF provider keeps the document ID, timestamp, and size unchanged, a same-size local edit made after the scan but before this method runs passes the revision check, and replacementSnapshot then records the already-edited bytes as its expected state. The later content revalidation therefore succeeds and publication deletes the backup containing the unrecognized edit. Fresh evidence beyond the earlier during-staging finding is that strong content evidence is first captured only after the weak scan-time guard; compare the protected item with scan-time content evidence, or report a conflict when that evidence is unavailable.

AGENTS.md reference: AGENTS.md:L375-L376

Useful? React with 👍 / 👎.

Comment on lines +407 to +409
if (Thread.currentThread().isInterrupted) {
throw kotlinx.coroutines.CancellationException("Local replacement verification cancelled.")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Observe coroutine cancellation while hashing replacements

When a remote file replaces a large local directory and WorkManager cancels during the post-download snapshot, this loop checks only thread interruption. withAndroidFileSyncRunCancellation represents coroutine cancellation in its cancellation token and does not interrupt the worker thread, but that token is not passed here, so hashing can continue and the synchronous publisher can rename and delete the local item after cancellation. Pass the run cancellation predicate into replacement verification so cancellation stops before publication.

AGENTS.md reference: AGENTS.md:L274-L276

Useful? React with 👍 / 👎.

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