feat(filebox): Phase 108 FileBox write/write_all implementation

Implement write functionality for FileBox following Phase 108 specification,
completing the read/write pipeline via Ring0.FsApi.

## Implementation Summary

### Task 2: FsApi / Ring0FsFileIo write implementation
- Added write() method to FileIo trait
- Implemented write() in Ring0FsFileIo (truncate mode via Ring0.FsApi.write_all)
- Updated FileCaps to { read: true, write: true } for standard profile
- Added write() stub to CoreRoFileIo (returns Unsupported)

### Task 3: FileBox write/write_all implementation
- Updated FileBox.write_all() to delegate to provider.write()
- Updated FileBox.write() to convert content to text and call provider.write()
- UTF-8 conversion via String::from_utf8_lossy (text-oriented design)
- Returns "OK" on success, "Error: ..." on failure

### Task 4: Test coverage
- Round-trip test (write → read):  PASS
- Truncate mode verification:  PASS
- Write without open error:  PASS
- Read-only provider rejection:  PASS
- Auto-registration test updated:  PASS

### Task 5: Documentation updates
- phase107_fsapi_fileio_bridge.md: Added Phase 108 section
- core_boxes_design.md: Updated Ring0.FsApi relationship section
- CURRENT_TASK.md: Added Phase 108 completion entry

## Design Decisions (from phase108_filebox_write_semantics.md)

- **Write mode**: truncate (overwrite existing file each time)
- **Text-oriented**: UTF-8 conversion via from_utf8_lossy
- **Append mode**: Planned for Phase 109+
- **Error handling**: FileError::Io for failures, Fail-Fast on caps.write=false

## Test Results

```
cargo test --release --lib filebox
test result: ok. 5 passed; 0 failed; 1 ignored
```

All FileBox tests pass, including Phase 107 compatibility tests.

## Pipeline Complete

```
FileBox.write(content)
    ↓
FileBox.write_all(buf)
    ↓
provider.write(text) ← Ring0FsFileIo implementation
    ↓
Ring0.FsApi.write_all()
    ↓
std::fs::write()
```

## Next Steps (Backlog)

- Phase 109: minimal/no-fs profile
- Phase 110: FileHandleBox (multiple files simultaneously)
- Phase 111: append mode implementation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-03 18:40:33 +09:00
parent d3e43b9a45
commit 61dc4dec9b
8 changed files with 206 additions and 54 deletions

View File

@ -87,19 +87,19 @@ impl FileBox {
}
}
pub fn write_all(&self, _buf: &[u8]) -> Result<(), String> {
// Fail-Fast by capability: consult provider caps
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Err("Write unsupported by current FileBox provider (read-only)".to_string());
pub fn write_all(&self, buf: &[u8]) -> Result<(), String> {
if let Some(ref provider) = self.provider {
let caps = provider.caps();
if !caps.write {
return Err("Write not supported by FileBox provider".to_string());
}
// Phase 108: UTF-8 conversion (text-oriented design)
let text = String::from_utf8_lossy(buf).to_string();
provider.write(&text)
.map_err(|e| format!("Write failed: {:?}", e))
} else {
Err("No provider available".to_string())
}
// Write-capable provider not wired yet
Err("Write supported by provider but not implemented in this build".to_string())
}
/// ファイルの内容を読み取る
@ -111,21 +111,28 @@ impl FileBox {
}
/// ファイルに内容を書き込む
pub fn write(&self, _content: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new(
"Error: write unsupported by provider (read-only)",
));
pub fn write(&self, content: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(ref provider) = self.provider {
let caps = provider.caps();
if !caps.write {
return Box::new(StringBox::new(
"Error: write not supported by provider (read-only)".to_string()
));
}
// Phase 108: Convert content to text
let text = if let Some(str_box) = content.as_any().downcast_ref::<StringBox>() {
str_box.to_string_box().value
} else {
content.to_string_box().value
};
match provider.write(&text) {
Ok(()) => Box::new(StringBox::new("OK".to_string())),
Err(e) => Box::new(StringBox::new(format!("Error: {:?}", e))),
}
} else {
Box::new(StringBox::new("Error: no provider available".to_string()))
}
Box::new(StringBox::new(
"Error: write supported but not implemented in this build",
))
}
/// ファイルが存在するかチェック

View File

@ -52,8 +52,9 @@ pub trait FileIo: Send + Sync {
fn caps(&self) -> FileCaps;
fn open(&self, path: &str) -> FileResult<()>;
fn read(&self) -> FileResult<String>;
fn write(&self, text: &str) -> FileResult<()>; // Phase 108: write support
fn close(&self) -> FileResult<()>;
// Future: write/exists/stat …
// Future: exists/stat …
}
/// Normalize newlines to LF (optional helper)