Data Integrity Guarantees¶
Ayame is built to edit files where a single wrong byte is unacceptable —
financial records, legal text, scientific data. This page states the integrity
guarantees the editor makes, the mechanism behind each one, and the automated
test that verifies it. Every check listed here runs on ordinary
cargo test --locked, so it executes on every CI run.
What "correct" means here¶
A guarantee is only meaningful if it is mechanically verified. Each guarantee
below is paired with an executable test under crates/ayame-core/tests/. The
tests compare against a trusted reference (a plain Vec model, a sequential
re-run, or the original bytes) and assert byte-for-byte equality, using a
SHA-256 checksum utility for legible failures.
| # | Guarantee | Mechanism | Verified by |
|---|---|---|---|
| 1 | Saving reproduces the exact bytes (BOM, per-line terminator, and a missing final newline all preserved). | Untouched lines are copied verbatim from the memory map; only edited lines are re-encoded. | tests/roundtrip_bytes.rs |
| 2 | The edit overlay is equivalent to a trusted model under any sequence of insert / replace / delete / undo / redo. | Overlay edits and history are diffed against a Vec reference after every step. |
tests/edit_overlay_equivalence.rs |
| 3 | The line and column the editor shows are exactly where an edit lands — including wide characters, tabs, multi-cursor, and immediately after scrolling. | Logical line numbers and Unicode-scalar columns are stable across viewport windows and batch edits. | tests/caret_coordinates.rs |
| 4 | Encoding round-trips are byte-exact, and an unrepresentable character aborts instead of writing a lossy file. | The encoder reports unmappable characters; untouched lines are never re-encoded. | tests/encoding_roundtrip.rs |
| 5 | Crash recovery restores edits up to the last committed transaction, and no crash corrupts the original file. | A write-ahead log (WAL) records each committed edit; a torn trailing record is ignored, corruption is refused. | tests/crash_recovery.rs |
| 6 | Parallel transforms are byte-identical to sequential ones (sort / replace / case / split), across chunk boundaries, mixed EOL, and a missing final newline. | The parallel path fans out over line chunks but preserves per-line terminators and order. | tests/transform_equivalence.rs |
| 7 | Correctness holds at the ten-billion-line design target: the sparse-index arithmetic stays exact and bounded, and arbitrary-position resolution / tail edits stay byte-exact. | Index checkpoint/memory math is unit-tested at 1e10 lines; the resolve/edit/save pipeline runs on a real million-line file with a tiny stride. Front-end line numbers stay exact below 2^53. | tests/extreme_scale.rs, web/test/scale.test.ts |
Atomic save¶
Saving never leaves a half-written file where the target used to be. The core
save path (EditSession::save_to_path) is:
- Stream the full new content to a temporary sibling file opened with
create_new(so a stale temp can never be reused). flushandfsyncthe temporary file so its bytes reach the disk.- Atomically rename the temp over the target.
fsyncthe target's parent directory so the rename itself is durable.
A reader therefore only ever observes the complete old file or the complete new
file — never a partial write. tests/crash_recovery.rs asserts the postcondition
(complete target, no temp residue, source untouched on a save-as).
The implementation has one ordinary publication primitive and two explicitly different state-transition protocols:
| Path | Implementation | Why |
|---|---|---|
| Edit saves, transforms, split parts, and server exports | fsync::replace_with_staged |
Publishes a complete staged sibling and makes the directory entry durable. |
| In-place save with a live document | serve/edit.rs::swap_in_staged_file |
Keeps the old inode under a session-owned aside path until the mmap-backed state transition commits. |
| WAL compaction | wal::rename_via_aside |
WAL readers know the .old aside name and fall back to it during the rename crash window. |
The shared primitive is covered by its fallback/rollback unit tests plus the transform overwrite, split round-trip, and server save tests. The WAL suite separately exercises target-missing fallback and staged compaction.
Crash recovery (write-ahead log)¶
While a file is open for editing, the edit overlay lives in memory. Its only on-disk trace is the write-ahead log, which appends one record per committed transaction. On restart:
- A clean log replays every committed edit and restores the exact pre-crash view (content and dirtiness).
- A torn trailing record — an append cut short by a crash, recognizable because it has no terminating newline — is ignored; the committed prefix still replays.
- A corrupt record (complete but unparseable) makes the whole log
Invalid: recovery refuses rather than guessing, and the original file is left untouched. - A stale log, whose base file changed underneath it, is refused so edits are never applied to the wrong content.
Change-history marker consistency¶
Change-history markers are a derived view of the same sparse EditSession
overlays used for rendering and saving; the browser never compares document
text. The current overlay is compared with the exact overlay snapshot from the
last successful save, both anchored to the immutable as-opened mmap.
unsavedmarks current lines that differ from the saved snapshot.savedmarks lines in the saved snapshot that differ from the content as it was opened. An unsaved state takes visual precedence when both histories meet at one boundary.- A deletion is placed on the next surviving logical line. A trailing or
whole-document deletion is placed at
total_lines, the editor's existing[EOF]row, so it is not silently clamped onto unrelated content. - The save commit records the overlay that actually produced the staged bytes and changes marker status only after the atomic file swap succeeds. A failed or conflicted save leaves both the saved baseline and marker colors intact.
- Undo/redo recomputes from restored overlay generations. Revert/reload clears
the session history. WAL recovery starts from the on-disk file as the saved
baseline and reconstructs recovered edits as
unsaved; saved markers remain intentionally session-only.
Storage is capped sparse state (BTreeSet, one million entries per marker
kind), not a line-count-sized bitmap. Viewport reads enumerate only the fetched
range; the position pane folds the same marker set into exactly 2,048 bins.
Scratch and spill placement¶
Out-of-core work needs disk: dirty sessions materialize a worker input, the
external-merge sort spills runs, and uploads stage bytes. These go to a
disk-backed scratch base, never RAM-backed tmpfs, so a huge operation
cannot OOM/ENOSPC the way it would under Linux's default /tmp. Resolution
order:
--scratch-dir DIR(serve/gui) or$AYAME_SCRATCH_DIR.--cache-dir DIR/scratchwhen a cache dir is given.- Otherwise a
scratch/subdir of the per-user cache root (the same disk-backed location the index cache uses:~/.cache/ayame,~/Library/Caches/ayame,%LOCALAPPDATA%\ayame). - The OS temp dir only when no home/cache location is discoverable at all.
sort/group also accept --spill-dir to point the spill somewhere explicit
(e.g. the same volume as a very large input). Put the scratch base on a volume
with room for the answer plus its spill.
Known non-guarantees¶
Honesty about the edges is part of the guarantee:
- External modification of an open file. Ayame memory-maps the file it is viewing. If another process rewrites that file underneath a live session, the in-memory view can be inconsistent until reload. The WAL's base-identity check detects this on recovery (reported as stale), but it is not prevented during a live session.
- The
ASCIIlabel is lenient. Text taggedASCIIis encoded as UTF-8 rather than rejected, because ASCII is a UTF-8 subset. Byte-level abort on unrepresentable characters applies to the narrow codecs (Shift_JIS, EUC-JP). - Scale. Extreme scale is verified in two halves (see guarantee 7): the ten-billion-line index arithmetic is unit-tested directly, and the resolve/edit/save pipeline is exercised on a real million-line file with a tiny stride. A full ten-billion-line file (~100 GB) is never materialized in CI, so that end-to-end case rests on the arithmetic plus the pipeline test, not on a physical fixture.
Running the checks¶
The data-integrity suite is the set of integration tests under
crates/ayame-core/tests/; they run alongside the crate's unit tests on every
cargo test and in CI.