mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-13 16:10:58 +01:00
Groundwork for moving diet-guard, screen-locker and wake-alarm off fixed
role ids ('pc'/'phone'/'desktop') onto per-install uuids, the scheme todo
and home_inventory already use. A role constant collides the moment a
second machine takes the same role, and a reinstall silently inherits the
previous install's CRDT identity.
The migration cannot rewrite history: the device id is baked into every HLC
stamp already written, is the record path segment and the revision key. So
a device that has just switched ids must still recognise its former id as
its own -- otherwise skip-own-writes stops matching the old path, and the
device pulls back and re-merges its own pre-migration log as though a peer
had written it. Idempotent under LWW, but pure wasted transfer on every
tick.
Both halves therefore take an optional legacy id and test set membership
(own_ids / ownIds) rather than equality against a single id:
- crdt-sync (Python) 0.5.1 -> 0.6.0: new crdt_sync._device with
DeviceIdentity + load_device_identity (generate-or-load, persisted); the
mkdir is deliberately unguarded, since handing back an unpersisted id
would mint a fresh uuid every restart and strand a device dir each time.
sync_log gains legacy_device_id.
- crdt_sync_dart 0.6.0 -> 0.7.0: DeviceIdentity value type; syncLog gains
legacyDeviceId. Persistence stays app-side, where the existing uuid apps
already keep it in SharedPreferences under 'crdt.nodeId'.
Both changes are backward compatible: the parameter defaults to null, which
reproduces the previous single-id behaviour exactly.
Each half has a positive test and a negative control proving the old path
*is* pulled when no legacy id is declared -- so the skip is verified to be
the parameter's doing, not an artifact of the fixture.
Python: 239 tests, 100% branch coverage. Dart: 248 tests, analyzer clean,
device.dart 11/11 and sync.dart 57/57 lines covered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZH89WEqAjuJT8WZdqc7AP
52 lines
2.1 KiB
Dart
52 lines
2.1 KiB
Dart
/// This device's stable sync identity.
|
|
library;
|
|
|
|
/// This device's current sync id, plus the id it used to push under before.
|
|
///
|
|
/// Every device that pushes into a namespace needs an id that is *its own*:
|
|
/// it is the directory segment its log is pushed under
|
|
/// (`<pathPrefix>/<deviceId>/<filename>`), the revision key, and the node
|
|
/// component baked into every HLC stamp it writes (`<iso>-<counter>-<id>`).
|
|
/// Two devices sharing an id overwrite each other's pushed file on every
|
|
/// tick, so the id must be per-*install*, not per-role.
|
|
///
|
|
/// A migration from a role constant (`phone`, `pc`, `desktop`) to a persisted
|
|
/// uuid cannot rewrite history: stamps already written keep the old id, and
|
|
/// the old path still holds the log pushed under it. So a device that has
|
|
/// just switched ids must still recognise its *former* id as itself -- which
|
|
/// is what [legacyId] carries, and why [ownIds] is a set. Skip-own-writes has
|
|
/// to test membership of that set rather than equality with one id, or the
|
|
/// device re-downloads and re-merges its own pre-migration history as though
|
|
/// a peer had written it.
|
|
class DeviceIdentity {
|
|
/// Creates an identity for [deviceId], optionally carrying a [legacyId].
|
|
const DeviceIdentity({required this.deviceId, this.legacyId});
|
|
|
|
/// The id to push, stamp and key revisions under from now on.
|
|
final String deviceId;
|
|
|
|
/// The id this device pushed under before migrating to a persisted uuid.
|
|
///
|
|
/// Null once the old path has been reclaimed.
|
|
final String? legacyId;
|
|
|
|
/// Every id that means "this device", for skip-own-writes checks.
|
|
Set<String> get ownIds => {deviceId, ?legacyId};
|
|
|
|
/// Whether [otherDeviceId] is one of this device's own ids.
|
|
bool isOwn(String otherDeviceId) => ownIds.contains(otherDeviceId);
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
other is DeviceIdentity &&
|
|
other.deviceId == deviceId &&
|
|
other.legacyId == legacyId;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(deviceId, legacyId);
|
|
|
|
@override
|
|
String toString() =>
|
|
'DeviceIdentity(deviceId: $deviceId, legacyId: $legacyId)';
|
|
}
|