feat(sync): re-deliver Apple Health write-back changes

Measurement upserts in /api/sync/changes carry appleHealthMirror. A new
measurementMirror domain delivers { id, appleHealthMirror } for the live
readings of every token whose setting changed past the cursor, keyed on
(token.mirrorChangedAt, measurement.id). Switching the setting writes the
token row only, so the readings keep their updatedAt and syncVersion and
nothing keyed on them recomputes. The domain is additive inside the v2
cursor and is left out of the retention expiry, since its watermark is a
setting's age, not a deletion's.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 19:28:04 +02:00
1 parent 8d10e5ab94
commit aab2e0ae7d
13 files changed
+226 -56

No files matched your search

@@ -18,6 +18,7 @@
"measuredAt": "2026-10-14T06:30:00.000Z",
"source": "MANUAL",
"notes": "after the run",
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
},
@@ -30,6 +31,7 @@
"measuredAt": "2026-10-14T07:00:00.000Z",
"source": "WITHINGS",
"notes": null,
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
}
@@ -197,6 +199,10 @@
}
],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -219,6 +225,7 @@
"cycle_day_tombstones": 0,
"cycle_upserts": 2,
"cycle_tombstones": 0,
"measurement_mirror_upserts": 0,
"cycle_module_enabled": true,
"has_more": true,
"cursor_present": false
@@ -18,6 +18,7 @@
"measuredAt": "2026-06-17T09:30:00.000Z",
"source": "APPLE_HEALTH",
"notes": null,
"appleHealthMirror": false,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
}
@@ -69,6 +70,10 @@
"updatedAt": "<updatedAt>"
}
]
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -91,6 +96,7 @@
"cycle_day_tombstones": 1,
"cycle_upserts": 0,
"cycle_tombstones": 1,
"measurement_mirror_upserts": 0,
"cycle_module_enabled": true,
"has_more": true,
"cursor_present": true
@@ -18,6 +18,7 @@
"measuredAt": "2026-10-14T08:00:00.000Z",
"source": "MANUAL",
"notes": null,
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
}
@@ -39,6 +40,10 @@
"cycles": {
"upserts": [],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -61,6 +66,7 @@
"cycle_day_tombstones": 0,
"cycle_upserts": 0,
"cycle_tombstones": 0,
"measurement_mirror_upserts": 0,
"cycle_module_enabled": true,
"has_more": false,
"cursor_present": true
@@ -26,6 +26,10 @@
"cycles": {
"upserts": [],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -48,6 +52,7 @@
"cycle_day_tombstones": 0,
"cycle_upserts": 0,
"cycle_tombstones": 0,
"measurement_mirror_upserts": 0,
"cycle_module_enabled": true,
"has_more": false,
"cursor_present": true
@@ -26,6 +26,10 @@
"cycles": {
"upserts": [],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -26,6 +26,10 @@
"cycles": {
"upserts": [],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -18,6 +18,7 @@
"measuredAt": "2026-10-14T06:30:00.000Z",
"source": "MANUAL",
"notes": "after the run",
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
},
@@ -30,6 +31,7 @@
"measuredAt": "2026-10-14T07:00:00.000Z",
"source": "WITHINGS",
"notes": null,
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
},
@@ -42,6 +44,7 @@
"measuredAt": "2026-06-17T09:30:00.000Z",
"source": "APPLE_HEALTH",
"notes": null,
"appleHealthMirror": false,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
},
@@ -54,6 +57,7 @@
"measuredAt": "2026-10-14T08:00:00.000Z",
"source": "MANUAL",
"notes": null,
"appleHealthMirror": true,
"syncVersion": "<syncVersion>",
"updatedAt": "<updatedAt>"
}
@@ -158,6 +162,10 @@
"cycles": {
"upserts": [],
"tombstones": []
},
"measurementMirror": {
"upserts": [],
"tombstones": []
}
}
},
@@ -180,6 +188,7 @@
"cycle_day_tombstones": 0,
"cycle_upserts": 0,
"cycle_tombstones": 0,
"measurement_mirror_upserts": 0,
"cycle_module_enabled": false,
"has_more": false,
"cursor_present": false