Files
hakorune/docs/archive/phases/phase-106-156/phase111_filehandlebox_append_metadata.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

20 KiB
Raw Blame History

Phase 111: FileHandleBox append モード + metadata 拡張

0. ゴール

  • Phase 110 で作った FileHandleBox複数回アクセス I/Oに対して:
    • "a" append モード を追加して、ログ/追記用途に対応する。
    • ファイルメタデータ取得 APIsize / exists / is_file / is_dirを設計・実装する。
    • すべて Ring0FsFileIo → Ring0.FsApi 経由で行い、レイヤー構造と Fail-Fast を崩さない。

1. スコープと非スコープ

スコープ(今回やること)

  1. 設計ドキュメント:

    • append モードの意味truncate との違い)を正確に定義。
    • metadata APIsize/exists/is_file/is_dirを決定。
    • FsApi 拡張append_allの位置づけを明記。
  2. FsApi / Ring0FsFileIo 側の拡張:

    • FsApi trait に append_all(path, data) メソッドを追加。
    • Ring0FsFileIo で append/truncate の 2 モード実装。
  3. FileHandleBox API の拡張:

    • open(path, mode) に "a" サポート("r"/"w"/"a" の 3 モード)。
    • write_all が mode に応じて truncate/append を切り替え。
    • metadata helpersize/exists/is_file/is_dirを内部 Rust API として実装。
  4. Profile との整合:

    • Default: append & metadata 有効。
    • NoFs: open 自体がエラー、metadata もエラー。
  5. テスト + ドキュメント:

    • FileHandleBox append テスト。
    • metadata テスト。
    • NoFs profile 対応テスト。

非スコープ(今回はやらない)

  • modifiedmtime情報Phase 112+ で検討)。
  • FileBoxワンショット API側への append 追加(必要になれば後フェーズ)。
  • パスワイルドカード、ディレクトリ列挙、ウォッチャなどの高度機能。
  • ACL/パーミッション/ロックなどの OS 特有機能。
  • NyashBox 公開 APImetadata メソッドの .hako 側からの呼び出し)→ Phase 112+ で検討。

2. Task 1: 設計ドキュメントappend metadata

2.1 実装内容

ファイル:

  • 本ドキュメントphase111_filehandlebox_append_metadata.md

2.2 設計決定

append モードの仕様

open(path, "a") の意味:

  • 存在しない場合 → 新規作成。
  • 存在する場合 → ファイル末尾に書き足す。

write_all(content) の挙動:

  • Mode "w" → truncate毎回上書き、Phase 108 決定どおり)。
  • Mode "a" → append末尾に追記
  • Mode "r" で write_all → "FileHandleBox is opened in read-only mode" エラー。

truncate vs append の明確な使い分け:

// truncate mode: ログファイルを毎回リセット
handle.open("output.log", "w")?;
handle.write_all("Session started\n")?;

// append mode: 複数回の実行結果を累積
handle.open("history.log", "a")?;
handle.write_all("Operation 1\n")?;  // 末尾に追記
handle.write_all("Operation 2\n")?;  // さらに末尾に追記

metadata API の仕様

最小限の属性セット:

  • size() → ファイルサイズ(バイト数)
  • exists() → ファイルが存在するか
  • is_file() → ファイルであるか(ディレクトリではない)
  • is_dir() → ディレクトリであるか

注: modifiedmtimeは Phase 112 以降で検討FsMetadata への追加が必要)。

ライフサイクルと metadata の関係:

  • path が設定されていればopen 済みでなくてもmetadata 取得可能。
  • 例: close() 後でも stat 可能。
  • 例: open せずに path だけ指定して metadata 取得(予定)。

理由: FsApi.metadata(path) は stateless な操作なので、FileIo インスタンスの有無に依存しない。

FsApi 拡張の位置づけ

FsApi.append_all(path, data) の追加:

  • Phase 107: read_to_string / read / write_all が確立。
  • Phase 111: append_all を追加write_all と対称的)。
