package store import ( "bytes" "os" "path/filepath" "strings" "testing" "agent-system-backend/pkg/extension" ) func TestContextManifestTerminalRequiresMatchingAttempt(t *testing.T) { st, err := OpenSQLite(filepath.Join(t.TempDir(), "receipt.db")) if err != nil { t.Fatal(err) } defer st.Close() m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "attempt-1", InputSHA256: "input-hash", Delivery: "responded"} if err := st.AppendContextManifest("alice", "run-1", m); err == nil { t.Fatal("terminal receipt without attempted send") } m.Delivery = "attempted" if err := st.AppendContextManifest("alice", "run-1", m); err != nil { t.Fatal(err) } if err := st.AppendContextManifest("alice", "run-1", m); err == nil { t.Fatal("duplicate attempted receipt") } m.Delivery = "responded" if err := st.AppendContextManifest("alice", "run-2", m); err == nil { t.Fatal("attempt reused across target") } if err := st.AppendContextManifest("alice", "run-1", m); err != nil { t.Fatal(err) } m.Delivery = "unknown" if err := st.AppendContextManifest("alice", "run-1", m); err == nil { t.Fatal("one attempt acquired two terminal states") } } func TestContextManifestDoesNotPersistWorkflowOutputInResolveScope(t *testing.T) { st, err := OpenSQLite(filepath.Join(t.TempDir(), "scope-privacy.db")) if err != nil { t.Fatal(err) } defer st.Close() const restricted = "OLD_RESTRICTED_BODY" m := extension.ProjectContextManifest{ProjectID: "project-1", AttemptID: "judge-attempt", InputSHA256: "input-hash", Delivery: "attempted"} scope := ContextManifestScope{Bound: true} if err := st.AppendContextManifestWithScope("alice", "run-1", m, scope); err != nil { t.Fatal(err) } m.Delivery = "responded" if err := st.AppendContextManifestWithScope("alice", "run-1", m, scope); err != nil { t.Fatal(err) } rows, err := st.db.Query(st.q(`SELECT delivery,manifest_json FROM context_manifests WHERE attempt_id=? ORDER BY created_at,id`), m.AttemptID) if err != nil { t.Fatal(err) } defer rows.Close() seen := 0 for rows.Next() { var delivery, manifest string if err := rows.Scan(&delivery, &manifest); err != nil { t.Fatal(err) } seen++ if strings.Contains(manifest, restricted) { t.Errorf("%s trusted receipt persisted workflow output body", delivery) } } if err := rows.Err(); err != nil { t.Fatal(err) } if seen != 2 { t.Fatalf("expected attempted and responded receipts, got %d", seen) } var rawColumns int if err := st.db.QueryRow(`SELECT COUNT(*) FROM pragma_table_info('context_manifests') WHERE name IN ('resolve_goal','resolve_target_id')`).Scan(&rawColumns); err != nil || rawColumns != 0 { t.Fatalf("fresh receipt schema retains raw scope columns: columns=%d err=%v", rawColumns, err) } } func TestCandidateV35RawScopeColumnsAreScrubbedOnUpgrade(t *testing.T) { path := filepath.Join(t.TempDir(), "candidate-v35.db") st, err := OpenSQLite(path) if err != nil { t.Fatal(err) } m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "old-model", InputSHA256: "input", Delivery: "attempted"} if err := st.AppendContextManifestWithScope("alice", "run-1", m, ContextManifestScope{Bound: true}); err != nil { t.Fatal(err) } for _, query := range []string{ `DELETE FROM schema_migrations WHERE version=36`, `ALTER TABLE context_manifests ADD COLUMN resolve_goal TEXT NOT NULL DEFAULT ''`, `ALTER TABLE context_manifests ADD COLUMN resolve_target_id TEXT NOT NULL DEFAULT ''`, `UPDATE context_manifests SET resolve_goal='OLD_RESTRICTED_BODY',resolve_target_id='doc-private'`, } { if _, err := st.db.Exec(query); err != nil { t.Fatal(err) } } if err := st.Close(); err != nil { t.Fatal(err) } upgraded, err := OpenSQLite(path) if err != nil { t.Fatal(err) } defer upgraded.Close() var columns int if err := upgraded.db.QueryRow(`SELECT COUNT(*) FROM pragma_table_info('context_manifests') WHERE name IN ('resolve_goal','resolve_target_id')`).Scan(&columns); err != nil || columns != 0 { t.Fatalf("candidate raw scope columns survived v36: columns=%d err=%v", columns, err) } var count int if err := upgraded.db.QueryRow(`SELECT COUNT(*) FROM context_manifests WHERE attempt_id='old-model'`).Scan(&count); err != nil || count != 1 { t.Fatalf("trusted manifest disappeared during scrub: count=%d err=%v", count, err) } for _, suffix := range []string{"", "-wal"} { data, err := os.ReadFile(path + suffix) if err == nil && bytes.Contains(data, []byte("OLD_RESTRICTED_BODY")) { t.Fatalf("candidate raw scope bytes remain in SQLite %s after upgrade", suffix) } } } func TestCandidateV35CommittedDropKeepsPendingScrubAcrossRestart(t *testing.T) { path := filepath.Join(t.TempDir(), "candidate-crash.db") st, err := OpenSQLite(path) if err != nil { t.Fatal(err) } m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "crash-model", InputSHA256: "input", Delivery: "attempted"} if err := st.AppendContextManifestWithScope("alice", "run-1", m, ContextManifestScope{Bound: true}); err != nil { t.Fatal(err) } for _, query := range []string{ `DELETE FROM schema_migrations WHERE version=36`, `ALTER TABLE context_manifests ADD COLUMN resolve_goal TEXT NOT NULL DEFAULT ''`, `ALTER TABLE context_manifests ADD COLUMN resolve_target_id TEXT NOT NULL DEFAULT ''`, `UPDATE context_manifests SET resolve_goal='OLD_RESTRICTED_BODY',resolve_target_id='doc-private'`, } { if _, err := st.db.Exec(query); err != nil { t.Fatal(err) } } // Apply only the transactional DDL and ledger. A process crash at this // boundary must not make the next open mistake the file for scrubbed. if err := st.migratePass(); err != nil { t.Fatal(err) } var pending int if err := st.db.QueryRow(`SELECT pending FROM context_scope_scrub_state WHERE id=1`).Scan(&pending); err != nil || pending != 1 { t.Fatalf("committed v36 lost physical scrub obligation: pending=%d err=%v", pending, err) } if err := st.Close(); err != nil { t.Fatal(err) } restarted, err := OpenSQLite(path) if err != nil { t.Fatal(err) } defer restarted.Close() if err := restarted.db.QueryRow(`SELECT pending FROM context_scope_scrub_state WHERE id=1`).Scan(&pending); err != nil || pending != 0 { t.Fatalf("restart did not complete pending scrub: pending=%d err=%v", pending, err) } for _, suffix := range []string{"", "-wal"} { data, err := os.ReadFile(path + suffix) if err == nil && bytes.Contains(data, []byte("OLD_RESTRICTED_BODY")) { t.Fatalf("restart left candidate raw bytes in SQLite %s", suffix) } } } func TestEarlierV36WithoutMarkerIsConservativelyRescrubbed(t *testing.T) { path := filepath.Join(t.TempDir(), "old-v36.db") st, err := OpenSQLite(path) if err != nil { t.Fatal(err) } m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "old-v36-model", InputSHA256: "input", Delivery: "attempted"} if err := st.AppendContextManifestWithScope("alice", "run-1", m, ContextManifestScope{Bound: true}); err != nil { t.Fatal(err) } for _, query := range []string{ `ALTER TABLE context_manifests ADD COLUMN resolve_goal TEXT NOT NULL DEFAULT ''`, `UPDATE context_manifests SET resolve_goal='OLD_RESTRICTED_BODY'`, `UPDATE context_manifests SET resolve_goal=''`, `ALTER TABLE context_manifests DROP COLUMN resolve_goal`, `DROP TABLE context_scope_scrub_state`, } { if _, err := st.db.Exec(query); err != nil { t.Fatal(err) } } if err := st.Close(); err != nil { t.Fatal(err) } restarted, err := OpenSQLite(path) if err != nil { t.Fatal(err) } defer restarted.Close() var pending int if err := restarted.db.QueryRow(`SELECT pending FROM context_scope_scrub_state WHERE id=1`).Scan(&pending); err != nil || pending != 0 { t.Fatalf("earlier v36 without marker was not re-scrubbed: pending=%d err=%v", pending, err) } for _, suffix := range []string{"", "-wal"} { data, err := os.ReadFile(path + suffix) if err == nil && bytes.Contains(data, []byte("OLD_RESTRICTED_BODY")) { t.Fatalf("old v36 raw bytes survived re-scrub in SQLite %s", suffix) } } } func TestCandidateV35PostgresRawScopeScrubAndFreshSchema(t *testing.T) { st := openTestStore(t) if st.Dialect() != "postgres" { t.Skip("isolated PostgreSQL DSN not configured") } checkColumns := func(want int) { t.Helper() var count int if err := st.db.QueryRow(`SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=current_schema() AND table_name='context_manifests' AND column_name IN ('resolve_goal','resolve_target_id')`).Scan(&count); err != nil || count != want { t.Fatalf("raw scope columns=%d want=%d err=%v", count, want, err) } } checkColumns(0) m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "candidate-model", InputSHA256: "input", Delivery: "attempted"} if err := st.AppendContextManifestWithScope("alice", "run-1", m, ContextManifestScope{Bound: true}); err != nil { t.Fatal(err) } for _, query := range []string{ `DELETE FROM schema_migrations WHERE version=36`, `ALTER TABLE context_manifests ADD COLUMN resolve_goal TEXT NOT NULL DEFAULT ''`, `ALTER TABLE context_manifests ADD COLUMN resolve_target_id TEXT NOT NULL DEFAULT ''`, `UPDATE context_manifests SET resolve_goal='OLD_RESTRICTED_BODY',resolve_target_id='doc-private'`, } { if _, err := st.db.Exec(query); err != nil { t.Fatal(err) } } checkColumns(2) if err := st.migrate(); err != nil { t.Fatal(err) } checkColumns(0) var count int if err := st.db.QueryRow(`SELECT COUNT(*) FROM context_manifests WHERE attempt_id='candidate-model'`).Scan(&count); err != nil || count != 1 { t.Fatalf("trusted manifest lost during PG scrub: count=%d err=%v", count, err) } } func TestContextManifestTerminalCannotChangeTrustedResolveScope(t *testing.T) { st, err := OpenSQLite(filepath.Join(t.TempDir(), "scope.db")) if err != nil { t.Fatal(err) } defer st.Close() m := extension.ProjectContextManifest{ProjectID: "p1", AttemptID: "model-1", WorkflowStepAttemptID: "engine/draft/1", InputSHA256: "input-hash", Delivery: "attempted"} scope := ContextManifestScope{Bound: true} if err := st.AppendContextManifestWithScope("alice", "run-1", m, scope); err != nil { t.Fatal(err) } m.Delivery = "responded" if err := st.AppendContextManifest("alice", "run-1", m); err == nil { t.Fatal("terminal receipt dropped its trusted Resolve scope") } if err := st.AppendContextManifestWithScope("alice", "run-1", m, scope); err != nil { t.Fatal(err) } }