83 lines
5.2 KiB
Go
83 lines
5.2 KiB
Go
package callmemory
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestSourceDeletionIncludesBothAutomaticAndReviewedMemories(t *testing.T) {
|
||
|
|
for _, origin := range []string{"automatic", "reviewed"} {
|
||
|
|
t.Run(origin, func(t *testing.T) {
|
||
|
|
f := setup(t)
|
||
|
|
ctx := context.Background()
|
||
|
|
automatic, _ := f.add(t)
|
||
|
|
reviewed, _ := f.add(t)
|
||
|
|
unrelated, _ := f.add(t)
|
||
|
|
call, _, _ := f.call(t)
|
||
|
|
f.exec(t, `UPDATE call_session SET manifest_status='DISCARDED',call_state='ENDED',local_audio_off=true,sdk_stopped=true,remote_state='STOPPED_OBSERVED',ended_at=now() WHERE id=$1::uuid`, call.SessionID)
|
||
|
|
for _, utterance := range []string{"story-source", "unrelated-source"} {
|
||
|
|
f.exec(t, `INSERT INTO call_utterance(session_id,utterance_id,source_revision,speaker,text_content,interrupted,source_at) VALUES($1::uuid,$2,1,'USER',$2,false,now())`, call.SessionID, utterance)
|
||
|
|
}
|
||
|
|
story, _ := addCallSourceStory(t, f, call.SessionID, "story-source", 1)
|
||
|
|
f.exec(t, `UPDATE memory_version SET source_refs=$2::jsonb WHERE memory_id=(SELECT id FROM curated_memory WHERE public_id=$1::uuid)`, automatic, jsonBytes([]map[string]any{{"type": "provider_memory", "session_id": call.SessionID, "provider_id": "provider-test"}}))
|
||
|
|
f.exec(t, `UPDATE memory_version SET source_refs=$2::jsonb WHERE memory_id=(SELECT id FROM curated_memory WHERE public_id=$1::uuid)`, reviewed, jsonBytes([]map[string]any{{"type": "story", "story_id": story}, {"session_id": call.SessionID, "utterance_id": "story-source", "source_revision": 1}}))
|
||
|
|
target, derived := automatic, reviewed
|
||
|
|
if origin == "reviewed" {
|
||
|
|
target, derived = reviewed, automatic
|
||
|
|
}
|
||
|
|
mutation := f.request(t, "mutations", MutationInput{ID: uuid.NewString(), ElderID: f.elder, Action: "DELETE", Targets: []string{target}, Expected: map[string]int64{target: 1}, Scope: "SOURCE_AND_DERIVED"})
|
||
|
|
targets := mutation["targets"].([]any)
|
||
|
|
if len(targets) != 2 || targets[0] != target || targets[1] != derived {
|
||
|
|
t.Fatalf("both source-derived memories were not tracked: %v", targets)
|
||
|
|
}
|
||
|
|
var derivedScope string
|
||
|
|
if err := f.p.QueryRow(ctx, `SELECT scope FROM memory_deletion_ledger WHERE memory_id=(SELECT id FROM curated_memory WHERE public_id=$1::uuid)`, derived).Scan(&derivedScope); err != nil || derivedScope != "MEMORY_AND_DERIVED" {
|
||
|
|
t.Fatalf("derived memory enlarged original source scope: scope=%s err=%v", derivedScope, err)
|
||
|
|
}
|
||
|
|
if err := f.s.Maintain(ctx); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var remaining, unrelatedStatus, keptSource string
|
||
|
|
if err := f.p.QueryRow(ctx, `SELECT count(*)::text FROM memory_version mv JOIN curated_memory cm ON cm.id=mv.memory_id WHERE cm.public_id=ANY($1::uuid[]) AND mv.content<>'[deleted]'`, []string{automatic, reviewed}).Scan(&remaining); err != nil || remaining != "0" {
|
||
|
|
t.Fatalf("one derived memory retained plaintext: count=%s err=%v", remaining, err)
|
||
|
|
}
|
||
|
|
if err := f.p.QueryRow(ctx, `SELECT status FROM curated_memory WHERE public_id=$1::uuid`, unrelated).Scan(&unrelatedStatus); err != nil || unrelatedStatus != "ACTIVE" {
|
||
|
|
t.Fatalf("unrelated memory changed: status=%s err=%v", unrelatedStatus, err)
|
||
|
|
}
|
||
|
|
if err := f.p.QueryRow(ctx, `SELECT text_content FROM call_utterance WHERE session_id=$1::uuid AND utterance_id='unrelated-source'`, call.SessionID).Scan(&keptSource); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if origin == "reviewed" && keptSource != "unrelated-source" {
|
||
|
|
t.Fatal("deleting the derived automatic summary erased unrelated original speech")
|
||
|
|
}
|
||
|
|
if origin == "automatic" && keptSource != "[deleted]" {
|
||
|
|
t.Fatal("session-wide deletion left an original source")
|
||
|
|
}
|
||
|
|
f.exec(t, `UPDATE memory_projection_job SET state='VISIBLE' WHERE subject_id=$1::uuid`, f.subject)
|
||
|
|
f.exec(t, `UPDATE memory_user_state SET viking_visible_revision=memory_revision WHERE subject_id=$1::uuid`, f.subject)
|
||
|
|
f.exec(t, `UPDATE memory_deletion_ledger SET backup_cleanup_verified_at=now(),evidence_ref='synthetic test' WHERE memory_id=(SELECT id FROM curated_memory WHERE public_id=$1::uuid)`, target)
|
||
|
|
if err := f.s.Maintain(ctx); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
progress := f.request(t, "mutations/status", map[string]any{"mutation_id": mutation["mutation_id"]})
|
||
|
|
if progress["mutation_state"] == "COMPLETED" || progress["stage_states"].(map[string]any)["backup_cleanup"] != "PENDING" {
|
||
|
|
t.Fatal("deletion completed before the derived memory backup was cleared", progress)
|
||
|
|
}
|
||
|
|
f.exec(t, `UPDATE memory_deletion_ledger SET backup_cleanup_verified_at=now(),evidence_ref='synthetic test' WHERE memory_id=(SELECT id FROM curated_memory WHERE public_id=$1::uuid)`, derived)
|
||
|
|
if err := f.s.Maintain(ctx); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
progress = f.request(t, "mutations/status", map[string]any{"mutation_id": mutation["mutation_id"]})
|
||
|
|
if progress["mutation_state"] != "COMPLETED" {
|
||
|
|
t.Fatal("both memories were reconciled but deletion remained open", progress)
|
||
|
|
}
|
||
|
|
var deleted int
|
||
|
|
if err := f.p.QueryRow(ctx, `SELECT count(*) FROM curated_memory WHERE public_id=ANY($1::uuid[]) AND status='DELETED'`, []string{automatic, reviewed}).Scan(&deleted); err != nil || deleted != 2 {
|
||
|
|
t.Fatalf("both memories did not reach DELETED: count=%d err=%v", deleted, err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|