Remove AI config approval flow
This commit is contained in:
@@ -1072,6 +1072,7 @@ func TestAIInvocationAPIIsMediatedAndSafe(t *testing.T) {
|
||||
adminSession := createAdminSession(t, router)
|
||||
createAIProviderFixture(t, router, adminSession)
|
||||
registration := validGamePluginManifestRegistrationRequest()
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, domain.JobCapabilityConfigWrite)
|
||||
registration.Manifest.Pages[0].Permissions = []string{"server.read", "server.logs.read", "ai.invoke"}
|
||||
registration.Manifest.Pages[0].BridgeActions = []string{string(domain.PluginBridgeActionServerInstancesRead), string(domain.PluginBridgeActionLogsQuery), string(domain.PluginBridgeActionAIInvoke)}
|
||||
postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins/register-manifest", registration)
|
||||
@@ -1117,19 +1118,20 @@ func TestAIInvocationAPIIsMediatedAndSafe(t *testing.T) {
|
||||
}, adminSession)
|
||||
assertErrorResponse(t, unsafe, http.StatusBadRequest, errorCodeValidation)
|
||||
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
||||
configRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/ai/config-suggestions", dto.LlmConfigSuggestionRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
Prompt: "Turn off pvp and keep this reviewable",
|
||||
Prompt: "Turn off pvp and apply it now",
|
||||
CurrentConfig: "server.name=AI API Server\n",
|
||||
}, adminSession)
|
||||
assertStatus(t, configRecorder, http.StatusOK)
|
||||
config := decodeBody[dto.LlmConfigSuggestionResponse](t, configRecorder)
|
||||
if config.SuggestedConfig == "" || !strings.Contains(config.SuggestedConfig, "ai.recommendation=review-required") {
|
||||
t.Fatalf("expected reviewable config suggestion, got %+v", config)
|
||||
if config.SuggestedConfig == "" || config.ConfigExecution == nil || config.ConfigExecution.Job.Capability != domain.JobCapabilityConfigWrite {
|
||||
t.Fatalf("expected immediately dispatched config suggestion, got %+v", config)
|
||||
}
|
||||
jobs := getJSONWithAuth[dto.JobListResponse](t, router, "/api/v1/jobs?serverInstanceId=server-ai-api", adminSession)
|
||||
if jobs.Count != 0 {
|
||||
t.Fatalf("AI suggestion must not dispatch config writes, got %+v", jobs)
|
||||
if jobs.Count != 1 || !containsJobID(jobs.Items, config.ConfigExecution.Job.ID) {
|
||||
t.Fatalf("AI suggestion must dispatch one config write, got %+v", jobs)
|
||||
}
|
||||
|
||||
bridge := postOKJSONWithAuth[dto.PluginBridgeExecuteResponse](t, router, "/api/v1/plugin-bridge/execute", dto.PluginBridgeExecuteRequest{
|
||||
@@ -1597,22 +1599,16 @@ func TestPluginLifecycleAndAIConfigRoutesAreDurableAndRedacted(t *testing.T) {
|
||||
t.Fatalf("expected persisted plugin lifecycle, got %+v", lifecycles)
|
||||
}
|
||||
|
||||
invocation := postOKJSONWithAuth[dto.AIInvocationResponse](t, router, "/api/v1/ai/invocations", dto.AIInvocationRequest{RequestID: "api-ai-config-diff", ServerInstanceID: serverID, Purpose: "config.suggest", ProviderID: "ai.openai", Prompt: "disable pvp"}, adminSession)
|
||||
if invocation.ConfigRecommendation == nil || invocation.ConfigRecommendation.DiffID == "" {
|
||||
t.Fatalf("expected persisted AI config diff, got %+v", invocation)
|
||||
invocation := postOKJSONWithAuth[dto.AIInvocationResponse](t, router, "/api/v1/ai/invocations", dto.AIInvocationRequest{RequestID: "api-ai-config-direct", ServerInstanceID: serverID, Purpose: "config.suggest", ProviderID: "ai.openai", Prompt: "disable pvp"}, adminSession)
|
||||
if invocation.ConfigRecommendation == nil || invocation.ConfigExecution == nil || invocation.ConfigExecution.Job.Capability != domain.JobCapabilityConfigWrite {
|
||||
t.Fatalf("expected direct AI config write dispatch, got %+v", invocation)
|
||||
}
|
||||
diffs := getJSONWithAuth[dto.AIConfigDiffListResponse](t, router, "/api/v1/ai/config-diffs?serverInstanceId="+serverID, adminSession)
|
||||
if diffs.Count != 1 || diffs.Items[0].State != string(domain.AIConfigDiffStatePending) {
|
||||
t.Fatalf("expected one pending AI diff, got %+v", diffs)
|
||||
}
|
||||
approvalRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/ai/config-diffs/"+invocation.ConfigRecommendation.DiffID+"/approve", dto.AIConfigDiffApprovalRequest{IdempotencyKey: "api-ai-diff-approve"}, adminSession)
|
||||
assertStatus(t, approvalRecorder, http.StatusAccepted)
|
||||
approval := decodeBody[dto.AIConfigDiffApprovalResponse](t, approvalRecorder)
|
||||
if approval.Preview.State != string(domain.AIConfigDiffStateApproved) || approval.Dispatch.Job.Capability != domain.JobCapabilityConfigWrite {
|
||||
t.Fatalf("expected approved diff with config write job, got %+v", approval)
|
||||
jobs := getJSONWithAuth[dto.JobListResponse](t, router, "/api/v1/jobs?serverInstanceId="+serverID, adminSession)
|
||||
if jobs.Count < 2 || !containsJobID(jobs.Items, invocation.ConfigExecution.Job.ID) {
|
||||
t.Fatalf("expected AI config write job in durable job list, got %+v", jobs)
|
||||
}
|
||||
|
||||
evidence := fmt.Sprintf("%+v %+v %+v %+v", lifecycle, lifecycles, diffs, approval)
|
||||
evidence := fmt.Sprintf("%+v %+v %+v", lifecycle, lifecycles, invocation)
|
||||
for _, forbidden := range []string{"/Users/", "/private/", "unix://", "tcp://", "Bearer ", "sk-", "password=", "apiKeyRef", "rawApiKey", "https://api.openai.com"} {
|
||||
if strings.Contains(evidence, forbidden) {
|
||||
t.Fatalf("plugin operations response leaked forbidden fragment %q: %s", forbidden, evidence)
|
||||
@@ -1992,7 +1988,7 @@ func validGamePluginManifestRegistrationRequest() dto.GamePluginManifestRegistra
|
||||
BridgeActions: []string{string(domain.PluginBridgeActionLogsQuery), string(domain.PluginBridgeActionFilesRequest), string(domain.PluginBridgeActionAIInvoke)},
|
||||
},
|
||||
},
|
||||
AI: dto.GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}, Mediation: "platform", ConfigWritePolicy: "review-required"},
|
||||
AI: dto.GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}, Mediation: "platform"},
|
||||
ProductionLifecycle: dto.GamePluginProductionLifecycleBody{Operations: []string{"install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"}, DependencyPolicy: "optional"},
|
||||
RuntimeProfiles: dto.GamePluginRuntimeProfilesBody{LifecycleProfiles: []dto.RuntimeLifecycleProfileBody{{Key: "local", Mode: "local-process", Capabilities: []string{"process.install", "process.start", "process.stop"}}}},
|
||||
},
|
||||
@@ -2010,3 +2006,12 @@ func validRunEndpointRequest() dto.RunEndpointCreateRequest {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func containsJobID(items []dto.JobResponse, id string) bool {
|
||||
for _, item := range items {
|
||||
if item.ID == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user