feat(platform): forward protected requests to run

This commit is contained in:
npc0-hue
2026-07-30 09:58:41 +08:00
parent 99be8f0f3a
commit 1e004dc9ec
16 changed files with 484 additions and 7 deletions
+26
View File
@@ -302,6 +302,9 @@ func (svc *CoreService) queueGameClientBridgeCommand(requesterID string, request
if err := validateProtectedGameClientBridgePayload(declaration.ProtectedRequest, request.Payload); err != nil {
return domain.GameClientBridgeCommand{}, err
}
if declaration.ProtectedRequest != nil && declaration.TimeoutSeconds > protectedRequestMaxTimeoutSeconds {
return domain.GameClientBridgeCommand{}, validationError("protected bridge request timeout exceeds Run policy")
}
existing, err := svc.store.GameClientBridgeCommands().GetByIdempotency(request.ServerInstanceID, requesterID, request.CommandType, request.IdempotencyKey)
if err == nil {
@@ -340,6 +343,12 @@ func (svc *CoreService) queueGameClientBridgeCommand(requesterID string, request
CreatedAt: stamp,
UpdatedAt: stamp,
}
if declaration.ProtectedRequest != nil {
command.Payload = redactedProtectedRequestPayload(declaration.ProtectedRequest)
if approvalState == domain.GameClientBridgeApprovalApproved {
command.RunJobID = jobIDFromParts("job-protected-request", command.ServerInstanceID, command.ID)
}
}
summary := "queued declared game client bridge command"
if declaration.ProtectedRequest != nil {
summary = protectedGameClientBridgeAuditSummary(declaration.ProtectedRequest, request.Payload)
@@ -352,6 +361,14 @@ func (svc *CoreService) queueGameClientBridgeCommand(requesterID string, request
if err := svc.store.GameClientBridgeCommands().Create(command); err != nil {
return domain.GameClientBridgeCommand{}, err
}
if declaration.ProtectedRequest != nil && command.RunJobID != "" {
if err := svc.dispatchProtectedRequest(command, declaration, request.Payload); err != nil {
if deleteErr := svc.store.GameClientBridgeCommands().Delete(command.ID); deleteErr != nil {
return domain.GameClientBridgeCommand{}, deleteErr
}
return domain.GameClientBridgeCommand{}, err
}
}
return domain.CopyGameClientBridgeCommand(command), nil
}
@@ -386,6 +403,9 @@ func (svc *CoreService) claimGameClientBridgeCommands(component gameClientBridge
if len(claimed) == limit {
break
}
if command.RunJobID != "" {
continue
}
if command.ApprovalState != domain.GameClientBridgeApprovalNotRequired && command.ApprovalState != domain.GameClientBridgeApprovalApproved {
continue
}
@@ -474,6 +494,9 @@ func (svc *CoreService) completeGameClientBridgeCommand(component gameClientBrid
if err := svc.store.GameClientBridgeCommands().Update(command); err != nil {
return domain.GameClientBridgeCommand{}, err
}
if command.RunJobID != "" {
svc.protectedRequests.Delete(command.RunJobID)
}
return domain.CopyGameClientBridgeCommand(command), nil
}
@@ -524,6 +547,9 @@ func (svc *CoreService) CancelGameClientBridgeCommandForSession(sessionID string
if err := svc.store.GameClientBridgeCommands().Update(command); err != nil {
return domain.GameClientBridgeCommand{}, err
}
if command.RunJobID != "" {
svc.protectedRequests.Delete(command.RunJobID)
}
return domain.CopyGameClientBridgeCommand(command), nil
}
@@ -1,6 +1,7 @@
package service
import (
"encoding/json"
"strings"
"testing"
"time"
@@ -115,6 +116,79 @@ func TestProtectedGameClientBridgeRequestIsScopedAndRedacted(t *testing.T) {
}
}
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")
+8
View File
@@ -263,6 +263,9 @@ func (svc *CoreService) CompleteRunJob(result domain.RunJobResult) (domain.RunJo
if err := svc.updateScheduledJob(job); err != nil {
return domain.RunJobResultResult{}, err
}
if err := svc.projectProtectedRequestJobResult(job, result, stamp); err != nil {
return domain.RunJobResultResult{}, err
}
if err := svc.projectLifecycleJobResult(job, stamp); err != nil {
return domain.RunJobResultResult{}, err
}
@@ -621,6 +624,10 @@ func firstEligibleSupportedJob(jobs []domain.Job, capabilities []string, stamp t
}
func assignmentFromJob(job domain.Job, leaseToken string) domain.RunJobAssignment {
fencingToken := uint64(0)
if isProtectedRequestCapability(job.Capability) {
fencingToken = uint64(job.Attempt)
}
return domain.RunJobAssignment{
JobID: job.ID,
ServerInstanceID: job.ServerInstanceID,
@@ -635,6 +642,7 @@ func assignmentFromJob(job domain.Job, leaseToken string) domain.RunJobAssignmen
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: job.ExecutionInput.WorkspaceScope, Content: job.ExecutionInput.Content, ExpectedVersion: job.ExecutionInput.ExpectedVersion, ExpectedChecksum: job.ExecutionInput.ExpectedChecksum, MaxReadBytes: job.ExecutionInput.MaxReadBytes, RemoteAdapterKey: job.ExecutionInput.RemoteAdapterKey, RemoteAdapterKind: job.ExecutionInput.RemoteAdapterKind, TimeoutSeconds: job.ExecutionInput.TimeoutSeconds, PluginID: job.ExecutionInput.PluginID, LifecycleOperation: job.ExecutionInput.LifecycleOperation, TargetVersion: job.ExecutionInput.TargetVersion, Inputs: domain.CopyStringMap(job.ExecutionInput.Inputs), DLLExtensions: append([]domain.RuntimeDLLExtensionPlan(nil), job.ExecutionInput.DLLExtensions...), SourceRCON: domain.CopyRuntimeSourceRCONPlan(job.ExecutionInput.SourceRCON), Deployment: deploymentPlanForDispatchValue(job.ExecutionInput.Deployment)},
LeaseToken: leaseToken,
Attempt: job.Attempt,
FencingToken: fencingToken,
MaxAttempts: job.RetryPolicy.MaxAttempts,
AckDeadlineAt: job.AckDeadlineAt,
LeaseExpiresAt: job.LeaseExpiresAt,
+247
View File
@@ -0,0 +1,247 @@
package service
import (
"strings"
"sync"
"time"
"browser.local/platform/domain"
"browser.local/platform/validator"
)
const protectedRequestMaxTimeoutSeconds = 120
type protectedRequestPayload struct {
commandID string
kind string
transportKey string
targetKey string
requestText string
expiresAt time.Time
}
// protectedRequestBroker keeps opaque request text out of durable jobs and
// bridge records. It releases a payload exactly once to a current Run lease.
type protectedRequestBroker struct {
mu sync.Mutex
now func() time.Time
payloads map[string]protectedRequestPayload
}
func newProtectedRequestBroker(now func() time.Time) *protectedRequestBroker {
return &protectedRequestBroker{now: now, payloads: map[string]protectedRequestPayload{}}
}
func (broker *protectedRequestBroker) Put(jobID string, payload protectedRequestPayload) error {
broker.mu.Lock()
defer broker.mu.Unlock()
broker.pruneLocked()
if _, exists := broker.payloads[jobID]; exists {
return validationError("protected request idempotency key is already pending")
}
broker.payloads[jobID] = payload
return nil
}
func (broker *protectedRequestBroker) Consume(jobID string) (protectedRequestPayload, error) {
broker.mu.Lock()
defer broker.mu.Unlock()
broker.pruneLocked()
payload, exists := broker.payloads[jobID]
if !exists {
return protectedRequestPayload{}, validationError("protected request input is unavailable")
}
delete(broker.payloads, jobID)
return payload, nil
}
func (broker *protectedRequestBroker) Delete(jobID string) {
broker.mu.Lock()
defer broker.mu.Unlock()
delete(broker.payloads, jobID)
}
func (broker *protectedRequestBroker) pruneLocked() {
stamp := broker.now()
for jobID, payload := range broker.payloads {
if !stamp.Before(payload.expiresAt) {
delete(broker.payloads, jobID)
}
}
}
func protectedRequestCapability(kind string) (string, string, error) {
switch kind {
case "sql":
return domain.JobCapabilityRemoteRunProtectedSQL, "protected-sql", nil
case "rcon":
return domain.JobCapabilityRemoteRunProtectedRCON, "protected-rcon", nil
case "program":
return domain.JobCapabilityRemoteRunProgram, "protected-program", nil
default:
return "", "", validationError("protected request kind is unsupported")
}
}
func redactedProtectedRequestPayload(declaration *domain.GameClientBridgeProtectedRequestDeclaration) map[string]any {
return map[string]any{declaration.TextField: "redacted"}
}
func (svc *CoreService) dispatchProtectedRequest(command domain.GameClientBridgeCommand, declaration domain.GameClientBridgeCommandDeclaration, payload map[string]any) error {
if declaration.ProtectedRequest == nil || declaration.TimeoutSeconds < 1 || declaration.TimeoutSeconds > protectedRequestMaxTimeoutSeconds {
return validationError("protected request timeout is out of bounds")
}
requestText, _ := payload[declaration.ProtectedRequest.TextField].(string)
capability, adapterKind, err := protectedRequestCapability(declaration.ProtectedRequest.Kind)
if err != nil {
return err
}
jobID := command.RunJobID
if jobID == "" {
return validationError("protected request job binding is missing")
}
if err := svc.protectedRequests.Put(jobID, protectedRequestPayload{commandID: command.ID, kind: declaration.ProtectedRequest.Kind, transportKey: declaration.ProtectedRequest.TransportKey, targetKey: declaration.ProtectedRequest.TargetKey, requestText: requestText, expiresAt: command.ExpiresAt}); err != nil {
return err
}
job := domain.Job{
ID: jobID,
ServerInstanceID: command.ServerInstanceID,
RunEndpointID: mustProtectedRequestRunEndpoint(svc, command.ServerInstanceID),
Capability: capability,
TargetKey: declaration.ProtectedRequest.TargetKey,
InputRef: "input://protected-request/" + command.ID,
IdempotencyKey: "protected-request:" + command.ID,
Progress: domain.JobProgress{Percent: 0, Message: "protected request queued"},
RetryPolicy: domain.JobRetryPolicy{MaxAttempts: 1, InitialBackoffSeconds: 1, MaxBackoffSeconds: 1},
ExecutionInput: domain.JobExecutionInput{
WorkspaceScope: command.ProfileKey,
RemoteAdapterKey: declaration.ProtectedRequest.TransportKey,
RemoteAdapterKind: adapterKind,
TimeoutSeconds: declaration.TimeoutSeconds,
PluginID: command.PluginID,
},
}
if job.RunEndpointID == "" {
svc.protectedRequests.Delete(jobID)
return validationError("protected request server binding is unavailable")
}
created, err := svc.CreateJob(job)
if err != nil {
svc.protectedRequests.Delete(jobID)
return err
}
if created.ID != jobID || created.ServerInstanceID != job.ServerInstanceID || created.Capability != capability || created.TargetKey != job.TargetKey || created.ExecutionInput.RemoteAdapterKey != job.ExecutionInput.RemoteAdapterKey || created.ExecutionInput.RemoteAdapterKind != adapterKind {
svc.protectedRequests.Delete(jobID)
return validationError("protected request idempotency key is already bound")
}
return nil
}
func mustProtectedRequestRunEndpoint(svc *CoreService, serverInstanceID string) string {
instance, err := svc.store.ServerInstances().Get(serverInstanceID)
if err != nil {
return ""
}
return instance.RunEndpointID
}
func (svc *CoreService) GetProtectedRequestExecutionInput(request domain.ProtectedRequestExecutionInputRequest) (domain.ProtectedRequestExecutionInput, error) {
if err := validator.ValidateProtectedRequestExecutionInputRequest(request); err != nil {
return domain.ProtectedRequestExecutionInput{}, err
}
job, err := svc.activeFencedInputJob(request.RunEndpointID, request.SessionToken, request.JobID, request.LeaseToken, request.Attempt)
if err != nil {
return domain.ProtectedRequestExecutionInput{}, err
}
if request.FencingToken != uint64(job.Attempt) || !isProtectedRequestCapability(job.Capability) || job.RetryPolicy.MaxAttempts != 1 || !strings.HasPrefix(job.InputRef, "input://protected-request/") {
return domain.ProtectedRequestExecutionInput{}, validationError("job is not a fenced protected request")
}
commands, err := svc.store.GameClientBridgeCommands().List(domain.GameClientBridgeCommandFilter{ServerInstanceID: job.ServerInstanceID})
if err != nil {
return domain.ProtectedRequestExecutionInput{}, err
}
var command domain.GameClientBridgeCommand
for _, candidate := range commands {
if candidate.RunJobID == job.ID {
command = candidate
break
}
}
if command.ID == "" || command.ApprovalState != domain.GameClientBridgeApprovalApproved || command.State != domain.GameClientBridgeCommandPending || !command.ExpiresAt.After(svc.now()) {
return domain.ProtectedRequestExecutionInput{}, validationError("protected request is not currently authorized")
}
payload, err := svc.protectedRequests.Consume(job.ID)
if err != nil {
return domain.ProtectedRequestExecutionInput{}, err
}
capability, adapterKind, capabilityErr := protectedRequestCapability(payload.kind)
if capabilityErr != nil || capability != job.Capability || payload.targetKey != job.TargetKey || payload.transportKey != job.ExecutionInput.RemoteAdapterKey || adapterKind != job.ExecutionInput.RemoteAdapterKind {
return domain.ProtectedRequestExecutionInput{}, validationError("protected request logical binding is invalid")
}
return domain.CopyProtectedRequestExecutionInput(domain.ProtectedRequestExecutionInput{JobID: job.ID, ServerInstanceID: job.ServerInstanceID, RunEndpointID: job.RunEndpointID, FencingToken: request.FencingToken, Authorized: true, ApprovalState: "approved", QueueState: "claimed", ExpiresAt: payload.expiresAt, Kind: payload.kind, TransportKey: payload.transportKey, TargetKey: payload.targetKey, RequestText: payload.requestText}), nil
}
func isProtectedRequestCapability(capability string) bool {
_, _, err := protectedRequestCapabilityForCapability(capability)
return err == nil
}
func protectedRequestCapabilityForCapability(capability string) (string, string, error) {
switch capability {
case domain.JobCapabilityRemoteRunProtectedSQL:
return "sql", "protected-sql", nil
case domain.JobCapabilityRemoteRunProtectedRCON:
return "rcon", "protected-rcon", nil
case domain.JobCapabilityRemoteRunProgram:
return "program", "protected-program", nil
default:
return "", "", validationError("job is not a protected request")
}
}
func (svc *CoreService) projectProtectedRequestJobResult(job domain.Job, result domain.RunJobResult, stamp time.Time) error {
if !isProtectedRequestCapability(job.Capability) {
return nil
}
svc.protectedRequests.Delete(job.ID)
svc.bridgeMu.Lock()
defer svc.bridgeMu.Unlock()
commands, err := svc.store.GameClientBridgeCommands().List(domain.GameClientBridgeCommandFilter{ServerInstanceID: job.ServerInstanceID})
if err != nil {
return err
}
for _, command := range commands {
if command.RunJobID != job.ID || isTerminalGameClientBridgeCommandState(command.State) {
continue
}
switch result.State {
case domain.JobStateSucceeded:
command.State = domain.GameClientBridgeCommandSucceeded
command.Result.Status = domain.GameClientBridgeResultSucceeded
case domain.JobStateCancelled:
command.State = domain.GameClientBridgeCommandCancelled
command.Result.Status = domain.GameClientBridgeResultCancelled
case domain.JobStateFailed:
command.State = domain.GameClientBridgeCommandFailed
command.Result.Status = domain.GameClientBridgeResultFailed
if result.ErrorCode == "protected_request_unknown" || strings.HasSuffix(result.ExecutionResult.Kind, ".unknown") {
command.State = domain.GameClientBridgeCommandUnknown
command.Result.Status = domain.GameClientBridgeResultUnknown
}
default:
return nil
}
command.Result.Summary = "protected request completed by Run"
command.Result.CompletedBy = "run"
command.Result.CompletedAt = stamp
command.CompletedAt = stamp
command.UpdatedAt = stamp
auditID, auditErr := svc.recordAuditEventWithID("run", "game-client-bridge.command.result", "game-client-bridge-command", command.ID, domain.AuditResultSuccess, "Run recorded protected bridge command result")
if auditErr != nil {
return auditErr
}
command.AuditReferences = append(command.AuditReferences, auditID)
return svc.store.GameClientBridgeCommands().Update(command)
}
return nil
}
+3
View File
@@ -140,6 +140,7 @@ type Core interface {
GetDependencyExecutionInput(domain.DependencyExecutionInputRequest) (domain.DependencyExecutionInput, error)
DispatchSourceRCONCommandForSession(string, domain.SourceRCONCommandRequest) (domain.SourceRCONCommandDispatch, error)
GetSourceRCONExecutionInput(domain.SourceRCONExecutionInputRequest) (domain.SourceRCONExecutionInput, error)
GetProtectedRequestExecutionInput(domain.ProtectedRequestExecutionInputRequest) (domain.ProtectedRequestExecutionInput, error)
GetRunUpdateInput(domain.RunUpdateInputRequest) (domain.RunUpdateInput, error)
ReadRunUpdateChunk(domain.RunUpdateChunkRequest) (domain.RunUpdateChunk, error)
ReportRunUpdateHealth(domain.RunUpdateHealthReport) (domain.RunUpdateHealthResult, error)
@@ -244,6 +245,7 @@ type CoreService struct {
auditSeq uint64
productionMu sync.Mutex
sourceRCONCommands *sourceRCONCommandBroker
protectedRequests *protectedRequestBroker
aiProviderClient AIProviderClient
secretEnvelope SecretEnvelope
networkFingerprintKey []byte
@@ -278,6 +280,7 @@ func newCoreServiceWithLogStore(store repo.Store, logStore LogBodyStore, now fun
artifactTransfers: map[string]domain.ArtifactTransferSession{},
artifactPayloads: map[string][]byte{},
sourceRCONCommands: newSourceRCONCommandBroker(now),
protectedRequests: newProtectedRequestBroker(now),
aiProviderClient: MockAIProviderClient{},
secretEnvelope: newSecretEnvelope(developmentSecretEnvelopeKey),
networkFingerprintKey: []byte(developmentSecretEnvelopeKey),