@@ -189,47 +189,113 @@ func (e *ScopedEndpoint) Clone() fwkdl.AttributeMap {
189189 return clone
190190}
191191
192- // Scope confines endpoints to what plugin declares. extensionPoint labels the
193- // violation counter, so a misdeclared plugin is attributable to the point that
194- // ran it. The returned Violations accumulates what the plugin did outside its
195- // declarations, for callers whose extension point can report it.
196- //
197- // A plugin that declares nothing reaches nothing: an absent declaration is a
198- // statement that the plugin exchanges no data, not a request to be exempt.
199- func Scope (logger logr.Logger , extensionPoint string , plugin fwkplugin.Plugin , endpoints []fwksched.Endpoint ) ([]fwksched.Endpoint , * Violations ) {
192+ // scopeSpec holds the allowed-key sets derived from a plugin's declarations.
193+ // The maps are shared read-only by every ScopedEndpoint wrapper across all
194+ // invocations.
195+ type scopeSpec struct {
196+ allowedPut map [fwkplugin.DataKey ]struct {}
197+ allowedGet map [fwkplugin.DataKey ]struct {}
198+ }
199+
200+ // denyAllSpec confines an unregistered plugin the same way as one that
201+ // declares nothing.
202+ var denyAllSpec = & scopeSpec {}
203+
204+ // scopeSpecs maps a plugin's TypedName().String() to the spec derived from its
205+ // declarations. RegisterScopeSpecs writes it during startup; Scope only reads.
206+ // ValidateAndOrderDataDependencies keys plugins the same way, so within the
207+ // datalayer contract the typed name identifies the plugin.
208+ var (
209+ scopeSpecsMu sync.RWMutex
210+ scopeSpecs = map [string ]* scopeSpec {}
211+
212+ // unregisteredReported dedups the error log for plugins missing from
213+ // scopeSpecs, which Scope would otherwise emit on every invocation.
214+ unregisteredReported sync.Map // string -> struct{}
215+ )
216+
217+ // RegisterScopeSpecs derives the allowed-key sets from each plugin's
218+ // Produces() and Consumes() declarations and stores them for Scope to look up.
219+ // Declarations are fixed at plugin construction, so the sets are derived once
220+ // here rather than on every Scope invocation. Call it after all plugins are
221+ // instantiated; registering a typed name again replaces its spec.
222+ func RegisterScopeSpecs (plugins []fwkplugin.Plugin ) {
223+ scopeSpecsMu .Lock ()
224+ defer scopeSpecsMu .Unlock ()
225+ for _ , plugin := range plugins {
226+ scopeSpecs [plugin .TypedName ().String ()] = buildScopeSpec (plugin )
227+ }
228+ }
229+
230+ func buildScopeSpec (plugin fwkplugin.Plugin ) * scopeSpec {
200231 produces := map [fwkplugin.DataKey ]any {}
201232 if producer , ok := plugin .(fwkplugin.ProducerPlugin ); ok {
202233 produces = producer .Produces ()
203234 }
204-
205- allowedPut := make (map [fwkplugin.DataKey ]struct {}, len (produces ))
206- allowedGet := make (map [fwkplugin.DataKey ]struct {}, len (produces ))
235+ spec := & scopeSpec {
236+ allowedPut : make (map [fwkplugin.DataKey ]struct {}, len (produces )),
237+ allowedGet : make (map [fwkplugin.DataKey ]struct {}, len (produces )),
238+ }
207239 for key := range produces {
208- allowedPut [key ] = struct {}{}
240+ spec . allowedPut [key ] = struct {}{}
209241 // A producer may read back its own output.
210- allowedGet [key ] = struct {}{}
242+ spec . allowedGet [key ] = struct {}{}
211243 }
212244 if consumer , ok := plugin .(fwkplugin.ConsumerPlugin ); ok {
213245 deps := consumer .Consumes ()
214246 for key := range deps .Required {
215- allowedGet [key ] = struct {}{}
247+ spec . allowedGet [key ] = struct {}{}
216248 }
217249 for key := range deps .Optional {
218- allowedGet [key ] = struct {}{}
250+ spec . allowedGet [key ] = struct {}{}
219251 }
220252 }
253+ return spec
254+ }
221255
222- violations := & Violations {}
256+ // scopeSpecFor returns the registered spec for a plugin's typed name, or the
257+ // deny-all spec when none was registered. The miss is logged once per typed
258+ // name: it indicates a wiring bug, and the resulting confinement also shows up
259+ // through the violation counter as soon as the plugin touches an attribute.
260+ func scopeSpecFor (logger logr.Logger , name string ) * scopeSpec {
261+ scopeSpecsMu .RLock ()
262+ spec , ok := scopeSpecs [name ]
263+ scopeSpecsMu .RUnlock ()
264+ if ok {
265+ return spec
266+ }
267+ if _ , reported := unregisteredReported .LoadOrStore (name , struct {}{}); ! reported {
268+ logger .Error (fmt .Errorf ("plugin %q has no registered scope spec; pass it to RegisterScopeSpecs" , name ),
269+ "Confining an unregistered plugin to nothing" )
270+ }
271+ return denyAllSpec
272+ }
273+
274+ // Scope confines endpoints to what plugin declares. extensionPoint labels the
275+ // violation counter, so a misdeclared plugin is attributable to the point that
276+ // ran it. The returned Violations accumulates what the plugin did outside its
277+ // declarations, for callers whose extension point can report it.
278+ //
279+ // A plugin that declares nothing reaches nothing: an absent declaration is a
280+ // statement that the plugin exchanges no data, not a request to be exempt.
281+ //
282+ // The allowed-key sets come from the registry populated by RegisterScopeSpecs
283+ // at startup, treating Produces() and Consumes() as immutable after
284+ // construction. A plugin that was never registered is confined to nothing.
285+ func Scope (logger logr.Logger , extensionPoint string , plugin fwkplugin.Plugin , endpoints []fwksched.Endpoint ) ([]fwksched.Endpoint , * Violations ) {
223286 typedName := plugin .TypedName ()
287+ spec := scopeSpecFor (logger , typedName .String ())
288+
289+ violations := & Violations {}
224290 // One backing array rather than an allocation per endpoint: this runs for
225291 // every filter and scorer on every request, over the whole candidate set.
226292 wrappers := make ([]ScopedEndpoint , len (endpoints ))
227293 scoped := make ([]fwksched.Endpoint , len (endpoints ))
228294 for i , endpoint := range endpoints {
229295 wrappers [i ] = ScopedEndpoint {
230296 inner : endpoint ,
231- allowedPut : allowedPut ,
232- allowedGet : allowedGet ,
297+ allowedPut : spec . allowedPut ,
298+ allowedGet : spec . allowedGet ,
233299 typedName : typedName ,
234300 extensionPoint : extensionPoint ,
235301 logger : logger ,
0 commit comments