[agent/claude] Update all Go files that import forge.lthn.ai/core/go or for... #2

Merged
Virgil merged 1 commit from agent/update-all-go-files-that-import-forge-lt into main 2026-03-21 12:08:30 +00:00
4 changed files with 75 additions and 53 deletions

3
go.mod
View file

@ -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

6
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.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=

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"
)
// 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")