Skip to content

aw-sync: return and persist a SyncReport — a pass is a side effect, so nothing can report what it did #695

Description

@ErikBjare

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

The information mostly already exists and is discarded. sync_one computes new_events_count per bucket and logs it; sync_run knows 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 SyncReport from sync_run, aggregate it in pull_all/push, and persist the latest one:

pub struct SyncReport {
    pub started: DateTime<Utc>,
    pub finished: DateTime<Utc>,
    pub mode: SyncMode,
    pub peers: Vec<PeerReport>,   // one per peer considered
    pub pushed: Vec<BucketReport>,
}

pub struct PeerReport {
    pub device_id: String,
    pub hostname: String,
    pub path: PathBuf,
    pub outcome: PeerOutcome,     // Imported | Skipped(reason) | Failed(error)
    pub buckets: Vec<BucketReport>,
}

pub struct BucketReport {
    pub bucket_id: String,
    pub events_new: i64,
    pub resumed_at: Option<DateTime<Utc>>,
}

Then:

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

Activity

  1. TimeToBuildBob commented on Sep 16, 2026

    @TimeToBuildBob
    Contributor

    Implemented in #699.

    sync_run / pull_all / push now return a SyncReport. The latest one is persisted under {data_dir}/aw-sync/last-sync-report.json (not the Syncthing folder). aw-sync status prints it; JNI returns real counts instead of a fixed success string; duplicate-device_id skips show up as PeerOutcome::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.

  2. added a commit that references this issue on Sep 16, 2026
    8cdfdff
  3. TimeToBuildBob commented on Sep 16, 2026

    @TimeToBuildBob
    Contributor

    Already implemented in #699 (zero-peer report test included).

    #700 just landed, so that PR is DIRTY against master. Holding the rebase until #678 and #697 are in — one rebase over the remaining v0.14.0 sequence, not per-PR. Nothing further on this issue until then.

  4. added a commit that references this issue on Sep 16, 2026
    3cf0434
  5. TimeToBuildBob commented on Sep 17, 2026

    @TimeToBuildBob
    Contributor

    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 / push return a SyncReport, aggregated per pass — aw-sync/src/report.rs (PeerReport, PeerOutcome::Skipped { reason }, BucketReport, events_new/events_pulled/events_pushed counters).
    • 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 status prints the last pass — aw-sync/src/status.rs calls load_last_report(), and an unpersisted state reads as Last 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).

  6. added a commit that references this issue on Sep 17, 2026
    37aa0b0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions