feat(phase106): FileBox provider_lock整理 & Fail-Fast強化(案B統一版)

Task 1: CoreBoxId.category() 修正
- File を CoreRequired 側に移動(L126)
- テスト期待値修正(L371)
- Phase 106 intent コメント更新(L107-115)

Task 2: provider_lock API 確認
- 変更なし(既存の set/get API をそのまま使用)
- get_filebox_provider_strict() は追加しない(シンプルに保つ)

Task 3: FileBox SSOT コメント追加
- L5-7 に責務明示コメント追加

Task 4: PluginHost に FileBox provider チェック追加
- with_core_from_registry_optional() に Phase 106 チェック追加(L158-167)
- test_with_core_from_registry_filebox_required() 追加(L413-445)
- 既存テスト2件を FileBox provider 初期化対応(L301-321, L323-351)
- test_with_core_from_registry_missing_box() をエラーメッセージ拡張(L386-410)

Task 5: ドキュメント更新
- core_boxes_design.md に Phase 106 セクション追加(L248-265)
- 責務分離原則・Ring0.FsApi 延期を明記

完了条件:
 ビルド成功(cargo build --release)
 テスト全PASS(cargo test --lib runtime: 64 passed; 0 failed)
 指示書の実装チェックリスト全て completed
This commit is contained in:
nyash-codex
2025-12-03 17:55:26 +09:00
parent 52c13e658d
commit 66479f921d
7 changed files with 136 additions and 23 deletions

View File

@ -297,8 +297,9 @@ fn check_layer_boundary() {
- ランナー: `src/runner/modes/cranelift.rs`, `--jit-direct``src/runner/mod.rs` - ランナー: `src/runner/modes/cranelift.rs`, `--jit-direct``src/runner/mod.rs`
- 進行中の論点と手順は `CURRENT_TASK.md` を参照してね(最新のデバッグ方針・フラグが載ってるよ)。 - 進行中の論点と手順は `CURRENT_TASK.md` を参照してね(最新のデバッグ方針・フラグが載ってるよ)。
**PyVM 主経路Phase15 方針** **PyVM ラインPhase15 歴史メモ/現在は撤退済み**
- 主経路: Python/llvmlite + PyVM を標準の実行/検証経路として扱うよ。Rust VM/JIT は補助(保守/比較/プラグイン検証) > 注: 2025-12 現在、PyVM 実行経路は完全撤退中だよ。以下は Phase15 当時の方針と運用メモで、今は「歴史情報」としてだけ残しているよ。日常の開発・CI では Rust VM / LLVM ラインだけを使ってね
- 当時の主経路: Python/llvmlite + PyVM を標準の実行/検証経路として扱うよ。Rust VM/JIT は補助(保守/比較/プラグイン検証)。
- 使い分け: - 使い分け:
- PyVM推奨・日常確認: `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.hako` - PyVM推奨・日常確認: `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.hako`
- llvmlite ハーネス: `NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash --backend llvm apps/APP/main.hako` - llvmlite ハーネス: `NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash --backend llvm apps/APP/main.hako`
@ -356,8 +357,9 @@ Notes
- `NYASH_NY_COMPILER_CHILD_ARGS` → スペース区切りで子にそのまま渡す - `NYASH_NY_COMPILER_CHILD_ARGS` → スペース区切りで子にそのまま渡す
- 子側apps/selfhost-compiler/compiler.hako`--read-tmp` を受理して `tmp/ny_parser_input.ny` を読むplugins 必要)。 - 子側apps/selfhost-compiler/compiler.hako`--read-tmp` を受理して `tmp/ny_parser_input.ny` を読むplugins 必要)。
## PyVM Scope & PolicyStage2 開発用の範囲) ## PyVM Scope & PolicyStage2 開発用の範囲・歴史メモ
- 目的: PyVM は「開発用の参照実行器」だよ。JSON v0 → MIR 実行の意味論確認と llvmlite とのパリティ監視に使う(プロダクション最適化はしない) > 注: このセクションも Phase15〜17 期の設計メモだよ。PyVM ラインは現在は撤退済みで、実行確認・CI は Rust VM / LLVM のみを対象にしているよ
- 目的: 当時PyVM は「開発用の参照実行器」だよ。JSON v0 → MIR 実行の意味論確認と llvmlite とのパリティ監視に使う(プロダクション最適化はしない)。
- 必須命令: `const/binop/compare/branch/jump/ret/phi``call/externcall/boxcall`(最小)。 - 必須命令: `const/binop/compare/branch/jump/ret/phi``call/externcall/boxcall`(最小)。
- Box/メソッド(最小実装): - Box/メソッド(最小実装):
- ConsoleBox: `print/println/log` - ConsoleBox: `print/println/log`
@ -376,8 +378,9 @@ Notes
- Bridge 短絡RHS スキップ): `tools/ny_stage2_shortcircuit_smoke.sh` - Bridge 短絡RHS スキップ): `tools/ny_stage2_shortcircuit_smoke.sh`
- CI: `.github/workflows/pyvm-smoke.yml` を常時緑に維持。LLVM18 がある環境では `tools/parity.sh --lhs pyvm --rhs llvmlite` を任意ジョブで回す。 - CI: `.github/workflows/pyvm-smoke.yml` を常時緑に維持。LLVM18 がある環境では `tools/parity.sh --lhs pyvm --rhs llvmlite` を任意ジョブで回す。
## Interpreter vs PyVM実行経路の役割と優先度 ## Interpreter vs PyVM実行経路の役割と優先度・歴史メモ
- 優先経路: PyVMPythonを"意味論リファレンス実行器"として採用。日常の機能確認・CI の軽量ゲート・llvmlite とのパリティ監視を PyVM で行う > 注: ここで言う「優先経路: PyVM」は Phase15 期のものだよ。現在は PyVM ラインは撤退済みで、Rust VM / LLVM を優先経路として扱うよ
- (当時の方針)優先経路: PyVMPythonを"意味論リファレンス実行器"として採用。日常の機能確認・CI の軽量ゲート・llvmlite とのパリティ監視を PyVM で行う。
- 補助経路: Rust の MIR Interpreter は純Rust単独で回る簡易器として維持。拡張はしないBoxCall 等の未対応は既知。Python が使えない環境での簡易再現や Pipe ブリッジの補助に限定。 - 補助経路: Rust の MIR Interpreter は純Rust単独で回る簡易器として維持。拡張はしないBoxCall 等の未対応は既知。Python が使えない環境での簡易再現や Pipe ブリッジの補助に限定。
- Bridge--ny-parser-pipe: 既定は Rust MIR Interpreter を使用。副作用なしの短絡など、実装範囲内を確認。副作用を含む実行検証は PyVM スモーク側で担保。 - Bridge--ny-parser-pipe: 既定は Rust MIR Interpreter を使用。副作用なしの短絡など、実装範囲内を確認。副作用を含む実行検証は PyVM スモーク側で担保。
- 開発の原則: 仕様差が出た場合、llvmlite に合わせて PyVM を優先調整。Rust Interpreter は保守維持(安全修正のみ)。 - 開発の原則: 仕様差が出た場合、llvmlite に合わせて PyVM を優先調整。Rust Interpreter は保守維持(安全修正のみ)。
@ -386,8 +389,9 @@ Notes
- Phase15 中は Rust VM/JIT への新規機能追加を最小化し、Pythonllvmlite/PyVM側での実装・検証を優先する。 - Phase15 中は Rust VM/JIT への新規機能追加を最小化し、Pythonllvmlite/PyVM側での実装・検証を優先する。
- Runner/Bridge は必要最小の配線のみ(子プロセスタイムアウト・静音・フォールバック)。意味論の追加はまず PyVM/llvmlite に実装し、必要時のみ Rust 側へ反映。 - Runner/Bridge は必要最小の配線のみ(子プロセスタイムアウト・静音・フォールバック)。意味論の追加はまず PyVM/llvmlite に実装し、必要時のみ Rust 側へ反映。
## SelfHosting への移行PyVM → Nyashロードマップ将来 ## SelfHosting への移行PyVM → Nyashロードマップ歴史メモ
- 目標: PyVM の最小実行器を Nyash スクリプトへ段階移植し、自己ホスト中も Python 依存を徐々に縮小する > 注: このロードマップは「PyVM からの段階移行」を前提にした初期案だよ。現在は PyVM ライン自体が撤退しており、Rust VM / LLVM / Nyash 自己ホストの 3 本を前提に設計を進めているよ
- 目標: 当時の計画PyVM の最小実行器を Nyash スクリプトへ段階移植し、自己ホスト中も Python 依存を徐々に縮小する。
- ステップ(小粒度): - ステップ(小粒度):
1) Nyash で MIR(JSON) ローダ(ファイル→構造体)を実装(最小 op セット)。 1) Nyash で MIR(JSON) ローダ(ファイル→構造体)を実装(最小 op セット)。
2) const/binop/compare/branch/jump/ret/phi を Nyash で実装し、既存 PyVM スモークを通過。 2) const/binop/compare/branch/jump/ret/phi を Nyash で実装し、既存 PyVM スモークを通過。

