diff --git a/.doc/test-reports/2026-09-12-workflow-governance-remediation-test-report.md b/.doc/test-reports/2026-09-12-workflow-governance-remediation-test-report.md index c22111ed..580331ba 100644 --- a/.doc/test-reports/2026-09-12-workflow-governance-remediation-test-report.md +++ b/.doc/test-reports/2026-09-12-workflow-governance-remediation-test-report.md @@ -69,6 +69,32 @@ PASS ok chat-agent-system/internal/server 0.514s **评价判定**: PASS — 正式工作流 `test_manager` 末阶段完成→`planning`;正式工作流 `main` 激活/DocGov 批准/阶段推进链通过 +### 5.5 PI Agent 真实全链路(隔离 test 库 + MiniMax-M3) + +**环境**: +- `TEST_DATABASE_URL` → `agent_system_test@127.0.0.1:5434` +- `RUNNER_AUTH_TOKEN` → dev runner(`18865/health` 200) +- 工作区挂载 → `data/dev/workspaces`(与 pi-agent 容器共享) + +**命令**: +```bash +RUNNER_AUTH_TOKEN='dev_runner_secret_token' \ +TEST_DATABASE_URL='postgres://app:test_secret_db_pass@127.0.0.1:5434/agent_system_test' \ + go test ./internal/server/... -run TestWorkflowGovernance_PiAgent_PG_FullChain -count=1 -timeout 1h -v +``` + +**实际输出**: +``` +✓ [1. 项目创建] id=10 code=pg-piagent-治-dbbbb9f7 +✓ [2. 工作流激活] instance_id=6 +✓ [3. pi-agent intake] doc=doc_proj10_intake title=用户初始需求收集表 elapsed=6m28s +✓ [4. 文档批准] stage ready hook 已触发 +✓ [5. 阶段推进] PG + pi-agent 治理全链路完成 +--- PASS: TestWorkflowGovernance_PiAgent_PG_FullChain (387.93s) +``` + +**评价判定**: PASS — 正式工作流 `main` + 真实 pi-agent intake 三步骤 + DocGov 批准 + 阶段推进 + ## 六、 自动化测试套件汇总 (Test Suite Execution) ``` @@ -79,6 +105,6 @@ TEST_DATABASE_URL=... go test ./internal/server/... -run 'TestE2E_WorkflowGovern ## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) -- Phase A–C 核心路径与 PG 治理 E2E 已通过;`test_manager`/`main` 正式工作流在隔离 test 库验证 PASS。 -- 仍待:三流程 + 真实 PI Agent(MiniMax-M3)全链路实测(`TestWorkflowGovernance_PiAgent_PG_FullChain`)写入本报告后再合 main。 +- Phase A–C 核心路径、PG 治理 E2E 与 PI Agent 真实全链路均已 PASS。 +- 仍待:`product_manager` 正式工作流单独 PG+PI 链路(可选补测)后合 main。 - 建议每周运行 `scripts/fix-completed-instance-lifecycle.sql` dry-run 检查历史不一致。 diff --git a/internal/server/test_helpers_test.go b/internal/server/test_helpers_test.go index a38bec9a..690f98fc 100644 --- a/internal/server/test_helpers_test.go +++ b/internal/server/test_helpers_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "net/url" "os" + "path/filepath" "strconv" "strings" "testing" @@ -117,6 +118,29 @@ func envOr(key, fallback string) string { return fallback } +func piAgentSharedDataDir(t *testing.T) string { + t.Helper() + if d := strings.TrimSpace(os.Getenv("DATA_DIR")); d != "" { + return d + } + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + for _, rel := range []string{".", "..", "../..", "../../.."} { + base := filepath.Clean(filepath.Join(cwd, rel)) + for _, envName := range []string{"dev", "test"} { + candidate := filepath.Join(base, "data", envName) + ws := filepath.Join(candidate, "workspaces") + if st, statErr := os.Stat(ws); statErr == nil && st.IsDir() { + return candidate + } + } + } + t.Skip("shared pi-agent DATA_DIR not found (mount data/test/workspaces or data/dev/workspaces)") + return "" +} + func seedOfficialWorkflowManifest(t *testing.T, pool *pgxpool.Pool, workflowKey string) { t.Helper() filename, ok := workflowdef.V2ManifestFiles[workflowKey] diff --git a/internal/server/workflow_governance_piagent_pg_test.go b/internal/server/workflow_governance_piagent_pg_test.go index 12e6de13..bb402761 100644 --- a/internal/server/workflow_governance_piagent_pg_test.go +++ b/internal/server/workflow_governance_piagent_pg_test.go @@ -17,7 +17,6 @@ import ( "chat-agent-system/internal/projectinstance" "chat-agent-system/internal/projectworkflow" "chat-agent-system/internal/server" - "chat-agent-system/internal/squad" "chat-agent-system/internal/workflowdef" ) @@ -34,10 +33,17 @@ func piAgentRunnerHealthy() bool { func setupPGPiAgentServer(t *testing.T, pool *pgxpool.Pool) (http.Handler, *server.Server) { t.Helper() cfg := configFromTestDatabaseURL(t) + cfg.DataDir = piAgentSharedDataDir(t) cfg.AgentBackend = "pi-agent" - cfg.PiAgentRunnerURL = "http://127.0.0.1:18865" + cfg.AgentTimeout = 60 * time.Minute + cfg.PiAgentRunnerURL = envOr("PI_AGENT_RUNNER_URL", "http://127.0.0.1:18865") + cfg.PiAgentRunnerAuthToken = envOr("PI_AGENT_RUNNER_AUTH_TOKEN", envOr("RUNNER_AUTH_TOKEN", "")) cfg.PiAgentModel = "MiniMax-M3" cfg.PiAgentProvider = "minimax" + if cfg.PiAgentRunnerAuthToken == "" { + t.Skip("RUNNER_AUTH_TOKEN not set; skipping pi-agent PG E2E") + } + mergePiAgentE2EKbConfig(cfg) svc := bizdev.MustNewService(t, cfg) if svc.Pool() == nil { t.Skip("bizdev service not connected to postgres") @@ -70,33 +76,8 @@ func TestWorkflowGovernance_PiAgent_PG_FullChain(t *testing.T) { defer ts.Close() client := &http.Client{Timeout: 50 * time.Minute} - wfSvc := workflowdef.NewService(pool, nil) - wfKey := "pg_pi_p5_" + time.Now().Format("150405.000") - _, ver, err := wfSvc.CreateDefinition(ctx, workflowdef.CreateDefinitionInput{ - Key: wfKey, Name: "PG PiAgent Governance WF", - Definition: workflowdef.VersionDefinition{ - SchemaVersion: 1, - Stages: []workflowdef.StageDef{{ - Key: "biz_intake", Name: "Intake", Position: 0, - DocTasks: []workflowdef.DocTaskDef{{ - Key: "intake", Required: true, Blocking: true, DefaultEnabled: true, - OutputDocType: "intake", OutputDocTitle: "Customer Intake", - }}, - }, { - Key: "biz_feasibility", Name: "Feasibility", Position: 1, Optional: true, - DocTasks: []workflowdef.DocTaskDef{{ - Key: "feasibility_reg", Required: false, Blocking: false, DefaultEnabled: true, - OutputDocType: "feasibility", OutputDocTitle: "Regulatory Path", - }}, - }}, - }, - }, 1) - if err != nil { - t.Fatalf("create workflow def: %v", err) - } - if _, err := wfSvc.PublishVersion(ctx, ver.WorkflowID, ver.Version, 1); err != nil { - t.Fatalf("publish workflow: %v", err) - } + wfKey := workflowdef.OfficialKeyMain + seedOfficialWorkflowManifest(t, pool, wfKey) creatorToken := loginTestUser(t, handler, "zhanggong", "123456") reviewerToken := loginTestUser(t, handler, "lizhuguan", "123456") @@ -128,7 +109,7 @@ func TestWorkflowGovernance_PiAgent_PG_FullChain(t *testing.T) { t.Logf("✓ [1. 项目创建] id=%d code=%s", projectID, proj.Code) // 规划 + 激活 - reqDefault := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/projects/%d/workflow-plan/default?workflow_key=%s&version=1", projectID, wfKey), nil) + reqDefault := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/projects/%d/workflow-plan/default?workflow_key=%s&version=0", projectID, wfKey), nil) withBearerAuth(reqDefault, creatorToken) recDefault := httptest.NewRecorder() handler.ServeHTTP(recDefault, reqDefault) @@ -176,7 +157,7 @@ func TestWorkflowGovernance_PiAgent_PG_FullChain(t *testing.T) { instanceID := activated.Instance.ID t.Logf("✓ [2. 工作流激活] instance_id=%d", instanceID) - // pi-agent 执行 intake(精简单步 WBS) + // pi-agent 执行 intake(默认三步骤 WBS,工作区挂载 data/*/workspaces 供 runner 读取) taskStart := time.Now() taskBody, _ := json.Marshal(server.TaskRunRequestDTO{ TaskKey: "intake", @@ -189,10 +170,15 @@ func TestWorkflowGovernance_PiAgent_PG_FullChain(t *testing.T) { AuthorID: creatorID, ReviewerID: reviewerID, ApproverID: approverID, - CustomSteps: []squad.StepSpec{{ - ID: "step_01_intake", Role: "writer", Title: "Intake snapshot", - SubWorkspace: "tasks/01_intake", Mode: "execute", - Accept: []string{"Deliver output.md with project intake summary"}, + Fields: map[string]string{ + "customer_name": "PG PiAgent 治理链客户", + "device_type": proj.DeviceType, + "intended_use": "Tissue resection", + "target_market": "NMPA Class III", + }, + Materials: []bizdev.MaterialInput{{ + Filename: "project_brief.md", + Content: []byte("# 项目简报\n\n- 产品:超声软组织手术系统\n- 目标:NMPA III 类注册路径摸底\n- 关键需求:双频刀头、灌注与负压吸引\n"), }}, }) reqTask, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/tasks/run", bytes.NewReader(taskBody)) @@ -259,9 +245,9 @@ func TestWorkflowGovernance_PiAgent_PG_FullChain(t *testing.T) { } advanceBody, _ := json.Marshal(projectinstance.AdvanceStageInput{ - StageKey: "biz_intake", ExpectedConfigRevision: activated.Instance.ConfigRevision, + StageKey: "V0.1_triage", ExpectedConfigRevision: activated.Instance.ConfigRevision, }) - reqAdvance := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/workflow-instances/%d/stages/biz_intake/advance", instanceID), bytes.NewReader(advanceBody)) + reqAdvance := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/workflow-instances/%d/stages/V0.1_triage/advance", instanceID), bytes.NewReader(advanceBody)) withBearerAuth(reqAdvance, creatorToken) recAdvance := httptest.NewRecorder() handler.ServeHTTP(recAdvance, reqAdvance)