feat(io): Migrate filesystem access to pkg/io Medium abstraction (#172)

* feat(io): add pkg/io with symlink-safe path validation

- Add pkg/io with Medium interface for filesystem abstraction
- Add pkg/io/local with sandboxed filesystem implementation
- Add symlink-safe path validation to prevent bypass attacks
- Add sentinel errors (ErrPathTraversal, ErrSymlinkTraversal)
- Add NewSandboxed() for creating sandboxed Medium instances
- Add MockMedium for testing

Closes #169

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(io): extend Medium interface with Delete, Rename, List, Stat operations

Add missing filesystem operations to Medium interface:
- Delete(path) - removes file or empty directory
- DeleteAll(path) - removes path and contents recursively
- Rename(old, new) - moves or renames files/directories
- Exists(path) - checks if path exists
- IsDir(path) - checks if path is a directory
- List(path) - returns directory contents as []os.DirEntry
- Stat(path) - returns file info as os.FileInfo

Implements both local.Medium and MockMedium with full support.

Closes #102

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(io): MockMedium.Read returns os.ErrNotExist for consistency

Ensures os.IsNotExist(err) works with MockMedium like with real filesystem.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-02-01 22:50:55 +00:00 committed by GitHub
parent 3365bfd5ba
commit 5631e04a12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,7 +1,7 @@
package io
import (
"errors"
"os"
coreerr "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/io/local"
@ -104,7 +104,7 @@ func NewMockMedium() *MockMedium {
func (m *MockMedium) Read(path string) (string, error) {
content, ok := m.Files[path]
if !ok {
return "", coreerr.E("io.MockMedium.Read", "file not found: "+path, errors.New("file not found"))
return "", coreerr.E("io.MockMedium.Read", "file not found: "+path, os.ErrNotExist)
}
return content, nil
}