ci / vet-and-test (pull_request) Failing after 44s
Why: A member's agent cannot see published project knowledge inside a run
(runCtx.Retrieve and hostcaps.search are owner-only), and members can
neither see shared projects nor open conversations in them - both are P0
prerequisites for RDM G1 (EXT-13/EXT-15, proposal
2026-10-09-ext-g1-b1-b3-exec-backend).
Evidence: Red tests first - TestRunContextRetrieveShared (undefined
SetSharedKnowledgeAllow), TestInprocHostKnowledgeSearchShared (0 shared
hits), TestSharedProjectListAndConversation (empty list) all failed before
and pass after; go test ./... 32/32 packages ok.
Impact: runmgr gains SetSharedKnowledgeAllow and routes Retrieve through
RetrieveShared when the predicate is registered (nil = unchanged); hostcaps
search consults the same EXT-8 slot; GET /api/projects appends
adapter-authorized shared projects (projectView with shared flag, deduped);
POST /api/projects/{id}/conversations falls back to
CreateConversationInProject when the own-project path misses and the
adapter authorizes; store gains ListAllProjects (candidates only, callers
must filter through the adapter) and CreateConversationInProject.
Revocation takes effect immediately on both paths; absence of an adapter
is byte-for-byte the previous behavior. No pkg/extension surface change.
Refs: docs/proposals/2026-10-09-ext-g1-b1-b3-exec-backend.md (EXT-13, EXT-15)
70 lines
2.7 KiB
Go
70 lines
2.7 KiB
Go
package app
|
||
|
||
// EXT-13 红测(提案 2026-10-09-ext-g1-b1-b3 §1):进程内 Host.KnowledgeSearch
|
||
// 的检索面在适配器放行后吃到他人项目的共享知识;未放行保持本人两路、零泄露。
|
||
|
||
import (
|
||
"context"
|
||
"path/filepath"
|
||
"testing"
|
||
|
||
"agent-system-backend/internal/rag"
|
||
"agent-system-backend/internal/settings"
|
||
"agent-system-backend/internal/store"
|
||
core "agent-system-core"
|
||
)
|
||
|
||
// newExt13Host EXT-13 装配:真实 store + rag 管线 + authz 槽 + inprocHost(bob 视角)。
|
||
func newExt13Host(t *testing.T) (interface {
|
||
KnowledgeSearch(ctx context.Context, projectID core.ID, text string, topK int) ([]core.RetrievalHit, error)
|
||
}, *projectAuthz) {
|
||
t.Helper()
|
||
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "test.db"))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Cleanup(func() { st.Close() })
|
||
set := settings.NewService(st)
|
||
pipe := rag.NewPipeline(st, set, nil, nil)
|
||
authz := &projectAuthz{}
|
||
h := inprocHost{caps: hostcaps{st: st, pipe: pipe, set: set, authz: authz}, pluginID: "rdm", owner: "bob"}
|
||
// alice 已发布的项目知识(带 ProjectID;模拟发布状态机产物)。
|
||
if err := st.AddKnowledge(core.KnowledgeDoc{Owner: "alice", ProjectID: "p-1",
|
||
Source: "plugin:rdm", Ref: "ext13-1", Chunk: "EXT13共享独占令牌陈述"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return h, authz
|
||
}
|
||
|
||
func TestInprocHostKnowledgeSearchShared(t *testing.T) {
|
||
h, authz := newExt13Host(t)
|
||
ctx := context.Background()
|
||
q := core.RetrievalQuery{Text: "EXT13共享独占令牌", TopK: 5}
|
||
|
||
if hits, err := h.KnowledgeSearch(ctx, "", q.Text, q.TopK); err != nil || len(hits) != 0 {
|
||
t.Fatalf("before grant: hits=%d err=%v, want 0(未放行零泄露)", len(hits), err)
|
||
}
|
||
// 适配器放行 bob × p-1 → Host 检索面吃到 alice 的共享命中。
|
||
// (槽为首个注册者生效——切换授权改闭包内变量,不重注册。)
|
||
allowBob := true
|
||
authz.set("rdm", func(owner string, projectID core.ID) (bool, error) {
|
||
return allowBob && owner == "bob" && string(projectID) == "p-1", nil
|
||
})
|
||
hits, err := h.KnowledgeSearch(ctx, "", q.Text, q.TopK)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(hits) == 0 {
|
||
t.Fatal("after grant: host search got 0 shared hits, want ≥1(EXT-13)")
|
||
}
|
||
// 指定他人 ProjectID 时同样经适配器(发布边界在下游状态机)。
|
||
if hits, err := h.KnowledgeSearch(ctx, "p-1", q.Text, q.TopK); err != nil || len(hits) == 0 {
|
||
t.Fatalf("scoped to shared project: hits=%d err=%v, want ≥1", len(hits), err)
|
||
}
|
||
// 撤销(同一注册内翻转授权)→ 共享命中即停、零泄露。
|
||
allowBob = false
|
||
if hits, err := h.KnowledgeSearch(ctx, "", q.Text, q.TopK); err != nil || len(hits) != 0 {
|
||
t.Fatalf("after revoke: hits=%d err=%v, want 0", len(hits), err)
|
||
}
|
||
}
|