Remove pre-1.0 audit and protected request scaffolding
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -14,7 +12,7 @@ func newGameClientBridgeService(t *testing.T) (*CoreService, *time.Time) {
|
||||
t.Helper()
|
||||
now := time.Date(2026, 7, 20, 10, 0, 0, 0, time.UTC)
|
||||
store := repo.NewMemoryStore()
|
||||
plugin := domain.GamePlugin{ID: "game.scum", RuntimeProfiles: domain.GamePluginRuntimeProfiles{ClientManagers: []domain.RuntimeClientManagerProfile{{Key: "scum-client", Health: domain.RuntimeClientManagerHealth{RequiredCapabilities: []string{gameClientBridgeCapability}}}}}, GameClientBridge: domain.GameClientBridgeManifest{Commands: []domain.GameClientBridgeCommandDeclaration{{Type: "announcement.send", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, TimeoutSeconds: 600, MaxPayloadBytes: 4096}}, Snapshots: []domain.GameClientBridgeSnapshotDeclaration{{Type: "players", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 100}}, {Type: "health", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 60}}, {Type: "companion.health", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 100}}}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000}}}
|
||||
plugin := domain.GamePlugin{ID: "game.scum", RuntimeProfiles: domain.GamePluginRuntimeProfiles{ClientManagers: []domain.RuntimeClientManagerProfile{{Key: "scum-client", Health: domain.RuntimeClientManagerHealth{RequiredCapabilities: []string{gameClientBridgeCapability}}}}}, GameClientBridge: domain.GameClientBridgeManifest{Commands: []domain.GameClientBridgeCommandDeclaration{{Type: "diagnostic.ping", ApprovalLevel: domain.GameClientBridgeApprovalLevelNone, TimeoutSeconds: 600, MaxPayloadBytes: 4096}}, Snapshots: []domain.GameClientBridgeSnapshotDeclaration{{Type: "players", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 100}}, {Type: "health", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 60}}, {Type: "companion.health", SchemaVersion: "1", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 100}}}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000}}}
|
||||
if err := store.GamePlugins().Create(plugin); err != nil {
|
||||
t.Fatalf("seed bridge plugin: %v", err)
|
||||
}
|
||||
@@ -23,7 +21,7 @@ func newGameClientBridgeService(t *testing.T) (*CoreService, *time.Time) {
|
||||
}
|
||||
|
||||
func bridgeQueueRequest(now time.Time, key string) domain.GameClientBridgeQueueRequest {
|
||||
return domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "announcement.send", Payload: map[string]any{"message": "hello"}, IdempotencyKey: key, Priority: 10, ExpiresAt: now.Add(5 * time.Minute)}
|
||||
return domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "diagnostic.ping", Payload: map[string]any{"message": "hello"}, IdempotencyKey: key, Priority: 10, ExpiresAt: now.Add(5 * time.Minute)}
|
||||
}
|
||||
|
||||
func bridgeComponent() gameClientBridgeComponentSession {
|
||||
@@ -45,8 +43,8 @@ func TestGameClientBridgeCommandLifecycleAndIdempotency(t *testing.T) {
|
||||
t.Fatalf("idempotency reuse: command=%#v err=%v", duplicate, err)
|
||||
}
|
||||
commands, _ := svc.store.GameClientBridgeCommands().List(domain.GameClientBridgeCommandFilter{})
|
||||
if len(commands) != 1 || len(command.AuditReferences) != 1 {
|
||||
t.Fatalf("expected one durable audited command: %#v", commands)
|
||||
if len(commands) != 1 {
|
||||
t.Fatalf("expected one durable command: %#v", commands)
|
||||
}
|
||||
|
||||
component := bridgeComponent()
|
||||
@@ -71,12 +69,8 @@ func TestGameClientBridgeCommandLifecycleAndIdempotency(t *testing.T) {
|
||||
if err != nil || completed.State != domain.GameClientBridgeCommandSucceeded || completed.Result.Status != domain.GameClientBridgeResultSucceeded || completed.CompletedAt.IsZero() {
|
||||
t.Fatalf("complete bridge command: %#v err=%v", completed, err)
|
||||
}
|
||||
if len(completed.AuditReferences) < 3 {
|
||||
t.Fatalf("expected queue, claim, and result audit references: %#v", completed.AuditReferences)
|
||||
}
|
||||
auditReferenceCount := len(completed.AuditReferences)
|
||||
replayed, err := svc.completeGameClientBridgeCommand(component, resultRequest)
|
||||
if err != nil || replayed.ID != completed.ID || replayed.State != completed.State || !replayed.CompletedAt.Equal(completed.CompletedAt) || len(replayed.AuditReferences) != auditReferenceCount {
|
||||
if err != nil || replayed.ID != completed.ID || replayed.State != completed.State || !replayed.CompletedAt.Equal(completed.CompletedAt) {
|
||||
t.Fatalf("exact terminal result retry was not idempotent: replayed=%#v err=%v", replayed, err)
|
||||
}
|
||||
if _, err := svc.completeGameClientBridgeCommand(component, domain.GameClientBridgeResultRequest{SessionToken: "session-token", CommandID: command.ID, FencingToken: 1, Status: domain.GameClientBridgeResultFailed, Summary: "conflict"}); err == nil {
|
||||
@@ -84,111 +78,6 @@ func TestGameClientBridgeCommandLifecycleAndIdempotency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedGameClientBridgeRequestIsScopedAndRedacted(t *testing.T) {
|
||||
svc, clock := newGameClientBridgeService(t)
|
||||
plugin, err := svc.store.GamePlugins().Get("game.scum")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plugin.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "database", Kind: "sqlite", TargetKey: "database", Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}}}
|
||||
plugin.GameClientBridge.Commands = append(plugin.GameClientBridge.Commands, domain.GameClientBridgeCommandDeclaration{Type: "database.request", ApprovalLevel: domain.GameClientBridgeApprovalLevelPlatformAdmin, TimeoutSeconds: 60, MaxPayloadBytes: 4096, ProtectedRequest: &domain.GameClientBridgeProtectedRequestDeclaration{Kind: "sql", TransportKey: "database", TargetKey: "database", TextField: "requestText", MaxTextBytes: 1024}})
|
||||
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
text := "UPDATE players SET rank = 2 WHERE id = 7"
|
||||
request := domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "database.request", Payload: map[string]any{"requestText": text}, IdempotencyKey: "protected-1", ExpiresAt: clock.Add(time.Minute)}
|
||||
command, err := svc.queueGameClientBridgeCommand("user-1", request)
|
||||
if err != nil {
|
||||
t.Fatalf("queue protected request: %v", err)
|
||||
}
|
||||
if command.ApprovalState != domain.GameClientBridgeApprovalPending {
|
||||
t.Fatalf("protected request bypassed approval: %#v", command)
|
||||
}
|
||||
if _, err := svc.queueGameClientBridgeCommand("user-1", domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "database.request", Payload: map[string]any{"requestText": text, "unexpected": true}, IdempotencyKey: "protected-extra", ExpiresAt: clock.Add(time.Minute)}); err == nil {
|
||||
t.Fatal("protected request accepted undeclared payload field")
|
||||
}
|
||||
events, err := svc.store.AuditEvents().List(domain.AuditEventFilter{ResourceID: command.ID})
|
||||
if err != nil || len(events) != 1 {
|
||||
t.Fatalf("protected request audit: events=%#v err=%v", events, err)
|
||||
}
|
||||
if strings.Contains(events[0].Summary, text) || !strings.Contains(events[0].Summary, "text=redacted") {
|
||||
t.Fatalf("audit leaked protected request: %#v", events[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedGameClientBridgeRequestDispatchesOneTimeRunInput(t *testing.T) {
|
||||
svc, clock := newGameClientBridgeService(t)
|
||||
plugin, err := svc.store.GamePlugins().Get("game.scum")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plugin.RequiredRunCapabilities = []string{domain.JobCapabilityRemoteRunProtectedSQL}
|
||||
plugin.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}}}
|
||||
plugin.GameClientBridge.Commands = append(plugin.GameClientBridge.Commands, domain.GameClientBridgeCommandDeclaration{Type: "database.request", ApprovalLevel: domain.GameClientBridgeApprovalLevelPlatformAdmin, TimeoutSeconds: 120, MaxPayloadBytes: 4096, ProtectedRequest: &domain.GameClientBridgeProtectedRequestDeclaration{Kind: "sql", TransportKey: "scum-database", TargetKey: "scum-database", TextField: "requestText", MaxTextBytes: 1024}})
|
||||
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := svc.store.Users().Create(domain.User{ID: "platform-admin", Email: "admin@example.test", Roles: []string{"platform-admin"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := svc.store.ServerInstances().Create(domain.ServerInstance{ID: "server-1", PluginID: plugin.ID, RunEndpointID: "run-local", Name: "Protected Bridge", State: domain.ServerInstanceStateRunning}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hello := validRunControlHello()
|
||||
hello.CapabilityReport.Capabilities = []string{domain.JobCapabilityRemoteRunProtectedSQL}
|
||||
hello.CapabilityReport.Fingerprint = "protected-request-capabilities"
|
||||
run, err := svc.RegisterRunHello(hello)
|
||||
if err != nil {
|
||||
t.Fatalf("register Run: %v", err)
|
||||
}
|
||||
|
||||
text := "SELECT player_id, position FROM players WHERE player_id = 7"
|
||||
command, err := svc.queueGameClientBridgeCommand("platform-admin", domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: plugin.ID, ProfileKey: "scum-client", CommandType: "database.request", Payload: map[string]any{"requestText": text}, IdempotencyKey: "protected-run-1", ExpiresAt: clock.Add(time.Minute)})
|
||||
if err != nil {
|
||||
t.Fatalf("queue protected request: %v", err)
|
||||
}
|
||||
if command.RunJobID == "" || command.Payload["requestText"] != "redacted" || command.ApprovalState != domain.GameClientBridgeApprovalApproved {
|
||||
t.Fatalf("protected command was not redacted and dispatched: %#v", command)
|
||||
}
|
||||
commandJSON, _ := json.Marshal(command)
|
||||
if strings.Contains(string(commandJSON), text) {
|
||||
t.Fatalf("protected bridge command persisted request text: %s", commandJSON)
|
||||
}
|
||||
if claimed, err := svc.claimGameClientBridgeCommands(bridgeComponent(), 10); err != nil || len(claimed) != 0 {
|
||||
t.Fatalf("protected request must not be exposed to the Companion: commands=%#v err=%v", claimed, err)
|
||||
}
|
||||
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: run.SessionToken, Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}, Capacity: domain.RunCapacity{MaxJobs: 1}})
|
||||
if err != nil || !claim.HasJob || claim.Job == nil || claim.Job.JobID != command.RunJobID || claim.Job.FencingToken == 0 {
|
||||
t.Fatalf("claim protected Run job: claim=%#v err=%v", claim, err)
|
||||
}
|
||||
assignmentJSON, _ := json.Marshal(claim.Job)
|
||||
if strings.Contains(string(assignmentJSON), text) {
|
||||
t.Fatalf("Run assignment exposed protected request text: %s", assignmentJSON)
|
||||
}
|
||||
ack, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: run.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "accepted"})
|
||||
if err != nil || !ack.Accepted {
|
||||
t.Fatalf("ack protected Run job: ack=%#v err=%v", ack, err)
|
||||
}
|
||||
if _, err := svc.GetProtectedRequestExecutionInput(domain.ProtectedRequestExecutionInputRequest{RunEndpointID: "run-local", SessionToken: run.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, FencingToken: claim.Job.FencingToken + 1}); err == nil {
|
||||
t.Fatal("expected fencing mismatch rejection")
|
||||
}
|
||||
input, err := svc.GetProtectedRequestExecutionInput(domain.ProtectedRequestExecutionInputRequest{RunEndpointID: "run-local", SessionToken: run.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, FencingToken: claim.Job.FencingToken})
|
||||
if err != nil || input.RequestText != text || input.Kind != "sql" || input.TransportKey != "scum-database" || !input.Authorized {
|
||||
t.Fatalf("read protected Run input: input=%#v err=%v", input, err)
|
||||
}
|
||||
if _, err := svc.GetProtectedRequestExecutionInput(domain.ProtectedRequestExecutionInputRequest{RunEndpointID: "run-local", SessionToken: run.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, FencingToken: claim.Job.FencingToken}); err == nil {
|
||||
t.Fatal("expected one-time protected input rejection")
|
||||
}
|
||||
if _, err := svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: "run-local", SessionToken: run.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, State: domain.JobStateFailed, Progress: domain.RunJobProgressReport{Percent: 100, Message: "unknown request"}, ErrorCode: "protected_request_unknown", ExecutionResult: domain.JobExecutionResult{Kind: "protected.sql.unknown", AuditSummary: "protected request outcome is unknown"}}); err != nil {
|
||||
t.Fatalf("complete protected Run job: %v", err)
|
||||
}
|
||||
completed, err := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if err != nil || completed.State != domain.GameClientBridgeCommandUnknown || completed.Result.Status != domain.GameClientBridgeResultUnknown || strings.Contains(completed.Result.Summary, text) {
|
||||
t.Fatalf("project protected Run result: command=%#v err=%v", completed, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameClientBridgeIdempotencyScopeIsAppliedByService(t *testing.T) {
|
||||
svc, clock := newGameClientBridgeService(t)
|
||||
request := bridgeQueueRequest(*clock, "scope-key")
|
||||
@@ -266,7 +155,7 @@ func TestGameClientBridgePendingCommandExpiresBeforeFirstClaim(t *testing.T) {
|
||||
t.Fatalf("expired pending command was claimable: %#v err=%v", claimed, err)
|
||||
}
|
||||
expired, err := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() || expired.Claim.FencingToken != 0 || len(expired.AuditReferences) != 2 {
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() || expired.Claim.FencingToken != 0 {
|
||||
t.Fatalf("first claim did not persist pending command expiry: %#v err=%v", expired, err)
|
||||
}
|
||||
}
|
||||
@@ -293,7 +182,7 @@ func TestGameClientBridgeExpiredLeaseRejectsMutationsBeforeReclaim(t *testing.T)
|
||||
}
|
||||
for _, command := range claimed {
|
||||
protected, getErr := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if getErr != nil || protected.State != domain.GameClientBridgeCommandClaimed || !protected.Claim.AcknowledgedAt.IsZero() || protected.Result.Status != "" || !protected.CompletedAt.IsZero() || protected.Claim.FencingToken != command.Claim.FencingToken || len(protected.AuditReferences) != 2 {
|
||||
if getErr != nil || protected.State != domain.GameClientBridgeCommandClaimed || !protected.Claim.AcknowledgedAt.IsZero() || protected.Result.Status != "" || !protected.CompletedAt.IsZero() || protected.Claim.FencingToken != command.Claim.FencingToken {
|
||||
t.Fatalf("expired lease mutation changed protected command: %#v err=%v", protected, getErr)
|
||||
}
|
||||
}
|
||||
@@ -341,8 +230,8 @@ func TestGameClientBridgeClaimMutationsExpireAtCommandDeadline(t *testing.T) {
|
||||
}
|
||||
for _, command := range commands {
|
||||
expired, err := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() || len(expired.AuditReferences) < 3 {
|
||||
t.Fatalf("deadline mutation did not persist audited expiry: %#v err=%v", expired, err)
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() {
|
||||
t.Fatalf("deadline mutation did not persist expiry: %#v err=%v", expired, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,11 +249,11 @@ func TestGameClientBridgeFailedResultIsPersisted(t *testing.T) {
|
||||
}
|
||||
request := domain.GameClientBridgeResultRequest{SessionToken: "session-token", CommandID: command.ID, FencingToken: claimed[0].Claim.FencingToken, Status: domain.GameClientBridgeResultFailed, Summary: "game window unavailable", Payload: map[string]any{"retryable": true}}
|
||||
failed, err := svc.completeGameClientBridgeCommand(component, request)
|
||||
if err != nil || failed.State != domain.GameClientBridgeCommandFailed || failed.Result.Status != domain.GameClientBridgeResultFailed || failed.Result.Summary != request.Summary || failed.Result.CompletedBy != component.Session.ID || failed.CompletedAt.IsZero() || len(failed.AuditReferences) != 3 {
|
||||
if err != nil || failed.State != domain.GameClientBridgeCommandFailed || failed.Result.Status != domain.GameClientBridgeResultFailed || failed.Result.Summary != request.Summary || failed.Result.CompletedBy != component.Session.ID || failed.CompletedAt.IsZero() {
|
||||
t.Fatalf("record failed result: %#v err=%v", failed, err)
|
||||
}
|
||||
persisted, err := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if err != nil || persisted.State != domain.GameClientBridgeCommandFailed || persisted.Result.Status != domain.GameClientBridgeResultFailed || persisted.Result.Payload["retryable"] != true || !persisted.CompletedAt.Equal(failed.CompletedAt) || len(persisted.AuditReferences) != len(failed.AuditReferences) {
|
||||
if err != nil || persisted.State != domain.GameClientBridgeCommandFailed || persisted.Result.Status != domain.GameClientBridgeResultFailed || persisted.Result.Payload["retryable"] != true || !persisted.CompletedAt.Equal(failed.CompletedAt) {
|
||||
t.Fatalf("failed result was not persisted: %#v err=%v", persisted, err)
|
||||
}
|
||||
}
|
||||
@@ -393,8 +282,8 @@ func TestGameClientBridgeOperatorCancellationExpiresAtCommandDeadline(t *testing
|
||||
t.Fatal("expected cancellation at command deadline to be rejected")
|
||||
}
|
||||
expired, err := svc.store.GameClientBridgeCommands().Get(command.ID)
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() || expired.Cancellation.RequestedBy != "" || len(expired.AuditReferences) != 2 {
|
||||
t.Fatalf("deadline cancellation did not preserve audited expiry: %#v err=%v", expired, err)
|
||||
if err != nil || expired.State != domain.GameClientBridgeCommandExpired || expired.CompletedAt.IsZero() || expired.Cancellation.RequestedBy != "" {
|
||||
t.Fatalf("deadline cancellation did not preserve expiry: %#v err=%v", expired, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,9 +313,8 @@ func TestGameClientBridgeOperatorCancellationRejectsLateSuccess(t *testing.T) {
|
||||
if err != nil || cancelled.State != domain.GameClientBridgeCommandCancelled || cancelled.Cancellation.RequestedBy != user.ID {
|
||||
t.Fatalf("cancel bridge command: %#v err=%v", cancelled, err)
|
||||
}
|
||||
auditReferenceCount := len(cancelled.AuditReferences)
|
||||
repeated, err := svc.CancelGameClientBridgeCommandForSession(auth.SessionID, domain.GameClientBridgeCancelRequest{CommandID: command.ID, Reason: "operator requested"})
|
||||
if err != nil || repeated.State != domain.GameClientBridgeCommandCancelled || !repeated.Cancellation.CancelledAt.Equal(cancelled.Cancellation.CancelledAt) || len(repeated.AuditReferences) != auditReferenceCount {
|
||||
if err != nil || repeated.State != domain.GameClientBridgeCommandCancelled || !repeated.Cancellation.CancelledAt.Equal(cancelled.Cancellation.CancelledAt) {
|
||||
t.Fatalf("repeated cancellation was not idempotent: %#v err=%v", repeated, err)
|
||||
}
|
||||
remaining, err := svc.claimGameClientBridgeCommands(component, 1)
|
||||
|
||||
Reference in New Issue
Block a user