FIELD NOTE 02 · RECOVERY BOUNDARIES
Discard 13 bytes.
Or stop?
A storage engine can recover an unfinished final record. A complete corrupt record calls for a different answer. Here is one experiment you can reproduce in Python and C.
1. Build a history with consequences.
PUT alpha=one PUT beta=two PUT alpha=updated DELETE beta
CrashVault stores these four mutations in a 115-byte append-only log. Replay gives one live key: alpha=updated. beta is absent. Both languages implement the same versioned format independently.
The example copies the valid log, then appends only the first 13 bytes of another PUT frame. This is a synthetic incomplete tail; no physical power loss occurs.
2. Compare exact bytes, not a success message.
| Stage | Python | C |
|---|---|---|
| Damaged copy | 128 bytes | 128 bytes |
| After reopen | 115 bytes | 115 bytes |
| Recovered mutations / keys | 4 / 1 | 4 / 1 |
| After PUT resumed=yes | 144 bytes | 144 bytes |
The script asserts equal key/value state, removal of exactly 13 bytes, successful continuation and byte-identical final logs. The original valid file stays unchanged. These local results were measured on Windows 10 with Python 3.13.7 and GCC 15.2, with default file sync enabled.
3. Change the failure, change the decision.
Now flip a checksum bit in a complete final record. Python raises CorruptionError; C reports checksum mismatch. Both preserve the damaged bytes and stop recovery. Quietly dropping this complete record could hide corruption of a mutation that had already been acknowledged.
Incomplete final frame: recover the valid prefix.
Complete invalid frame: report corruption and preserve the evidence.
A redundant length guard helps distinguish accidental length damage from an unfinished record. CRC32 checks payload integrity against accidental corruption. Neither authenticates records or defeats deliberate checksum rewriting.
4. An ACK has a specific boundary.
validate → append → flush → request file sync → update index → acknowledge
Python requests os.fsync; C requests fsync on POSIX or _commit on Windows. A missing acknowledgement does not prove absence: a complete unacknowledged record can survive and replay. The pending record in this example is incomplete by construction.
The engine does not sync the parent directory when creating a new filename. Physical persistence still depends on filesystem and device behavior. This example covers neither hardware power cuts nor lost interior sectors, and it has no multi-record transactions or process locking. Open each log with one process/Store at a time. These limits make the evidence useful without a production-ready claim.
5. Reproduce and inspect.
git clone https://github.com/mohammmad357/crashvault.git cd crashvault # Build the native C binary with the README command first. python tools/recovery_demo.py --json results/RECOVERY_DEMO.json python -m unittest discover -s tests -v
Use the walkthrough and platform build commands, inspect the executable example and raw measured results, then read the format contract. The script uses temporary synthetic files. All eight existing tests passed locally; the repository badge reports remote CI separately.
فارسی — چرا گاهی باید بازیابی متوقف شود؟
فایل سالم ۱۱۵ بایت است؛ با ۱۳ بایت انتهای ناتمام، به ۱۲۸ بایت میرسد. هر دو زبان فقط بخش ناتمام را حذف میکنند، چهار عملیات را بازمیسازند و بعد از یک نوشتن تازه به فایل یکسان ۱۴۴بایتی میرسند.
اما رکورد کامل با checksum خراب، ممکن است دادهٔ قبلاً تأییدشده باشد. حذف خاموش آن پنهانکردن خرابی است؛ بنابراین برنامه خطا میدهد و فایل را دستنخورده نگه میدارد. این آزمایش قطع برق واقعی نیست و sync پوشه، رفتار دیسک و قفل چندپردازشی را تضمین نمیکند.