Merge pull request 'feat(agentic): agent trust model — security wall between non-aligned agents' (#149) from feat/agentic-trust-model into new

This commit is contained in:
Charon (snider-linux) 2026-02-12 20:06:50 +00:00
commit 4bc43939a6
4 changed files with 835 additions and 0 deletions

238
pkg/trust/policy.go Normal file
View file

@ -0,0 +1,238 @@
package trust
import (
"fmt"
"strings"
)
// Policy defines the access rules for a given trust tier.
type Policy struct {
// Tier is the trust level this policy applies to.
Tier Tier
// Allowed lists the capabilities granted at this tier.
Allowed []Capability
// RequiresApproval lists capabilities that need human/higher-tier approval.
RequiresApproval []Capability
// Denied lists explicitly denied capabilities.
Denied []Capability
}
// PolicyEngine evaluates capability requests against registered policies.
type PolicyEngine struct {
registry *Registry
policies map[Tier]*Policy
}
// Decision is the result of a policy evaluation.
type Decision int
const (
// Deny means the action is not permitted.
Deny Decision = iota
// Allow means the action is permitted.
Allow
// NeedsApproval means the action requires human or higher-tier approval.
NeedsApproval
)
// String returns the human-readable name of the decision.
func (d Decision) String() string {
switch d {
case Deny:
return "deny"
case Allow:
return "allow"
case NeedsApproval:
return "needs_approval"
default:
return fmt.Sprintf("unknown(%d)", int(d))
}
}
// EvalResult contains the outcome of a capability evaluation.
type EvalResult struct {
Decision Decision
Agent string
Cap Capability
Reason string
}
// NewPolicyEngine creates a policy engine with the given registry and default policies.
func NewPolicyEngine(registry *Registry) *PolicyEngine {
pe := &PolicyEngine{
registry: registry,
policies: make(map[Tier]*Policy),
}
pe.loadDefaults()
return pe
}
// Evaluate checks whether the named agent can perform the given capability.
// If the agent has scoped repos and the capability is repo-scoped, the repo
// parameter is checked against the agent's allowed repos.
func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string) EvalResult {
agent := pe.registry.Get(agentName)
if agent == nil {
return EvalResult{
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: "agent not registered",
}
}
policy, ok := pe.policies[agent.Tier]
if !ok {
return EvalResult{
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("no policy for tier %s", agent.Tier),
}
}
// Check explicit denials first.
for _, denied := range policy.Denied {
if denied == cap {
return EvalResult{
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s is denied for tier %s", cap, agent.Tier),
}
}
}
// Check if capability requires approval.
for _, approval := range policy.RequiresApproval {
if approval == cap {
return EvalResult{
Decision: NeedsApproval,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s requires approval for tier %s", cap, agent.Tier),
}
}
}
// Check if capability is allowed.
for _, allowed := range policy.Allowed {
if allowed == cap {
// For repo-scoped capabilities, verify repo access.
if isRepoScoped(cap) && len(agent.ScopedRepos) > 0 {
if !repoAllowed(agent.ScopedRepos, repo) {
return EvalResult{
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("agent %q does not have access to repo %q", agentName, repo),
}
}
}
return EvalResult{
Decision: Allow,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s allowed for tier %s", cap, agent.Tier),
}
}
}
return EvalResult{
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s not granted for tier %s", cap, agent.Tier),
}
}
// SetPolicy replaces the policy for a given tier.
func (pe *PolicyEngine) SetPolicy(p Policy) error {
if !p.Tier.Valid() {
return fmt.Errorf("trust.SetPolicy: invalid tier %d", p.Tier)
}
pe.policies[p.Tier] = &p
return nil
}
// GetPolicy returns the policy for a tier, or nil if none is set.
func (pe *PolicyEngine) GetPolicy(t Tier) *Policy {
return pe.policies[t]
}
// loadDefaults installs the default trust policies from the issue spec.
func (pe *PolicyEngine) loadDefaults() {
// Tier 3 — Full Trust
pe.policies[TierFull] = &Policy{
Tier: TierFull,
Allowed: []Capability{
CapPushRepo,
CapMergePR,
CapCreatePR,
CapCreateIssue,
CapCommentIssue,
CapReadSecrets,
CapRunPrivileged,
CapAccessWorkspace,
CapModifyFlows,
},
}
// Tier 2 — Verified
pe.policies[TierVerified] = &Policy{
Tier: TierVerified,
Allowed: []Capability{
CapPushRepo, // scoped to assigned repos
CapCreatePR, // can create, not merge
CapCreateIssue,
CapCommentIssue,
CapReadSecrets, // scoped to their repos
},
RequiresApproval: []Capability{
CapMergePR,
},
Denied: []Capability{
CapAccessWorkspace, // cannot access other agents' workspaces
CapModifyFlows,
CapRunPrivileged,
},
}
// Tier 1 — Untrusted
pe.policies[TierUntrusted] = &Policy{
Tier: TierUntrusted,
Allowed: []Capability{
CapCreatePR, // fork only, checked at enforcement layer
CapCommentIssue,
},
Denied: []Capability{
CapPushRepo,
CapMergePR,
CapCreateIssue,
CapReadSecrets,
CapRunPrivileged,
CapAccessWorkspace,
CapModifyFlows,
},
}
}
// isRepoScoped returns true if the capability is constrained by repo scope.
func isRepoScoped(cap Capability) bool {
return strings.HasPrefix(string(cap), "repo.") ||
strings.HasPrefix(string(cap), "pr.") ||
cap == CapReadSecrets
}
// repoAllowed checks if repo is in the agent's scoped list.
func repoAllowed(scoped []string, repo string) bool {
if repo == "" {
return false
}
for _, r := range scoped {
if r == repo {
return true
}
}
return false
}

