replay
A cold Flow of the frames in scope, in append order, terminated by exactly one verdict on how the stream ended — CleanTail or Truncated.
The verdict arrives on every replay collected to completion. A consumer that cuts the flow short — take(n), first(), an early return from collect — gets no verdict, and that is the honest answer: it stopped reading before the archive said how it ended.
Each collection re-reads the archive, so a flow collected after a later append sees the later frames too. The flow completes when the archive's tail is reached; it does not wait for future appends.
It never throws for damaged bytes, because an archive is best-effort and throwing would discard every intact frame ahead of the damage. But it does not stay silent about them either: that is what the terminal ReplayEvent is for. Use frames to opt out of the verdict, explicitly, when you do not need to know.
Samples
var records = 0
var complete = false
// Collect to COMPLETION. The terminal verdict is what a replay sells — a history that
// stopped at damage, and one that did not, are otherwise indistinguishable. A consumer
// that cuts the flow short (take, first, an early return) gets no verdict, honestly.
bolt.replay(ReplayScope.All).collect { event ->
when (event) {
is Archived -> records += event.ops.size
CleanTail -> complete = true
is Truncated -> when (event.reason) {
// Not readable YET — a writer mid-append, a device still locked. Resuming
// from atOffset later can work.
TruncationReason.SegmentHeader, TruncationReason.Frame -> retryFrom(event.atOffset)
// GONE. atOffset is the honest end of the readable history and is NOT a
// resume cursor: nothing will ever produce the records behind it.
TruncationReason.MissingRegion -> reportPermanentGap(event.atOffset)
}
}
}
if (!complete) reportPartialHistory(records)