pub trait FsApi: Send + Sync {
    fn read_to_string(&self, path: &Path) -> Result<String, IoError>;
    fn read(&self, path: &Path) -> Result<Vec<u8>, IoError>;
    fn write_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>;
    fn append_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>;  // ← 新規
    fn exists(&self, path: &Path) -> bool;
    fn metadata(&self, path: &Path) -> Result<FsMetadata, IoError>;
    fn canonicalize(&self, path: &Path) -> Result<PathBuf, IoError>;
}

Ring0FsFileIo での実装:

  • open(path, mode) で path と mode を保持。
  • write(text) で:
    • Mode "w" → FsApi.write_all(path, text.as_bytes())truncate
    • Mode "a" → FsApi.append_all(path, text.as_bytes())append
  • 内部 metadata_helper で FsApi.metadata(path) を呼び出し。

3. Task 2: FsApi / Ring0FsFileIo 拡張

3.1 実装内容

ファイル:

  • src/runtime/ring0/traits.rsFsApi trait 拡張)
  • src/runtime/ring0/std_impls.rsFsApi 実装)
  • src/providers/ring1/file/ring0_fs_fileio.rsRing0FsFileIo 拡張)

3.2 やること

FsApi trait 拡張src/runtime/ring0/traits.rs

  1. append_all メソッドを追加:
pub trait FsApi: Send + Sync {
    // ... 既存メソッド ...

    /// ファイルに追記append
    ///
    /// ファイルが存在しない場合は新規作成、存在する場合は末尾に追記。
    /// Phase 111: write_all と対称的に提供。
    fn append_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>;
}
  1. FsMetadata の確認:
    • 既に実装済みの FsMetadata を確認:
      pub struct FsMetadata {
          pub is_file: bool,
          pub is_dir: bool,
          pub len: u64,
      }
      
    • Phase 111 では modified は追加しない(後フェーズで)。

std_impls.rs での実装

impl FsApi for StdFsApi {
    fn append_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError> {
        use std::fs::OpenOptions;
        use std::io::Write;

        let mut file = OpenOptions::new()
            .create(true)        // 存在しなければ作成
            .append(true)        // append モードで開く
            .open(path)
            .map_err(|e| IoError::Io(format!("append_all failed: {}", e)))?;

        file.write_all(data)
            .map_err(|e| IoError::Io(format!("write failed: {}", e)))
    }
}

Ring0FsFileIo での append 対応src/providers/ring1/file/ring0_fs_fileio.rs

  1. struct フィールド確認:

    • path, mode を保持していることを確認。
  2. write() メソッドを拡張:

impl FileIo for Ring0FsFileIo {
    fn write(&self, text: &str) -> FileResult<()> {
        if self.mode == "a" {
            // Append mode
            self.ring0.fs.append_all(Path::new(&self.path), text.as_bytes())
                .map_err(FileError::Io)
        } else if self.mode == "w" {
            // Truncate mode (default)
            self.ring0.fs.write_all(Path::new(&self.path), text.as_bytes())
                .map_err(FileError::Io)
        } else if self.mode == "r" {
            Err(FileError::Unsupported(
                "Cannot write in read-only mode".to_string()
            ))
        } else {
            Err(FileError::Unsupported(
                format!("Unsupported mode: {}", self.mode)
            ))
        }
    }

    // 内部 metadata helperphase 111
    fn metadata(&self) -> FileResult<crate::runtime::ring0::traits::FsMetadata> {
        self.ring0.fs.metadata(Path::new(&self.path))
            .map_err(FileError::Io)
    }
}
  1. FileIo trait は追加しない ← Phase 111 での決定:
    • metadata メソッドは FileIo trait には追加せず、Ring0FsFileIo のプライベートメソッドとして実装。
    • FileHandleBox が metadata 取得時に、Ring0FsFileIo の内部ヘルパを呼び出す形にする。

