Three logical changes rolled into one commit:
1. stdlib HTTP read timeout. urlopen's `timeout=` only bounds connect,
and CPython versions disagree on whether the resulting socket keeps
that pinned. A half-dead Immich server that has sent headers but
stalls on the body would hang the whole batch. Pin a read-side
timeout on every HTTPConnection/HTTPSConnection at import so the
bound holds across versions and reading paths. Verified with a
stand-alone server/client pair: with the fix, a stalled body read
fails in 0.5s with TimeoutError; without the fix, the same script
hangs past the test timeout.
Also distinguish `_add_to_album` ambiguity from failure: an entry
that omits `success` is now treated the same way as an empty body
(downgrade to `album: "unconfirmed"`), not as a ValueError. Only an
entry that explicitly says `success: false` raises. Broadens the
transient-error catch in `_wait_for_storage_settle` from
(HTTPError, URLError) to OSError so socket timeouts and connection
resets during polling do not abort the wait.
2. CI matrix now includes 3.11. tomllib is stdlib from 3.11, and the
packaging-drift test in tests/test_packaging.py uses it. Without
3.11 the 3.10 job importorskip-s that test, leaving drift
detection silent at the lowest supported interpreter.
3. Test ergonomics. tests/conftest.py extracts the per-test image
batch fixture (was duplicated across 10+ tests). README clarifies
that retry_archive's exit code reflects `errors` entries only —
`unconfirmed` album results exit 0, not non-zero.
Plus a User-Agent string that reports the installed package version
(via importlib.metadata) so the value cannot drift from pyproject.toml.
70 tests pass; was 62. ruff + mypy clean. Verified on Python 3.11
and 3.12 locally.