View File

@ -226,13 +226,43 @@ pub struct Ring0Context { pub io: Box<dyn IoApi>, /* ... */ }
| SSOT | 分散 | 1ファイル | 保守性向上 | | SSOT | 分散 | 1ファイル | 保守性向上 |
| IDE支援 | なし | 補完可能 | 開発体験向上 | | IDE支援 | なし | 補完可能 | 開発体験向上 |
### 5.3 Phase 85 との関係 ### 5.3 Phase 85 との関係FileBox 再分類)
Phase 85 の調査結果を完全反映 Phase 85 の時点では、次の 3 区分で Box を分類していた
- **core_required (6個)**: StringBox, IntegerBox, BoolBox, ArrayBox, MapBox, ConsoleBox - **core_required (6個)**: StringBox, IntegerBox, BoolBox, ArrayBox, MapBox, ConsoleBox
- **core_optional (9個)**: FloatBox, NullBox, FileBox, PathBox, RegexBox, MathBox, TimeBox, JsonBox, TomlBox - **core_optional (9個)**: FloatBox, NullBox, FileBox, PathBox, RegexBox, MathBox, TimeBox, JsonBox, TomlBox
- **特殊型 (4個)**: FunctionBox, ResultBox, MethodBox, MissingBox - **特殊型 (4個)**: FunctionBox, ResultBox, MethodBox, MissingBox
その後、Ring0/Ring1-Core の整理と selfhost ラインの安定化を進める中で、
FileBox は selfhost/通常ランタイムでは事実上必須(ログ・ツール・ハコチェックなどで常用)
であることが明確になったため、「core_required 相当」として扱うよう設計を更新した。
現行の分類は次の通り:
- **core_required (7個)**: StringBox, IntegerBox, BoolBox, ArrayBox, MapBox, ConsoleBox, FileBox
- **core_optional (8個)**: FloatBox, NullBox, PathBox, RegexBox, MathBox, TimeBox, JsonBox, TomlBox
- **特殊型 (4個)**: FunctionBox, ResultBox, MethodBox, MissingBox
最終的なソース・オブ・トゥルースは `src/runtime/core_box_ids.rs``CoreBoxId::is_core_required()` /
`CoreBoxId::category()` であり、このドキュメントはその意図を補足する設計メモとして位置づけている。
## Phase 106: 設計統一案B
### 責務分離原則
- **CoreBoxId**: 「必須かどうか」の判定is_core_required() / category()
- selfhost/default では File が必須
- 将来 minimal/no-fs プロファイルでは optional に変更可能
- **provider_lock**: 「FileBox provider を登録・読む」のみ(シンプルなロック機構)
- **PluginHost**: startup 時に CoreBoxId.is_core_required() で provider をチェック
- 未登録なら CoreInitError::MissingService で fail-fast
### Ring0.FsApi との関係Phase 107 延期)
Ring0.FsApiwrite 能力あり)と FileIo traitread-onlyの統合は、
Phase 107+ で実施予定。現在は概念を分離したまま。
(理由: Phase 106 は provider_lock 整理に専念し、FsApi 統合は別 phase で)
### 5.4 今後の拡張 ### 5.4 今後の拡張
新しいメソッド追加は `src/runtime/core_box_ids.rs` の編集のみで完結: 新しいメソッド追加は `src/runtime/core_box_ids.rs` の編集のみで完結:

