LeVCS/crates/levcs-store
Levi Neuwirth 9b2117a406
Seal a full journal and continue in a fresh one
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
2026-08-09 16:13:21 +02:00
..
benches Freeze Wave A: Phase 1 storage spine 2026-07-26 19:47:03 -04:00
examples Freeze Wave A: Phase 1 storage spine 2026-07-26 19:47:03 -04:00
src Seal a full journal and continue in a fresh one 2026-08-09 16:13:21 +02:00
tests Seal a full journal and continue in a fresh one 2026-08-09 16:13:21 +02:00
Cargo.toml Implement D0-B storage publication interfaces 2026-07-27 22:31:32 -04:00