NR caught a real durability gap during migration prep: the
sequence
base.WriteMessage(msg);
source.MarkSent(srcMsg);
looks atomic, but the OS write buffer hasn't necessarily reached
the platter when MarkSent runs. Crash in that window = silent
message drop.
Add `Sync` virtual on TMessageBase (default no-op for read-only
and in-memory backends). Six writable backends override and
flush every open stream they own via SysUtils.FileFlush
(fpfsync on Unix, FlushFileBuffers on Windows):
JAM .JHR / .JDT / .JDX / .JLR
Squish .SQD / .SQI / .SQL
Hudson msginfo / msgidx / msghdr / msgtxt / msgtoidx / LASTREAD.BBS
PCBoard MSGS file + index
EzyCom header + text
GoldBase msginfo / msgidx / msghdr / msgtxt / msgtoidx / LASTREAD.DAT
MSG inherits no-op (per-write open/close — buffer flushes on
close, but dir entry isn't fsynced; future enhancement).
Wildcat inherits no-op (legacy `file` IO, not TFileStream).
Helper for backend authors: TMessageBase.FlushStream(S: TStream)
class method that handles the TFileStream cast + nil-safety.
`Sync` raises after Close (data is finalized; nothing to flush).
Test: TestSyncWriteable in test_consumer_round1 -- writes a
message via JAM and Squish, calls Sync (no raise), Close, calls
Sync again (raise expected).
docs/API.md: new "Sync (durability)" section explaining the
commit-after-fsync pattern with the canonical example.
Symmetric to fpc-ftn-transport TPktWriter.Sync (commit ee8c6ad)
that NR's review prompted on the transport side.
Suite: 48/48 (added TestSyncWriteable to test_consumer_round1).