Add L0-L4 security clearance for fleet commands and AI elevation.

Gate manual and AI commands by per-agent clearance, auto-elevate stuck hosts to L4 when AI mode allows, and surface clearance in Access Depth and LOTL timeline.
This commit is contained in:
AetherForge
2026-06-07 02:30:00 -07:00
parent dd612251d1
commit f89ba94cb7
21 changed files with 952 additions and 46 deletions

View File

@@ -5,6 +5,8 @@ import (
"sync"
"testing"
"time"
"crypto-miner-server/internal/clearance"
)
type mockSnap struct {
@@ -38,7 +40,7 @@ type mockStore struct {
rows []string
}
func (m *mockStore) InsertAIDecision(_, _, _, executed string) error {
func (m *mockStore) InsertAIDecision(_, _, _, executed string, _ *CourtDecisionMeta) error {
m.mu.Lock()
m.rows = append(m.rows, executed)
m.mu.Unlock()
@@ -59,6 +61,8 @@ func TestSchedulerExecutesRestartCommand(t *testing.T) {
&mockSnap{ids: []string{"agent-1"}, snap: AgentSnapshot{AgentID: "agent-1", Name: "host"}},
exec,
store,
nil,
nil,
)
sched.lastRun["agent-1"] = time.Now().Add(-2 * time.Minute)
sched.Tick()
@@ -84,9 +88,77 @@ func TestSchedulerNoOpWhenDisabled(t *testing.T) {
&mockSnap{ids: []string{"agent-1"}},
exec,
nil,
nil,
nil,
)
sched.Tick()
if len(exec.calls) != 0 {
t.Fatalf("expected no calls")
}
}
type mockElevator struct {
mu sync.Mutex
requests []struct {
agentID string
toLevel int
reason string
source string
}
level int
}
func (m *mockElevator) Level(string) int {
if m.level > 0 {
return m.level
}
return clearance.L1
}
func (m *mockElevator) RequestElevation(agentID string, toLevel int, reason, source string) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.requests = append(m.requests, struct {
agentID string
toLevel int
reason string
source string
}{agentID, toLevel, reason, source})
m.level = toLevel
return toLevel, nil
}
func TestSchedulerStuckHostTriggersL4Elevation(t *testing.T) {
old := DecideFunc
defer func() { DecideFunc = old }()
DecideFunc = func(_ context.Context, _, _, _, _ string) (string, error) {
return `{"commands":[{"type":"noop","args":{}}]}`, nil
}
elevator := &mockElevator{}
sched := NewScheduler(
&mockCfg{cfg: Config{Enabled: true, Endpoint: "http://test/v1", IntervalSec: 1, AutoElevateClearance: true}},
&mockSnap{
ids: []string{"stuck-1"},
snap: AgentSnapshot{
AgentID: "stuck-1", Name: "host", Stuck: true, FailedTierCount: 14,
},
},
&mockExec{},
&mockStore{},
nil,
elevator,
)
sched.lastRun["stuck-1"] = time.Now().Add(-2 * time.Minute)
sched.Tick()
elevator.mu.Lock()
defer elevator.mu.Unlock()
if len(elevator.requests) != 1 {
t.Fatalf("expected 1 elevation, got %+v", elevator.requests)
}
req := elevator.requests[0]
if req.toLevel != clearance.L4 || req.reason != "stuck host recovery" {
t.Fatalf("unexpected elevation: %+v", req)
}
}