From b2f017e94e02b40e3e185e61aae6bd9610bb0909 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 13 Mar 2026 13:38:01 +0000 Subject: [PATCH] docs: add CLAUDE.md project instructions Co-Authored-By: Virgil --- CLAUDE.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..607841e --- /dev/null +++ b/CLAUDE.md @@ -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.