Add onion contingency miner tree with orchestrator and Seer telemetry.
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
Single-host branch runner complements fallback chain under Fleet AI Control: persona ghost forks, onion_miner_log hops, server exhaustion splice from graft mining genome, Crucible depth badge, and hospice retire after max cycles.
This commit is contained in:
131
server/internal/api/contingency_bridge.go
Normal file
131
server/internal/api/contingency_bridge.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
fleetai "crypto-miner-server/internal/ai"
|
||||
"crypto-miner-server/internal/mining"
|
||||
"crypto-miner-server/internal/strategy"
|
||||
)
|
||||
|
||||
func (h *WSHub) contingencyOrchestrator() *mining.ContingencyOrchestrator {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
return h.contingencyOrch
|
||||
}
|
||||
|
||||
func (h *WSHub) initContingencyOrchestrator() {
|
||||
if h == nil || h.contingencyOrch != nil {
|
||||
return
|
||||
}
|
||||
policy := h.serverPolicySnapshot()
|
||||
h.contingencyOrch = mining.NewContingencyOrchestrator(mining.OrchestratorDeps{
|
||||
AIControl: policy.AIControlEnabled,
|
||||
Persona: policy.AIPersona,
|
||||
GraftFor: h.graftPolicyLookup,
|
||||
OnSeer: h.emitContingencySeerEvent,
|
||||
OnPush: h.pushContingencyBranchParams,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *WSHub) graftPolicyLookup(agentID string) (*strategy.GraftPolicy, bool) {
|
||||
if graft, ok := h.GraftPolicyForAgent(agentID); ok {
|
||||
return graft, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (h *WSHub) emitContingencySeerEvent(agentID string, payload map[string]interface{}) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
emitter := &HubSeerEmitter{Hub: h, DB: h.db}
|
||||
_ = emitter.EmitSeerEvent("onion_miner_log", agentID, payload)
|
||||
}
|
||||
|
||||
func (h *WSHub) pushContingencyBranchParams(agentID string, params mining.BranchParamsPush) error {
|
||||
body, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.SendToAgent(agentID, Message{Type: "contingency_branch_params", Payload: body})
|
||||
}
|
||||
|
||||
func (h *WSHub) handleOnionMinerLog(agentID string, raw json.RawMessage) {
|
||||
h.initContingencyOrchestrator()
|
||||
orch := h.contingencyOrchestrator()
|
||||
if orch == nil || !orch.Enabled() {
|
||||
return
|
||||
}
|
||||
hop, err := mining.ParseOnionHop(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
emitter := &HubSeerEmitter{Hub: h, DB: h.db}
|
||||
var payload map[string]interface{}
|
||||
_ = json.Unmarshal(raw, &payload)
|
||||
if payload == nil {
|
||||
payload = map[string]interface{}{}
|
||||
}
|
||||
payload["agent_id"] = agentID
|
||||
_ = emitter.EmitSeerEvent("onion_miner_log", agentID, payload)
|
||||
|
||||
push, ok := orch.ObserveHop(agentID, hop)
|
||||
if !ok {
|
||||
h.cacheContingencyDepth(agentID, hop.Depth)
|
||||
return
|
||||
}
|
||||
if err := h.pushContingencyBranchParams(agentID, push); err != nil {
|
||||
log.Printf("[contingency] push branch params to %s: %v", agentID[:min(8, len(agentID))], err)
|
||||
}
|
||||
h.cacheContingencyDepth(agentID, hop.Depth)
|
||||
}
|
||||
|
||||
func (h *WSHub) cacheContingencyDepth(agentID string, depth int) {
|
||||
if depth <= 0 {
|
||||
if orch := h.contingencyOrchestrator(); orch != nil {
|
||||
depth = orch.Depth(agentID)
|
||||
}
|
||||
}
|
||||
if depth <= 0 {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
if h.agentLiveTelemetry[agentID] == nil {
|
||||
h.agentLiveTelemetry[agentID] = make(map[string]interface{})
|
||||
}
|
||||
h.agentLiveTelemetry[agentID]["contingency_depth"] = depth
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *WSHub) attachContingencyPolicy(resp map[string]interface{}) {
|
||||
policy := h.serverPolicySnapshot()
|
||||
if !policy.AIControlEnabled {
|
||||
return
|
||||
}
|
||||
cp := fleetai.BuildContingencyPolicy(true, policy.AIPersona)
|
||||
if cp != nil {
|
||||
resp["contingency_policy"] = cp
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) contingencyDepthForAgent(agentID string) int {
|
||||
if orch := h.contingencyOrchestrator(); orch != nil {
|
||||
if d := orch.Depth(agentID); d > 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if tel, ok := h.agentLiveTelemetry[agentID]; ok {
|
||||
if v, ok := tel["contingency_depth"].(int); ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := tel["contingency_depth"].(float64); ok {
|
||||
return int(v)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
62
server/internal/api/contingency_bridge_test.go
Normal file
62
server/internal/api/contingency_bridge_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-server/internal/db"
|
||||
)
|
||||
|
||||
func TestAttachContingencyPolicyWhenAIControl(t *testing.T) {
|
||||
hub := NewWSHub(nil)
|
||||
hub.serverPolicy = ServerPolicy{AIControlEnabled: true, AIPersona: "silent"}
|
||||
|
||||
resp := map[string]interface{}{}
|
||||
hub.attachContingencyPolicy(resp)
|
||||
cp, ok := resp["contingency_policy"].(map[string]interface{})
|
||||
if !ok || cp["enabled"] != true {
|
||||
t.Fatalf("policy=%v", resp["contingency_policy"])
|
||||
}
|
||||
order, _ := cp["branch_order"].([]string)
|
||||
if len(order) == 0 {
|
||||
t.Fatalf("branch_order=%v", cp["branch_order"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleOnionMinerLogEmitsSeerAndDepth(t *testing.T) {
|
||||
database, err := db.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hub := NewWSHub(database)
|
||||
hub.serverPolicy = ServerPolicy{AIControlEnabled: true, AIPersona: "balanced"}
|
||||
|
||||
raw, _ := json.Marshal(map[string]interface{}{
|
||||
"method": "inprocess", "outcome": "won", "contingency_depth": 4,
|
||||
})
|
||||
hub.handleOnionMinerLog("agent-onion", raw)
|
||||
|
||||
if hub.contingencyDepthForAgent("agent-onion") != 4 {
|
||||
t.Fatalf("depth=%d", hub.contingencyDepthForAgent("agent-onion"))
|
||||
}
|
||||
events, _ := database.ListSeerEvents(10)
|
||||
found := false
|
||||
for _, ev := range events {
|
||||
if ev.EventType == "onion_miner_log" && ev.AgentID == "agent-onion" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("expected onion_miner_log seer event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContingencyPolicyAbsentWithoutAIControl(t *testing.T) {
|
||||
hub := NewWSHub(nil)
|
||||
hub.serverPolicy = ServerPolicy{AIControlEnabled: false}
|
||||
resp := map[string]interface{}{}
|
||||
hub.attachContingencyPolicy(resp)
|
||||
if _, ok := resp["contingency_policy"]; ok {
|
||||
t.Fatal("expected no contingency_policy without ai control")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user