package core import ( "encoding/json" "strings" "testing" ) // Loss of the optional task object makes refresh discard stage findings. func TestTaskSnapshotCardRoundTrip(t *testing.T) { raw := []byte(`{"id":"card1","projectId":"p1","conversationId":"c1","kind":"progress","status":"active","task":{"schemaVersion":1,"taskId":"t1","attemptId":"a1","cardId":"card1","revision":4,"executionState":"running","focus":"核对材料","findings":[{"id":"f1","text":"日期不同","kind":"material_record","status":"active","revision":1}]}}`) var card ActivityCard if err := json.Unmarshal(raw, &card); err != nil { t.Fatal(err) } encoded, err := json.Marshal(card) if err != nil { t.Fatal(err) } var restored map[string]json.RawMessage if err := json.Unmarshal(encoded, &restored); err != nil { t.Fatal(err) } if _, ok := restored["task"]; !ok { t.Fatal("task findings were lost in card persistence") } var task map[string]json.RawMessage if err := json.Unmarshal(restored["task"], &task); err != nil { t.Fatal(err) } if string(task["revision"]) != "4" || string(task["focus"]) != `"核对材料"` { t.Fatal("revision/focus did not survive") } } func validTaskSnapshot() TaskSnapshot { return TaskSnapshot{SchemaVersion: 1, TaskID: "t1", AttemptID: "a1", CardID: "card1", ProjectID: "p1", ConversationID: "c1", OwnerInstanceID: "i1", Revision: 1, ExecutionState: "queued", OutcomeCompleteness: "none", DeliveryState: "pending", StreamScope: "conversation:c1", Title: "整理材料"} } func TestTaskSnapshotRejectsUnsafeAndUnboundState(t *testing.T) { for _, mutate := range []func(*TaskSnapshot){ func(s *TaskSnapshot) { s.TaskID = "" }, func(s *TaskSnapshot) { s.AttemptID = "" }, func(s *TaskSnapshot) { s.Revision = 9007199254740992 }, func(s *TaskSnapshot) { s.SourceSeq = -1 }, func(s *TaskSnapshot) { s.ExecutionState = "imagined" }, func(s *TaskSnapshot) { s.StreamScope = "conversation:other" }, func(s *TaskSnapshot) { s.ExecutionState = "running" }, } { s := validTaskSnapshot() mutate(&s) if s.Validate() == nil { t.Errorf("invalid snapshot accepted: %+v", s) } } if err := validTaskSnapshot().Validate(); err != nil { t.Fatal(err) } } func TestWorkCheckpointRejectsVerifiedAndEmptyEvidence(t *testing.T) { c := WorkCheckpointCandidate{CheckpointID: "cp1", PhaseID: "crosscheck", Findings: []WorkFindingCandidate{{FindingKey: "date", Text: "日期不同", Kind: "verified", Sources: []TaskSourceQuote{{SourceID: "src1", Quote: "六月"}}}}} if err := ValidateWorkCheckpoint(c); err == nil || !strings.Contains(err.Error(), "kind") { t.Fatal("model granted verified status", err) } c.Findings[0].Kind = "material_record" c.Findings[0].Sources = nil if ValidateWorkCheckpoint(c) == nil { t.Fatal("source-free fact accepted") } c.Findings[0].Sources = []TaskSourceQuote{{SourceID: "src1", Quote: "六月"}} if err := ValidateWorkCheckpoint(c); err != nil { t.Fatal(err) } c.Findings[0].Text = strings.Repeat("x", 17000) if ValidateWorkCheckpoint(c) == nil { t.Fatal("unbounded candidate accepted") } } func TestWorkCheckpointEvidenceModesAndBounds(t *testing.T) { base := func() WorkCheckpointCandidate { return WorkCheckpointCandidate{CheckpointID: "cp-evidence", PhaseID: "crosscheck", Findings: []WorkFindingCandidate{{ FindingKey: "phase", Text: "待核对", Kind: "needs_user", Sources: []TaskSourceQuote{{SourceID: "total", Quote: "总12项"}, {SourceID: "phase", Quote: "一期8项"}, {SourceID: "dependent", Quote: "其中3项依赖接口"}}, }}} } n := func(v int) *int { return &v } total := WorkEvidencePremise{Role: "total", SourceIndex: 0, Value: n(12)} phase := WorkEvidencePremise{Role: "phase", SourceIndex: 1, Value: n(8)} dependent := WorkEvidencePremise{Role: "dependent", SourceIndex: 2, Value: n(3)} for _, tc := range []struct { name string evidence WorkFindingEvidence valid bool }{ {"direct", WorkFindingEvidence{Mode: "direct_relation", Premises: []WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}, true}, {"difference", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{total, phase}, Result: n(4)}, true}, {"unresolved", WorkFindingEvidence{Mode: "unresolved_relation", Premises: []WorkEvidencePremise{total, phase, dependent}}, true}, {"unknown mode", WorkFindingEvidence{Mode: "prediction", Premises: []WorkEvidencePremise{total, phase}}, false}, {"missing result", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{total, phase}}, false}, {"extra result", WorkFindingEvidence{Mode: "unresolved_relation", Premises: []WorkEvidencePremise{total, phase, dependent}, Result: n(3)}, false}, {"duplicate role", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{total, total}, Result: n(4)}, false}, {"missing premise", WorkFindingEvidence{Mode: "unresolved_relation", Premises: []WorkEvidencePremise{total, phase}}, false}, {"negative index", WorkFindingEvidence{Mode: "direct_relation", Premises: []WorkEvidencePremise{{Role: "direct_relation", SourceIndex: -1}}}, false}, {"foreign index", WorkFindingEvidence{Mode: "direct_relation", Premises: []WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 3}}}, false}, {"negative value", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(-1)}, phase}, Result: n(4)}, false}, {"large value", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(1000001)}, phase}, Result: n(4)}, false}, {"direct value", WorkFindingEvidence{Mode: "direct_relation", Premises: []WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0, Value: n(1)}}}, false}, {"missing value", WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{{Role: "total", SourceIndex: 0}, phase}, Result: n(4)}, false}, } { t.Run(tc.name, func(t *testing.T) { c := base() c.Findings[0].Evidence = &tc.evidence err := ValidateWorkCheckpoint(c) if (err == nil) != tc.valid { t.Fatalf("valid=%v err=%v", tc.valid, err) } }) } c := base() c.Findings[0].Evidence = &WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{total, phase}, Result: n(4)} c.Findings[0].Text = strings.Repeat("字", 5500) if err := ValidateWorkCheckpoint(c); err == nil || !strings.Contains(err.Error(), "16KiB") { t.Fatal("evidence bypassed checkpoint size cap", err) } } func TestTaskSnapshotBodyPreservesLimitsAndHonestStates(t *testing.T) { s := validTaskSnapshot() s.Remaining = "图片内容未纳入" s.ExecutionState = "failed" s.Conclusion = "处理失败,已有发现可查看" s.Findings = []TaskFinding{{ID: "f1", Text: "金额缺失", Kind: "material_record", Status: "active", Revision: 1}} raw, _ := json.Marshal(s.SummaryBody()) for _, want := range []string{"执行失败", "图片内容未纳入", "金额缺失", "处理失败"} { if !strings.Contains(string(raw), want) { t.Errorf("lost critical summary %s", want) } } } func TestTaskSnapshotUnstartedAndFinishedDoNotClaimReading(t *testing.T) { for _, state := range []string{"queued", "completed", "interrupted"} { s := validTaskSnapshot() s.ExecutionState = state s.Conclusion = "处理已结束" b, _ := json.Marshal(s.SummaryBody()) if strings.Contains(string(b), "正在读取") { t.Errorf("%s falsely claims active reading", state) } } }