This addresses the remaining point on #3335.
`VoiceCloneNode` and `CustomVoiceNode` passed the free-text
`custom_model_path` widget straight to `load_qwen_model()`, which checked
it with `os.path.exists()` / `os.path.isdir()` before anything else. That
is two problems in one: a shared workflow can name a folder anywhere on
the host and have a model loaded from it, and on Windows a path that
names another machine makes this host connect to it during that very
check, which is enough to hand that machine the user's sign-in
credentials.
- `_resolve_custom_model_dir()` treats the widget as a folder name
relative to a fixed set of bases that the local installation decides,
never the workflow: `models/qwen-tts` (plus whatever
extra_model_paths.yaml registers under `qwen-tts` / `TTS`), and
`output/qwen3tts_finetune`, where the Train node writes its
checkpoints, so "fine-tune then generate" keeps working.
- Network share paths and absolute paths are refused before any
filesystem call; the remaining relative value goes through the
existing `_resolve_within()` (realpath + commonpath) with an existence
check.
- The resolution happens before the tokenizer check and the model cache
key, so nothing touches the path first and differently spelled values
cannot split or poison the cache.
- An unusable value now raises instead of printing a warning and
silently falling back, so a shared workflow cannot quietly change
which model gets loaded.
- The Train node returns the checkpoint as a relative folder name (the
absolute location is printed in the console) to match what
`custom_model_path` accepts.
- Model search folders were factored into `_model_search_roots()`, which
the loader now uses as well.
- Widget placeholders and tooltips, both READMEs, doc/update.md and the
fine-tuning example workflow were updated accordingly.