Files
Agent-System-Backend/internal/store/workflow_step_source_test.go
weichen ecb86f4309 Recheck project workflow sources without storing prompts
Why: Goal-sensitive workflow verification persisted model prompts and prior output text in trusted receipt rows, while cross-step model inputs could reuse stale machine output.

Evidence: RED receipt privacy test; full Go suite, targeted race, isolated PostgreSQL store/runmgr/storage-migrate tests, vet, and static build passed locally before commit. A known unrelated TempDir cleanup flake failed once and passed on targeted and serial full replay.

Impact: Add optional same-provider source recheck, verify every included item and evidence body before reuse, gate cross-step outputs, reject unsettled attempts, remove raw scope columns, scrub isolated candidate v34 SQLite/PG schemas, and reject unsafe backups.

Refs: Approved RDM stage6 W6 source-recheck design adjustment r1; W6 independent review.
2026-10-14 05:38:11 +00:00

104 lines
4.4 KiB
Go

package store
import (
"crypto/sha256"
"path/filepath"
"testing"
"time"
"agent-system-backend/pkg/extension"
core "agent-system-core"
)
func TestWorkflowStepSourceRequiresRespondedManifestAndExactRunStep(t *testing.T) {
st, err := OpenSQLite(filepath.Join(t.TempDir(), "step.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
p, err := st.CreateProject("alice", "P")
if err != nil {
t.Fatal(err)
}
c, err := st.CreateConversation("alice", string(p.ID), "C", "")
if err != nil {
t.Fatal(err)
}
r := core.Run{Owner: "alice", ConversationID: c.ID, ProjectID: p.ID, Kind: core.RunConversation, Status: core.RunPending}
if err := st.CreateRun(&r); err != nil {
t.Fatal(err)
}
started := time.Now().Add(-time.Second)
m := extension.ProjectContextManifest{ProjectID: p.ID, AttemptID: "attempt-1", WorkflowStepAttemptID: "engine-run/draft/1", Delivery: "attempted", InputSHA256: "input", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1", Watermark: "w1"}
scope := ContextManifestScope{Bound: true}
if err := st.AppendContextManifestWithScope("alice", string(r.ID), m, scope); err != nil {
t.Fatal(err)
}
if _, err := st.BindWorkflowStepSource("alice", string(r.ID), "draft", "engine-run/draft/1", "prompt", started, []byte("old body")); err == nil {
t.Fatal("unanswered model attempt bound output")
}
unrelated := m
unrelated.AttemptID = "other-model-attempt"
unrelated.WorkflowStepAttemptID = "engine-run/other/1"
if err := st.AppendContextManifestWithScope("alice", string(r.ID), unrelated, scope); err != nil {
t.Fatal(err)
}
unrelated.Delivery = "responded"
if err := st.AppendContextManifestWithScope("alice", string(r.ID), unrelated, scope); err != nil {
t.Fatal(err)
}
if _, err := st.BindWorkflowStepSource("alice", string(r.ID), "draft", "engine-run/draft/1", "prompt", started, []byte("old body")); err == nil {
t.Fatal("another step's responded attempt bound draft output")
}
m.Delivery = "responded"
if err := st.AppendContextManifestWithScope("alice", string(r.ID), m, scope); err != nil {
t.Fatal(err)
}
bound, err := st.BindWorkflowStepSource("alice", string(r.ID), "draft", "engine-run/draft/1", "prompt", started, []byte("old body"))
if err != nil || bound.AttemptID != "attempt-1" || bound.StepAttemptID != "engine-run/draft/1" || bound.OutputSHA256 != sha256.Sum256([]byte("old body")) {
t.Fatalf("source binding missed actual output: %+v err=%v", bound, err)
}
if _, err := st.WorkflowStepSource("alice", string(r.ID), "other"); err == nil {
t.Fatal("another step reused output binding")
}
if _, err := st.WorkflowStepSource("bob", string(r.ID), "draft"); err == nil {
t.Fatal("another owner read output binding")
}
if _, err := st.BindWorkflowStepSource("alice", string(r.ID), "tool", "engine-run/tool/1", "tool", started, []byte("tool body")); err == nil {
t.Fatal("tool output bound as model-sourced material")
}
legacy := m
legacy.AttemptID, legacy.WorkflowStepAttemptID, legacy.Delivery = "legacy-model", "engine-run/legacy/1", "attempted"
if err := st.AppendContextManifest("alice", string(r.ID), legacy); err != nil {
t.Fatal(err)
}
legacy.Delivery = "responded"
if err := st.AppendContextManifest("alice", string(r.ID), legacy); err != nil {
t.Fatal(err)
}
if _, err := st.BindWorkflowStepSource("alice", string(r.ID), "legacy", "engine-run/legacy/1", "prompt", started, []byte("legacy body")); err == nil {
t.Fatal("model receipt without bound original Resolve scope became a workflow source")
}
unknown := m
unknown.AttemptID, unknown.Delivery = "unknown-model-attempt", "attempted"
if err := st.AppendContextManifestWithScope("alice", string(r.ID), unknown, scope); err != nil {
t.Fatal(err)
}
unknown.Delivery = "unknown"
if err := st.AppendContextManifestWithScope("alice", string(r.ID), unknown, scope); err != nil {
t.Fatal(err)
}
if _, err := st.RespondedWorkflowStepManifests("alice", string(r.ID), "engine-run/draft/1"); err == nil {
t.Fatal("unknown model attempt hidden by earlier responded attempt")
}
if _, err := st.BindWorkflowStepSource("alice", string(r.ID), "draft", "engine-run/draft/1", "prompt", started, []byte("old body")); err == nil {
t.Fatal("unknown model attempt rebound completed step")
}
if err := st.UnbindWorkflowStepSource("alice", string(r.ID), "draft"); err != nil {
t.Fatal(err)
}
if _, err := st.WorkflowStepSource("alice", string(r.ID), "draft"); err == nil {
t.Fatal("retry retained an earlier source binding")
}
}