View File

@ -2,6 +2,10 @@
// Nyashの箱システムによるファイル入出力を提供します。 // Nyashの箱システムによるファイル入出力を提供します。
// 参考: 既存Boxの設計思想 // 参考: 既存Boxの設計思想
// SSOT: FileBox は「FileIo provider を常に経由する」provider_lock に一元化)。
// provider の有無・必須/optional の判定は provider_lock/CoreBoxId の責務で、
// FileBox 実装内では生の環境変数や静的状態を見ない設計。
// SSOT provider design (ring0/1) — modules are currently placeholders // SSOT provider design (ring0/1) — modules are currently placeholders
pub mod box_shim; // Thin delegating shim pub mod box_shim; // Thin delegating shim
pub mod builtin_factory; pub mod builtin_factory;

View File

@ -20,7 +20,8 @@ use std::{fs, process};
impl NyashRunner { impl NyashRunner {
// legacy run_file_legacy removed (was commented out) // legacy run_file_legacy removed (was commented out)
/// Helper: run PyVM harness over a MIR module, returning the exit code /// Legacy helper: run PyVM harness over a MIR module, returning the exit code.
/// The PyVM line is retired from regular development/CI; kept for historical debugging only.
fn run_pyvm_harness( fn run_pyvm_harness(
&self, &self,
module: &nyash_rust::mir::MirModule, module: &nyash_rust::mir::MirModule,

View File

@ -2,7 +2,10 @@ use super::super::NyashRunner;
use nyash_rust::{mir::MirCompiler, parser::NyashParser}; use nyash_rust::{mir::MirCompiler, parser::NyashParser};
use std::{fs, process}; use std::{fs, process};
/// Execute using PyVM only (no Rust VM runtime). Emits MIR(JSON) and invokes tools/pyvm_runner.py. /// Legacy helper: execute using PyVM only (no Rust VM runtime).
/// Emits MIR(JSON) and invokes tools/pyvm_runner.py.
/// NOTE: The PyVM line is retired from regular development/CI; this mode is kept for
/// historical debugging and should not be used as a primary execution path.
pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
if crate::config::env::env_bool("NYASH_PYVM_TRACE") { if crate::config::env::env_bool("NYASH_PYVM_TRACE") {
eprintln!("[pyvm] entry"); eprintln!("[pyvm] entry");

View File

@ -104,18 +104,27 @@ impl CoreBoxId {
Self::iter().find(|id| id.name() == name) Self::iter().find(|id| id.name() == name)
} }
/// Phase 86: core_required チェック /// Phase 106: core_required チェック
///
/// FileBox は Phase 85 では core_optional として分類していたが、
/// selfhost/通常ランタイムでは事実上必須(ログ・ツール・ハコチェック等で常用)
/// であることが明確になったため、「core_required 相当」として扱う設計に統一した。
///
/// **設計原則**:
/// - 必須判定は CoreBoxId に一本化provider_lock は「登録・読む」だけ)
/// - 将来 minimal/no-fs プロファイルを導入する場合は、ここで profile パラメータを追加可能
pub fn is_core_required(&self) -> bool { pub fn is_core_required(&self) -> bool {
use CoreBoxId::*; use CoreBoxId::*;
matches!(self, String | Integer | Bool | Array | Map | Console) matches!(self, String | Integer | Bool | Array | Map | Console | File)
} }
/// Phase 87: カテゴリ分類 /// Phase 87: カテゴリ分類
pub fn category(&self) -> CoreBoxCategory { pub fn category(&self) -> CoreBoxCategory {
use CoreBoxId::*; use CoreBoxId::*;
match self { match self {
String | Integer | Bool | Array | Map | Console => CoreBoxCategory::CoreRequired, // Phase 106: File を CoreRequired 側に移動selfhost/通常ランタイムでは必須)
Float | Null | File | Path | Regex | Math | Time | Json | Toml => CoreBoxCategory::CoreOptional, String | Integer | Bool | Array | Map | Console | File => CoreBoxCategory::CoreRequired,
Float | Null | Path | Regex | Math | Time | Json | Toml => CoreBoxCategory::CoreOptional,
Function | Result | Method | Missing => CoreBoxCategory::Special, Function | Result | Method | Missing => CoreBoxCategory::Special,
} }
} }
@ -342,23 +351,24 @@ mod tests {
#[test] #[test]
fn test_core_box_id_is_core_required() { fn test_core_box_id_is_core_required() {
// Phase 85: core_required (6個) // Phase 85: core_required (6個) + FileBox を実質必須扱いに拡張
assert!(CoreBoxId::String.is_core_required()); assert!(CoreBoxId::String.is_core_required());
assert!(CoreBoxId::Integer.is_core_required()); assert!(CoreBoxId::Integer.is_core_required());
assert!(CoreBoxId::Bool.is_core_required()); assert!(CoreBoxId::Bool.is_core_required());
assert!(CoreBoxId::Array.is_core_required()); assert!(CoreBoxId::Array.is_core_required());
assert!(CoreBoxId::Map.is_core_required()); assert!(CoreBoxId::Map.is_core_required());
assert!(CoreBoxId::Console.is_core_required()); assert!(CoreBoxId::Console.is_core_required());
assert!(CoreBoxId::File.is_core_required());
// Phase 85: core_optional // core_optional の代表例
assert!(!CoreBoxId::File.is_core_required());
assert!(!CoreBoxId::Float.is_core_required()); assert!(!CoreBoxId::Float.is_core_required());
} }
#[test] #[test]
fn test_core_box_id_category() { fn test_core_box_id_category() {
assert_eq!(CoreBoxId::String.category(), CoreBoxCategory::CoreRequired); assert_eq!(CoreBoxId::String.category(), CoreBoxCategory::CoreRequired);
assert_eq!(CoreBoxId::File.category(), CoreBoxCategory::CoreOptional); // Phase 106: File の分類を修正
assert_eq!(CoreBoxId::File.category(), CoreBoxCategory::CoreRequired);
assert_eq!(CoreBoxId::Function.category(), CoreBoxCategory::Special); assert_eq!(CoreBoxId::Function.category(), CoreBoxCategory::Special);
} }

View File

@ -153,6 +153,18 @@ impl PluginHost {
config: CoreServicesConfig, config: CoreServicesConfig,
) -> Result<Self, CoreInitError> { ) -> Result<Self, CoreInitError> {
use crate::runtime::core_services::*; use crate::runtime::core_services::*;
use crate::runtime::provider_lock;
// Phase 106: FileBox provider チェック追加
// CoreBoxId がFileを必須と判定している場合、provider が登録されていることを確認
if CoreBoxId::File.is_core_required() {
if provider_lock::get_filebox_provider().is_none() {
return Err(CoreInitError::MissingService {
box_id: CoreBoxId::File,
message: "FileBox provider not registered (required for selfhost/default profile)".to_string(),
});
}
}
let mut core = CoreServices { let mut core = CoreServices {
string: None, string: None,
@ -291,11 +303,16 @@ mod tests {
// Phase 94: 実際の registry を使用してテスト // Phase 94: 実際の registry を使用してテスト
use crate::runtime::ring0::default_ring0; use crate::runtime::ring0::default_ring0;
use crate::box_factory::builtin::BuiltinBoxFactory; use crate::box_factory::builtin::BuiltinBoxFactory;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::runtime::provider_lock;
let ring0 = Arc::new(default_ring0()); let ring0 = Arc::new(default_ring0());
let mut registry = UnifiedBoxRegistry::new(); let mut registry = UnifiedBoxRegistry::new();
registry.register(Arc::new(BuiltinBoxFactory::new())); registry.register(Arc::new(BuiltinBoxFactory::new()));
// Phase 106: Initialize FileBox provider before PluginHost creation
let _ = provider_lock::set_filebox_provider(Arc::new(CoreRoFileIo::new()));
let plugin_host = PluginHost::with_core_from_registry(ring0, &registry) let plugin_host = PluginHost::with_core_from_registry(ring0, &registry)
.expect("CoreServices should be initialized with builtin boxes"); .expect("CoreServices should be initialized with builtin boxes");
@ -308,11 +325,16 @@ mod tests {
// Phase 94: 実際の registry を使用して全フィールドが存在することを確認 // Phase 94: 実際の registry を使用して全フィールドが存在することを確認
use crate::runtime::ring0::default_ring0; use crate::runtime::ring0::default_ring0;
use crate::box_factory::builtin::BuiltinBoxFactory; use crate::box_factory::builtin::BuiltinBoxFactory;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::runtime::provider_lock;
let ring0 = Arc::new(default_ring0()); let ring0 = Arc::new(default_ring0());
let mut registry = UnifiedBoxRegistry::new(); let mut registry = UnifiedBoxRegistry::new();
registry.register(Arc::new(BuiltinBoxFactory::new())); registry.register(Arc::new(BuiltinBoxFactory::new()));
// Phase 106: Initialize FileBox provider before PluginHost creation
let _ = provider_lock::set_filebox_provider(Arc::new(CoreRoFileIo::new()));
let plugin_host = PluginHost::with_core_from_registry(ring0, &registry) let plugin_host = PluginHost::with_core_from_registry(ring0, &registry)
.expect("CoreServices should be initialized"); .expect("CoreServices should be initialized");
@ -371,17 +393,56 @@ mod tests {
let result = PluginHost::with_core_from_registry(ring0, &registry); let result = PluginHost::with_core_from_registry(ring0, &registry);
assert!(result.is_err()); assert!(result.is_err());
// Phase 95.5: エラーメッセージに "not found in registry" が含まれることを確認 // Phase 95.5 + Phase 106: エラーメッセージチェック
// FileBox provider が未登録の場合は、CoreBoxId::File のエラーが優先される
if let Err(e) = result { if let Err(e) = result {
let msg = format!("{}", e); let msg = format!("{}", e);
eprintln!("Error message: {}", msg); // デバッグ出力 eprintln!("Error message: {}", msg); // デバッグ出力
assert!( assert!(
msg.contains("not found in registry") || msg.contains("creation failed") || msg.contains("Unknown Box type"), msg.contains("not found in registry") ||
"Error message should contain 'not found in registry', 'creation failed' or 'Unknown Box type', got: {}", msg.contains("creation failed") ||
msg.contains("Unknown Box type") ||
msg.contains("FileBox provider not registered"),
"Error message should contain expected error patterns, got: {}",
msg msg
); );
} }
} }
#[test]
fn test_with_core_from_registry_filebox_required() {
// Phase 106: FileBox provider なし → エラー
// Note: この test は provider_lock::get_filebox_provider() が None を返す場合のみ有効。
// OnceLock の性質上、同一プロセス内で他のテストが先に provider を set すると
// このテストは期待通りに動作しないprovider が既に存在するため)。
// そのため、provider が既に set されている場合は test を skip する。
use crate::runtime::ring0::default_ring0;
use crate::box_factory::builtin::BuiltinBoxFactory;
use crate::runtime::provider_lock;
// provider が既に set されている場合は test skip
if provider_lock::get_filebox_provider().is_some() {
eprintln!("Skipping test_with_core_from_registry_filebox_required: provider already set by another test");
return;
}
let ring0 = Arc::new(default_ring0());
let mut registry = UnifiedBoxRegistry::new();
registry.register(Arc::new(BuiltinBoxFactory::new()));
// provider_lock を初期化せず(呼び出さず)
// provider が無い状態で with_core_from_registry() を呼ぶ
let result = PluginHost::with_core_from_registry(ring0, &registry);
assert!(result.is_err());
if let Err(CoreInitError::MissingService { box_id, .. }) = result {
assert_eq!(box_id, CoreBoxId::File);
} else {
panic!("Expected MissingService error for FileBox");
}
}
} }
#[cfg(test)] #[cfg(test)]