Ingesting a scan's worth of chat messages persisted each new message
with its own append() call, so a batch of fresh messages opened and
committed one SQLite transaction per row.
- A new appendBatch persists many messages in a single transaction,
using the same INSERT OR IGNORE dedup key as append() and preserving
input order so load()'s rowid ordering is unchanged.
- Every message is serialized and encrypted before the transaction
opens, so a message with a non-UTF-8 body is skipped on its own
without leaving a transaction open; the valid siblings still persist.
- Ingest collects the newly stored messages and writes them once after
the loop instead of per message. It is inert (returns 0) while locked
or on empty input.
Tests cover a batch insert, dedup on re-insert, empty input, bad-UTF-8
isolation with a valid sibling and a following append(), insert order,
and the locked no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>