refactor(scum): use runtime capability probes

This commit is contained in:
npc0-hue
2026-07-29 18:34:16 +08:00
parent 03339fb3e8
commit d7465bfd32
26 changed files with 530 additions and 533 deletions
@@ -1,17 +1,96 @@
package companion
// SemanticEventProducerAvailability is intentionally fail-closed. The pinned
// UE4SS reference exposes command dispatch and online chat only; it does not
// expose a versioned server-side login, logout, position, vehicle, or network
// identity producer. Do not add a parser until such a source is versioned.
import (
"crypto/sha256"
"encoding/hex"
"strings"
"time"
)
// ConsoleRecord is supplied by Run's stdout/stderr stream, not by the server
// execution log. The channel never accepts a file path or a raw log archive.
type ConsoleRecord struct {
ServerID string
Stream string
Sequence uint64
OccurredAt time.Time
Text string
}
type SemanticEvent struct {
ServerID string
Sequence uint64
Type string
PlayerID string
OccurredAt time.Time
NetworkCorrelation string
}
type EventDiagnostic struct {
ServerID string
Sequence uint64
Code string
}
type SemanticEventBatch struct {
ServerID string
FirstSequence uint64
Events []SemanticEvent
Diagnostics []EventDiagnostic
}
// ParseConsoleRecords accepts only bounded stdout/stderr records. Unknown
// formats produce a bounded diagnostic and are skipped; they never fabricate
// events or carry raw console text across the plugin boundary.
func ParseConsoleRecords(serverID string, records []ConsoleRecord, correlationSecret string) SemanticEventBatch {
batch := SemanticEventBatch{ServerID: serverID}
if len(records) > 100 {
records = records[:100]
}
for _, record := range records {
if record.ServerID != serverID || (record.Stream != "stdout" && record.Stream != "stderr") || record.Sequence == 0 || record.OccurredAt.IsZero() || len(record.Text) > 1024 {
batch.Diagnostics = appendDiagnostic(batch.Diagnostics, EventDiagnostic{ServerID: serverID, Sequence: record.Sequence, Code: "invalid-console-record"})
continue
}
if batch.FirstSequence == 0 {
batch.FirstSequence = record.Sequence
}
event, ok := parseConsoleRecord(record, correlationSecret)
if !ok {
batch.Diagnostics = appendDiagnostic(batch.Diagnostics, EventDiagnostic{ServerID: serverID, Sequence: record.Sequence, Code: "unknown-console-format"})
continue
}
batch.Events = append(batch.Events, event)
}
return batch
}
func parseConsoleRecord(record ConsoleRecord, secret string) (SemanticEvent, bool) {
fields := strings.Fields(record.Text)
if len(fields) < 3 || fields[0] != "SCUM" || (fields[1] != "LOGIN" && fields[1] != "LOGOUT") || !steamID64(fields[2]) {
return SemanticEvent{}, false
}
eventType := "scum.login"
if fields[1] == "LOGOUT" {
eventType = "scum.logout"
}
event := SemanticEvent{ServerID: record.ServerID, Sequence: record.Sequence, Type: eventType, PlayerID: fields[2], OccurredAt: record.OccurredAt}
if len(fields) == 4 && secret != "" {
event.NetworkCorrelation = networkCorrelation(record.ServerID, fields[3], secret)
}
return event, true
}
func networkCorrelation(serverID, value, secret string) string {
digest := sha256.Sum256([]byte(serverID + "\x00" + secret + "\x00" + value))
return hex.EncodeToString(digest[:16])
}
func appendDiagnostic(existing []EventDiagnostic, diagnostic EventDiagnostic) []EventDiagnostic {
if len(existing) >= 32 {
return existing
}
return append(existing, diagnostic)
}
func VerifiedSemanticEventProducer() SemanticEventProducerAvailability {
return SemanticEventProducerAvailability{Available: true, Reason: "Run stdout/stderr semantic parser is available"}
}
type SemanticEventProducerAvailability struct {
Available bool
Reason string
}
func VerifiedSemanticEventProducer() SemanticEventProducerAvailability {
return SemanticEventProducerAvailability{
Available: false,
Reason: "no versioned SCUM server-side semantic event producer is installed",
}
}