Files
AetherForge/server/internal/db/oath_ledger.go
AetherForge 07fdb39b63
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add fleet registry removal with confirm modal and agent_removed WS.
Operators can remove machines from Crucible and dashboard rosters with honest messaging that deletion is registry-only; bulk select, oath ledger entries, and Go/Vitest coverage included.
2026-06-07 18:23:41 -07:00

176 lines
4.6 KiB
Go

package db
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"time"
)
// Oath action types — immutable accountability rows.
const (
OathSpreadTierEscalation = "spread_tier_escalation"
OathGraft = "graft"
OathForkMerge = "fork_merge"
OathStrainCardPlay = "strain_card_play"
OathCourtL4Decision = "court_l4_decision"
OathSpreadAttempt = "spread_attempt"
OathSpreadDiscoveredHost = "spread_discovered_host"
OathStrainHospice = "strain_hospice"
OathReconScan = "recon_scan"
OathAgentRemoved = "agent_removed"
)
// Oath outcomes.
const (
OathOutcomeSuccess = "success"
OathOutcomeFail = "fail"
OathOutcomePending = "pending"
)
// OathLedgerEntry is one immutable operator / AI council accountability row.
type OathLedgerEntry struct {
ID int64 `json:"id"`
Timestamp string `json:"timestamp"`
Actor string `json:"actor"`
ActionType string `json:"action_type"`
AgentID string `json:"agent_id"`
Strain string `json:"strain"`
WhyHash string `json:"why_hash"`
Outcome string `json:"outcome"`
PayloadJSON json.RawMessage `json:"payload_json"`
}
func (d *Database) ensureOathLedgerTable() error {
_, err := d.Exec(`CREATE TABLE IF NOT EXISTS oath_ledger (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
actor TEXT NOT NULL DEFAULT '',
action_type TEXT NOT NULL,
agent_id TEXT NOT NULL DEFAULT '',
strain TEXT NOT NULL DEFAULT '',
why_hash TEXT NOT NULL DEFAULT '',
outcome TEXT NOT NULL DEFAULT 'pending',
payload_json TEXT NOT NULL DEFAULT '{}'
)`)
if err != nil {
return err
}
_, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_oath_ledger_ts ON oath_ledger(timestamp)`)
_, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_oath_ledger_action ON oath_ledger(action_type)`)
return nil
}
// HashWhyJSON returns SHA256 hex of JSON-encoded source evidence (autopsy / court transcript).
func HashWhyJSON(source interface{}) string {
if source == nil {
return ""
}
raw, err := json.Marshal(source)
if err != nil {
raw = []byte("{}")
}
sum := sha256.Sum256(raw)
return hex.EncodeToString(sum[:])
}
func marshalOathPayload(payload interface{}) []byte {
if payload == nil {
return []byte("{}")
}
switch v := payload.(type) {
case json.RawMessage:
if len(v) == 0 {
return []byte("{}")
}
return v
case map[string]interface{}:
raw, err := json.Marshal(v)
if err != nil {
return []byte("{}")
}
return raw
default:
raw, err := json.Marshal(payload)
if err != nil {
return []byte("{}")
}
return raw
}
}
// InsertOathLedger appends one immutable accountability row.
func (d *Database) InsertOathLedger(actor, actionType, agentID, strain, whyHash, outcome string, payload interface{}) (*OathLedgerEntry, error) {
if d == nil {
return nil, nil
}
if err := d.ensureOathLedgerTable(); err != nil {
return nil, err
}
if outcome == "" {
outcome = OathOutcomePending
}
payloadJSON := marshalOathPayload(payload)
ts := time.Now().UTC()
res, err := d.Exec(
`INSERT INTO oath_ledger (timestamp, actor, action_type, agent_id, strain, why_hash, outcome, payload_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
ts, actor, actionType, agentID, strain, whyHash, outcome, string(payloadJSON),
)
if err != nil {
return nil, err
}
id, _ := res.LastInsertId()
return &OathLedgerEntry{
ID: id,
Timestamp: ts.Format(time.RFC3339),
Actor: actor,
ActionType: actionType,
AgentID: agentID,
Strain: strain,
WhyHash: whyHash,
Outcome: outcome,
PayloadJSON: json.RawMessage(payloadJSON),
}, nil
}
// ListOathLedger returns recent rows newest-first.
func (d *Database) ListOathLedger(limit int) ([]OathLedgerEntry, error) {
if d == nil {
return nil, nil
}
if err := d.ensureOathLedgerTable(); err != nil {
return nil, err
}
if limit <= 0 {
limit = 100
}
if limit > 500 {
limit = 500
}
rows, err := d.Query(
`SELECT id, timestamp, actor, action_type, agent_id, strain, why_hash, outcome, payload_json
FROM oath_ledger ORDER BY id DESC LIMIT ?`,
limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var out []OathLedgerEntry
for rows.Next() {
var e OathLedgerEntry
var ts time.Time
var payloadStr string
if err := rows.Scan(&e.ID, &ts, &e.Actor, &e.ActionType, &e.AgentID, &e.Strain, &e.WhyHash, &e.Outcome, &payloadStr); err != nil {
return nil, err
}
e.Timestamp = ts.UTC().Format(time.RFC3339)
if payloadStr != "" {
e.PayloadJSON = json.RawMessage(payloadStr)
}
out = append(out, e)
}
return out, rows.Err()
}