5fb4f8745b61
H-11: always exchange manifests; per-entry timestamps are diff authority; transitive sync test
- time
- 2026-07-29 14:33 UTC · 2 months ago
- machine
- koh2
- agent
- claude-opus-4
- session
- audit-remediation-2026-07-29
- parent
- 0fee2a175dd2
↓ peach-android-5fb4f874.tar.gzatlas at this save
files changed2 files
app/src/main/java/com/peachpasswords/sync/SyncCoordinator.kt+0 −45app/src/test/java/com/peachpasswords/sync/SyncCoordinatorTest.kt+25 −4app/src/main/java/com/peachpasswords/sync/SyncCoordinator.kt+0 −45
@@ -120,30 +120,6 @@120120 warnOnSkew(nowMs, hello.deviceTime)121121 val exchangeDeviceManifest = vault.negotiatesDeviceManifest(hello)122122 123- if (hello.version == localVersion) {124- // Entry versions do not cover paired-device introductions or125- // revocations. Exchange that signed graph when both peers126- // advertise support; old peers keep the legacy DONE fast path.127- if (exchangeDeviceManifest) {128- session.sendMsg(129- SyncMessage.DeviceManifest(vault.buildOutgoingDeviceManifest()),130- )131- val theirs = session.recvAs<SyncMessage.DeviceManifest>(132- "initiator",133- "SYNC_DEVICE_MANIFEST",134- )135- if (theirs.manifest != null) {136- vault.applyIncomingDeviceManifest(137- theirs.manifest,138- session.remoteStaticPublicKey,139- )140- }141- }142- session.sendMsg(SyncMessage.Done(0, 0))143- session.awaitClosingDone("initiator", "SYNC_DONE")144- return@withTimeout SyncOutcome(SyncOutcomeStatus.COMPLETED)145- }146-147123 val ourEntries = vault.buildEntryManifest()148124 val ourTombstones = vault.buildTombstoneManifest()149125 session.sendMsg(SyncMessage.Manifest(ourEntries, ourTombstones))@@ -242,27 +218,6 @@242218 }243219 warnOnSkew(nowMs, hello.deviceTime)244220 val exchangeDeviceManifest = vault.negotiatesDeviceManifest(hello)245-246- if (hello.version == localVersion) {247- if (exchangeDeviceManifest) {248- val theirs = session.recvAs<SyncMessage.DeviceManifest>(249- "responder",250- "SYNC_DEVICE_MANIFEST",251- )252- session.sendMsg(253- SyncMessage.DeviceManifest(vault.buildOutgoingDeviceManifest()),254- )255- if (theirs.manifest != null) {256- vault.applyIncomingDeviceManifest(257- theirs.manifest,258- session.remoteStaticPublicKey,259- )260- }261- }262- session.recvAs<SyncMessage.Done>("responder", "SYNC_DONE")263- session.sendMsg(SyncMessage.Done(0, 0))264- return@withTimeout SyncOutcome(SyncOutcomeStatus.COMPLETED)265- }266221 267222 val ourEntries = vault.buildEntryManifest()268223 val ourTombstones = vault.buildTombstoneManifest()app/src/test/java/com/peachpasswords/sync/SyncCoordinatorTest.kt+25 −4
@@ -99,7 +99,7 @@9999 assertTrue(messages.contains("sync-stage: role=responder wait=SYNC_DONE"))100100 }101101 102- @Test fun equal_versions_short_circuit_without_transfer() = runBlocking {102+ @Test fun equal_versions_still_exchange_entry_manifests() = runBlocking {103103 val (sa, sb) = duplex()104104 val a = FakeAdapter("A", version = 7).apply { put("x", 100) }105105 val b = FakeAdapter("B", version = 7).apply { put("y", 200) }@@ -110,9 +110,30 @@110110 111111 assertEquals(SyncOutcomeStatus.COMPLETED, outA.status)112112 assertEquals(SyncOutcomeStatus.COMPLETED, outB.status)113- assertEquals(0, outA.entriesReceived)114- assertFalse(a.blobs.containsKey("y"))115- assertFalse(b.blobs.containsKey("x"))113+ assertEquals(1, outA.entriesReceived)114+ assertTrue(a.blobs.containsKey("y"))115+ assertTrue(b.blobs.containsKey("x"))116+ }117+118+ @Test fun remote_change_transits_through_peer_with_unchanged_version() = runBlocking {119+ val a = FakeAdapter("A", version = 7)120+ val b = FakeAdapter("B", version = 7).apply { put("from-b", 200) }121+ val c = FakeAdapter("C", version = 7)122+123+ suspend fun sync(initiator: FakeAdapter, responder: FakeAdapter) {124+ val (si, sr) = duplex()125+ val ri = async { runSyncInitiator(si, initiator, nowMs = 0) }126+ val rr = async { runSyncResponder(sr, responder, nowMs = 0) }127+ assertEquals(SyncOutcomeStatus.COMPLETED, ri.await().status)128+ assertEquals(SyncOutcomeStatus.COMPLETED, rr.await().status)129+ }130+131+ sync(a, b)132+ assertTrue(a.blobs.containsKey("from-b"))133+ assertEquals(7L, a.version)134+135+ sync(a, c)136+ assertTrue(c.blobs.containsKey("from-b"))116137 }117138 118139 @Test fun equal_versions_still_exchange_device_graphs_when_both_peers_support_it() =