Ariver
2026-06-03 ce96a0e5272d6bcdf732dd0fc1ff8a9d9c834a0c
Fix Round 4 model download QA issues
5 files modified
4 files added
278 ■■■■■ changed files
02-P-NBL/round-4/code-review.md 6 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-4/dev-self-check.md 32 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log patch | view | raw | blame | history
02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log 23 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-4/logs/local-build-install-20260603-round4.log 154 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log 38 ●●●●● patch | view | raw | blame | history
privatevoice.src/frontend/src/components/settings/GeneralPage.svelte 3 ●●●● patch | view | raw | blame | history
privatevoice.src/internal/model/downloader.go 8 ●●●●● patch | view | raw | blame | history
privatevoice.src/internal/model/model_test.go 14 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-4/code-review.md
@@ -36,3 +36,9 @@
- `privatevoice.src/frontend/src/lib/i18n/en.json`
- `privatevoice.src/frontend/src/lib/i18n/zh.json`
## 5. QA 预检发现与处理
- 发现:Parakeet 下载过程中如果 App 中途退出,`.downloads/parakeet-en` 会保留大体积 partial。
- 处理:`DownloadProfile` 现在会在同模型新下载开始前清理旧 partial。
- 发现:Parakeet 下载时设置页 Engine 显示 Loading,但实际 Moonshine 仍可用。
- 处理:前端只在已安装模型切换或下载完成后进入 Loading,下载过程中保留当前引擎状态。
02-P-NBL/round-4/dev-self-check.md
@@ -80,3 +80,35 @@
- 切回中文后 SenseVoice 正常。
- 下载失败和中断下载不破坏 Moonshine/SenseVoice。
## 5. QA 预检补丁
QA 预检中已补充两处修复:
- 下载过程中不再把 Engine UI 误显示为 Loading;旧 Moonshine/SenseVoice 引擎保持当前状态显示。
- App 退出或重启导致的旧 `.downloads/<model>` partial,会在下一次同模型下载前清理,避免长期占用磁盘。
补丁后再次通过:
```bash
cd privatevoice.src
go test ./... -count=1
```
日志:`logs/go-test-all-20260603-round4-after-qa-fix.log`
补丁后再次通过:
```bash
cd privatevoice.src/frontend
npm run build
```
日志:`logs/npm-run-build-20260603-round4-after-qa-fix.log`
补丁后再次通过:
```bash
git diff --check
```
日志:`logs/git-diff-check-20260603-round4-after-qa-fix.log`
02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log
02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log
New file
@@ -0,0 +1,23 @@
?       voicesnap    [no test files]
?       voicesnap/internal/audio    [no test files]
ok      voicesnap/internal/config    0.654s
ok      voicesnap/internal/correctioncsv    1.184s
?       voicesnap/internal/dock    [no test files]
ok      voicesnap/internal/engine    1.378s
ok      voicesnap/internal/history    1.711s
?       voicesnap/internal/hotkey    [no test files]
?       voicesnap/internal/input    [no test files]
ok      voicesnap/internal/language    3.217s
?       voicesnap/internal/logger    [no test files]
ok      voicesnap/internal/model    2.491s
ok      voicesnap/internal/modelselection    2.033s
?       voicesnap/internal/overlay    [no test files]
?       voicesnap/internal/paths    [no test files]
?       voicesnap/internal/permissions    [no test files]
?       voicesnap/internal/singleinstance    [no test files]
?       voicesnap/internal/sound    [no test files]
?       voicesnap/internal/startup    [no test files]
?       voicesnap/internal/textproc    [no test files]
?       voicesnap/internal/updater    [no test files]
ok      voicesnap/internal/userdict    2.798s
?       voicesnap/services    [no test files]
02-P-NBL/round-4/logs/local-build-install-20260603-round4.log
New file
@@ -0,0 +1,154 @@
==> Preflight checks
Using existing local signing identity: PrivateVoice Local Code Signing
==> Cleaning old local build
==> Building frontend
> privatevoice-input-frontend@2.1.21 build
> vite build
vite v6.4.1 building for production...
transforming...
2:46:23 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:28:0 Elements with the 'dialog' interactive role must have a tabindex value
https://svelte.dev/e/a11y_interactive_supports_focus
26:
27: <!-- svelte-ignore a11y_click_events_have_key_events -->
28: <div class="overlay" onclick={onclose} role="dialog">
          ^
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
2:46:23 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:30:2 Non-interactive element `<div>` should not be assigned mouse or keyboard event listeners
https://svelte.dev/e/a11y_no_noninteractive_element_interactions
28: <div class="overlay" onclick={onclose} role="dialog">
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
            ^
