Skip to content

Commit b7d7287

Browse files
committed
feat(valkey-store): gate Load on NamespacePrefix to refuse autoload probing Mirror local-store's pattern: reject model names without store.NamespacePrefix so the model loader's greedy autoload probe cannot bind an arbitrary model name to the vector store backend (the #9287 failure mode). Also adds unit tests for the gate covering: prefixed namespace, prefix alone, unprefixed model name, empty model, and nil opts.
Signed-off-by: Daria Korenieva <daric2612@gmail.com>
1 parent 4db5b1d commit b7d7287

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

backend/go/valkey-store/store.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,26 @@ func newWithClient(client valkey.Client, cfg Config, namespace string) *ValkeySt
113113
// identifier (one process per (backend, model) tuple upstream), so we derive
114114
// an isolated key prefix and index name from it, and opts.Options carries the
115115
// per-store connection/index configuration.
116+
//
117+
// The NamespacePrefix gate mirrors local-store: core's StoreBackend always
118+
// sends the model name with store.NamespacePrefix; anything else is the model
119+
// loader's greedy autoload probing with a real model name, which must be
120+
// refused or the LLM binds to the vector store (the #9287 failure mode).
116121
func (s *ValkeyStore) Load(opts *pb.ModelOptions) error {
122+
if opts == nil {
123+
return fmt.Errorf("valkey-store: refusing to load: nil model options (expected %q prefix)", store.NamespacePrefix)
124+
}
125+
if !strings.HasPrefix(opts.GetModel(), store.NamespacePrefix) {
126+
return fmt.Errorf("valkey-store: refusing to load %q: not a store namespace (expected %q prefix)", opts.GetModel(), store.NamespacePrefix)
127+
}
128+
117129
cfg, err := loadConfig(opts)
118130
if err != nil {
119131
return err
120132
}
121133
s.cfg = cfg
122134

123-
namespace := ""
124-
if opts != nil {
125-
namespace = opts.Model
126-
}
135+
namespace := opts.Model
127136
s.prefix = keyPrefix(namespace)
128137
s.indexName = indexName(namespace)
129138

backend/go/valkey-store/store_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,41 @@ var _ = Describe("loadConfig", func() {
113113
})
114114
})
115115

116+
var _ = Describe("Load namespace gate", func() {
117+
It("accepts prefixed store namespaces", func() {
118+
s := NewValkeyStore()
119+
// Load will fail at the Valkey connect step (no server), but only after
120+
// passing the namespace gate. A network error is acceptable here — it
121+
// means the prefix check passed.
122+
err := s.Load(&pb.ModelOptions{Model: store.NamespacePrefix + "any-namespace", Options: []string{"addr:localhost:1"}})
123+
Expect(err).NotTo(MatchError(ContainSubstring("not a store namespace")))
124+
})
125+
126+
It("accepts the prefix alone (default store)", func() {
127+
s := NewValkeyStore()
128+
err := s.Load(&pb.ModelOptions{Model: store.NamespacePrefix, Options: []string{"addr:localhost:1"}})
129+
Expect(err).NotTo(MatchError(ContainSubstring("not a store namespace")))
130+
})
131+
132+
It("refuses model names without the namespace prefix", func() {
133+
s := NewValkeyStore()
134+
err := s.Load(&pb.ModelOptions{Model: "some-llm.gguf"})
135+
Expect(err).To(MatchError(ContainSubstring("not a store namespace")))
136+
})
137+
138+
It("refuses an empty model name", func() {
139+
s := NewValkeyStore()
140+
err := s.Load(&pb.ModelOptions{})
141+
Expect(err).To(MatchError(ContainSubstring("not a store namespace")))
142+
})
143+
144+
It("refuses nil opts", func() {
145+
s := NewValkeyStore()
146+
err := s.Load(nil)
147+
Expect(err).To(HaveOccurred())
148+
})
149+
})
150+
116151
var _ = Describe("StoresSet", func() {
117152
It("rejects empty input", func() {
118153
s, _ := newMockStore(testCfg())

0 commit comments

Comments
 (0)