5.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
forge.lthn.ai/core/go-io is the mandatory I/O abstraction layer for the CoreGO ecosystem. All data access — files, configs, journals, state — MUST go through the io.Medium interface. Never use raw os, filepath, or ioutil calls.
The Premise
The directory you start your binary in becomes the immutable root. io.NewSandboxed(".") (or the CWD at launch) defines the filesystem boundary — everything the process sees is relative to that root. This is the SASE containment model.
If you need a top-level system process (root at /), you literally run it from / — but that should only be for internal services never publicly exposed. Any user-facing or agent-facing process runs sandboxed to its project directory.
Swap the Medium and the same code runs against S3, SQLite, a Borg DataNode, or a runc rootFS — the binary doesn't know or care. This is what makes LEM model execution safe: the runner is an apartment with walls, not an open box.
Commands
core go test # Run all tests
core go test --run Name # Single test
core go fmt # Format
core go lint # Lint
core go vet # Vet
core go qa # fmt + vet + lint + test
If running go directly (outside core), set GOWORK=off to avoid workspace resolution errors:
GOWORK=off go test -cover ./...
Architecture
Core Interface
io.Medium — 17 methods: Read, Write, WriteMode, EnsureDir, IsFile, Delete, DeleteAll, Rename, List, Stat, Open, Create, Append, ReadStream, WriteStream, Exists, IsDir.
// Sandboxed to a project directory
m, _ := io.NewSandboxed("/home/user/projects/example.com")
m.Write("config/app.yaml", content) // writes inside sandbox
m.Read("../../../etc/passwd") // blocked — escape detected
// Unsandboxed system access (use sparingly)
io.Local.Read("/etc/hostname")
// Copy between any two mediums
io.Copy(s3Medium, "backup.tar", localMedium, "restore/backup.tar")
Backends (8 implementations)
| Package | Backend | Use Case |
|---|---|---|
local |
Local filesystem | Default, sandboxed path validation, symlink escape detection |
s3 |
AWS S3 | Cloud storage, prefix-scoped |
sqlite |
SQLite (WAL mode) | Embedded database storage |
node |
In-memory + tar | Borg DataNode port, also implements fs.FS/fs.ReadFileFS/fs.ReadDirFS |
datanode |
Borg DataNode | Thread-safe (RWMutex) in-memory, snapshot/restore via tar |
store |
SQLite KV store | Group-namespaced key-value with Go template rendering |
workspace |
Core service | Encrypted workspaces, SHA-256 IDs, PGP keypairs |
MemoryMedium |
In-memory map | Testing — no filesystem needed |
store.Medium maps filesystem paths as group/key — first path segment is the group, remainder is the key. List("") returns groups as directories.
Sigil Transformation Framework (sigil/)
Composable data transformations applied in chains:
| Sigil | Purpose |
|---|---|
ReverseSigil |
Byte reversal (symmetric) |
HexSigil |
Base16 encoding |
Base64Sigil |
Base64 encoding |
GzipSigil |
Compression |
JSONSigil |
JSON formatting |
HashSigil |
Cryptographic hashing (SHA-256, SHA-512, BLAKE2, etc.) |
ChaChaPolySigil |
XChaCha20-Poly1305 encryption with pre-obfuscation |
Pre-obfuscation strategies: XORObfuscator, ShuffleMaskObfuscator.
// Encrypt then compress
encrypted, _ := sigil.Transmute(data, []sigil.Sigil{chacha, gzip})
// Decompress then decrypt (reverse order)
plain, _ := sigil.Untransmute(encrypted, []sigil.Sigil{chacha, gzip})
Sigils can be created by name via sigil.NewSigil("hex"), sigil.NewSigil("sha256"), etc.
Security
local.Medium.validatePath()follows symlinks component-by-component, checks each resolved path is still under root- Sandbox escape attempts log
[SECURITY]to stderr with timestamp, root, attempted path, username DeleteandDeleteAllrefuse/and$HOMEio.NewSandboxed(root)enforces containment — this is the SASE boundary
Conventions
Import Aliasing
Standard io is always aliased to avoid collision with this package:
goio "io"
coreerr "forge.lthn.ai/core/go-log"
coreio "forge.lthn.ai/core/go-io" // when imported from subpackages
Error Handling
All errors use coreerr.E("pkg.Method", "description", wrappedErr) from forge.lthn.ai/core/go-log. Follow this pattern in new code.
Compile-Time Interface Checks
Backend packages use var _ io.Medium = (*Medium)(nil) to verify interface compliance at compile time.
Dependencies
forge.lthn.ai/Snider/Borg— DataNode containerforge.lthn.ai/core/go-log— error handling (coreerr.E())forge.lthn.ai/core/go— Core DI (workspace service only)forge.lthn.ai/core/go-crypt— PGP key generation (workspace service only)aws-sdk-go-v2— S3 backendgolang.org/x/crypto— XChaCha20-Poly1305, BLAKE2, SHA-3 (sigil package)modernc.org/sqlite— SQLite backends (pure Go, no CGO)github.com/stretchr/testify— test assertions
Sentinel Errors
Sentinel errors (var NotFoundError, var InvalidKeyError, etc.) use standard errors.New() — this is correct Go convention. Only inline error returns in functions should use coreerr.E().
Testing
Use io.NewMemoryMedium() or io.NewSandboxed(t.TempDir()) in tests — never hit real S3/SQLite unless integration testing. S3 tests use an interface-based mock (s3.Client).