From dbe5086a0aee87741f2f58c82269fa3650edb247 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 07:19:20 +0000 Subject: [PATCH 1/7] fix(dx): audit errors, update CLAUDE.md, clean up node import guard - Replace errors.New() with coreerr.E() in sigil/sigils.go (HashSigil.In, NewSigil) - Update CLAUDE.md: add missing deps (go-crypt, x/crypto, testify), fix go/pkg/core path, add GOWORK=off note, document sentinel error convention, add qa command - Remove redundant unused import guard in node/node.go Co-Authored-By: Virgil --- CLAUDE.md | 17 +++++++++++++++-- node/node.go | 3 --- sigil/sigils.go | 6 +++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 607841e..9a27f7a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`). diff --git a/node/node.go b/node/node.go index 45d29c1..2c93c45 100644 --- a/node/node.go +++ b/node/node.go @@ -607,6 +607,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 diff --git a/sigil/sigils.go b/sigil/sigils.go index 4ef0762..2baffff 100644 --- a/sigil/sigils.go +++ b/sigil/sigils.go @@ -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) } } -- 2.45.3 From f3f741c0a7b998dcebd1333b1a2ed8c21a316345 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 17:23:23 +0000 Subject: [PATCH 2/7] feat(security): add WriteMode to Medium interface for file permissions Codex security review found that migrating os.WriteFile(path, data, 0600) to coreio.Local.Write() changed permissions from owner-only to world-readable (0644). This is a security regression for encryption output, private keys, and auth hashes. WriteMode(path, content, mode) allows callers to specify permissions. Write() remains the default (0644) for non-sensitive files. Affected implementors updated: local.Medium, MockMedium, Node, datanode.Medium. Co-Authored-By: Virgil --- datanode/client.go | 4 ++++ io.go | 9 +++++++++ local/client.go | 10 +++++++++- node/node.go | 5 +++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/datanode/client.go b/datanode/client.go index 418d8b4..fcfe524 100644 --- a/datanode/client.go +++ b/datanode/client.go @@ -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() diff --git a/io.go b/io.go index 6b9f885..8161fdf 100644 --- a/io.go +++ b/io.go @@ -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 diff --git a/local/client.go b/local/client.go index 2768510..22fd769 100644 --- a/local/client.go +++ b/local/client.go @@ -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. diff --git a/node/node.go b/node/node.go index 2c93c45..6a6b79d 100644 --- a/node/node.go +++ b/node/node.go @@ -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) -- 2.45.3 From 949b0098dc98353bce85343472ce00406022d4c8 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 17:47:54 +0000 Subject: [PATCH 3/7] chore: sync dependencies for v0.1.6 Co-Authored-By: Virgil --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7f85b4d..8ac01cd 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.26.0 require ( forge.lthn.ai/Snider/Borg v0.3.1 - forge.lthn.ai/core/go v0.3.1 + forge.lthn.ai/core/go v0.3.2 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 ( diff --git a/go.sum b/go.sum index 7bbe168..877c714 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 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.2 h1:VB9pW6ggqBhe438cjfE2iSI5Lg+62MmRbaOFglZM+nQ= +forge.lthn.ai/core/go v0.3.2/go.mod h1:f7/zb3Labn4ARfwTq5Bi2AFHY+uxyPHozO+hLb54eFo= 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 +97,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= -- 2.45.3 From 1b147f4680c4f633a43fadf4335ad67b66a9c8e9 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 17:52:56 +0000 Subject: [PATCH 4/7] chore: sync dependencies for v0.1.7 Co-Authored-By: Virgil --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8ac01cd..dacd54d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( forge.lthn.ai/Snider/Borg v0.3.1 - forge.lthn.ai/core/go v0.3.2 + forge.lthn.ai/core/go v0.3.3 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 diff --git a/go.sum b/go.sum index 877c714..e598e6c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 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.2 h1:VB9pW6ggqBhe438cjfE2iSI5Lg+62MmRbaOFglZM+nQ= -forge.lthn.ai/core/go v0.3.2/go.mod h1:f7/zb3Labn4ARfwTq5Bi2AFHY+uxyPHozO+hLb54eFo= +forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0= +forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ= 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= -- 2.45.3 From 92e85351a68a53521171364236c377a614560071 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 21 Mar 2026 12:08:11 +0000 Subject: [PATCH 5/7] refactor: migrate core import to dappco.re/go/core Co-Authored-By: Virgil --- go.mod | 3 +- go.sum | 6 ++- workspace/service.go | 92 +++++++++++++++++++++++++-------------- workspace/service_test.go | 27 +++++------- 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index dacd54d..3f87ee0 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module forge.lthn.ai/core/go-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.3 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 @@ -15,6 +15,7 @@ require ( ) 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 diff --git a/go.sum b/go.sum index e598e6c..d25c96d 100644 --- a/go.sum +++ b/go.sum @@ -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.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0= -forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ= +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= diff --git a/workspace/service.go b/workspace/service.go index d54d1a4..5325d2b 100644 --- a/workspace/service.go +++ b/workspace/service.go @@ -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" ) -// 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) diff --git a/workspace/service_test.go b/workspace/service_test.go index 852d526..1cab667 100644 --- a/workspace/service_test.go +++ b/workspace/service_test.go @@ -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") -- 2.45.3 From 61d66f45a8e9cec8b37d34298a81c5d21e4fb8b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 23:44:10 +0000 Subject: [PATCH 6/7] chore: migrate to dappco.re vanity import path Co-Authored-By: Claude Opus 4.6 (1M context) --- datanode/client_test.go | 2 +- go.mod | 2 +- io.go | 2 +- node/node.go | 2 +- workspace/service.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/datanode/client_test.go b/datanode/client_test.go index e76e5e1..651d322 100644 --- a/datanode/client_test.go +++ b/datanode/client_test.go @@ -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" ) diff --git a/go.mod b/go.mod index 3f87ee0..6c77560 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module forge.lthn.ai/core/go-io +module dappco.re/go/core/io go 1.26.0 diff --git a/io.go b/io.go index 8161fdf..c31592f 100644 --- a/io.go +++ b/io.go @@ -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. diff --git a/node/node.go b/node/node.go index 6a6b79d..418d590 100644 --- a/node/node.go +++ b/node/node.go @@ -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 diff --git a/workspace/service.go b/workspace/service.go index 5325d2b..c1978a1 100644 --- a/workspace/service.go +++ b/workspace/service.go @@ -10,7 +10,7 @@ import ( core "dappco.re/go/core" coreerr "forge.lthn.ai/core/go-log" - "forge.lthn.ai/core/go-io" + "dappco.re/go/core/io" ) // Workspace provides management for encrypted user workspaces. -- 2.45.3 From 4b8d05559b17a910c56f43d21b69340f4f5d0412 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 14:05:15 +0000 Subject: [PATCH 7/7] ci: add Core ecosystem CI workflow with CodeRabbit auto-fix Uses dAppCore/build actions for test, auto-fix on CodeRabbit changes, and auto-merge on CodeRabbit approval. Co-Authored-By: Virgil --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4963a87 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main] + pull_request_review: + types: [submitted] + +jobs: + test: + if: github.event_name != 'pull_request_review' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dAppCore/build/actions/build/core@dev + with: + go-version: "1.26" + run-vet: "true" + + auto-fix: + if: > + github.event_name == 'pull_request_review' && + github.event.review.user.login == 'coderabbitai' && + github.event.review.state == 'changes_requested' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + - uses: dAppCore/build/actions/fix@dev + with: + go-version: "1.26" + + auto-merge: + if: > + github.event_name == 'pull_request_review' && + github.event.review.user.login == 'coderabbitai' && + github.event.review.state == 'approved' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Merge PR + run: gh pr merge ${{ github.event.pull_request.number }} --merge --delete-branch + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} -- 2.45.3