fix(go): mirror transactional Auggie hooks

This commit is contained in:
ZacharyZhang-NY committed 2026-07-20 10:23:35 -04:00
1 parent dccec10fea
commit 3ca5d701de
13 files changed
+1795 -510

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
package main
import (
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestAugmentCLIPreflightPreservesMalformedSettings(t *testing.T) {
homeDir := filepath.Join(t.TempDir(), "home")
settingsPath := filepath.Join(homeDir, ".augment", "settings.json")
keyPath := filepath.Join(t.TempDir(), "private.key")
if err := os.MkdirAll(filepath.Dir(settingsPath), 0700); err != nil {
t.Fatalf("create Auggie settings directory: %v", err)
}
if err := os.WriteFile(settingsPath, []byte("{ malformed"), 0600); err != nil {
t.Fatalf("write malformed Auggie settings: %v", err)
}
privateKey := "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc"
if err := os.WriteFile(keyPath, []byte(privateKey+"\n"), 0600); err != nil {
t.Fatalf("write private key: %v", err)
}
command := exec.Command(
"go",
"run",
".",
"install",
"--agent",
"augment",
"--org-id",
"org-1",
"--agent-id",
"agent-1",
"--private-key-file",
keyPath,
"--kid",
"kid-1",
"--base-url",
"https://api.elydora.com",
)
command.Env = append(
os.Environ(),
"HOME="+homeDir,
"USERPROFILE="+homeDir,
)
output, err := command.CombinedOutput()
if err == nil || !strings.Contains(
string(output),
"parse Auggie user settings",
) {
t.Fatalf("CLI preflight result = %v\n%s", err, output)
}
raw, readErr := os.ReadFile(settingsPath)
if readErr != nil || string(raw) != "{ malformed" {
t.Fatalf("malformed settings changed: %q, %v", raw, readErr)
}
if _, statErr := os.Lstat(filepath.Join(homeDir, ".elydora")); !errors.Is(
statErr,
os.ErrNotExist,
) {
t.Fatalf("runtime directory created during failed preflight: %v", statErr)
}
}
+83 -81
View File
@@ -1,137 +1,139 @@
package plugins
import "fmt"
import (
"fmt"
"path/filepath"
)
// AugmentPlugin manages Auggie native global user hooks.
type AugmentPlugin struct{}
type AugmentPlugin struct {
rename renameFunc
}
// ManagesGuardRuntime reports that Auggie commits its provider guard together
// with both wrappers, the audit runtime, and user settings.
func (p *AugmentPlugin) ManagesGuardRuntime() bool {
return true
}
// PreflightInstall validates settings and runtime identity before any write.
func (p *AugmentPlugin) PreflightInstall(config InstallConfig) error {
document, err := readAugmentDocument()
if err != nil {
return err
}
_, _, err = preflightAugmentInstallation(config, document)
return err
}
func (p *AugmentPlugin) Install(config InstallConfig) error {
if config.AgentID == "" {
return fmt.Errorf("agent ID is required")
}
document, err := readAugmentConfig()
document, err := readAugmentDocument()
if err != nil {
return err
}
if config.GuardScriptPath == "" {
return fmt.Errorf("guard script path is required")
}
guardExists, err := regularFileExists(
config.GuardScriptPath, "Elydora guard runtime",
)
paths, nodePath, err := preflightAugmentInstallation(config, document)
if err != nil {
return err
}
if !guardExists {
return fmt.Errorf(
"Elydora guard runtime is missing: %s", config.GuardScriptPath,
)
}
hookPath, err := hookScriptPath(config.AgentID)
if err != nil {
return err
}
if config.HookScript != "" {
hookPath = config.HookScript
}
nodePath, err := resolveNodeRuntime()
if err != nil {
return err
}
if err := validateAugmentMatchers(document.hooks, nodePath); err != nil {
return err
}
runtimeRoot, err := augmentRuntimeRoot()
if err != nil {
return err
}
wrappers := resolveAugmentWrapperPaths(runtimeRoot, config.AgentID)
hooks, _ := removeManagedAugmentHooks(document.hooks, "", runtimeRoot)
hooks, _ := removeManagedAugmentHooks(document.hooks, "", paths.runtimeRoot)
hooks["PreToolUse"] = append(
hooks["PreToolUse"],
buildAugmentGroup(buildAugmentHandler(wrappers.guard)),
buildAugmentGroup(buildAugmentHandler(paths.guardWrapperPath)),
)
hooks["PostToolUse"] = append(
hooks["PostToolUse"],
buildAugmentGroup(buildAugmentHandler(wrappers.audit)),
buildAugmentGroup(buildAugmentHandler(paths.auditWrapperPath)),
)
next := cloneAugmentObject(document.root)
next["hooks"] = renderAugmentHooks(hooks)
runtimeConfig := config
runtimeConfig.AgentName = augmentAgentKey
if err := GenerateHookScript(hookPath, runtimeConfig); err != nil {
return fmt.Errorf("generate hook script: %w", err)
rendered, err := renderAugmentDocument(document, hooks)
if err != nil {
return fmt.Errorf("render Auggie user settings: %w", err)
}
if err := writeHookFileAtomic(
wrappers.guard,
buildAugmentWrapper(nodePath, config.GuardScriptPath),
0700,
changes, err := prepareAugmentInstallationChanges(
config,
paths,
nodePath,
rendered,
)
if err != nil {
return err
}
if err := writeAugmentChanges(
changes,
"Install Augment Code CLI hooks",
p.rename,
paths.runtimeRoot,
paths.agentDirectory,
filepath.Dir(document.configPath),
); err != nil {
return fmt.Errorf("write Auggie guard wrapper: %w", err)
}
if err := writeHookFileAtomic(
wrappers.audit,
buildAugmentWrapper(nodePath, hookPath),
0700,
); err != nil {
return fmt.Errorf("write Auggie audit wrapper: %w", err)
}
if err := writeHookJSONObjectAtomic(document.configPath, next); err != nil {
return fmt.Errorf("write Auggie settings: %w", err)
return err
}
fmt.Println("Auggie: user-level PreToolUse and PostToolUse hooks installed.")
return nil
}
func (p *AugmentPlugin) Uninstall(agentID string) error {
document, err := readAugmentConfig()
if err != nil || !document.exists {
document, err := readAugmentDocument()
if err != nil {
return err
}
runtimeRoot, err := augmentRuntimeRoot()
if !document.exists {
return nil
}
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return err
}
hooks, changed := removeManagedAugmentHooks(
document.hooks, agentID, runtimeRoot,
document.hooks,
agentID,
runtimeRoot,
)
if !changed {
return nil
}
next := cloneAugmentObject(document.root)
if len(hooks) == 0 {
delete(next, "hooks")
} else {
next["hooks"] = renderAugmentHooks(hooks)
rendered, err := renderAugmentDocument(document, hooks)
if err != nil {
return fmt.Errorf("render Auggie user settings: %w", err)
}
if len(next) == 0 {
return removeHookFile(document.configPath, "Auggie settings")
change, err := prepareRenderedAugmentChange(rendered)
if err != nil {
return err
}
return writeHookJSONObjectAtomic(document.configPath, next)
return writeAugmentChanges(
[]*fileChange{change},
"Uninstall Augment Code CLI hooks",
p.rename,
"",
"",
filepath.Dir(document.configPath),
)
}
func (p *AugmentPlugin) Status() (PluginStatus, error) {
document, err := readAugmentConfig()
configPath, pathErr := augmentConfigPath()
entry := SupportedAgents[augmentAgentKey]
status := PluginStatus{
AgentName: augmentAgentKey,
DisplayName: "Augment Code CLI",
ConfigPath: document.configPath,
AgentName: augmentAgentKey, DisplayName: entry.Name, ConfigPath: configPath,
}
if pathErr != nil {
return status, pathErr
}
document, err := readAugmentDocument()
if err != nil {
return status, err
}
runtimeRoot, err := augmentRuntimeRoot()
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return status, err
}
contracts := augmentRuntimeContracts(document.hooks, runtimeRoot)
if len(contracts) == 0 {
status.HookConfigured = len(contracts) > 0
if !status.HookConfigured {
return status, nil
}
status.HookConfigured = true
status.HookScriptExists, err = augmentRuntimeFilesExist(
contracts, runtimeRoot,
contracts,
runtimeRoot,
)
if err != nil {
return status, err
@@ -1,9 +1,12 @@
package plugins
import (
"bytes"
"encoding/json"
"fmt"
"math"
"path/filepath"
"reflect"
"runtime"
"strings"
)
@@ -42,6 +45,14 @@ type augmentDocument struct {
configPath string
root map[string]any
hooks augmentHooks
raw []byte
}
type augmentRenderedDocument struct {
document *augmentDocument
changed bool
next []byte
remove bool
}
type augmentWrapperPaths struct {
@@ -200,6 +211,71 @@ func readAugmentHooks(root map[string]any) (augmentHooks, error) {
return hooks, nil
}
func parseAugmentDocument(configPath string, raw []byte) (*augmentDocument, error) {
label := fmt.Sprintf("Auggie user settings at %s", configPath)
root, err := decodeStrictJSONObject(raw, label)
if err != nil {
return nil, err
}
hooks, err := readAugmentHooks(root)
if err != nil {
return nil, err
}
return &augmentDocument{
exists: true, configPath: configPath, root: root, hooks: hooks,
raw: append([]byte(nil), raw...),
}, nil
}
func createAugmentDocument(configPath string) *augmentDocument {
return &augmentDocument{
configPath: configPath,
root: map[string]any{},
hooks: augmentHooks{},
}
}
func renderAugmentDocument(
document *augmentDocument,
hooks augmentHooks,
) (*augmentRenderedDocument, error) {
if document == nil {
return nil, fmt.Errorf("Auggie settings document is required")
}
if reflect.DeepEqual(hooks, document.hooks) {
return &augmentRenderedDocument{document: document}, nil
}
if !document.exists && len(hooks) == 0 {
return &augmentRenderedDocument{document: document}, nil
}
root := cloneAugmentObject(document.root)
if len(hooks) == 0 {
delete(root, "hooks")
} else {
root["hooks"] = renderAugmentHooks(hooks)
}
if len(root) == 0 {
return &augmentRenderedDocument{
document: document,
changed: true,
remove: true,
}, nil
}
next, err := json.MarshalIndent(root, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Auggie user settings: %w", err)
}
next = append(next, '\n')
if _, err := parseAugmentDocument(document.configPath, next); err != nil {
return nil, fmt.Errorf("validate rendered Auggie user settings: %w", err)
}
return &augmentRenderedDocument{
document: document,
changed: !document.exists || !bytes.Equal(next, document.raw),
next: next,
}, nil
}
func renderAugmentHooks(hooks augmentHooks) map[string]any {
result := make(map[string]any, len(hooks))
for event, groups := range hooks {
@@ -0,0 +1,226 @@
package plugins
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type augmentRuntimePaths struct {
runtimeRoot string
agentDirectory string
configPath string
keyPath string
guardPath string
auditPath string
guardWrapperPath string
auditWrapperPath string
}
func validateAugmentInstallConfig(config InstallConfig) error {
for _, field := range []struct{ name, value string }{
{"agent name", config.AgentName},
{"organization ID", config.OrgID},
{"agent ID", config.AgentID},
{"key ID", config.KID},
{"private key", config.PrivateKey},
{"base URL", config.BaseURL},
{"guard script path", config.GuardScriptPath},
} {
if field.value == "" {
return fmt.Errorf("%s is required", field.name)
}
}
if config.AgentName != augmentAgentKey {
return fmt.Errorf(
"Augment Code CLI installation requires agent name %s",
augmentAgentKey,
)
}
if strings.TrimSpace(config.OrgID) == "" {
return fmt.Errorf("organization ID is required")
}
if strings.TrimSpace(config.KID) == "" {
return fmt.Errorf("key ID is required")
}
if config.Token != "" && strings.TrimSpace(config.Token) == "" {
return fmt.Errorf("token must contain a non-whitespace value when provided")
}
if err := validateManagedPrivateKey(config.PrivateKey); err != nil {
return err
}
return validateManagedBaseURL(config.BaseURL)
}
func augmentAgentPaths(config InstallConfig) (*augmentRuntimePaths, error) {
if err := validateAugmentInstallConfig(config); err != nil {
return nil, err
}
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return nil, err
}
wrappers := resolveAugmentWrapperPaths(agentDirectory)
paths := &augmentRuntimePaths{
runtimeRoot: runtimeRoot, agentDirectory: agentDirectory,
configPath: filepath.Join(agentDirectory, "config.json"),
keyPath: filepath.Join(agentDirectory, "private.key"),
guardPath: filepath.Join(agentDirectory, augmentGuardScript),
auditPath: filepath.Join(agentDirectory, augmentAuditScript),
guardWrapperPath: wrappers.guard,
auditWrapperPath: wrappers.audit,
}
if !filepath.IsAbs(config.GuardScriptPath) ||
!sameAugmentPath(config.GuardScriptPath, paths.guardPath) {
return nil, fmt.Errorf(
"Elydora guard runtime must use the managed agent directory: %s",
paths.guardPath,
)
}
if config.HookScript != "" && (!filepath.IsAbs(config.HookScript) ||
!sameAugmentPath(config.HookScript, paths.auditPath)) {
return nil, fmt.Errorf(
"Elydora audit runtime must use the managed agent directory: %s",
paths.auditPath,
)
}
return paths, nil
}
func preflightAugmentInstallation(
config InstallConfig,
document *augmentDocument,
) (*augmentRuntimePaths, string, error) {
if document == nil || document.configPath == "" {
return nil, "", fmt.Errorf(
"Augment Code CLI installation requires a user settings document",
)
}
paths, err := augmentAgentPaths(config)
if err != nil {
return nil, "", err
}
if err := validateAugmentRuntimeIdentity(
paths.agentDirectory,
config.AgentID,
); err != nil {
return nil, "", err
}
nodePath, err := resolveNodeRuntime()
if err != nil {
return nil, "", err
}
if err := requireAugmentAbsoluteNode(nodePath); err != nil {
return nil, "", err
}
if err := validateAugmentMatchers(document.hooks, nodePath); err != nil {
return nil, "", err
}
return paths, nodePath, nil
}
func buildAugmentRuntimeConfig(config InstallConfig) ([]byte, error) {
value := map[string]any{
"org_id": config.OrgID, "agent_id": config.AgentID, "kid": config.KID,
"base_url": config.BaseURL, "agent_name": augmentAgentKey,
}
if config.Token != "" {
value["token"] = config.Token
}
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
}
encoded = append(encoded, '\n')
if len(encoded) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"Elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
return encoded, nil
}
func prepareAugmentInstallationChanges(
config InstallConfig,
paths *augmentRuntimePaths,
nodePath string,
rendered *augmentRenderedDocument,
) ([]*fileChange, error) {
if paths == nil || rendered == nil {
return nil, fmt.Errorf("prepared Auggie installation is required")
}
if err := validateAugmentRuntimeIdentity(
paths.agentDirectory,
config.AgentID,
); err != nil {
return nil, err
}
runtimeConfig, err := buildAugmentRuntimeConfig(config)
if err != nil {
return nil, err
}
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{
paths.guardPath,
"Elydora guard runtime",
[]byte(generateGuardScript(
augmentAgentKey,
config.AgentID,
"",
false,
"",
)),
0700,
},
{paths.configPath, "Elydora runtime config", runtimeConfig, 0600},
{paths.keyPath, "Elydora private key", []byte(config.PrivateKey), 0600},
{
paths.auditPath,
"Elydora audit runtime",
[]byte(buildHookScriptWithOutput(
augmentAgentKey,
config.AgentID,
"",
false,
true,
)),
0700,
},
{
paths.guardWrapperPath,
"Auggie guard wrapper",
buildAugmentWrapper(nodePath, paths.guardPath),
0700,
},
{
paths.auditWrapperPath,
"Auggie audit wrapper",
buildAugmentWrapper(nodePath, paths.auditPath),
0700,
},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
change, err := prepareFileChange(item.path, item.label, item.content, item.mode)
if err != nil {
return nil, err
}
changes = append(changes, change)
}
documentChange, err := prepareRenderedAugmentChange(rendered)
if err != nil {
return nil, err
}
return append(changes, documentChange), nil
}
+80 -154
View File
@@ -33,49 +33,91 @@ func augmentAuditWrapperName() string {
return "augment-hook.sh"
}
func augmentHomeDirectory() (string, error) {
func augmentConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err)
}
return home, nil
}
func augmentRuntimeRoot() (string, error) {
home, err := augmentHomeDirectory()
if err != nil {
return "", err
}
return filepath.Join(home, ".elydora"), nil
}
func augmentConfigPath() (string, error) {
home, err := augmentHomeDirectory()
if err != nil {
return "", err
}
return filepath.Join(home, ".augment", "settings.json"), nil
}
func readAugmentConfig() (augmentDocument, error) {
func readAugmentDocument() (*augmentDocument, error) {
configPath, err := augmentConfigPath()
if err != nil {
return augmentDocument{}, err
return nil, err
}
root, exists, err := readHookJSONObject(configPath, "Auggie settings")
if _, err := managedPhysicalDirectoryExists(
filepath.Dir(configPath),
"Auggie configuration directory",
); err != nil {
return nil, err
}
snapshot, err := readManagedFile(
configPath,
"Auggie user settings",
maxManagedSourceBytes,
)
if err != nil {
return augmentDocument{}, err
return nil, err
}
hooks, err := readAugmentHooks(root)
if err != nil {
return augmentDocument{}, err
if snapshot == nil {
return createAugmentDocument(configPath), nil
}
return augmentDocument{
exists: exists,
configPath: configPath,
root: root,
hooks: hooks,
}, nil
return parseAugmentDocument(configPath, snapshot.contents)
}
func prepareRenderedAugmentChange(
rendered *augmentRenderedDocument,
) (*fileChange, error) {
if rendered == nil || !rendered.changed {
return nil, nil
}
return prepareSourceChange(
rendered.document.configPath,
"Auggie user settings",
rendered.document.raw,
rendered.document.exists,
rendered.next,
0600,
rendered.remove,
)
}
func writeAugmentChanges(
changes []*fileChange,
label string,
rename renameFunc,
runtimeRoot string,
agentDirectory string,
settingsDirectory string,
) error {
hasChanges := false
for _, change := range changes {
if change != nil {
hasChanges = true
break
}
}
if !hasChanges {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
if settingsDirectory != "" {
if err := ensureManagedDirectory(
settingsDirectory,
"Auggie configuration directory",
); err != nil {
return err
}
}
return writeChanges(changes, label, rename)
}
func validateAugmentMatchers(hooks augmentHooks, nodePath string) error {
@@ -106,7 +148,9 @@ func validateAugmentMatchers(hooks augmentHooks, nodePath string) error {
return fmt.Errorf(
"Auggie settings group hooks.%s[%d] matcher "+
"must be a valid JavaScript regular expression: %s",
event, groupIndex, message,
event,
groupIndex,
message,
)
}
}
@@ -159,134 +203,16 @@ func buildAugmentGroup(handler map[string]any) augmentGroup {
}
}
func resolveAugmentWrapperPaths(runtimeRoot, agentID string) augmentWrapperPaths {
agentDirectory := filepath.Join(runtimeRoot, agentID)
func resolveAugmentWrapperPaths(agentDirectory string) augmentWrapperPaths {
return augmentWrapperPaths{
guard: filepath.Join(agentDirectory, augmentGuardWrapperName()),
audit: filepath.Join(agentDirectory, augmentAuditWrapperName()),
}
}
func managedAugmentIDs(
groups []augmentGroup,
wrapperName, runtimeRoot string,
) map[string]string {
result := map[string]string{}
for _, group := range groups {
for _, handler := range group.handlers {
agentID, managed := managedAugmentAgentID(handler, wrapperName, runtimeRoot)
if managed {
key := agentID
if runtime.GOOS == "windows" {
key = strings.ToLower(key)
}
result[key] = agentID
}
}
func requireAugmentAbsoluteNode(nodePath string) error {
if !filepath.IsAbs(nodePath) || !isClaudeNodeExecutable(nodePath) {
return fmt.Errorf("Auggie hooks require an absolute Node.js executable path")
}
return result
}
func augmentRuntimeContracts(
hooks augmentHooks,
runtimeRoot string,
) []augmentRuntimeContract {
guards := managedAugmentIDs(
hooks["PreToolUse"], augmentGuardWrapperName(), runtimeRoot,
)
audits := managedAugmentIDs(
hooks["PostToolUse"], augmentAuditWrapperName(), runtimeRoot,
)
keys := make([]string, 0, len(guards))
for key := range guards {
if _, exists := audits[key]; exists {
keys = append(keys, key)
}
}
sort.Strings(keys)
contracts := make([]augmentRuntimeContract, 0, len(keys))
for _, key := range keys {
agentID := guards[key]
agentDirectory := filepath.Join(runtimeRoot, agentID)
contracts = append(contracts, augmentRuntimeContract{
agentID: agentID,
guardPath: filepath.Join(agentDirectory, augmentGuardScript),
auditPath: filepath.Join(agentDirectory, augmentAuditScript),
guardWrapper: filepath.Join(agentDirectory, augmentGuardWrapperName()),
auditWrapper: filepath.Join(agentDirectory, augmentAuditWrapperName()),
})
}
return contracts
}
func augmentRuntimeFilesExist(
contracts []augmentRuntimeContract,
runtimeRoot string,
) (bool, error) {
entries, err := os.ReadDir(runtimeRoot)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf(
"read Elydora runtime directory at %s: %w", runtimeRoot, err,
)
}
for _, contract := range contracts {
var entryName string
for _, entry := range entries {
if entry.IsDir() && sameAugmentAgentID(entry.Name(), contract.agentID) {
entryName = entry.Name()
break
}
}
if entryName == "" {
continue
}
agentDirectory := filepath.Join(runtimeRoot, entryName)
configPath := filepath.Join(agentDirectory, "config.json")
config, exists, err := readHookJSONObject(
configPath, "Elydora runtime config",
)
if err != nil {
return false, err
}
if !exists {
continue
}
agentName, ok := config["agent_name"].(string)
if !ok {
return false, fmt.Errorf(
`Elydora runtime config at %s field "agent_name" must be a string`,
configPath,
)
}
if agentName != augmentAgentKey {
continue
}
files := []struct {
path string
label string
}{
{contract.guardPath, "Elydora guard runtime"},
{contract.auditPath, "Elydora audit runtime"},
{contract.guardWrapper, "Auggie guard wrapper"},
{contract.auditWrapper, "Auggie audit wrapper"},
}
complete := true
for _, file := range files {
exists, err := regularFileExists(file.path, file.label)
if err != nil {
return false, err
}
if !exists {
complete = false
break
}
}
if complete {
return true, nil
}
}
return false, nil
return nil
}
@@ -0,0 +1,53 @@
package plugins
import (
"os"
"os/exec"
"strings"
"testing"
)
func TestAugmentOfficialBinaryAcceptsInstalledUserHooks(t *testing.T) {
entry := os.Getenv("ELYDORA_AUGGIE_ENTRY")
if entry == "" {
t.Skip("ELYDORA_AUGGIE_ENTRY is unset")
}
nodePath, err := resolveNodeRuntime()
if err != nil {
t.Fatalf("resolve Node.js runtime: %v", err)
}
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
environment := append(
os.Environ(),
"HOME="+fixture.homeDir,
"USERPROFILE="+fixture.homeDir,
)
version := exec.Command(nodePath, entry, "--version")
version.Dir = fixture.projectDir
version.Env = environment
versionOutput, err := version.CombinedOutput()
if err != nil || !strings.Contains(string(versionOutput), "0.33.0") {
t.Fatalf("official Auggie version: %v\n%s", err, versionOutput)
}
load := exec.Command(nodePath, entry, "tools", "list")
load.Dir = fixture.projectDir
load.Env = environment
loadOutput, err := load.CombinedOutput()
if err != nil {
t.Fatalf("official Auggie settings load: %v\n%s", err, loadOutput)
}
output := strings.ToLower(string(loadOutput))
for _, problem := range []string{
"invalid settings",
"settings validation",
"failed to parse",
"hook configuration error",
} {
if strings.Contains(output, problem) {
t.Fatalf("official Auggie loader reported %q:\n%s", problem, loadOutput)
}
}
}
@@ -0,0 +1,290 @@
package plugins
import (
"encoding/json"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
func installAugmentRuntimeFixture(
t *testing.T,
status string,
) (*augmentFixture, *codexTestAPI, *httptest.Server) {
t.Helper()
api := &codexTestAPI{status: status}
server := httptest.NewServer(api)
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
fixture.config.BaseURL = server.URL
if err := fixture.plugin.Install(fixture.config); err != nil {
server.Close()
t.Fatalf("install Auggie hooks: %v", err)
}
return fixture, api, server
}
func augmentOfficialPayload(event string) map[string]any {
value := map[string]any{
"hook_event_name": event,
"conversation_id": "conversation-1",
"workspace_roots": []any{"C:/workspace"},
"tool_name": "launch-process",
"tool_input": map[string]any{
"command": "go test ./...",
"nested": map[string]any{"preserve": true},
},
"is_mcp_tool": false,
"conversation_data": []any{
map[string]any{"role": "user", "content": "preserve this"},
},
"mcp_metadata": map[string]any{
"server": "local", "transport": "stdio",
},
"user_context": map[string]any{"account": "user-1"},
"future_field": map[string]any{"survives": []any{"exactly", float64(2)}},
}
if event == "PostToolUse" {
value["tool_output"] = map[string]any{
"stdout": "passed", "stderr": "", "exit_code": float64(0),
}
}
return value
}
func marshalAugmentPayload(t *testing.T, payload map[string]any) []byte {
t.Helper()
encoded, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal Auggie payload: %v", err)
}
return encoded
}
func TestAugmentGuardPropagatesOfficialBlockingExitCode(t *testing.T) {
for _, status := range []string{"frozen", "revoked"} {
t.Run(status, func(t *testing.T) {
fixture, _, server := installAugmentRuntimeFixture(t, status)
defer server.Close()
handler := augmentTestManagedHandler(
t,
readAugmentTestObject(t, fixture.configPath),
"PreToolUse",
fixture.guardWrapper,
)
exit, stdout, stderr := runAugmentCommand(
t,
handler["command"].(string),
fixture,
marshalAugmentPayload(t, augmentOfficialPayload("PreToolUse")),
)
if exit != 2 || stdout != "" ||
!strings.Contains(strings.ToLower(stderr), status) ||
!strings.Contains(stderr, "Tool execution blocked") {
t.Fatalf("guard result = %d, %q, %q", exit, stdout, stderr)
}
})
}
}
func TestAugmentAuditPreservesCompleteNativePayload(t *testing.T) {
fixture, api, server := installAugmentRuntimeFixture(t, "active")
defer server.Close()
payload := augmentOfficialPayload("PostToolUse")
handler := augmentTestManagedHandler(
t,
readAugmentTestObject(t, fixture.configPath),
"PostToolUse",
fixture.auditWrapper,
)
exit, stdout, stderr := runAugmentCommand(
t,
handler["command"].(string),
fixture,
marshalAugmentPayload(t, payload),
)
if exit != 0 || stdout != "" || stderr != "" {
t.Fatalf("audit result = %d, %q, %q", exit, stdout, stderr)
}
_, postAuth, operations := api.snapshot()
if postAuth != "Bearer token-1" ||
len(operations) != 1 ||
!reflect.DeepEqual(operations[0]["payload"], payload) {
t.Fatalf("Auggie native operation = auth %q, %#v", postAuth, operations)
}
if operations[0]["prev_chain_hash"] !=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" {
t.Fatalf("Auggie operation chain = %#v", operations[0])
}
}
func TestAugmentRuntimeFailuresRemainObservableAndFailOpen(t *testing.T) {
fixture, _, server := installAugmentRuntimeFixture(t, "active")
settings := readAugmentTestObject(t, fixture.configPath)
guard := augmentTestManagedHandler(
t,
settings,
"PreToolUse",
fixture.guardWrapper,
)
audit := augmentTestManagedHandler(
t,
settings,
"PostToolUse",
fixture.auditWrapper,
)
server.Close()
guardExit, _, guardError := runAugmentCommand(
t,
guard["command"].(string),
fixture,
marshalAugmentPayload(t, augmentOfficialPayload("PreToolUse")),
)
auditExit, _, _ := runAugmentCommand(
t,
audit["command"].(string),
fixture,
marshalAugmentPayload(t, augmentOfficialPayload("PostToolUse")),
)
malformedExit, _, _ := runAugmentCommand(
t,
audit["command"].(string),
fixture,
[]byte("{ malformed"),
)
log, err := os.ReadFile(filepath.Join(fixture.agentDir, "error.log"))
if guardExit != 0 ||
!strings.Contains(guardError, "Failed to resolve agent status") ||
auditExit != 0 ||
malformedExit != 0 ||
err != nil ||
!strings.Contains(string(log), "invalid JSON") ||
!strings.Contains(strings.ToLower(string(log)), "fetch failed") {
t.Fatalf(
"fail-open results = (%d, %q), %d, %d, log=%q, err=%v",
guardExit,
guardError,
auditExit,
malformedExit,
log,
err,
)
}
}
func TestAugmentStatusRequiresExactRuntimeFiles(t *testing.T) {
for _, name := range []string{
"config.json",
"private.key",
augmentGuardScript,
augmentAuditScript,
augmentGuardWrapperName(),
augmentAuditWrapperName(),
} {
t.Run("missing "+name, func(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
status, err := fixture.plugin.Status()
if err != nil || !status.Installed || !status.HookConfigured ||
!status.HookScriptExists {
t.Fatalf("installed status = %#v, %v", status, err)
}
path := filepath.Join(fixture.agentDir, name)
if err := os.Remove(path); err != nil {
t.Fatalf("remove %s: %v", path, err)
}
status, err = fixture.plugin.Status()
if err != nil || status.Installed || !status.HookConfigured ||
status.HookScriptExists {
t.Fatalf("missing %s status = %#v, %v", path, status, err)
}
})
}
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks for wrapper check: %v", err)
}
writeAugmentTestFile(
t,
fixture.auditWrapper,
[]byte("tampered wrapper\n"),
0700,
)
status, err := fixture.plugin.Status()
if err != nil || status.Installed || status.HookScriptExists {
t.Fatalf("tampered-wrapper status = %#v, %v", status, err)
}
fixture = prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks for key check: %v", err)
}
writeAugmentTestFile(t, fixture.privateKey, []byte("invalid"), 0600)
if _, err := fixture.plugin.Status(); err == nil ||
!strings.Contains(err.Error(), "canonical 32-byte") {
t.Fatalf("invalid-key status error = %v", err)
}
}
func TestAugmentStatusSurfacesInvalidRuntimeMetadata(t *testing.T) {
for _, testCase := range []struct{ name, source, want string }{
{"malformed", "{ malformed", "parse Elydora runtime config"},
{"duplicate", `{"agent_name":"augment","agent_name":"augment"}`, "duplicate key"},
{
"unsupported",
`{"org_id":"o","agent_id":"agent-1","kid":"k","base_url":"https://api.test","agent_name":"augment","extra":true}`,
"unsupported field",
},
{
"identity",
`{"org_id":"o","agent_id":"other","kid":"k","base_url":"https://api.test","agent_name":"augment"}`,
"identity does not match",
},
} {
t.Run(testCase.name, func(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
writeAugmentTestFile(
t,
fixture.runtimeConfig,
[]byte(testCase.source),
0600,
)
_, err := fixture.plugin.Status()
if err == nil || !strings.Contains(err.Error(), testCase.want) {
t.Fatalf("status error = %v, want %q", err, testCase.want)
}
})
}
}
func TestAugmentStatusIgnoresIncompleteManagedHookPairs(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
settings := readAugmentTestObject(t, fixture.configPath)
delete(requireObject(t, settings["hooks"]), "PostToolUse")
writeAugmentTestObject(t, fixture.configPath, settings)
status, err := fixture.plugin.Status()
if err != nil || status.Installed || status.HookConfigured {
t.Fatalf("incomplete-pair status = %#v, %v", status, err)
}
}
func TestAugmentRuntimeConfigOmitsEmptyToken(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
fixture.config.Token = ""
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
config := readAugmentTestObject(t, fixture.runtimeConfig)
if _, exists := config["token"]; exists {
t.Fatalf("empty token persisted: %#v", config)
}
}
@@ -0,0 +1,354 @@
package plugins
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
"sort"
"strings"
)
func managedAugmentIDs(
groups []augmentGroup,
wrapperName string,
runtimeRoot string,
) map[string]string {
result := map[string]string{}
for _, group := range groups {
for _, handler := range group.handlers {
agentID, managed := managedAugmentAgentID(
handler,
wrapperName,
runtimeRoot,
)
if !managed {
continue
}
key := agentID
if runtime.GOOS == "windows" {
key = strings.ToLower(key)
}
result[key] = agentID
}
}
return result
}
func augmentRuntimeContracts(
hooks augmentHooks,
runtimeRoot string,
) []augmentRuntimeContract {
guards := managedAugmentIDs(
hooks["PreToolUse"],
augmentGuardWrapperName(),
runtimeRoot,
)
audits := managedAugmentIDs(
hooks["PostToolUse"],
augmentAuditWrapperName(),
runtimeRoot,
)
keys := make([]string, 0, len(guards))
for key := range guards {
if _, exists := audits[key]; exists {
keys = append(keys, key)
}
}
sort.Strings(keys)
contracts := make([]augmentRuntimeContract, 0, len(keys))
for _, key := range keys {
agentID := guards[key]
agentDirectory := filepath.Join(runtimeRoot, agentID)
wrappers := resolveAugmentWrapperPaths(agentDirectory)
contracts = append(contracts, augmentRuntimeContract{
agentID: agentID,
guardPath: filepath.Join(agentDirectory, augmentGuardScript),
auditPath: filepath.Join(agentDirectory, augmentAuditScript),
guardWrapper: wrappers.guard,
auditWrapper: wrappers.audit,
})
}
return contracts
}
func readAugmentRuntimeConfig(path string) (map[string]any, bool, error) {
snapshot, err := readManagedFile(
path,
"Elydora runtime config",
maxRuntimeConfigBytes,
)
if err != nil || snapshot == nil {
return nil, snapshot != nil, err
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
config, err := decodeStrictJSONObject(snapshot.contents, label)
return config, true, err
}
func requireAugmentRuntimeString(
config map[string]any,
field string,
configPath string,
) (string, error) {
value, ok := config[field].(string)
if !ok || strings.TrimSpace(value) == "" {
return "", fmt.Errorf(
"Elydora runtime config %s is invalid: %s",
field,
configPath,
)
}
return value, nil
}
func validateAugmentRuntimeConfig(
config map[string]any,
expectedAgentID string,
configPath string,
) error {
supported := stringSet(
"org_id",
"agent_id",
"kid",
"base_url",
"token",
"agent_name",
)
fields := make([]string, 0, len(config))
for field := range config {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
if _, ok := supported[field]; !ok {
return fmt.Errorf(
`Elydora runtime config has unsupported field %q: %s`,
field,
configPath,
)
}
}
if _, err := requireAugmentRuntimeString(config, "org_id", configPath); err != nil {
return err
}
if _, err := requireAugmentRuntimeString(config, "kid", configPath); err != nil {
return err
}
agentID, err := requireAugmentRuntimeString(config, "agent_id", configPath)
if err != nil {
return err
}
if !sameAugmentAgentID(agentID, expectedAgentID) ||
config["agent_name"] != augmentAgentKey {
return fmt.Errorf(
"Elydora runtime identity does not match Auggie hooks: %s",
configPath,
)
}
if _, exists := config["token"]; exists {
if _, err := requireAugmentRuntimeString(config, "token", configPath); err != nil {
return err
}
}
baseURL, err := requireAugmentRuntimeString(config, "base_url", configPath)
if err != nil {
return err
}
if err := validateManagedBaseURL(baseURL); err != nil {
return fmt.Errorf(
"Elydora runtime config base URL is invalid at %s: %w",
configPath,
err,
)
}
return nil
}
func validateAugmentRuntimeIdentity(agentDirectory, agentID string) error {
runtimeRoot := filepath.Dir(agentDirectory)
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return err
}
configPath := filepath.Join(agentDirectory, "config.json")
config, configExists, err := readAugmentRuntimeConfig(configPath)
if err != nil {
return err
}
artifactExists := false
for _, item := range []struct {
path string
label string
limit int64
}{
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
{filepath.Join(agentDirectory, augmentGuardScript), "Elydora guard runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, augmentAuditScript), "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, augmentGuardWrapperName()), "Auggie guard wrapper", maxManagedSourceBytes},
{filepath.Join(agentDirectory, augmentAuditWrapperName()), "Auggie audit wrapper", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "chain-state.json"), "Elydora chain state", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "status-cache.json"), "Elydora status cache", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "error.log"), "Elydora error log", maxManagedSourceBytes},
} {
exists, inspectErr := managedPhysicalFileExists(
item.path,
item.label,
item.limit,
)
if inspectErr != nil {
return inspectErr
}
artifactExists = artifactExists || exists
}
if !configExists {
if artifactExists {
return fmt.Errorf(
"Elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != augmentAgentKey ||
!sameAugmentAgentID(configuredID, agentID) {
return fmt.Errorf(
"Elydora runtime config identity does not match Auggie agent %s: %s",
agentID,
configPath,
)
}
return nil
}
func augmentRuntimeContractExists(
contract augmentRuntimeContract,
runtimeRoot string,
nodePath string,
) (bool, error) {
agentDirectory := filepath.Dir(contract.guardPath)
wrappers := resolveAugmentWrapperPaths(agentDirectory)
if !sameAugmentPath(filepath.Dir(agentDirectory), runtimeRoot) ||
!sameAugmentPath(
contract.auditPath,
filepath.Join(agentDirectory, augmentAuditScript),
) ||
!sameAugmentPath(contract.guardWrapper, wrappers.guard) ||
!sameAugmentPath(contract.auditWrapper, wrappers.audit) {
return false, nil
}
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return false, err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return false, err
}
configPath := filepath.Join(agentDirectory, "config.json")
keyPath := filepath.Join(agentDirectory, "private.key")
config, configExists, err := readAugmentRuntimeConfig(configPath)
if err != nil {
return false, err
}
key, err := readManagedFile(keyPath, "Elydora private key", maxProtectedSecretBytes)
if err != nil {
return false, err
}
guard, err := readManagedFile(
contract.guardPath,
"Elydora guard runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
audit, err := readManagedFile(
contract.auditPath,
"Elydora audit runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
guardWrapper, err := readManagedFile(
contract.guardWrapper,
"Auggie guard wrapper",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
auditWrapper, err := readManagedFile(
contract.auditWrapper,
"Auggie audit wrapper",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
if !configExists || key == nil || guard == nil || audit == nil ||
guardWrapper == nil || auditWrapper == nil {
return false, nil
}
if err := validateAugmentRuntimeConfig(
config,
contract.agentID,
configPath,
); err != nil {
return false, err
}
if err := validateManagedPrivateKey(string(key.contents)); err != nil {
return false, fmt.Errorf("Elydora private key at %s: %w", keyPath, err)
}
return len(guard.contents) > 0 &&
len(audit.contents) > 0 &&
bytes.Equal(
guardWrapper.contents,
buildAugmentWrapper(nodePath, contract.guardPath),
) &&
bytes.Equal(
auditWrapper.contents,
buildAugmentWrapper(nodePath, contract.auditPath),
), nil
}
func augmentRuntimeFilesExist(
contracts []augmentRuntimeContract,
runtimeRoot string,
) (bool, error) {
nodePath, err := resolveNodeRuntime()
if err != nil {
return false, err
}
if err := requireAugmentAbsoluteNode(nodePath); err != nil {
return false, err
}
for _, contract := range contracts {
exists, err := augmentRuntimeContractExists(contract, runtimeRoot, nodePath)
if err != nil {
return false, err
}
if exists {
return true, nil
}
}
return false, nil
}
+74 -226
View File
@@ -1,7 +1,7 @@
package plugins
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"reflect"
@@ -10,16 +10,20 @@ import (
"testing"
)
func TestAugmentRegistryAndFactory(t *testing.T) {
func TestAugmentRegistryFactoryAndGuardOwnership(t *testing.T) {
entry := SupportedAgents[augmentAgentKey]
if entry.Name != "Augment Code CLI" ||
entry.ConfigDir != "~/.augment" ||
entry.ConfigFile != "settings.json" {
t.Fatalf("Auggie registry entry = %#v", entry)
}
if _, ok := NewPlugin(augmentAgentKey).(*AugmentPlugin); !ok {
plugin, ok := NewPlugin(augmentAgentKey).(*AugmentPlugin)
if !ok {
t.Fatalf("Auggie plugin factory returned %T", NewPlugin(augmentAgentKey))
}
if !plugin.ManagesGuardRuntime() {
t.Fatal("Auggie plugin must own guard runtime generation")
}
}
func TestAugmentGeneratedArgumentParsers(t *testing.T) {
@@ -46,7 +50,8 @@ func TestAugmentInstallPreservesSettingsAndIsIdempotent(t *testing.T) {
}
}`
fixture := prepareAugmentFixture(
t, augmentFixtureOptions{existingRaw: &existing},
t,
augmentFixtureOptions{existingRaw: &existing},
)
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
@@ -64,178 +69,77 @@ func TestAugmentInstallPreservesSettingsAndIsIdempotent(t *testing.T) {
len(requireArray(t, hooks["PostToolUse"])) != 1 {
t.Fatalf("unexpected hooks after repeat install: %#v", hooks)
}
preGroups := requireArray(t, hooks["PreToolUse"])
managedGroup := requireObject(t, preGroups[len(preGroups)-1])
managedGroup := requireObject(
t,
requireArray(t, hooks["PreToolUse"])[1],
)
if managedGroup["matcher"] != ".*" {
t.Fatalf("managed matcher = %#v", managedGroup["matcher"])
}
guard := augmentTestManagedHandler(
t, settings, "PreToolUse", fixture.guardWrapper,
t,
settings,
"PreToolUse",
fixture.guardWrapper,
)
audit := augmentTestManagedHandler(
t, settings, "PostToolUse", fixture.auditWrapper,
t,
settings,
"PostToolUse",
fixture.auditWrapper,
)
for _, handler := range []map[string]any{guard, audit} {
wantKeys := map[string]struct{}{"type": {}, "command": {}, "timeout": {}}
if len(handler) != len(wantKeys) {
t.Fatalf("handler has extra fields: %#v", handler)
want := map[string]any{
"type": "command", "command": handler["command"],
"timeout": augmentHookTimeout,
}
for key := range handler {
if _, exists := wantKeys[key]; !exists {
t.Fatalf("handler has unexpected field %q", key)
}
}
if handler["type"] != "command" || handler["timeout"] != augmentHookTimeout {
t.Fatalf("unexpected handler: %#v", handler)
if !reflect.DeepEqual(handler, want) {
t.Fatalf("managed handler = %#v", handler)
}
}
guardWrapper, err := os.ReadFile(fixture.guardWrapper)
if err != nil || !strings.Contains(string(guardWrapper), augmentGuardScript) {
t.Fatalf("guard wrapper = %q, %v", guardWrapper, err)
nodePath, err := resolveNodeRuntime()
if err != nil {
t.Fatalf("resolve Node.js runtime: %v", err)
}
auditWrapper, err := os.ReadFile(fixture.auditWrapper)
if err != nil || !strings.Contains(string(auditWrapper), augmentAuditScript) {
t.Fatalf("audit wrapper = %q, %v", auditWrapper, err)
}
if runtime.GOOS == "windows" {
if !strings.HasPrefix(string(guardWrapper), "@echo off\r\n") ||
!strings.Contains(string(guardWrapper), "exit /b %errorlevel%") {
t.Fatalf("invalid Windows wrapper: %q", guardWrapper)
for _, item := range []struct {
path, script string
}{
{fixture.guardWrapper, fixture.guardPath},
{fixture.auditWrapper, fixture.hookPath},
} {
actual, err := os.ReadFile(item.path)
if err != nil {
t.Fatalf("read wrapper %s: %v", item.path, err)
}
} else {
info, statErr := os.Stat(fixture.guardWrapper)
if statErr != nil || info.Mode().Perm()&0100 == 0 ||
!strings.HasPrefix(string(guardWrapper), "#!/bin/sh\nexec ") {
t.Fatalf("invalid POSIX wrapper: %q, %#v, %v", guardWrapper, info, statErr)
want := buildAugmentWrapper(nodePath, item.script)
if string(actual) != string(want) {
t.Fatalf("wrapper %s = %q, want %q", item.path, actual, want)
}
}
runtimeConfig := readAugmentTestObject(t, fixture.runtimeConfig)
if runtimeConfig["agent_name"] != augmentAgentKey {
t.Fatalf("runtime agent name = %#v", runtimeConfig["agent_name"])
if runtimeConfig["agent_name"] != augmentAgentKey ||
runtimeConfig["agent_id"] != augmentTestAgentID {
t.Fatalf("runtime identity = %#v", runtimeConfig)
}
auditSource, err := os.ReadFile(fixture.hookPath)
if err != nil || !strings.Contains(string(auditSource), "const NATIVE_PAYLOAD = true;") {
t.Fatalf("audit runtime native payload = %v, %v", err, strings.Contains(string(auditSource), "const NATIVE_PAYLOAD = true;"))
}
for _, path := range []string{
filepath.Join(fixture.workspaceDir, ".augment", "settings.json"),
filepath.Join(fixture.workspaceDir, ".augment", "settings.local.json"),
filepath.Join(fixture.projectDir, ".augment", "settings.json"),
filepath.Join(fixture.projectDir, ".augment", "settings.local.json"),
} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("workspace settings written at %s: %v", path, err)
}
}
}
func TestAugmentWrappersBlockAndForwardPayloadByteForByte(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
capturePath := filepath.Join(t.TempDir(), "captured-event.json")
captureJSON, err := json.Marshal(capturePath)
if err != nil {
t.Fatalf("marshal capture path: %v", err)
}
captureScript := "const fs = require('node:fs'); const chunks = []; " +
"process.stdin.on('data', chunk => chunks.push(chunk)); " +
"process.stdin.on('end', () => fs.writeFileSync(" +
string(captureJSON) + ", Buffer.concat(chunks)));\n"
if err := os.WriteFile(fixture.hookPath, []byte(captureScript), 0700); err != nil {
t.Fatalf("write capture hook: %v", err)
}
settings := readAugmentTestObject(t, fixture.configPath)
prePayload := `{"hook_event_name":"PreToolUse","conversation_id":"conversation-1","workspace_roots":["workspace"],"tool_name":"launch-process","tool_input":{"command":"echo test"},"is_mcp_tool":false}`
guard := augmentTestManagedHandler(
t, settings, "PreToolUse", fixture.guardWrapper,
)
exitCode, stderr := runAugmentCommand(
t, guard["command"].(string), fixture.homeDir, prePayload,
)
if exitCode != 2 || !strings.Contains(stderr, "Agent is frozen by Elydora") {
t.Fatalf("guard exit = %d, stderr = %q", exitCode, stderr)
}
postPayload := `{"hook_event_name":"PostToolUse","conversation_id":"conversation-1","workspace_roots":["workspace"],"tool_name":"launch-process","tool_input":{"command":"echo test"},"tool_output":"test","is_mcp_tool":false}`
audit := augmentTestManagedHandler(
t, settings, "PostToolUse", fixture.auditWrapper,
)
exitCode, stderr = runAugmentCommand(
t, audit["command"].(string), fixture.homeDir, postPayload,
)
if exitCode != 0 {
t.Fatalf("audit exit = %d, stderr = %q", exitCode, stderr)
}
captured, err := os.ReadFile(capturePath)
if err != nil || string(captured) != postPayload {
t.Fatalf("captured payload = %q, %v", captured, err)
}
}
func TestAugmentStatusRequiresPairCoreRuntimesAndWrappers(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
status, err := fixture.plugin.Status()
if err != nil || !status.Installed || !status.HookConfigured ||
!status.HookScriptExists {
t.Fatalf("installed status = %#v, %v", status, err)
}
if err := os.Remove(fixture.guardWrapper); err != nil {
t.Fatalf("remove guard wrapper: %v", err)
}
status, err = fixture.plugin.Status()
if err != nil || status.Installed || !status.HookConfigured ||
status.HookScriptExists {
t.Fatalf("missing-wrapper status = %#v, %v", status, err)
}
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("restore Auggie hooks: %v", err)
}
if err := os.Remove(fixture.hookPath); err != nil {
t.Fatalf("remove hook runtime: %v", err)
}
status, err = fixture.plugin.Status()
if err != nil || status.Installed || status.HookScriptExists {
t.Fatalf("missing-runtime status = %#v, %v", status, err)
}
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("restore Auggie hooks: %v", err)
}
settings := readAugmentTestObject(t, fixture.configPath)
delete(requireObject(t, settings["hooks"]), "PostToolUse")
writeAugmentTestObject(t, fixture.configPath, settings)
status, err = fixture.plugin.Status()
if err != nil || status.Installed || status.HookConfigured {
t.Fatalf("incomplete-pair status = %#v, %v", status, err)
}
}
func TestAugmentStatusSkipsIncompleteEarlierContract(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
settings := readAugmentTestObject(t, fixture.configPath)
hooks := requireObject(t, settings["hooks"])
for _, item := range []struct{ event, wrapper string }{
{"PreToolUse", augmentGuardWrapperName()},
{"PostToolUse", augmentAuditWrapperName()},
} {
hooks[item.event] = append(
requireArray(t, hooks[item.event]),
map[string]any{"hooks": []any{buildAugmentHandler(filepath.Join(
filepath.Dir(fixture.agentDir), "agent-0", item.wrapper,
))}},
)
}
writeAugmentTestObject(t, fixture.configPath, settings)
status, err := fixture.plugin.Status()
if err != nil || !status.Installed {
t.Fatalf("status with earlier incomplete contract = %#v, %v", status, err)
}
}
func TestAugmentUninstallPreservesExactUserOwnership(t *testing.T) {
existing := `{"owner":"user","hooks":{"Notification":[]}}`
fixture := prepareAugmentFixture(
t, augmentFixtureOptions{existingRaw: &existing},
t,
augmentFixtureOptions{existingRaw: &existing},
)
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
@@ -251,12 +155,15 @@ func TestAugmentUninstallPreservesExactUserOwnership(t *testing.T) {
requireArray(t, managedGroup["hooks"]),
map[string]any{"type": "command", "command": "user-command"},
)
preGroups = append(preGroups,
preGroups = append(
preGroups,
map[string]any{"hooks": []any{buildAugmentHandler(
fixture.guardWrapper + ".backup",
)}},
map[string]any{"hooks": []any{buildAugmentHandler(filepath.Join(
filepath.Dir(fixture.agentDir), "agent-10", augmentGuardWrapperName(),
filepath.Dir(fixture.agentDir),
"agent-10",
augmentGuardWrapperName(),
))}},
)
hooks["PreToolUse"] = preGroups
@@ -274,7 +181,10 @@ func TestAugmentUninstallPreservesExactUserOwnership(t *testing.T) {
len(requireArray(t, remainingHooks["PreToolUse"])) != 4 {
t.Fatalf("user settings changed: %#v", remaining)
}
if !reflect.DeepEqual(requireArray(t, remainingHooks["Notification"]), []any{}) {
if !reflect.DeepEqual(
requireArray(t, remainingHooks["Notification"]),
[]any{},
) {
t.Fatalf("empty notification changed: %#v", remainingHooks["Notification"])
}
raw, _ := os.ReadFile(fixture.configPath)
@@ -287,7 +197,7 @@ func TestAugmentUninstallPreservesExactUserOwnership(t *testing.T) {
}
}
func TestAugmentInstallReplacesStaleAndPreservesEmptyGroups(t *testing.T) {
func TestAugmentInstallReplacesStaleHooksAndPreservesEmptyGroups(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
@@ -305,7 +215,9 @@ func TestAugmentInstallReplacesStaleAndPreservesEmptyGroups(t *testing.T) {
hooks[item.event] = append(
requireArray(t, hooks[item.event]),
map[string]any{"hooks": []any{buildAugmentHandler(filepath.Join(
filepath.Dir(fixture.agentDir), "agent-old", item.wrapper,
filepath.Dir(fixture.agentDir),
"agent-old",
item.wrapper,
))}},
)
}
@@ -331,14 +243,17 @@ func TestAugmentUninstallRemovesOwnedSettings(t *testing.T) {
if err := fixture.plugin.Uninstall(augmentTestAgentID); err != nil {
t.Fatalf("uninstall Auggie hooks: %v", err)
}
if _, err := os.Stat(fixture.configPath); !os.IsNotExist(err) {
if _, err := os.Stat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("owned Auggie settings remain: %v", err)
}
}
func TestAugmentMalformedSettingsPreventRuntimeWrites(t *testing.T) {
for _, testCase := range []struct{ name, raw, want string }{
{"malformed", "{ malformed", "parse Auggie settings"},
{"malformed", "{ malformed", "parse Auggie user settings"},
{"duplicate", `{"hooks":{},"hooks":{}}`, "duplicate key"},
{"comments", "{\"hooks\":{} // comment\n}", "invalid character"},
{"trailing-comma", `{"hooks":{},}`, "invalid character"},
{"null-root", "null", "must contain a JSON object"},
{"null-hooks", `{"hooks":null}`, `field "hooks" must be an object`},
{"unknown-event", `{"hooks":{"UnknownEvent":[]}}`, "unsupported event"},
@@ -356,7 +271,8 @@ func TestAugmentMalformedSettingsPreventRuntimeWrites(t *testing.T) {
} {
t.Run(testCase.name, func(t *testing.T) {
fixture := prepareAugmentFixture(
t, augmentFixtureOptions{existingRaw: &testCase.raw},
t,
augmentFixtureOptions{existingRaw: &testCase.raw},
)
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), testCase.want) {
@@ -366,75 +282,7 @@ func TestAugmentMalformedSettingsPreventRuntimeWrites(t *testing.T) {
if readErr != nil || string(raw) != testCase.raw {
t.Fatalf("original settings changed: %q, %v", raw, readErr)
}
for _, path := range []string{
fixture.hookPath,
fixture.runtimeConfig,
fixture.privateKey,
fixture.guardWrapper,
fixture.auditWrapper,
} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("runtime write occurred at %s: %v", path, err)
}
}
assertNoAugmentRuntimeWrites(t, fixture)
})
}
}
func TestAugmentMissingGuardPreventsWrites(t *testing.T) {
fixture := prepareAugmentFixture(
t, augmentFixtureOptions{withoutGuard: true},
)
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "guard runtime is missing") {
t.Fatalf("install error = %v", err)
}
for _, path := range []string{
fixture.configPath,
fixture.hookPath,
fixture.runtimeConfig,
fixture.privateKey,
fixture.guardWrapper,
fixture.auditWrapper,
} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("write occurred at %s: %v", path, err)
}
}
}
func TestAugmentStatusSurfacesMalformedRuntimeMetadata(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
if err := os.WriteFile(
fixture.runtimeConfig, []byte("{ malformed"), 0600,
); err != nil {
t.Fatalf("corrupt runtime config: %v", err)
}
if _, err := fixture.plugin.Status(); err == nil ||
!strings.Contains(err.Error(), "parse Elydora runtime config") {
t.Fatalf("status error = %v", err)
}
}
func TestAugmentAtomicWritesLeaveNoTemporaryFiles(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
for _, directory := range []string{
fixture.agentDir, filepath.Dir(fixture.configPath),
} {
entries, err := os.ReadDir(directory)
if err != nil {
t.Fatalf("read directory %s: %v", directory, err)
}
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".tmp") {
t.Fatalf("temporary file remains: %s", entry.Name())
}
}
}
}
@@ -11,18 +11,20 @@ import (
"testing"
)
const augmentTestAgentID = "agent-1"
const (
augmentTestAgentID = "agent-1"
augmentPrivateKey = "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc"
)
type augmentFixtureOptions struct {
existingRaw *string
withoutGuard bool
existingRaw *string
}
type augmentFixture struct {
plugin *AugmentPlugin
config InstallConfig
homeDir string
workspaceDir string
projectDir string
agentDir string
configPath string
guardPath string
@@ -42,49 +44,34 @@ func prepareAugmentFixture(
options augmentFixtureOptions,
) *augmentFixture {
t.Helper()
homeDir := filepath.Join(t.TempDir(), "home with spaces and 'quote")
workspaceDir := filepath.Join(homeDir, "workspace")
root := t.TempDir()
homeDir := filepath.Join(root, "home with spaces and 'quote")
projectDir := filepath.Join(root, "project with spaces")
if err := os.MkdirAll(projectDir, 0700); err != nil {
t.Fatalf("create project directory: %v", err)
}
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
agentDir := filepath.Join(homeDir, ".elydora", augmentTestAgentID)
configPath := filepath.Join(homeDir, ".augment", "settings.json")
guardPath := filepath.Join(agentDir, augmentGuardScript)
if err := os.MkdirAll(workspaceDir, 0755); err != nil {
t.Fatalf("create workspace: %v", err)
}
if err := os.MkdirAll(agentDir, 0755); err != nil {
t.Fatalf("create agent directory: %v", err)
}
if !options.withoutGuard {
source := "process.stdin.resume(); process.stderr.write('Agent is frozen by Elydora.'); process.exit(2);\n"
if err := os.WriteFile(guardPath, []byte(source), 0700); err != nil {
t.Fatalf("write guard runtime: %v", err)
}
}
if options.existingRaw != nil {
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
t.Fatalf("create Auggie config directory: %v", err)
}
if err := os.WriteFile(configPath, []byte(*options.existingRaw), 0600); err != nil {
t.Fatalf("write Auggie settings: %v", err)
}
}
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
guardPath := filepath.Join(agentDir, augmentGuardScript)
return &augmentFixture{
plugin: &AugmentPlugin{},
homeDir: homeDir,
workspaceDir: workspaceDir,
agentDir: agentDir,
plugin: &AugmentPlugin{},
config: InstallConfig{
AgentName: augmentAgentKey,
OrgID: "org-1",
AgentID: augmentTestAgentID,
PrivateKey: "test-key",
KID: "kid-1",
BaseURL: "https://api.elydora.test",
GuardScriptPath: guardPath,
AgentName: augmentAgentKey, OrgID: "org-1", AgentID: augmentTestAgentID,
PrivateKey: augmentPrivateKey, KID: "kid-1", Token: "token-1",
BaseURL: "https://api.elydora.test", GuardScriptPath: guardPath,
},
configPath: configPath,
guardPath: guardPath,
homeDir: homeDir, projectDir: projectDir, agentDir: agentDir,
configPath: configPath, guardPath: guardPath,
hookPath: filepath.Join(agentDir, augmentAuditScript),
guardWrapper: filepath.Join(agentDir, augmentGuardWrapperName()),
auditWrapper: filepath.Join(agentDir, augmentAuditWrapperName()),
@@ -108,19 +95,34 @@ func readAugmentTestObject(t *testing.T, path string) map[string]any {
func writeAugmentTestObject(t *testing.T, path string, value map[string]any) {
t.Helper()
encoded, err := json.Marshal(value)
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
t.Fatalf("marshal Auggie settings: %v", err)
}
if err := os.WriteFile(path, encoded, 0600); err != nil {
t.Fatalf("write Auggie settings: %v", err)
encoded = append(encoded, '\n')
writeAugmentTestFile(t, path, encoded, 0600)
}
func writeAugmentTestFile(
t *testing.T,
path string,
contents []byte,
mode os.FileMode,
) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
t.Fatalf("create parent directory for %s: %v", path, err)
}
if err := os.WriteFile(path, contents, mode); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
func augmentTestManagedHandler(
t *testing.T,
settings map[string]any,
event, wrapperPath string,
event string,
wrapperPath string,
) map[string]any {
t.Helper()
expected := buildAugmentCommand(wrapperPath)
@@ -140,8 +142,10 @@ func augmentTestManagedHandler(
func runAugmentCommand(
t *testing.T,
command, homeDir, payload string,
) (int, string) {
command string,
fixture *augmentFixture,
payload []byte,
) (int, string, string) {
t.Helper()
var process *exec.Cmd
if runtime.GOOS == "windows" {
@@ -154,20 +158,67 @@ func runAugmentCommand(
} else {
process = exec.Command("sh", "-c", command)
}
process.Dir = fixture.projectDir
process.Env = append(
os.Environ(), "HOME="+homeDir, "USERPROFILE="+homeDir,
os.Environ(),
"HOME="+fixture.homeDir,
"USERPROFILE="+fixture.homeDir,
)
process.Stdin = bytes.NewBufferString(payload)
var stderr bytes.Buffer
process.Stdin = bytes.NewReader(payload)
var stdout, stderr bytes.Buffer
process.Stdout = &stdout
process.Stderr = &stderr
err := process.Run()
if err == nil {
return 0, stderr.String()
return 0, stdout.String(), stderr.String()
}
var exitError *exec.ExitError
if errors.As(err, &exitError) {
return exitError.ExitCode(), stderr.String()
return exitError.ExitCode(), stdout.String(), stderr.String()
}
t.Fatalf("run Auggie hook command: %v", err)
return -1, stderr.String()
return -1, stdout.String(), stderr.String()
}
func assertNoAugmentRuntimeWrites(t *testing.T, fixture *augmentFixture) {
t.Helper()
for _, path := range []string{
fixture.runtimeConfig,
fixture.privateKey,
fixture.guardPath,
fixture.hookPath,
fixture.guardWrapper,
fixture.auditWrapper,
} {
if _, err := os.Lstat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("runtime file exists at %s: %v", path, err)
}
}
}
func assertNoAugmentTransactionArtifacts(t *testing.T, root string) {
t.Helper()
if _, err := os.Lstat(root); errors.Is(err, os.ErrNotExist) {
return
}
err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
if err != nil {
return err
}
name := entry.Name()
if filepath.Ext(name) == ".tmp" || filepath.Ext(name) == ".rollback" {
t.Errorf("transaction artifact remains at %s", path)
}
return nil
})
if err != nil {
t.Fatalf("walk Auggie fixture: %v", err)
}
}
func augmentSymlinkOrSkip(t *testing.T, target, link string) {
t.Helper()
if err := os.Symlink(target, link); err != nil {
t.Skipf("symbolic links unavailable: %v", err)
}
}
@@ -0,0 +1,393 @@
package plugins
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
func prepareAugmentTestInstallation(
t *testing.T,
fixture *augmentFixture,
) ([]*fileChange, *augmentRuntimePaths, *augmentDocument) {
t.Helper()
document, err := readAugmentDocument()
if err != nil {
t.Fatalf("read Auggie document: %v", err)
}
paths, nodePath, err := preflightAugmentInstallation(fixture.config, document)
if err != nil {
t.Fatalf("preflight Auggie installation: %v", err)
}
hooks, _ := removeManagedAugmentHooks(document.hooks, "", paths.runtimeRoot)
hooks["PreToolUse"] = append(
hooks["PreToolUse"],
buildAugmentGroup(buildAugmentHandler(paths.guardWrapperPath)),
)
hooks["PostToolUse"] = append(
hooks["PostToolUse"],
buildAugmentGroup(buildAugmentHandler(paths.auditWrapperPath)),
)
rendered, err := renderAugmentDocument(document, hooks)
if err != nil {
t.Fatalf("render Auggie document: %v", err)
}
changes, err := prepareAugmentInstallationChanges(
fixture.config,
paths,
nodePath,
rendered,
)
if err != nil {
t.Fatalf("prepare Auggie installation: %v", err)
}
return changes, paths, document
}
func TestAugmentInstallRollsBackAllSevenFiles(t *testing.T) {
original := "{\"telemetryEnabled\":true}\n"
fixture := prepareAugmentFixture(
t,
augmentFixtureOptions{existingRaw: &original},
)
failed := false
fixture.plugin.rename = func(source, destination string) error {
if sameAugmentPath(destination, fixture.configPath) &&
strings.HasSuffix(source, ".tmp") &&
!failed {
failed = true
return errors.New("injected Auggie settings failure")
}
return os.Rename(source, destination)
}
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "injected Auggie settings failure") {
t.Fatalf("install error = %v", err)
}
raw, readErr := os.ReadFile(fixture.configPath)
if readErr != nil || string(raw) != original {
t.Fatalf("original settings changed: %q, %v", raw, readErr)
}
assertNoAugmentRuntimeWrites(t, fixture)
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}
func TestPreparedAugmentInstallRejectsConcurrentSettingsChange(t *testing.T) {
original := `{"telemetryEnabled":true}`
fixture := prepareAugmentFixture(
t,
augmentFixtureOptions{existingRaw: &original},
)
changes, paths, document := prepareAugmentTestInstallation(t, fixture)
concurrent := []byte(
"{\"telemetryEnabled\":false,\"hooks\":{\"Notification\":[]}}\n",
)
if err := os.WriteFile(fixture.configPath, concurrent, 0600); err != nil {
t.Fatalf("write concurrent settings: %v", err)
}
err := writeAugmentChanges(
changes,
"Install Augment Code CLI hooks",
nil,
paths.runtimeRoot,
paths.agentDirectory,
filepath.Dir(document.configPath),
)
if err == nil || !strings.Contains(err.Error(), "changed during installation") {
t.Fatalf("prepared install error = %v", err)
}
actual, readErr := os.ReadFile(fixture.configPath)
if readErr != nil || string(actual) != string(concurrent) {
t.Fatalf("concurrent settings changed: %q, %v", actual, readErr)
}
assertNoAugmentRuntimeWrites(t, fixture)
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}
func TestAugmentInstallDetectsConcurrentSettingsIdentityReplacement(t *testing.T) {
source := []byte(`{"owner":"original"}`)
fixture := prepareAugmentFixture(
t,
augmentFixtureOptions{existingRaw: augmentString(string(source))},
)
mutated := false
fixture.plugin.rename = func(stagedPath, destination string) error {
if !mutated && strings.HasSuffix(stagedPath, ".tmp") {
mutated = true
external := fixture.configPath + ".external"
if err := os.Rename(fixture.configPath, external); err != nil {
return err
}
if err := os.WriteFile(fixture.configPath, source, 0600); err != nil {
return err
}
}
return os.Rename(stagedPath, destination)
}
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "changed during installation") {
t.Fatalf("concurrent identity error = %v", err)
}
actual, readErr := os.ReadFile(fixture.configPath)
if readErr != nil || string(actual) != string(source) {
t.Fatalf("replacement settings changed: %q, %v", actual, readErr)
}
assertNoAugmentRuntimeWrites(t, fixture)
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}
func TestPreparedAugmentInstallRejectsStaleSettingsBeforeStaging(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
document, err := readAugmentDocument()
if err != nil {
t.Fatalf("read Auggie document: %v", err)
}
paths, nodePath, err := preflightAugmentInstallation(fixture.config, document)
if err != nil {
t.Fatalf("preflight Auggie installation: %v", err)
}
hooks, _ := removeManagedAugmentHooks(document.hooks, "", paths.runtimeRoot)
hooks["PreToolUse"] = append(
hooks["PreToolUse"],
buildAugmentGroup(buildAugmentHandler(paths.guardWrapperPath)),
)
hooks["PostToolUse"] = append(
hooks["PostToolUse"],
buildAugmentGroup(buildAugmentHandler(paths.auditWrapperPath)),
)
rendered, err := renderAugmentDocument(document, hooks)
if err != nil {
t.Fatalf("render Auggie document: %v", err)
}
writeAugmentTestFile(
t,
fixture.configPath,
[]byte("{\"hooks\":{\"Notification\":[]}}\n"),
0600,
)
_, err = prepareAugmentInstallationChanges(
fixture.config,
paths,
nodePath,
rendered,
)
if err == nil || !strings.Contains(err.Error(), "changed before update") {
t.Fatalf("stale settings error = %v", err)
}
assertNoAugmentRuntimeWrites(t, fixture)
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}
func TestAugmentOrphanRuntimeArtifactsFailBeforeSettingsWrites(t *testing.T) {
for _, runtimeName := range []string{
"private.key",
augmentGuardScript,
augmentAuditScript,
augmentGuardWrapperName(),
augmentAuditWrapperName(),
"chain-state.json",
"status-cache.json",
"error.log",
} {
t.Run(runtimeName, func(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
artifact := filepath.Join(fixture.agentDir, runtimeName)
writeAugmentTestFile(t, artifact, []byte("orphan\n"), 0600)
err := fixture.plugin.Install(fixture.config)
if err == nil ||
!strings.Contains(err.Error(), "identity cannot be verified") {
t.Fatalf("orphan runtime error = %v", err)
}
raw, readErr := os.ReadFile(artifact)
if readErr != nil || string(raw) != "orphan\n" {
t.Fatalf("orphan artifact changed: %q, %v", raw, readErr)
}
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("settings written after orphan error: %v", err)
}
})
}
}
func TestAugmentMismatchedRuntimeIdentityFailsBeforeWrites(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
writeAugmentTestObject(t, fixture.runtimeConfig, map[string]any{
"agent_id": "another-agent", "agent_name": augmentAgentKey,
})
original, err := os.ReadFile(fixture.runtimeConfig)
if err != nil {
t.Fatalf("read runtime config: %v", err)
}
err = fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "identity does not match") {
t.Fatalf("identity error = %v", err)
}
actual, readErr := os.ReadFile(fixture.runtimeConfig)
if readErr != nil || string(actual) != string(original) {
t.Fatalf("runtime config changed: %q, %v", actual, readErr)
}
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("settings written after identity error: %v", err)
}
}
func TestAugmentLinkedSettingsRuntimeAndWrappersAreRejected(t *testing.T) {
for _, kind := range []string{
"configuration",
"settings",
"runtime",
"agent-directory",
"runtime-config",
"wrapper",
} {
t.Run(kind, func(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
target := filepath.Join(t.TempDir(), kind+"-target")
switch kind {
case "settings":
writeAugmentTestFile(t, target, []byte("{}\n"), 0600)
if err := os.MkdirAll(filepath.Dir(fixture.configPath), 0700); err != nil {
t.Fatalf("create settings directory: %v", err)
}
augmentSymlinkOrSkip(t, target, fixture.configPath)
case "configuration":
if err := os.MkdirAll(target, 0700); err != nil {
t.Fatalf("create target directory: %v", err)
}
if err := os.MkdirAll(filepath.Dir(filepath.Dir(fixture.configPath)), 0700); err != nil {
t.Fatalf("create configuration parent: %v", err)
}
augmentSymlinkOrSkip(t, target, filepath.Dir(fixture.configPath))
case "runtime":
if err := os.MkdirAll(target, 0700); err != nil {
t.Fatalf("create target directory: %v", err)
}
if err := os.MkdirAll(filepath.Dir(filepath.Dir(fixture.agentDir)), 0700); err != nil {
t.Fatalf("create runtime parent: %v", err)
}
augmentSymlinkOrSkip(t, target, filepath.Dir(fixture.agentDir))
case "agent-directory":
if err := os.MkdirAll(target, 0700); err != nil {
t.Fatalf("create target directory: %v", err)
}
if err := os.MkdirAll(filepath.Dir(fixture.agentDir), 0700); err != nil {
t.Fatalf("create runtime root: %v", err)
}
augmentSymlinkOrSkip(t, target, fixture.agentDir)
case "runtime-config":
writeAugmentTestFile(
t,
target,
[]byte(`{"agent_id":"agent-1","agent_name":"augment"}`),
0600,
)
if err := os.MkdirAll(fixture.agentDir, 0700); err != nil {
t.Fatalf("create agent directory: %v", err)
}
augmentSymlinkOrSkip(t, target, fixture.runtimeConfig)
case "wrapper":
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
writeAugmentTestFile(t, target, []byte("external wrapper\n"), 0600)
if err := os.Remove(fixture.guardWrapper); err != nil {
t.Fatalf("remove guard wrapper: %v", err)
}
augmentSymlinkOrSkip(t, target, fixture.guardWrapper)
}
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "physical") {
t.Fatalf("linked %s error = %v", kind, err)
}
})
}
}
func TestAugmentInstallValidatesRuntimeInputsBeforeWrites(t *testing.T) {
for _, testCase := range []struct {
name, field, value, want string
}{
{"agent-name", "agent_name", "codex", "requires agent name augment"},
{"agent-id", "agent_id", "../escape", "invalid agent ID"},
{"organization", "org_id", " ", "organization ID is required"},
{"key-id", "kid", " ", "key ID is required"},
{"private-key", "private_key", "invalid", "canonical 32-byte"},
{"token", "token", " ", "non-whitespace"},
{"base-url", "base_url", "https://api.elydora.com?q=1", "query parameters"},
{"guard-path", "guard_script_path", "outside", "managed agent directory"},
{"audit-path", "hook_script", "outside", "managed agent directory"},
} {
t.Run(testCase.name, func(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
switch testCase.field {
case "agent_name":
fixture.config.AgentName = testCase.value
case "agent_id":
fixture.config.AgentID = testCase.value
case "org_id":
fixture.config.OrgID = testCase.value
case "kid":
fixture.config.KID = testCase.value
case "private_key":
fixture.config.PrivateKey = testCase.value
case "token":
fixture.config.Token = testCase.value
case "base_url":
fixture.config.BaseURL = testCase.value
case "guard_script_path":
fixture.config.GuardScriptPath = testCase.value
case "hook_script":
fixture.config.HookScript = testCase.value
}
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), testCase.want) {
t.Fatalf("validation error = %v, want %q", err, testCase.want)
}
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("settings written after validation error: %v", err)
}
assertNoAugmentRuntimeWrites(t, fixture)
})
}
}
func TestAugmentInstallLeavesNoTransactionArtifacts(t *testing.T) {
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}
func TestAugmentUninstallPreservesSettingsAfterCommitFailure(t *testing.T) {
original := `{"owner":"user"}`
fixture := prepareAugmentFixture(
t,
augmentFixtureOptions{existingRaw: &original},
)
if err := fixture.plugin.Install(fixture.config); err != nil {
t.Fatalf("install Auggie hooks: %v", err)
}
before, err := os.ReadFile(fixture.configPath)
if err != nil {
t.Fatalf("read installed settings: %v", err)
}
fixture.plugin.rename = func(source, destination string) error {
if sameAugmentPath(destination, fixture.configPath) &&
strings.HasSuffix(source, ".tmp") {
return errors.New("injected Auggie uninstall failure")
}
return os.Rename(source, destination)
}
err = fixture.plugin.Uninstall(augmentTestAgentID)
if err == nil || !strings.Contains(err.Error(), "injected Auggie uninstall failure") {
t.Fatalf("uninstall error = %v", err)
}
after, readErr := os.ReadFile(fixture.configPath)
if readErr != nil || string(after) != string(before) {
t.Fatalf("Auggie settings changed: %q, %v", after, readErr)
}
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
}