mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
fix(findings): address Grok review r2 server batch (Codex gpt-6.1-sol)
Authored by Codex CLI (gpt-6.1-sol, reasoning high, first attempt). Historical review task message follows the new suggest rule and treats the unified report as best-effort; v3 tool refresh no longer re-adds a removed reporter binding.
This commit is contained in:
1 parent
5b342f9f78
commit
54f43fcd3f
4 files changed
+162
-4
No files matched your search
@@ -34,7 +34,7 @@ func (s *Server) toolCompleteFindingCaseReview() actool.CoreTool {
|
|||||||
"finding_id": idParam("所选独立漏洞记录 ID"),
|
"finding_id": idParam("所选独立漏洞记录 ID"),
|
||||||
"verdict": map[string]any{"type": "string", "enum": []string{"independent", "merged", "suggested"}},
|
"verdict": map[string]any{"type": "string", "enum": []string{"independent", "merged", "suggested"}},
|
||||||
"reason": strParam("判断理由,引用已证据化的共同点或不同代码路径;仅类型、标题或资产相同不足以证明共同根因"),
|
"reason": strParam("判断理由,引用已证据化的共同点或不同代码路径;仅类型、标题或资产相同不足以证明共同根因"),
|
||||||
"root_cause_evidence": strParam("原始报告/证据中支持实际共同或不同根因的具体材料;不确定则说明缺失证据及已保存建议")}, "finding_id", "verdict", "reason", "root_cause_evidence")
|
"root_cause_evidence": strParam("原始报告/证据中支持实际共同或不同根因的具体材料;suggested 须说明具体共同点、缺失的关键证据及已保存分组;没有具体共同点应判 independent")}, "finding_id", "verdict", "reason", "root_cause_evidence")
|
||||||
return wrTool("complete_finding_case_review", "历史整理结束前提交每条所选记录的根因判断并验收:必须已读取全部记录,疑似必须已有待确认分组建议;归并只需已在活动文件夹中,统一报告可稍后补齐。验收失败不能宣布完成。", objSchema(map[string]any{"summary": strParam("整理结果总结"), "decisions": map[string]any{"type": "array", "items": decision}}, "summary", "decisions"), func(ctx context.Context, raw json.RawMessage) (actool.Result, error) {
|
return wrTool("complete_finding_case_review", "历史整理结束前提交每条所选记录的根因判断并验收:必须已读取全部记录,疑似必须已有待确认分组建议;归并只需已在活动文件夹中,统一报告可稍后补齐。验收失败不能宣布完成。", objSchema(map[string]any{"summary": strParam("整理结果总结"), "decisions": map[string]any{"type": "array", "items": decision}}, "summary", "decisions"), func(ctx context.Context, raw json.RawMessage) (actool.Result, error) {
|
||||||
var a findingReviewConclusion
|
var a findingReviewConclusion
|
||||||
if err := json.Unmarshal(raw, &a); err != nil {
|
if err := json.Unmarshal(raw, &a); err != nil {
|
||||||
|
|||||||
@@ -4,11 +4,46 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Autumn-27/artex/intercept"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Autumn-27/artex/intercept"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFindingCaseReviewTaskMessagePolicy(t *testing.T) {
|
||||||
|
message := fmt.Sprintf(findingCaseReviewTaskMessage, `[101,102,103]`)
|
||||||
|
for _, want := range []string{
|
||||||
|
"finding_ids=[101,102,103]",
|
||||||
|
"根因明确相同则直接归并",
|
||||||
|
"相同端点、处理器/后端组件、参数或缺失检查",
|
||||||
|
"缺少确认共同根因的关键证据",
|
||||||
|
"一个覆盖2..20条同任务疑似成员的分组",
|
||||||
|
"不要拆成两两建议",
|
||||||
|
"仅类型或资产相同不足以建议",
|
||||||
|
"没有具体共同点时保持 independent",
|
||||||
|
"不同代码路径时可判 independent",
|
||||||
|
"尽力更新统一报告",
|
||||||
|
"统一报告暂缺或待更新不阻塞 complete_finding_case_review",
|
||||||
|
"不归并未选记录",
|
||||||
|
"不探测目标",
|
||||||
|
"遵守人工否决",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(message, want) {
|
||||||
|
t.Errorf("task message missing policy: %s", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(message, "不确定提交建议") {
|
||||||
|
t.Fatal("task message suggests solely because of uncertainty")
|
||||||
|
}
|
||||||
|
schema, err := json.Marshal((&Server{}).toolCompleteFindingCaseReview().InputSchema())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(schema), "没有具体共同点应判 independent") || strings.Contains(string(schema), "不确定则说明缺失证据及已保存建议") {
|
||||||
|
t.Fatal("completion schema must not require suggestions solely because of uncertainty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const completeFindingReviewReport = `# 同一权限缺陷
|
const completeFindingReviewReport = `# 同一权限缺陷
|
||||||
## 概述
|
## 概述
|
||||||
同一对象权限检查缺失。
|
同一对象权限检查缺失。
|
||||||
|
|||||||
+16
-2
@@ -397,6 +397,8 @@ func (s *Server) seedFindingCaseTools() {
|
|||||||
}
|
}
|
||||||
names := []string{}
|
names := []string{}
|
||||||
previous, _, _ := s.m.pg.GetSetting("finding_case_tools_v1")
|
previous, _, _ := s.m.pg.GetSetting("finding_case_tools_v1")
|
||||||
|
// v3 refreshes descriptions and schemas without repeating the v2 binding migration.
|
||||||
|
previousReview, _, _ := s.m.pg.GetSetting("finding_case_tools_v2_review")
|
||||||
for _, t := range s.findingCaseTools() {
|
for _, t := range s.findingCaseTools() {
|
||||||
schema, _ := json.Marshal(t.InputSchema())
|
schema, _ := json.Marshal(t.InputSchema())
|
||||||
if err := s.m.pg.RefreshToolDefaults(t.Name(), t.Description(), schema); err != nil {
|
if err := s.m.pg.RefreshToolDefaults(t.Name(), t.Description(), schema); err != nil {
|
||||||
@@ -405,15 +407,27 @@ func (s *Server) seedFindingCaseTools() {
|
|||||||
if err := s.m.pg.SeedTool(t.Name(), t.Description(), schema, json.RawMessage(`["reporter"]`)); err != nil {
|
if err := s.m.pg.SeedTool(t.Name(), t.Description(), schema, json.RawMessage(`["reporter"]`)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if previous != "true" || t.Name() == "complete_finding_case_review" {
|
needsBinding := previous != "true"
|
||||||
|
if t.Name() == "complete_finding_case_review" {
|
||||||
|
needsBinding = previousReview != "true"
|
||||||
|
}
|
||||||
|
if needsBinding {
|
||||||
names = append(names, t.Name())
|
names = append(names, t.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s.m.pg.AddAgentToToolBinding("reporter", names); err != nil {
|
if err := s.m.pg.AddAgentToToolBinding("reporter", names); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if previousReview != "true" {
|
||||||
|
if err := s.m.pg.SetSetting("finding_case_tools_v2_review", "true"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = s.m.pg.SetSetting(flag, "true")
|
_ = s.m.pg.SetSetting(flag, "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findingCaseReviewTaskMessage = "人工选择整理(只读原始记录):本任务所选 finding_ids=%s。逐条读取已有报告,仅判断同一实际根因;根因明确相同则直接归并。只有证据指向相同端点、处理器/后端组件、参数或缺失检查等具体共同点,但缺少确认共同根因的关键证据时,才提交一个覆盖2..20条同任务疑似成员的分组,不要拆成两两建议;仅类型或资产相同不足以建议,没有具体共同点时保持 independent,不要因为不确定就提交建议。证据表明属于不同代码路径时可判 independent。已有或新建组应尽力更新统一报告;统一报告暂缺或待更新不阻塞 complete_finding_case_review,可由后续运行或人工补齐。禁止生成或覆盖原始报告、改评级状态、补绑证据,不归并未选记录,不探测目标,遵守人工否决。结束前调用 complete_finding_case_review 提交逐条判断并通过验收。"
|
||||||
|
|
||||||
func (s *Server) reviewFindingCases(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) reviewFindingCases(w http.ResponseWriter, r *http.Request) {
|
||||||
var a findingCaseRequest
|
var a findingCaseRequest
|
||||||
if !decodeCaseRequest(w, r, &a) {
|
if !decodeCaseRequest(w, r, &a) {
|
||||||
@@ -479,7 +493,7 @@ func (s *Server) reviewFindingCases(w http.ResponseWriter, r *http.Request) {
|
|||||||
taskID, fids := reservation.TaskID, reservation.FindingIDs
|
taskID, fids := reservation.TaskID, reservation.FindingIDs
|
||||||
taskContext := taskContexts[taskID]
|
taskContext := taskContexts[taskID]
|
||||||
raw, _ := json.Marshal(fids)
|
raw, _ := json.Marshal(fids)
|
||||||
msg := fmt.Sprintf("人工选择整理(只读原始记录):本任务所选 finding_ids=%s。逐条读取已有报告,仅判断同一实际根因,明确则归并,不确定提交建议;已有组更新统一报告。禁止生成或覆盖原始报告、改评级状态、补绑证据,不归并未选记录,不探测目标。结束前调用 complete_finding_case_review 提交逐条判断并通过验收。", raw)
|
msg := fmt.Sprintf(findingCaseReviewTaskMessage, raw)
|
||||||
item := triggeredRun{agentKey: "reporter", title: "漏洞整理 · task#" + i64s(taskID), message: msg, taskID: taskID, taskDesc: taskContext.taskDesc, taskGoal: taskContext.taskGoal, conversationID: reservation.ConversationID}
|
item := triggeredRun{agentKey: "reporter", title: "漏洞整理 · task#" + i64s(taskID), message: msg, taskID: taskID, taskDesc: taskContext.taskDesc, taskGoal: taskContext.taskGoal, conversationID: reservation.ConversationID}
|
||||||
cfg := s.readTriggerBehavior("reporter")
|
cfg := s.readTriggerBehavior("reporter")
|
||||||
s.queueMu.Lock()
|
s.queueMu.Lock()
|
||||||
|
|||||||
@@ -42,6 +42,115 @@ func testCaseServer(t *testing.T) (*Server, int64, []int64) {
|
|||||||
t.Cleanup(func() { _, _ = pg.Exec(`DELETE FROM findings WHERE task_id=$1`, task.ID); _ = pg.DeleteTask(task.ID) })
|
t.Cleanup(func() { _, _ = pg.Exec(`DELETE FROM findings WHERE task_id=$1`, task.ID); _ = pg.DeleteTask(task.ID) })
|
||||||
return &Server{m: &Manager{pg: pg}}, task.ID, ids
|
return &Server{m: &Manager{pg: pg}}, task.ID, ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindingCaseToolMigrationPreservesBindings(t *testing.T) {
|
||||||
|
s, _, _ := testCaseServer(t)
|
||||||
|
pg := s.m.pg
|
||||||
|
for _, flag := range []string{"finding_case_tools_v1", "finding_case_tools_v2_review", "finding_case_tools_v3_review"} {
|
||||||
|
value, exists, err := pg.GetSetting(flag)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if exists {
|
||||||
|
if err := pg.SetSetting(flag, value); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
} else if _, err := pg.Exec(`DELETE FROM settings WHERE key=$1`, flag); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tool := range s.findingCaseTools() {
|
||||||
|
key := tool.Name()
|
||||||
|
old, err := pg.GetTool(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if old == nil {
|
||||||
|
if _, err := pg.Exec(`DELETE FROM tools WHERE key=$1`, key); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bindings, _ := json.Marshal(old.Agents)
|
||||||
|
if err := pg.UpdateTool(key, old.Description, old.Schema, bindings, old.Enabled); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := pg.SeedTool(key, tool.Description(), nil, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
v1, v2 bool
|
||||||
|
}{
|
||||||
|
{name: "initial"},
|
||||||
|
{name: "v1_to_v3", v1: true},
|
||||||
|
{name: "v2_to_v3", v1: true, v2: true},
|
||||||
|
{name: "v2_without_v1_flag", v2: true},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for flag, value := range map[string]bool{
|
||||||
|
"finding_case_tools_v1": tc.v1,
|
||||||
|
"finding_case_tools_v2_review": tc.v2,
|
||||||
|
"finding_case_tools_v3_review": false,
|
||||||
|
} {
|
||||||
|
if value {
|
||||||
|
if err := pg.SetSetting(flag, "true"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
} else if _, err := pg.Exec(`DELETE FROM settings WHERE key=$1`, flag); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tool := range s.findingCaseTools() {
|
||||||
|
if err := pg.UpdateTool(tool.Name(), "old description", json.RawMessage(`{}`), json.RawMessage(`["custom-agent"]`), false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.seedFindingCaseTools()
|
||||||
|
for _, tool := range s.findingCaseTools() {
|
||||||
|
row, err := pg.GetTool(tool.Name())
|
||||||
|
if err != nil || row == nil {
|
||||||
|
t.Fatal("missing tool", tool.Name(), err)
|
||||||
|
}
|
||||||
|
wantReporter := !tc.v1
|
||||||
|
if tool.Name() == "complete_finding_case_review" {
|
||||||
|
wantReporter = !tc.v2
|
||||||
|
}
|
||||||
|
if contains(row.Agents, "reporter") != wantReporter || !contains(row.Agents, "custom-agent") || row.Enabled {
|
||||||
|
t.Fatalf("binding/enabled configuration changed incorrectly: %+v", row)
|
||||||
|
}
|
||||||
|
if row.Description != tool.Description() || len(row.Schema) <= 2 {
|
||||||
|
t.Fatalf("description/schema not refreshed: %+v", row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, flag := range []string{"finding_case_tools_v2_review", "finding_case_tools_v3_review"} {
|
||||||
|
if value, _, err := pg.GetSetting(flag); err != nil || value != "true" {
|
||||||
|
t.Fatalf("migration flag %s not saved: %q, %v", flag, value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := pg.RemoveAgentFromTool("reporter", "complete_finding_case_review"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// A refresh retry must not replay a completed legacy binding migration.
|
||||||
|
if tc.v1 {
|
||||||
|
if err := pg.SetSetting("finding_case_tools_v3_review", "false"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.seedFindingCaseTools()
|
||||||
|
row, err := pg.GetTool("complete_finding_case_review")
|
||||||
|
if err != nil || row == nil || contains(row.Agents, "reporter") {
|
||||||
|
t.Fatal("refresh restored an operator-removed binding", row, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFindingCaseAPIPaginationAndTools(t *testing.T) {
|
func TestFindingCaseAPIPaginationAndTools(t *testing.T) {
|
||||||
s, task, ids := testCaseServer(t)
|
s, task, ids := testCaseServer(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
Reference in new issue
Block a user