package app import ( "agent-system-backend/internal/rag" "agent-system-backend/internal/settings" "agent-system-backend/internal/store" "agent-system-backend/pkg/extension" core "agent-system-core" "context" "testing" ) func TestHostKnowledgeV2AndProjectReplacement(t *testing.T) { st, err := store.OpenSQLite(t.TempDir() + "/t.db") if err != nil { t.Fatal(err) } defer st.Close() for _, owner := range []string{"alice", "bob"} { if err := st.EnsureUser(owner, ""); err != nil { t.Fatal(err) } } pipe := rag.NewPipeline(st, settings.NewService(st), nil, nil) auth := &projectAuthz{} alice := inprocHost{owner: "alice", pluginID: "rdm", caps: hostcaps{st: st, pipe: pipe, authz: auth}} first := []core.KnowledgeDoc{{ID: "slice-1", ProjectID: "domain-1", Ref: "notes.md", Chunk: "sharedneedle one"}} second := []core.KnowledgeDoc{{ID: "slice-2", ProjectID: "domain-2", Ref: "notes.md", Chunk: "sharedneedle two"}} for _, ds := range [][]core.KnowledgeDoc{first, second, second} { if err := alice.KnowledgePublish(context.Background(), ds, false); err != nil { t.Fatal(err) } } own, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "sharedneedle"}) if err != nil || len(own) != 2 { t.Fatalf("cross project publish replaced first: %+v %v", own, err) } auth.set("rdm", func(string, core.ID) (bool, error) { return true, nil }) auth.setV2("rdm", func(q extension.ResourceAuthzQuery) (bool, error) { return q.ResourceType == "project" || (q.ResourceType == "knowledge" && q.ResourceID == "slice-1" && q.ProjectID == "domain-1" && q.Actor == "bob" && q.Owner == "alice"), nil }) bob := inprocHost{owner: "bob", pluginID: "rdm", caps: alice.caps} hits, err := bob.KnowledgeSearch(context.Background(), "", "sharedneedle", 5) if err != nil || len(hits) != 1 || hits[0].Doc.ID != "slice-1" { t.Fatalf("host V2=%+v %v", hits, err) } } func TestHostKnowledgeResolvesTrustedProjectScope(t *testing.T) { st, err := store.OpenSQLite(t.TempDir() + "/t.db") if err != nil { t.Fatal(err) } defer st.Close() if err := st.AddKnowledge(core.KnowledgeDoc{ID: "slice-current", Owner: "alice", ProjectID: "domain-current", Chunk: "scopeneedle current"}, core.KnowledgeDoc{ID: "slice-other", Owner: "alice", ProjectID: "domain-other", Chunk: "scopeneedle other"}); err != nil { t.Fatal(err) } slot := &projectLifecycleSlot{} provider := completeLifecycle() provider.List = func(_ context.Context, h extension.Host) ([]core.Project, error) { if h.Owner() != "bob" { t.Fatalf("wrong scope actor %q", h.Owner()) } return []core.Project{{ID: "host-current", Settings: map[string]any{"rdm.projectId": "domain-current", "rdm.state": "ready"}}}, nil } caps := hostcaps{st: st, pipe: rag.NewPipeline(st, settings.NewService(st), nil, nil), authz: &projectAuthz{}, projects: slot} if err := (pluginRegistry{pluginID: "rdm", caps: caps}).RegisterProjectLifecycle(provider); err != nil { t.Fatal(err) } caps.authz.setV2("rdm", func(extension.ResourceAuthzQuery) (bool, error) { return true, nil }) h := inprocHost{owner: "bob", pluginID: "rdm", caps: caps} hits, err := h.KnowledgeSearch(context.Background(), "host-current", "scopeneedle", 5) if err != nil || len(hits) != 1 || hits[0].Doc.ID != "slice-current" { t.Fatalf("host scope: %+v %v", hits, err) } } func TestRegisteredProjectHostRejectsForeignKnowledgeSource(t *testing.T) { st, err := store.OpenSQLite(t.TempDir() + "/t.db") if err != nil { t.Fatal(err) } defer st.Close() if err := st.EnsureUser("alice", ""); err != nil { t.Fatal(err) } slot := &projectLifecycleSlot{} caps := hostcaps{st: st, projects: slot} if err := (pluginRegistry{pluginID: "rdm", caps: caps}).RegisterProjectLifecycle(completeLifecycle()); err != nil { t.Fatal(err) } h := inprocHost{owner: "alice", pluginID: "other", caps: caps} for _, publish := range []bool{false, true} { docs := []core.KnowledgeDoc{{ID: "claimed-published", ProjectID: "domain", Source: "plugin:rdm:published", Ref: "ref", Chunk: "spoofneedle"}} var err error if publish { err = h.KnowledgePublish(context.Background(), docs, false) } else { err = h.KnowledgeWrite(context.Background(), docs, false) } if err == nil { t.Fatalf("foreign namespace accepted publish=%v", publish) } } if hits, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "spoofneedle"}); err != nil || len(hits) != 0 { t.Fatalf("foreign source wrote data: %+v %v", hits, err) } // Existing ordinary Hosts preserve their documented caller-provided Source. h.caps.projects = nil if err := h.KnowledgeWrite(context.Background(), []core.KnowledgeDoc{{Source: "manual", Chunk: "ordinaryneedle"}}, false); err != nil { t.Fatalf("ordinary source semantics changed: %v", err) } } func TestProjectPublishPreservesForeignPluginAndScope(t *testing.T) { st, err := store.OpenSQLite(t.TempDir() + "/t.db") if err != nil { t.Fatal(err) } defer st.Close() if err := st.EnsureUser("alice", ""); err != nil { t.Fatal(err) } slot := &projectLifecycleSlot{} caps := hostcaps{st: st, projects: slot} if err := (pluginRegistry{pluginID: "rdm", caps: caps}).RegisterProjectLifecycle(completeLifecycle()); err != nil { t.Fatal(err) } original := core.KnowledgeDoc{ID: "rdm-published", Owner: "alice", ProjectID: "domain", Source: "plugin:rdm:published", Ref: "notes.md", Chunk: "originalneedle"} if err := st.AddKnowledge(original); err != nil { t.Fatal(err) } if err := st.UpsertKnowledgeVector(context.Background(), "alice", original.ID, original.Ref, []float32{1, 0}); err != nil { t.Fatal(err) } h := inprocHost{owner: "alice", pluginID: "other", caps: caps} replacement := []core.KnowledgeDoc{{ID: "other", ProjectID: "domain", Source: "plugin:other", Ref: "notes.md", Chunk: "replacementneedle"}} for i := 0; i < 2; i++ { if err := h.KnowledgePublish(context.Background(), replacement, false); err != nil { t.Fatal(err) } } if hits, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "originalneedle"}); err != nil || len(hits) != 1 { t.Fatalf("foreign plugin deleted RDM publication: %+v %v", hits, err) } if hits, err := st.SearchKnowledgeVectors(context.Background(), "alice", []float32{1, 0}, 5, core.RetrievalQuery{ProjectID: "domain"}); err != nil || len(hits) != 1 || hits[0].Doc.ID != original.ID { t.Fatalf("foreign plugin deleted RDM vector: %+v %v", hits, err) } h.pluginID = "rdm" if err := h.KnowledgePublish(context.Background(), []core.KnowledgeDoc{{ID: "unscoped", Source: "plugin:rdm", Ref: "notes.md", Chunk: "privateneedle"}}, false); err != nil { t.Fatal(err) } if hits, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "originalneedle", ProjectID: "domain"}); err != nil || len(hits) != 1 { t.Fatalf("unscoped publication deleted scoped RDM data: %+v %v", hits, err) } } func TestUnregisteredPublishRetainsLegacyRefScope(t *testing.T) { st, err := store.OpenSQLite(t.TempDir() + "/t.db") if err != nil { t.Fatal(err) } defer st.Close() if err := st.EnsureUser("alice", ""); err != nil { t.Fatal(err) } if err := st.AddKnowledge(core.KnowledgeDoc{ID: "legacy", Owner: "alice", ProjectID: "old-project", Source: "manual", Ref: "same", Chunk: "legacyneedle"}); err != nil { t.Fatal(err) } h := inprocHost{owner: "alice", pluginID: "other", caps: hostcaps{st: st}} if err := h.KnowledgePublish(context.Background(), []core.KnowledgeDoc{{ID: "replacement", Source: "manual", Ref: "same", Chunk: "replacementneedle"}}, false); err != nil { t.Fatal(err) } if hits, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "legacyneedle"}); err != nil || len(hits) != 0 { t.Fatalf("unregistered legacy scope changed: %+v %v", hits, err) } if hits, err := st.SearchKnowledge("alice", core.RetrievalQuery{Text: "replacementneedle"}); err != nil || len(hits) != 1 { t.Fatalf("unregistered replacement missing: %+v %v", hits, err) } }