testsAndMisc/python_pkg/syncyomi_guard/__init__.py
Krzysztof kuhy Rudnicki cef72fbab0 syncyomi_guard: refuse a collapsed payload as the new good copy
TachiyomiSY 1.13.2 can report a restore as SUCCESS while writing almost
nothing, then sync that near-empty library up and replace the only good
copy. On 2026-08-09 a 14 MB payload holding 2182 manga became a 267 KB
stub, and the real library survived only as stale pages in the SQLite
write-ahead log.

The upstream bug is Kotlin in an APK and cannot be fixed here, so this
targets the fixable half: decode the payload, count what is actually in
it, and refuse to treat a collapse as normal drift. A pure is-it-zero
check would have caught this incident but not the harder case upstream
describes, where a restore keeps some entries and drops the rest, so the
threshold is proportional. Categories get their own rule because all
2182 manga survived the round trip and all 18 categories did not.

Adds the setup script and a runbook covering the WAL-page salvage used
to recover the library on the day.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YQLwdquDCNmWtvgi3Lk72J
2026-08-10 15:33:18 +02:00

42 lines
1.3 KiB
Python

"""Integrity guard for the SyncYomi server's library payload.
TachiyomiSY 1.13.2 has an open upstream bug (jobobby04/TachiyomiSY#1634, #1638)
where a backup restore fails with ``SQLITE_BUSY`` on the app's *own* database.
The loud failure is survivable. The dangerous case is the quiet one: a restore
that reports ``Worker result SUCCESS`` while having written almost nothing, and
then pushes that near-empty library up to the server, replacing the only good
copy.
That happened on 2026-08-09: a 14 MB payload holding 2182 manga became a 267 KB
stub holding only sources and settings, and the real library survived solely as
stale pages in the SQLite write-ahead log.
This package exists so that never depends on luck again. It decodes the payload,
counts what is actually in it, and refuses to accept a collapse as normal. It
cannot prevent the upstream bug — that is Kotlin code in an APK — so it aims at
the part that is actually fixable: never letting a degraded copy become the only
copy.
"""
from __future__ import annotations
from python_pkg.syncyomi_guard.payload import (
PayloadError,
PayloadStats,
decode_payload,
)
from python_pkg.syncyomi_guard.verdict import (
Thresholds,
Verdict,
compare,
)
__all__ = [
"PayloadError",
"PayloadStats",
"Thresholds",
"Verdict",
"compare",
"decode_payload",
]