diff --git a/internal/cli/dispatcherref_test.go b/internal/cli/dispatcherref_test.go new file mode 100644 index 00000000..e9413d11 --- /dev/null +++ b/internal/cli/dispatcherref_test.go @@ -0,0 +1,32 @@ +package cli + +import ( + "testing" + + "gitea.mynodes.xyz/ilker/llmgw/internal/control" + "gitea.mynodes.xyz/ilker/llmgw/internal/control/agentsvc" +) + +func TestTheForwardingDispatcherCarriesEveryCapability(t *testing.T) { + // dispatcherRef exists to break a construction cycle, and it satisfies the + // dispatcher interface by forwarding each method by hand. That makes it a + // place where capabilities go missing silently: the controller reaches + // past the interface by type assertion, and an assertion against this type + // fails for anything nobody remembered to forward. + // + // It happened. `ctl weights --collect` returned "this control plane cannot + // ask nodes to delete files" against a real cluster, while every unit test + // passed — because the test dispatchers do not implement the capability + // either, so its absence looked exactly like the expected case. + var ref any = &dispatcherRef{} + if _, ok := ref.(control.WeightRemover); !ok { + t.Error("dispatcherRef drops WeightRemover, so the control plane cannot reclaim disk") + } + + // And the thing it forwards to must have it, or the forwarding has nothing + // to reach. + var svc any = &agentsvc.Service{} + if _, ok := svc.(control.WeightRemover); !ok { + t.Error("the agent service does not implement WeightRemover") + } +} diff --git a/internal/cli/server.go b/internal/cli/server.go index 5aeebc1e..904342bd 100644 --- a/internal/cli/server.go +++ b/internal/cli/server.go @@ -372,6 +372,26 @@ func (r *dispatcherRef) get() (reconciler.Dispatcher, error) { return r.d, nil } +// RemoveWeights forwards a capability beyond scheduling. +// +// It has to be spelled out here rather than inherited, because this type +// satisfies reconciler.Dispatcher by forwarding each method by hand — so +// anything the controller reaches by type assertion stops at this boundary +// unless it is forwarded too. A capability added to the service and not to +// this type is simply absent at runtime, with nothing at compile time to say +// so. +func (r *dispatcherRef) RemoveWeights(ctx context.Context, nodeID string, paths []string) error { + d, err := r.get() + if err != nil { + return err + } + rm, ok := d.(control.WeightRemover) + if !ok { + return errors.New("the agent service cannot remove weights") + } + return rm.RemoveWeights(ctx, nodeID, paths) +} + func (r *dispatcherRef) StartShard(ctx context.Context, cmd reconciler.ShardCommand) error { d, err := r.get() if err != nil {