diff --git a/tests/test_sync_error_classification.py b/tests/test_sync_error_classification.py index 391a8f0..dc4bff3 100644 --- a/tests/test_sync_error_classification.py +++ b/tests/test_sync_error_classification.py @@ -43,9 +43,42 @@ class SyncErrorClassificationTest(unittest.TestCase): self.assertEqual( [(value["generation_id"], value["result"]) for value in acknowledgements], - [("gen_consumed", "consumed"), ("gen_rejected", "rejected")], + [("gen_rejected", "rejected"), ("gen_consumed", "consumed")], ) + def test_recovery_envelope_prioritizes_recent_terminal_evidence(self): + with sqlite3.connect(":memory:") as database: + database.execute( + "CREATE TABLE sync_consumed_generations(" + "generation_id TEXT,producer_peer_id TEXT,consumer_peer_id TEXT," + "consumed_at TEXT,result TEXT,ack_json TEXT)" + ) + for index in range(40): + generation = f"gen_{index:02d}" + database.execute( + "INSERT INTO sync_consumed_generations VALUES(?,?,?,?,?,?)", + ( + generation, + "android", + "desktop", + f"2026-09-18T12:{index:02d}:00Z", + "consumed", + json.dumps({ + "generation_id": generation, + "result": "consumed", + "diagnostic": "", + }), + ), + ) + + acknowledgements = sync_peer_worker.recovery_acknowledgements( + database, "android", "desktop" + ) + + self.assertEqual(32, len(acknowledgements)) + self.assertEqual("gen_39", acknowledgements[0]["generation_id"]) + self.assertEqual("gen_08", acknowledgements[-1]["generation_id"]) + def test_mtp_refreshes_a_retained_peer_advertisement_before_validation(self): expected_peer = "peer_00000000-0000-4000-8000-000000000001" with tempfile.TemporaryDirectory() as directory: diff --git a/tools/sync_peer_worker.py b/tools/sync_peer_worker.py index c1f3dc5..e041caf 100644 --- a/tools/sync_peer_worker.py +++ b/tools/sync_peer_worker.py @@ -72,10 +72,14 @@ def recovery_acknowledgements(db, producer_peer_id: str, consumer_peer_id: str) rows = db.execute( "SELECT ack_json FROM sync_consumed_generations WHERE producer_peer_id=? " "AND consumer_peer_id=? AND result IN('consumed','rejected') " - "ORDER BY consumed_at,generation_id LIMIT 32", + "ORDER BY consumed_at DESC,generation_id DESC LIMIT 32", (producer_peer_id, consumer_peer_id), ).fetchall() acknowledgements = [json.loads(row[0]) for row in rows] + # WHY: the bounded envelope must prioritize the newest interrupted + # conversations. Older acknowledged generations are normally already + # archived; selecting them first can starve the exact ACKs needed to free + # active producer capacity. # ACKs created before rejection diagnostics were normalized may contain a # solidus that Android's platform JSON canonicalizer hashes differently. # They remain durable evidence but cannot close a peer row, so do not let