cli/pkg/trust/trust.go
Athena 7259d86208 feat(agentic): add agent trust model with tiered access control
Implements the security wall between non-aligned agents (issue #97).

Adds pkg/trust with:
- Three trust tiers: Full (Tier 3), Verified (Tier 2), Untrusted (Tier 1)
- Agent registry with mutex-protected concurrent access
- Policy engine with capability-based access control
- Repo-scoped permissions for Tier 2 agents
- Default policies matching the spec (rate limits, approval gates, denials)
- 49 tests covering all tiers, capabilities, edge cases, and helpers

Closes #97

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:53:52 +00:00

165 lines
4.3 KiB
Go

// Package trust implements an agent trust model with tiered access control.
//
// Agents are assigned trust tiers that determine their capabilities:
//
// - Tier 3 (Full Trust): Internal agents with full access (e.g., Athena, Virgil, Charon)
// - Tier 2 (Verified): Partner agents with scoped access (e.g., Clotho, Hypnos)
// - Tier 1 (Untrusted): External/community agents with minimal access
//
// The package provides a Registry for managing agent identities and a PolicyEngine
// for evaluating capability requests against trust policies.
package trust
import (
"fmt"
"sync"
"time"
)
// Tier represents an agent's trust level in the system.
type Tier int
const (
// TierUntrusted is for external/community agents with minimal access.
TierUntrusted Tier = 1
// TierVerified is for partner agents with scoped access.
TierVerified Tier = 2
// TierFull is for internal agents with full access.
TierFull Tier = 3
)
// String returns the human-readable name of the tier.
func (t Tier) String() string {
switch t {
case TierUntrusted:
return "untrusted"
case TierVerified:
return "verified"
case TierFull:
return "full"
default:
return fmt.Sprintf("unknown(%d)", int(t))
}
}
// Valid returns true if the tier is a recognised trust level.
func (t Tier) Valid() bool {
return t >= TierUntrusted && t <= TierFull
}
// Capability represents a specific action an agent can perform.
type Capability string
const (
CapPushRepo Capability = "repo.push"
CapMergePR Capability = "pr.merge"
CapCreatePR Capability = "pr.create"
CapCreateIssue Capability = "issue.create"
CapCommentIssue Capability = "issue.comment"
CapReadSecrets Capability = "secrets.read"
CapRunPrivileged Capability = "cmd.privileged"
CapAccessWorkspace Capability = "workspace.access"
CapModifyFlows Capability = "flows.modify"
)
// Agent represents an agent identity in the trust system.
type Agent struct {
// Name is the unique identifier for the agent (e.g., "Athena", "Clotho").
Name string
// Tier is the agent's trust level.
Tier Tier
// ScopedRepos limits repo access for Tier 2 agents. Empty means no repo access.
// Tier 3 agents ignore this field (they have access to all repos).
ScopedRepos []string
// RateLimit is the maximum requests per minute. 0 means unlimited.
RateLimit int
// TokenExpiresAt is when the agent's token expires.
TokenExpiresAt time.Time
// CreatedAt is when the agent was registered.
CreatedAt time.Time
}
// Registry manages agent identities and their trust tiers.
type Registry struct {
mu sync.RWMutex
agents map[string]*Agent
}
// NewRegistry creates an empty agent registry.
func NewRegistry() *Registry {
return &Registry{
agents: make(map[string]*Agent),
}
}
// Register adds or updates an agent in the registry.
// Returns an error if the agent name is empty or the tier is invalid.
func (r *Registry) Register(agent Agent) error {
if agent.Name == "" {
return fmt.Errorf("trust.Register: agent name is required")
}
if !agent.Tier.Valid() {
return fmt.Errorf("trust.Register: invalid tier %d for agent %q", agent.Tier, agent.Name)
}
if agent.CreatedAt.IsZero() {
agent.CreatedAt = time.Now()
}
if agent.RateLimit == 0 {
agent.RateLimit = defaultRateLimit(agent.Tier)
}
r.mu.Lock()
defer r.mu.Unlock()
r.agents[agent.Name] = &agent
return nil
}
// Get returns the agent with the given name, or nil if not found.
func (r *Registry) Get(name string) *Agent {
r.mu.RLock()
defer r.mu.RUnlock()
return r.agents[name]
}
// Remove deletes an agent from the registry.
func (r *Registry) Remove(name string) bool {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.agents[name]; !ok {
return false
}
delete(r.agents, name)
return true
}
// List returns all registered agents. The returned slice is a snapshot.
func (r *Registry) List() []Agent {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]Agent, 0, len(r.agents))
for _, a := range r.agents {
out = append(out, *a)
}
return out
}
// Len returns the number of registered agents.
func (r *Registry) Len() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.agents)
}
// defaultRateLimit returns the default rate limit for a given tier.
func defaultRateLimit(t Tier) int {
switch t {
case TierUntrusted:
return 10
case TierVerified:
return 60
case TierFull:
return 0 // unlimited
default:
return 10
}
}