fix(aw-sync): scan pre-#697 buckets by origin to avoid re-import after Android hostname migration - #708
Conversation
…e-import after Android hostname migration Closes ActivityWatch#707. When both the raw-ID and sanitized-ID lookups miss in `get_or_create_sync_bucket`, fall back to scanning the destination for a `-synced-from-` bucket whose base ID matches and whose `$aw.sync.origin` sanitizes to the same value. This recovers the case where a desktop imported an Android peer before ActivityWatch#697 landed (`…-synced-from-POCO F8 Ultra` with `$aw.sync.origin = "POCO F8 Ultra"`), then the phone ran ActivityWatch/aw-android#273 and its staging hostname became `poco_f8_ultra` (sanitized, no `$aw.sync.origin` on first-hand buckets). The two direct lookups both produced `…-synced-from-poco_f8_ultra`, missed, and created a new bucket — causing a full re-import (every event twice in /timeline). The fallback scan finds the legacy bucket by matching sanitized origins and reuses it. When two distinct pre-ActivityWatch#697 buckets share the same sanitized origin (ambiguous), the function returns an error rather than silently merging distinct histories (ActivityWatch#697 :368). Two tests added: - `test_pre697_origin_scan_resumes_legacy_bucket`: desktop with `…-synced-from-POCO F8 Ultra` + `$aw.sync.origin` receives a post-migration pull and resumes without creating a fork. - `test_pre697_origin_scan_refuses_ambiguous_candidates`: two legacy buckets with different raw origins that sanitize identically trigger an error. Git-Session-Id: 3846
🤖 AI code reviewSafe to merge — 2 findings disposed (accepted-tradeoff; rejected)Updated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
Adds a fallback scan in get_or_create_sync_bucket for pull operations: when both the raw-ID and sanitized-ID lookups miss, it lists all destination buckets, filters those whose base ID (before -synced-from-) matches the source's base ID and whose $aw.sync.origin sanitizes to the same value as the source's origin, and reuses the single match or errors on multiple matches. Adds two integration tests covering the resume and ambiguity-refusal paths. Not safe to merge — 1 P1 openConfidence 3/5 2 findings · ❌ 1 P1 ·
|
| commit | score | findings | engine | when |
|---|---|---|---|---|
749d83add41b |
4/5 | 2 | llm | 2026-09-17 14:56 UTC |
1d3cc39ad16f |
3/5 | 2 | llm | 2026-09-17 18:08 UTC |
Reviewed a90bed070fb3 · openrouter/deepseek/deepseek-v4-flash-0731 · llm engine · 47s · about this reviewer
Maintainer commands
@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.
|
@TimeToBuildBob Logic is right and the key is complete: Two test tightenings before merge, then LGTM:
On your reviewer's findings: P2 #1 ( Nit, optional: |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #708 +/- ##
==========================================
+ Coverage 70.81% 79.92% +9.10%
==========================================
Files 51 74 +23
Lines 2916 7974 +5058
==========================================
+ Hits 2065 6373 +4308
- Misses 851 1601 +750 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…r sync Two tightenings per ActivityWatch#707 review: 1. test_pre697_origin_scan_resumes_legacy_bucket: seed the legacy bucket with one event at T0, put T0-1h and T0+1h in the source. After sync, assert exactly 2 events (existing + new). 3 would mean the cursor was ignored and the full history was re-imported. 2. test_pre697_origin_scan_refuses_ambiguous_candidates: add a second, healthy source bucket (aw-watcher-window). sync_datastores must return Ok overall — the ambiguous android bucket is skipped (warn+continue), not a fatal abort. Assert the healthy bucket received events; assert neither ambiguous legacy bucket was written to. Git-Session-Id: eacd
|
Done in 1d3cc39:
Skipped the optional |
…e bucket The refusal test asserted both legacy candidates received zero events, but the ambiguous source bucket held no events — so a regression that silently reused one candidate would also have copied nothing and the assertions would still pass. Insert an event into the ambiguous source bucket (plus a premise guard that it really has one) so only an actual skip keeps both legacy buckets at 0. Git-Session-Id: 9059e8cf-aa8c-5d86-bc5d-0167df2a66b0
|
Head advanced to Your point 2 (the test must not pass for the wrong reason) was only half-landed: the ambiguous source bucket carried no events, so the
|
|
AI reviewer updated on P1 ("pre-#697 buckets lack P2 ("resume test doesn't assert specific events") — marginal. The count=2 assertion (vs. 3 = cursor ignored) is the proof of no re-import, which is the test's goal. A wrong-cursor-direction bug (imports before-T0 but not after-T0) is a distinct failure mode outside this PR's scope. Leaving as-is.
Does your LGTM extend to |
Convergence Adjudication — Merge RecommendationHead verified: Fixed (this review arc)
Findings disposed
CIAll platforms green: Android, macOS, ubuntu, windows, clippy, format, coverage. Domain riskSync logic is inherently stateful. The fallback scan is guarded by both existing exact lookups (raw + sanitized), so it only runs on first import of a bucket that was never mapped. The known limitation (sole normalized-origin candidate from a different device) is pre-existing in the sanitized-ID path and is the accepted tradeoff. Convergence
|
Closes #707.
Summary
When both the raw-ID and sanitized-ID lookups miss in
get_or_create_sync_bucket, add a fallback scan of the destination's-synced-from-buckets for one whose base ID matches and whose$aw.sync.originsanitizes to the same value as the current pull target.This recovers the case described in #707:
aw-watcher-android-synced-from-POCO F8 Ultrawith$aw.sync.origin = "POCO F8 Ultra"(stamped by fix(aw-sync): sanitize whitespace hostnames on import; make per-bucket errors non-fatal #697 on import).poco_f8_ultra(sanitized); first-hand buckets carry no$aw.sync.origin.new_idandsanitized_idas…-synced-from-poco_f8_ultra, both miss, a new bucket is created → full re-import (every event twice in/timeline, old bucket orphaned).With this fix the fallback scan finds the legacy bucket via its stamped
$aw.sync.originand resumes from it.Ambiguity guard: if two distinct pre-#697 buckets share the same sanitized origin, the function returns an error rather than silently merging distinct histories (the #697 :368 note).
Tests added
test_pre697_origin_scan_resumes_legacy_bucket: post-migration phone pull against a desktop with a raw legacy bucket — must resume, not fork.test_pre697_origin_scan_refuses_ambiguous_candidates: two legacy buckets whose raw origins both sanitize to the same value — must error, not merge.Scope
Dissolves entirely under the
(device_id, id)identity work in ActivityWatch/activitywatch#302 as noted in the issue. This is a bounded compat shim, not a redesign.Co-Authored-By: Bob noreply@timetobuildbob.com