Files
agent-exec-transport 837480d4c1
governance / governance (pull_request) Successful in 23s
ci / vet-and-test (pull_request) Successful in 1m26s
agent-exec transport fe547395130f262af6f15a09d8a4aa2d/1f4b762f53be
2026-10-20 16:15:37 +00:00

438 lines
31 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package runmgr
import (
core "agent-system-core"
"encoding/json"
"strings"
"testing"
)
func evidenceTestSources() materialSourceRegistry {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "需求.txt", Origin: &core.DocumentOrigin{ArtifactID: "req", ContentHash: "hash-req", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "上线六月。需求规定总预算为50万元,交付包含12项需求,其中3项依赖外部接口。"}}},
{Meta: core.DocMeta{Filename: "会议.txt", Origin: &core.DocumentOrigin{ArtifactID: "meeting", ContentHash: "hash-meeting", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "会议决定分两期交付,第一期先完成8项核心需求。总预算暂按50万元评估,但审批尚未完成。"}}},
{Meta: core.DocMeta{Filename: "评审.txt", Origin: &core.DocumentOrigin{ArtifactID: "review", ContentHash: "hash-review", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "评审意见要求上线七月以留出接口联调时间;不等于已批准增加经费。"}}},
}
return materialSources(docs, 48000)
}
func evidenceQuote(t *testing.T, sources materialSourceRegistry, artifact, quote string) core.TaskSourceQuote {
t.Helper()
for id, source := range sources {
if source.Ref.ArtifactID == artifact {
return core.TaskSourceQuote{SourceID: id, BlockIndex: source.Ref.BlockIndex, Quote: quote}
}
}
t.Fatalf("missing artifact %s", artifact)
return core.TaskSourceQuote{}
}
func evidenceCandidate(t *testing.T, sources materialSourceRegistry, mode string) core.WorkCheckpointCandidate {
t.Helper()
n := func(v int) *int { return &v }
return core.WorkCheckpointCandidate{CheckpointID: "cp-evidence", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{
FindingKey: "phase-list-gap", Text: "剩余4项包含全部3项接口依赖需求", Kind: "analysis", Assumptions: "把总量中的依赖项视作第二期清单",
Sources: []core.TaskSourceQuote{
evidenceQuote(t, sources, "req", "交付包含12项需求,其中3项依赖外部接口"),
evidenceQuote(t, sources, "meeting", "第一期先完成8项核心需求"),
},
Evidence: &core.WorkFindingEvidence{Mode: mode, Premises: []core.WorkEvidencePremise{
{Role: "total", SourceIndex: 0, Value: n(12)},
{Role: "phase", SourceIndex: 1, Value: n(8)},
{Role: "dependent", SourceIndex: 0, Value: n(3)},
}},
}}}
}
func TestMaterialEvidenceUnsupportedMembershipBecomesPending(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "unresolved_relation")
normalized, notes, err := normalizeMaterialEvidence(candidate, sources)
if err != nil {
t.Fatal(err)
}
f := normalized.Findings[0]
if f.Kind != "needs_user" || strings.Contains(f.Text+f.Impact+f.Assumptions, "包含全部3项") || !strings.Contains(f.Text, "0–3") {
t.Fatalf("unsupported membership escaped: %+v", f)
}
if len(notes) == 0 || !strings.Contains(strings.Join(notes, " "), "待确认") {
t.Fatal("model did not receive downgrade receipt", notes)
}
}
func TestMaterialEvidenceDifferenceUsesOnlyVerifiedCounts(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
result := 4
candidate.Findings[0].Evidence.Result = &result
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil {
t.Fatal(err)
}
f := normalized.Findings[0]
if f.Kind != "analysis" || !strings.Contains(f.Text, "12−8=4") || !strings.Contains(f.Text, "推算") || strings.Contains(f.Text+f.Impact+f.Assumptions, "包含全部3项") {
t.Fatalf("difference was not safely rewritten: %+v", f)
}
result = 5
candidate.Findings[0].Evidence.Result = &result
normalized, _, err = normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("wrong model result became a fact: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceDirectRelationRequiresLiteralQuote(t *testing.T) {
docs := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "决定.txt", Origin: &core.DocumentOrigin{ArtifactID: "direct", ContentHash: "hash-direct", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第二期包含3项依赖外部接口的需求。接口负责人仍待确认。"}}}}
sources := materialSources(docs, 48000)
quote := evidenceQuote(t, sources, "direct", "第二期包含3项依赖外部接口的需求")
candidate := core.WorkCheckpointCandidate{CheckpointID: "direct", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "relation", Text: "模型概括", Kind: "material_record", Sources: []core.TaskSourceQuote{quote}, Evidence: &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "material_record" || normalized.Findings[0].Text != quote.Quote {
t.Fatalf("explicit relation was lost: %+v %v", normalized, err)
}
candidate.Findings[0].Sources[0].Quote = quote.Quote + "。"
normalized, _, err = normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "material_record" {
t.Fatalf("complete sentence with punctuation was lost: %+v %v", normalized, err)
}
candidate.Findings[0].Sources[0].Quote = "第二期"
normalized, _, err = normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("incomplete relation was promoted: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceDirectRelationCannotCarryUncheckedExtraQuote(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "决定.txt", Origin: &core.DocumentOrigin{ArtifactID: "direct", ContentHash: "direct-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第二期包含3项依赖外部接口的需求。"}}},
{Meta: core.DocMeta{Filename: "评审.txt", Origin: &core.DocumentOrigin{ArtifactID: "review", ContentHash: "review-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "评审认为不等于已批准增加经费。"}}},
}
sources := materialSources(docs, 48000)
candidate := core.WorkCheckpointCandidate{CheckpointID: "extra-quote", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "relation", Text: "已批准", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "direct", "第二期包含3项依赖外部接口的需求"), evidenceQuote(t, sources, "review", "已批准")}, Evidence: &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("unchecked second quote escaped direct relation: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceRejectsUnknownNumericRevisionUnit(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "旧范围.txt", Origin: &core.DocumentOrigin{ArtifactID: "total", ContentHash: "total-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "总12项需求,现为13个。"}}},
{Meta: core.DocMeta{Filename: "阶段.txt", Origin: &core.DocumentOrigin{ArtifactID: "phase", ContentHash: "phase-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第一期先完成8项核心需求。"}}},
}
sources := materialSources(docs, 48000)
n := func(v int) *int { return &v }
candidate := core.WorkCheckpointCandidate{CheckpointID: "revised-unit", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "gap", Text: "差额4项", Kind: "analysis", Assumptions: "按旧值推算", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "total", "总12项需求"), evidenceQuote(t, sources, "phase", "第一期先完成8项核心需求")}, Evidence: &core.WorkFindingEvidence{Mode: "difference", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}}, Result: n(4)}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("unknown revised unit was ignored: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceRejectsMixedUnitsAndAmbiguousPhaseCounts(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
result := 4
candidate.Findings[0].Evidence.Result = &result
candidate.Findings[0].Sources[1].Quote = "总预算暂按50万元评估"
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("budget amount used as requirement count: %+v %v", normalized, err)
}
docs := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "阶段.txt", Origin: &core.DocumentOrigin{ArtifactID: "phase", ContentHash: "h", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第一期8项、第二期4项"}}}}
phaseSources := materialSources(docs, 48000)
candidate = evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
candidate.Findings[0].Evidence.Premises[1].Value = &result
candidate.Findings[0].Evidence.Result = &result
candidate.Findings[0].Sources[1] = evidenceQuote(t, phaseSources, "phase", "第一期8项、第二期4项")
for k, v := range phaseSources {
sources[k] = v
}
normalized, _, err = normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("second-phase number used as first-phase count: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceDoesNotCountBudgetItemsAsRequirements(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
result := 4
candidate.Findings[0].Evidence.Result = &result
for _, tc := range []struct {
artifact string
text string
index int
}{
{"budget-total", "项目共有12项预算科目", 0},
{"budget-phase", "第一期先完成8项预算条目", 1},
} {
doc := core.ParsedDocument{Meta: core.DocMeta{Filename: tc.artifact + ".txt", Origin: &core.DocumentOrigin{ArtifactID: tc.artifact, ContentHash: tc.artifact, ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.text}}}
for id, src := range materialSources([]core.ParsedDocument{doc}, 48000) {
sources[id] = src
}
cp := candidate
cp.Findings = append([]core.WorkFindingCandidate(nil), candidate.Findings...)
cp.Findings[0].Sources = append([]core.TaskSourceQuote(nil), candidate.Findings[0].Sources...)
cp.Findings[0].Sources[tc.index] = evidenceQuote(t, sources, tc.artifact, tc.text)
normalized, _, err := normalizeMaterialEvidence(cp, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("%s was treated as requirement count: %+v %v", tc.artifact, normalized, err)
}
}
}
func TestMaterialEvidenceHostTextDoesNotConsumeCandidateWireLimit(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
result := 4
candidate.Findings[0].Evidence.Result = &result
candidate.Findings[0].Text = "4"
candidate.Findings[0].Assumptions = "x"
raw, err := json.Marshal(candidate)
if err != nil {
t.Fatal(err)
}
candidate.Focus = strings.Repeat("x", 16*1024-len(raw)-25)
if err := core.ValidateWorkCheckpoint(candidate); err != nil {
t.Fatal("wire candidate must fit", err)
}
if _, _, err := normalizeMaterialEvidence(candidate, sources); err != nil {
t.Fatal("host-generated text should not be treated as a second wire candidate", err)
}
}
func TestMaterialEvidenceRejectsUnprovidedOrForeignSource(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "unresolved_relation")
candidate.Findings[0].Sources[0].SourceID = "foreign"
if _, _, err := normalizeMaterialEvidence(candidate, sources); err == nil {
t.Fatal("foreign source was accepted")
}
withoutOrigin := core.ParsedDocument{Meta: core.DocMeta{Filename: "未知.txt"}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "总12项"}}}
if len(materialSources([]core.ParsedDocument{withoutOrigin}, 48000)) != 0 {
t.Fatal("missing Origin produced a source")
}
doc := core.ParsedDocument{Meta: core.DocMeta{Filename: "截断.txt", Origin: &core.DocumentOrigin{ArtifactID: "short", ContentHash: "h", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "已提供前缀"}, {Type: core.BlockPara, Text: "隐藏的第二期包含3项"}}}
short := materialSources([]core.ParsedDocument{doc}, 5)
for _, src := range short {
if src.Ref.BlockIndex != 0 || strings.Contains(src.Provided, "第二期") {
t.Fatal("hidden block was registered", src)
}
}
}
func TestMaterialEvidenceDoesNotDropNegationOutsideShortQuote(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "评审.txt", Origin: &core.DocumentOrigin{ArtifactID: "review", ContentHash: "review-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "评审认为不等于已批准增加经费。"}}},
{Meta: core.DocMeta{Filename: "需求.txt", Origin: &core.DocumentOrigin{ArtifactID: "req", ContentHash: "req-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "并非总12项需求。"}}},
{Meta: core.DocMeta{Filename: "会议.txt", Origin: &core.DocumentOrigin{ArtifactID: "meeting", ContentHash: "meeting-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第一期先完成8项核心需求。"}}},
}
sources := materialSources(docs, 48000)
approval := core.WorkCheckpointCandidate{CheckpointID: "approval-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "approval", Text: "已批准", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "review", "已批准")}}}}
normalized, _, err := normalizeMaterialEvidence(approval, sources)
if err != nil || normalized.Findings[0].Kind != "material_record" || !strings.Contains(normalized.Findings[0].Sources[0].Quote, "不等于已批准") {
t.Fatalf("negation was removed from a source record: %+v %v", normalized, err)
}
approval.Findings[0].Evidence = &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}
normalized, _, err = normalizeMaterialEvidence(approval, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("negated approval was promoted as a direct relation: %+v %v", normalized, err)
}
n := func(v int) *int { return &v }
counts := core.WorkCheckpointCandidate{CheckpointID: "count-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "count", Text: "差额4项", Kind: "analysis", Assumptions: "据材料推算", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "req", "总12项需求"), evidenceQuote(t, sources, "meeting", "第一期先完成8项核心需求")}, Evidence: &core.WorkFindingEvidence{Mode: "difference", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}}, Result: n(4)}}}}
normalized, _, err = normalizeMaterialEvidence(counts, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("negated total was used in arithmetic: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceRestoresNegationForUnlistedDecisionWord(t *testing.T) {
docs := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "方案.txt", Origin: &core.DocumentOrigin{ArtifactID: "decision", ContentHash: "decision-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "方案尚未通过。"}}}}
sources := materialSources(docs, 48000)
candidate := core.WorkCheckpointCandidate{CheckpointID: "decision-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "decision", Text: "通过", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "decision", "通过")}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "material_record" || normalized.Findings[0].Sources[0].Quote != "方案尚未通过" {
t.Fatalf("short decision quote lost negation: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceRejectsProjectPrefixScopesAndCancelledCounts(t *testing.T) {
n := func(v int) *int { return &v }
for _, tc := range []struct {
name, total, phase, dependent string
}{
{"two scopes in one source", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口"},
{"cancelled total", "已取消总12项需求", "第一期先完成8项核心需求", "其中3项依赖外部接口"},
} {
t.Run(tc.name, func(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "范围.txt", Origin: &core.DocumentOrigin{ArtifactID: "total", ContentHash: "h-total", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.total}}},
{Meta: core.DocMeta{Filename: "阶段.txt", Origin: &core.DocumentOrigin{ArtifactID: "phase", ContentHash: "h-phase", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.phase}}},
{Meta: core.DocMeta{Filename: "依赖.txt", Origin: &core.DocumentOrigin{ArtifactID: "dependent", ContentHash: "h-dependent", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.dependent}}},
}
sources := materialSources(docs, 48000)
candidate := core.WorkCheckpointCandidate{CheckpointID: "unsafe-scope", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "scope", Text: "差额与归属确定", Kind: "analysis", Assumptions: "模型推断", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "total", tc.total), evidenceQuote(t, sources, "phase", tc.phase), evidenceQuote(t, sources, "dependent", tc.dependent)}, Evidence: &core.WorkFindingEvidence{Mode: "unresolved_relation", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}, {Role: "dependent", SourceIndex: 2, Value: n(12)}}}}}}
if tc.name == "two scopes in one source" {
candidate.Findings[0].Sources = candidate.Findings[0].Sources[:1]
candidate.Findings[0].Evidence.Premises[1].SourceIndex = 0
candidate.Findings[0].Evidence.Premises[2].SourceIndex = 0
} else {
candidate.Findings[0].Evidence.Mode = "difference"
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
candidate.Findings[0].Evidence.Result = n(4)
}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("unsafe scope/count was treated as proven: %+v %v", normalized, err)
}
})
}
}
func TestMaterialEvidenceDoesNotPromoteTruncatedVisibleSentence(t *testing.T) {
visible := "预算已批准"
doc := core.ParsedDocument{Meta: core.DocMeta{Filename: "预算.txt", Origin: &core.DocumentOrigin{ArtifactID: "budget", ContentHash: "budget-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "预算已批准只是传闻,尚未核实。"}}}
sources := materialSources([]core.ParsedDocument{doc}, len([]rune(visible)))
candidate := core.WorkCheckpointCandidate{CheckpointID: "truncated", PhaseID: "reading", Findings: []core.WorkFindingCandidate{{FindingKey: "approval", Text: "预算已批准", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "budget", "已批准")}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("unfinished sentence became a positive source record: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceRejectsRevisedNumberInSameSentence(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "旧范围.txt", Origin: &core.DocumentOrigin{ArtifactID: "total", ContentHash: "total-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "总12项需求,已调整为13项需求。"}}},
{Meta: core.DocMeta{Filename: "阶段.txt", Origin: &core.DocumentOrigin{ArtifactID: "phase", ContentHash: "phase-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第一期先完成8项核心需求。"}}},
}
sources := materialSources(docs, 48000)
n := func(v int) *int { return &v }
candidate := core.WorkCheckpointCandidate{CheckpointID: "revised-count", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "gap", Text: "差额4项", Kind: "analysis", Assumptions: "按旧值推算", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "total", "总12项需求"), evidenceQuote(t, sources, "phase", "第一期先完成8项核心需求")}, Evidence: &core.WorkFindingEvidence{Mode: "difference", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}}, Result: n(4)}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("revised number was used as current count: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceSingleSourceDoesNotProveUniqueMembership(t *testing.T) {
doc := core.ParsedDocument{Meta: core.DocMeta{Filename: "三项数字.txt", Origin: &core.DocumentOrigin{ArtifactID: "counts", ContentHash: "counts-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "总12项需求。第一期8项需求。其中12项依赖接口。"}}}
sources := materialSources([]core.ParsedDocument{doc}, 48000)
n := func(v int) *int { return &v }
quote := evidenceQuote(t, sources, "counts", "总12项需求。第一期8项需求。其中12项依赖接口")
candidate := core.WorkCheckpointCandidate{CheckpointID: "same-source", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "membership", Text: "接口依赖项都在第一期之外", Kind: "analysis", Assumptions: "同一来源", Sources: []core.TaskSourceQuote{quote}, Evidence: &core.WorkFindingEvidence{Mode: "unresolved_relation", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 0, Value: n(8)}, {Role: "dependent", SourceIndex: 0, Value: n(12)}}}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("source position was treated as a proof of common scope: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceSameExplicitScopeCanProveUniqueMembership(t *testing.T) {
doc := core.ParsedDocument{Meta: core.DocMeta{Filename: "A项目.txt", Origin: &core.DocumentOrigin{ArtifactID: "project-a", ContentHash: "project-a-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "项目A总12项需求,项目A第一期8项需求,项目A其中12项依赖接口。"}}}
sources := materialSources([]core.ParsedDocument{doc}, 48000)
n := func(v int) *int { return &v }
quote := evidenceQuote(t, sources, "project-a", "项目A总12项需求,项目A第一期8项需求,项目A其中12项依赖接口")
candidate := core.WorkCheckpointCandidate{CheckpointID: "same-named-scope", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "membership", Text: "第一期之外4项依赖接口", Kind: "analysis", Assumptions: "同名范围", Sources: []core.TaskSourceQuote{quote}, Evidence: &core.WorkFindingEvidence{Mode: "unresolved_relation", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 0, Value: n(8)}, {Role: "dependent", SourceIndex: 0, Value: n(12)}}}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "analysis" || !strings.Contains(normalized.Findings[0].Text, "4项") {
t.Fatalf("explicit same scope lost its calculable relation: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceDifferentExplicitScopesBecomePending(t *testing.T) {
docs := []core.ParsedDocument{
{Meta: core.DocMeta{Filename: "A.txt", Origin: &core.DocumentOrigin{ArtifactID: "req", ContentHash: "req-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "A项目总12项需求。"}}},
{Meta: core.DocMeta{Filename: "B.txt", Origin: &core.DocumentOrigin{ArtifactID: "meeting", ContentHash: "meeting-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "B项目第一期8项需求。"}}},
}
sources := materialSources(docs, 48000)
n := func(v int) *int { return &v }
candidate := core.WorkCheckpointCandidate{CheckpointID: "different-projects", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "gap", Text: "差额4项", Kind: "analysis", Assumptions: "模型推断", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "req", "总12项需求"), evidenceQuote(t, sources, "meeting", "第一期8项需求")}, Evidence: &core.WorkFindingEvidence{Mode: "difference", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}}, Result: n(4)}}}}
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("different project counts were subtracted: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceUnstructuredBatchPlacementBecomesPending(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence = nil
candidate.Findings[0].Text = "接口依赖工作均安排在后续批次"
candidate.Findings[0].Impact = "应直接进入下批联调"
candidate.Findings[0].Assumptions = "模型推断"
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" || strings.Contains(normalized.Findings[0].Text, "均安排") {
t.Fatalf("unstructured placement escaped: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceOnlyKeepsCautiousOrdinaryAnalysisWithoutProof(t *testing.T) {
sources := evidenceTestSources()
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence = nil
candidate.Findings[0].Assumptions = "基于所引材料"
candidate.Findings[0].Text = "两份材料存在差异,需核对"
candidate.Findings[0].Impact = ""
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "analysis" {
t.Fatalf("cautious ordinary analysis was lost: %+v %v", normalized, err)
}
candidate.Findings[0].Text = "外部依赖工作全放到下一轮"
normalized, _, err = normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("unproven paraphrased placement escaped: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceDoesNotCopyApprovalBehindCautionWord(t *testing.T) {
sources := evidenceTestSources()
for _, assertion := range []string{"方案已获准,建议执行", "方案已同意,需核对", "建议直接执行已获准方案"} {
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence = nil
candidate.Findings[0].Text = assertion
candidate.Findings[0].Impact = ""
candidate.Findings[0].Assumptions = "根据所引材料"
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("affirmative decision hid behind caution: %+v %v", normalized, err)
}
}
candidate := evidenceCandidate(t, sources, "difference")
candidate.Findings[0].Evidence = nil
candidate.Findings[0].Text = "两份材料存在差异,需核对"
candidate.Findings[0].Impact = "建议执行已获准方案"
candidate.Findings[0].Assumptions = "根据所引材料"
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
if err != nil || normalized.Findings[0].Kind != "analysis" || normalized.Findings[0].Impact != "" || strings.Contains(normalized.Findings[0].Assumptions, "已获准") {
t.Fatalf("ordinary cautious analysis retained unsafe ancillary prose: %+v %v", normalized, err)
}
}
func TestMaterialEvidenceAnalysisDirectRelationBecomesNeedsUser(t *testing.T) {
docs := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "决定.txt", Origin: &core.DocumentOrigin{ArtifactID: "direct", ContentHash: "hash-direct", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "第二期包含3项依赖外部接口的需求。接口负责人仍待确认。"}}}}
sources := materialSources(docs, 48000)
quote := evidenceQuote(t, sources, "direct", "第二期包含3项依赖外部接口的需求")
analysis := core.WorkCheckpointCandidate{CheckpointID: "analysis-direct", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{
FindingKey: "relation", Text: "第二期含接口依赖", Kind: "analysis", Assumptions: "按原句理解归属",
Sources: []core.TaskSourceQuote{quote},
Evidence: &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}},
}}}
normalized, _, err := normalizeMaterialEvidence(analysis, sources)
if err != nil || normalized.Findings[0].Kind != "needs_user" {
t.Fatalf("analysis+direct_relation was not downgraded: %+v %v", normalized, err)
}
record := core.WorkCheckpointCandidate{CheckpointID: "record-plain", PhaseID: "reading", Findings: []core.WorkFindingCandidate{{
FindingKey: "date", Text: "上线六月", Kind: "material_record",
Sources: []core.TaskSourceQuote{evidenceQuote(t, evidenceTestSources(), "req", "上线六月")},
}}}
normalized, _, err = normalizeMaterialEvidence(record, evidenceTestSources())
if err != nil || normalized.Findings[0].Kind != "material_record" {
t.Fatalf("material_record without evidence lost kind: %+v %v", normalized, err)
}
}