31:     <h2 class="title">{t('update.title')}</h2>
32:     <p class="version">{t('update.version', { version: info.version })}</p>
2:46:23 PM [vite-plugin-svelte] src/components/shared/ToggleSwitch.svelte:17:0 Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
https://svelte.dev/e/a11y_consider_explicit_label
15: </script>
16:
17: <button
           ^
18:   class="toggle"
19:   class:active={checked}
✓ 163 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                      0.41 kB │ gzip:  0.27 kB
dist/assets/app-icon-Bs-pS5hx.png  400.98 kB
dist/assets/index-Bm3gykhy.css      35.00 kB │ gzip:  5.92 kB
dist/assets/index-DUZkgsrv.js      142.07 kB │ gzip: 47.42 kB
✓ built in 853ms
==> Building Go binary (arm64)
# voicesnap
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/go.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000000.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000001.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000002.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000003.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000004.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000005.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000006.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000007.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000028.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000029.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000030.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000031.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000032.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000033.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000034.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000035.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000036.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000037.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000038.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000039.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000040.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000041.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000042.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000043.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000044.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000045.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000046.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000047.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000048.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000049.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000050.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000051.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000052.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000053.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000054.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000055.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
ld: warning: building for macOS-11.0, but linking with dylib '@rpath/libonnxruntime.1.23.2.dylib' which was built for newer version 13.4
==> Assembling PrivateVoice Input.app
==> Fixing rpaths
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: warning: changes being made to the file will invalidate the code signature in: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
==> Code signing with stable local identity
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib: replacing existing signature
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib: replacing existing signature
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/MacOS/PrivateVoice Input: replacing existing signature
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: replacing existing signature
==> Verifying app signature
--prepared:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
--validated:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
--prepared:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
--validated:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: valid on disk
/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: satisfies its Designated Requirement
Executable=/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/MacOS/PrivateVoice Input
Identifier=com.privatevoice.input
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20500 size=28343 flags=0x10000(runtime) hashes=875+7 location=embedded
Signature size=1830
Authority=PrivateVoice Local Code Signing
Signed Time=Jun 3, 2026 at 14:46:31
Info.plist entries=12
TeamIdentifier=not set
Runtime Version=26.2.0
Sealed Resources version=2 rules=13 files=3
Internal requirements count=1 size=100
==> Creating DMG
created: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg
==> Signing DMG
正在检查“Protective Master Boot Record(MBR:0)”的校验和…
Protective Master Boot Record(MBR:0):已验证CRC32 $A8E7551D
正在检查“GPT Header(Primary GPT Header:1)”的校验和…
    GPT Header(Primary GPT Header:1):已验证CRC32 $27DFC2C8
