项目文件夹

文件
2026-07-13 13:00:08 +08:00

640 行
24 KiB
Go

此文件含有不可见的 Unicode 字符
此文件含有人类无法区分的不可见的 Unicode 字符,但可以由计算机进行不同的处理。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
package evidence
import (
"context"
"encoding/json"
"testing"
)
func TestLedgerRecordsSuccessAndFailureReceipts(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "bash",
Args: json.RawMessage(`{"command":"go test ./..."}`),
Success: true,
Command: "go test ./...",
})
ledger.Record(Receipt{
ToolName: "bash",
Args: json.RawMessage(`{"command":"go test ./internal/..."}`),
Success: false,
Command: "go test ./internal/...",
})
if !ledger.HasSuccessfulCommand("go test ./...") {
t.Fatal("successful bash command should verify")
}
if ledger.HasSuccessfulCommand("go test ./internal/...") {
t.Fatal("failed bash command must not verify")
}
}
func TestLedgerHasWriteOrCommandSince(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{ToolName: "todo_write", Success: true, Todos: []TodoItem{{Content: "edit", Status: "in_progress"}}})
ledger.Record(Receipt{ToolName: "write_file", Success: true, Write: true, Paths: []string{"a.go"}})
ledger.Record(Receipt{ToolName: "bash", Success: false, Command: "go test ./..."})
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "edit"})
if got := ledger.Len(); got != 4 {
t.Fatalf("Len() = %d, want 4", got)
}
if !ledger.HasWriteOrCommandSince(0) {
t.Fatal("write receipt at index 1 should count from index 0")
}
if !ledger.HasWriteOrCommandSince(-1) {
t.Fatal("negative index should behave like 0")
}
if ledger.HasWriteOrCommandSince(2) {
t.Fatal("failed command and bookkeeping receipts must not count as progress")
}
ledger.Record(Receipt{ToolName: "bash", Success: true, Command: "go test ./..."})
if !ledger.HasWriteOrCommandSince(2) {
t.Fatal("successful command receipt after index should count as progress")
}
var nilLedger *Ledger
if nilLedger.HasWriteOrCommandSince(0) {
t.Fatal("nil ledger must report no progress")
}
}
func TestLedgerMatchesFileReadAndWriteReceipts(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{ToolName: "read_file", Success: true, Paths: []string{`internal/tool/builtin/completestep.go`}, Read: true})
ledger.Record(Receipt{ToolName: "write_file", Success: true, Paths: []string{`internal/evidence/evidence.go`}, Write: true})
ledger.Record(Receipt{ToolName: "edit_file", Success: false, Paths: []string{`failed.go`}, Write: true})
if !ledger.HasSuccessfulReadOrWrite([]string{`internal\tool\builtin\completestep.go`}) {
t.Fatal("read receipt should verify the same path across separators")
}
if !ledger.HasSuccessfulWrite([]string{`internal/evidence/evidence.go`}) {
t.Fatal("write receipt should verify written path")
}
if ledger.HasSuccessfulWrite([]string{`failed.go`}) {
t.Fatal("failed write receipt must not verify")
}
}
func TestLedgerReportsAnchorRefreshReadsAfterWrites(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{ToolName: "write_file", Success: true, Paths: []string{`src\a.go`}, Write: true})
writeIndex, ok := ledger.LatestSuccessfulWriteIndex([]string{`src/a.go`})
if !ok {
t.Fatal("expected latest write index")
}
if ledger.HasSuccessfulAnchorRefreshReadAfter([]string{`src/a.go`}, writeIndex) {
t.Fatal("read-after-write should be false before a read")
}
ledger.Record(Receipt{ToolName: "grep", Success: true, Paths: []string{`src/a.go`}, Read: true, Args: json.RawMessage(`{"path":"src/a.go","pattern":"func"}`)})
if ledger.HasSuccessfulAnchorRefreshReadAfter([]string{`src/a.go`}, writeIndex) {
t.Fatal("grep should not refresh anchor edit state")
}
ledger.Record(Receipt{ToolName: "read_file", Success: true, Paths: []string{`src/a.go`}, Read: true, Args: json.RawMessage(`{"path":"src/a.go","offset":100,"limit":20}`)})
if ledger.HasSuccessfulAnchorRefreshReadAfter([]string{`src/a.go`}, writeIndex) {
t.Fatal("windowed read_file should not refresh anchor edit state")
}
ledger.Record(Receipt{ToolName: "read_file", Success: true, Paths: []string{`src/a.go`}, Read: true, Args: json.RawMessage(`{"path":"src/a.go"}`)})
if !ledger.HasSuccessfulAnchorRefreshReadAfter([]string{`src/a.go`}, writeIndex) {
t.Fatal("read-after-write should be true after a successful read")
}
}
func TestLedgerReportsFinalReadinessReceiptsAfterWriter(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{ToolName: "bash", Success: true, Command: "go test ./..."})
ledger.Record(Receipt{ToolName: "write_file", Success: true, Paths: []string{"changed.go"}, Write: true})
ledger.Record(Receipt{ToolName: "bash", Success: false, Command: "git diff --check"})
ledger.Record(Receipt{ToolName: "todo_write", Success: true, Todos: []TodoItem{{Content: "Edit code", Status: "in_progress"}}})
writer, ok := ledger.LatestSuccessfulWriterIndex()
if !ok {
t.Fatal("expected latest successful writer")
}
if ledger.HasSuccessfulCommandAfter("go test ./...", writer) {
t.Fatal("command before latest writer must not satisfy final readiness")
}
if ledger.HasSuccessfulCommandAfter("git diff --check", writer) {
t.Fatal("failed command must not satisfy final readiness")
}
if ledger.HasSuccessfulCompleteStepAfter(writer) {
t.Fatal("missing complete_step must not satisfy final readiness")
}
if !ledger.HasSuccessfulTodoWrite() {
t.Fatal("successful todo_write receipt should be reported")
}
ledger.Record(Receipt{ToolName: "bash", Success: true, Command: "git diff --check"})
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "Edit code"})
if !ledger.HasSuccessfulCommandAfter("git diff --check", writer) {
t.Fatal("command after latest writer should satisfy final readiness")
}
if !ledger.HasSuccessfulCompleteStepAfter(writer) {
t.Fatal("complete_step after latest writer should satisfy final readiness")
}
}
func TestLedgerResetClearsTurnReceipts(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{ToolName: "bash", Success: true, Command: "go test ./..."})
ledger.Reset()
if ledger.HasSuccessfulCommand("go test ./...") {
t.Fatal("reset should clear prior-turn evidence")
}
}
func TestContextCarriesLedger(t *testing.T) {
ledger := NewLedger()
ctx := WithLedger(context.Background(), ledger)
got, ok := FromContext(ctx)
if !ok {
t.Fatal("ledger missing from context")
}
if got != ledger {
t.Fatal("context returned a different ledger")
}
}
func TestReceiptFromToolCallExtractsEvidenceFields(t *testing.T) {
bash := ReceiptFromToolCall("bash", json.RawMessage(`{"command":"git diff --check"}`), true, false)
if bash.Command != "git diff --check" {
t.Fatalf("bash command = %q", bash.Command)
}
if bash.Write {
t.Fatal("bash should not be treated as a verified file writer")
}
write := ReceiptFromToolCall("write_file", json.RawMessage(`{"path":"internal/evidence/evidence.go","content":"x"}`), true, false)
if !write.Write || len(write.Paths) != 1 || write.Paths[0] != `internal/evidence/evidence.go` {
t.Fatalf("write receipt not extracted: %+v", write)
}
read := ReceiptFromToolCall("read_file", json.RawMessage(`{"path":"internal/tool/builtin/completestep.go"}`), true, true)
if !read.Read || len(read.Paths) != 1 {
t.Fatalf("read receipt not extracted: %+v", read)
}
glob := ReceiptFromToolCall("glob", json.RawMessage(`{"pattern":"**/*.go"}`), true, true)
if !glob.Read {
t.Fatalf("generic read-only tool should be treated as read context: %+v", glob)
}
}
func TestReceiptFromToolCallExtractsTodoWriteItems(t *testing.T) {
receipt := ReceiptFromToolCall("todo_write", json.RawMessage(`{"todos":[
{"content":"Add parser","status":"in_progress","activeForm":"Adding parser"},
{"content":"Wire parser","status":"pending","level":1}
]}`), true, true)
if len(receipt.Todos) != 2 {
t.Fatalf("todos not extracted: %+v", receipt)
}
if receipt.Todos[0].Content != "Add parser" || receipt.Todos[0].Status != "in_progress" || receipt.Todos[0].ActiveForm != "Adding parser" {
t.Fatalf("first todo not extracted: %+v", receipt.Todos[0])
}
if receipt.Todos[1].Level != 1 {
t.Fatalf("todo level not extracted: %+v", receipt.Todos[1])
}
}
func TestReceiptFromToolCallExtractsCompleteStep(t *testing.T) {
receipt := ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"Add parser",
"result":"parser added",
"evidence":[{"kind":"manual","summary":"checked manually"}]
}`), true, true)
if receipt.Step != "Add parser" {
t.Fatalf("complete_step step = %q", receipt.Step)
}
if !receipt.StepProof {
t.Fatalf("complete_step evidence proof not extracted: %+v", receipt)
}
if receipt.Read {
t.Fatalf("complete_step should not be treated as read-only context: %+v", receipt)
}
missingResult := ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"Add parser",
"evidence":[{"kind":"manual","summary":"checked manually"}]
}`), false, true)
if missingResult.StepProof {
t.Fatalf("complete_step without result should not count as proof: %+v", missingResult)
}
missingCommand := ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"Add parser",
"result":"parser added",
"evidence":[{"kind":"verification","summary":"checked manually"}]
}`), false, true)
if missingCommand.StepProof {
t.Fatalf("verification evidence without command should not count as proof: %+v", missingCommand)
}
emptyProof := ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"Add parser",
"result":"parser added",
"evidence":[]
}`), false, true)
if emptyProof.StepProof {
t.Fatalf("empty complete_step evidence should not count as proof: %+v", emptyProof)
}
}
func TestReceiptFromToolCallExtractsCompleteStepIndex(t *testing.T) {
receipt := ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step_index":2,
"result":"done",
"evidence":[{"kind":"manual","summary":"checked"}]
}`), true, true)
if receipt.Step != "2" {
t.Fatalf("step index not extracted as step identity: %+v", receipt)
}
if !receipt.StepProof {
t.Fatalf("step proof not detected: %+v", receipt)
}
}
func TestLedgerMatchesLatestSuccessfulTodoStep(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: false,
Todos: []TodoItem{{Content: "Failed only", Status: "in_progress"}},
})
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Add parser", Status: "in_progress", ActiveForm: "Adding parser"},
{Content: "Wire parser", Status: "completed"},
{Content: "Document parser", Status: "pending"},
},
})
for _, step := range []string{"Add parser", "Adding parser", "2"} {
match, ok := ledger.MatchLatestTodoStep(step)
if !ok {
t.Fatalf("latest todo receipt missing for %q", step)
}
if !match.Found {
t.Fatalf("step %q did not match latest todo list", step)
}
if step == "2" && match.Content != "Wire parser" {
t.Fatalf("numeric step matched %q, want Wire parser", match.Content)
}
}
match, ok := ledger.MatchLatestTodoStep("Failed only")
if !ok {
t.Fatal("successful todo receipt should exist")
}
if match.Found {
t.Fatal("failed todo_write receipt must not match")
}
}
func TestMatchTodoStepToleratesCitationDrift(t *testing.T) {
// Verbatim shape from discussion #3970: todo authored with a fullwidth
// colon, cited back with a halfwidth one — and stuck forever.
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Phase 4:环境准备", Status: "completed"},
{Content: "Phase 5:脚本编辑与执行代码", Status: "in_progress"},
{Content: "Review notes", Status: "pending"},
},
})
matches := map[string]int{
"Phase 5: 脚本编辑与执行代码": 2,
"phase 5:脚本编辑与执行代码": 2,
" Phase 5:脚本编辑与执行代码": 2,
"脚本编辑与执行代码": 2,
"Phase 4:环境": 1,
"REVIEW NOTES": 3,
"": 2,
}
for step, want := range matches {
match, ok := ledger.MatchLatestTodoStep(step)
if !ok || !match.Found {
t.Fatalf("step %q should match todo %d, got found=%v", step, want, match.Found)
}
if match.Index != want {
t.Errorf("step %q matched todo %d, want %d", step, match.Index, want)
}
}
for _, step := range []string{"deploy backend", "代码", "Phase 9:不存在的阶段"} {
if match, _ := ledger.MatchLatestTodoStep(step); match.Found {
t.Errorf("step %q should not match, got todo %d (%q)", step, match.Index, match.Content)
}
}
}
func TestMatchTodoStepAmbiguousContainmentStaysUnmatched(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Deploy backend service", Status: "in_progress"},
{Content: "Deploy backend worker", Status: "pending"},
},
})
if match, _ := ledger.MatchLatestTodoStep("Deploy backend"); match.Found {
t.Fatalf("ambiguous citation should stay unmatched, got todo %d (%q)", match.Index, match.Content)
}
if match, _ := ledger.MatchLatestTodoStep("Deploy backend worker"); !match.Found || match.Index != 2 {
t.Fatal("exact citation must still resolve despite shared prefix")
}
}
func TestLedgerRequiresCompleteStepForNewCompletedTodos(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Add parser", Status: "in_progress"},
{Content: "Already done", Status: "completed"},
},
})
current := []TodoItem{
{Content: "Add parser", Status: "completed"},
{Content: "Already done", Status: "completed"},
}
missing, hasBaseline := ledger.UnverifiedCompletedTodos(current)
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 1 || missing[0].Content != "Add parser" {
t.Fatalf("missing = %+v, want only Add parser", missing)
}
ledger.Record(Receipt{ToolName: "complete_step", Success: false, Step: "Add parser"})
missing, hasBaseline = ledger.UnverifiedCompletedTodos(current)
if !hasBaseline {
t.Fatal("expected prior todo_write baseline after failed complete_step")
}
if len(missing) != 1 || missing[0].Content != "Add parser" {
t.Fatalf("failed complete_step without proof-bearing recovery should not authorize completion, missing = %+v", missing)
}
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "Add parser"})
missing, hasBaseline = ledger.UnverifiedCompletedTodos(current)
if !hasBaseline {
t.Fatal("expected prior todo_write baseline after successful complete_step")
}
if len(missing) != 0 {
t.Fatalf("successful complete_step should authorize completion, missing = %+v", missing)
}
}
func TestLedgerMatchesCompletionByActiveFormAndNumber(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Add parser", Status: "in_progress", ActiveForm: "Adding parser"},
{Content: "Wire parser", Status: "in_progress"},
},
})
current := []TodoItem{
{Content: "Add parser", Status: "completed", ActiveForm: "Adding parser"},
{Content: "Wire parser", Status: "in_progress"},
}
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "Adding parser"})
missing, hasBaseline := ledger.UnverifiedCompletedTodos(current)
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 0 {
t.Fatalf("activeForm complete_step should authorize completion, missing = %+v", missing)
}
current = []TodoItem{
{Content: "Add parser", Status: "completed", ActiveForm: "Adding parser"},
{Content: "Wire parser", Status: "completed"},
}
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "2"})
missing, hasBaseline = ledger.UnverifiedCompletedTodos(current)
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 0 {
t.Fatalf("numeric complete_step should authorize completion, missing = %+v", missing)
}
}
func TestLedgerNumericCompleteStepDoesNotAuthorizeReplacedTodo(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{{Content: "Add parser", Status: "in_progress"}},
})
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "1"})
missing, hasBaseline := ledger.UnverifiedCompletedTodos([]TodoItem{
{Content: "Ship parser", Status: "completed"},
})
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 1 || missing[0].Content != "Ship parser" {
t.Fatalf("numeric complete_step should not authorize a replaced todo, missing = %+v", missing)
}
}
func TestLedgerNumericCompleteStepFollowsReorderedSignedTodo(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Add parser", Status: "in_progress"},
{Content: "Write tests", Status: "pending"},
},
})
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "1"})
missing, hasBaseline := ledger.UnverifiedCompletedTodos([]TodoItem{
{Content: "Write tests", Status: "pending"},
{Content: "Add parser", Status: "completed"},
})
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 0 {
t.Fatalf("numeric complete_step should follow the signed todo identity after reorder, missing = %+v", missing)
}
}
func TestLedgerNoBaselineDoesNotConstrainCompletedTodos(t *testing.T) {
ledger := NewLedger()
missing, hasBaseline := ledger.UnverifiedCompletedTodos([]TodoItem{
{Content: "Add parser", Status: "completed"},
})
if hasBaseline {
t.Fatal("empty ledger should not report a prior todo_write baseline")
}
if len(missing) != 0 {
t.Fatalf("no baseline should not report missing completions, got %+v", missing)
}
}
func TestLedgerNumericCompleteStepAuthorizesRephrasedTodo(t *testing.T) {
ledger := NewLedger()
ledger.Record(Receipt{
ToolName: "todo_write",
Success: true,
Todos: []TodoItem{
{Content: "Add parser", Status: "in_progress"},
{Content: "Write tests", Status: "pending"},
},
})
ledger.Record(Receipt{ToolName: "complete_step", Success: true, Step: "1"})
// The model rephrased item 1 (added detail) but it's the same task.
missing, hasBaseline := ledger.UnverifiedCompletedTodos([]TodoItem{
{Content: "Add parser with streaming support", Status: "completed"},
{Content: "Write tests", Status: "pending"},
})
if !hasBaseline {
t.Fatal("expected prior todo_write baseline")
}
if len(missing) != 0 {
t.Fatalf("rephrased todo at same index should be authorized by content overlap, missing = %+v", missing)
}
// The model also rephrased item 2; still ok because the new text contains the old.
missing, hasBaseline = ledger.UnverifiedCompletedTodos([]TodoItem{
{Content: "Add parser with streaming support", Status: "completed"},
{Content: "Write tests and benchmarks", Status: "completed"},
})
if !hasBaseline {
t.Fatal("expected prior todo_write baseline for second rephrase")
}
// Item 1 is already authorized; item 2 is also rephrased but lacks a
// complete_step — so it should still be flagged.
if len(missing) != 1 || missing[0].Content != "Write tests and benchmarks" {
t.Fatalf("rephrased todo without complete_step should still be missing, got %+v", missing)
}
}
func TestToolCallMutatesForDeliveryProfile(t *testing.T) {
tests := []struct {
name string
toolName string
args string
readOnly bool
want bool
}{
{name: "trusted reader", toolName: "read_file", args: `{"path":"a.go"}`, readOnly: true},
{name: "file writer", toolName: "edit_file", args: `{"path":"a.go"}`, want: true},
{name: "delegated task meta", toolName: "task", args: `{"prompt":"fix it"}`},
{name: "run_skill meta", toolName: "run_skill", args: `{"name":"review"}`},
{name: "review meta", toolName: "review", args: `{"task":"review changes"}`},
{name: "security_review meta", toolName: "security_review", args: `{"task":"security"}`},
{name: "use_capability meta", toolName: "use_capability", args: `{"action":"inspect","capability_id":"mcp-server:github"}`},
{name: "test command", toolName: "bash", args: `{"command":"go test ./..."}`},
{name: "diff review", toolName: "bash", args: `{"command":"git diff --check"}`},
{name: "formatter write", toolName: "bash", args: `{"command":"gofmt -w internal/a.go"}`, want: true},
{name: "file redirect", toolName: "bash", args: `{"command":"printf x > generated.txt"}`, want: true},
{name: "compound verification", toolName: "bash", args: `{"command":"go test ./... 2>&1 | tail -20"}`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToolCallMutates(tt.toolName, json.RawMessage(tt.args), tt.readOnly); got != tt.want {
t.Fatalf("ToolCallMutates(%q, %s) = %v, want %v", tt.toolName, tt.args, got, tt.want)
}
})
}
}
func TestToolCallRequiresDeliveryCriteriaForExecutionCommands(t *testing.T) {
if !ToolCallRequiresDeliveryCriteria("bash", json.RawMessage(`{"command":"go test ./..."}`), false) {
t.Fatal("verification command should require delivery acceptance criteria")
}
if !ToolCallRequiresDeliveryCriteria("bash", json.RawMessage(`{"command":"npm run test"}`), false) {
t.Fatal("npm run test should require delivery acceptance criteria")
}
if !ToolCallRequiresDeliveryCriteria("bash", json.RawMessage(`{"command":"git diff --check"}`), false) {
t.Fatal("git diff --check is a verification command and should require acceptance criteria")
}
}
func TestLedgerDeliverySignoffRequiresPostMutationVerificationAndReview(t *testing.T) {
ledger := NewLedger()
ledger.Record(ReceiptFromToolCall("todo_write", json.RawMessage(`{"todos":[{"content":"Ship parser","status":"in_progress"}]}`), true, true))
ledger.Record(ReceiptFromToolCall("edit_file", json.RawMessage(`{"path":"internal/parser.go"}`), true, false))
mutation, ok := ledger.LatestSuccessfulMutationIndex()
if !ok {
t.Fatal("expected mutation receipt")
}
ledger.Record(ReceiptFromToolCall("read_file", json.RawMessage(`{"path":"internal/parser.go"}`), true, true))
ledger.Record(ReceiptFromToolCall("bash", json.RawMessage(`{"command":"go test ./internal/..."}`), true, false))
ledger.Record(ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"Ship parser",
"result":"parser shipped",
"evidence":[{"kind":"verification","summary":"tests passed","command":"go test ./internal/..."}]
}`), true, true))
if !ledger.HasSuccessfulAcceptanceCriteria() {
t.Fatal("expected non-empty todo_write to establish acceptance criteria")
}
if !ledger.HasSuccessfulReviewAfter(mutation) {
t.Fatal("expected post-mutation read to satisfy review")
}
if !ledger.HasSuccessfulDeliverySignoffAfter(mutation) {
t.Fatal("expected post-mutation verification cited by complete_step")
}
}
func TestLedgerDeliverySignoffRejectsPreMutationVerification(t *testing.T) {
ledger := NewLedger()
ledger.Record(ReceiptFromToolCall("bash", json.RawMessage(`{"command":"go test ./..."}`), true, false))
ledger.Record(ReceiptFromToolCall("edit_file", json.RawMessage(`{"path":"main.go"}`), true, false))
mutation, ok := ledger.LatestSuccessfulMutationIndex()
if !ok {
t.Fatal("expected mutation receipt")
}
ledger.Record(ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"change",
"result":"changed",
"evidence":[{"kind":"verification","summary":"tests passed before edit","command":"go test ./..."}]
}`), true, true))
if ledger.HasSuccessfulDeliverySignoffAfter(mutation) {
t.Fatal("pre-mutation verification must not sign off changed work")
}
}
func TestLedgerDeliverySignoffRejectsInspectionCommandMasqueradingAsVerification(t *testing.T) {
ledger := NewLedger()
ledger.Record(ReceiptFromToolCall("edit_file", json.RawMessage(`{"path":"main.go"}`), true, false))
mutation, ok := ledger.LatestSuccessfulMutationIndex()
if !ok {
t.Fatal("expected mutation receipt")
}
ledger.Record(ReceiptFromToolCall("bash", json.RawMessage(`{"command":"git status --short"}`), true, false))
ledger.Record(ReceiptFromToolCall("complete_step", json.RawMessage(`{
"step":"change",
"result":"changed",
"evidence":[{"kind":"verification","summary":"claimed verification","command":"git status --short"}]
}`), true, true))
if ledger.HasSuccessfulDeliverySignoffAfter(mutation) {
t.Fatal("inspection-only git status must not count as delivery verification")
}
}