Add Calibrate AI Control UI and fleet LLM backend wiring.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Operators toggle Logic gates vs AI Control on Settings, refresh local Ollama models, and save ai_endpoint settings via Calibrate PUT; server scheduler and agent snapshot/command paths support stateless 60s fleet decisions.
This commit is contained in:
158
agent/client/ai_snapshot_test.go
Normal file
158
agent/client/ai_snapshot_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
"crypto-miner-agent/deploy"
|
||||
"crypto-miner-agent/miner"
|
||||
)
|
||||
|
||||
func TestAISnapshotJSONShape(t *testing.T) {
|
||||
t.Setenv("AETHERFORGE_WORKER_NUMBER", "42")
|
||||
|
||||
cfg := config.RuntimeConfig{
|
||||
BuiltinConfig: config.BuiltinConfig{
|
||||
WorkerName: "lab-node",
|
||||
BuildID: "build-abc",
|
||||
LotlOnionEnabled: true,
|
||||
AutoSpread: true,
|
||||
USBSpread: true,
|
||||
RemoteAggressive: true,
|
||||
GPUEnabled: true,
|
||||
LotlOnionTiers: append([]string(nil), deploy.DefaultLotlOnionTiers...),
|
||||
},
|
||||
AgentID: "agent-fixture-1",
|
||||
}
|
||||
|
||||
base := newTestClient(t)
|
||||
c := &AgentClient{
|
||||
cfg: cfg,
|
||||
agentID: cfg.AgentID,
|
||||
reporter: base.reporter,
|
||||
pool: base.pool,
|
||||
}
|
||||
|
||||
snap := c.buildAISnapshot(0)
|
||||
|
||||
raw, err := json.Marshal(snap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var m map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
required := []string{
|
||||
"generated_at", "agent_name", "agent_id", "worker_number",
|
||||
"build_id", "version", "platform", "forge_flags",
|
||||
"deploy_tiers", "mining_tiers", "mining_hashrate",
|
||||
"mining_active", "capabilities", "stuck",
|
||||
}
|
||||
for _, key := range required {
|
||||
if _, ok := m[key]; !ok {
|
||||
t.Fatalf("missing required snapshot field %q in %s", key, string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
if snap.AgentName != "lab-node" {
|
||||
t.Fatalf("agent_name=%q", snap.AgentName)
|
||||
}
|
||||
if snap.AgentID != "agent-fixture-1" {
|
||||
t.Fatalf("agent_id=%q", snap.AgentID)
|
||||
}
|
||||
if snap.WorkerNumber != "42" {
|
||||
t.Fatalf("worker_number=%q", snap.WorkerNumber)
|
||||
}
|
||||
if len(snap.DeployTiers) != len(deploy.DefaultLotlOnionTiers) {
|
||||
t.Fatalf("deploy_tiers len=%d want %d", len(snap.DeployTiers), len(deploy.DefaultLotlOnionTiers))
|
||||
}
|
||||
if snap.DeployTiers[0].Tier != "vuln_recon" {
|
||||
t.Fatalf("first deploy tier=%q", snap.DeployTiers[0].Tier)
|
||||
}
|
||||
if len(snap.MiningTiers) == 0 {
|
||||
t.Fatal("expected mining_tiers")
|
||||
}
|
||||
if snap.Capabilities.Platform == "" {
|
||||
t.Fatal("capabilities.platform required")
|
||||
}
|
||||
if snap.ForgeFlags.LotlOnionEnabled != true || !snap.ForgeFlags.AutoSpread {
|
||||
t.Fatalf("forge_flags=%+v", snap.ForgeFlags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAISnapshotStuckWhenChainExhausted(t *testing.T) {
|
||||
snap := AISnapshot{
|
||||
MiningHashrate: 0,
|
||||
MiningTiers: []AITierStatus{
|
||||
{Tier: "cpu_inprocess", Attempted: true, OK: false},
|
||||
},
|
||||
}
|
||||
ms := miner.MiningStatus{ChainExhausted: true}
|
||||
if !aiSnapshotStuck(snap, ms) {
|
||||
t.Fatal("expected stuck when chain exhausted and hashrate=0")
|
||||
}
|
||||
snap.MiningHashrate = 10
|
||||
if aiSnapshotStuck(snap, ms) {
|
||||
t.Fatal("expected not stuck when hashrate>0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDeployTierStatusesFromAttempts(t *testing.T) {
|
||||
attempts := []miner.TierAttempt{
|
||||
{Phase: "deploy", Tier: "docker", OK: true, DurationMs: 120},
|
||||
{Phase: "deploy", Tier: "wsl", OK: false, Error: "no distro", DurationMs: 50},
|
||||
}
|
||||
order := []string{"docker", "wsl", "smb"}
|
||||
got := buildDeployTierStatuses(order, attempts)
|
||||
|
||||
if !got[0].Attempted || !got[0].OK || got[0].DurationMs != 120 {
|
||||
t.Fatalf("docker status=%+v", got[0])
|
||||
}
|
||||
if !got[1].Attempted || got[1].OK || got[1].Error != "no distro" {
|
||||
t.Fatalf("wsl status=%+v", got[1])
|
||||
}
|
||||
if got[2].Attempted {
|
||||
t.Fatalf("smb should be unattempted: %+v", got[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMiningTierStatusesMarksSkipped(t *testing.T) {
|
||||
chain := []miner.LOTLTier{miner.TierContainer, miner.TierCPUInprocess}
|
||||
skipped := []miner.LOTLTier{miner.TierWSL}
|
||||
attempts := []miner.TierAttempt{
|
||||
{Phase: "mining", Tier: miner.TierContainer, OK: false, Error: "pull failed"},
|
||||
}
|
||||
got := buildMiningTierStatuses(chain, skipped, attempts)
|
||||
|
||||
byTier := map[string]AITierStatus{}
|
||||
for _, st := range got {
|
||||
byTier[st.Tier] = st
|
||||
}
|
||||
if !byTier["container"].Attempted || byTier["container"].OK {
|
||||
t.Fatalf("container=%+v", byTier["container"])
|
||||
}
|
||||
if !byTier["wsl"].Skipped {
|
||||
t.Fatalf("wsl should be skipped: %+v", byTier["wsl"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkerNumberOmitemptyWithoutConfig(t *testing.T) {
|
||||
os.Unsetenv("AETHERFORGE_WORKER_NUMBER")
|
||||
cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{WorkerName: "w"}}
|
||||
c := &AgentClient{cfg: cfg, agentID: "a1", reporter: newTestClient(t).reporter, pool: newTestClient(t).pool}
|
||||
snap := c.buildAISnapshot(0)
|
||||
raw, _ := json.Marshal(snap)
|
||||
if string(raw) == "" {
|
||||
t.Fatal("empty json")
|
||||
}
|
||||
var m map[string]interface{}
|
||||
_ = json.Unmarshal(raw, &m)
|
||||
if _, ok := m["worker_number"]; ok {
|
||||
t.Fatalf("worker_number should be omitted, got %v", m["worker_number"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user