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

@ -42,8 +42,31 @@ impl FileIo for CoreRoFileIo {
}
}
fn write(&self, _text: &str) -> FileResult<()> {
// CoreRoFileIo is read-only, write is not supported
Err(FileError::Unsupported)
}
fn close(&self) -> FileResult<()> {
*self.handle.write().unwrap() = None;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_core_ro_write_unsupported() {
let fileio = CoreRoFileIo::new();
// Write should fail with Unsupported
let result = fileio.write("test");
assert!(result.is_err());
match result.unwrap_err() {
FileError::Unsupported => { /* expected */ }
_ => panic!("Expected Unsupported error"),
}
}
}

View File

@ -39,8 +39,8 @@ impl Ring0FsFileIo {
impl FileIo for Ring0FsFileIo {
fn caps(&self) -> FileCaps {
// Phase 107: Read-only
FileCaps::read_only()
// Phase 108: Read/write support
FileCaps { read: true, write: true }
}
fn open(&self, path: &str) -> FileResult<()> {
@ -80,6 +80,22 @@ impl FileIo for Ring0FsFileIo {
}
}
fn write(&self, text: &str) -> FileResult<()> {
let current_path = self.path.read().unwrap();
match current_path.as_ref() {
Some(path) => {
// Delegate to Ring0.FsApi (truncate mode: overwrite existing file)
let path_obj = Path::new(path);
self.ring0.fs.write_all(path_obj, text.as_bytes())
.map_err(|e| FileError::Io(format!("Write failed: {}", e)))
}
None => {
Err(FileError::Io("No file is currently open. Call open() first.".to_string()))
}
}
}
fn close(&self) -> FileResult<()> {
let mut current_path = self.path.write().unwrap();
*current_path = None;
@ -113,10 +129,10 @@ mod tests {
let ring0 = Arc::new(default_ring0());
let fileio = Ring0FsFileIo::new(ring0);
// Test capabilities
// Test capabilities (Phase 108: write support added)
let caps = fileio.caps();
assert!(caps.read);
assert!(!caps.write);
assert!(caps.write);
// Test open
assert!(fileio.open(test_path).is_ok());
@ -175,4 +191,70 @@ mod tests {
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
// ===== Phase 108: Write tests =====
#[test]
fn test_filebox_write_read_roundtrip() {
let test_path = "/tmp/phase108_roundtrip.txt";
let test_content = "Hello, Phase 108!";
// Setup: Create file first (open() requires file to exist)
setup_test_file(test_path, "initial");
let ring0 = Arc::new(default_ring0());
let fileio = Ring0FsFileIo::new(ring0);
// Test capabilities
let caps = fileio.caps();
assert!(caps.read);
assert!(caps.write);
// Write content (truncate mode)
assert!(fileio.open(test_path).is_ok());
assert!(fileio.write(test_content).is_ok());
assert!(fileio.close().is_ok());
// Read back and verify
assert!(fileio.open(test_path).is_ok());
let content = fileio.read().unwrap();
assert_eq!(content, test_content);
assert!(fileio.close().is_ok());
// Cleanup
cleanup_test_file(test_path);
}
#[test]
fn test_filebox_write_truncate_mode() {
let test_path = "/tmp/phase108_truncate.txt";
setup_test_file(test_path, "Original content");
let ring0 = Arc::new(default_ring0());
let fileio = Ring0FsFileIo::new(ring0);
// Overwrite with new content (truncate mode)
assert!(fileio.open(test_path).is_ok());
assert!(fileio.write("New content").is_ok());
assert!(fileio.close().is_ok());
// Verify truncate behavior
assert!(fileio.open(test_path).is_ok());
let content = fileio.read().unwrap();
assert_eq!(content, "New content");
assert!(fileio.close().is_ok());
cleanup_test_file(test_path);
}
#[test]
fn test_filebox_write_without_open() {
let ring0 = Arc::new(default_ring0());
let fileio = Ring0FsFileIo::new(ring0);
// Write without open should fail
let result = fileio.write("test");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No file is currently open"));
}
}