- FsApi / FileIo / FileBox の関係を明確化 - UTF-8 ハンドリング、二重 open、プラグイン優先時の Err ハンドリングを明記 - Ring0FsFileIo 実装設計(read-only 版) - 層の棲み分けと拡張ポイント(MockFileIo / NetworkFileIo / minimal/no-fs)確立 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
Phase 107: Ring0.FsApi ↔ FileIo 統合(FileBox の足場固め)
0. ゴール
- Ring0.FsApi(OS ファイル API)と FileIo(FileBox プラグイン用 I/O 抽象)の関係を明確に整理
- 自前 FileBox 実装の「OS への道」を一本のパイプにする:
FileBox → FileIo implementation → Ring0.FsApi → std::fs - これにより以下を実現:
- FileBox まわりのハードコード削減
- 将来の no-fs プロファイル / mock Fs の差し替え容易化
- Ring0 と Ring1(FileBox) の依存関係の明確化
1. スコープと非スコープ
スコープ(今回やること)
-
設計&ドキュメント
- FsApi / FileIo / FileBox / provider_lock の関係を図と文章で整理
- FileIo を「Ring0.FsApi のラッパ(provider)」として位置づけ
-
実装(段階的)
- Ring0.FsApi を read/write の SSOT として確認・微調整
- Ring0 ベースの FileIo 実装追加(Ring0FsFileIo)
- selfhost/通常ランタイムでこれをデフォルト provider として登録
-
Fail-Fast との接続
- Phase 106 の「FileBox provider 必須チェック」と矛盾しない仕様確認
- 標準パスで必ず Ring0FsFileIo が入ることを保証
非スコープ(今回はやらない)
- FileBox の write/delete/copy 全実装(別 Phase)
- FileBox API 大幅変更(メソッド名変更等)
- minimal/no-fs プロファイル実装(Phase 108 候補)
2. Task 1: FsApi を SSOT として整理(docs + 確認)
2.1 実装内容
ファイル:
src/runtime/ring0/traits.rsdocs/development/current/main/core_boxes_design.md- 新規:
docs/development/current/main/phase107_fsapi_fileio_bridge.md(このファイル)
2.2 やること
-
traits.rs で FsApi の公開インターフェースを確認:
pub trait FsApi: Send + Sync { fn read_file(&self, path: &Path) -> Result<Vec<u8>, IoError>; fn write_file(&self, path: &Path, content: &[u8]) -> Result<(), IoError>; // stat / exists 相当? } -
このドキュメント(phase107_fsapi_fileio_bridge.md)に記載:
- 「FsApi = OS ファイル I/O の SSOT(Rust ローカル)」
- 「FileIo = FileBox 用 provider interface。実装の 1 つとして FsApi を内部で使う」という関係
- Ring0 → Ring1 の一方向依存の図
-
core_boxes_design.md に一文追加:
- FileBox セクションに「実体 I/O は FileIo → FsApi → OS」を記載
3. Task 2: FileIo を「FsApi ラッパ」として設計
3.1 実装内容
ファイル:
src/boxes/file/provider.rsphase107_fsapi_fileio_bridge.md
3.2 やること
-
FileIo trait の役割を明確化:
pub trait FileIo: Send + Sync { fn caps(&self) -> FileCaps; fn open(&self, path: &str) -> FileResult<()>; fn read(&self) -> FileResult<String>; fn close(&self) -> FileResult<()>; }- 設計: FileIo は「現在開いているファイルハンドル」に対する操作
- FsApi は stateless(Path → 読込/書込)
- FileIo は stateful(open → read/close)
-
実装設計を docs に記載(擬似コード):
pub struct Ring0FsFileIo { ring0: Arc<Ring0Context>, path: String, caps: FileCaps, } impl FileIo for Ring0FsFileIo { fn caps(&self) -> FileCaps { self.caps } fn open(&self, path: &str) -> FileResult<()> { // FsApi 経由で存在確認など Ok(()) } fn read(&self) -> FileResult<String> { self.ring0.fs.read_file(Path::new(&self.path)) .map(|bytes| String::from_utf8_lossy(&bytes).to_string()) .map_err(FileError::Io) } fn close(&self) -> FileResult<()> { Ok(()) } } -
実装時の検討事項を明示(以下のセクション参照)
3.3 実装時の検討事項(重要)
① Byte → String 変換のポリシー
問題: Ring0FsFileIo の read() で String::from_utf8_lossy() を使うと、バイナリファイルの無効な UTF-8 を置換してしまう。
検討事項:
-
Option A: 現状通り
from_utf8_lossy()で置換- 利点: Nyash が文字列中心だから OK
- 欠点: バイナリ情報が失われる
-
Option B:
String::from_utf8()で Err を返す- 利点: エラーが明示的
- 欠点: FileIo trait を
Result<String, Utf8Error>に変更する必要あり(破壊的)
推奨: Option A(from_utf8_lossy)を採用
- 理由: Nyash は言語実装として「テキストファイル」が主用途
- バイナリ対応は「将来の拡張」として Phase 108+ で検討
- docs に「FileBox は UTF-8 テキストファイル向け」と明記
② FileBox の open 状態での二重 open
問題: FileBox.open() が呼ばれるたびに provider.open() が呼ばれる。
- 既存の FileBox.open() → provider.open() の流れで、close() なしに再度 open() が呼ばれるケース
検討事項:
-
Option A: close() を呼び出し側に強制する
- FileBox.open() 前に明示的に close() 呼び出し
-
Option B: Ring0FsFileIo 内で自動管理
- 新しい open() が来たら前回の close を自動実行
-
Option C: セマンティクスを明記
- 「一度 open したら close() まで新しい open は受け付けない」
推奨: Option C(セマンティクス明記) + docs 追記
- Ring0FsFileIo.open() は「既に path が set されていたら Err を返す」
- FileBox.open() の docs に「FileBox は同時に 1 ファイルのみ開く」と明記
- 複数ファイル同時アクセスは「将来の FileHandleBox」で対応
③ プラグイン優先ロジックでの Err ハンドリング
問題: provider_lock::set_filebox_provider() は OnceLock なので 1 回のみ可能。
- プラグインが先に登録 → init_default_filebox_provider() が Err を返す
検討事項:
-
Option A: Err を単に無視する
- 呼び出し側で Err を無視(silent)
-
Option B: Warning をログに出す
- ring0.log.info() / warn() で「プラグイン provider が既に設定されています」と出力
-
Option C: init_default_filebox_provider() を Option を返すように変更
- 呼び出し側で「設定済み」を区別可能
推奨: Option B(Warning ログ出力)
- 実装:
init_default_filebox_provider()の戻り値をResult<(), String>にして、- Ok(()) → デフォルト provider を登録した
- Err(msg) → 既にプラグイン側で登録済み(msg = "Plugin provider already registered")
- 呼び出し側で
ring0.log.debug()で記録(verbose 時に可視) - Fail-Fast は保たれる(MissingService は出ない)
4. Task 3: Ring0 ベースの FileIo 実装を追加
4.1 実装内容
ファイル候補:
src/boxes/file/core_ro.rsまたはsrc/boxes/file/builtin_factory.rssrc/runtime/provider_lock.rs(ヘルパー関数)src/runtime/plugin_host.rs(起動時初期化)
4.2 やること
-
Ring0FsFileIo 実装を追加:
- フィールド:
Arc<Ring0Context>,String path - open(path): path を保持、FsApi 経由で存在確認(読み取り向け)
- read(): FsApi.read_file → String 変換(from_utf8_lossy ポリシー採用)
- close(): 単に Ok(())(実質 noop、ハンドル管理なし)
- caps:
FileCaps { read: true, write: false }(Phase 107 では read-only)
- フィールド:
-
provider_lock 側にヘルパー追加:
pub fn init_default_filebox_provider( ring0: &Arc<Ring0Context> ) -> Result<(), String> { // Ring0 ベースの FileIo を登録 let provider = Arc::new(Ring0FsFileIo::new(ring0.clone())); set_filebox_provider(provider) .map_err(|_| "Plugin FileBox provider already registered".to_string()) } -
PluginHost/initialize_runtime に統合:
- CoreServices 初期化後に
init_default_filebox_provider(&ring0)を呼ぶ - 戻り値が Err の場合は debug ログを出力(プラグイン優先)
- Fail-Fast は影響なし(既にプラグイン provider が set されているだけ)
- CoreServices 初期化後に
5. Task 4: Fail-Fast & プロファイルとの整合
5.1 実装内容
ファイル:
src/runtime/plugin_host.rsdocs/development/current/main/phase106_filebox_design_revised.md
5.2 やること
-
Phase 106 との整合確認:
- Phase 106: 「FileBox provider 未登録なら CoreInitError::MissingService」
- Phase 107: 「標準パスで Ring0FsFileIo が自動登録されるので MissingService は基本的に起きない」
- phase106 のドキュメントに追記: 「Phase 107 で自動登録機構が追加された」
-
将来用フック(docs に記載):
- minimal/no-fs プロファイル導入時:
CoreBoxId::File.is_core_required(profile)に拡張- その profile では
init_default_filebox_provider()を呼ばない
- これで「FileBox 無し環境」も可能に
- minimal/no-fs プロファイル導入時:
6. Task 5: ドキュメント統合
6.1 実装内容
ファイル:
docs/development/current/main/core_boxes_design.mddocs/development/current/main/phase106_filebox_design_revised.mdphase107_fsapi_fileio_bridge.md(このドキュメント)ring0-inventory.md(任意)
6.2 やること
-
core_boxes_design.md に図と説明を追加:
[FileBox] ↓ (provider経由) [Ring0FsFileIo] (FileIo実装) ↓ (read_file/write_file呼び出し) [Ring0.FsApi] (OS I/O抽象) ↓ [std::fs] -
phase106_filebox_design_revised.md の "Phase 107" セクション更新:
- 「Phase 107 で FsApi 統合を行う予定」が「実装済み」に変更
-
phase107_fsapi_fileio_bridge.md にまとめる:
- FsApi / FileIo / FileBox / provider_lock / PluginHost の関係を 1 ドキュメントで整理
7. 実装チェックリスト(Phase 107)
- FsApi / FileIo / FileBox / provider_lock / PluginHost の関係が図付きで整理されている
- Ring0 ベースの FileIo 実装(Ring0FsFileIo)が追加されている
- selfhost/通常ランタイム起動で、デフォルトとして Ring0FsFileIo が provider_lock に登録される
- UTF-8 ハンドリング ポリシー(from_utf8_lossy採用)が docs に明記されている
- FileBox の一度に1ファイルのみ open セマンティクスが docs に明記されている
- プラグイン優先時の Err ハンドリング(debug ログ出力)が実装されている
- Phase 106 との整合確認完了(MissingService は基本レア)
- 将来用フック(minimal/no-fs プロファイル)が docs に記載されている
- ビルド・テスト完全成功(FileBox/provider_lock/plugin_host 関連テスト)
8. 設計原則(Phase 107 で確立)
層の棲み分けが完全化
層 役割 知識範囲
──────────────────────────────────────────────────────────────
Ring0.FsApi OS I/O 抽象化 Rust std::fs のみ
Ring0FsFileIo FileIo 実装 (1つの実装例) Ring0.FsApi 使用
FileIo trait FileBox 向け I/O 抽象 FsApi を知らない
provider_lock 登録・参照 OnceLock管理
FileBox ユーザーAPI provider 経由のみ
拡張ポイント
将来の実装:
- MockFileIo: FsApi の代わりに in-memory mock を使う
- NetworkFileIo: FsApi の代わりに remote FS を使う
- minimal/no-fs: provider 登録をスキップ(FileBox optional化)
Phase 107 指示書作成日: 2025-12-03(検討事項追加版)