Repository navigation
aw-sync: return and persist a SyncReport — a pass is a side effect, so nothing can report what it did #695
Description
Activity
Implemented in #699.
sync_run/pull_all/pushnow return aSyncReport. The latest one is persisted under{data_dir}/aw-sync/last-sync-report.json(not the Syncthing folder).aw-sync statusprints it; JNI returns real counts instead of a fixed success string; duplicate-device_idskips show up asPeerOutcome::Skipped.Did not change error policy — a per-peer failure still aborts the pass (#688). The partial report is persisted on that path so the failure is visible afterwards.
- added a commit that references this issue
on Sep 16, 2026 - added a commit that references this issue
on Sep 16, 2026 Closing the loop from the merged side — #699 landed as
37aa0b0(2026-09-17T08:41:16Z) and this issue was closed by Erik one second later.Verified against the acceptance criteria on
master:sync_run/pull_all/pushreturn aSyncReport, aggregated per pass —aw-sync/src/report.rs(PeerReport,PeerOutcome::Skipped { reason },BucketReport,events_new/events_pulled/events_pushedcounters).- The latest report is persisted to
{data_dir}/aw-sync/last-sync-report.json(atomic rename, unique temp name so overlapping daemon/Android writers can't truncate each other). Persist failure warns instead of failing an already-successful pass. aw-sync statusprints the last pass —aw-sync/src/status.rscallsload_last_report(), and an unpersisted state reads asLast pass: (none — no pass has been persisted yet)rather than silence.- The JNI fixed string is gone;
to_jni_json()returns real counts, so Sync status is a timestamp and a boolean — the model cannot answer what synced, when next, or from whom aw-android#274 is a rendering job now. - Master CI is green on the merge commit (
success, run 2026-09-17T08:41:18Z).
Deliberately unchanged, per the scope note in the issue: error policy. A per-peer failure still aborts the pass — that partial report is now persisted on the abort path so the failure stays visible afterwards. The abort behaviour itself remains tracked at #688 (in-flight slice: #703).
- added a commit that references this issue
on Sep 17, 2026
A sync pass is modelled as a side effect rather than as a function that returns what it did. That single choice is why sync failures are structurally invisible, and it is cheap to reverse.
Today
sync_run(...) -> Result<(), Box<dyn Error>>— success carries no information.The JNI wrapper reports a fixed string regardless of what happened:
Ten peers or zero peers, a million events or none — identical output.
Android persists exactly
SyncStatus(completedAt: Long, success: Boolean)(Sync status is a timestamp and a boolean — the model cannot answer what synced, when next, or from whom aw-android#274).The daemon has no status at all; its only signal is exiting (aw-sync: per-peer and per-bucket errors abort the whole sync pass (and burn the supervisor's restart budget) #688).
The information mostly already exists and is discarded.
sync_onecomputesnew_events_countper bucket and logs it;sync_runknows which peers it found. None of it survives the call.This is the mechanism behind #682: a daemon ran 102 consecutive passes importing nothing, logging
Pulling...each time. Not that nobody looked — there was nothing to look at.Proposal
Return a
SyncReportfromsync_run, aggregate it inpull_all/push, and persist the latest one:Then:
aw-sync status(feat(aw-sync): addstatusdoctor command and fail-loud empty-pull warnings #687) reports the last pass as well as current folder state.PeerOutcome::Skippedgives the duplicate-device_id dedupe from fix(aw-sync): skip duplicate device_id folders that truncate history on pull #686 somewhere to report itself, instead of a log line nobody reads.Scope boundary
This covers facts about what a pass did. It deliberately does not cover facts the sync folder cannot currently express — "which device is this peer", "when did it last push", "is a peer missing that used to be here". Those need per-device metadata in the folder (the manifest discussed in ActivityWatch/activitywatch#302 / #691) and are out of scope here.
Worth doing first precisely because it is small, has no format implications, and unblocks three other issues.
Credit: found in a design review of aw-sync.
Related: #684, #687, #688, #682, ActivityWatch/aw-android#274.
cc @TimeToBuildBob