◈ MAZE SIGNAL

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.

StagePythonC
Damaged copy128 bytes128 bytes
After reopen115 bytes115 bytes
Recovered mutations / keys4 / 14 / 1
After PUT resumed=yes144 bytes144 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 پوشه، رفتار دیسک و قفل چندپردازشی را تضمین نمی‌کند.