4. Task 3: FileHandleBox API 実装append + metadata

4.1 実装内容

ファイル:

  • src/boxes/file/handle_box.rs

4.2 やること

open() メソッド拡張

pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> {
    // Double-open チェック
    if self.is_open() {
        return Err(already_open());
    }

    // Mode バリデーション("r", "w", "a" のみ)
    if mode != "r" && mode != "w" && mode != "a" {
        return Err(unsupported_mode(mode));
    }

    // NoFs profile チェック(既存)
    if self.io.is_none() {  // provider が無い
        return Err(provider_disabled_in_nofs_profile());
    }

    // path と mode を保存
    self.path = path.to_string();
    self.mode = mode.to_string();

    // FileIo に open を委譲mode を含める)
    self.io
        .as_ref()
        .unwrap()
        .open(path)
        .map_err(|e| format!("Open failed: {:?}", e))
}

: mode パラメータはファイル操作のモード制御に使用し、FileIo.open() 呼び出し時には既に self.mode に保存されている。

write_all() メソッド拡張

pub fn write_all(&self, content: &str) -> Result<(), String> {
    // open 済みチェック
    if !self.is_open() {
        return Err(not_open());
    }

    // read-only チェック
    if self.mode == "r" {
        return Err("FileHandleBox is opened in read-only mode".to_string());
    }

    // write (mode に基づいて truncate/append を切り替え)
    self.io
        .as_ref()
        .unwrap()
        .write(content)
        .map_err(|e| format!("Write failed: {:?}", e))
}

write() メソッド内Ring0FsFileIoで mode チェックして append/truncate を判定。

metadata メソッド群(内部 Rust API

impl FileHandleBox {
    /// ファイルサイズを取得(バイト数)
    ///
    /// Path さえあればopen 済みでなくてもstat 可能。
    pub fn size(&self) -> Result<u64, String> {
        if self.path.is_empty() {
            return Err("FileHandleBox path not set".to_string());
        }

        // Ring0FsFileIo の内部 metadata_helper を呼び出し
        // または Ring0Context から直接 FsApi.metadata() を取得
        // 詳細は実装時に決定
        self.metadata_internal()
            .map(|meta| meta.len)
    }

    /// ファイルが存在するか確認
    pub fn exists(&self) -> Result<bool, String> {
        if self.path.is_empty() {
            return Err("FileHandleBox path not set".to_string());
        }

        self.metadata_internal()
            .map(|_| true)
            .or_else(|_| Ok(false))  // not found → false
    }

    /// ファイルであるか確認
    pub fn is_file(&self) -> Result<bool, String> {
        if self.path.is_empty() {
            return Err("FileHandleBox path not set".to_string());
        }

        self.metadata_internal()
            .map(|meta| meta.is_file)
    }

    /// ディレクトリであるか確認
    pub fn is_dir(&self) -> Result<bool, String> {
        if self.path.is_empty() {
            return Err("FileHandleBox path not set".to_string());
        }

        self.metadata_internal()
            .map(|meta| meta.is_dir)
    }

    /// 内部ヘルパー: FsApi.metadata を呼び出し
    fn metadata_internal(&self) -> Result<crate::runtime::ring0::traits::FsMetadata, String> {
        // 実装パターン:
        // Option 1: io.as_ref().unwrap() から metadata() ヘルパを呼び出す
        // Option 2: provider_lock から Ring0Context を取得して FsApi を呼び出す
        // Phase 111 では Option 1 を推奨io と path が一体的に扱える)
        todo!("metadata_internal implementation")
    }
}

設計方針:

  • metadata メソッド群は Rust 内部 API として実装。
  • .hako から呼び出す場合は、Phase 112+ で MethodBox 登録を検討。
  • 現在は Unit テスト で動作確認するだけ。

5. Task 4: Profile との整合チェック

5.1 実装内容

ファイル:

  • src/runtime/runtime_profile.rs(確認のみ、修正不要)
  • src/runtime/plugin_host.rs(確認のみ、修正不要)
  • docs/development/current/main/phase111_filehandlebox_append_metadata.md(本ドキュメント)

5.2 現行設計確認

Default プロファイル

  • FileBox / FileHandleBox 両方が Ring0FsFileIo を使用。
  • append / metadata もフル機能で動作。

NoFs プロファイル

  • FileBox: open せず read/write でエラー "FileBox disabled in no-fs profile"。
  • FileHandleBox: open 自体が "FS disabled in NoFs profile" エラーで拒否。
  • metadata: NoFs で path を stat しない実装上、open 済みだからこそ stat 可能という前提)。