268
pkg/trust/policy_test.go Normal file
View file

@ -0,0 +1,268 @@
package trust
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestEngine(t *testing.T) *PolicyEngine {
t.Helper()
r := NewRegistry()
require.NoError(t, r.Register(Agent{
Name: "Athena",
Tier: TierFull,
}))
require.NoError(t, r.Register(Agent{
Name: "Clotho",
Tier: TierVerified,
ScopedRepos: []string{"host-uk/core", "host-uk/docs"},
}))
require.NoError(t, r.Register(Agent{
Name: "BugSETI-001",
Tier: TierUntrusted,
}))
return NewPolicyEngine(r)
}
// --- Decision ---
func TestDecisionString_Good(t *testing.T) {
assert.Equal(t, "deny", Deny.String())
assert.Equal(t, "allow", Allow.String())
assert.Equal(t, "needs_approval", NeedsApproval.String())
}
func TestDecisionString_Bad_Unknown(t *testing.T) {
assert.Contains(t, Decision(99).String(), "unknown")
}
// --- Tier 3 (Full Trust) ---
func TestEvaluate_Good_Tier3CanDoAnything(t *testing.T) {
pe := newTestEngine(t)
caps := []Capability{
CapPushRepo, CapMergePR, CapCreatePR, CapCreateIssue,
CapCommentIssue, CapReadSecrets, CapRunPrivileged,
CapAccessWorkspace, CapModifyFlows,
}
for _, cap := range caps {
result := pe.Evaluate("Athena", cap, "")
assert.Equal(t, Allow, result.Decision, "Athena should be allowed %s", cap)
}
}
// --- Tier 2 (Verified) ---
func TestEvaluate_Good_Tier2CanCreatePR(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapCreatePR, "host-uk/core")
assert.Equal(t, Allow, result.Decision)
}
func TestEvaluate_Good_Tier2CanPushToScopedRepo(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapPushRepo, "host-uk/core")
assert.Equal(t, Allow, result.Decision)
}
func TestEvaluate_Good_Tier2NeedsApprovalToMerge(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapMergePR, "host-uk/core")
assert.Equal(t, NeedsApproval, result.Decision)
}
func TestEvaluate_Good_Tier2CanCreateIssue(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapCreateIssue, "")
assert.Equal(t, Allow, result.Decision)
}
func TestEvaluate_Bad_Tier2CannotAccessWorkspace(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapAccessWorkspace, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier2CannotModifyFlows(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapModifyFlows, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier2CannotRunPrivileged(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapRunPrivileged, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier2CannotPushToUnscopedRepo(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Clotho", CapPushRepo, "host-uk/secret-repo")
assert.Equal(t, Deny, result.Decision)
assert.Contains(t, result.Reason, "does not have access")
}
func TestEvaluate_Bad_Tier2RepoScopeEmptyRepo(t *testing.T) {
pe := newTestEngine(t)
// Push without specifying a repo should be denied for scoped agents.
result := pe.Evaluate("Clotho", CapPushRepo, "")
assert.Equal(t, Deny, result.Decision)
}
// --- Tier 1 (Untrusted) ---
func TestEvaluate_Good_Tier1CanCreatePR(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapCreatePR, "")
assert.Equal(t, Allow, result.Decision)
}
func TestEvaluate_Good_Tier1CanCommentIssue(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapCommentIssue, "")
assert.Equal(t, Allow, result.Decision)
}
func TestEvaluate_Bad_Tier1CannotPush(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapPushRepo, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier1CannotMerge(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapMergePR, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier1CannotCreateIssue(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapCreateIssue, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier1CannotReadSecrets(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapReadSecrets, "")
assert.Equal(t, Deny, result.Decision)
}
func TestEvaluate_Bad_Tier1CannotRunPrivileged(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("BugSETI-001", CapRunPrivileged, "")
assert.Equal(t, Deny, result.Decision)
}
// --- Edge cases ---
func TestEvaluate_Bad_UnknownAgent(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Unknown", CapCreatePR, "")
assert.Equal(t, Deny, result.Decision)
assert.Contains(t, result.Reason, "not registered")
}
func TestEvaluate_Good_EvalResultFields(t *testing.T) {
pe := newTestEngine(t)
result := pe.Evaluate("Athena", CapPushRepo, "")
assert.Equal(t, "Athena", result.Agent)
assert.Equal(t, CapPushRepo, result.Cap)
assert.NotEmpty(t, result.Reason)
}
// --- SetPolicy ---
func TestSetPolicy_Good(t *testing.T) {
pe := newTestEngine(t)
err := pe.SetPolicy(Policy{
Tier: TierVerified,
Allowed: []Capability{CapPushRepo, CapMergePR},
})
require.NoError(t, err)
// Verify the new policy is in effect.
result := pe.Evaluate("Clotho", CapMergePR, "host-uk/core")
assert.Equal(t, Allow, result.Decision)
}
func TestSetPolicy_Bad_InvalidTier(t *testing.T) {
pe := newTestEngine(t)
err := pe.SetPolicy(Policy{Tier: Tier(0)})
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid tier")
}
func TestGetPolicy_Good(t *testing.T) {
pe := newTestEngine(t)
p := pe.GetPolicy(TierFull)
require.NotNil(t, p)
assert.Equal(t, TierFull, p.Tier)
}
func TestGetPolicy_Bad_NotFound(t *testing.T) {
pe := newTestEngine(t)
assert.Nil(t, pe.GetPolicy(Tier(99)))
}
// --- isRepoScoped / repoAllowed helpers ---
func TestIsRepoScoped_Good(t *testing.T) {
assert.True(t, isRepoScoped(CapPushRepo))
assert.True(t, isRepoScoped(CapCreatePR))
assert.True(t, isRepoScoped(CapMergePR))
assert.True(t, isRepoScoped(CapReadSecrets))
}
func TestIsRepoScoped_Bad_NotScoped(t *testing.T) {
assert.False(t, isRepoScoped(CapRunPrivileged))
assert.False(t, isRepoScoped(CapAccessWorkspace))
assert.False(t, isRepoScoped(CapModifyFlows))
}
func TestRepoAllowed_Good(t *testing.T) {
scoped := []string{"host-uk/core", "host-uk/docs"}
assert.True(t, repoAllowed(scoped, "host-uk/core"))
assert.True(t, repoAllowed(scoped, "host-uk/docs"))
}
func TestRepoAllowed_Bad_NotInScope(t *testing.T) {
scoped := []string{"host-uk/core"}
assert.False(t, repoAllowed(scoped, "host-uk/secret"))
}
func TestRepoAllowed_Bad_EmptyRepo(t *testing.T) {
scoped := []string{"host-uk/core"}
assert.False(t, repoAllowed(scoped, ""))
}
func TestRepoAllowed_Bad_EmptyScope(t *testing.T) {
assert.False(t, repoAllowed(nil, "host-uk/core"))
assert.False(t, repoAllowed([]string{}, "host-uk/core"))
}
// --- Tier 3 ignores repo scoping ---
func TestEvaluate_Good_Tier3IgnoresRepoScope(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{
Name: "Virgil",
Tier: TierFull,
ScopedRepos: []string{}, // empty scope should not restrict Tier 3
}))
pe := NewPolicyEngine(r)
result := pe.Evaluate("Virgil", CapPushRepo, "any-repo")
assert.Equal(t, Allow, result.Decision)
}
// --- Default rate limits ---
func TestDefaultRateLimit(t *testing.T) {
assert.Equal(t, 10, defaultRateLimit(TierUntrusted))
assert.Equal(t, 60, defaultRateLimit(TierVerified))
assert.Equal(t, 0, defaultRateLimit(TierFull))
assert.Equal(t, 10, defaultRateLimit(Tier(99))) // unknown defaults to 10
}

165
pkg/trust/trust.go Normal file
View file

@ -0,0 +1,165 @@
// 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
}
}

