feat(scum): gate live data on schema evidence

This commit is contained in:
npc0-hue
2026-08-11 13:28:36 +08:00
parent e5d26e5420
commit a58da47c60
18 changed files with 1265 additions and 3 deletions
+53
View File
@@ -399,6 +399,28 @@ type GameMapTrajectoryDeclarationBody struct {
RetentionSeconds int `json:"retentionSeconds"`
}
type SCUMSchemaProbeDeclarationBody struct {
Capability string `json:"capability"`
TargetKey string `json:"targetKey"`
Bounds SCUMSchemaProbeBoundsDTO `json:"bounds"`
}
type SCUMLiveDataCapabilityGateBody struct {
Capability string `json:"capability"`
Gate string `json:"gate"`
AdapterVersion string `json:"adapterVersion"`
RequiredSchemaFingerprint string `json:"requiredSchemaFingerprint,omitempty"`
RequiredAssetDigests []string `json:"requiredAssetDigests,omitempty"`
EvidenceStatus string `json:"evidenceStatus"`
SafeReason string `json:"safeReason"`
}
type SCUMLiveDataManifestBody struct {
SchemaVersion string `json:"schemaVersion"`
Probe SCUMSchemaProbeDeclarationBody `json:"probe"`
CapabilityGates []SCUMLiveDataCapabilityGateBody `json:"capabilityGates"`
}
type GamePluginManifestBody struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -419,6 +441,7 @@ type GamePluginManifestBody struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
SCUMLiveData SCUMLiveDataManifestBody `json:"scumLiveData,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
}
@@ -458,6 +481,7 @@ type GamePluginCreateRequest struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
SCUMLiveData SCUMLiveDataManifestBody `json:"scumLiveData,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
ValidationViolations []string `json:"validationViolations,omitempty"`
}
@@ -486,6 +510,7 @@ type GamePluginResponse struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesResponseBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
SCUMLiveData *SCUMLiveDataManifestBody `json:"scumLiveData,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
ValidationViolations []string `json:"validationViolations,omitempty"`
Status domain.GamePluginStatus `json:"status"`
@@ -1082,11 +1107,20 @@ func (request GamePluginManifestRegistrationRequest) ToDomain() domain.GamePlugi
RemoteAccess: request.Manifest.RemoteAccess.ToDomain(),
RuntimeProfiles: request.Manifest.RuntimeProfiles.ToDomain(),
GameClientBridge: request.Manifest.GameClientBridge.ToDomain(),
SCUMLiveData: request.Manifest.SCUMLiveData.ToDomain(),
MapTrajectories: mapTrajectoryDeclarationToDomain(request.Manifest.MapTrajectories),
},
}
}
func (body SCUMLiveDataManifestBody) ToDomain() domain.SCUMLiveDataManifest {
gates := make([]domain.SCUMLiveDataCapabilityGateDeclaration, len(body.CapabilityGates))
for index, gate := range body.CapabilityGates {
gates[index] = domain.SCUMLiveDataCapabilityGateDeclaration{Capability: domain.SCUMDataCapability(gate.Capability), Gate: domain.SCUMCapabilityGateState(gate.Gate), AdapterVersion: gate.AdapterVersion, RequiredSchemaFingerprint: gate.RequiredSchemaFingerprint, RequiredAssetDigests: domain.CopyStringSlice(gate.RequiredAssetDigests), EvidenceStatus: domain.SCUMCapabilityEvidenceStatus(gate.EvidenceStatus), SafeReason: gate.SafeReason}
}
return domain.SCUMLiveDataManifest{SchemaVersion: body.SchemaVersion, Probe: domain.SCUMSchemaProbeDeclaration{Capability: body.Probe.Capability, TargetKey: body.Probe.TargetKey, Bounds: scumProbeBoundsToDomain(body.Probe.Bounds)}, CapabilityGates: gates}
}
func pluginAssetFilesToDomain(files []PluginAssetFileBody) []domain.PluginAssetFile {
if files == nil {
return nil
@@ -1268,6 +1302,7 @@ func (request GamePluginCreateRequest) ToDomain() domain.GamePlugin {
RemoteAccess: request.RemoteAccess.ToDomain(),
RuntimeProfiles: request.RuntimeProfiles.ToDomain(),
GameClientBridge: request.GameClientBridge.ToDomain(),
SCUMLiveData: request.SCUMLiveData.ToDomain(),
MapTrajectories: mapTrajectoryDeclarationToDomain(request.MapTrajectories),
ValidationViolations: domain.CopyStringSlice(request.ValidationViolations),
}
@@ -1521,6 +1556,7 @@ func GamePluginFromDomain(plugin domain.GamePlugin) GamePluginResponse {
RemoteAccess: remoteAccessFromDomain(plugin.RemoteAccess),
RuntimeProfiles: runtimeProfilesFromDomain(plugin.RuntimeProfiles),
GameClientBridge: gameClientBridgeManifestFromDomain(plugin.GameClientBridge),
SCUMLiveData: scumLiveDataManifestPtrFromDomain(plugin.SCUMLiveData),
MapTrajectories: mapTrajectoryDeclarationFromDomain(plugin.MapTrajectories),
ValidationViolations: plugin.ValidationViolations,
Status: plugin.Status,
@@ -1625,6 +1661,23 @@ func productionLifecycleFromDomain(lifecycle domain.GamePluginProductionLifecycl
return GamePluginProductionLifecycleBody{Operations: lifecycle.Operations, DependencyPolicy: lifecycle.DependencyPolicy, ApprovalRequired: lifecycle.ApprovalRequired}
}
func scumLiveDataManifestFromDomain(value domain.SCUMLiveDataManifest) SCUMLiveDataManifestBody {
value = domain.CopySCUMLiveDataManifest(value)
gates := make([]SCUMLiveDataCapabilityGateBody, len(value.CapabilityGates))
for index, gate := range value.CapabilityGates {
gates[index] = SCUMLiveDataCapabilityGateBody{Capability: string(gate.Capability), Gate: string(gate.Gate), AdapterVersion: gate.AdapterVersion, RequiredSchemaFingerprint: gate.RequiredSchemaFingerprint, RequiredAssetDigests: domain.CopyStringSlice(gate.RequiredAssetDigests), EvidenceStatus: string(gate.EvidenceStatus), SafeReason: gate.SafeReason}
}
return SCUMLiveDataManifestBody{SchemaVersion: value.SchemaVersion, Probe: SCUMSchemaProbeDeclarationBody{Capability: value.Probe.Capability, TargetKey: value.Probe.TargetKey, Bounds: scumProbeBoundsFromDomain(value.Probe.Bounds)}, CapabilityGates: gates}
}
func scumLiveDataManifestPtrFromDomain(value domain.SCUMLiveDataManifest) *SCUMLiveDataManifestBody {
if value.SchemaVersion == "" && value.Probe.Capability == "" && len(value.CapabilityGates) == 0 {
return nil
}
body := scumLiveDataManifestFromDomain(value)
return &body
}
func gameClientBridgeManifestFromDomain(value domain.GameClientBridgeManifest) GameClientBridgeManifestBody {
value = domain.CopyGameClientBridgeManifest(value)
commands := make([]GameClientBridgeCommandDeclarationBody, len(value.Commands))
+140
View File
@@ -0,0 +1,140 @@
package dto
import (
"time"
"browser.local/platform/domain"
)
type SCUMBindingIdentityDTO struct {
ServerInstanceID string `json:"serverInstanceId"`
RunBindingID string `json:"runBindingId"`
RunEndpointID string `json:"runEndpointId"`
PluginID string `json:"pluginId"`
PluginVersion string `json:"pluginVersion"`
AdapterVersion string `json:"adapterVersion"`
GameVersion string `json:"gameVersion"`
DatabaseIdentity string `json:"databaseIdentity"`
}
type SCUMSchemaProbeBoundsDTO struct {
MaxObjects int `json:"maxObjects"`
MaxColumnsPerObject int `json:"maxColumnsPerObject"`
MaxIndexesPerObject int `json:"maxIndexesPerObject"`
MaxForeignKeys int `json:"maxForeignKeys"`
MaxCardinalityReads int `json:"maxCardinalityReads"`
MaxSampleRows int `json:"maxSampleRows"`
TimeoutMS int `json:"timeoutMs"`
MaxResultBytes int `json:"maxResultBytes"`
}
type SCUMSchemaProbeRequestDTO struct {
RequestID string `json:"requestId"`
JobID string `json:"jobId"`
Binding SCUMBindingIdentityDTO `json:"binding"`
Bounds SCUMSchemaProbeBoundsDTO `json:"bounds"`
RequestedAt time.Time `json:"requestedAt"`
}
type SCUMSafeErrorDTO struct {
Code string `json:"code"`
Message string `json:"message,omitempty"`
Retryable bool `json:"retryable"`
}
type SCUMSchemaColumnEvidenceDTO struct {
NameFingerprint string `json:"nameFingerprint"`
DeclaredType string `json:"declaredType"`
Nullable *bool `json:"nullable,omitempty"`
PrimaryKey bool `json:"primaryKey"`
Ordinal int `json:"ordinal"`
}
type SCUMSchemaIndexEvidenceDTO struct {
NameFingerprint string `json:"nameFingerprint"`
Unique bool `json:"unique"`
ColumnHashes []string `json:"columnHashes"`
}
type SCUMSchemaForeignKeyEvidenceDTO struct {
FromColumnHash string `json:"fromColumnHash"`
ToObjectHash string `json:"toObjectHash"`
ToColumnHash string `json:"toColumnHash"`
}
type SCUMSchemaObjectEvidenceDTO struct {
ObjectHash string `json:"objectHash"`
Kind string `json:"kind"`
NameFingerprint string `json:"nameFingerprint"`
DeclaredColumns []SCUMSchemaColumnEvidenceDTO `json:"declaredColumns"`
Indexes []SCUMSchemaIndexEvidenceDTO `json:"indexes"`
ForeignKeys []SCUMSchemaForeignKeyEvidenceDTO `json:"foreignKeys"`
ApproximateRows *int64 `json:"approximateRows,omitempty"`
SampleFingerprints []string `json:"sampleFingerprints,omitempty"`
}
type SCUMSchemaProbeResultDTO struct {
RequestID string `json:"requestId"`
JobID string `json:"jobId"`
Binding SCUMBindingIdentityDTO `json:"binding"`
Status string `json:"status"`
SchemaFingerprint string `json:"schemaFingerprint,omitempty"`
ObservedAt time.Time `json:"observedAt"`
ResultDigest string `json:"resultDigest,omitempty"`
Objects []SCUMSchemaObjectEvidenceDTO `json:"objects,omitempty"`
SafeError SCUMSafeErrorDTO `json:"safeError,omitempty"`
Limits SCUMSchemaProbeBoundsDTO `json:"limits"`
}
type SCUMCapabilityGateDTO struct {
Capability string `json:"capability"`
State string `json:"state"`
Enabled bool `json:"enabled"`
ReasonCode string `json:"reasonCode"`
Reason string `json:"reason"`
}
func SCUMSchemaProbeRequestToDomain(value SCUMSchemaProbeRequestDTO) domain.SCUMSchemaProbeRequest {
return domain.SCUMSchemaProbeRequest{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityToDomain(value.Binding), Bounds: scumProbeBoundsToDomain(value.Bounds), RequestedAt: value.RequestedAt}
}
func SCUMSchemaProbeResultFromDomain(value domain.SCUMSchemaProbeResult) SCUMSchemaProbeResultDTO {
value = domain.CopySCUMSchemaProbeResult(value)
objects := make([]SCUMSchemaObjectEvidenceDTO, len(value.Objects))
for index, object := range value.Objects {
columns := make([]SCUMSchemaColumnEvidenceDTO, len(object.DeclaredColumns))
for i, column := range object.DeclaredColumns {
columns[i] = SCUMSchemaColumnEvidenceDTO{NameFingerprint: column.NameFingerprint, DeclaredType: column.DeclaredType, Nullable: column.Nullable, PrimaryKey: column.PrimaryKey, Ordinal: column.Ordinal}
}
indexes := make([]SCUMSchemaIndexEvidenceDTO, len(object.Indexes))
for i, item := range object.Indexes {
indexes[i] = SCUMSchemaIndexEvidenceDTO{NameFingerprint: item.NameFingerprint, Unique: item.Unique, ColumnHashes: append([]string(nil), item.ColumnHashes...)}
}
foreignKeys := make([]SCUMSchemaForeignKeyEvidenceDTO, len(object.ForeignKeys))
for i, item := range object.ForeignKeys {
foreignKeys[i] = SCUMSchemaForeignKeyEvidenceDTO{FromColumnHash: item.FromColumnHash, ToObjectHash: item.ToObjectHash, ToColumnHash: item.ToColumnHash}
}
objects[index] = SCUMSchemaObjectEvidenceDTO{ObjectHash: object.ObjectHash, Kind: object.Kind, NameFingerprint: object.NameFingerprint, DeclaredColumns: columns, Indexes: indexes, ForeignKeys: foreignKeys, ApproximateRows: object.ApproximateRows, SampleFingerprints: append([]string(nil), object.SampleFingerprints...)}
}
return SCUMSchemaProbeResultDTO{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityFromDomain(value.Binding), Status: string(value.Status), SchemaFingerprint: value.SchemaFingerprint, ObservedAt: value.ObservedAt, ResultDigest: value.ResultDigest, Objects: objects, SafeError: SCUMSafeErrorDTO{Code: string(value.SafeError.Code), Message: value.SafeError.Message, Retryable: value.SafeError.Retryable}, Limits: scumProbeBoundsFromDomain(value.Limits)}
}
func SCUMCapabilityGateFromDomain(value domain.SCUMCapabilityGate) SCUMCapabilityGateDTO {
return SCUMCapabilityGateDTO{Capability: string(value.Capability), State: string(value.State), Enabled: value.Enabled, ReasonCode: string(value.ReasonCode), Reason: value.Reason}
}
func scumBindingIdentityToDomain(value SCUMBindingIdentityDTO) domain.SCUMBindingIdentity {
return domain.SCUMBindingIdentity{ServerInstanceID: value.ServerInstanceID, RunBindingID: value.RunBindingID, RunEndpointID: value.RunEndpointID, PluginID: value.PluginID, PluginVersion: value.PluginVersion, AdapterVersion: value.AdapterVersion, GameVersion: value.GameVersion, DatabaseIdentity: value.DatabaseIdentity}
}
func scumBindingIdentityFromDomain(value domain.SCUMBindingIdentity) SCUMBindingIdentityDTO {
return SCUMBindingIdentityDTO{ServerInstanceID: value.ServerInstanceID, RunBindingID: value.RunBindingID, RunEndpointID: value.RunEndpointID, PluginID: value.PluginID, PluginVersion: value.PluginVersion, AdapterVersion: value.AdapterVersion, GameVersion: value.GameVersion, DatabaseIdentity: value.DatabaseIdentity}
}
func scumProbeBoundsToDomain(value SCUMSchemaProbeBoundsDTO) domain.SCUMSchemaProbeBounds {
return domain.SCUMSchemaProbeBounds{MaxObjects: value.MaxObjects, MaxColumnsPerObject: value.MaxColumnsPerObject, MaxIndexesPerObject: value.MaxIndexesPerObject, MaxForeignKeys: value.MaxForeignKeys, MaxCardinalityReads: value.MaxCardinalityReads, MaxSampleRows: value.MaxSampleRows, TimeoutMS: value.TimeoutMS, MaxResultBytes: value.MaxResultBytes}
}
func scumProbeBoundsFromDomain(value domain.SCUMSchemaProbeBounds) SCUMSchemaProbeBoundsDTO {
return SCUMSchemaProbeBoundsDTO{MaxObjects: value.MaxObjects, MaxColumnsPerObject: value.MaxColumnsPerObject, MaxIndexesPerObject: value.MaxIndexesPerObject, MaxForeignKeys: value.MaxForeignKeys, MaxCardinalityReads: value.MaxCardinalityReads, MaxSampleRows: value.MaxSampleRows, TimeoutMS: value.TimeoutMS, MaxResultBytes: value.MaxResultBytes}
}