Phase 111 での確認:

  • NoFs profile でも metadata_internal() が呼び出されうるか?
    • path が設定されている状態で metadata() を呼ぶ場合、どうするか。
    • 現在の設計では io が無い状態では metadata も失敗OK

6. Task 5: テスト + docs 更新

6.1 テスト

ファイル:

  • src/boxes/file/handle_box.rs 内の test モジュール

テスト 1: append モード動作確認

#[test]
fn test_filehandlebox_append_mode() {
    use std::fs;

    let path = "/tmp/phase111_append_test.txt";
    let _ = fs::remove_file(path);  // cleanup

    // First write (truncate)
    let mut handle = FileHandleBox::new();
    handle.open(path, "w").unwrap();
    handle.write_all("hello\n").unwrap();
    handle.close().unwrap();

    // Append
    let mut handle = FileHandleBox::new();
    handle.open(path, "a").unwrap();
    handle.write_all("world\n").unwrap();
    handle.close().unwrap();

    // Verify
    let content = fs::read_to_string(path).unwrap();
    assert_eq!(content, "hello\nworld\n");

    let _ = fs::remove_file(path);
}

テスト 2: metadatasize確認

#[test]
fn test_filehandlebox_metadata_size() {
    use std::fs;

    let path = "/tmp/phase111_metadata_test.txt";
    let _ = fs::remove_file(path);

    // Write test file
    let mut handle = FileHandleBox::new();
    handle.open(path, "w").unwrap();
    handle.write_all("hello").unwrap();  // 5 bytes
    handle.close().unwrap();

    // Check size
    let mut handle = FileHandleBox::new();
    let size = handle.size().unwrap();
    assert_eq!(size, 5);

    let _ = fs::remove_file(path);
}

テスト 3: metadatais_file / is_dir確認

#[test]
fn test_filehandlebox_metadata_is_file() {
    use std::fs;

    let path = "/tmp/phase111_file_test.txt";
    let _ = fs::remove_file(path);

    // Create file
    let mut handle = FileHandleBox::new();
    handle.open(path, "w").unwrap();
    handle.close().unwrap();

    // Check is_file
    let mut handle = FileHandleBox::new();
    let is_file = handle.is_file().unwrap();
    assert!(is_file);

    let is_dir = handle.is_dir().unwrap();
    assert!(!is_dir);

    let _ = fs::remove_file(path);
}

テスト 4: read-only mode での write 拒否

#[test]
fn test_filehandlebox_write_readonly_error() {
    use std::fs;

    let path = "/tmp/phase111_readonly_test.txt";
    let _ = fs::remove_file(path);

    // Create file
    fs::write(path, "content").unwrap();

    // Open in read mode
    let mut handle = FileHandleBox::new();
    handle.open(path, "r").unwrap();

    // Try to write → Error
    let result = handle.write_all("new");
    assert!(result.is_err());
    assert!(result.unwrap_err().contains("read-only"));

    let _ = fs::remove_file(path);
}

テスト 5: NoFs profile での open 拒否

