feat(modeladmin): reject alias configs with invalid targets on create/edit

Validate alias targets at create/swap entry points (ImportModelEndpoint,
EditYAML, PatchConfig) so a dangling, chained, or disabled alias target is
rejected at save time rather than surfacing as a runtime error.

Assisted-by: Claude:opus-4-8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2026-06-20 09:45:45 +00:00
parent 0c06be8aab
commit 882d320020
3 changed files with 30 additions and 0 deletions

View File

@@ -181,6 +181,12 @@ func ImportModelEndpoint(cl *config.ModelConfigLoader, appConfig *config.Applica
return c.JSON(http.StatusBadRequest, ModelResponse{Success: false, Error: msg})
}
// Reject aliases whose target is missing, chained, or disabled so a
// dangling alias can't be persisted and surface as a runtime error later.
if err := cl.ValidateAliasTarget(&modelConfig); err != nil {
return c.JSON(http.StatusBadRequest, ModelResponse{Success: false, Error: err.Error()})
}
// Create the configuration file
configPath := filepath.Join(appConfig.SystemState.Model.ModelsPath, modelConfig.Name+".yaml")
if err := utils.VerifyPath(modelConfig.Name+".yaml", appConfig.SystemState.Model.ModelsPath); err != nil {

View File

@@ -130,6 +130,9 @@ func (s *ConfigService) PatchConfig(_ context.Context, name string, patch map[st
}
return nil, ErrInvalidConfig
}
if err := s.Loader.ValidateAliasTarget(&updated); err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
}
if err := writeFileAtomic(configPath, yamlData, 0644); err != nil {
return nil, fmt.Errorf("write config file: %w", err)
}
@@ -215,6 +218,9 @@ func (s *ConfigService) EditYAML(_ context.Context, name string, body []byte, ml
if valid, _ := req.Validate(); !valid {
return nil, ErrInvalidConfig
}
if err := s.Loader.ValidateAliasTarget(&req); err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
}
configPath := existing.GetModelConfigFile()
modelsPath := s.modelsPath()

View File

@@ -211,5 +211,23 @@ var _ = Describe("ConfigService", func() {
_, err := svc.EditYAML(ctx, "alpha", nil, nil)
Expect(err).To(MatchError(ErrEmptyBody))
})
It("rejects editing a config into an alias with a missing target", func() {
writeModelYAML(svc, dir, "base", map[string]any{"backend": "llama-cpp"})
body := []byte("name: base\nalias: ghost\n")
_, err := svc.EditYAML(ctx, "base", body, nil)
Expect(err).To(MatchError(ErrInvalidConfig))
Expect(err.Error()).To(ContainSubstring("ghost"))
})
It("accepts editing a config into an alias with a real target", func() {
writeModelYAML(svc, dir, "base", map[string]any{"backend": "llama-cpp"})
writeModelYAML(svc, dir, "target", map[string]any{"backend": "llama-cpp"})
body := []byte("name: base\nalias: target\n")
_, err := svc.EditYAML(ctx, "base", body, nil)
Expect(err).ToNot(HaveOccurred())
})
})
})