[agent/codex] Full audit per issue #4. Read CLAUDE.md. Report ALL findings... #5

Merged
Virgil merged 11 commits from agent/deep-audit-per-issue--4--read-claude-md into dev 2026-03-22 18:12:40 +00:00
11 changed files with 128 additions and 70 deletions

View file

@ -22,6 +22,12 @@ 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:
```bash
GOWORK=off go test -cover ./...
```
## Architecture
@ -113,10 +119,17 @@ Backend packages use `var _ io.Medium = (*Medium)(nil)` to verify interface comp
- `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)
- `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 backend
- `golang.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 ErrNotFound`, `var ErrInvalidKey`, etc.) use standard `errors.New()` — this is correct Go convention. Only inline error returns in functions should use `coreerr.E()`.
## 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.
Use `io.MockMedium` or `io.NewSandboxed(t.TempDir())` in tests — never hit real S3/SQLite unless integration testing. S3 tests use an interface-based mock (`s3API`).

View file

@ -135,6 +135,10 @@ func (m *Medium) Write(p, content string) error {
return nil
}
func (m *Medium) WriteMode(p, content string, mode os.FileMode) error {
return m.Write(p, content)
}
func (m *Medium) EnsureDir(p string) error {
m.mu.Lock()
defer m.mu.Unlock()

View file

@ -4,7 +4,7 @@ import (
"io"
"testing"
coreio "forge.lthn.ai/core/go-io"
coreio "dappco.re/go/core/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

7
go.mod
View file

@ -1,20 +1,21 @@
module forge.lthn.ai/core/go-io
module dappco.re/go/core/io
go 1.26.0
require (
dappco.re/go/core v0.4.7
forge.lthn.ai/Snider/Borg v0.3.1
forge.lthn.ai/core/go v0.3.1
forge.lthn.ai/core/go-crypt v0.1.6
forge.lthn.ai/core/go-log v0.0.4
github.com/aws/aws-sdk-go-v2 v1.41.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.49.0
modernc.org/sqlite v1.46.2
modernc.org/sqlite v1.47.0
)
require (
forge.lthn.ai/core/go v0.3.0 // indirect
github.com/ProtonMail/go-crypto v1.4.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect

10
go.sum
View file

@ -1,7 +1,9 @@
dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA=
dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM=
forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4=
forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
forge.lthn.ai/core/go-crypt v0.1.6 h1:jB7L/28S1NR+91u3GcOYuKfBLzPhhBUY1fRe6WkGVns=
forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo=
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
@ -97,8 +99,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE=
modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk=
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

11
io.go
View file

@ -9,7 +9,7 @@ import (
"time"
coreerr "forge.lthn.ai/core/go-log"
"forge.lthn.ai/core/go-io/local"
"dappco.re/go/core/io/local"
)
// Medium defines the standard interface for a storage backend.
@ -20,8 +20,13 @@ type Medium interface {
Read(path string) (string, error)
// Write saves the given content to a file, overwriting it if it exists.
// Default permissions: 0644. For sensitive files, use WriteMode.
Write(path, content string) error
// WriteMode saves content with explicit file permissions.
// Use 0600 for sensitive files (keys, secrets, encrypted output).
WriteMode(path, content string, mode os.FileMode) error
// EnsureDir makes sure a directory exists, creating it if necessary.
EnsureDir(path string) error
@ -200,6 +205,10 @@ func (m *MockMedium) Write(path, content string) error {
return nil
}
func (m *MockMedium) WriteMode(path, content string, mode os.FileMode) error {
return m.Write(path, content)
}
// EnsureDir records that a directory exists in the mock filesystem.
func (m *MockMedium) EnsureDir(path string) error {
m.Dirs[path] = true

View file

@ -124,7 +124,15 @@ func (m *Medium) Read(p string) (string, error) {
}
// Write saves content to file, creating parent directories as needed.
// Files are created with mode 0644. For sensitive files (keys, secrets),
// use WriteMode with 0600.
func (m *Medium) Write(p, content string) error {
return m.WriteMode(p, content, 0644)
}
// WriteMode saves content to file with explicit permissions.
// Use 0600 for sensitive files (encryption output, private keys, auth hashes).
func (m *Medium) WriteMode(p, content string, mode os.FileMode) error {
full, err := m.validatePath(p)
if err != nil {
return err
@ -132,7 +140,7 @@ func (m *Medium) Write(p, content string) error {
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
return err
}
return os.WriteFile(full, []byte(content), 0644)
return os.WriteFile(full, []byte(content), mode)
}
// EnsureDir creates directory if it doesn't exist.

View file

@ -15,7 +15,7 @@ import (
"strings"
"time"
coreio "forge.lthn.ai/core/go-io"
coreio "dappco.re/go/core/io"
)
// Node is an in-memory filesystem that implements coreio.Node (and therefore
@ -361,6 +361,11 @@ func (n *Node) Write(p, content string) error {
return nil
}
// WriteMode saves content with explicit permissions (no-op for in-memory node).
func (n *Node) WriteMode(p, content string, mode os.FileMode) error {
return n.Write(p, content)
}
// FileGet is an alias for Read.
func (n *Node) FileGet(p string) (string, error) {
return n.Read(p)
@ -607,6 +612,3 @@ var _ fs.File = (*dataFileReader)(nil)
// ensure all internal compile-time checks are grouped above
// no further type assertions needed
// unused import guard
var _ = os.ErrNotExist

View file

@ -11,9 +11,9 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
coreerr "forge.lthn.ai/core/go-log"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/md4"
@ -204,7 +204,7 @@ func (s *HashSigil) In(data []byte) ([]byte, error) {
h, _ = blake2b.New512(nil)
default:
// MD5SHA1 is not supported as a direct hash
return nil, errors.New("sigil: hash algorithm not available")
return nil, coreerr.E("sigil.HashSigil.In", "hash algorithm not available", nil)
}
h.Write(data)
@ -269,6 +269,6 @@ func NewSigil(name string) (Sigil, error) {
case "blake2b-512":
return NewHashSigil(crypto.BLAKE2b_512), nil
default:
return nil, errors.New("sigil: unknown sigil name")
return nil, coreerr.E("sigil.NewSigil", "unknown sigil name: "+name, nil)
}
}

View file

@ -7,14 +7,29 @@ import (
"path/filepath"
"sync"
core "dappco.re/go/core"
coreerr "forge.lthn.ai/core/go-log"
core "forge.lthn.ai/core/go/pkg/core"
"forge.lthn.ai/core/go-io"
"dappco.re/go/core/io"
)
// Service implements the core.Workspace interface.
// Workspace provides management for encrypted user workspaces.
type Workspace interface {
CreateWorkspace(identifier, password string) (string, error)
SwitchWorkspace(name string) error
WorkspaceFileGet(filename string) (string, error)
WorkspaceFileSet(filename, content string) error
}
// cryptProvider is the interface for PGP key generation.
type cryptProvider interface {
CreateKeyPair(name, passphrase string) (string, error)
}
// Service implements the Workspace interface.
type Service struct {
core *core.Core
crypt cryptProvider
activeWorkspace string
rootPath string
medium io.Medium
@ -22,7 +37,8 @@ type Service struct {
}
// New creates a new Workspace service instance.
func New(c *core.Core) (any, error) {
// An optional cryptProvider can be passed to supply PGP key generation.
func New(c *core.Core, crypt ...cryptProvider) (any, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, coreerr.E("workspace.New", "failed to determine home directory", err)
@ -35,6 +51,10 @@ func New(c *core.Core) (any, error) {
medium: io.Local,
}
if len(crypt) > 0 && crypt[0] != nil {
s.crypt = crypt[0]
}
if err := s.medium.EnsureDir(rootPath); err != nil {
return nil, coreerr.E("workspace.New", "failed to ensure root directory", err)
}
@ -43,13 +63,16 @@ func New(c *core.Core) (any, error) {
}
// CreateWorkspace creates a new encrypted workspace.
// Identifier is hashed (SHA-256 as proxy for LTHN) to create the directory name.
// Identifier is hashed (SHA-256) to create the directory name.
// A PGP keypair is generated using the password.
func (s *Service) CreateWorkspace(identifier, password string) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 1. Identification (LTHN hash proxy)
if s.crypt == nil {
return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil)
}
hash := sha256.Sum256([]byte(identifier))
wsID := hex.EncodeToString(hash[:])
wsPath := filepath.Join(s.rootPath, wsID)
@ -58,26 +81,18 @@ func (s *Service) CreateWorkspace(identifier, password string) (string, error) {
return "", coreerr.E("workspace.CreateWorkspace", "workspace already exists", nil)
}
// 2. Directory structure
dirs := []string{"config", "log", "data", "files", "keys"}
for _, d := range dirs {
for _, d := range []string{"config", "log", "data", "files", "keys"} {
if err := s.medium.EnsureDir(filepath.Join(wsPath, d)); err != nil {
return "", coreerr.E("workspace.CreateWorkspace", "failed to create directory: "+d, err)
}
}
// 3. PGP Keypair generation
crypt := s.core.Crypt()
if crypt == nil {
return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil)
}
privKey, err := crypt.CreateKeyPair(identifier, password)
privKey, err := s.crypt.CreateKeyPair(identifier, password)
if err != nil {
return "", coreerr.E("workspace.CreateWorkspace", "failed to generate keys", err)
}
// Save private key
if err := s.medium.Write(filepath.Join(wsPath, "keys", "private.key"), privKey); err != nil {
if err := s.medium.WriteMode(filepath.Join(wsPath, "keys", "private.key"), privKey, 0600); err != nil {
return "", coreerr.E("workspace.CreateWorkspace", "failed to save private key", err)
}
@ -98,36 +113,41 @@ func (s *Service) SwitchWorkspace(name string) error {
return nil
}
// activeFilePath returns the full path to a file in the active workspace,
// or an error if no workspace is active.
func (s *Service) activeFilePath(op, filename string) (string, error) {
if s.activeWorkspace == "" {
return "", coreerr.E(op, "no active workspace", nil)
}
return filepath.Join(s.rootPath, s.activeWorkspace, "files", filename), nil
}
// WorkspaceFileGet retrieves the content of a file from the active workspace.
// In a full implementation, this would involve decryption using the workspace key.
func (s *Service) WorkspaceFileGet(filename string) (string, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if s.activeWorkspace == "" {
return "", coreerr.E("workspace.WorkspaceFileGet", "no active workspace", nil)
path, err := s.activeFilePath("workspace.WorkspaceFileGet", filename)
if err != nil {
return "", err
}
path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename)
return s.medium.Read(path)
}
// WorkspaceFileSet saves content to a file in the active workspace.
// In a full implementation, this would involve encryption using the workspace key.
func (s *Service) WorkspaceFileSet(filename, content string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.activeWorkspace == "" {
return coreerr.E("workspace.WorkspaceFileSet", "no active workspace", nil)
path, err := s.activeFilePath("workspace.WorkspaceFileSet", filename)
if err != nil {
return err
}
path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename)
return s.medium.Write(path, content)
}
// HandleIPCEvents handles workspace-related IPC messages.
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
switch m := msg.(type) {
case map[string]any:
action, _ := m["action"].(string)
@ -135,15 +155,21 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
case "workspace.create":
id, _ := m["identifier"].(string)
pass, _ := m["password"].(string)
_, err := s.CreateWorkspace(id, pass)
return err
wsID, err := s.CreateWorkspace(id, pass)
if err != nil {
return core.Result{}
}
return core.Result{Value: wsID, OK: true}
case "workspace.switch":
name, _ := m["name"].(string)
return s.SwitchWorkspace(name)
if err := s.SwitchWorkspace(name); err != nil {
return core.Result{}
}
return core.Result{OK: true}
}
}
return nil
return core.Result{OK: true}
}
// Ensure Service implements core.Workspace.
var _ core.Workspace = (*Service)(nil)
// Ensure Service implements Workspace.
var _ Workspace = (*Service)(nil)

View file

@ -1,32 +1,25 @@
package workspace
import (
"os"
"path/filepath"
"testing"
core "dappco.re/go/core"
"forge.lthn.ai/core/go-crypt/crypt/openpgp"
core "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
func TestWorkspace(t *testing.T) {
// Setup core with crypt service
c, _ := core.New(
core.WithName("crypt", openpgp.New),
)
tempHome, _ := os.MkdirTemp("", "core-test-home")
defer os.RemoveAll(tempHome)
// Mock os.UserHomeDir by setting HOME env
oldHome := os.Getenv("HOME")
os.Setenv("HOME", tempHome)
defer os.Setenv("HOME", oldHome)
s_any, err := New(c)
c := core.New()
pgpSvc, err := openpgp.New(nil)
assert.NoError(t, err)
s := s_any.(*Service)
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
svc, err := New(c, pgpSvc.(cryptProvider))
assert.NoError(t, err)
s := svc.(*Service)
// Test CreateWorkspace
id, err := s.CreateWorkspace("test-user", "pass123")