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)
73 lines
2.5 KiB
Go
73 lines
2.5 KiB
Go
package runmgr
|
||
|
||
// EXT-13 红测(提案 2026-10-09-ext-g1-b1-b3 §1):回合内检索(knowledge_search
|
||
// 工具与 @knowledge 注入的同一入口 runCtx.Retrieve)在适配器放行后吃到共享命中;
|
||
// 未放行/未注册谓词时保持本人两路、零泄露。
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
core "agent-system-core"
|
||
)
|
||
|
||
type sharedFakeRetriever struct {
|
||
own []core.RetrievalHit
|
||
shared []core.RetrievalHit
|
||
gotAlt bool
|
||
}
|
||
|
||
func (f *sharedFakeRetriever) Retrieve(ctx context.Context, owner core.ID, q core.RetrievalQuery) ([]core.RetrievalHit, error) {
|
||
return f.own, nil
|
||
}
|
||
|
||
func (f *sharedFakeRetriever) RetrieveShared(ctx context.Context, owner core.ID, q core.RetrievalQuery, allow func(docOwner, projectID core.ID) bool) ([]core.RetrievalHit, error) {
|
||
f.gotAlt = true
|
||
out := append([]core.RetrievalHit{}, f.own...) // 对齐 rag.Pipeline:本人两路如常
|
||
for _, h := range f.shared {
|
||
if allow(h.Doc.Owner, h.Doc.ProjectID) {
|
||
out = append(out, h)
|
||
}
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func TestRunContextRetrieveShared(t *testing.T) {
|
||
m := New(nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||
fr := &sharedFakeRetriever{
|
||
own: []core.RetrievalHit{{Doc: core.KnowledgeDoc{Ref: "own-1", Chunk: "本人命中"}}},
|
||
shared: []core.RetrievalHit{{Doc: core.KnowledgeDoc{Ref: "sh-1", Chunk: "共享命中", Owner: "alice", ProjectID: "p-1"}}},
|
||
}
|
||
m.SetRetriever(fr)
|
||
rc := &runCtx{mgr: m, run: core.Run{Owner: "bob"}}
|
||
|
||
// 未注册共享谓词:本人两路如常,不触碰共享通道。
|
||
hits, err := rc.Retrieve(core.RetrievalQuery{Text: "命中"})
|
||
if err != nil || len(hits) != 1 || hits[0].Doc.Ref != "own-1" {
|
||
t.Fatalf("no predicate: hits=%+v err=%v", hits, err)
|
||
}
|
||
if fr.gotAlt {
|
||
t.Fatal("no predicate: must not call RetrieveShared(未注册=现状)")
|
||
}
|
||
|
||
// 注册谓词(放行 bob×p-1)→ 本人 + 共享。
|
||
m.SetSharedKnowledgeAllow(func(owner string, projectID core.ID) bool {
|
||
return owner == "bob" && string(projectID) == "p-1"
|
||
})
|
||
fr.gotAlt = false
|
||
hits, err = rc.Retrieve(core.RetrievalQuery{Text: "命中"})
|
||
if err != nil || len(hits) != 2 {
|
||
t.Fatalf("with predicate: hits=%+v err=%v, want own+shared", hits, err)
|
||
}
|
||
if !fr.gotAlt {
|
||
t.Fatal("with predicate: RetrieveShared not consulted(EXT-13)")
|
||
}
|
||
|
||
// 谓词拒绝 → 只剩本人。
|
||
m.SetSharedKnowledgeAllow(func(owner string, projectID core.ID) bool { return false })
|
||
hits, err = rc.Retrieve(core.RetrievalQuery{Text: "命中"})
|
||
if err != nil || len(hits) != 1 || hits[0].Doc.Ref != "own-1" {
|
||
t.Fatalf("denied: hits=%+v err=%v, want own only", hits, err)
|
||
}
|
||
}
|