164
pkg/trust/trust_test.go Normal file
View file

@ -0,0 +1,164 @@
package trust
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- Tier ---
func TestTierString_Good(t *testing.T) {
assert.Equal(t, "untrusted", TierUntrusted.String())
assert.Equal(t, "verified", TierVerified.String())
assert.Equal(t, "full", TierFull.String())
}
func TestTierString_Bad_Unknown(t *testing.T) {
assert.Contains(t, Tier(99).String(), "unknown")
}
func TestTierValid_Good(t *testing.T) {
assert.True(t, TierUntrusted.Valid())
assert.True(t, TierVerified.Valid())
assert.True(t, TierFull.Valid())
}
func TestTierValid_Bad(t *testing.T) {
assert.False(t, Tier(0).Valid())
assert.False(t, Tier(4).Valid())
assert.False(t, Tier(-1).Valid())
}
// --- Registry ---
func TestRegistryRegister_Good(t *testing.T) {
r := NewRegistry()
err := r.Register(Agent{Name: "Athena", Tier: TierFull})
require.NoError(t, err)
assert.Equal(t, 1, r.Len())
}
func TestRegistryRegister_Good_SetsDefaults(t *testing.T) {
r := NewRegistry()
err := r.Register(Agent{Name: "Athena", Tier: TierFull})
require.NoError(t, err)
a := r.Get("Athena")
require.NotNil(t, a)
assert.Equal(t, 0, a.RateLimit) // full trust = unlimited
assert.False(t, a.CreatedAt.IsZero())
}
func TestRegistryRegister_Good_TierDefaults(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "A", Tier: TierUntrusted}))
require.NoError(t, r.Register(Agent{Name: "B", Tier: TierVerified}))
require.NoError(t, r.Register(Agent{Name: "C", Tier: TierFull}))
assert.Equal(t, 10, r.Get("A").RateLimit)
assert.Equal(t, 60, r.Get("B").RateLimit)
assert.Equal(t, 0, r.Get("C").RateLimit)
}
func TestRegistryRegister_Good_PreservesExplicitRateLimit(t *testing.T) {
r := NewRegistry()
err := r.Register(Agent{Name: "Custom", Tier: TierVerified, RateLimit: 30})
require.NoError(t, err)
assert.Equal(t, 30, r.Get("Custom").RateLimit)
}
func TestRegistryRegister_Good_Update(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierVerified}))
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
assert.Equal(t, 1, r.Len())
assert.Equal(t, TierFull, r.Get("Athena").Tier)
}
func TestRegistryRegister_Bad_EmptyName(t *testing.T) {
r := NewRegistry()
err := r.Register(Agent{Tier: TierFull})
assert.Error(t, err)
assert.Contains(t, err.Error(), "name is required")
}
func TestRegistryRegister_Bad_InvalidTier(t *testing.T) {
r := NewRegistry()
err := r.Register(Agent{Name: "Bad", Tier: Tier(0)})
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid tier")
}
func TestRegistryGet_Good(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
a := r.Get("Athena")
require.NotNil(t, a)
assert.Equal(t, "Athena", a.Name)
}
func TestRegistryGet_Bad_NotFound(t *testing.T) {
r := NewRegistry()
assert.Nil(t, r.Get("nonexistent"))
}
func TestRegistryRemove_Good(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
assert.True(t, r.Remove("Athena"))
assert.Equal(t, 0, r.Len())
}
func TestRegistryRemove_Bad_NotFound(t *testing.T) {
r := NewRegistry()
assert.False(t, r.Remove("nonexistent"))
}
func TestRegistryList_Good(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
require.NoError(t, r.Register(Agent{Name: "Clotho", Tier: TierVerified}))
agents := r.List()
assert.Len(t, agents, 2)
names := make(map[string]bool)
for _, a := range agents {
names[a.Name] = true
}
assert.True(t, names["Athena"])
assert.True(t, names["Clotho"])
}
func TestRegistryList_Good_Empty(t *testing.T) {
r := NewRegistry()
assert.Empty(t, r.List())
}
func TestRegistryList_Good_Snapshot(t *testing.T) {
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
agents := r.List()
// Modifying the returned slice should not affect the registry.
agents[0].Tier = TierUntrusted
assert.Equal(t, TierFull, r.Get("Athena").Tier)
}
// --- Agent ---
func TestAgentTokenExpiry(t *testing.T) {
agent := Agent{
Name: "Test",
Tier: TierVerified,
TokenExpiresAt: time.Now().Add(-1 * time.Hour),
}
assert.True(t, time.Now().After(agent.TokenExpiresAt))
agent.TokenExpiresAt = time.Now().Add(1 * time.Hour)
assert.True(t, time.Now().Before(agent.TokenExpiresAt))
}