VEC-3 fix: recover model downloads after a failed transfer - #102
Merged
Merged
Conversation
A download that died mid-file left an .incomplete partial that no later retry could reconcile, so the model could never be downloaded again. - ModelDownloadRecovery clears stale *.incomplete markers (whisper repo tree plus the shared Hugging Face cache) on retry and on delete, without touching completed files - ModelDownloadTasks now passes a run token, exposes isCurrent to guard terminal writes, and makes cancel drop the entry so a retry can start even when the transfer ignores cancellation - DownloadStallWatchdog fails a transfer that stops reporting progress so the row offers Resume instead of a frozen bar - cancelDownload immediately marks the model paused Tests: ModelDownloadRecoveryTests, retry-gate and watchdog coverage. Co-authored-by: multica-agent <github@multica.ai>
Independent review found the cancel-then-retry path could run two writers against the same cache paths, whisper cleanup could delete another variant's partial, and a stalled transfer could be kept alive by an unchanged callback. - ModelDownloadTasks keeps the single-writer slot while a cancelled run is still exiting, so a retry waits for it instead of racing it on disk - Whisper partial cleanup only removes files under the requested variant - DownloadProgressSignal only counts real byte/fraction growth as progress - tests assert a single active writer with ordered start/end, whisper variant scoping, and unchanged-callback stall behaviour Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
…acOS Co-authored-by: multica-agent <github@multica.ai>
…scope Co-authored-by: multica-agent <github@multica.ai>
…ation, and startup cleanup Co-authored-by: multica-agent <github@multica.ai>
…n with default.metallib Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
…he main actor Applies the p2 managed-cleanup patch on 421cb90: startup reclaim now covers generation roots, prepared candidates, rollback backups, and retired cleanup roots; runtime cleanup retires a tree in O(1) and deletes it detached, so a model-sized copy never blocks the MainActor arbitration point. - ModelStorage: managed temporary prefixes, retireManagedDirectory, cleanupOrphanedGenerationStaging over all managed roots - Whisper/LLM/ASR publication paths retire candidates/backups detached and re-check the token while cleanup is pending - counterexamples: application Cancel allows Resume before the old writer returns, startup cleanup removes every orphan, cleanup stays responsive Co-authored-by: multica-agent <github@multica.ai>
- verification: managed-artifact restart reclaim with mutation evidence, O(1) retire + detached delete responsiveness, application-layer Resume before the old writer returns; suite figures at 3442f83 (667 executed, 14 skipped, 0 failures) - plan: mark the reclaim, responsiveness, and token re-check items Co-authored-by: multica-agent <github@multica.ai>
Applies p3 (startup+resume P1) and p4 (initializer seam + Sendable) on eb4c08a, verified on macOS with Xcode 27. - ModelCatalog.init takes a path-scoped startup-cleanup factory; production uses the detached helper, and download entry points gate on the task - the initializer counterexample drives the real wiring with an enter/exit barrier and a detached watchdog, so a synchronous or MainActor-child cleanup fails it - a Whisper seam lets the Resume counterexample enter through the real ModelCatalog.downloadWhisper while the old dependency is still suspended - mark the Whisper progress callback @sendable and capture only progress scalars before crossing actors Co-authored-by: multica-agent <github@multica.ai>
Fills the pending rows with measured values from the macOS Mac mini / Xcode 27 run: focused 3 executed 0 failures, full suite 670 executed 14 skipped 0 failures, Release build exit 0, live Whisper transcription 0.15s, and both startup-cleanup mutations exiting non-zero with a passing recovery run. Co-authored-by: multica-agent <github@multica.ai>
… harness - verification: startup responsiveness and application-entry Resume now read as pass at 1cba571 with the measured mutation exit codes, recovery proof and tree hashes; remove the stale pending sentences and old-head readings - plan: tick the final rerun, mutation rerun and skip taxonomy rows; the only open item is the release-owner resource-ceiling decision, kept visible - correct the skip taxonomy everywhere: 4 live-download gates plus 10 environment/model gates Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix model downloads that become unrecoverable after a failed, cancelled, or stalled transfer. Retries now use generation-isolated staging, reject late publication from cancelled writers, preserve complete published models, and keep expensive cleanup/candidate preparation off the MainActor.
The accepted product code and tests are fixed at
1cba5718aef0dd61254838fa77d8181cb4af820a. Later commits are documentation-only; no product-code change or full regression rerun is part of the remaining evidence follow-up.Root cause
Fix
.utter-generations/<token>/downloadroot and isolated Hub cache from operation start.ModelCataloginitializer path, off the MainActor, and gate fresh download entry points until it completes.ModelCatalog.downloadWhisperrequest to start while the cancelled dependency call is still suspended, without sharing its staging root or publication rights.Fixed code identity
1cba5718aef0dd61254838fa77d8181cb4af820ad15b2c7b02262bf4823646903730122dba90a56fSourcestree:ca355f4ff185ece0cc99a3c73cb9921510018cccTeststree:7d69e1226b6a109bffc5ae3c3b0a83dace33c9d3263071e0885fc0e9245c35794e850fca2c76dadb(documentation only; Sources/Tests identical to1cba5718)249eca3fd79df4bced0ec42c55baba7dcb383e47(documentation only; Sources/Tests identical to1cba5718)bf859aa3139c54b0e560ed9123a116407d1c9ee8(documentation only; Sources/Tests identical to1cba5718)47a0c276e4abf80f76c935c282dcbcda13e6329c(documentation only; Sources/Tests identical to1cba5718)Accepted macOS verification at
1cba571827A266a), Apple Swift 6.4 (swiftlang-6.4.0.34.1,clang-2100.3.34.1).swift build: exit 0.testModelCatalogInitializerStartupCleanupKeepsMainActorResponsivetestStartupCleanupKeepsMainActorResponsivetestApplicationCatalogResumeEntryStartsBeforeOldDependencyReturnsOPENTYPE_LIVE_DOWNLOAD_INTEGRATION=1; the other 10 are environment/model gated (ANE model, Apple Speech sample, chat-template probe, foreground bundle identity, Espresso fallback, prompt dump, Qwen native ASR ×2, streaming ASR ×2).bash scripts/build-app.sh --app-only: exit 0; signeddist/Utter.appwith a 3.7 MBdefault.metallib.docs/assets/demos/en-sample.m4a, 0.15 seconds).Task { @MainActor in }cleanup exited 1; the restored test exited 0 after each mutation and returned to treed15b2c7b….Raw accepted regression log:
mac-p34-1cba5718-verification.log.Audit-grade mutation transcript
The audit-grade mutation transcript was captured on macOS using
mac-p102-mutation-harness.sh. It created an independent clean checkout fixed at1cba5718aef0dd61254838fa77d8181cb4af820a, performed exactly four focused runs, streamed and recorded complete combined stdout/stderr, verified target assertion failure markers, and verified exact tree restoration.Its raw header records macOS 27.2, Xcode 27.0 (build
27A266a), and Apple Swift 6.4 (swiftlang-6.4.0.34.1,clang-2100.3.34.1). The Xcode 16.2 / Swift 6.0.3 claim in handoff prose was a transcription error, not execution evidence. Earlier live-download and model-loading results retain their original commit and log attribution below.The four focused runs were:
UtilityTests.swift:409-413(5 expected failures).d15b2c7b02262bf4823646903730122dba90a56f.Task { @MainActor in }— failed atUtilityTests.swift:411-412(2 expected failures).d15b2c7b02262bf4823646903730122dba90a56f.Measured results (
mac-p102-mutation-harness.log):5b6ea6e1d45fec7a56d60513023ab0c3e305e6f152398b98ad3c68660e4842401/ failed with 5 expected assertion failures atUtilityTests.swift:409-413/ elapsed 268s (includes initial scratch build).0(passed in 0.033s) / matched exact baselined15b2c7b02262bf4823646903730122dba90a56f(git diff --exit-code0) / elapsed 14s.1/ failed with 2 expected assertion failures atUtilityTests.swift:411-412/ elapsed 14s.0(passed in 0.032s) / matched exact baselined15b2c7b02262bf4823646903730122dba90a56f(git diff --exit-code0) / elapsed 12s.1cba5718aef0dd61254838fa77d8181cb4af820a: exit0, 0 harness failures, porcelain status clean.This evidence recapture does not require another product-code build, full XCTest run, live-download run, or Release build.
Evidence lineage
0167504b920de6b36cb36f1b8e703ce486d69e8f: baseline recovery implementation.28a847f6018871f70210f119c4d650aed1e13040: live Whisper/Hub interruption and Resume verification.6bcc5adc69c42f21aa905c05b74b6e041fc0b4c7: detached candidate preparation, rollback, and startup cleanup integration.522947265e83f07c730b00c2130db5c45727de55: MLX container load and real generation withdefault.metallib.3442f83641869f912247bef796e624efd813605a: managed-artifact reclaim, O(1) retirement, detached delete, and token recheck.eb4c08a39b2104cbb1045fe0cd7eb589bd1c8e3f: historical documentation baseline before the final P1 continuation.1cba5718aef0dd61254838fa77d8181cb4af820a: accepted final code/test tree and macOS verification target.332e38dafabbe46f3514514a21aba5fe7163ce5d: initial documentation of the measured p3/p4 results.263071e0885fc0e9245c35794e850fca2c76dadb: documentation-only rerun totals, skip taxonomy, mutation summary, and resource-risk decision point.249eca3fd79df4bced0ec42c55baba7dcb383e47: documentation-only follow-up recording the audit-grade mutation transcript and measured results.bf859aa3139c54b0e560ed9123a116407d1c9ee8: documentation-only correction of the raw-log toolchain provenance and stale pending wording.47a0c276e4abf80f76c935c282dcbcda13e6329c: documentation-only correction of the Restored A timing to the raw-log value.Residual risk and release gates
Fixes VEC-3