- A checkpoint larger than one record (16 MiB) is written as CHKPT_BEGIN
[size u64, CRC-32], CHKPT_DATA chunks and CHKPT_END. Replay assembles it
and applies it only when complete with the size and CRC matching; an
unfinished one is a torn tail (valid_bytes stops before it). Buffers
over 16 MiB could not be checkpointed before: a lost record left their
journal behind for good, and they were never compacted. Records are
written as segments straight from the snapshot. Header version 2;
version 1 journals are still read.
- Checkpoint thresholds scale with the buffer size (edits of half its
size; the timer only after a sixteenth; compaction at twice its size;
gap retries spaced by size), and a checkpoint that would compact is
written straight into the compacted journal instead of being appended
and then copied.
- The recovery prompt keeps the content it replayed for its preview and
installs it on 'y', instead of replaying the journal a second time.
- A base file over 64 MiB gets its CRC from the writer thread before it
creates the journal (it had none, so touching such a file made its
journal unusable for recovery).
- kte_tests takes optional name substrings to run a subset of tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- Ownership is applied before permissions: chown clears setuid/setgid,
so saving a 06755 file left it 0755.
- The umask is read once during static initialisation. Reading it at
save time needed umask(0)/umask(old), briefly changing it for the swap
writer thread creating files.
- Before rewriting a hard-linked file in place, the new content is
written and synced to a copy beside it; if the in-place write fails
(ENOSPC, EIO) the copy is kept and named in the error.
- Journal headers also record a CRC-32 of the base file (flags bit 1,
bytes 40..43). A changed mtime with the same size and content
(touch, checkout back and forth) no longer marks a journal stale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- open_ctx takes a non-blocking flock on the journal. A journal locked
by another kte process is left alone: this session reports it once
and does not write (records from two sessions used to interleave in
one file). Opening a file whose journal is live in another session
skips recovery instead of replaying or removing that journal.
- The header records the size and mtime of the file the journal's
records apply to (flags bit 0, bytes 24..39; previously reserved, so
no version change). Before offering recovery, a journal whose base no
longer matches the file on disk (changed after a crash, or the crash
fell between a save's rename and the journal reset) is treated as
stale and offered for deletion, instead of replaying its edits onto
different text. Journals from older kte (flag clear) replay as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- Added detailed journaling system (`SwapManager`) for crash recovery, including edit recording and replay.
- Integrated recovery prompts for handling swap files during file open flows.
- Implemented swap file cleanup, checkpointing, and compaction mechanisms.
- Added extensive unit tests for swap-related behaviors such as recovery prompts, file pruning, and corruption handling.
- Updated CMake to include new test files.
- Introduced SwapManager for sidecar journaling of buffer mutations, with a safe recovery mechanism.
- Added group undo/redo functionality, allowing atomic grouping of related edits.
- Implemented `SwapRecorder` and integrated it as a callback interface for mutations.
- Added unit tests for swap journaling (save/load/replay) and undo grouping.
- Refactored undo to support group tracking and ID management.
- Updated CMake to include the new tests and swap journaling logic.