A journal is preallocated, and a shard that filled it stopped accepting
work. Nothing about the refused transactions was wrong and no amount of
retrying made room, so the shard was simply done -- the one failure mode
a store cannot have.
Rotation cannot happen from inside preparation. The open group's frames
are built but not appended and each carries `journal_id` in its header,
so sealing mid-formation would leave them naming a journal that no longer
takes writes. `accept` therefore publishes the open group, rotates, and
re-prepares, which is the shape the group-full path already used. The
reservation is released first; speculative state is not advanced until
past the fit test, so that release is the whole rollback.
Two triggers, and the second is not implied by the first: a group that no
longer fits must rotate or be refused, and a cursor that has reached
`segment_max_bytes` must rotate so segments stay near their configured
size. With `segment_max_bytes` below `journal_preallocate_bytes` that
boundary arrives first and every time, and checking only the fit let
segments grow to the whole preallocation whatever the ceiling said. The
threshold is deliberately not a per-group cap: it is read before a group
is added rather than inside one, so a segment may overshoot by at most a
group and no committed group is ever split.
A frame no *empty* journal could hold is a ceiling, not a rotation --
sealing would produce a fresh journal that refuses it again, forever. It
is measured against the preallocation less the journal header, because a
new journal's cursor starts past that header; comparing against the whole
preallocation called frames in that gap rotatable and retried them into
the same refusal.
The manifest advances `committed_shard_sequence` to the sealed segment's
last. `validate_manifest_sequence_coverage` requires the final retained
tail range to end exactly there, and sealing does make that prefix
durable under a second, manifest-referenced name. Installing an index run
is the case that differs and correctly carries the field forward: a run
makes no journal frame more durable than it already was.
The successor generation pins the new segment and the fresh tail. The
predecessor's active tail is not carried forward -- that file is the
segment now, and retaining both would leave one logical generation naming
two sources, which `object_source` refuses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGdH5V43XWnj1PdHqiPktQ