#[test]
fn test_filehandlebox_nofs_profile_error() {
    // NYASH_RUNTIME_PROFILE=no-fs を設定して実行
    let mut handle = FileHandleBox::new();
    let result = handle.open("/tmp/test.txt", "w");
    assert!(result.is_err());
    assert!(result.unwrap_err().contains("disabled"));
}

6.2 ドキュメント更新

phase110_filehandlebox_design.md に追記

Section 「Phase 111 との関係」を追加:

### Phase 111: append モード + metadata 拡張

- **append モード**: open(path, "a") で末尾に追記。truncate (mode "w") と明確に区別。
- **metadata**: size / exists / is_file / is_dir を Ring0FsFileIo 経由で取得。
- **FsApi 拡張**: append_all(path, data) を追加write_all と対称的)。
- **内部 Rust API**: metadata メソッド群は .hako 公開せず、テスト・内部用のみ。
- **modifiedmtime**: Phase 112+ で検討予定。

core_boxes_design.md 更新

FileHandleBox セクションに 12 行追記:

- Phase 111: append モード + metadata APIsize/exists/is_file/is_dir実装完了。

CURRENT_TASK.md 更新

Phase 111 の完了行を追加:

## Phase 111: FileHandleBox append + metadata 拡張(完了予定)

- [ ] FsApi.append_all() 追加
- [ ] Ring0FsFileIo での append/truncate 切り替え実装
- [ ] FileHandleBox.open に "a" mode サポート
- [ ] FileHandleBox.metadata_internal / size / exists / is_file / is_dir 実装
- [ ] append テスト + metadata テスト + NoFs profile テスト
- [ ] core_boxes_design / phase111 docs 更新済み
- [ ] ビルド・テスト完全成功

## Backlog

### Phase 112: Ring0 Service Registry 統一化

- Ring0.log の統一レジストリ化
- provider_lock の汎用化
- metadata を FsMetadata に modified フィールド追加

### Phase 113: FileHandleBox NyashBox 公開 API

- metadata メソッドを .hako から呼び出し可能に
- MethodBox 登録

### Phase 114: FileIo 機能拡張

- exists / stat / canonicalize を FileIo trait に追加
- 権限・ロック機構の検討

7. 実装チェックリストPhase 111

  • FsApi trait に append_all() メソッド追加
  • StdFsApi で append_all() 実装OpenOptions.append()
  • Ring0FsFileIo.write() を mode チェックして append/truncate 切り替え
  • FileHandleBox.open() に mode バリデーション("r"/"w"/"a" のみ)
  • FileHandleBox.metadata_internal() が FsApi.metadata() を呼び出し
  • FileHandleBox.size / exists / is_file / is_dir を内部 Rust API として実装
  • append テストwrite → append → 内容確認)
  • metadata テストsize, is_file, is_dir
  • read-only mode での write 拒否テスト
  • NoFs profile 対応テストopen がエラー)
  • core_boxes_design / phase111 docs / CURRENT_TASK 更新済み
  • ビルド・テスト完全成功

8. 設計原則Phase 111 で確立)

append vs truncate の明確な使い分け

【Mode】    【挙動】                  【用途】
─────────────────────────────────────────────
"w"         毎回上書きtruncate   ログファイル初期化、設定ファイル更新
"a"         末尾に追記append     履歴ログ、イベント記録
"r"         読み取り専用             ファイル読み込み

FsApi の成熟度

Phase 107-108:

  • read_to_string, read, write_all, exists, metadata

Phase 111:

  • append_allwrite_all と対称的)

Phase 112+:

  • modified フィールドSystemTimeの FsMetadata 追加
  • Ring0 service registry 統一化

FileHandleBox のメタデータ戦略

Phase 111:

  • metadata は内部 Rust API のみ(.hako 非公開)
  • テスト・デバッグ用途

Phase 112+:

  • MethodBox 登録で .hako 公開
  • modified を FsMetadata に追加

Phase 111 指示書完成日: 2025-12-03修正案統合版 Status: Historical