Remove AI config approval flow

This commit is contained in:
npc0-hue
2026-09-22 15:33:32 +08:00
parent 20008b4043
commit a8b5d483a3
54 changed files with 364 additions and 875 deletions
+24 -19
View File
@@ -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
}