fix(quality): 闭环 Go 质量门禁(鉴权富化/V0.5/vet/health/KB gate/记忆轮询)

This commit is contained in:
weichen
2026-09-13 02:50:23 +00:00
parent ac9fe7f09b
commit 3d09bd6d7d
9 changed files with 84 additions and 29 deletions
+8 -5
View File
@@ -121,12 +121,15 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
if !resV05.Success {
t.Fatalf("Stage V0.5 should succeed, got error: %s", resV05.ErrorReason)
}
if len(resV05.SubArtifacts) != 3 {
t.Fatalf("Stage V0.5 should produce 3 sub-artifacts (research, feasibility_reg, prelim_design), got %d", len(resV05.SubArtifacts))
if len(resV05.SubArtifacts) != 5 {
t.Fatalf("Stage V0.5 should produce 5 sub-artifacts, got %d", len(resV05.SubArtifacts))
}
t.Logf("✓ Stage V0.5 成功完成: 包含任务=[%s, %s, %s], 成熟度评分=%d (%s)",
resV05.SubArtifacts[0].TaskKey, resV05.SubArtifacts[1].TaskKey, resV05.SubArtifacts[2].TaskKey,
resV05.AdvisorBrief.MaturityScore, resV05.AdvisorBrief.MaturityLevel)
keys := make([]string, len(resV05.SubArtifacts))
for i, sub := range resV05.SubArtifacts {
keys[i] = sub.TaskKey
}
t.Logf("✓ Stage V0.5 成功完成: 包含任务=%v, 成熟度评分=%d (%s)",
keys, resV05.AdvisorBrief.MaturityScore, resV05.AdvisorBrief.MaturityLevel)
// 验证 feasibility_reg 受控文档已建立
docID := "doc_proj1001_feasibility_reg"
+16 -1
View File
@@ -159,7 +159,7 @@ func NewService(cfg *config.Config) (*Service, error) {
return svc, nil
}
// SchemaVersion 返回当前数据库 schema 版本(goose)。
// SchemaVersion 返回启动时缓存的数据库 schema 版本(goose)。
func (s *Service) SchemaVersion() int64 {
if s == nil {
return 0
@@ -167,6 +167,21 @@ func (s *Service) SchemaVersion() int64 {
return s.schemaVersion
}
// LiveSchemaVersion 从数据库实时读取当前 schema 版本;失败时回退缓存值。
func (s *Service) LiveSchemaVersion(ctx context.Context) int64 {
if s == nil {
return 0
}
if s.pool == nil {
return s.schemaVersion
}
ver, err := db.CurrentVersion(ctx, s.pool)
if err != nil {
return s.schemaVersion
}
return ver
}
// DatabaseReady 数据库是否已连接且迁移完成。
func (s *Service) DatabaseReady() bool {
return s != nil && s.pool != nil
+3 -3
View File
@@ -145,9 +145,9 @@ func TestExecuteStageV01AndV05(t *testing.T) {
if stage2Res.AdvisorBrief.MaturityLevel != "V0.5" {
t.Errorf("expected V0.5 maturity level, got %s", stage2Res.AdvisorBrief.MaturityLevel)
}
// Stage 2 包含 3 个任务 (research, feasibility_reg, prelim_design)
if len(stage2Res.SubArtifacts) != 3 {
t.Errorf("expected 3 sub artifacts in stage 2, got %d", len(stage2Res.SubArtifacts))
// Stage V0.5 包含 5 个任务(与 SupportedStages 一致)
if len(stage2Res.SubArtifacts) != 5 {
t.Errorf("expected 5 sub artifacts in stage 2, got %d", len(stage2Res.SubArtifacts))
}
// 3. 运行 Stage 3 (V1.0 Quoting),透传全部前序累积
+25 -18
View File
@@ -522,28 +522,35 @@ func TestChatSession_SemanticMemoryRecallAndAsyncTurnRecord(t *testing.T) {
t.Errorf("expected snippet to contain YY 0505, got: %s", session.UserCtx.RecalledMemorySnippet)
}
// 4. 等待异步写入 goroutine 落盘
time.Sleep(100 * time.Millisecond)
// 5. 验证本轮当场生成的问答对是否已被向量记忆库索引
results, err := ctxMgr.SearchChatMemory(ctx, ctxmgr.ChatMemorySearchOptions{
Query: "电磁兼容样机数量",
UserID: 99,
ProjectID: 200,
TopK: 5,
MinScore: 0.1,
})
if err != nil {
t.Fatalf("SearchChatMemory failed: %v", err)
}
// 4. 等待异步写入 goroutine 落盘(轮询至 deadline,避免固定 sleep 竞态)
deadline := time.Now().Add(2 * time.Second)
var results []ctxmgr.RecalledMemoryItem
foundCurrentTurn := false
for _, r := range results {
if r.SessionID == "sess_current_recall" {
foundCurrentTurn = true
for time.Now().Before(deadline) {
var searchErr error
results, searchErr = ctxMgr.SearchChatMemory(ctx, ctxmgr.ChatMemorySearchOptions{
Query: "电磁兼容样机数量",
UserID: 99,
ProjectID: 200,
TopK: 5,
MinScore: 0.1,
})
if searchErr != nil {
t.Fatalf("SearchChatMemory failed: %v", searchErr)
}
for _, r := range results {
if r.SessionID == "sess_current_recall" {
foundCurrentTurn = true
break
}
}
if foundCurrentTurn {
break
}
time.Sleep(20 * time.Millisecond)
}
// 5. 验证本轮当场生成的问答对是否已被向量记忆库索引
if !foundCurrentTurn {
t.Errorf("expected current turn to be asynchronously indexed into chat memory")
}
+13
View File
@@ -93,6 +93,19 @@ func (e *CoordinatorEngine) executeIntentPlan(
if pendingConfirm != nil && userCtx.PendingExpectation != nil {
last.NextExpectation = userCtx.PendingExpectation
}
slots := map[string]string{}
if recRes.TopIntent != nil && len(recRes.TopIntent.FilledSlots) > 0 {
slots = recRes.TopIntent.FilledSlots
}
if last.AdvisorBrief == nil {
last.AdvisorBrief = userCtx.LatestAdvisorBrief
}
if last.AdvisorBrief == nil {
last.AdvisorBrief = synthesizeBaselineBrief(userCtx)
}
e.enrichAdvisorBriefWithCases(ctx, last.AdvisorBrief, userCtx, slots)
return last, nil
}
@@ -38,6 +38,9 @@ func TestEngine_IntentPlan_QueryThenQuery(t *testing.T) {
if !strings.Contains(res.NaturalReply, "步骤") {
t.Errorf("expected multi-step reply, got: %s", res.NaturalReply)
}
if res.AdvisorBrief == nil {
t.Fatal("expected AdvisorBrief on multi-intent plan result")
}
}
func TestEngine_ExpectationPlusNew_ExecutesQueryKeepsConfirm(t *testing.T) {
+7
View File
@@ -2,6 +2,7 @@ package kb_test
import (
"context"
"os"
"strings"
"testing"
"time"
@@ -11,6 +12,12 @@ import (
)
func TestKB_RealIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping KB real integration in short mode")
}
if os.Getenv("RUN_KB_INTEGRATION") != "1" {
t.Skip("set RUN_KB_INTEGRATION=1 to run KB real integration")
}
cfg, err := config.Load("")
if err != nil {
t.Fatalf("failed to load config: %v", err)
+4 -1
View File
@@ -87,7 +87,10 @@ func TestUploadProjectAsset_RejectsOversizedFilename(t *testing.T) {
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/projects/7/assets", &body)
req.Header.Set("Content-Type", w.FormDataContentType())
withBearerAuth(req, loginTestAdmin(t, srv))
resp, _ := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusUnsupportedMediaType {
t.Fatalf("expected 415 for long filename, got %d", resp.StatusCode)
+5 -1
View File
@@ -91,7 +91,11 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
dbReady := s.svc != nil && s.svc.DatabaseReady()
schemaVersion := int64(0)
if s.svc != nil {
schemaVersion = s.svc.SchemaVersion()
if dbReady {
schemaVersion = s.svc.LiveSchemaVersion(context.Background())
} else {
schemaVersion = s.svc.SchemaVersion()
}
}
schemaReady := schemaVersion > 0 || (s.cfg != nil && s.cfg.AllowInMemory)
taskStoreReady := s.svc != nil && taskrun.TablesReady(context.Background(), s.svc.Pool())