diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index bf12ea3..52c3fb1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -40,5 +40,16 @@ jobs: go-version-file: go.mod cache-dependency-path: go.sum + - name: Check Go formatting + run: | + unformatted="$(gofmt -l $(find . -name '*.go' -not -path './.git/*'))" + if [ -n "$unformatted" ]; then + echo "The following Go files are not gofmt-formatted:" + echo "$unformatted" + echo + echo "Run: gofmt -w \$(find . -name '*.go' -not -path './.git/*')" + exit 1 + fi + - name: Run unit tests run: go test ./... diff --git a/cmd/atryum/commands.go b/cmd/atryum/commands.go index 2be39e1..005403c 100644 --- a/cmd/atryum/commands.go +++ b/cmd/atryum/commands.go @@ -12,7 +12,6 @@ import ( "runtime" "strconv" "strings" - ) const validMindSetupBaseURL = "https://app.dev.vm.validmind.ai" diff --git a/cmd/atryum/main.go b/cmd/atryum/main.go index 2a82fde..b20b3bf 100644 --- a/cmd/atryum/main.go +++ b/cmd/atryum/main.go @@ -527,14 +527,14 @@ type evaluatorAdapter struct { func (e *evaluatorAdapter) EvaluateToolCall(ctx context.Context, req invocation.EvaluateRequest) (invocation.EvaluateResponse, error) { resp, err := e.client.EvaluateToolCall(ctx, backendclient.EvaluateRequest{ - ModelConfigCUID: req.ModelConfigCUID, - OrgCUID: req.OrgCUID, - AgentVMCUID: req.AgentVMCUID, + ModelConfigCUID: req.ModelConfigCUID, + OrgCUID: req.OrgCUID, + AgentVMCUID: req.AgentVMCUID, CharterFieldKey: req.CharterFieldKey, - ServerName: req.ServerName, - ToolName: req.ToolName, - ToolArgs: req.ToolArgs, - Context: req.Context, + ServerName: req.ServerName, + ToolName: req.ToolName, + ToolArgs: req.ToolArgs, + Context: req.Context, }) if err != nil { return invocation.EvaluateResponse{}, err diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 63a54b2..0ce7ee1 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -339,14 +339,14 @@ type RuleListResponse struct { // ─── Agent admin types ──────────────────────────────────────────────────────── type AdminAgent struct { - CUID string `json:"cuid"` - OrgName string `json:"org_name"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - AgentIDs []string `json:"agent_ids"` - SyncedAt time.Time `json:"synced_at"` - Enabled bool `json:"enabled"` - Charter string `json:"charter,omitempty"` + CUID string `json:"cuid"` + OrgName string `json:"org_name"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + AgentIDs []string `json:"agent_ids"` + SyncedAt time.Time `json:"synced_at"` + Enabled bool `json:"enabled"` + Charter string `json:"charter,omitempty"` // Synced is true when this agent originated from a ValidMind sync // (vm_organization_cuid is non-empty). Synced agents cannot be deleted // manually — they are removed by re-syncing with a different org/record-type. @@ -376,15 +376,15 @@ type AgentListResponse struct { func toAdminAgent(a store.AgentRecord) AdminAgent { ids := parseAgentIDs(a.AgentIDs) return AdminAgent{ - CUID: a.ID, - OrgName: a.VMOrganizationName, - Name: a.VMName, - Description: a.VMDescription, - AgentIDs: ids, - SyncedAt: a.SyncedAt, - Enabled: a.Enabled, - Charter: a.Charter, - Synced: a.VMOrganizationCUID != "", + CUID: a.ID, + OrgName: a.VMOrganizationName, + Name: a.VMName, + Description: a.VMDescription, + AgentIDs: ids, + SyncedAt: a.SyncedAt, + Enabled: a.Enabled, + Charter: a.Charter, + Synced: a.VMOrganizationCUID != "", } } @@ -1179,7 +1179,6 @@ func apiMatchPatterns(patterns []string, value string) bool { return false } - // annotatedTool is the on-the-wire shape used for tools/list when atryum is // able to compute a per-tool policy disposition. It mirrors mcp.Tool but adds // an `annotations` block plus a description prefix so models that ignore @@ -1463,22 +1462,22 @@ func (h *Handler) adminInvocationDetail(w http.ResponseWriter, r *http.Request) if req.CreateRule.Enabled != nil { enabled = *req.CreateRule.Enabled } - newRule := store.Rule{ - ID: "rule_" + newUUID(), - Action: req.CreateRule.Action, - ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns), - ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns), - ModelConfigCUID: req.CreateRule.ModelConfigCUID, - AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs), - Description: req.CreateRule.Description, - Enabled: enabled, - } - if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return + newRule := store.Rule{ + ID: "rule_" + newUUID(), + Action: req.CreateRule.Action, + ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns), + ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns), + ModelConfigCUID: req.CreateRule.ModelConfigCUID, + AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs), + Description: req.CreateRule.Description, + Enabled: enabled, + } + if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } } - } - if err := h.svc.Approve(r.Context(), id); err != nil { + if err := h.svc.Approve(r.Context(), id); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } @@ -1522,22 +1521,22 @@ func (h *Handler) adminInvocationDetail(w http.ResponseWriter, r *http.Request) if req.CreateRule.Enabled != nil { enabled = *req.CreateRule.Enabled } - newRule := store.Rule{ - ID: "rule_" + newUUID(), - Action: req.CreateRule.Action, - ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns), - ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns), - ModelConfigCUID: req.CreateRule.ModelConfigCUID, - AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs), - Description: req.CreateRule.Description, - Enabled: enabled, - } - if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return + newRule := store.Rule{ + ID: "rule_" + newUUID(), + Action: req.CreateRule.Action, + ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns), + ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns), + ModelConfigCUID: req.CreateRule.ModelConfigCUID, + AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs), + Description: req.CreateRule.Description, + Enabled: enabled, + } + if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } } - } - if err := h.svc.Deny(r.Context(), id, req.Message); err != nil { + if err := h.svc.Deny(r.Context(), id, req.Message); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } diff --git a/internal/backend/client.go b/internal/backend/client.go index ee46c6b..7791f9a 100644 --- a/internal/backend/client.go +++ b/internal/backend/client.go @@ -345,14 +345,14 @@ func (c *Client) FetchCustomFields(ctx context.Context, orgCUID, primaryRecordTy // EvaluateRequest is sent to the VM backend to ask the LLM whether a tool // call should be approved or denied. type EvaluateRequest struct { - ModelConfigCUID string `json:"model_config_cuid"` - OrgCUID string `json:"org_cuid,omitempty"` - AgentVMCUID string `json:"agent_vm_cuid,omitempty"` + ModelConfigCUID string `json:"model_config_cuid"` + OrgCUID string `json:"org_cuid,omitempty"` + AgentVMCUID string `json:"agent_vm_cuid,omitempty"` CharterFieldKey string `json:"charter_field_key,omitempty"` - ServerName string `json:"server_name"` - ToolName string `json:"tool_name"` - ToolArgs map[string]any `json:"tool_args,omitempty"` - Context string `json:"context,omitempty"` + ServerName string `json:"server_name"` + ToolName string `json:"tool_name"` + ToolArgs map[string]any `json:"tool_args,omitempty"` + Context string `json:"context,omitempty"` } // EvaluateResponse is the result returned by the VM backend after LLM evaluation. diff --git a/internal/config/config.go b/internal/config/config.go index 7d647f4..6427b4f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,7 +9,6 @@ import ( "atryum/internal/auth" ) - type Config struct { Server ServerConfig `toml:"server"` Backend BackendConfig `toml:"backend"` diff --git a/internal/invocation/agent_id_test.go b/internal/invocation/agent_id_test.go index 7709e0b..06aa9c6 100644 --- a/internal/invocation/agent_id_test.go +++ b/internal/invocation/agent_id_test.go @@ -153,7 +153,6 @@ func TestInvokeWithoutAgentIDPersistsClientInfoFromRequest(t *testing.T) { } } - // External (non-MCP) callers like the amp-plugin example don't run behind // the auth middleware today, so there is no identity in context. They can // instead self-declare via the new `agent_id` field on ExternalSubmitRequest, diff --git a/internal/invocation/policy/types.go b/internal/invocation/policy/types.go index 28e5f61..bb41703 100644 --- a/internal/invocation/policy/types.go +++ b/internal/invocation/policy/types.go @@ -46,4 +46,3 @@ type Provider interface { DisplayName() string Evaluate(ctx context.Context, call CallContext) (Decision, error) } - diff --git a/internal/invocation/rules.go b/internal/invocation/rules.go index b5c8fe7..2722c03 100644 --- a/internal/invocation/rules.go +++ b/internal/invocation/rules.go @@ -85,4 +85,3 @@ func matchPatterns(patterns []string, value string) bool { } return false } - diff --git a/internal/invocation/service.go b/internal/invocation/service.go index c2d85c5..1d684e3 100644 --- a/internal/invocation/service.go +++ b/internal/invocation/service.go @@ -96,19 +96,19 @@ type SyncSettingsProvider interface { // EvaluateRequest mirrors backend.EvaluateRequest so the service package does // not import the backend package directly. type EvaluateRequest struct { - ModelConfigCUID string `json:"model_config_cuid"` - OrgCUID string `json:"org_cuid,omitempty"` - AgentVMCUID string `json:"agent_vm_cuid,omitempty"` - CharterFieldKey string `json:"charter_field_key,omitempty"` + ModelConfigCUID string `json:"model_config_cuid"` + OrgCUID string `json:"org_cuid,omitempty"` + AgentVMCUID string `json:"agent_vm_cuid,omitempty"` + CharterFieldKey string `json:"charter_field_key,omitempty"` // AtryumLLMConfigID references a local LLM config for native evaluation. // When set, the local evaluator is used instead of the VM backend. AtryumLLMConfigID string `json:"atryum_llm_config_id,omitempty"` // Charter is the agent's governing text sent to the local LLM judge. - Charter string `json:"charter,omitempty"` - ServerName string `json:"server_name"` - ToolName string `json:"tool_name"` - ToolArgs map[string]any `json:"tool_args,omitempty"` - Context string `json:"context,omitempty"` + Charter string `json:"charter,omitempty"` + ServerName string `json:"server_name"` + ToolName string `json:"tool_name"` + ToolArgs map[string]any `json:"tool_args,omitempty"` + Context string `json:"context,omitempty"` } // EvaluateResponse mirrors backend.EvaluateResponse. @@ -491,9 +491,9 @@ func (s *Service) runAIEvaluation(ctx context.Context, rule *ApprovalRule, serve OrgCUID: orgCUID, AgentVMCUID: agentVMCUID, CharterFieldKey: charterFieldKey, - ServerName: serverName, - ToolName: toolName, - ToolArgs: toolArgs, + ServerName: serverName, + ToolName: toolName, + ToolArgs: toolArgs, }) if err != nil { slog.Error("ai_evaluation: LLM evaluation failed; falling back to human_approval", diff --git a/internal/store/agent_sync_settings.go b/internal/store/agent_sync_settings.go index c3529bc..ad85f17 100644 --- a/internal/store/agent_sync_settings.go +++ b/internal/store/agent_sync_settings.go @@ -14,13 +14,13 @@ import ( // synchronisation from the ValidMind backend. All fields are empty strings // when the user has not yet configured sync (i.e. no row exists yet). type AgentSyncSettings struct { - OrgCUID string - AgentRecordTypeSlug string - CharterFieldKey string - SummaryModelConfigCUID string - SummaryAtryumLLMConfigID string - DefaultAgentVMCUID string - UpdatedAt time.Time + OrgCUID string + AgentRecordTypeSlug string + CharterFieldKey string + SummaryModelConfigCUID string + SummaryAtryumLLMConfigID string + DefaultAgentVMCUID string + UpdatedAt time.Time } var agentSyncSettingsColumns = []string{ diff --git a/internal/store/agents.go b/internal/store/agents.go index 1fdf35e..877db6f 100644 --- a/internal/store/agents.go +++ b/internal/store/agents.go @@ -265,7 +265,7 @@ func (r *AgentsRepo) DeleteAll(ctx context.Context) error { } // DeleteSynced removes only agent records that originated from a ValidMind -// sync (vm_organization_cuid != ''). Manually-created agents (empty +// sync (vm_organization_cuid != ”). Manually-created agents (empty // vm_organization_cuid) are preserved. func (r *AgentsRepo) DeleteSynced(ctx context.Context) error { query, args, err := r.sb.Delete("agents"). diff --git a/internal/store/migrations/011_agent_sync_settings.go b/internal/store/migrations/011_agent_sync_settings.go index 0b869f8..51ed763 100644 --- a/internal/store/migrations/011_agent_sync_settings.go +++ b/internal/store/migrations/011_agent_sync_settings.go @@ -5,7 +5,7 @@ func migration011() Definition { Version: 11, Name: "011_agent_sync_settings", Steps: []Step{ - RawDialect("create agent_sync_settings table", ` + RawDialect("create agent_sync_settings table", ` CREATE TABLE IF NOT EXISTS agent_sync_settings ( id INTEGER PRIMARY KEY DEFAULT 1, org_cuid TEXT NOT NULL DEFAULT '', diff --git a/internal/store/rules.go b/internal/store/rules.go index ebefce8..409175b 100644 --- a/internal/store/rules.go +++ b/internal/store/rules.go @@ -21,18 +21,18 @@ import ( // AgentCUIDs is a JSON-encoded list of Atryum agent CUIDs the rule applies to; // an empty slice means "match all agents". type Rule struct { - ID string - Action string - ServerPatterns []string - ToolPatterns []string - ModelConfigCUID string - AtryumLLMConfigID string - AgentCUIDs []string - Description string - Enabled bool - Order int - CreatedAt time.Time - UpdatedAt time.Time + ID string + Action string + ServerPatterns []string + ToolPatterns []string + ModelConfigCUID string + AtryumLLMConfigID string + AgentCUIDs []string + Description string + Enabled bool + Order int + CreatedAt time.Time + UpdatedAt time.Time } // RulesRepo provides CRUD and ordering operations for approval_rules. diff --git a/internal/store/rules_test.go b/internal/store/rules_test.go index 593ae25..494cc31 100644 --- a/internal/store/rules_test.go +++ b/internal/store/rules_test.go @@ -8,7 +8,7 @@ import ( // TestRulesRepo_CreateVMPathAIEvaluationRule is a regression test for the // VM-backend ai_evaluation rule path. Such rules set only ModelConfigCUID and // leave AtryumLLMConfigID empty. Migration 018 added atryum_llm_config_id as -// TEXT NOT NULL DEFAULT '', so writing SQL NULL (the old emptyToNil behavior) +// TEXT NOT NULL DEFAULT ”, so writing SQL NULL (the old emptyToNil behavior) // for an empty value violated the NOT NULL constraint and made VM-path rules // impossible to create. The empty string must be persisted directly instead. func TestRulesRepo_CreateVMPathAIEvaluationRule(t *testing.T) {