Skip to content

fix(sync): capture upload metadata and payload together - #208

Merged
johnny4young merged 6 commits into
mainfrom
fix/coherent-upload-snapshots
Oct 9, 2026
Merged

johnny4young merged 6 commits into
mainfrom
fix/coherent-upload-snapshots

Conversation

@johnny4young

@johnny4young johnny4young commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Single and bulk upload payloads are now decoded from the same captured database row as their metadata. This preserves content-addressed blob identity and the existing encryption and policy behavior.
  • Parameterized real-WAL tests commit a text edit after the upload SELECT. They check that the captured body stays coherent and that acknowledging it keeps the newer revision pending. Explicit timeout assertions make sure a missed interleaving cannot pass silently.
  • Documented the outbound snapshot invariant and updated the package capability comment.

Changes

  • pendingUpload(id:) and pendingUploads() decode content with content(from:) on the row they selected, instead of looking it up again by id.
  • Folded the now-unused content(for:includingExpired:) into content(for:). Behavior is unchanged: expired rows are still filtered.
  • ARCHITECTURE.md now states what the captured-row invariant covers. The record's system fields and board membership are still read in separate transactions.
  • Added a user-facing Fixed entry to the untagged 0.10.0 CHANGELOG section and a mention in the v0.10.0 release notes.
  • Merged current main, which includes fix(sync): re-register clips an applied remote change leaves dirty #209 (re-registering clips left dirty by a remote apply). The two fixes touch different parts of SyncLocalStore.swift and do not interact.

Follow-ups (not in this PR)

  • Clips deleted while their upload is being prepared. A clip that was never synced and is deleted after its upload row is read can still be uploaded, with no tombstone queued for it. Before this PR, the same window uploaded a metadata-only record, so the gap already existed. Closing it needs a decision in prepare/acknowledge or in the deletion path.
  • One read for the whole record. Reading system fields and membership in the same transaction as the row would require a change to the SyncLocalStore protocol.
  • Blob read race. A blob can be removed by a concurrent orphan sweep between the existence check and the read. Fixing that belongs in BlobStore.
  • Test coverage. Binary and concurrent-deletion cases for the snapshot test.

Testing

  • CI on the final head: lint, package tests and coverage, macOS and iOS app builds, and the dispatched full UI test workflow.
  • Locally (no Swift toolchain): swift-format lint --strict on the touched files, check-version-sync and check-product-truth.

Checklist

  • make format and make lint pass
  • make test passes
  • Conventional Commit message (no AI co-authorship / "generated-by" trailers)
  • No real clipboard content in logs, tests, or fixtures; test data is synthetic
  • Docs / CHANGELOG updated if behavior changed

Generated by Claude Code

johnny4young and others added 6 commits October 9, 2026 14:33
- Fold content(for:includingExpired:) into content(for:); upload preparation no longer reselects content by id, so no caller passes true.
- State in ARCHITECTURE.md what the captured-row invariant covers and what is still read separately.
- Mention the coherent upload fix in the v0.10.0 release notes.
@johnny4young
johnny4young marked this pull request as ready for review October 9, 2026 21:25
@johnny4young
johnny4young merged commit 33f850c into main Oct 9, 2026
6 checks passed
@johnny4young
johnny4young deleted the fix/coherent-upload-snapshots branch October 9, 2026 21:40
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.

2 participants