正在检查“GPT Partition Data(Primary GPT Table:2)”的校验和…
GPT Partition Data(Primary GPT Table:已验证CRC32 $86F223D2
正在检查“(Apple_Free:3)”的校验和…
                      (Apple_Free:3):已验证CRC32 $00000000
正在检查“disk image(Apple_APFS:4)”的校验和…
            disk image(Apple_APFS:4):已验证CRC32 $4FDADCFF
正在检查“(Apple_Free:5)”的校验和…
                      (Apple_Free:5):已验证CRC32 $00000000
正在检查“GPT Partition Data(Backup GPT Table:6)”的校验和…
GPT Partition Data(Backup GPT Table::已验证CRC32 $86F223D2
正在检查“GPT Header(Backup GPT Header:7)”的校验和…
     GPT Header(Backup GPT Header:7):已验证CRC32 $09CBE188
已验证CRC32 $757AF404
hdiutil: verify: checksum of "/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg" is VALID
==> Installing to /Applications
--prepared:/Applications/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
--validated:/Applications/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
--prepared:/Applications/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
--validated:/Applications/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
/Applications/PrivateVoice Input.app: valid on disk
/Applications/PrivateVoice Input.app: satisfies its Designated Requirement
==> Local build complete
  App: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app
  DMG: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg
  Compatibility DMG: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-arm64-local.dmg
  Signing identity: PrivateVoice Local Code Signing
02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log
New file
@@ -0,0 +1,38 @@
> privatevoice-input-frontend@2.1.21 build
> vite build
vite v6.4.1 building for production...
transforming...
2:57:48 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:28:0 Elements with the 'dialog' interactive role must have a tabindex value
https://svelte.dev/e/a11y_interactive_supports_focus
26:
27: <!-- svelte-ignore a11y_click_events_have_key_events -->
28: <div class="overlay" onclick={onclose} role="dialog">
          ^
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
2:57:48 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:30:2 Non-interactive element `<div>` should not be assigned mouse or keyboard event listeners
https://svelte.dev/e/a11y_no_noninteractive_element_interactions
28: <div class="overlay" onclick={onclose} role="dialog">
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
            ^
31:     <h2 class="title">{t('update.title')}</h2>
32:     <p class="version">{t('update.version', { version: info.version })}</p>
2:57:49 PM [vite-plugin-svelte] src/components/shared/ToggleSwitch.svelte:17:0 Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
https://svelte.dev/e/a11y_consider_explicit_label
15: </script>
16:
17: <button
           ^
18:   class="toggle"
19:   class:active={checked}
✓ 163 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                      0.41 kB │ gzip:  0.28 kB
dist/assets/app-icon-Bs-pS5hx.png  400.98 kB
dist/assets/index-Bm3gykhy.css      35.00 kB │ gzip:  5.92 kB
dist/assets/index-QkfSvZCU.js      142.09 kB │ gzip: 47.42 kB
✓ built in 2.17s
privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
@@ -185,12 +185,13 @@
    modelBusyID = option.modelID
    modelProgress = 0
    modelError = ''
    engineStatus.set('loading')
    try {
      if (option.installed) {
        engineStatus.set('loading')
        await Call.ByName('voicesnap/services.EngineService.SelectModel', option.modelID)
      } else {
        await Call.ByName('voicesnap/services.EngineService.DownloadModelByID', option.modelID)
        engineStatus.set('loading')
      }
      await loadModelOptions()
    } catch (err: any) {
privatevoice.src/internal/model/downloader.go
@@ -27,6 +27,7 @@
    if err := os.MkdirAll(modelsDir, 0755); err != nil {
        return fmt.Errorf("failed to create models dir: %w", err)
    }
    cleanupStaleDownloadDirs(profile, modelsDir)
    var lastErr error
    for i, url := range urls {
@@ -52,6 +53,13 @@
    return fmt.Errorf("download failed: %w", lastErr)
}
func cleanupStaleDownloadDirs(profile ModelProfile, modelsDir string) {
    downloadRoot := filepath.Join(modelsDir, ".downloads", profile.ID)
    if err := os.RemoveAll(downloadRoot); err != nil {
        logger.Info("Failed to remove stale download dir %s: %v", downloadRoot, err)
    }
}
func downloadAndInstallFromURL(profile ModelProfile, url, modelsDir string, attempt int, progress ProgressCallback) error {
    runID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), attempt)
    downloadDir := filepath.Join(modelsDir, ".downloads", profile.ID, runID)
privatevoice.src/internal/model/model_test.go
@@ -377,6 +377,20 @@
    }
}
func TestDownloadProfileRemovesStalePartialDownload(t *testing.T) {
    root := t.TempDir()
    stalePath := filepath.Join(root, ".downloads", DefaultModelID, "old-run", "model_package")
    writeTestFile(t, stalePath, "partial")
    err := DownloadProfile(DefaultModelProfile(), []string{"://bad-url"}, root, nil)
    if err == nil {
        t.Fatal("expected download failure")
    }
    if _, statErr := os.Stat(filepath.Dir(stalePath)); !os.IsNotExist(statErr) {
        t.Fatalf("stale download run still exists or stat failed: %v", statErr)
    }
}
func TestDownloadProfileFallsBackWhenPrimaryArchiveInvalid(t *testing.T) {
    root := t.TempDir()
    validArchive := tarArchive(t, map[string]string{