コンテンツにスキップ

データ完全性の保証

Ayame は 1 バイトの誤りも許されない ファイル(金融記録・法務文書・科学 データなど)の編集を想定して作られています。このページでは、エディタが保証する データ完全性の内容、それぞれの仕組み、そしてそれを検証する自動テストを明記します。 ここに挙げるチェックはすべて通常の cargo test --locked で実行されるため、 CI の毎回の実行で走ります。

ここでの「正しさ」の定義

保証は、機械的に検証できて初めて意味を持ちます。以下の各保証には、 crates/ayame-core/tests/ 配下の実行可能なテストが 1 対 1 で対応します。テストは 信頼できる参照(素朴な Vec モデル、逐次実行の再現、または元のバイト列)と 突き合わせ、1 バイト単位 の一致を検証します。失敗時に読みやすいよう、 SHA-256 のチェックサムユーティリティを用います。

# 保証 仕組み 検証テスト
1 保存が元のバイト列を正確に再現する(BOM・改行コード・末尾改行の有無を保持)。 未編集行は mmap から逐語コピーし、編集行だけを再エンコードする。 tests/roundtrip_bytes.rs
2 挿入・置換・削除・undo・redo のどんな列に対しても、編集オーバーレイが信頼できるモデルと等価。 各ステップ後にオーバーレイと履歴を Vec 参照と差分照合する。 tests/edit_overlay_equivalence.rs
3 エディタに表示される行・列が、編集が実際に着地する行・列と完全に一致する(全角・タブ・マルチカーソル・スクロール直後を含む)。 論理行番号と Unicode スカラー単位の列が、ビューポートやバッチ編集をまたいでも安定。 tests/caret_coordinates.rs
4 エンコーディング往復がバイト正確で、表現できない文字があるときは中途半端に書かず中断する。 エンコーダが変換不能文字を報告し、未編集行は再エンコードしない。 tests/encoding_roundtrip.rs
5 クラッシュ復元は直前にコミットされた変更まで正確に戻し、どのクラッシュでも元ファイルを壊さない。 ログ先行書き込み(WAL)が各コミットを記録。途中で切れた末尾レコードは無視し、破損は拒否する。 tests/crash_recovery.rs
6 並列変換(ソート/置換/ケース変換/分割)が逐次実行とバイト一致する(チャンク境界・EOL 混在・末尾改行なしを含む)。 並列経路は行チャンクに分割しつつ、行ごとの改行コードと順序を保持する。 tests/transform_equivalence.rs
7 100 億行という設計目標でも正確性が保たれる:疎インデックスの演算が正確かつ有界で、任意位置の解決・末尾編集がバイト正確。 インデックスのチェックポイント/メモリ演算を 1e10 行で単体テストし、解決・編集・保存パイプラインを実際の 100 万行ファイル(極小ストライド)で走らせる。フロントの行番号は 2^53 未満で正確を保つ。 tests/extreme_scale.rsweb/test/scale.test.ts

原子的な保存

保存が、対象ファイルのあった場所に中途半端な書きかけを残すことはありません。 コアの保存経路(EditSession::save_to_path)は次の手順です。

  1. 新しい内容全体を、create_new で開いた 一時的な兄弟ファイル へ書き出す (古い一時ファイルを再利用してしまうことがない)。
  2. flush し、一時ファイルを fsync してバイト列をディスクに届ける。
  3. 一時ファイルを対象へ 原子的にリネーム する。
  4. 対象の 親ディレクトリを fsync して、リネーム自体を永続化する。

したがって読み手が観測するのは、完全な旧ファイルか完全な新ファイルのどちらかだけ で、途中状態を見ることはありません。tests/crash_recovery.rs がこの事後条件 (完全な対象ファイル・一時ファイルの残骸なし・save-as で元ファイル不変)を検証 します。

実装は通常の公開処理を 1 つに集約し、整合性契約が異なる 2 つの状態遷移だけを 明示的な別経路にしています。

経路 実装 別経路である理由
編集の保存・変換・分割パート・サーバーのエクスポート fsync::replace_with_staged 完成した兄弟 stage を公開し、ディレクトリエントリまで永続化する。
ライブ document の in-place 保存 serve/edit.rs::swap_in_staged_file mmap ベースの状態遷移がコミットするまで、旧 inode をセッション管理の aside パスに保持する。
WAL compaction wal::rename_via_aside WAL reader が .old aside 名を認識し、rename 中のクラッシュ窓ではそこへフォールバックする。

共通処理は fallback/rollback の単体テストに加え、変換の上書き・分割の往復・ サーバー保存のテストで検証します。WAL は対象欠落時の fallback と staged compaction を専用スイートで別途検証します。

クラッシュ復元(ログ先行書き込み)

ファイルを編集用に開いている間、編集オーバーレイはメモリ上にあります。そのディスク 上の痕跡は WAL だけで、コミットされたトランザクションごとに 1 レコードを追記します。 再起動時には次のように振る舞います。

  • 正常な ログは、コミット済みの編集をすべて再生し、クラッシュ直前のビュー (内容とダーティ状態)を正確に復元する。
  • 途中で切れた末尾レコード(追記中のクラッシュで切れたもの。末尾に改行がない ことで識別できる)は無視し、コミット済みの前半部分は再生する。
  • 破損した レコード(完結しているが解釈不能)はログ全体を Invalid とし、推測 で進めず復元を拒否する。元ファイルには触れない。
  • 土台のファイルが差し替わった 古い(stale) ログは拒否し、誤った内容に編集を 適用しない。

保証しないこと(既知の非保証)

境界を正直に述べることも、保証の一部です。

  • 開いているファイルの外部からの変更。 Ayame は表示中のファイルを mmap します。 ライブセッションの裏で別プロセスがそのファイルを書き換えると、リロードするまで メモリ上のビューが不整合になり得ます。WAL の土台同一性チェックが復元時にこれを 検出し(stale として報告)ますが、ライブセッション中に防止するわけではありません。
  • ASCII ラベルは寛容。 ASCII とされたテキストは、ASCII が UTF-8 の部分集合 であるため、拒否せず UTF-8 として書き出します。表現不能文字での中断は、狭い コーデック(Shift_JIS・EUC-JP)に適用されます。
  • スケール。 極端なスケールは 2 つに分けて検証します(保証 7 参照):100 億行の インデックス演算 を直接単体テストし、解決・編集・保存の パイプライン を極小 ストライドの実 100 万行ファイルで走らせます。100 億行の実ファイル(〜100 GB)は CI で生成しないため、その端から端までのケースは物理的な実体ではなく、この演算+ パイプラインテストに依拠します。

チェックの実行

cargo test --locked -p ayame-core

データ完全性スイートは crates/ayame-core/tests/ 配下の統合テスト群で、クレートの 単体テストと一緒に、毎回の cargo test と CI で実行されます。