docs: add CLAUDE.md project instructions

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-13 13:38:01 +00:00
parent a97bbc4ae2
commit b2f017e94e

122
CLAUDE.md Normal file
View file

@ -0,0 +1,122 @@
# 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
```bash
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
```
## Architecture
### Core Interface
`io.Medium` — 18 methods: Read, Write, EnsureDir, IsFile, FileGet, FileSet, Delete, DeleteAll, Rename, List, Stat, Open, Create, Append, ReadStream, WriteStream, Exists, IsDir.
```go
// 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 |
| `MockMedium` | 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`.
```go
// 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
- `Delete` and `DeleteAll` refuse `/` and `$HOME`
- `io.NewSandboxed(root)` enforces containment — this is the SASE boundary
## Conventions
### Import Aliasing
Standard `io` is always aliased to avoid collision with this package:
```go
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 container
- `forge.lthn.ai/core/go-log` — error handling (`coreerr.E()`)
- `forge.lthn.ai/core/go/pkg/core` — Core DI (workspace service only)
- `aws-sdk-go-v2` — S3 backend
- `modernc.org/sqlite` — SQLite backends (pure Go, no CGO)
## Testing
All backends have full test coverage. Use `io.MockMedium` or `io.NewSandboxed(t.TempDir())` in tests — never hit real S3/SQLite unless integration testing.