From 9fb367b26e1e624d844b452e808687c092e97383 Mon Sep 17 00:00:00 2001 From: MrScripty Date: Thu, 24 Sep 2026 17:57:55 -0700 Subject: [PATCH 01/32] feat(torch): select release-compatible runtime defaults --- .../issues.md | 2 +- .../upstream-version-manager-progress.md | 86 +- .../torch-upstream-version-management/plan.md | 43 +- electron/src/generated/desktop-contract.ts | 20 +- .../desktop-contract.validators.d.ts | 10 +- .../generated/desktop-contract.validators.js | 2703 +++++++++++------ electron/src/preload.ts | 6 + .../TorchDesktopProjection.test.tsx | 18 +- .../components/TorchInstallPreview.test.tsx | 331 +- .../src/components/TorchInstallPreview.tsx | 146 +- .../components/app-panels/TorchPanel.test.tsx | 10 +- frontend/src/generated/desktop-contract.ts | 20 +- .../desktop-contract.validators.d.ts | 10 +- .../generated/desktop-contract.validators.js | 2703 +++++++++++------ frontend/src/types/api-bridge-runtime.ts | 3 +- frontend/src/types/torch-install.ts | 2 + .../src/version_manager/mod.rs | 6 +- .../src/version_manager/torch_alternatives.rs | 965 +++++- .../src/version_manager/torch_preview.rs | 103 +- rust/crates/pumas-rpc/src/contract.rs | 239 ++ rust/crates/pumas-rpc/src/contract/export.rs | 8 + rust/crates/pumas-rpc/src/handlers/mod.rs | 6 + torch-server/resolve_runtime.py | 308 +- torch-server/tests/test_resolve_runtime.py | 270 +- 24 files changed, 5827 insertions(+), 2191 deletions(-) diff --git a/docs/plans/current-standards-remediation-2026-09-03/issues.md b/docs/plans/current-standards-remediation-2026-09-03/issues.md index a1a7d4631..8a4e585d0 100644 --- a/docs/plans/current-standards-remediation-2026-09-03/issues.md +++ b/docs/plans/current-standards-remediation-2026-09-03/issues.md @@ -34,7 +34,7 @@ acceptance. Current program-level coordination findings follow. | `PRG-I14` | High | Rust M2F's producer-owned `ConversionProgressOutcome` preserves the core contract's camelCase inner fields, while the handwritten frontend `api-conversion.ts` claims snake_case progress fields. | Cross-process contract drift; does not invalidate the closed Rust producer slice but blocks PRG-A2 consumer acceptance. | Preserve Rust as semantic owner. Platform M1 must generate/decode its exact shape after zero-Legacy, and frontend M4 must delete/migrate the handwritten mismatch rather than making another local schema copy. | Malformed/mismatched conversion payload corpus fails explicitly through the generated Electron/preload Adapter and renderer consumer tests. | | `PRG-I15` | High | Rust M2H review found the core interrupted-download scan mapped a `spawn_blocking` join failure to an empty list, while an intentionally disabled or failed-to-initialize HF client mapped status/mutation/list calls to not-found, false-without-reason, or successful empty data. | Producer truth and lifecycle composition; invalidated the affected M2H closed-outcome claim and would have made platform M1 generate unavailable/failure states as valid missing/empty results. | Resolved: the canonical core/public/desktop seams return disabled HF as `Err`, preserve task-join failure, and keep legitimate missing/false/empty results distinct. The transitional UniFFI caller only projects `Result` pending its accepted removal; no manifest/shared expansion occurred. | Passed: direct disabled-HF and deterministic join-failure tests, real desktop valid-missing/empty cases, real local-IPC method rejection, full core/RPC/adapter suites, default/no-default Clippy/check, format, and diff checks. This seam remains valid, but PRG-I19 separately reopens the recovery mutation commands, so aggregate M2H/count acceptance is suspended. | | `PRG-I16` | Medium | The post-M3 full frontend suite initially passed 470/471: unchanged `ModelMetadataModal.test.tsx` still queried deleted accessible backdrop label `Close metadata modal` and emitted `act` warnings from unawaited initial loads. | Focused evidence reconciliation; frontend owns the stale caller test, and the failure blocked M3-S3b/full-tranche acceptance without invalidating accepted ModalDialog behavior. | Resolved test-only: the caller awaits initial metadata, drives the aria-hidden backdrop's actual `mousedown` Interface, and sends Escape to `document`; no product source or obsolete accessible backdrop was restored. | Passed: focused modal/Popover tests, full lint/types/builds, and the expanded full frontend suite at 109 files/473 tests without the prior warnings. | -| `PRG-I17` | High | Platform M3 found that the reachable GUI-managed Torch path installed upstream source while its launcher expected Pumas `serve.py`; the manifest also advertised Python 3.10 despite the repository pinning 3.12.3. The embedded sidecar, upstream wheel resolver, and managed runtime path have since been implemented and exercised through RPC, including real 2.10 CUDA/Tuldok inference. | Cross-plan deployment authority; Torch runtime installation/process ownership stays in `pumas-app-manager`, sidecar execution stays in `torch-server`, and desktop/API projection consumes the RPC contract. | **Resolved within the repository-owner-authorized scope:** discover stable upstream `vMAJOR.MINOR.PATCH` tags independently of Pumas qualification, subject to a 20-page / 120-second complete-listing budget that fails without partial success. Offer CPU/CUDA/ROCm binary-wheel installation only when the full official hash-locked dependencies resolve for Linux x86_64 and an installed CPython 3.10–3.13. Do not provision Python, compile Torch from source, or claim XPU/other operating systems. Keep install, selection, default, start, and stop explicit. | Passed U1–U4: live discovery returned 63 stable tags; dynamic `v2.9.0+cpu` / CPython 3.12 preview resolved 25 hashed wheels, installed, passed identity/core probe, was selected, passed managed sidecar health/protocol 3, and stopped by owned generation. Cancellation/state-preservation and typed-preview rejection tests passed; the composed desktop projection test passed. See the [accepted plan](../torch-upstream-version-management/plan.md), [current runtime inventory](../torch-diffusion-serving/reports/upstream-version-manager-progress.md), and [retained acceptance summary and artifact manifest](../torch-upstream-version-management/reports/v2.9.0-cpu-rpc-acceptance.json). This is runtime-manager acceptance via RPC plus composed desktop controls, not a real interactive desktop run or an all-release image claim. The actual Tuldok image result remains the exact Torch 2.10 / CUDA 13.0 / Python 3.12 / FLUX.2 tuple. | +| `PRG-I17` | High | Platform M3 found that the reachable GUI-managed Torch path installed upstream source while its launcher expected Pumas `serve.py`; the manifest also advertised Python 3.10 despite the repository pinning 3.12.3. The embedded sidecar, upstream wheel resolver, and managed runtime path have since been implemented and exercised through RPC, including real 2.10 CUDA/Tuldok inference. | Cross-plan deployment authority; Torch runtime installation/process ownership stays in `pumas-app-manager`, sidecar execution stays in `torch-server`, and desktop/API projection consumes the RPC contract. | **Resolved within the repository-owner-authorized scope:** discover stable upstream `vMAJOR.MINOR.PATCH` tags independently of Pumas qualification, subject to a 20-page / 120-second complete-listing budget that fails without partial success. Offer CPU/CUDA/ROCm binary-wheel installation only when the full official hash-locked dependencies resolve for Linux x86_64 and an installed CPython 3.10–3.13. Do not provision Python, compile Torch from source, or claim XPU/other operating systems. Keep install, selection, default, start, and stop explicit. | Passed U1–U4: live discovery returned 63 stable tags; dynamic `v2.9.0+cpu` / CPython 3.12 preview resolved 25 hashed wheels, installed, passed identity/core probe, was selected, passed managed sidecar health/protocol 3, and stopped by owned generation. U6 now discovers release-specific official wheels, filters channels against detected display vendors, recommends only when scan and driver checks support it, and defaults to a Pumas-owned FLUX.2 profile with build/Python/profile choices in Advanced. The live `v2.14.0` scan returned no `cu134`; it does not establish a completed v2.14 dependency preview, install, or image generation. Cancellation/state-preservation and typed-preview rejection tests passed; the composed desktop projection test passed. See the [accepted plan](../torch-upstream-version-management/plan.md), [current runtime inventory](../torch-diffusion-serving/reports/upstream-version-manager-progress.md), and [retained acceptance summary and artifact manifest](../torch-upstream-version-management/reports/v2.9.0-cpu-rpc-acceptance.json). This is runtime-manager acceptance via RPC plus composed desktop controls, not a real interactive desktop run or an all-release image claim. The actual Tuldok image result remains the exact Torch 2.10 / CUDA 13.0 / Python 3.12 / FLUX.2 tuple. | | `PRG-I18` | High | Rust M2I review found four corrected producer counterexample families, then downstream consumer review found a fifth: a complete Q5 row still carried subordinate selected-Q4 recovery identity, and the current renderer prioritizes that identity when matching activity, falsely attaching Q4 state to the ready Q5 row. The earlier families were whole-catalog rejection of valid mixed state, bidirectional hierarchy dirtiness, host-dependent recovery grammar, and bounded run-ID collision. | Rust core/RPC catalog contract and program sequence; the consumer contradiction reopens M2I and blocks its count increase, M2J, RUST-A2, platform M1 generation, and frontend M4 consumption even though the corrected hierarchy/list/grammar/bounds subclaims remain valid. | Active corrective gate: `Complete` cannot carry subordinate recovery identity; only a truly partial row may expose a recovery action. Preserve authoritative ready classification without a renderer-unneeded mixed state, and keep the earlier corrected producer evidence as partial subclaims. | Cross-owner Q5-complete/Q4-part projection must serialize complete with no recovery identity, then the actual renderer matching/row-state corpus must keep Q5 ready and must not attach Q4 activity. Re-run both feature modes and independent review before M2I counts advance or M2J starts. | | `PRG-I19` | Critical | The original M2H recovery request accepted caller-owned repository/path authority. Corrected Slice A closes the durable publication/store prerequisite, and fourth-freeze Slice B now closes the deep task/blocking owner after three rejected boundaries exposed reservation, drain, projection, cancellation, callback, and filesystem-custody defects. C1 and C2 are internally verified, and C3's narrow ordinary admission/resume/restore checkpoint passed review and targeted evidence. The interrupted-response/fresh-owner-reopen path is verified; full C3 is active after PRG-I24's migration scope approval; hard process-crash recovery, stable runtime queue identity, comprehensive owned restore, stalled pause, relocation, and guard-free effects remain incomplete. Builder callback wrappers still spawn the real importer mutation and return before lifecycle observation; C4 owns that repair. Live desktop consumers still send the removed ambient shape. | Rust M2H mutation/filesystem/lifecycle authority plus the M2I/frontend handoff. The unresolved ordinary lifecycle can publish false active state, strand Pausing, signal or call user code under a guard, surrender shared marker/part authority while a state remains resumable, lose FIFO/crash truth, let importer metadata mutate after cancel/release, or regain ambient/path-string authority. M2I recovery/count acceptance stays reopened and M2J, RUST-A2, downstream generation/consumption, and incompatible standalone producer integration remain blocked. | The aggregate boundary remains decomposed into five serialized slices. Slices A/B and C1/C2 are internally accepted; C3 has only its narrow admission checkpoint verified. The focused Rust plan owns current evidence and remaining integration limits. Slice C uses the focused Rust plan's exact source write set, including its bounded shared finalization seam. It makes admission durably ordered before public success/effects; derives destination identity and effects from one held configured root plus validated relative target; exclusively persists ordinary, queue, and Pending/Verified quarantine truth with independent sticky provenance; retains Paused/recoverable Error or Pending authority; releases only verified published terminal state by exact generation with panic rescue; binds pause/resume/relocation to the owner; linearizes guard-free snapshots; owns the actual Aux/Completion importer futures under destination custody; and keeps public synchronous callbacks notification-only. No new public constructor/wire outcome is admitted. Preserve all A/B publication, custody, cleanup, failure-provenance, and projection guarantees. One active Pumas runtime per root remains the task-admission precondition. General client Drop remains M4/RUST-A6; public core/UniFFI ambient routes are transitional only. | Slice A passed both reviews after 18 atomic and 22 store tests per mode. Slice B passed both reviews and root reproduction at exact fourth-freeze hashes with lifecycle 18/18 and download 85/85 per mode, both Clippy modes, format/diff, and current plan gates. Slice C requires deterministic durable-admission/FIFO crash restore and malformed-v3 graph validation, quarantine begin/verify/remove ambiguity, alias/missing/replacement identity, atomic-marker ambiguity, production importer mutation/cancel/successor ordering, exact/partial/auxiliary/recovery-domain contention, pause/final-cleanup winners, dormant resume/cancel/relocation, terminal panic rescue, and reentrant guard/signal oracles; all A/B regressions and focused dual-mode gates must pass before its exact-hash freeze and reviews. Slices D/E then prove domain and closed/full claims. Coherent verified incremental commits follow the root commit decision; compatible reachable contracts are required, and full C3/consumer/release acceptance remains open. | | `PRG-I20` | High | Cross-owner review of the frozen Electron launcher-root recovery producer found that every resolved authority projected to generic `ready`, so a valid environment/argument authority incorrectly enabled a persisted selection that the same explicit authority would outrank on restart. The native chooser also had no single attempt owner: dialog rejection was not projected through the closed result, concurrent invocations could race, and attempts remained available after `restarting`, `replacement-visibility-unknown`, or `published-durability-unavailable`. | Platform M2 startup/selection lifecycle and the serialized frontend recovery handoff. The producer could not define an honest user decision or integrate with its renderer consumer while selection policy and attempt ownership were ambiguous. | Resolved at the corrected producer source boundary: path-free ready state carries `select-library|correct-launch-input`; only supersedable authority offers the chooser. One main-process handler owns dialog, validation, persistence, and synchronous relaunch request; concurrent callers share its Promise; unchanged outcomes explicitly retry; ambiguous, published, and restarting outcomes lock/replay; dialog and relaunch rejection have closed typed outcomes. | Passed locally: 18 focused recovery/preload checks, full Electron build and seven test files, lint, TypeScript validation, plan structure, and diff checks. Program independently reran the full Electron tests/lint/validation. These lifecycle subclaims remain valid, but PRG-I21 separately reopens the uncommitted producer/consumer tranche because the compiled sandboxed preload cannot load its decoder module. | diff --git a/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md b/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md index 106988a49..037a0f11f 100644 --- a/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md +++ b/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md @@ -46,9 +46,27 @@ the current-standards coordination entry is [PRG-I17](../../current-standards-re interpreters. One installed build/interpreter/adapter combination is supported per upstream tag; the UI must disclose the installed combination before a different combination is attempted. +- Dynamic install choices now come from a bounded scan of the official PyTorch + wheel directory and exact per-channel indexes for the selected release and + installed CPython tags. A release/build/Python pair is offered only when that + exact Torch wheel exists for Linux x86_64. Partial scans remain inconclusive; + they do not turn an unobserved wheel into a confirmed absence. The desktop uses + the manager recommendation without requiring build, Python, or image-profile + selections, and advanced settings contain only returned exact pairs. On a + NVIDIA+Intel host, CUDA and CPU may be shown while ROCm is filtered out. + Automatic CUDA selection requires a complete scan and an installed NVIDIA + driver meeting the documented Linux minor-compatibility floor for the channel; + unknown/newer channel families remain advanced. Minor compatibility can limit + features and PTX, so device and model checks still run after installation. + ROCm remains an advanced choice because PCI detection and an `amdgpu` binding + alone do not establish that a particular GPU supports a particular ROCm wheel. + An incomplete scan recommends an exact CPU wheel provisionally when present. - The desktop asks the manager for available choices and a retained preview before installation. Dynamic previews show every resolved wheel URL, version, - and SHA-256, with `none` or FLUX.2 adapter dependencies selected separately. + and SHA-256. The default `flux2` selection means Pumas' FLUX.2 image dependency + profile; it is not an upstream Torch component. Core runtime only is available + as an explicit advanced profile. Neither profile implies qualification for + image generation on every release. The manager retains the exact pip resolution and a fingerprint of the chosen interpreter; installation consumes that retained lock without resolving again. Previews expire after 30 minutes and the in-memory store retains at most 32; @@ -94,6 +112,30 @@ the current-standards coordination entry is [PRG-I17](../../current-standards-re stops only its admitted process generation. The desktop exposes a generation-conditional Stop action after a successful trial. +## 2026-09-24 release-specific choices follow-up + +The reported `v2.14.0` rejection was caused by applying a global `cu134` build +choice to every release. Official wheel discovery confirmed that `v2.14.0` has +exact CPU and CUDA `cu126`, `cu130`, and `cu132` wheel candidates and no +`cu134` candidate; Python changes cannot make the absent build/release pair +exist. The manager's release-options RPC now scans official channels and +returns exact installed-interpreter matches. It filters choices by detected PCI +display vendor, ranks Python 3.12 then 3.13, 3.11, 3.10 when several exact +matches are available, and uses conservative driver policy for automatic CUDA +recommendation. NVIDIA's published minor-compatibility floors are used for +CUDA 13.x, 12.x, and 11.x; unrecognized families and ROCm are not automatically +recommended. The current execution sandbox detects GPU vendors but cannot +communicate with the NVIDIA driver, so its automatic choice is CPU where an exact +CPU wheel exists. This is not a claim about the driver's availability to the +unsandboxed desktop process. + +The flow selects Pumas' FLUX.2 image dependency profile by default. Exact package +resolution still determines whether that profile can be installed for a given +Torch release, and a passing package preview does not prove device use, image +generation, or Tuldok compatibility. The existing real Tuldok evidence remains +limited to Torch 2.10 / CUDA 13.0 / Python 3.12 / FLUX.2; no v2.14.0 image result +or all-release inference claim is added by this implementation. + ## Earlier evidence retained from the previous report The counts and direct-resolver experiments in this subsection predate the @@ -196,7 +238,11 @@ The original isolated launcher root and temporary acceptance file were under management acceptance for the authorized Linux/Python scope. It does not claim a real interactive Pumas desktop run. -## 2026-09-24 current-tree gates +## Historical pre-U6 branch gates + +These counts describe the earlier PR branch before the release-specific U6 +changes below. They remain useful as the prior baseline, not as the current-tree +verification result. - Python resolver: 18 tests passed, including Rust/Python ordered build-vocabulary parity; Ruff passed. The `pumas-app-manager` library suite passed 136 tests, @@ -235,6 +281,42 @@ real interactive Pumas desktop run. completed successfully on 2026-09-24; all five executed jobs passed, including the default-feature Rust quality and no-inference headless gates. +## 2026-09-24 U6 release-specific gates + +- The live official-index command + `python3.12 -I torch-server/resolve_runtime.py --release-options --version 2.14.0 --interpreter python3.10 --interpreter python3.11 --interpreter python3.12` + completed with + `completeScan=true` for installed CPython 3.10, 3.11, and 3.12. Exact + candidates were CPU, `cu126`, `cu130`, `cu132`, `rocm7.2`, and `rocm7.14` + for each interpreter; there was no `cu134` candidate. The manager filters + those raw upstream results against detected display vendors, so an + NVIDIA-plus-Intel host retains CPU/CUDA and does not offer ROCm. CUDA + auto-recommendation also requires a complete scan and a recognized NVIDIA + driver floor. This sandbox cannot query the NVIDIA driver, so it does not + establish which CUDA channel the unsandboxed desktop will recommend. +- Full Python suite: 84 tests passed; `ruff check torch-server` and + `ruff format --check torch-server` passed. `pumas-app-manager`: all 149 tests + passed, including the 14 release-options, driver-floor, incomplete-scan, and + legacy-alternatives filtering tests. `pumas-rpc`: 256 unit tests, 17 + integration tests, and 2 intent integration tests passed; 10 integration and + 2 live-network intent tests were ignored. Rust formatting and manager Clippy + with warnings denied passed. +- Frontend: all 126 test files and 708 tests passed; typecheck and lint passed. + The focused preview tests cover automatic recommendation, exact-release + advanced choices, a null recommendation, Pumas-owned FLUX.2 labeling, and + rejection of CPU-host CUDA/ROCm or mismatched alternatives. Electron's + generated desktop contract check passed. The independent Sol xhigh review + found no issue in the repaired host/release filtering or retained-preview + boundary; the Astra high final review found no remaining implementation + finding and requested this historical/current inventory split. +- The live scan confirms wheel availability only. A direct full resolver attempt + for `v2.14.0` / `cu132` / FLUX.2 ran for several minutes without returning a + result and was interrupted; this establishes neither success nor a dependency + incompatibility. A completed retained dependency preview, install, managed + startup, and Tuldok image generation have not been established here. The + existing actual image result remains limited to Torch 2.10 / CUDA 13.0 / + Python 3.12 / FLUX.2. + ## Separate desktop and image-serving claim 1. The real FLUX.2 GPU/Tuldok result is exercised through RPC and remains scoped diff --git a/docs/plans/torch-upstream-version-management/plan.md b/docs/plans/torch-upstream-version-management/plan.md index 3771ebb87..5e8a5809c 100644 --- a/docs/plans/torch-upstream-version-management/plan.md +++ b/docs/plans/torch-upstream-version-management/plan.md @@ -1,6 +1,6 @@ # Plan: Upstream Torch Version Management -**Plan status:** `Completed` +**Plan status:** `Active` **Owner:** Pumas runtime integration @@ -9,18 +9,24 @@ reviewing the active Torch deployment inventory. ## Execution state -**Current phase:** U1–U4 are accepted for the approved Linux x86_64 / installed -CPython scope. Focused tests, production builds, generated-contract checks, and -read-only cross-layer reviews passed; the real v2.9.0 CPU install and managed RPC -lifecycle passed on 2026-09-24. U5 retains the exact Torch 2.10 CUDA 13.0 / FLUX.2 -Tuldok evidence. A real interactive Pumas desktop image run and image generation -across other Torch tuples are not claimed by this plan. - -**Blocker:** None within the authorized scope. - -**Next slice:** No acceptance work remains in this plan. A real desktop-driven -Tuldok repeat or broader user-defined image-model plugin contract would need a -separately scoped follow-up. +**Current phase:** U1–U5 remain accepted for the approved Linux x86_64 / installed +CPython scope. U6 implementation and acceptance gates are complete: the live +release-specific `v2.14.0` wheel scan, host filtering and driver policy, +generated RPC contract, manager tests, and desktop tests pass. A full +`v2.14.0` FLUX.2 dependency preview and image-generation run remain a separate +exact-tuple validation. A real interactive Pumas desktop image run and image +generation across other Torch tuples are not claimed by this plan. + +**Blocker:** No source implementation blocker. The current sandbox cannot +communicate with the NVIDIA driver, and the full `v2.14.0` FLUX.2 dependency +preview has not completed. CUDA recommendation and Tuldok image generation for +that tuple still need host evidence. + +**Next slice:** Resolve and retain the exact `v2.14.0` / `cu132` / automatically +selected installed Python / Pumas FLUX.2 dependency preview. If it resolves, +install, select, start, and generate through Tuldok on the GPU host; record any +upstream dependency incompatibility explicitly. Keep this exact-tuple result +separate from any all-release claim. ## Product contract @@ -48,6 +54,16 @@ does not claim the dynamic path's full per-wheel URL preview. Every other tuple for `v2.9.1` uses the normal retained preview path and does not inherit the preset's qualification. +Normal installation does not require the user to pick a build, Python +interpreter, or dependency profile. The manager discovers exact official wheels +for the selected release and installed CPython 3.10–3.13 interpreters, filters +GPU channels by detected display-device vendor, and returns a recommendation +when its bounded scan and host checks support one. Advanced controls can select +only a returned exact wheel/interpreter pair. Every install still requires the +existing full dependency preview and retained hash lock. The default image +profile is Pumas-owned FLUX.2 dependencies, not an upstream Torch feature; Core +runtime only remains an explicit advanced choice. + Installing a release does not activate it or make it the default. Selection, default selection, sidecar startup, and stopping remain explicit version/profile operations. A successful import or health check qualifies only its exact @@ -74,6 +90,7 @@ updated PRG-I17 disposition. | U3 | A non-preset release installs into staging from the retained lock, passes identity/core probes, and publishes only after validation. Failure/cancellation preserves installed, active, and default state. | Managed installation tests and an isolated real v2.9.0+cpu / CPython 3.12 installation on Linux x86_64; see the [RPC acceptance summary and artifact manifest](reports/v2.9.0-cpu-rpc-acceptance.json). | | U4 | The installed runtime can be inspected, explicitly selected, started by a managed Torch profile, health/protocol checked, and stopped by its owned generation through Pumas RPC or the desktop. | Composed desktop test plus a real older-release RPC lifecycle on Linux x86_64; see the [RPC acceptance summary](reports/v2.9.0-cpu-rpc-acceptance.json). | | U5 | The current real Tuldok image result remains accurately scoped to its exact Torch 2.10 CUDA 13.0 / FLUX.2 tuple; no general model/plugin or all-release image-generation support is inferred. | Reuse [2.10 GPU and Tuldok evidence](../torch-diffusion-serving/reports/upstream-v210-cu130-flux2-e2e.md); update the runtime inventory in [the progress report](../torch-diffusion-serving/reports/upstream-version-manager-progress.md). | +| U6 | For each selected stable release, the manager discovers that release's exact official CPU/CUDA/ROCm wheel matches for installed CPython interpreters, filters GPU families against detected display devices, and supplies a host-aware default when compatibility is established. Build, Python, and Pumas dependency profile are optional advanced settings. Partial discovery stays inconclusive; a wheel match still requires the full retained dependency preview. | Dynamic official-index scan fixtures and live `v2.14.0` scan; NVIDIA driver-floor/unknown/partial-scan manager tests; typed RPC and generated-contract checks; desktop preview tests for automatic defaults, release-bounded overrides, null recommendations, and Pumas profile labeling. No all-release image qualification is inferred. | ## Ownership and verification diff --git a/electron/src/generated/desktop-contract.ts b/electron/src/generated/desktop-contract.ts index eb3e65bad..ae8ff956d 100644 --- a/electron/src/generated/desktop-contract.ts +++ b/electron/src/generated/desktop-contract.ts @@ -1,5 +1,5 @@ -// Generated from pumas-rpc contract.rs; SHA256 4f6771f852879c4de52ee8d1e66c25a3bba650bf832d9dbba022b3b8b0b4c75c. DO NOT EDIT. -import { validateAvailableVersionsOutcome, validateBackendStatusOutcome, validateCancelInstallationOutcome, validateCatalogSearchOutcome, validateCheckVersionDependenciesOutcome, validateCheckVersionDependenciesParams, validateConversionCancelledOutcome, validateConversionEnvironmentOutcome, validateConversionListOutcome, validateConversionProgressResponse, validateConversionSetupStartedOutcome, validateConversionSetupStatusOutcome, validateConversionStartedOutcome, validateDownloadIdParams, validateDownloadListOutcome, validateDownloadMutationOutcome, validateDownloadStartedOutcome, validateDownloadStatusOutcome, validateFindTorchAlternativesParams, validateGetBackendSetupParams, validateGetHfDownloadDetailsParams, validateGetReleaseDependenciesOutcome, validateGetReleaseDependenciesParams, validateGetTorchPreviewReportParams, validateGetTorchRuntimeProbeParams, validateGithubCacheStatusOutcome, validateHfDownloadDetailsOutcome, validateInferenceSettingsOutcome, validateInstallVersionDependenciesOutcome, validateInstallVersionDependenciesParams, validateInstallVersionOutcome, validateInstallVersionParams, validateInstallationProgressOutcome, validateInstalledVersionsOutcome, validateLibraryModelMetadataOutcome, validateLinkHealthOutcome, validateModelIndexRefreshOutcome, validateModelsOutcome, validatePartialDownloadOutcome, validatePreviewTorchRuntimeParams, validatePublicError, validateRecoverDownloadParams, validateRemoveVersionOutcome, validateRouterProfileSyncStatus, validateRuntimeLaunchOutcome, validateRuntimeLaunchParams, validateRuntimeRunningOutcome, validateRuntimeStopOutcome, validateSearchCatalogParams, validateSelectedVersionOutcome, validateSetDefaultVersionOutcome, validateSetDefaultVersionParams, validateStartBackendSetupParams, validateStartConversionSetupParams, validateStopRuntimeProfileGenerationOutcome, validateStopRuntimeProfileGenerationParams, validateSuccessOutcome, validateSupportedQuantTypesOutcome, validateSwitchVersionOutcome, validateSwitchVersionParams, validateTorchRuntimePreview, validateTorchRuntimePreviewArtifact, validateTorchRuntimePreviewOutcome, validateTorchRuntimePreviewQualification, validateTorchRuntimePreviewRejectionReason, validateTrialTorchRuntimeOutcome, validateTrialTorchRuntimeParams, validateUpdateInferenceSettingsOutcome, validateUpdateInferenceSettingsParams, validateUpdateModelNotesOutcome, validateUpdateModelNotesParams, validateValidateInstallationsOutcome, validateVersionInfoOutcome, validateVersionStatusOutcome } from './desktop-contract.validators.js'; +// Generated from pumas-rpc contract.rs; SHA256 67dd940c814b877de830883b563b559875d539d93a9158aba6cb812ffba3419a. DO NOT EDIT. +import { validateAvailableVersionsOutcome, validateBackendStatusOutcome, validateCancelInstallationOutcome, validateCatalogSearchOutcome, validateCheckVersionDependenciesOutcome, validateCheckVersionDependenciesParams, validateConversionCancelledOutcome, validateConversionEnvironmentOutcome, validateConversionListOutcome, validateConversionProgressResponse, validateConversionSetupStartedOutcome, validateConversionSetupStatusOutcome, validateConversionStartedOutcome, validateDownloadIdParams, validateDownloadListOutcome, validateDownloadMutationOutcome, validateDownloadStartedOutcome, validateDownloadStatusOutcome, validateFindTorchAlternativesParams, validateGetBackendSetupParams, validateGetHfDownloadDetailsParams, validateGetReleaseDependenciesOutcome, validateGetReleaseDependenciesParams, validateGetTorchPreviewReportParams, validateGetTorchReleaseOptionsParams, validateGetTorchRuntimeProbeParams, validateGithubCacheStatusOutcome, validateHfDownloadDetailsOutcome, validateInferenceSettingsOutcome, validateInstallVersionDependenciesOutcome, validateInstallVersionDependenciesParams, validateInstallVersionOutcome, validateInstallVersionParams, validateInstallationProgressOutcome, validateInstalledVersionsOutcome, validateLibraryModelMetadataOutcome, validateLinkHealthOutcome, validateModelIndexRefreshOutcome, validateModelsOutcome, validatePartialDownloadOutcome, validatePreviewTorchRuntimeParams, validatePublicError, validateRecoverDownloadParams, validateRemoveVersionOutcome, validateRouterProfileSyncStatus, validateRuntimeLaunchOutcome, validateRuntimeLaunchParams, validateRuntimeRunningOutcome, validateRuntimeStopOutcome, validateSearchCatalogParams, validateSelectedVersionOutcome, validateSetDefaultVersionOutcome, validateSetDefaultVersionParams, validateStartBackendSetupParams, validateStartConversionSetupParams, validateStopRuntimeProfileGenerationOutcome, validateStopRuntimeProfileGenerationParams, validateSuccessOutcome, validateSupportedQuantTypesOutcome, validateSwitchVersionOutcome, validateSwitchVersionParams, validateTorchDriverAvailability, validateTorchGpuVendor, validateTorchReleaseCombination, validateTorchReleaseDriverStatus, validateTorchReleaseOptionsOutcome, validateTorchReleaseOptionsStatus, validateTorchReleaseRecommendation, validateTorchRuntimePreview, validateTorchRuntimePreviewArtifact, validateTorchRuntimePreviewOutcome, validateTorchRuntimePreviewQualification, validateTorchRuntimePreviewRejectionReason, validateTrialTorchRuntimeOutcome, validateTrialTorchRuntimeParams, validateUpdateInferenceSettingsOutcome, validateUpdateInferenceSettingsParams, validateUpdateModelNotesOutcome, validateUpdateModelNotesParams, validateValidateInstallationsOutcome, validateVersionInfoOutcome, validateVersionStatusOutcome } from './desktop-contract.validators.js'; export type AvailableVersionsOutcome = (AvailableVersionsSuccess) | (AvailableVersionsRateLimited); export type AvailableVersionsRateLimited = { "error": string; "rate_limited": true; "retry_after_secs": number | null; "success": false }; export type AvailableVersionsSuccess = { "success": true; "versions": ReadonlyArray }; @@ -50,6 +50,7 @@ export type GetHfDownloadDetailsParams = ({ "quants"?: ReadonlyArray | n export type GetReleaseDependenciesOutcome = { "dependencies": ReadonlyArray; "success": true }; export type GetReleaseDependenciesParams = ({ "app_id": string; "tag": string }) | ({ "appId": string; "tag": string }); export type GetTorchPreviewReportParams = ({ "preview_id": string }) | ({ "previewId": string }); +export type GetTorchReleaseOptionsParams = { "tag": string }; export type GetTorchRuntimeProbeParams = { "tag": string }; export type GithubCacheStatusNoManager = { "has_cache": false; "is_fetching": false; "is_valid": false }; export type GithubCacheStatusOutcome = (GithubCacheStatusSnapshot) | (GithubCacheStatusNoManager); @@ -113,6 +114,13 @@ export type SuccessOutcome = { "success": true }; export type SupportedQuantTypesOutcome = { "quant_types": ReadonlyArray; "success": true }; export type SwitchVersionOutcome = { "success": boolean }; export type SwitchVersionParams = ({ "app_id": string; "tag": string }) | ({ "appId": string; "tag": string }); +export type TorchDriverAvailability = "available" | "unavailable" | "not_present" | "unknown"; +export type TorchGpuVendor = "nvidia" | "amd" | "intel"; +export type TorchReleaseCombination = { "build": string; "python": string; "sha256"?: string | null; "wheelUrl": string }; +export type TorchReleaseDriverStatus = { "amd": TorchDriverAvailability; "nvidia": TorchDriverAvailability }; +export type TorchReleaseOptionsOutcome = { "checkedChannels": ReadonlyArray; "combinations": ReadonlyArray; "completeScan": boolean; "detectedGpuVendors": ReadonlyArray; "driverStatus": TorchReleaseDriverStatus; "issues": ReadonlyArray; "recommendationNote": string | null; "recommended": (TorchReleaseRecommendation) | (null); "status": TorchReleaseOptionsStatus; "tag": string }; +export type TorchReleaseOptionsStatus = "matches" | "none" | "inconclusive"; +export type TorchReleaseRecommendation = { "build": string; "python": string }; export type TorchRuntimePreview = { "adapter": string; "artifacts": ReadonlyArray; "build": string; "expiresInSeconds": number; "previewId": string; "python": string; "qualification": TorchRuntimePreviewQualification; "tag": string }; export type TorchRuntimePreviewArtifact = { "name": string; "sha256": string; "url": string; "version": string }; export type TorchRuntimePreviewOutcome = ({ "preview": TorchRuntimePreview; "status": "resolved" }) | ({ "message": string; "reason": TorchRuntimePreviewRejectionReason; "status": "rejected" }); @@ -189,6 +197,7 @@ export function decodeGetHfDownloadDetailsParams(input: unknown): DecodeOutcome< export function decodeGetReleaseDependenciesOutcome(input: unknown): DecodeOutcome { return decode(input, validateGetReleaseDependenciesOutcome); } export function decodeGetReleaseDependenciesParams(input: unknown): DecodeOutcome { return decode(input, validateGetReleaseDependenciesParams); } export function decodeGetTorchPreviewReportParams(input: unknown): DecodeOutcome { return decode(input, validateGetTorchPreviewReportParams); } +export function decodeGetTorchReleaseOptionsParams(input: unknown): DecodeOutcome { return decode(input, validateGetTorchReleaseOptionsParams); } export function decodeGetTorchRuntimeProbeParams(input: unknown): DecodeOutcome { return decode(input, validateGetTorchRuntimeProbeParams); } export function decodeGithubCacheStatusOutcome(input: unknown): DecodeOutcome { return decode(input, validateGithubCacheStatusOutcome); } export function decodeHfDownloadDetailsOutcome(input: unknown): DecodeOutcome { return decode(input, validateHfDownloadDetailsOutcome); } @@ -225,6 +234,13 @@ export function decodeSuccessOutcome(input: unknown): DecodeOutcome { return decode(input, validateSupportedQuantTypesOutcome); } export function decodeSwitchVersionOutcome(input: unknown): DecodeOutcome { return decode(input, validateSwitchVersionOutcome); } export function decodeSwitchVersionParams(input: unknown): DecodeOutcome { return decode(input, validateSwitchVersionParams); } +export function decodeTorchDriverAvailability(input: unknown): DecodeOutcome { return decode(input, validateTorchDriverAvailability); } +export function decodeTorchGpuVendor(input: unknown): DecodeOutcome { return decode(input, validateTorchGpuVendor); } +export function decodeTorchReleaseCombination(input: unknown): DecodeOutcome { return decode(input, validateTorchReleaseCombination); } +export function decodeTorchReleaseDriverStatus(input: unknown): DecodeOutcome { return decode(input, validateTorchReleaseDriverStatus); } +export function decodeTorchReleaseOptionsOutcome(input: unknown): DecodeOutcome { return decode(input, validateTorchReleaseOptionsOutcome); } +export function decodeTorchReleaseOptionsStatus(input: unknown): DecodeOutcome { return decode(input, validateTorchReleaseOptionsStatus); } +export function decodeTorchReleaseRecommendation(input: unknown): DecodeOutcome { return decode(input, validateTorchReleaseRecommendation); } export function decodeTorchRuntimePreview(input: unknown): DecodeOutcome { return decode(input, validateTorchRuntimePreview); } export function decodeTorchRuntimePreviewArtifact(input: unknown): DecodeOutcome { return decode(input, validateTorchRuntimePreviewArtifact); } export function decodeTorchRuntimePreviewOutcome(input: unknown): DecodeOutcome { return decode(input, validateTorchRuntimePreviewOutcome); } diff --git a/electron/src/generated/desktop-contract.validators.d.ts b/electron/src/generated/desktop-contract.validators.d.ts index a3d66a5d4..8a0ca8711 100644 --- a/electron/src/generated/desktop-contract.validators.d.ts +++ b/electron/src/generated/desktop-contract.validators.d.ts @@ -1,4 +1,4 @@ -// Generated from pumas-rpc contract.rs; SHA256 4f6771f852879c4de52ee8d1e66c25a3bba650bf832d9dbba022b3b8b0b4c75c. DO NOT EDIT. +// Generated from pumas-rpc contract.rs; SHA256 67dd940c814b877de830883b563b559875d539d93a9158aba6cb812ffba3419a. DO NOT EDIT. export declare function validateAvailableVersionsOutcome(value: unknown): boolean; export declare function validateBackendStatusOutcome(value: unknown): boolean; export declare function validateCancelInstallationOutcome(value: unknown): boolean; @@ -23,6 +23,7 @@ export declare function validateGetHfDownloadDetailsParams(value: unknown): bool export declare function validateGetReleaseDependenciesOutcome(value: unknown): boolean; export declare function validateGetReleaseDependenciesParams(value: unknown): boolean; export declare function validateGetTorchPreviewReportParams(value: unknown): boolean; +export declare function validateGetTorchReleaseOptionsParams(value: unknown): boolean; export declare function validateGetTorchRuntimeProbeParams(value: unknown): boolean; export declare function validateGithubCacheStatusOutcome(value: unknown): boolean; export declare function validateHfDownloadDetailsOutcome(value: unknown): boolean; @@ -59,6 +60,13 @@ export declare function validateSuccessOutcome(value: unknown): boolean; export declare function validateSupportedQuantTypesOutcome(value: unknown): boolean; export declare function validateSwitchVersionOutcome(value: unknown): boolean; export declare function validateSwitchVersionParams(value: unknown): boolean; +export declare function validateTorchDriverAvailability(value: unknown): boolean; +export declare function validateTorchGpuVendor(value: unknown): boolean; +export declare function validateTorchReleaseCombination(value: unknown): boolean; +export declare function validateTorchReleaseDriverStatus(value: unknown): boolean; +export declare function validateTorchReleaseOptionsOutcome(value: unknown): boolean; +export declare function validateTorchReleaseOptionsStatus(value: unknown): boolean; +export declare function validateTorchReleaseRecommendation(value: unknown): boolean; export declare function validateTorchRuntimePreview(value: unknown): boolean; export declare function validateTorchRuntimePreviewArtifact(value: unknown): boolean; export declare function validateTorchRuntimePreviewOutcome(value: unknown): boolean; diff --git a/electron/src/generated/desktop-contract.validators.js b/electron/src/generated/desktop-contract.validators.js index 3ee71fab8..abfafbec0 100644 --- a/electron/src/generated/desktop-contract.validators.js +++ b/electron/src/generated/desktop-contract.validators.js @@ -1,4 +1,4 @@ -// Generated from pumas-rpc contract.rs; SHA256 4f6771f852879c4de52ee8d1e66c25a3bba650bf832d9dbba022b3b8b0b4c75c. DO NOT EDIT. +// Generated from pumas-rpc contract.rs; SHA256 67dd940c814b877de830883b563b559875d539d93a9158aba6cb812ffba3419a. DO NOT EDIT. var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; @@ -7735,7 +7735,7 @@ function validate55(data, { instancePath = "", parentData, parentDataProperty, r validate55.errors = vErrors; return errors === 0; } -var validateGetTorchRuntimeProbeParams = validate56; +var validateGetTorchReleaseOptionsParams = validate56; function validate56(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; @@ -7771,9 +7771,45 @@ function validate56(data, { instancePath = "", parentData, parentDataProperty, r validate56.errors = vErrors; return errors === 0; } -var validateGithubCacheStatusOutcome = validate57; -var schema67 = { "additionalProperties": false, "properties": { "age_seconds": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] }, "has_cache": { "type": "boolean" }, "is_fetching": { "type": "boolean" }, "is_valid": { "type": "boolean" }, "last_fetched": { "type": ["string", "null"] }, "releases_count": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] } }, "required": ["has_cache", "is_valid", "is_fetching", "age_seconds", "last_fetched", "releases_count"], "type": "object" }; +var validateGetTorchRuntimeProbeParams = validate57; function validate57(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.tag === void 0 && (missing0 = "tag")) { + validate57.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!(key0 === "tag")) { + validate57.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.tag !== void 0) { + if (typeof data.tag !== "string") { + validate57.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + } + } + } + } else { + validate57.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate57.errors = vErrors; + return errors === 0; +} +var validateGithubCacheStatusOutcome = validate58; +var schema68 = { "additionalProperties": false, "properties": { "age_seconds": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] }, "has_cache": { "type": "boolean" }, "is_fetching": { "type": "boolean" }, "is_valid": { "type": "boolean" }, "last_fetched": { "type": ["string", "null"] }, "releases_count": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] } }, "required": ["has_cache", "is_valid", "is_fetching", "age_seconds", "last_fetched", "releases_count"], "type": "object" }; +function validate58(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -7810,7 +7846,7 @@ function validate57(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.age_seconds; const _errs5 = errors; if (!(typeof data0 == "number" && (!(data0 % 1) && !isNaN(data0)) && isFinite(data0)) && data0 !== null) { - const err2 = { instancePath: instancePath + "/age_seconds", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/age_seconds/type", keyword: "type", params: { type: schema67.properties.age_seconds.type }, message: "must be integer,null" }; + const err2 = { instancePath: instancePath + "/age_seconds", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/age_seconds/type", keyword: "type", params: { type: schema68.properties.age_seconds.type }, message: "must be integer,null" }; if (vErrors === null) { vErrors = [err2]; } else { @@ -7898,7 +7934,7 @@ function validate57(data, { instancePath = "", parentData, parentDataProperty, r let data4 = data.last_fetched; const _errs13 = errors; if (typeof data4 !== "string" && data4 !== null) { - const err8 = { instancePath: instancePath + "/last_fetched", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/last_fetched/type", keyword: "type", params: { type: schema67.properties.last_fetched.type }, message: "must be string,null" }; + const err8 = { instancePath: instancePath + "/last_fetched", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/last_fetched/type", keyword: "type", params: { type: schema68.properties.last_fetched.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err8]; } else { @@ -7915,7 +7951,7 @@ function validate57(data, { instancePath = "", parentData, parentDataProperty, r let data5 = data.releases_count; const _errs15 = errors; if (!(typeof data5 == "number" && (!(data5 % 1) && !isNaN(data5)) && isFinite(data5)) && data5 !== null) { - const err9 = { instancePath: instancePath + "/releases_count", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/releases_count/type", keyword: "type", params: { type: schema67.properties.releases_count.type }, message: "must be integer,null" }; + const err9 = { instancePath: instancePath + "/releases_count", schemaPath: "#/definitions/GithubCacheStatusSnapshot/properties/releases_count/type", keyword: "type", params: { type: schema68.properties.releases_count.type }, message: "must be integer,null" }; if (vErrors === null) { vErrors = [err9]; } else { @@ -8100,7 +8136,7 @@ function validate57(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err22); } errors++; - validate57.errors = vErrors; + validate58.errors = vErrors; return false; } else { errors = _errs0; @@ -8112,26 +8148,26 @@ function validate57(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate57.errors = vErrors; + validate58.errors = vErrors; return errors === 0; } -var validateHfDownloadDetailsOutcome = validate58; -var schema71 = { "additionalProperties": false, "description": "Exact download details derived from a repository file tree.", "properties": { "downloadOptions": { "default": [], "items": { "$ref": "#/definitions/DownloadOption" }, "type": "array" }, "repoId": { "type": "string" }, "totalSizeBytes": { "default": null, "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["repoId", "downloadOptions", "totalSizeBytes"], "type": "object" }; -var schema72 = { "additionalProperties": false, "description": "Download option for a quantization variant or file group.", "properties": { "fileGroup": { "$ref": "#/definitions/FileGroup" }, "quant": { "type": "string" }, "sizeBytes": { "default": null, "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["quant", "sizeBytes"], "type": "object" }; -function validate61(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateHfDownloadDetailsOutcome = validate59; +var schema72 = { "additionalProperties": false, "description": "Exact download details derived from a repository file tree.", "properties": { "downloadOptions": { "default": [], "items": { "$ref": "#/definitions/DownloadOption" }, "type": "array" }, "repoId": { "type": "string" }, "totalSizeBytes": { "default": null, "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["repoId", "downloadOptions", "totalSizeBytes"], "type": "object" }; +var schema73 = { "additionalProperties": false, "description": "Download option for a quantization variant or file group.", "properties": { "fileGroup": { "$ref": "#/definitions/FileGroup" }, "quant": { "type": "string" }, "sizeBytes": { "default": null, "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["quant", "sizeBytes"], "type": "object" }; +function validate62(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.quant === void 0 && (missing0 = "quant") || data.sizeBytes === void 0 && (missing0 = "sizeBytes")) { - validate61.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate62.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "fileGroup" || key0 === "quant" || key0 === "sizeBytes")) { - validate61.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate62.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8145,13 +8181,13 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r if (data0 && typeof data0 == "object" && !Array.isArray(data0)) { let missing1; if (data0.filenames === void 0 && (missing1 = "filenames") || data0.shardCount === void 0 && (missing1 = "shardCount") || data0.label === void 0 && (missing1 = "label")) { - validate61.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs5 = errors; for (const key1 in data0) { if (!(key1 === "filenames" || key1 === "label" || key1 === "shardCount")) { - validate61.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8167,7 +8203,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r for (let i0 = 0; i0 < len0; i0++) { const _errs8 = errors; if (typeof data1[i0] !== "string") { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/filenames/" + i0, schemaPath: "#/definitions/FileGroup/properties/filenames/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/filenames/" + i0, schemaPath: "#/definitions/FileGroup/properties/filenames/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs8 === errors; @@ -8176,7 +8212,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/filenames", schemaPath: "#/definitions/FileGroup/properties/filenames/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/filenames", schemaPath: "#/definitions/FileGroup/properties/filenames/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -8188,7 +8224,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r if (data0.label !== void 0) { const _errs10 = errors; if (typeof data0.label !== "string") { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/label", schemaPath: "#/definitions/FileGroup/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/label", schemaPath: "#/definitions/FileGroup/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid2 = _errs10 === errors; @@ -8200,17 +8236,17 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r let data4 = data0.shardCount; const _errs12 = errors; if (!(typeof data4 == "number" && (!(data4 % 1) && !isNaN(data4)) && isFinite(data4))) { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs12) { if (typeof data4 == "number" && isFinite(data4)) { if (data4 > 4294967295 || isNaN(data4)) { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data4 < 0 || isNaN(data4)) { - validate61.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup/shardCount", schemaPath: "#/definitions/FileGroup/properties/shardCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -8225,7 +8261,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate61.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate62.errors = [{ instancePath: instancePath + "/fileGroup", schemaPath: "#/definitions/FileGroup/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -8237,7 +8273,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r if (data.quant !== void 0) { const _errs14 = errors; if (typeof data.quant !== "string") { - validate61.errors = [{ instancePath: instancePath + "/quant", schemaPath: "#/properties/quant/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate62.errors = [{ instancePath: instancePath + "/quant", schemaPath: "#/properties/quant/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs14 === errors; @@ -8249,17 +8285,17 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r let data6 = data.sizeBytes; const _errs16 = errors; if (!(typeof data6 == "number" && (!(data6 % 1) && !isNaN(data6)) && isFinite(data6)) && data6 !== null) { - validate61.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/type", keyword: "type", params: { type: schema72.properties.sizeBytes.type }, message: "must be integer,null" }]; + validate62.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/type", keyword: "type", params: { type: schema73.properties.sizeBytes.type }, message: "must be integer,null" }]; return false; } if (errors === _errs16) { if (typeof data6 == "number" && isFinite(data6)) { if (data6 > 9007199254740991 || isNaN(data6)) { - validate61.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate62.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data6 < 0 || isNaN(data6)) { - validate61.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate62.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -8274,27 +8310,27 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate61.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate62.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate61.errors = vErrors; + validate62.errors = vErrors; return errors === 0; } -function validate60(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate61(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.repoId === void 0 && (missing0 = "repoId") || data.downloadOptions === void 0 && (missing0 = "downloadOptions") || data.totalSizeBytes === void 0 && (missing0 = "totalSizeBytes")) { - validate60.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate61.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "downloadOptions" || key0 === "repoId" || key0 === "totalSizeBytes")) { - validate60.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate61.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8309,8 +8345,8 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; - if (!validate61(data0[i0], { instancePath: instancePath + "/downloadOptions/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate61.errors : vErrors.concat(validate61.errors); + if (!validate62(data0[i0], { instancePath: instancePath + "/downloadOptions/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate62.errors : vErrors.concat(validate62.errors); errors = vErrors.length; } var valid1 = _errs4 === errors; @@ -8319,7 +8355,7 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate60.errors = [{ instancePath: instancePath + "/downloadOptions", schemaPath: "#/properties/downloadOptions/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate61.errors = [{ instancePath: instancePath + "/downloadOptions", schemaPath: "#/properties/downloadOptions/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -8331,7 +8367,7 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r if (data.repoId !== void 0) { const _errs5 = errors; if (typeof data.repoId !== "string") { - validate60.errors = [{ instancePath: instancePath + "/repoId", schemaPath: "#/properties/repoId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate61.errors = [{ instancePath: instancePath + "/repoId", schemaPath: "#/properties/repoId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs5 === errors; @@ -8343,17 +8379,17 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r let data3 = data.totalSizeBytes; const _errs7 = errors; if (!(typeof data3 == "number" && (!(data3 % 1) && !isNaN(data3)) && isFinite(data3)) && data3 !== null) { - validate60.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/type", keyword: "type", params: { type: schema71.properties.totalSizeBytes.type }, message: "must be integer,null" }]; + validate61.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/type", keyword: "type", params: { type: schema72.properties.totalSizeBytes.type }, message: "must be integer,null" }]; return false; } if (errors === _errs7) { if (typeof data3 == "number" && isFinite(data3)) { if (data3 > 9007199254740991 || isNaN(data3)) { - validate60.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate61.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data3 < 0 || isNaN(data3)) { - validate60.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate61.errors = [{ instancePath: instancePath + "/totalSizeBytes", schemaPath: "#/properties/totalSizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -8368,27 +8404,27 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate60.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate61.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate60.errors = vErrors; + validate61.errors = vErrors; return errors === 0; } -function validate59(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate60(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.details === void 0 && (missing0 = "details")) { - validate59.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate60.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "details" || key0 === "success")) { - validate59.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate60.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8396,8 +8432,8 @@ function validate59(data, { instancePath = "", parentData, parentDataProperty, r if (_errs1 === errors) { if (data.details !== void 0) { const _errs2 = errors; - if (!validate60(data.details, { instancePath: instancePath + "/details", parentData: data, parentDataProperty: "details", rootData })) { - vErrors = vErrors === null ? validate60.errors : vErrors.concat(validate60.errors); + if (!validate61(data.details, { instancePath: instancePath + "/details", parentData: data, parentDataProperty: "details", rootData })) { + vErrors = vErrors === null ? validate61.errors : vErrors.concat(validate61.errors); errors = vErrors.length; } var valid0 = _errs2 === errors; @@ -8409,11 +8445,11 @@ function validate59(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.success; const _errs3 = errors; if (typeof data1 !== "boolean") { - validate59.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate60.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data1) { - validate59.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate60.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs3 === errors; @@ -8424,21 +8460,21 @@ function validate59(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate59.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate60.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate59.errors = vErrors; + validate60.errors = vErrors; return errors === 0; } -function validate58(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate59(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; let valid0 = false; const _errs1 = errors; - if (!validate59(data, { instancePath, parentData, parentDataProperty, rootData })) { - vErrors = vErrors === null ? validate59.errors : vErrors.concat(validate59.errors); + if (!validate60(data, { instancePath, parentData, parentDataProperty, rootData })) { + vErrors = vErrors === null ? validate60.errors : vErrors.concat(validate60.errors); errors = vErrors.length; } var _valid0 = _errs1 === errors; @@ -8537,7 +8573,7 @@ function validate58(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err6); } errors++; - validate58.errors = vErrors; + validate59.errors = vErrors; return false; } else { errors = _errs0; @@ -8549,15 +8585,15 @@ function validate58(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate58.errors = vErrors; + validate59.errors = vErrors; return errors === 0; } -var validateInferenceSettingsOutcome = validate65; -var schema76 = { "additionalProperties": false, "description": "Describes a single configurable inference parameter with its type,\ndefault value, and optional constraints.\n\nDownstream consumers (e.g. Pantograph node graph) use this schema\nto dynamically render UI controls for model-specific settings.", "properties": { "constraints": { "anyOf": [{ "$ref": "#/definitions/ParamConstraints" }, { "type": "null" }], "default": null, "description": "Optional numeric/enum constraints." }, "default": { "$ref": "#/definitions/DesktopJsonValue" }, "description": { "default": null, "description": "Optional description / tooltip.", "type": ["string", "null"] }, "key": { "description": 'Machine-readable key (e.g. "context_length", "denoising_steps").', "type": "string" }, "label": { "description": 'Human-readable label (e.g. "Context Length").', "type": "string" }, "param_type": { "allOf": [{ "$ref": "#/definitions/ParamType" }], "description": "Data type of this parameter." } }, "required": ["key", "label", "param_type", "default", "description", "constraints"], "type": "object" }; -var schema79 = { "description": "Data type for an inference parameter.", "enum": ["Number", "Integer", "String", "Boolean"], "type": "string" }; -var schema77 = { "additionalProperties": false, "description": "Constraints on an inference parameter value.", "properties": { "allowed_values": { "anyOf": [{ "type": "null" }, { "items": { "$ref": "#/definitions/DesktopJsonValue" }, "type": "array" }] }, "max": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "min": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] } }, "required": ["min", "max", "allowed_values"], "type": "object" }; -var wrapper0 = { validate: validate68 }; -function validate68(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInferenceSettingsOutcome = validate66; +var schema77 = { "additionalProperties": false, "description": "Describes a single configurable inference parameter with its type,\ndefault value, and optional constraints.\n\nDownstream consumers (e.g. Pantograph node graph) use this schema\nto dynamically render UI controls for model-specific settings.", "properties": { "constraints": { "anyOf": [{ "$ref": "#/definitions/ParamConstraints" }, { "type": "null" }], "default": null, "description": "Optional numeric/enum constraints." }, "default": { "$ref": "#/definitions/DesktopJsonValue" }, "description": { "default": null, "description": "Optional description / tooltip.", "type": ["string", "null"] }, "key": { "description": 'Machine-readable key (e.g. "context_length", "denoising_steps").', "type": "string" }, "label": { "description": 'Human-readable label (e.g. "Context Length").', "type": "string" }, "param_type": { "allOf": [{ "$ref": "#/definitions/ParamType" }], "description": "Data type of this parameter." } }, "required": ["key", "label", "param_type", "default", "description", "constraints"], "type": "object" }; +var schema80 = { "description": "Data type for an inference parameter.", "enum": ["Number", "Integer", "String", "Boolean"], "type": "string" }; +var schema78 = { "additionalProperties": false, "description": "Constraints on an inference parameter value.", "properties": { "allowed_values": { "anyOf": [{ "type": "null" }, { "items": { "$ref": "#/definitions/DesktopJsonValue" }, "type": "array" }] }, "max": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "min": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] } }, "required": ["min", "max", "allowed_values"], "type": "object" }; +var wrapper0 = { validate: validate69 }; +function validate69(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -8704,7 +8740,7 @@ function validate68(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err8); } errors++; - validate68.errors = vErrors; + validate69.errors = vErrors; return false; } else { errors = _errs0; @@ -8716,23 +8752,23 @@ function validate68(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate68.errors = vErrors; + validate69.errors = vErrors; return errors === 0; } -function validate67(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate68(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.min === void 0 && (missing0 = "min") || data.max === void 0 && (missing0 = "max") || data.allowed_values === void 0 && (missing0 = "allowed_values")) { - validate67.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate68.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "allowed_values" || key0 === "max" || key0 === "min")) { - validate67.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate68.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8763,8 +8799,8 @@ function validate67(data, { instancePath = "", parentData, parentDataProperty, r const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs8 = errors; - if (!validate68(data0[i0], { instancePath: instancePath + "/allowed_values/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors); + if (!validate69(data0[i0], { instancePath: instancePath + "/allowed_values/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate69.errors : vErrors.concat(validate69.errors); errors = vErrors.length; } var valid2 = _errs8 === errors; @@ -8793,7 +8829,7 @@ function validate67(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err2); } errors++; - validate67.errors = vErrors; + validate68.errors = vErrors; return false; } else { errors = _errs3; @@ -8814,17 +8850,17 @@ function validate67(data, { instancePath = "", parentData, parentDataProperty, r let data2 = data.max; const _errs9 = errors; if (!(typeof data2 == "number" && isFinite(data2)) && data2 !== null) { - validate67.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/type", keyword: "type", params: { type: schema77.properties.max.type }, message: "must be number,null" }]; + validate68.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/type", keyword: "type", params: { type: schema78.properties.max.type }, message: "must be number,null" }]; return false; } if (errors === _errs9) { if (typeof data2 == "number" && isFinite(data2)) { if (data2 > 17976931348623157e292 || isNaN(data2)) { - validate67.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate68.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data2 < -17976931348623157e292 || isNaN(data2)) { - validate67.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate68.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -8839,17 +8875,17 @@ function validate67(data, { instancePath = "", parentData, parentDataProperty, r let data3 = data.min; const _errs11 = errors; if (!(typeof data3 == "number" && isFinite(data3)) && data3 !== null) { - validate67.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/type", keyword: "type", params: { type: schema77.properties.min.type }, message: "must be number,null" }]; + validate68.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/type", keyword: "type", params: { type: schema78.properties.min.type }, message: "must be number,null" }]; return false; } if (errors === _errs11) { if (typeof data3 == "number" && isFinite(data3)) { if (data3 > 17976931348623157e292 || isNaN(data3)) { - validate67.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate68.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data3 < -17976931348623157e292 || isNaN(data3)) { - validate67.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate68.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -8864,27 +8900,27 @@ function validate67(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate67.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate68.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate67.errors = vErrors; + validate68.errors = vErrors; return errors === 0; } -function validate66(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate67(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.key === void 0 && (missing0 = "key") || data.label === void 0 && (missing0 = "label") || data.param_type === void 0 && (missing0 = "param_type") || data.default === void 0 && (missing0 = "default") || data.description === void 0 && (missing0 = "description") || data.constraints === void 0 && (missing0 = "constraints")) { - validate66.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate67.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "constraints" || key0 === "default" || key0 === "description" || key0 === "key" || key0 === "label" || key0 === "param_type")) { - validate66.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate67.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -8896,8 +8932,8 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r const _errs3 = errors; let valid1 = false; const _errs4 = errors; - if (!validate67(data0, { instancePath: instancePath + "/constraints", parentData: data, parentDataProperty: "constraints", rootData })) { - vErrors = vErrors === null ? validate67.errors : vErrors.concat(validate67.errors); + if (!validate68(data0, { instancePath: instancePath + "/constraints", parentData: data, parentDataProperty: "constraints", rootData })) { + vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors); errors = vErrors.length; } var _valid0 = _errs4 === errors; @@ -8924,7 +8960,7 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err1); } errors++; - validate66.errors = vErrors; + validate67.errors = vErrors; return false; } else { errors = _errs3; @@ -8943,8 +8979,8 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r if (valid0) { if (data.default !== void 0) { const _errs7 = errors; - if (!validate68(data.default, { instancePath: instancePath + "/default", parentData: data, parentDataProperty: "default", rootData })) { - vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors); + if (!validate69(data.default, { instancePath: instancePath + "/default", parentData: data, parentDataProperty: "default", rootData })) { + vErrors = vErrors === null ? validate69.errors : vErrors.concat(validate69.errors); errors = vErrors.length; } var valid0 = _errs7 === errors; @@ -8956,7 +8992,7 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r let data2 = data.description; const _errs8 = errors; if (typeof data2 !== "string" && data2 !== null) { - validate66.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: schema76.properties.description.type }, message: "must be string,null" }]; + validate67.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: schema77.properties.description.type }, message: "must be string,null" }]; return false; } var valid0 = _errs8 === errors; @@ -8967,7 +9003,7 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r if (data.key !== void 0) { const _errs10 = errors; if (typeof data.key !== "string") { - validate66.errors = [{ instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate67.errors = [{ instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs10 === errors; @@ -8978,7 +9014,7 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r if (data.label !== void 0) { const _errs12 = errors; if (typeof data.label !== "string") { - validate66.errors = [{ instancePath: instancePath + "/label", schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate67.errors = [{ instancePath: instancePath + "/label", schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs12 === errors; @@ -8990,11 +9026,11 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r let data5 = data.param_type; const _errs14 = errors; if (typeof data5 !== "string") { - validate66.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate67.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data5 === "Number" || data5 === "Integer" || data5 === "String" || data5 === "Boolean")) { - validate66.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/enum", keyword: "enum", params: { allowedValues: schema79.enum }, message: "must be equal to one of the allowed values" }]; + validate67.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/enum", keyword: "enum", params: { allowedValues: schema80.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs14 === errors; @@ -9009,27 +9045,27 @@ function validate66(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate66.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate67.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate66.errors = vErrors; + validate67.errors = vErrors; return errors === 0; } -function validate65(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate66(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.model_id === void 0 && (missing0 = "model_id") || data.inference_settings === void 0 && (missing0 = "inference_settings")) { - validate65.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate66.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "inference_settings" || key0 === "model_id" || key0 === "success")) { - validate65.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate66.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -9044,8 +9080,8 @@ function validate65(data, { instancePath = "", parentData, parentDataProperty, r const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; - if (!validate66(data0[i0], { instancePath: instancePath + "/inference_settings/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate66.errors : vErrors.concat(validate66.errors); + if (!validate67(data0[i0], { instancePath: instancePath + "/inference_settings/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate67.errors : vErrors.concat(validate67.errors); errors = vErrors.length; } var valid1 = _errs4 === errors; @@ -9054,7 +9090,7 @@ function validate65(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate65.errors = [{ instancePath: instancePath + "/inference_settings", schemaPath: "#/properties/inference_settings/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate66.errors = [{ instancePath: instancePath + "/inference_settings", schemaPath: "#/properties/inference_settings/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -9066,7 +9102,7 @@ function validate65(data, { instancePath = "", parentData, parentDataProperty, r if (data.model_id !== void 0) { const _errs5 = errors; if (typeof data.model_id !== "string") { - validate65.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate66.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs5 === errors; @@ -9078,11 +9114,11 @@ function validate65(data, { instancePath = "", parentData, parentDataProperty, r let data3 = data.success; const _errs7 = errors; if (typeof data3 !== "boolean") { - validate65.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate66.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data3) { - validate65.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate66.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs7 === errors; @@ -9094,31 +9130,31 @@ function validate65(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate65.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate66.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate65.errors = vErrors; + validate66.errors = vErrors; return errors === 0; } -var validateInstallationProgressOutcome = validate73; -var schema81 = { "additionalProperties": false, "properties": { "completedAt": { "type": ["string", "null"] }, "completedDependencies": { "maximum": 4294967295, "minimum": 0, "type": "integer" }, "completedItems": { "items": { "$ref": "#/definitions/RuntimeInstallationProgressItem" }, "type": "array" }, "currentItem": { "type": ["string", "null"] }, "dependencyCount": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "downloadSpeed": { "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "downloadedBytes": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "error": { "type": ["string", "null"] }, "etaSeconds": { "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "logPath": { "type": ["string", "null"] }, "overallProgress": { "maximum": 34028234663852886e22, "minimum": -34028234663852886e22, "type": ["number", "null"] }, "stage": { "$ref": "#/definitions/RuntimeInstallationStage" }, "stageProgress": { "maximum": 34028234663852886e22, "minimum": -34028234663852886e22, "type": ["number", "null"] }, "startedAt": { "type": "string" }, "success": { "type": ["boolean", "null"] }, "tag": { "type": "string" }, "totalSize": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["tag", "startedAt", "stage", "stageProgress", "overallProgress", "currentItem", "downloadSpeed", "etaSeconds", "totalSize", "downloadedBytes", "dependencyCount", "completedDependencies", "completedItems", "error", "completedAt", "success", "logPath"], "type": "object" }; -var schema82 = { "additionalProperties": false, "properties": { "completedAt": { "type": "string" }, "name": { "type": "string" }, "size": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] }, "type": { "type": "string" } }, "required": ["name", "type", "size", "completedAt"], "type": "object" }; -var schema83 = { "enum": ["download", "extract", "venv", "dependencies", "setup"], "type": "string" }; -function validate74(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstallationProgressOutcome = validate74; +var schema82 = { "additionalProperties": false, "properties": { "completedAt": { "type": ["string", "null"] }, "completedDependencies": { "maximum": 4294967295, "minimum": 0, "type": "integer" }, "completedItems": { "items": { "$ref": "#/definitions/RuntimeInstallationProgressItem" }, "type": "array" }, "currentItem": { "type": ["string", "null"] }, "dependencyCount": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "downloadSpeed": { "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "downloadedBytes": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "error": { "type": ["string", "null"] }, "etaSeconds": { "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "logPath": { "type": ["string", "null"] }, "overallProgress": { "maximum": 34028234663852886e22, "minimum": -34028234663852886e22, "type": ["number", "null"] }, "stage": { "$ref": "#/definitions/RuntimeInstallationStage" }, "stageProgress": { "maximum": 34028234663852886e22, "minimum": -34028234663852886e22, "type": ["number", "null"] }, "startedAt": { "type": "string" }, "success": { "type": ["boolean", "null"] }, "tag": { "type": "string" }, "totalSize": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] } }, "required": ["tag", "startedAt", "stage", "stageProgress", "overallProgress", "currentItem", "downloadSpeed", "etaSeconds", "totalSize", "downloadedBytes", "dependencyCount", "completedDependencies", "completedItems", "error", "completedAt", "success", "logPath"], "type": "object" }; +var schema83 = { "additionalProperties": false, "properties": { "completedAt": { "type": "string" }, "name": { "type": "string" }, "size": { "maximum": 9007199254740991, "minimum": 0, "type": ["integer", "null"] }, "type": { "type": "string" } }, "required": ["name", "type", "size", "completedAt"], "type": "object" }; +var schema84 = { "enum": ["download", "extract", "venv", "dependencies", "setup"], "type": "string" }; +function validate75(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.tag === void 0 && (missing0 = "tag") || data.startedAt === void 0 && (missing0 = "startedAt") || data.stage === void 0 && (missing0 = "stage") || data.stageProgress === void 0 && (missing0 = "stageProgress") || data.overallProgress === void 0 && (missing0 = "overallProgress") || data.currentItem === void 0 && (missing0 = "currentItem") || data.downloadSpeed === void 0 && (missing0 = "downloadSpeed") || data.etaSeconds === void 0 && (missing0 = "etaSeconds") || data.totalSize === void 0 && (missing0 = "totalSize") || data.downloadedBytes === void 0 && (missing0 = "downloadedBytes") || data.dependencyCount === void 0 && (missing0 = "dependencyCount") || data.completedDependencies === void 0 && (missing0 = "completedDependencies") || data.completedItems === void 0 && (missing0 = "completedItems") || data.error === void 0 && (missing0 = "error") || data.completedAt === void 0 && (missing0 = "completedAt") || data.success === void 0 && (missing0 = "success") || data.logPath === void 0 && (missing0 = "logPath")) { - validate74.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate75.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { - if (!func2.call(schema81.properties, key0)) { - validate74.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + if (!func2.call(schema82.properties, key0)) { + validate75.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -9128,7 +9164,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.completedAt; const _errs2 = errors; if (typeof data0 !== "string" && data0 !== null) { - validate74.errors = [{ instancePath: instancePath + "/completedAt", schemaPath: "#/properties/completedAt/type", keyword: "type", params: { type: schema81.properties.completedAt.type }, message: "must be string,null" }]; + validate75.errors = [{ instancePath: instancePath + "/completedAt", schemaPath: "#/properties/completedAt/type", keyword: "type", params: { type: schema82.properties.completedAt.type }, message: "must be string,null" }]; return false; } var valid0 = _errs2 === errors; @@ -9140,17 +9176,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.completedDependencies; const _errs4 = errors; if (!(typeof data1 == "number" && (!(data1 % 1) && !isNaN(data1)) && isFinite(data1))) { - validate74.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate75.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs4) { if (typeof data1 == "number" && isFinite(data1)) { if (data1 > 4294967295 || isNaN(data1)) { - validate74.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate75.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data1 < 0 || isNaN(data1)) { - validate74.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate75.errors = [{ instancePath: instancePath + "/completedDependencies", schemaPath: "#/properties/completedDependencies/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -9176,13 +9212,13 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data3 && typeof data3 == "object" && !Array.isArray(data3)) { let missing1; if (data3.name === void 0 && (missing1 = "name") || data3.type === void 0 && (missing1 = "type") || data3.size === void 0 && (missing1 = "size") || data3.completedAt === void 0 && (missing1 = "completedAt")) { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs11 = errors; for (const key1 in data3) { if (!(key1 === "completedAt" || key1 === "name" || key1 === "size" || key1 === "type")) { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -9191,7 +9227,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data3.completedAt !== void 0) { const _errs12 = errors; if (typeof data3.completedAt !== "string") { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/completedAt", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/completedAt/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/completedAt", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/completedAt/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs12 === errors; @@ -9202,7 +9238,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data3.name !== void 0) { const _errs14 = errors; if (typeof data3.name !== "string") { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/name", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/name", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs14 === errors; @@ -9214,17 +9250,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data6 = data3.size; const _errs16 = errors; if (!(typeof data6 == "number" && (!(data6 % 1) && !isNaN(data6)) && isFinite(data6)) && data6 !== null) { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/type", keyword: "type", params: { type: schema82.properties.size.type }, message: "must be integer,null" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/type", keyword: "type", params: { type: schema83.properties.size.type }, message: "must be integer,null" }]; return false; } if (errors === _errs16) { if (typeof data6 == "number" && isFinite(data6)) { if (data6 > 9007199254740991 || isNaN(data6)) { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data6 < 0 || isNaN(data6)) { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/size", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/size/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -9238,7 +9274,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data3.type !== void 0) { const _errs18 = errors; if (typeof data3.type !== "string") { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/type", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/type/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0 + "/type", schemaPath: "#/definitions/RuntimeInstallationProgressItem/properties/type/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs18 === errors; @@ -9251,7 +9287,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate74.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems/" + i0, schemaPath: "#/definitions/RuntimeInstallationProgressItem/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -9261,7 +9297,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate74.errors = [{ instancePath: instancePath + "/completedItems", schemaPath: "#/properties/completedItems/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate75.errors = [{ instancePath: instancePath + "/completedItems", schemaPath: "#/properties/completedItems/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -9274,7 +9310,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data8 = data.currentItem; const _errs20 = errors; if (typeof data8 !== "string" && data8 !== null) { - validate74.errors = [{ instancePath: instancePath + "/currentItem", schemaPath: "#/properties/currentItem/type", keyword: "type", params: { type: schema81.properties.currentItem.type }, message: "must be string,null" }]; + validate75.errors = [{ instancePath: instancePath + "/currentItem", schemaPath: "#/properties/currentItem/type", keyword: "type", params: { type: schema82.properties.currentItem.type }, message: "must be string,null" }]; return false; } var valid0 = _errs20 === errors; @@ -9286,17 +9322,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data9 = data.dependencyCount; const _errs22 = errors; if (!(typeof data9 == "number" && (!(data9 % 1) && !isNaN(data9)) && isFinite(data9)) && data9 !== null) { - validate74.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/type", keyword: "type", params: { type: schema81.properties.dependencyCount.type }, message: "must be integer,null" }]; + validate75.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/type", keyword: "type", params: { type: schema82.properties.dependencyCount.type }, message: "must be integer,null" }]; return false; } if (errors === _errs22) { if (typeof data9 == "number" && isFinite(data9)) { if (data9 > 4294967295 || isNaN(data9)) { - validate74.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate75.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data9 < 0 || isNaN(data9)) { - validate74.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate75.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -9311,17 +9347,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data10 = data.downloadSpeed; const _errs24 = errors; if (!(typeof data10 == "number" && isFinite(data10)) && data10 !== null) { - validate74.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/type", keyword: "type", params: { type: schema81.properties.downloadSpeed.type }, message: "must be number,null" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/type", keyword: "type", params: { type: schema82.properties.downloadSpeed.type }, message: "must be number,null" }]; return false; } if (errors === _errs24) { if (typeof data10 == "number" && isFinite(data10)) { if (data10 > 17976931348623157e292 || isNaN(data10)) { - validate74.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data10 < -17976931348623157e292 || isNaN(data10)) { - validate74.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadSpeed", schemaPath: "#/properties/downloadSpeed/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -9336,17 +9372,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data11 = data.downloadedBytes; const _errs26 = errors; if (!(typeof data11 == "number" && (!(data11 % 1) && !isNaN(data11)) && isFinite(data11))) { - validate74.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs26) { if (typeof data11 == "number" && isFinite(data11)) { if (data11 > 9007199254740991 || isNaN(data11)) { - validate74.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data11 < 0 || isNaN(data11)) { - validate74.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate75.errors = [{ instancePath: instancePath + "/downloadedBytes", schemaPath: "#/properties/downloadedBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -9361,7 +9397,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data12 = data.error; const _errs28 = errors; if (typeof data12 !== "string" && data12 !== null) { - validate74.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema81.properties.error.type }, message: "must be string,null" }]; + validate75.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema82.properties.error.type }, message: "must be string,null" }]; return false; } var valid0 = _errs28 === errors; @@ -9373,17 +9409,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data13 = data.etaSeconds; const _errs30 = errors; if (!(typeof data13 == "number" && isFinite(data13)) && data13 !== null) { - validate74.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/type", keyword: "type", params: { type: schema81.properties.etaSeconds.type }, message: "must be number,null" }]; + validate75.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/type", keyword: "type", params: { type: schema82.properties.etaSeconds.type }, message: "must be number,null" }]; return false; } if (errors === _errs30) { if (typeof data13 == "number" && isFinite(data13)) { if (data13 > 17976931348623157e292 || isNaN(data13)) { - validate74.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate75.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data13 < -17976931348623157e292 || isNaN(data13)) { - validate74.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate75.errors = [{ instancePath: instancePath + "/etaSeconds", schemaPath: "#/properties/etaSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -9398,7 +9434,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data14 = data.logPath; const _errs32 = errors; if (typeof data14 !== "string" && data14 !== null) { - validate74.errors = [{ instancePath: instancePath + "/logPath", schemaPath: "#/properties/logPath/type", keyword: "type", params: { type: schema81.properties.logPath.type }, message: "must be string,null" }]; + validate75.errors = [{ instancePath: instancePath + "/logPath", schemaPath: "#/properties/logPath/type", keyword: "type", params: { type: schema82.properties.logPath.type }, message: "must be string,null" }]; return false; } var valid0 = _errs32 === errors; @@ -9410,17 +9446,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data15 = data.overallProgress; const _errs34 = errors; if (!(typeof data15 == "number" && isFinite(data15)) && data15 !== null) { - validate74.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/type", keyword: "type", params: { type: schema81.properties.overallProgress.type }, message: "must be number,null" }]; + validate75.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/type", keyword: "type", params: { type: schema82.properties.overallProgress.type }, message: "must be number,null" }]; return false; } if (errors === _errs34) { if (typeof data15 == "number" && isFinite(data15)) { if (data15 > 34028234663852886e22 || isNaN(data15)) { - validate74.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; + validate75.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; return false; } else { if (data15 < -34028234663852886e22 || isNaN(data15)) { - validate74.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/minimum", keyword: "minimum", params: { comparison: ">=", limit: -34028234663852886e22 }, message: "must be >= -3.4028234663852886e+38" }]; + validate75.errors = [{ instancePath: instancePath + "/overallProgress", schemaPath: "#/properties/overallProgress/minimum", keyword: "minimum", params: { comparison: ">=", limit: -34028234663852886e22 }, message: "must be >= -3.4028234663852886e+38" }]; return false; } } @@ -9435,11 +9471,11 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data16 = data.stage; const _errs36 = errors; if (typeof data16 !== "string") { - validate74.errors = [{ instancePath: instancePath + "/stage", schemaPath: "#/definitions/RuntimeInstallationStage/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/stage", schemaPath: "#/definitions/RuntimeInstallationStage/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data16 === "download" || data16 === "extract" || data16 === "venv" || data16 === "dependencies" || data16 === "setup")) { - validate74.errors = [{ instancePath: instancePath + "/stage", schemaPath: "#/definitions/RuntimeInstallationStage/enum", keyword: "enum", params: { allowedValues: schema83.enum }, message: "must be equal to one of the allowed values" }]; + validate75.errors = [{ instancePath: instancePath + "/stage", schemaPath: "#/definitions/RuntimeInstallationStage/enum", keyword: "enum", params: { allowedValues: schema84.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs36 === errors; @@ -9451,17 +9487,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data17 = data.stageProgress; const _errs39 = errors; if (!(typeof data17 == "number" && isFinite(data17)) && data17 !== null) { - validate74.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/type", keyword: "type", params: { type: schema81.properties.stageProgress.type }, message: "must be number,null" }]; + validate75.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/type", keyword: "type", params: { type: schema82.properties.stageProgress.type }, message: "must be number,null" }]; return false; } if (errors === _errs39) { if (typeof data17 == "number" && isFinite(data17)) { if (data17 > 34028234663852886e22 || isNaN(data17)) { - validate74.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; + validate75.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; return false; } else { if (data17 < -34028234663852886e22 || isNaN(data17)) { - validate74.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/minimum", keyword: "minimum", params: { comparison: ">=", limit: -34028234663852886e22 }, message: "must be >= -3.4028234663852886e+38" }]; + validate75.errors = [{ instancePath: instancePath + "/stageProgress", schemaPath: "#/properties/stageProgress/minimum", keyword: "minimum", params: { comparison: ">=", limit: -34028234663852886e22 }, message: "must be >= -3.4028234663852886e+38" }]; return false; } } @@ -9475,7 +9511,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data.startedAt !== void 0) { const _errs41 = errors; if (typeof data.startedAt !== "string") { - validate74.errors = [{ instancePath: instancePath + "/startedAt", schemaPath: "#/properties/startedAt/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/startedAt", schemaPath: "#/properties/startedAt/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs41 === errors; @@ -9487,7 +9523,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data19 = data.success; const _errs43 = errors; if (typeof data19 !== "boolean" && data19 !== null) { - validate74.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: schema81.properties.success.type }, message: "must be boolean,null" }]; + validate75.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: schema82.properties.success.type }, message: "must be boolean,null" }]; return false; } var valid0 = _errs43 === errors; @@ -9498,7 +9534,7 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r if (data.tag !== void 0) { const _errs45 = errors; if (typeof data.tag !== "string") { - validate74.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate75.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs45 === errors; @@ -9510,17 +9546,17 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r let data21 = data.totalSize; const _errs47 = errors; if (!(typeof data21 == "number" && (!(data21 % 1) && !isNaN(data21)) && isFinite(data21)) && data21 !== null) { - validate74.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/type", keyword: "type", params: { type: schema81.properties.totalSize.type }, message: "must be integer,null" }]; + validate75.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/type", keyword: "type", params: { type: schema82.properties.totalSize.type }, message: "must be integer,null" }]; return false; } if (errors === _errs47) { if (typeof data21 == "number" && isFinite(data21)) { if (data21 > 9007199254740991 || isNaN(data21)) { - validate74.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate75.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data21 < 0 || isNaN(data21)) { - validate74.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate75.errors = [{ instancePath: instancePath + "/totalSize", schemaPath: "#/properties/totalSize/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -9549,21 +9585,21 @@ function validate74(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate74.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate75.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate74.errors = vErrors; + validate75.errors = vErrors; return errors === 0; } -function validate73(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate74(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; let valid0 = false; const _errs1 = errors; - if (!validate74(data, { instancePath, parentData, parentDataProperty, rootData })) { - vErrors = vErrors === null ? validate74.errors : vErrors.concat(validate74.errors); + if (!validate75(data, { instancePath, parentData, parentDataProperty, rootData })) { + vErrors = vErrors === null ? validate75.errors : vErrors.concat(validate75.errors); errors = vErrors.length; } var _valid0 = _errs1 === errors; @@ -9590,7 +9626,7 @@ function validate73(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err1); } errors++; - validate73.errors = vErrors; + validate74.errors = vErrors; return false; } else { errors = _errs0; @@ -9602,24 +9638,24 @@ function validate73(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate73.errors = vErrors; + validate74.errors = vErrors; return errors === 0; } -var validateInstalledVersionsOutcome = validate76; -function validate76(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstalledVersionsOutcome = validate77; +function validate77(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.versions === void 0 && (missing0 = "versions")) { - validate76.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate77.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success" || key0 === "versions")) { - validate76.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate77.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -9629,11 +9665,11 @@ function validate76(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.success; const _errs2 = errors; if (typeof data0 !== "boolean") { - validate76.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate77.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data0) { - validate76.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate77.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs2 === errors; @@ -9651,7 +9687,7 @@ function validate76(data, { instancePath = "", parentData, parentDataProperty, r for (let i0 = 0; i0 < len0; i0++) { const _errs6 = errors; if (typeof data1[i0] !== "string") { - validate76.errors = [{ instancePath: instancePath + "/versions/" + i0, schemaPath: "#/properties/versions/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate77.errors = [{ instancePath: instancePath + "/versions/" + i0, schemaPath: "#/properties/versions/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid1 = _errs6 === errors; @@ -9660,7 +9696,7 @@ function validate76(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate76.errors = [{ instancePath: instancePath + "/versions", schemaPath: "#/properties/versions/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate77.errors = [{ instancePath: instancePath + "/versions", schemaPath: "#/properties/versions/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -9672,28 +9708,28 @@ function validate76(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate76.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate77.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate76.errors = vErrors; + validate77.errors = vErrors; return errors === 0; } -var validateInstallVersionDependenciesOutcome = validate77; -function validate77(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstallVersionDependenciesOutcome = validate78; +function validate78(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate77.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate78.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success")) { - validate77.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate78.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -9701,22 +9737,22 @@ function validate77(data, { instancePath = "", parentData, parentDataProperty, r if (_errs1 === errors) { if (data.success !== void 0) { if (typeof data.success !== "boolean") { - validate77.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate78.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } } } } } else { - validate77.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate78.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate77.errors = vErrors; + validate78.errors = vErrors; return errors === 0; } -var validateInstallVersionDependenciesParams = validate78; -function validate78(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstallVersionDependenciesParams = validate79; +function validate79(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -9877,7 +9913,7 @@ function validate78(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err10); } errors++; - validate78.errors = vErrors; + validate79.errors = vErrors; return false; } else { errors = _errs0; @@ -9889,11 +9925,11 @@ function validate78(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate78.errors = vErrors; + validate79.errors = vErrors; return errors === 0; } -var validateInstallVersionOutcome = validate79; -function validate79(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstallVersionOutcome = validate80; +function validate80(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -10076,7 +10112,7 @@ function validate79(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err12); } errors++; - validate79.errors = vErrors; + validate80.errors = vErrors; return false; } else { errors = _errs0; @@ -10088,12 +10124,12 @@ function validate79(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate79.errors = vErrors; + validate80.errors = vErrors; return errors === 0; } -var validateInstallVersionParams = validate80; -var schema90 = { "anyOf": [{ "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "preview_id": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["app_id", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "previewId": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["app_id", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "preview_id": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["appId", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "previewId": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["appId", "tag"], "type": "object" }] }; -function validate80(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateInstallVersionParams = validate81; +var schema91 = { "anyOf": [{ "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "preview_id": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["app_id", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "previewId": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["app_id", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "preview_id": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["appId", "tag"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "previewId": { "default": null, "type": ["string", "null"] }, "tag": { "type": "string" } }, "required": ["appId", "tag"], "type": "object" }] }; +function validate81(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -10145,7 +10181,7 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.preview_id; const _errs6 = errors; if (typeof data1 !== "string" && data1 !== null) { - const err3 = { instancePath: instancePath + "/preview_id", schemaPath: "#/anyOf/0/properties/preview_id/type", keyword: "type", params: { type: schema90.anyOf[0].properties.preview_id.type }, message: "must be string,null" }; + const err3 = { instancePath: instancePath + "/preview_id", schemaPath: "#/anyOf/0/properties/preview_id/type", keyword: "type", params: { type: schema91.anyOf[0].properties.preview_id.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err3]; } else { @@ -10237,7 +10273,7 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r let data4 = data.previewId; const _errs15 = errors; if (typeof data4 !== "string" && data4 !== null) { - const err9 = { instancePath: instancePath + "/previewId", schemaPath: "#/anyOf/1/properties/previewId/type", keyword: "type", params: { type: schema90.anyOf[1].properties.previewId.type }, message: "must be string,null" }; + const err9 = { instancePath: instancePath + "/previewId", schemaPath: "#/anyOf/1/properties/previewId/type", keyword: "type", params: { type: schema91.anyOf[1].properties.previewId.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err9]; } else { @@ -10329,7 +10365,7 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r let data7 = data.preview_id; const _errs24 = errors; if (typeof data7 !== "string" && data7 !== null) { - const err15 = { instancePath: instancePath + "/preview_id", schemaPath: "#/anyOf/2/properties/preview_id/type", keyword: "type", params: { type: schema90.anyOf[2].properties.preview_id.type }, message: "must be string,null" }; + const err15 = { instancePath: instancePath + "/preview_id", schemaPath: "#/anyOf/2/properties/preview_id/type", keyword: "type", params: { type: schema91.anyOf[2].properties.preview_id.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err15]; } else { @@ -10421,7 +10457,7 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r let data10 = data.previewId; const _errs33 = errors; if (typeof data10 !== "string" && data10 !== null) { - const err21 = { instancePath: instancePath + "/previewId", schemaPath: "#/anyOf/3/properties/previewId/type", keyword: "type", params: { type: schema90.anyOf[3].properties.previewId.type }, message: "must be string,null" }; + const err21 = { instancePath: instancePath + "/previewId", schemaPath: "#/anyOf/3/properties/previewId/type", keyword: "type", params: { type: schema91.anyOf[3].properties.previewId.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err21]; } else { @@ -10476,7 +10512,7 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err24); } errors++; - validate80.errors = vErrors; + validate81.errors = vErrors; return false; } else { errors = _errs0; @@ -10488,26 +10524,26 @@ function validate80(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate80.errors = vErrors; + validate81.errors = vErrors; return errors === 0; } -var validateLibraryModelMetadataOutcome = validate81; -var schema92 = { "additionalProperties": false, "description": "Derived component metadata for a directory-root bundle model.", "properties": { "class_name": { "default": null, "type": ["string", "null"] }, "name": { "type": "string" }, "relative_path": { "type": "string" }, "source_library": { "default": null, "type": ["string", "null"] }, "state": { "$ref": "#/definitions/BundleComponentState" } }, "required": ["name", "relative_path", "source_library", "class_name", "state"], "type": "object" }; -var schema93 = { "description": "Presence/state of a bundle component derived from bundle metadata.", "enum": ["present", "missing", "unreadable", "path_escape"], "type": "string" }; -function validate82(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateLibraryModelMetadataOutcome = validate82; +var schema93 = { "additionalProperties": false, "description": "Derived component metadata for a directory-root bundle model.", "properties": { "class_name": { "default": null, "type": ["string", "null"] }, "name": { "type": "string" }, "relative_path": { "type": "string" }, "source_library": { "default": null, "type": ["string", "null"] }, "state": { "$ref": "#/definitions/BundleComponentState" } }, "required": ["name", "relative_path", "source_library", "class_name", "state"], "type": "object" }; +var schema94 = { "description": "Presence/state of a bundle component derived from bundle metadata.", "enum": ["present", "missing", "unreadable", "path_escape"], "type": "string" }; +function validate83(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.name === void 0 && (missing0 = "name") || data.relative_path === void 0 && (missing0 = "relative_path") || data.source_library === void 0 && (missing0 = "source_library") || data.class_name === void 0 && (missing0 = "class_name") || data.state === void 0 && (missing0 = "state")) { - validate82.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate83.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "class_name" || key0 === "name" || key0 === "relative_path" || key0 === "source_library" || key0 === "state")) { - validate82.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate83.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -10517,7 +10553,7 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.class_name; const _errs2 = errors; if (typeof data0 !== "string" && data0 !== null) { - validate82.errors = [{ instancePath: instancePath + "/class_name", schemaPath: "#/properties/class_name/type", keyword: "type", params: { type: schema92.properties.class_name.type }, message: "must be string,null" }]; + validate83.errors = [{ instancePath: instancePath + "/class_name", schemaPath: "#/properties/class_name/type", keyword: "type", params: { type: schema93.properties.class_name.type }, message: "must be string,null" }]; return false; } var valid0 = _errs2 === errors; @@ -10528,7 +10564,7 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r if (data.name !== void 0) { const _errs4 = errors; if (typeof data.name !== "string") { - validate82.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate83.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs4 === errors; @@ -10539,7 +10575,7 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r if (data.relative_path !== void 0) { const _errs6 = errors; if (typeof data.relative_path !== "string") { - validate82.errors = [{ instancePath: instancePath + "/relative_path", schemaPath: "#/properties/relative_path/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate83.errors = [{ instancePath: instancePath + "/relative_path", schemaPath: "#/properties/relative_path/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs6 === errors; @@ -10551,7 +10587,7 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r let data3 = data.source_library; const _errs8 = errors; if (typeof data3 !== "string" && data3 !== null) { - validate82.errors = [{ instancePath: instancePath + "/source_library", schemaPath: "#/properties/source_library/type", keyword: "type", params: { type: schema92.properties.source_library.type }, message: "must be string,null" }]; + validate83.errors = [{ instancePath: instancePath + "/source_library", schemaPath: "#/properties/source_library/type", keyword: "type", params: { type: schema93.properties.source_library.type }, message: "must be string,null" }]; return false; } var valid0 = _errs8 === errors; @@ -10563,11 +10599,11 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r let data4 = data.state; const _errs10 = errors; if (typeof data4 !== "string") { - validate82.errors = [{ instancePath: instancePath + "/state", schemaPath: "#/definitions/BundleComponentState/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate83.errors = [{ instancePath: instancePath + "/state", schemaPath: "#/definitions/BundleComponentState/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data4 === "present" || data4 === "missing" || data4 === "unreadable" || data4 === "path_escape")) { - validate82.errors = [{ instancePath: instancePath + "/state", schemaPath: "#/definitions/BundleComponentState/enum", keyword: "enum", params: { allowedValues: schema93.enum }, message: "must be equal to one of the allowed values" }]; + validate83.errors = [{ instancePath: instancePath + "/state", schemaPath: "#/definitions/BundleComponentState/enum", keyword: "enum", params: { allowedValues: schema94.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs10 === errors; @@ -10581,15 +10617,15 @@ function validate82(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate82.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate83.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate82.errors = vErrors; + validate83.errors = vErrors; return errors === 0; } -var wrapper2 = { validate: validate84 }; -function validate84(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var wrapper2 = { validate: validate85 }; +function validate85(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -10736,7 +10772,7 @@ function validate84(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err8); } errors++; - validate84.errors = vErrors; + validate85.errors = vErrors; return false; } else { errors = _errs0; @@ -10748,23 +10784,23 @@ function validate84(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate84.errors = vErrors; + validate85.errors = vErrors; return errors === 0; } -function validate86(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate87(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.file_type === void 0 && (missing0 = "file_type") || data.metadata === void 0 && (missing0 = "metadata")) { - validate86.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate87.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "file_type" || key0 === "metadata")) { - validate86.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate87.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -10773,7 +10809,7 @@ function validate86(data, { instancePath = "", parentData, parentDataProperty, r if (data.file_type !== void 0) { const _errs2 = errors; if (typeof data.file_type !== "string") { - validate86.errors = [{ instancePath: instancePath + "/file_type", schemaPath: "#/properties/file_type/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate87.errors = [{ instancePath: instancePath + "/file_type", schemaPath: "#/properties/file_type/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -10788,8 +10824,8 @@ function validate86(data, { instancePath = "", parentData, parentDataProperty, r if (data1 && typeof data1 == "object" && !Array.isArray(data1)) { for (const key1 in data1) { const _errs7 = errors; - if (!validate84(data1[key1], { instancePath: instancePath + "/metadata/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data1, parentDataProperty: key1, rootData })) { - vErrors = vErrors === null ? validate84.errors : vErrors.concat(validate84.errors); + if (!validate85(data1[key1], { instancePath: instancePath + "/metadata/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data1, parentDataProperty: key1, rootData })) { + vErrors = vErrors === null ? validate85.errors : vErrors.concat(validate85.errors); errors = vErrors.length; } var valid1 = _errs7 === errors; @@ -10798,7 +10834,7 @@ function validate86(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate86.errors = [{ instancePath: instancePath + "/metadata", schemaPath: "#/properties/metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate87.errors = [{ instancePath: instancePath + "/metadata", schemaPath: "#/properties/metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -10810,27 +10846,27 @@ function validate86(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate86.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate87.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate86.errors = vErrors; + validate87.errors = vErrors; return errors === 0; } -function validate81(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate82(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.model_id === void 0 && (missing0 = "model_id")) { - validate81.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate82.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "component_manifest" || key0 === "effective_metadata" || key0 === "embedded_metadata" || key0 === "model_id" || key0 === "primary_file" || key0 === "stored_metadata" || key0 === "success")) { - validate81.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate82.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -10845,8 +10881,8 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; - if (!validate82(data0[i0], { instancePath: instancePath + "/component_manifest/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate82.errors : vErrors.concat(validate82.errors); + if (!validate83(data0[i0], { instancePath: instancePath + "/component_manifest/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate83.errors : vErrors.concat(validate83.errors); errors = vErrors.length; } var valid1 = _errs4 === errors; @@ -10855,7 +10891,7 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate81.errors = [{ instancePath: instancePath + "/component_manifest", schemaPath: "#/properties/component_manifest/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate82.errors = [{ instancePath: instancePath + "/component_manifest", schemaPath: "#/properties/component_manifest/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -10871,8 +10907,8 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r if (data2 && typeof data2 == "object" && !Array.isArray(data2)) { for (const key1 in data2) { const _errs8 = errors; - if (!validate84(data2[key1], { instancePath: instancePath + "/effective_metadata/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data2, parentDataProperty: key1, rootData })) { - vErrors = vErrors === null ? validate84.errors : vErrors.concat(validate84.errors); + if (!validate85(data2[key1], { instancePath: instancePath + "/effective_metadata/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data2, parentDataProperty: key1, rootData })) { + vErrors = vErrors === null ? validate85.errors : vErrors.concat(validate85.errors); errors = vErrors.length; } var valid2 = _errs8 === errors; @@ -10881,7 +10917,7 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate81.errors = [{ instancePath: instancePath + "/effective_metadata", schemaPath: "#/properties/effective_metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate82.errors = [{ instancePath: instancePath + "/effective_metadata", schemaPath: "#/properties/effective_metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -10892,8 +10928,8 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r if (valid0) { if (data.embedded_metadata !== void 0) { const _errs9 = errors; - if (!validate86(data.embedded_metadata, { instancePath: instancePath + "/embedded_metadata", parentData: data, parentDataProperty: "embedded_metadata", rootData })) { - vErrors = vErrors === null ? validate86.errors : vErrors.concat(validate86.errors); + if (!validate87(data.embedded_metadata, { instancePath: instancePath + "/embedded_metadata", parentData: data, parentDataProperty: "embedded_metadata", rootData })) { + vErrors = vErrors === null ? validate87.errors : vErrors.concat(validate87.errors); errors = vErrors.length; } var valid0 = _errs9 === errors; @@ -10904,7 +10940,7 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r if (data.model_id !== void 0) { const _errs10 = errors; if (typeof data.model_id !== "string") { - validate81.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate82.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs10 === errors; @@ -10915,7 +10951,7 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r if (data.primary_file !== void 0) { const _errs12 = errors; if (typeof data.primary_file !== "string") { - validate81.errors = [{ instancePath: instancePath + "/primary_file", schemaPath: "#/properties/primary_file/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate82.errors = [{ instancePath: instancePath + "/primary_file", schemaPath: "#/properties/primary_file/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs12 === errors; @@ -10930,8 +10966,8 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r if (data7 && typeof data7 == "object" && !Array.isArray(data7)) { for (const key2 in data7) { const _errs17 = errors; - if (!validate84(data7[key2], { instancePath: instancePath + "/stored_metadata/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data7, parentDataProperty: key2, rootData })) { - vErrors = vErrors === null ? validate84.errors : vErrors.concat(validate84.errors); + if (!validate85(data7[key2], { instancePath: instancePath + "/stored_metadata/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data7, parentDataProperty: key2, rootData })) { + vErrors = vErrors === null ? validate85.errors : vErrors.concat(validate85.errors); errors = vErrors.length; } var valid3 = _errs17 === errors; @@ -10940,7 +10976,7 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate81.errors = [{ instancePath: instancePath + "/stored_metadata", schemaPath: "#/properties/stored_metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate82.errors = [{ instancePath: instancePath + "/stored_metadata", schemaPath: "#/properties/stored_metadata/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -10953,11 +10989,11 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r let data9 = data.success; const _errs18 = errors; if (typeof data9 !== "boolean") { - validate81.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate82.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data9) { - validate81.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate82.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs18 === errors; @@ -10973,16 +11009,16 @@ function validate81(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate81.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate82.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate81.errors = vErrors; + validate82.errors = vErrors; return errors === 0; } -var validateLinkHealthOutcome = validate90; -var schema97 = { "additionalProperties": false, "description": "Link health response.\n\nNote: Not FFI-compatible due to `usize` fields. Use wrapper types in pumas-uniffi.", "properties": { "broken_links": { "items": { "type": "string" }, "type": "array" }, "error": { "type": "null" }, "errors": { "items": { "type": "string" }, "type": "array" }, "healthy_links": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "orphaned_links": { "items": { "type": "string" }, "type": "array" }, "status": { "enum": ["healthy", "degraded"], "type": "string" }, "success": { "const": true, "type": "boolean" }, "total_links": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "warnings": { "items": { "type": "string" }, "type": "array" } }, "pumasLinkHealth": true, "required": ["success", "status", "total_links", "healthy_links", "broken_links", "orphaned_links", "warnings", "errors"], "type": "object" }; -function validate90(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateLinkHealthOutcome = validate91; +var schema98 = { "additionalProperties": false, "description": "Link health response.\n\nNote: Not FFI-compatible due to `usize` fields. Use wrapper types in pumas-uniffi.", "properties": { "broken_links": { "items": { "type": "string" }, "type": "array" }, "error": { "type": "null" }, "errors": { "items": { "type": "string" }, "type": "array" }, "healthy_links": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "orphaned_links": { "items": { "type": "string" }, "type": "array" }, "status": { "enum": ["healthy", "degraded"], "type": "string" }, "success": { "const": true, "type": "boolean" }, "total_links": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "warnings": { "items": { "type": "string" }, "type": "array" } }, "pumasLinkHealth": true, "required": ["success", "status", "total_links", "healthy_links", "broken_links", "orphaned_links", "warnings", "errors"], "type": "object" }; +function validate91(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs1 = errors; @@ -10990,13 +11026,13 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.status === void 0 && (missing0 = "status") || data.total_links === void 0 && (missing0 = "total_links") || data.healthy_links === void 0 && (missing0 = "healthy_links") || data.broken_links === void 0 && (missing0 = "broken_links") || data.orphaned_links === void 0 && (missing0 = "orphaned_links") || data.warnings === void 0 && (missing0 = "warnings") || data.errors === void 0 && (missing0 = "errors")) { - validate90.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate91.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs3 = errors; for (const key0 in data) { - if (!func2.call(schema97.properties, key0)) { - validate90.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + if (!func2.call(schema98.properties, key0)) { + validate91.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -11012,7 +11048,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r for (let i0 = 0; i0 < len0; i0++) { const _errs6 = errors; if (typeof data0[i0] !== "string") { - validate90.errors = [{ instancePath: instancePath + "/broken_links/" + i0, schemaPath: "#/definitions/LinkHealthResponse/properties/broken_links/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate91.errors = [{ instancePath: instancePath + "/broken_links/" + i0, schemaPath: "#/definitions/LinkHealthResponse/properties/broken_links/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs6 === errors; @@ -11021,7 +11057,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate90.errors = [{ instancePath: instancePath + "/broken_links", schemaPath: "#/definitions/LinkHealthResponse/properties/broken_links/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate91.errors = [{ instancePath: instancePath + "/broken_links", schemaPath: "#/definitions/LinkHealthResponse/properties/broken_links/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -11033,7 +11069,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r if (data.error !== void 0) { const _errs8 = errors; if (data.error !== null) { - validate90.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/definitions/LinkHealthResponse/properties/error/type", keyword: "type", params: { type: "null" }, message: "must be null" }]; + validate91.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/definitions/LinkHealthResponse/properties/error/type", keyword: "type", params: { type: "null" }, message: "must be null" }]; return false; } var valid2 = _errs8 === errors; @@ -11051,7 +11087,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r for (let i1 = 0; i1 < len1; i1++) { const _errs12 = errors; if (typeof data3[i1] !== "string") { - validate90.errors = [{ instancePath: instancePath + "/errors/" + i1, schemaPath: "#/definitions/LinkHealthResponse/properties/errors/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate91.errors = [{ instancePath: instancePath + "/errors/" + i1, schemaPath: "#/definitions/LinkHealthResponse/properties/errors/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid4 = _errs12 === errors; @@ -11060,7 +11096,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate90.errors = [{ instancePath: instancePath + "/errors", schemaPath: "#/definitions/LinkHealthResponse/properties/errors/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate91.errors = [{ instancePath: instancePath + "/errors", schemaPath: "#/definitions/LinkHealthResponse/properties/errors/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -11073,17 +11109,17 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r let data5 = data.healthy_links; const _errs14 = errors; if (!(typeof data5 == "number" && (!(data5 % 1) && !isNaN(data5)) && isFinite(data5))) { - validate90.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate91.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs14) { if (typeof data5 == "number" && isFinite(data5)) { if (data5 > 9007199254740991 || isNaN(data5)) { - validate90.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate91.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data5 < 0 || isNaN(data5)) { - validate90.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate91.errors = [{ instancePath: instancePath + "/healthy_links", schemaPath: "#/definitions/LinkHealthResponse/properties/healthy_links/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -11104,7 +11140,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r for (let i2 = 0; i2 < len2; i2++) { const _errs18 = errors; if (typeof data6[i2] !== "string") { - validate90.errors = [{ instancePath: instancePath + "/orphaned_links/" + i2, schemaPath: "#/definitions/LinkHealthResponse/properties/orphaned_links/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate91.errors = [{ instancePath: instancePath + "/orphaned_links/" + i2, schemaPath: "#/definitions/LinkHealthResponse/properties/orphaned_links/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid5 = _errs18 === errors; @@ -11113,7 +11149,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate90.errors = [{ instancePath: instancePath + "/orphaned_links", schemaPath: "#/definitions/LinkHealthResponse/properties/orphaned_links/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate91.errors = [{ instancePath: instancePath + "/orphaned_links", schemaPath: "#/definitions/LinkHealthResponse/properties/orphaned_links/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -11126,11 +11162,11 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r let data8 = data.status; const _errs20 = errors; if (typeof data8 !== "string") { - validate90.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/LinkHealthResponse/properties/status/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate91.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/LinkHealthResponse/properties/status/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data8 === "healthy" || data8 === "degraded")) { - validate90.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/LinkHealthResponse/properties/status/enum", keyword: "enum", params: { allowedValues: schema97.properties.status.enum }, message: "must be equal to one of the allowed values" }]; + validate91.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/LinkHealthResponse/properties/status/enum", keyword: "enum", params: { allowedValues: schema98.properties.status.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid2 = _errs20 === errors; @@ -11142,11 +11178,11 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r let data9 = data.success; const _errs22 = errors; if (typeof data9 !== "boolean") { - validate90.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/definitions/LinkHealthResponse/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate91.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/definitions/LinkHealthResponse/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data9) { - validate90.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/definitions/LinkHealthResponse/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate91.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/definitions/LinkHealthResponse/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid2 = _errs22 === errors; @@ -11158,17 +11194,17 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r let data10 = data.total_links; const _errs24 = errors; if (!(typeof data10 == "number" && (!(data10 % 1) && !isNaN(data10)) && isFinite(data10))) { - validate90.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate91.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs24) { if (typeof data10 == "number" && isFinite(data10)) { if (data10 > 9007199254740991 || isNaN(data10)) { - validate90.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate91.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data10 < 0 || isNaN(data10)) { - validate90.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate91.errors = [{ instancePath: instancePath + "/total_links", schemaPath: "#/definitions/LinkHealthResponse/properties/total_links/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -11189,7 +11225,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r for (let i3 = 0; i3 < len3; i3++) { const _errs28 = errors; if (typeof data11[i3] !== "string") { - validate90.errors = [{ instancePath: instancePath + "/warnings/" + i3, schemaPath: "#/definitions/LinkHealthResponse/properties/warnings/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate91.errors = [{ instancePath: instancePath + "/warnings/" + i3, schemaPath: "#/definitions/LinkHealthResponse/properties/warnings/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid6 = _errs28 === errors; @@ -11198,7 +11234,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate90.errors = [{ instancePath: instancePath + "/warnings", schemaPath: "#/definitions/LinkHealthResponse/properties/warnings/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate91.errors = [{ instancePath: instancePath + "/warnings", schemaPath: "#/definitions/LinkHealthResponse/properties/warnings/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -11208,7 +11244,7 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } if (valid2) { if (!Array.isArray(data.broken_links) || data.healthy_links + data.broken_links.length !== data.total_links || data.status === "healthy" !== (data.broken_links.length === 0)) { - validate90.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/pumasLinkHealth", keyword: "pumasLinkHealth", params: {}, message: 'must pass "pumasLinkHealth" keyword validation' }]; + validate91.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/pumasLinkHealth", keyword: "pumasLinkHealth", params: {}, message: 'must pass "pumasLinkHealth" keyword validation' }]; return false; } } @@ -11223,36 +11259,36 @@ function validate90(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate90.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate91.errors = [{ instancePath, schemaPath: "#/definitions/LinkHealthResponse/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { if (!Array.isArray(data.broken_links) || data.healthy_links + data.broken_links.length !== data.total_links || data.status === "healthy" !== (data.broken_links.length === 0)) { - validate90.errors = [{ instancePath, schemaPath: "#/pumasLinkHealth", keyword: "pumasLinkHealth", params: {}, message: 'must pass "pumasLinkHealth" keyword validation' }]; + validate91.errors = [{ instancePath, schemaPath: "#/pumasLinkHealth", keyword: "pumasLinkHealth", params: {}, message: 'must pass "pumasLinkHealth" keyword validation' }]; return false; } } } - validate90.errors = vErrors; + validate91.errors = vErrors; return errors === 0; } -var validateModelIndexRefreshOutcome = validate91; -function validate91(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateModelIndexRefreshOutcome = validate92; +function validate92(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.indexed_count === void 0 && (missing0 = "indexed_count")) { - validate91.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate92.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "indexed_count" || key0 === "success")) { - validate91.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate92.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -11262,17 +11298,17 @@ function validate91(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.indexed_count; const _errs2 = errors; if (!(typeof data0 == "number" && (!(data0 % 1) && !isNaN(data0)) && isFinite(data0))) { - validate91.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate92.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs2) { if (typeof data0 == "number" && isFinite(data0)) { if (data0 > 4294967295 || isNaN(data0)) { - validate91.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate92.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data0 < 0 || isNaN(data0)) { - validate91.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate92.errors = [{ instancePath: instancePath + "/indexed_count", schemaPath: "#/properties/indexed_count/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -11287,11 +11323,11 @@ function validate91(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.success; const _errs4 = errors; if (typeof data1 !== "boolean") { - validate91.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate92.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data1) { - validate91.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate92.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs4 === errors; @@ -11302,20 +11338,20 @@ function validate91(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate91.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate92.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate91.errors = vErrors; + validate92.errors = vErrors; return errors === 0; } -var validateModelsOutcome = validate92; -var schema100 = { "additionalProperties": false, "properties": { "artifact": { "$ref": "#/definitions/CatalogArtifactState" }, "dependencyCount": { "maximum": 512, "minimum": 0, "type": "integer" }, "displayDate": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "displayName": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "format": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "id": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "integrity": { "$ref": "#/definitions/CatalogIntegrityState" }, "modelDir": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "modelType": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "quantization": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "relatedAvailable": { "type": "boolean" }, "sizeBytes": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" } }, "pumasCatalogRow": true, "required": ["id", "modelDir", "displayName", "modelType", "dependencyCount", "relatedAvailable", "artifact", "integrity"], "type": "object" }; -var schema102 = { "enum": ["part_file_present", "expected_files_missing"], "type": "string" }; +var validateModelsOutcome = validate93; +var schema101 = { "additionalProperties": false, "properties": { "artifact": { "$ref": "#/definitions/CatalogArtifactState" }, "dependencyCount": { "maximum": 512, "minimum": 0, "type": "integer" }, "displayDate": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "displayName": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "format": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "id": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "integrity": { "$ref": "#/definitions/CatalogIntegrityState" }, "modelDir": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "modelType": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "quantization": { "minLength": 1, "pumasCanonicalText": true, "pumasUtf8Max": 4096, "type": "string" }, "relatedAvailable": { "type": "boolean" }, "sizeBytes": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" } }, "pumasCatalogRow": true, "required": ["id", "modelDir", "displayName", "modelType", "dependencyCount", "relatedAvailable", "artifact", "integrity"], "type": "object" }; +var schema103 = { "enum": ["part_file_present", "expected_files_missing"], "type": "string" }; var pattern17 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); var pattern18 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); var pattern19 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); -function validate94(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate95(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -11500,7 +11536,7 @@ function validate94(data, { instancePath = "", parentData, parentDataProperty, r errors++; } if (!(data3 === "part_file_present" || data3 === "expected_files_missing")) { - const err14 = { instancePath: instancePath + "/reasons/" + i0, schemaPath: "#/definitions/CatalogPartialReason/enum", keyword: "enum", params: { allowedValues: schema102.enum }, message: "must be equal to one of the allowed values" }; + const err14 = { instancePath: instancePath + "/reasons/" + i0, schemaPath: "#/definitions/CatalogPartialReason/enum", keyword: "enum", params: { allowedValues: schema103.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err14]; } else { @@ -11933,7 +11969,7 @@ function validate94(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err43); } errors++; - validate94.errors = vErrors; + validate95.errors = vErrors; return false; } else { errors = _errs0; @@ -11945,7 +11981,7 @@ function validate94(data, { instancePath = "", parentData, parentDataProperty, r } } } - validate94.errors = vErrors; + validate95.errors = vErrors; return errors === 0; } var pattern20 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); @@ -11955,20 +11991,20 @@ var pattern23 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); var pattern24 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); var pattern25 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); var pattern26 = new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u"); -function validate93(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate94(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.id === void 0 && (missing0 = "id") || data.modelDir === void 0 && (missing0 = "modelDir") || data.displayName === void 0 && (missing0 = "displayName") || data.modelType === void 0 && (missing0 = "modelType") || data.dependencyCount === void 0 && (missing0 = "dependencyCount") || data.relatedAvailable === void 0 && (missing0 = "relatedAvailable") || data.artifact === void 0 && (missing0 = "artifact") || data.integrity === void 0 && (missing0 = "integrity")) { - validate93.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate94.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { - if (!func2.call(schema100.properties, key0)) { - validate93.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + if (!func2.call(schema101.properties, key0)) { + validate94.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -11976,8 +12012,8 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (_errs1 === errors) { if (data.artifact !== void 0) { const _errs2 = errors; - if (!validate94(data.artifact, { instancePath: instancePath + "/artifact", parentData: data, parentDataProperty: "artifact", rootData })) { - vErrors = vErrors === null ? validate94.errors : vErrors.concat(validate94.errors); + if (!validate95(data.artifact, { instancePath: instancePath + "/artifact", parentData: data, parentDataProperty: "artifact", rootData })) { + vErrors = vErrors === null ? validate95.errors : vErrors.concat(validate95.errors); errors = vErrors.length; } var valid0 = _errs2 === errors; @@ -11989,17 +12025,17 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.dependencyCount; const _errs3 = errors; if (!(typeof data1 == "number" && (!(data1 % 1) && !isNaN(data1)) && isFinite(data1))) { - validate93.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate94.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs3) { if (typeof data1 == "number" && isFinite(data1)) { if (data1 > 512 || isNaN(data1)) { - validate93.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 512 }, message: "must be <= 512" }]; + validate94.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 512 }, message: "must be <= 512" }]; return false; } else { if (data1 < 0 || isNaN(data1)) { - validate93.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate94.errors = [{ instancePath: instancePath + "/dependencyCount", schemaPath: "#/properties/dependencyCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -12016,21 +12052,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs5) { if (typeof data2 === "string") { if (func5(data2) < 1) { - validate93.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data2.length === 0 || pattern20.test(data2)) { - validate93.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data2).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/displayDate", schemaPath: "#/properties/displayDate/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12045,21 +12081,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs7) { if (typeof data3 === "string") { if (func5(data3) < 1) { - validate93.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data3.length === 0 || pattern21.test(data3)) { - validate93.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data3).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/displayName", schemaPath: "#/properties/displayName/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12074,21 +12110,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs9) { if (typeof data4 === "string") { if (func5(data4) < 1) { - validate93.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data4.length === 0 || pattern22.test(data4)) { - validate93.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data4).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/format", schemaPath: "#/properties/format/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12103,21 +12139,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs11) { if (typeof data5 === "string") { if (func5(data5) < 1) { - validate93.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data5.length === 0 || pattern23.test(data5)) { - validate93.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data5).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12359,7 +12395,7 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err15); } errors++; - validate93.errors = vErrors; + validate94.errors = vErrors; return false; } else { errors = _errs15; @@ -12382,21 +12418,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs32) { if (typeof data12 === "string") { if (func5(data12) < 1) { - validate93.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data12.length === 0 || pattern24.test(data12)) { - validate93.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data12).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/modelDir", schemaPath: "#/properties/modelDir/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12411,21 +12447,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs34) { if (typeof data13 === "string") { if (func5(data13) < 1) { - validate93.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data13.length === 0 || pattern25.test(data13)) { - validate93.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data13).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/modelType", schemaPath: "#/properties/modelType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12440,21 +12476,21 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (errors === _errs36) { if (typeof data14 === "string") { if (func5(data14) < 1) { - validate93.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; + validate94.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" }]; return false; } else { if (data14.length === 0 || pattern26.test(data14)) { - validate93.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/pumasCanonicalText", keyword: "pumasCanonicalText", params: {}, message: 'must pass "pumasCanonicalText" keyword validation' }]; return false; } else { if (encodeURIComponent(data14).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate93.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate94.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } } else { - validate93.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate94.errors = [{ instancePath: instancePath + "/quantization", schemaPath: "#/properties/quantization/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -12466,7 +12502,7 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r if (data.relatedAvailable !== void 0) { const _errs38 = errors; if (typeof data.relatedAvailable !== "boolean") { - validate93.errors = [{ instancePath: instancePath + "/relatedAvailable", schemaPath: "#/properties/relatedAvailable/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate94.errors = [{ instancePath: instancePath + "/relatedAvailable", schemaPath: "#/properties/relatedAvailable/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs38 === errors; @@ -12478,17 +12514,17 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r let data16 = data.sizeBytes; const _errs40 = errors; if (!(typeof data16 == "number" && (!(data16 % 1) && !isNaN(data16)) && isFinite(data16))) { - validate93.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate94.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs40) { if (typeof data16 == "number" && isFinite(data16)) { if (data16 > 9007199254740991 || isNaN(data16)) { - validate93.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate94.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data16 < 0 || isNaN(data16)) { - validate93.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate94.errors = [{ instancePath: instancePath + "/sizeBytes", schemaPath: "#/properties/sizeBytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -12500,7 +12536,7 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r } if (valid0) { if (data.integrity?.state === "duplicate" && (!Array.isArray(data.integrity.otherModelIds) || data.integrity.count !== data.integrity.otherModelIds.length + 1 || data.integrity.count < 2 || data.integrity.otherModelIds.includes(data.id) || new Set(data.integrity.otherModelIds).size !== data.integrity.otherModelIds.length)) { - validate93.errors = [{ instancePath, schemaPath: "#/pumasCatalogRow", keyword: "pumasCatalogRow", params: {}, message: 'must pass "pumasCatalogRow" keyword validation' }]; + validate94.errors = [{ instancePath, schemaPath: "#/pumasCatalogRow", keyword: "pumasCatalogRow", params: {}, message: 'must pass "pumasCatalogRow" keyword validation' }]; return false; } } @@ -12518,27 +12554,27 @@ function validate93(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate93.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate94.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate93.errors = vErrors; + validate94.errors = vErrors; return errors === 0; } -function validate92(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate93(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.models === void 0 && (missing0 = "models")) { - validate92.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate93.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "models" || key0 === "success")) { - validate92.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate93.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -12552,8 +12588,8 @@ function validate92(data, { instancePath = "", parentData, parentDataProperty, r const _errs4 = errors; for (const key1 in data0) { const _errs5 = errors; - if (!validate93(data0[key1], { instancePath: instancePath + "/models/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data0, parentDataProperty: key1, rootData })) { - vErrors = vErrors === null ? validate93.errors : vErrors.concat(validate93.errors); + if (!validate94(data0[key1], { instancePath: instancePath + "/models/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data0, parentDataProperty: key1, rootData })) { + vErrors = vErrors === null ? validate94.errors : vErrors.concat(validate94.errors); errors = vErrors.length; } var valid1 = _errs5 === errors; @@ -12563,12 +12599,12 @@ function validate92(data, { instancePath = "", parentData, parentDataProperty, r } if (_errs4 === errors) { if (Object.entries(data0).some(([key, value]) => value === null || typeof value !== "object" || key !== value.id)) { - validate92.errors = [{ instancePath: instancePath + "/models", schemaPath: "#/properties/models/pumasCatalogMap", keyword: "pumasCatalogMap", params: {}, message: 'must pass "pumasCatalogMap" keyword validation' }]; + validate93.errors = [{ instancePath: instancePath + "/models", schemaPath: "#/properties/models/pumasCatalogMap", keyword: "pumasCatalogMap", params: {}, message: 'must pass "pumasCatalogMap" keyword validation' }]; return false; } } } else { - validate92.errors = [{ instancePath: instancePath + "/models", schemaPath: "#/properties/models/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate93.errors = [{ instancePath: instancePath + "/models", schemaPath: "#/properties/models/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -12581,11 +12617,11 @@ function validate92(data, { instancePath = "", parentData, parentDataProperty, r let data2 = data.success; const _errs6 = errors; if (typeof data2 !== "boolean") { - validate92.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate93.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data2) { - validate92.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate93.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs6 === errors; @@ -12596,32 +12632,32 @@ function validate92(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate92.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate93.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate92.errors = vErrors; + validate93.errors = vErrors; return errors === 0; } -var validatePartialDownloadOutcome = validate97; -var schema105 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { "DownloadStatus": { "description": "Model download status.", "enum": ["queued", "downloading", "pausing", "paused", "cancelling", "completed", "cancelled", "error"], "type": "string" }, "PartialDownloadActionName": { "enum": ["resume", "recover", "attach", "none"], "type": "string" }, "PartialDownloadReason": { "enum": ["hf_client_unavailable", "download_root_busy", "model_not_found", "model_not_partial", "recovery_unavailable", "recovery_context_stale", "resume_rejected", "already_completed", "already_cancelled", "invalid_repo_id", "repo_not_found", "rate_limited", "permission_denied", "network_error", "recover_failed"], "type": "string" } }, "properties": { "action": { "$ref": "#/definitions/PartialDownloadActionName" }, "download_id": { "type": ["string", "null"] }, "error": { "type": ["string", "null"] }, "reason_code": { "anyOf": [{ "$ref": "#/definitions/PartialDownloadReason" }, { "type": "null" }] }, "status": { "anyOf": [{ "$ref": "#/definitions/DownloadStatus" }, { "type": "null" }] }, "success": { "type": "boolean" } }, "pumasPartialOutcome": true, "required": ["success", "action", "download_id", "status", "reason_code", "error"], "title": "PartialDownloadOutcome", "type": "object" }; -var schema106 = { "enum": ["resume", "recover", "attach", "none"], "type": "string" }; -var schema107 = { "enum": ["hf_client_unavailable", "download_root_busy", "model_not_found", "model_not_partial", "recovery_unavailable", "recovery_context_stale", "resume_rejected", "already_completed", "already_cancelled", "invalid_repo_id", "repo_not_found", "rate_limited", "permission_denied", "network_error", "recover_failed"], "type": "string" }; -var schema108 = { "description": "Model download status.", "enum": ["queued", "downloading", "pausing", "paused", "cancelling", "completed", "cancelled", "error"], "type": "string" }; -function validate97(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validatePartialDownloadOutcome = validate98; +var schema106 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { "DownloadStatus": { "description": "Model download status.", "enum": ["queued", "downloading", "pausing", "paused", "cancelling", "completed", "cancelled", "error"], "type": "string" }, "PartialDownloadActionName": { "enum": ["resume", "recover", "attach", "none"], "type": "string" }, "PartialDownloadReason": { "enum": ["hf_client_unavailable", "download_root_busy", "model_not_found", "model_not_partial", "recovery_unavailable", "recovery_context_stale", "resume_rejected", "already_completed", "already_cancelled", "invalid_repo_id", "repo_not_found", "rate_limited", "permission_denied", "network_error", "recover_failed"], "type": "string" } }, "properties": { "action": { "$ref": "#/definitions/PartialDownloadActionName" }, "download_id": { "type": ["string", "null"] }, "error": { "type": ["string", "null"] }, "reason_code": { "anyOf": [{ "$ref": "#/definitions/PartialDownloadReason" }, { "type": "null" }] }, "status": { "anyOf": [{ "$ref": "#/definitions/DownloadStatus" }, { "type": "null" }] }, "success": { "type": "boolean" } }, "pumasPartialOutcome": true, "required": ["success", "action", "download_id", "status", "reason_code", "error"], "title": "PartialDownloadOutcome", "type": "object" }; +var schema107 = { "enum": ["resume", "recover", "attach", "none"], "type": "string" }; +var schema108 = { "enum": ["hf_client_unavailable", "download_root_busy", "model_not_found", "model_not_partial", "recovery_unavailable", "recovery_context_stale", "resume_rejected", "already_completed", "already_cancelled", "invalid_repo_id", "repo_not_found", "rate_limited", "permission_denied", "network_error", "recover_failed"], "type": "string" }; +var schema109 = { "description": "Model download status.", "enum": ["queued", "downloading", "pausing", "paused", "cancelling", "completed", "cancelled", "error"], "type": "string" }; +function validate98(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.action === void 0 && (missing0 = "action") || data.download_id === void 0 && (missing0 = "download_id") || data.status === void 0 && (missing0 = "status") || data.reason_code === void 0 && (missing0 = "reason_code") || data.error === void 0 && (missing0 = "error")) { - validate97.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate98.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "action" || key0 === "download_id" || key0 === "error" || key0 === "reason_code" || key0 === "status" || key0 === "success")) { - validate97.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate98.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -12631,11 +12667,11 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.action; const _errs2 = errors; if (typeof data0 !== "string") { - validate97.errors = [{ instancePath: instancePath + "/action", schemaPath: "#/definitions/PartialDownloadActionName/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate98.errors = [{ instancePath: instancePath + "/action", schemaPath: "#/definitions/PartialDownloadActionName/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data0 === "resume" || data0 === "recover" || data0 === "attach" || data0 === "none")) { - validate97.errors = [{ instancePath: instancePath + "/action", schemaPath: "#/definitions/PartialDownloadActionName/enum", keyword: "enum", params: { allowedValues: schema106.enum }, message: "must be equal to one of the allowed values" }]; + validate98.errors = [{ instancePath: instancePath + "/action", schemaPath: "#/definitions/PartialDownloadActionName/enum", keyword: "enum", params: { allowedValues: schema107.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs2 === errors; @@ -12647,7 +12683,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.download_id; const _errs5 = errors; if (typeof data1 !== "string" && data1 !== null) { - validate97.errors = [{ instancePath: instancePath + "/download_id", schemaPath: "#/properties/download_id/type", keyword: "type", params: { type: schema105.properties.download_id.type }, message: "must be string,null" }]; + validate98.errors = [{ instancePath: instancePath + "/download_id", schemaPath: "#/properties/download_id/type", keyword: "type", params: { type: schema106.properties.download_id.type }, message: "must be string,null" }]; return false; } var valid0 = _errs5 === errors; @@ -12659,7 +12695,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r let data2 = data.error; const _errs7 = errors; if (typeof data2 !== "string" && data2 !== null) { - validate97.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema105.properties.error.type }, message: "must be string,null" }]; + validate98.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema106.properties.error.type }, message: "must be string,null" }]; return false; } var valid0 = _errs7 === errors; @@ -12683,7 +12719,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r errors++; } if (!(data3 === "hf_client_unavailable" || data3 === "download_root_busy" || data3 === "model_not_found" || data3 === "model_not_partial" || data3 === "recovery_unavailable" || data3 === "recovery_context_stale" || data3 === "resume_rejected" || data3 === "already_completed" || data3 === "already_cancelled" || data3 === "invalid_repo_id" || data3 === "repo_not_found" || data3 === "rate_limited" || data3 === "permission_denied" || data3 === "network_error" || data3 === "recover_failed")) { - const err1 = { instancePath: instancePath + "/reason_code", schemaPath: "#/definitions/PartialDownloadReason/enum", keyword: "enum", params: { allowedValues: schema107.enum }, message: "must be equal to one of the allowed values" }; + const err1 = { instancePath: instancePath + "/reason_code", schemaPath: "#/definitions/PartialDownloadReason/enum", keyword: "enum", params: { allowedValues: schema108.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err1]; } else { @@ -12715,7 +12751,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err3); } errors++; - validate97.errors = vErrors; + validate98.errors = vErrors; return false; } else { errors = _errs10; @@ -12748,7 +12784,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r errors++; } if (!(data4 === "queued" || data4 === "downloading" || data4 === "pausing" || data4 === "paused" || data4 === "cancelling" || data4 === "completed" || data4 === "cancelled" || data4 === "error")) { - const err5 = { instancePath: instancePath + "/status", schemaPath: "#/definitions/DownloadStatus/enum", keyword: "enum", params: { allowedValues: schema108.enum }, message: "must be equal to one of the allowed values" }; + const err5 = { instancePath: instancePath + "/status", schemaPath: "#/definitions/DownloadStatus/enum", keyword: "enum", params: { allowedValues: schema109.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err5]; } else { @@ -12780,7 +12816,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r vErrors.push(err7); } errors++; - validate97.errors = vErrors; + validate98.errors = vErrors; return false; } else { errors = _errs17; @@ -12800,7 +12836,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r if (data.success !== void 0) { const _errs23 = errors; if (typeof data.success !== "boolean") { - validate97.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate98.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs23 === errors; @@ -12809,7 +12845,7 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r } if (valid0) { if (!((data.action === "resume" || data.action === "recover") && data.success === true && typeof data.download_id === "string" && data.download_id.length > 0 && data.status === "queued" && data.reason_code === null && data.error === null || data.action === "attach" && data.success === true && typeof data.download_id === "string" && data.download_id.length > 0 && ["queued", "downloading", "pausing", "cancelling"].includes(data.status) && data.reason_code === null && data.error === null || data.action === "none" && data.success === false && typeof data.error === "string" && (data.download_id === null && data.status === null && !["already_completed", "already_cancelled", "resume_rejected"].includes(data.reason_code) && data.reason_code !== null || typeof data.download_id === "string" && data.download_id.length > 0 && (data.status === "completed" && data.reason_code === "already_completed" || data.status === "cancelled" && data.reason_code === "already_cancelled" || ["paused", "error"].includes(data.status) && data.reason_code === "resume_rejected")))) { - validate97.errors = [{ instancePath, schemaPath: "#/pumasPartialOutcome", keyword: "pumasPartialOutcome", params: {}, message: 'must pass "pumasPartialOutcome" keyword validation' }]; + validate98.errors = [{ instancePath, schemaPath: "#/pumasPartialOutcome", keyword: "pumasPartialOutcome", params: {}, message: 'must pass "pumasPartialOutcome" keyword validation' }]; return false; } } @@ -12821,28 +12857,28 @@ function validate97(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate97.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate98.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate97.errors = vErrors; + validate98.errors = vErrors; return errors === 0; } -var validatePreviewTorchRuntimeParams = validate98; -function validate98(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validatePreviewTorchRuntimeParams = validate99; +function validate99(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.tag === void 0 && (missing0 = "tag") || data.build === void 0 && (missing0 = "build") || data.python === void 0 && (missing0 = "python")) { - validate98.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate99.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "adapter" || key0 === "build" || key0 === "python" || key0 === "tag")) { - validate98.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate99.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -12851,7 +12887,7 @@ function validate98(data, { instancePath = "", parentData, parentDataProperty, r if (data.adapter !== void 0) { const _errs2 = errors; if (typeof data.adapter !== "string") { - validate98.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate99.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -12862,7 +12898,7 @@ function validate98(data, { instancePath = "", parentData, parentDataProperty, r if (data.build !== void 0) { const _errs4 = errors; if (typeof data.build !== "string") { - validate98.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate99.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs4 === errors; @@ -12873,7 +12909,7 @@ function validate98(data, { instancePath = "", parentData, parentDataProperty, r if (data.python !== void 0) { const _errs6 = errors; if (typeof data.python !== "string") { - validate98.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate99.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs6 === errors; @@ -12884,7 +12920,7 @@ function validate98(data, { instancePath = "", parentData, parentDataProperty, r if (data.tag !== void 0) { const _errs8 = errors; if (typeof data.tag !== "string") { - validate98.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate99.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs8 === errors; @@ -12897,29 +12933,29 @@ function validate98(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate98.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate99.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate98.errors = vErrors; + validate99.errors = vErrors; return errors === 0; } -var validatePublicError = validate99; -var schema111 = { "description": "Stable public failure categories shared by RPC transports.", "enum": ["invalid_request", "not_found", "conflict", "cancelled", "unavailable", "operation_failed", "internal"], "type": "string" }; -function validate99(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validatePublicError = validate100; +var schema112 = { "description": "Stable public failure categories shared by RPC transports.", "enum": ["invalid_request", "not_found", "conflict", "cancelled", "unavailable", "operation_failed", "internal"], "type": "string" }; +function validate100(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.code === void 0 && (missing0 = "code") || data.class === void 0 && (missing0 = "class") || data.message === void 0 && (missing0 = "message")) { - validate99.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate100.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "class" || key0 === "code" || key0 === "message")) { - validate99.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate100.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -12929,11 +12965,11 @@ function validate99(data, { instancePath = "", parentData, parentDataProperty, r let data0 = data.class; const _errs2 = errors; if (typeof data0 !== "string") { - validate99.errors = [{ instancePath: instancePath + "/class", schemaPath: "#/definitions/PublicErrorClass/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate100.errors = [{ instancePath: instancePath + "/class", schemaPath: "#/definitions/PublicErrorClass/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data0 === "invalid_request" || data0 === "not_found" || data0 === "conflict" || data0 === "cancelled" || data0 === "unavailable" || data0 === "operation_failed" || data0 === "internal")) { - validate99.errors = [{ instancePath: instancePath + "/class", schemaPath: "#/definitions/PublicErrorClass/enum", keyword: "enum", params: { allowedValues: schema111.enum }, message: "must be equal to one of the allowed values" }]; + validate100.errors = [{ instancePath: instancePath + "/class", schemaPath: "#/definitions/PublicErrorClass/enum", keyword: "enum", params: { allowedValues: schema112.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs2 === errors; @@ -12945,17 +12981,17 @@ function validate99(data, { instancePath = "", parentData, parentDataProperty, r let data1 = data.code; const _errs5 = errors; if (!(typeof data1 == "number" && (!(data1 % 1) && !isNaN(data1)) && isFinite(data1))) { - validate99.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate100.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs5) { if (typeof data1 == "number" && isFinite(data1)) { if (data1 > 2147483647 || isNaN(data1)) { - validate99.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/maximum", keyword: "maximum", params: { comparison: "<=", limit: 2147483647 }, message: "must be <= 2147483647" }]; + validate100.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/maximum", keyword: "maximum", params: { comparison: "<=", limit: 2147483647 }, message: "must be <= 2147483647" }]; return false; } else { if (data1 < -2147483648 || isNaN(data1)) { - validate99.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/minimum", keyword: "minimum", params: { comparison: ">=", limit: -2147483648 }, message: "must be >= -2147483648" }]; + validate100.errors = [{ instancePath: instancePath + "/code", schemaPath: "#/properties/code/minimum", keyword: "minimum", params: { comparison: ">=", limit: -2147483648 }, message: "must be >= -2147483648" }]; return false; } } @@ -12969,7 +13005,7 @@ function validate99(data, { instancePath = "", parentData, parentDataProperty, r if (data.message !== void 0) { const _errs7 = errors; if (typeof data.message !== "string") { - validate99.errors = [{ instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate100.errors = [{ instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs7 === errors; @@ -12981,28 +13017,28 @@ function validate99(data, { instancePath = "", parentData, parentDataProperty, r } } } else { - validate99.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate100.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate99.errors = vErrors; + validate100.errors = vErrors; return errors === 0; } -var validateRecoverDownloadParams = validate100; -function validate100(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRecoverDownloadParams = validate101; +function validate101(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.modelId === void 0 && (missing0 = "modelId") || data.recoveryToken === void 0 && (missing0 = "recoveryToken")) { - validate100.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate101.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "modelId" || key0 === "recoveryToken")) { - validate100.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate101.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13017,16 +13053,16 @@ function validate100(data, { instancePath = "", parentData, parentDataProperty, const stem = component.split(".")[0].replace(/[a-z]/g, (letter) => letter.toUpperCase()); return component.length === 0 || component === "." || component === ".." || /[. ]$/.test(component) || encodeURIComponent(component).replace(/%[0-9A-F]{2}/g, "x").length > 255 || ["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"].includes(stem) || /^(COM|LPT)[1-9]$/.test(stem); })) { - validate100.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/pumasPortablePath", keyword: "pumasPortablePath", params: {}, message: 'must pass "pumasPortablePath" keyword validation' }]; + validate101.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/pumasPortablePath", keyword: "pumasPortablePath", params: {}, message: 'must pass "pumasPortablePath" keyword validation' }]; return false; } else { if (encodeURIComponent(data0).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate100.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate101.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } } else { - validate100.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate101.errors = [{ instancePath: instancePath + "/modelId", schemaPath: "#/properties/modelId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -13041,11 +13077,11 @@ function validate100(data, { instancePath = "", parentData, parentDataProperty, if (errors === _errs4) { if (typeof data1 === "string") { if (!pattern0.test(data1)) { - validate100.errors = [{ instancePath: instancePath + "/recoveryToken", schemaPath: "#/properties/recoveryToken/pattern", keyword: "pattern", params: { pattern: "^v1:[0-9a-f]{64}$" }, message: 'must match pattern "^v1:[0-9a-f]{64}$"' }]; + validate101.errors = [{ instancePath: instancePath + "/recoveryToken", schemaPath: "#/properties/recoveryToken/pattern", keyword: "pattern", params: { pattern: "^v1:[0-9a-f]{64}$" }, message: 'must match pattern "^v1:[0-9a-f]{64}$"' }]; return false; } } else { - validate100.errors = [{ instancePath: instancePath + "/recoveryToken", schemaPath: "#/properties/recoveryToken/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate101.errors = [{ instancePath: instancePath + "/recoveryToken", schemaPath: "#/properties/recoveryToken/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -13057,28 +13093,28 @@ function validate100(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate100.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate101.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate100.errors = vErrors; + validate101.errors = vErrors; return errors === 0; } -var validateRemoveVersionOutcome = validate101; -function validate101(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRemoveVersionOutcome = validate102; +function validate102(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate101.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate102.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success")) { - validate101.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate102.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13086,24 +13122,24 @@ function validate101(data, { instancePath = "", parentData, parentDataProperty, if (_errs1 === errors) { if (data.success !== void 0) { if (typeof data.success !== "boolean") { - validate101.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate102.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } } } } } else { - validate101.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate102.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate101.errors = vErrors; + validate102.errors = vErrors; return errors === 0; } -var validateRouterProfileSyncStatus = validate102; -var schema114 = { "definitions": { "RouterCatalogState": { "enum": ["current", "pending", "uncertain"], "type": "string" }, "RouterObservationState": { "enum": ["connecting", "current", "unavailable"], "type": "string" } }, "oneOf": [{ "additionalProperties": false, "properties": { "catalog_state": { "enum": ["current", "pending"], "type": "string" }, "generation": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "last_error": { "type": ["string", "null"] }, "observation_state": { "$ref": "#/definitions/RouterObservationState" }, "pending_model_ids": { "items": { "type": "string" }, "type": "array" }, "profile_id": { "type": "string" } }, "required": ["profile_id", "generation", "observation_state", "catalog_state", "pending_model_ids", "last_error"], "type": "object" }, { "additionalProperties": false, "properties": { "catalog_state": { "enum": ["uncertain"], "type": "string" }, "generation": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "last_error": { "type": ["string", "null"] }, "observation_state": { "enum": ["unavailable"], "type": "string" }, "pending_model_ids": { "items": { "type": "string" }, "type": "array" }, "profile_id": { "type": "string" } }, "required": ["profile_id", "generation", "observation_state", "catalog_state", "pending_model_ids", "last_error"], "type": "object" }] }; -var schema115 = { "enum": ["connecting", "current", "unavailable"], "type": "string" }; -function validate102(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRouterProfileSyncStatus = validate103; +var schema115 = { "definitions": { "RouterCatalogState": { "enum": ["current", "pending", "uncertain"], "type": "string" }, "RouterObservationState": { "enum": ["connecting", "current", "unavailable"], "type": "string" } }, "oneOf": [{ "additionalProperties": false, "properties": { "catalog_state": { "enum": ["current", "pending"], "type": "string" }, "generation": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "last_error": { "type": ["string", "null"] }, "observation_state": { "$ref": "#/definitions/RouterObservationState" }, "pending_model_ids": { "items": { "type": "string" }, "type": "array" }, "profile_id": { "type": "string" } }, "required": ["profile_id", "generation", "observation_state", "catalog_state", "pending_model_ids", "last_error"], "type": "object" }, { "additionalProperties": false, "properties": { "catalog_state": { "enum": ["uncertain"], "type": "string" }, "generation": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "last_error": { "type": ["string", "null"] }, "observation_state": { "enum": ["unavailable"], "type": "string" }, "pending_model_ids": { "items": { "type": "string" }, "type": "array" }, "profile_id": { "type": "string" } }, "required": ["profile_id", "generation", "observation_state", "catalog_state", "pending_model_ids", "last_error"], "type": "object" }] }; +var schema116 = { "enum": ["connecting", "current", "unavailable"], "type": "string" }; +function validate103(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -13149,7 +13185,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, errors++; } if (!(data0 === "current" || data0 === "pending")) { - const err3 = { instancePath: instancePath + "/catalog_state", schemaPath: "#/oneOf/0/properties/catalog_state/enum", keyword: "enum", params: { allowedValues: schema114.oneOf[0].properties.catalog_state.enum }, message: "must be equal to one of the allowed values" }; + const err3 = { instancePath: instancePath + "/catalog_state", schemaPath: "#/oneOf/0/properties/catalog_state/enum", keyword: "enum", params: { allowedValues: schema115.oneOf[0].properties.catalog_state.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err3]; } else { @@ -13206,7 +13242,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, let data2 = data.last_error; const _errs8 = errors; if (typeof data2 !== "string" && data2 !== null) { - const err7 = { instancePath: instancePath + "/last_error", schemaPath: "#/oneOf/0/properties/last_error/type", keyword: "type", params: { type: schema114.oneOf[0].properties.last_error.type }, message: "must be string,null" }; + const err7 = { instancePath: instancePath + "/last_error", schemaPath: "#/oneOf/0/properties/last_error/type", keyword: "type", params: { type: schema115.oneOf[0].properties.last_error.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err7]; } else { @@ -13232,7 +13268,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, errors++; } if (!(data3 === "connecting" || data3 === "current" || data3 === "unavailable")) { - const err9 = { instancePath: instancePath + "/observation_state", schemaPath: "#/definitions/RouterObservationState/enum", keyword: "enum", params: { allowedValues: schema115.enum }, message: "must be equal to one of the allowed values" }; + const err9 = { instancePath: instancePath + "/observation_state", schemaPath: "#/definitions/RouterObservationState/enum", keyword: "enum", params: { allowedValues: schema116.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err9]; } else { @@ -13360,7 +13396,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, errors++; } if (!(data7 === "uncertain")) { - const err17 = { instancePath: instancePath + "/catalog_state", schemaPath: "#/oneOf/1/properties/catalog_state/enum", keyword: "enum", params: { allowedValues: schema114.oneOf[1].properties.catalog_state.enum }, message: "must be equal to one of the allowed values" }; + const err17 = { instancePath: instancePath + "/catalog_state", schemaPath: "#/oneOf/1/properties/catalog_state/enum", keyword: "enum", params: { allowedValues: schema115.oneOf[1].properties.catalog_state.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err17]; } else { @@ -13417,7 +13453,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, let data9 = data.last_error; const _errs26 = errors; if (typeof data9 !== "string" && data9 !== null) { - const err21 = { instancePath: instancePath + "/last_error", schemaPath: "#/oneOf/1/properties/last_error/type", keyword: "type", params: { type: schema114.oneOf[1].properties.last_error.type }, message: "must be string,null" }; + const err21 = { instancePath: instancePath + "/last_error", schemaPath: "#/oneOf/1/properties/last_error/type", keyword: "type", params: { type: schema115.oneOf[1].properties.last_error.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err21]; } else { @@ -13443,7 +13479,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, errors++; } if (!(data10 === "unavailable")) { - const err23 = { instancePath: instancePath + "/observation_state", schemaPath: "#/oneOf/1/properties/observation_state/enum", keyword: "enum", params: { allowedValues: schema114.oneOf[1].properties.observation_state.enum }, message: "must be equal to one of the allowed values" }; + const err23 = { instancePath: instancePath + "/observation_state", schemaPath: "#/oneOf/1/properties/observation_state/enum", keyword: "enum", params: { allowedValues: schema115.oneOf[1].properties.observation_state.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err23]; } else { @@ -13544,7 +13580,7 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err28); } errors++; - validate102.errors = vErrors; + validate103.errors = vErrors; return false; } else { errors = _errs0; @@ -13556,24 +13592,24 @@ function validate102(data, { instancePath = "", parentData, parentDataProperty, } } } - validate102.errors = vErrors; + validate103.errors = vErrors; return errors === 0; } -var validateRuntimeLaunchOutcome = validate103; -function validate103(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRuntimeLaunchOutcome = validate104; +function validate104(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate103.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate104.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "error" || key0 === "log_path" || key0 === "ready" || key0 === "success")) { - validate103.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate104.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13582,7 +13618,7 @@ function validate103(data, { instancePath = "", parentData, parentDataProperty, if (data.error !== void 0) { const _errs2 = errors; if (typeof data.error !== "string") { - validate103.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate104.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -13593,7 +13629,7 @@ function validate103(data, { instancePath = "", parentData, parentDataProperty, if (data.log_path !== void 0) { const _errs4 = errors; if (typeof data.log_path !== "string") { - validate103.errors = [{ instancePath: instancePath + "/log_path", schemaPath: "#/properties/log_path/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate104.errors = [{ instancePath: instancePath + "/log_path", schemaPath: "#/properties/log_path/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs4 === errors; @@ -13604,7 +13640,7 @@ function validate103(data, { instancePath = "", parentData, parentDataProperty, if (data.ready !== void 0) { const _errs6 = errors; if (typeof data.ready !== "boolean") { - validate103.errors = [{ instancePath: instancePath + "/ready", schemaPath: "#/properties/ready/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate104.errors = [{ instancePath: instancePath + "/ready", schemaPath: "#/properties/ready/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs6 === errors; @@ -13615,7 +13651,7 @@ function validate103(data, { instancePath = "", parentData, parentDataProperty, if (data.success !== void 0) { const _errs8 = errors; if (typeof data.success !== "boolean") { - validate103.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate104.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs8 === errors; @@ -13628,58 +13664,58 @@ function validate103(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate103.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate104.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate103.errors = vErrors; + validate104.errors = vErrors; return errors === 0; } -var validateRuntimeLaunchParams = validate104; -function validate104(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRuntimeLaunchParams = validate105; +function validate105(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { for (const key0 in data) { - validate104.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate105.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } } else { - validate104.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate105.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate104.errors = vErrors; + validate105.errors = vErrors; return errors === 0; } -var validateRuntimeRunningOutcome = validate105; -function validate105(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRuntimeRunningOutcome = validate106; +function validate106(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (typeof data !== "boolean") { - validate105.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate106.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } - validate105.errors = vErrors; + validate106.errors = vErrors; return errors === 0; } -var validateRuntimeStopOutcome = validate106; -function validate106(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateRuntimeStopOutcome = validate107; +function validate107(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate106.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate107.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success")) { - validate106.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate107.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13687,36 +13723,36 @@ function validate106(data, { instancePath = "", parentData, parentDataProperty, if (_errs1 === errors) { if (data.success !== void 0) { if (typeof data.success !== "boolean") { - validate106.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate107.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } } } } } else { - validate106.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate107.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate106.errors = vErrors; + validate107.errors = vErrors; return errors === 0; } -var validateSearchCatalogParams = validate107; -var schema120 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "limit": { "maximum": 512, "minimum": 1, "type": ["integer", "null"] }, "offset": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "query": { "pumasUtf8Max": 4096, "type": "string" } }, "required": ["query"], "title": "SearchCatalogParams", "type": "object" }; -function validate107(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSearchCatalogParams = validate108; +var schema121 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "limit": { "maximum": 512, "minimum": 1, "type": ["integer", "null"] }, "offset": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "query": { "pumasUtf8Max": 4096, "type": "string" } }, "required": ["query"], "title": "SearchCatalogParams", "type": "object" }; +function validate108(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.query === void 0 && (missing0 = "query")) { - validate107.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate108.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "limit" || key0 === "offset" || key0 === "query")) { - validate107.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate108.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13726,17 +13762,17 @@ function validate107(data, { instancePath = "", parentData, parentDataProperty, let data0 = data.limit; const _errs2 = errors; if (!(typeof data0 == "number" && (!(data0 % 1) && !isNaN(data0)) && isFinite(data0)) && data0 !== null) { - validate107.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/type", keyword: "type", params: { type: schema120.properties.limit.type }, message: "must be integer,null" }]; + validate108.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/type", keyword: "type", params: { type: schema121.properties.limit.type }, message: "must be integer,null" }]; return false; } if (errors === _errs2) { if (typeof data0 == "number" && isFinite(data0)) { if (data0 > 512 || isNaN(data0)) { - validate107.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/maximum", keyword: "maximum", params: { comparison: "<=", limit: 512 }, message: "must be <= 512" }]; + validate108.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/maximum", keyword: "maximum", params: { comparison: "<=", limit: 512 }, message: "must be <= 512" }]; return false; } else { if (data0 < 1 || isNaN(data0)) { - validate107.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/minimum", keyword: "minimum", params: { comparison: ">=", limit: 1 }, message: "must be >= 1" }]; + validate108.errors = [{ instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/minimum", keyword: "minimum", params: { comparison: ">=", limit: 1 }, message: "must be >= 1" }]; return false; } } @@ -13751,17 +13787,17 @@ function validate107(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.offset; const _errs4 = errors; if (!(typeof data1 == "number" && (!(data1 % 1) && !isNaN(data1)) && isFinite(data1)) && data1 !== null) { - validate107.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/type", keyword: "type", params: { type: schema120.properties.offset.type }, message: "must be integer,null" }]; + validate108.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/type", keyword: "type", params: { type: schema121.properties.offset.type }, message: "must be integer,null" }]; return false; } if (errors === _errs4) { if (typeof data1 == "number" && isFinite(data1)) { if (data1 > 4294967295 || isNaN(data1)) { - validate107.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate108.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data1 < 0 || isNaN(data1)) { - validate107.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate108.errors = [{ instancePath: instancePath + "/offset", schemaPath: "#/properties/offset/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -13778,11 +13814,11 @@ function validate107(data, { instancePath = "", parentData, parentDataProperty, if (errors === _errs6) { if (typeof data2 === "string") { if (encodeURIComponent(data2).replace(/%[0-9A-F]{2}/g, "x").length > 4096) { - validate107.errors = [{ instancePath: instancePath + "/query", schemaPath: "#/properties/query/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; + validate108.errors = [{ instancePath: instancePath + "/query", schemaPath: "#/properties/query/pumasUtf8Max", keyword: "pumasUtf8Max", params: {}, message: 'must pass "pumasUtf8Max" keyword validation' }]; return false; } } else { - validate107.errors = [{ instancePath: instancePath + "/query", schemaPath: "#/properties/query/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate108.errors = [{ instancePath: instancePath + "/query", schemaPath: "#/properties/query/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } } @@ -13795,28 +13831,28 @@ function validate107(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate107.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate108.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate107.errors = vErrors; + validate108.errors = vErrors; return errors === 0; } -var validateSelectedVersionOutcome = validate108; -function validate108(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSelectedVersionOutcome = validate109; +function validate109(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.version === void 0 && (missing0 = "version")) { - validate108.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate109.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success" || key0 === "version")) { - validate108.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate109.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13826,11 +13862,11 @@ function validate108(data, { instancePath = "", parentData, parentDataProperty, let data0 = data.success; const _errs2 = errors; if (typeof data0 !== "boolean") { - validate108.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate109.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data0) { - validate108.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate109.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs2 === errors; @@ -13841,7 +13877,7 @@ function validate108(data, { instancePath = "", parentData, parentDataProperty, if (data.version !== void 0) { const _errs4 = errors; if (typeof data.version !== "string") { - validate108.errors = [{ instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate109.errors = [{ instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs4 === errors; @@ -13852,28 +13888,28 @@ function validate108(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate108.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate109.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate108.errors = vErrors; + validate109.errors = vErrors; return errors === 0; } -var validateSetDefaultVersionOutcome = validate109; -function validate109(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSetDefaultVersionOutcome = validate110; +function validate110(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate109.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate110.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success")) { - validate109.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate110.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -13881,23 +13917,23 @@ function validate109(data, { instancePath = "", parentData, parentDataProperty, if (_errs1 === errors) { if (data.success !== void 0) { if (typeof data.success !== "boolean") { - validate109.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate110.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } } } } } else { - validate109.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate110.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate109.errors = vErrors; + validate110.errors = vErrors; return errors === 0; } -var validateSetDefaultVersionParams = validate110; -var schema123 = { "anyOf": [{ "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "tag": { "default": null, "type": ["string", "null"] } }, "required": ["app_id"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "tag": { "default": null, "type": ["string", "null"] } }, "required": ["appId"], "type": "object" }] }; -function validate110(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSetDefaultVersionParams = validate111; +var schema124 = { "anyOf": [{ "additionalProperties": false, "properties": { "app_id": { "type": "string" }, "tag": { "default": null, "type": ["string", "null"] } }, "required": ["app_id"], "type": "object" }, { "additionalProperties": false, "properties": { "appId": { "type": "string" }, "tag": { "default": null, "type": ["string", "null"] } }, "required": ["appId"], "type": "object" }] }; +function validate111(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -13949,7 +13985,7 @@ function validate110(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.tag; const _errs6 = errors; if (typeof data1 !== "string" && data1 !== null) { - const err3 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/0/properties/tag/type", keyword: "type", params: { type: schema123.anyOf[0].properties.tag.type }, message: "must be string,null" }; + const err3 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/0/properties/tag/type", keyword: "type", params: { type: schema124.anyOf[0].properties.tag.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err3]; } else { @@ -14024,7 +14060,7 @@ function validate110(data, { instancePath = "", parentData, parentDataProperty, let data3 = data.tag; const _errs13 = errors; if (typeof data3 !== "string" && data3 !== null) { - const err8 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/1/properties/tag/type", keyword: "type", params: { type: schema123.anyOf[1].properties.tag.type }, message: "must be string,null" }; + const err8 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/1/properties/tag/type", keyword: "type", params: { type: schema124.anyOf[1].properties.tag.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err8]; } else { @@ -14060,7 +14096,7 @@ function validate110(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err10); } errors++; - validate110.errors = vErrors; + validate111.errors = vErrors; return false; } else { errors = _errs0; @@ -14072,25 +14108,25 @@ function validate110(data, { instancePath = "", parentData, parentDataProperty, } } } - validate110.errors = vErrors; + validate111.errors = vErrors; return errors === 0; } -var validateStartBackendSetupParams = validate111; -var schema124 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { "QuantBackend": { "description": "Identifies which quantization backend provides a capability.", "oneOf": [{ "const": "python_conversion", "description": "Existing Python-based safetensors \u2194 GGUF F16 conversion.", "type": "string" }, { "const": "llama_cpp", "description": "llama.cpp native quantization (llama-quantize, llama-imatrix).", "type": "string" }, { "const": "nvfp4", "description": "NVIDIA NVFP4 via TensorRT-LLM / nvidia-modelopt (Phase 2).", "type": "string" }, { "const": "fp8", "description": "CPU conversion to Transformers block-scaled FP8 Safetensors.", "type": "string" }, { "const": "sherry", "description": "Sherry / AngelSlim quantization-aware training (Phase 3).", "type": "string" }] } }, "properties": { "backend": { "$ref": "#/definitions/QuantBackend" }, "expected_previous_operation_id": { "default": null, "maxLength": 36, "minLength": 36, "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": ["string", "null"] } }, "required": ["backend"], "title": "StartBackendSetupParams", "type": "object" }; -function validate111(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateStartBackendSetupParams = validate112; +var schema125 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { "QuantBackend": { "description": "Identifies which quantization backend provides a capability.", "oneOf": [{ "const": "python_conversion", "description": "Existing Python-based safetensors \u2194 GGUF F16 conversion.", "type": "string" }, { "const": "llama_cpp", "description": "llama.cpp native quantization (llama-quantize, llama-imatrix).", "type": "string" }, { "const": "nvfp4", "description": "NVIDIA NVFP4 via TensorRT-LLM / nvidia-modelopt (Phase 2).", "type": "string" }, { "const": "fp8", "description": "CPU conversion to Transformers block-scaled FP8 Safetensors.", "type": "string" }, { "const": "sherry", "description": "Sherry / AngelSlim quantization-aware training (Phase 3).", "type": "string" }] } }, "properties": { "backend": { "$ref": "#/definitions/QuantBackend" }, "expected_previous_operation_id": { "default": null, "maxLength": 36, "minLength": 36, "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": ["string", "null"] } }, "required": ["backend"], "title": "StartBackendSetupParams", "type": "object" }; +function validate112(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.backend === void 0 && (missing0 = "backend")) { - validate111.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate112.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "backend" || key0 === "expected_previous_operation_id")) { - validate111.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate112.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14250,7 +14286,7 @@ function validate111(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err10); } errors++; - validate111.errors = vErrors; + validate112.errors = vErrors; return false; } else { errors = _errs4; @@ -14271,21 +14307,21 @@ function validate111(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.expected_previous_operation_id; const _errs15 = errors; if (typeof data1 !== "string" && data1 !== null) { - validate111.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/type", keyword: "type", params: { type: schema124.properties.expected_previous_operation_id.type }, message: "must be string,null" }]; + validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/type", keyword: "type", params: { type: schema125.properties.expected_previous_operation_id.type }, message: "must be string,null" }]; return false; } if (errors === _errs15) { if (typeof data1 === "string") { if (func5(data1) > 36) { - validate111.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/maxLength", keyword: "maxLength", params: { limit: 36 }, message: "must NOT have more than 36 characters" }]; + validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/maxLength", keyword: "maxLength", params: { limit: 36 }, message: "must NOT have more than 36 characters" }]; return false; } else { if (func5(data1) < 36) { - validate111.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/minLength", keyword: "minLength", params: { limit: 36 }, message: "must NOT have fewer than 36 characters" }]; + validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/minLength", keyword: "minLength", params: { limit: 36 }, message: "must NOT have fewer than 36 characters" }]; return false; } else { if (!pattern12.test(data1)) { - validate111.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/pattern", keyword: "pattern", params: { pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, message: 'must match pattern "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"' }]; + validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/pattern", keyword: "pattern", params: { pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, message: 'must match pattern "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"' }]; return false; } } @@ -14300,16 +14336,16 @@ function validate111(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate111.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate112.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate111.errors = vErrors; + validate112.errors = vErrors; return errors === 0; } -var validateStartConversionSetupParams = validate112; -var schema126 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "expected_previous_operation_id": { "default": null, "maxLength": 36, "minLength": 36, "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": ["string", "null"] } }, "title": "StartConversionSetupParams", "type": "object" }; -function validate112(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateStartConversionSetupParams = validate113; +var schema127 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "expected_previous_operation_id": { "default": null, "maxLength": 36, "minLength": 36, "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": ["string", "null"] } }, "title": "StartConversionSetupParams", "type": "object" }; +function validate113(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { @@ -14317,7 +14353,7 @@ function validate112(data, { instancePath = "", parentData, parentDataProperty, const _errs1 = errors; for (const key0 in data) { if (!(key0 === "expected_previous_operation_id")) { - validate112.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate113.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14327,21 +14363,21 @@ function validate112(data, { instancePath = "", parentData, parentDataProperty, let data0 = data.expected_previous_operation_id; const _errs2 = errors; if (typeof data0 !== "string" && data0 !== null) { - validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/type", keyword: "type", params: { type: schema126.properties.expected_previous_operation_id.type }, message: "must be string,null" }]; + validate113.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/type", keyword: "type", params: { type: schema127.properties.expected_previous_operation_id.type }, message: "must be string,null" }]; return false; } if (errors === _errs2) { if (typeof data0 === "string") { if (func5(data0) > 36) { - validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/maxLength", keyword: "maxLength", params: { limit: 36 }, message: "must NOT have more than 36 characters" }]; + validate113.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/maxLength", keyword: "maxLength", params: { limit: 36 }, message: "must NOT have more than 36 characters" }]; return false; } else { if (func5(data0) < 36) { - validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/minLength", keyword: "minLength", params: { limit: 36 }, message: "must NOT have fewer than 36 characters" }]; + validate113.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/minLength", keyword: "minLength", params: { limit: 36 }, message: "must NOT have fewer than 36 characters" }]; return false; } else { if (!pattern12.test(data0)) { - validate112.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/pattern", keyword: "pattern", params: { pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, message: 'must match pattern "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"' }]; + validate113.errors = [{ instancePath: instancePath + "/expected_previous_operation_id", schemaPath: "#/properties/expected_previous_operation_id/pattern", keyword: "pattern", params: { pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, message: 'must match pattern "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"' }]; return false; } } @@ -14351,28 +14387,28 @@ function validate112(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate112.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate113.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate112.errors = vErrors; + validate113.errors = vErrors; return errors === 0; } -var validateStopRuntimeProfileGenerationOutcome = validate113; -function validate113(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateStopRuntimeProfileGenerationOutcome = validate114; +function validate114(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.stopped === void 0 && (missing0 = "stopped")) { - validate113.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate114.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "stopped" || key0 === "success")) { - validate113.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate114.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14381,7 +14417,7 @@ function validate113(data, { instancePath = "", parentData, parentDataProperty, if (data.stopped !== void 0) { const _errs2 = errors; if (typeof data.stopped !== "boolean") { - validate113.errors = [{ instancePath: instancePath + "/stopped", schemaPath: "#/properties/stopped/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate114.errors = [{ instancePath: instancePath + "/stopped", schemaPath: "#/properties/stopped/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs2 === errors; @@ -14392,7 +14428,7 @@ function validate113(data, { instancePath = "", parentData, parentDataProperty, if (data.success !== void 0) { const _errs4 = errors; if (typeof data.success !== "boolean") { - validate113.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate114.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs4 === errors; @@ -14403,15 +14439,15 @@ function validate113(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate113.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate114.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate113.errors = vErrors; + validate114.errors = vErrors; return errors === 0; } -var validateStopRuntimeProfileGenerationParams = validate114; -function validate114(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateStopRuntimeProfileGenerationParams = validate115; +function validate115(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -14572,7 +14608,7 @@ function validate114(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err10); } errors++; - validate114.errors = vErrors; + validate115.errors = vErrors; return false; } else { errors = _errs0; @@ -14584,24 +14620,24 @@ function validate114(data, { instancePath = "", parentData, parentDataProperty, } } } - validate114.errors = vErrors; + validate115.errors = vErrors; return errors === 0; } -var validateSuccessOutcome = validate115; -function validate115(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSuccessOutcome = validate116; +function validate116(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success")) { - validate115.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate116.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "success")) { - validate115.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate116.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14610,39 +14646,39 @@ function validate115(data, { instancePath = "", parentData, parentDataProperty, if (data.success !== void 0) { let data0 = data.success; if (typeof data0 !== "boolean") { - validate115.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate116.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data0) { - validate115.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate116.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } } } } } else { - validate115.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate116.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate115.errors = vErrors; + validate116.errors = vErrors; return errors === 0; } -var validateSupportedQuantTypesOutcome = validate116; -function validate117(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateSupportedQuantTypesOutcome = validate117; +function validate118(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.name === void 0 && (missing0 = "name") || data.description === void 0 && (missing0 = "description") || data.bitsPerWeight === void 0 && (missing0 = "bitsPerWeight") || data.recommended === void 0 && (missing0 = "recommended") || data.backend === void 0 && (missing0 = "backend") || data.imatrixRecommended === void 0 && (missing0 = "imatrixRecommended")) { - validate117.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate118.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "backend" || key0 === "bitsPerWeight" || key0 === "description" || key0 === "imatrixRecommended" || key0 === "name" || key0 === "recommended")) { - validate117.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate118.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14839,7 +14875,7 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err12); } errors++; - validate117.errors = vErrors; + validate118.errors = vErrors; return false; } else { errors = _errs3; @@ -14862,16 +14898,16 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, if (errors === _errs19) { if (typeof data1 == "number" && isFinite(data1)) { if (data1 > 34028234663852886e22 || isNaN(data1)) { - validate117.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; + validate118.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/maximum", keyword: "maximum", params: { comparison: "<=", limit: 34028234663852886e22 }, message: "must be <= 3.4028234663852886e+38" }]; return false; } else { if (data1 < 0 || isNaN(data1)) { - validate117.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate118.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } } else { - validate117.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/type", keyword: "type", params: { type: "number" }, message: "must be number" }]; + validate118.errors = [{ instancePath: instancePath + "/bitsPerWeight", schemaPath: "#/properties/bitsPerWeight/type", keyword: "type", params: { type: "number" }, message: "must be number" }]; return false; } } @@ -14883,7 +14919,7 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, if (data.description !== void 0) { const _errs21 = errors; if (typeof data.description !== "string") { - validate117.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate118.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs21 === errors; @@ -14894,7 +14930,7 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, if (data.imatrixRecommended !== void 0) { const _errs23 = errors; if (typeof data.imatrixRecommended !== "boolean") { - validate117.errors = [{ instancePath: instancePath + "/imatrixRecommended", schemaPath: "#/properties/imatrixRecommended/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate118.errors = [{ instancePath: instancePath + "/imatrixRecommended", schemaPath: "#/properties/imatrixRecommended/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs23 === errors; @@ -14905,7 +14941,7 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, if (data.name !== void 0) { const _errs25 = errors; if (typeof data.name !== "string") { - validate117.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate118.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs25 === errors; @@ -14916,7 +14952,7 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, if (data.recommended !== void 0) { const _errs27 = errors; if (typeof data.recommended !== "boolean") { - validate117.errors = [{ instancePath: instancePath + "/recommended", schemaPath: "#/properties/recommended/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate118.errors = [{ instancePath: instancePath + "/recommended", schemaPath: "#/properties/recommended/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs27 === errors; @@ -14931,27 +14967,27 @@ function validate117(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate117.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate118.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate117.errors = vErrors; + validate118.errors = vErrors; return errors === 0; } -function validate116(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate117(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.quant_types === void 0 && (missing0 = "quant_types")) { - validate116.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate117.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "quant_types" || key0 === "success")) { - validate116.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate117.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -14966,8 +15002,8 @@ function validate116(data, { instancePath = "", parentData, parentDataProperty, const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; - if (!validate117(data0[i0], { instancePath: instancePath + "/quant_types/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate117.errors : vErrors.concat(validate117.errors); + if (!validate118(data0[i0], { instancePath: instancePath + "/quant_types/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate118.errors : vErrors.concat(validate118.errors); errors = vErrors.length; } var valid1 = _errs4 === errors; @@ -14976,7 +15012,7 @@ function validate116(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate116.errors = [{ instancePath: instancePath + "/quant_types", schemaPath: "#/properties/quant_types/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate117.errors = [{ instancePath: instancePath + "/quant_types", schemaPath: "#/properties/quant_types/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -14989,11 +15025,11 @@ function validate116(data, { instancePath = "", parentData, parentDataProperty, let data2 = data.success; const _errs5 = errors; if (typeof data2 !== "boolean") { - validate116.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate117.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data2) { - validate116.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate117.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs5 === errors; @@ -15004,242 +15040,951 @@ function validate116(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate116.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate117.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate117.errors = vErrors; + return errors === 0; +} +var validateSwitchVersionOutcome = validate120; +function validate120(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.success === void 0 && (missing0 = "success")) { + validate120.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!(key0 === "success")) { + validate120.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.success !== void 0) { + if (typeof data.success !== "boolean") { + validate120.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + return false; + } + } + } + } + } else { + validate120.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate120.errors = vErrors; + return errors === 0; +} +var validateSwitchVersionParams = validate121; +function validate121(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + const _errs0 = errors; + let valid0 = false; + const _errs1 = errors; + if (errors === _errs1) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.app_id === void 0 && (missing0 = "app_id") || data.tag === void 0 && (missing0 = "tag")) { + const err0 = { instancePath, schemaPath: "#/anyOf/0/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }; + if (vErrors === null) { + vErrors = [err0]; + } else { + vErrors.push(err0); + } + errors++; + } else { + const _errs3 = errors; + for (const key0 in data) { + if (!(key0 === "app_id" || key0 === "tag")) { + const err1 = { instancePath, schemaPath: "#/anyOf/0/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }; + if (vErrors === null) { + vErrors = [err1]; + } else { + vErrors.push(err1); + } + errors++; + break; + } + } + if (_errs3 === errors) { + if (data.app_id !== void 0) { + const _errs4 = errors; + if (typeof data.app_id !== "string") { + const err2 = { instancePath: instancePath + "/app_id", schemaPath: "#/anyOf/0/properties/app_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err2]; + } else { + vErrors.push(err2); + } + errors++; + } + var valid1 = _errs4 === errors; + } else { + var valid1 = true; + } + if (valid1) { + if (data.tag !== void 0) { + const _errs6 = errors; + if (typeof data.tag !== "string") { + const err3 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/0/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err3]; + } else { + vErrors.push(err3); + } + errors++; + } + var valid1 = _errs6 === errors; + } else { + var valid1 = true; + } + } + } + } + } else { + const err4 = { instancePath, schemaPath: "#/anyOf/0/type", keyword: "type", params: { type: "object" }, message: "must be object" }; + if (vErrors === null) { + vErrors = [err4]; + } else { + vErrors.push(err4); + } + errors++; + } + } + var _valid0 = _errs1 === errors; + valid0 = valid0 || _valid0; + if (!valid0) { + const _errs8 = errors; + if (errors === _errs8) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing1; + if (data.appId === void 0 && (missing1 = "appId") || data.tag === void 0 && (missing1 = "tag")) { + const err5 = { instancePath, schemaPath: "#/anyOf/1/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }; + if (vErrors === null) { + vErrors = [err5]; + } else { + vErrors.push(err5); + } + errors++; + } else { + const _errs10 = errors; + for (const key1 in data) { + if (!(key1 === "appId" || key1 === "tag")) { + const err6 = { instancePath, schemaPath: "#/anyOf/1/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }; + if (vErrors === null) { + vErrors = [err6]; + } else { + vErrors.push(err6); + } + errors++; + break; + } + } + if (_errs10 === errors) { + if (data.appId !== void 0) { + const _errs11 = errors; + if (typeof data.appId !== "string") { + const err7 = { instancePath: instancePath + "/appId", schemaPath: "#/anyOf/1/properties/appId/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err7]; + } else { + vErrors.push(err7); + } + errors++; + } + var valid2 = _errs11 === errors; + } else { + var valid2 = true; + } + if (valid2) { + if (data.tag !== void 0) { + const _errs13 = errors; + if (typeof data.tag !== "string") { + const err8 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/1/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err8]; + } else { + vErrors.push(err8); + } + errors++; + } + var valid2 = _errs13 === errors; + } else { + var valid2 = true; + } + } + } + } + } else { + const err9 = { instancePath, schemaPath: "#/anyOf/1/type", keyword: "type", params: { type: "object" }, message: "must be object" }; + if (vErrors === null) { + vErrors = [err9]; + } else { + vErrors.push(err9); + } + errors++; + } + } + var _valid0 = _errs8 === errors; + valid0 = valid0 || _valid0; + } + if (!valid0) { + const err10 = { instancePath, schemaPath: "#/anyOf", keyword: "anyOf", params: {}, message: "must match a schema in anyOf" }; + if (vErrors === null) { + vErrors = [err10]; + } else { + vErrors.push(err10); + } + errors++; + validate121.errors = vErrors; + return false; + } else { + errors = _errs0; + if (vErrors !== null) { + if (_errs0) { + vErrors.length = _errs0; + } else { + vErrors = null; + } + } + } + validate121.errors = vErrors; + return errors === 0; +} +var validateTorchDriverAvailability = validate122; +var schema136 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["available", "unavailable", "not_present", "unknown"], "title": "TorchDriverAvailability", "type": "string" }; +function validate122(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (typeof data !== "string") { + validate122.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data === "available" || data === "unavailable" || data === "not_present" || data === "unknown")) { + validate122.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema136.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + validate122.errors = vErrors; + return errors === 0; +} +var validateTorchGpuVendor = validate123; +var schema137 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["nvidia", "amd", "intel"], "title": "TorchGpuVendor", "type": "string" }; +function validate123(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (typeof data !== "string") { + validate123.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data === "nvidia" || data === "amd" || data === "intel")) { + validate123.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema137.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + validate123.errors = vErrors; + return errors === 0; +} +var validateTorchReleaseCombination = validate124; +var schema138 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "build": { "type": "string" }, "python": { "type": "string" }, "sha256": { "type": ["string", "null"] }, "wheelUrl": { "type": "string" } }, "required": ["build", "python", "wheelUrl"], "title": "TorchReleaseCombination", "type": "object" }; +function validate124(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.build === void 0 && (missing0 = "build") || data.python === void 0 && (missing0 = "python") || data.wheelUrl === void 0 && (missing0 = "wheelUrl")) { + validate124.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!(key0 === "build" || key0 === "python" || key0 === "sha256" || key0 === "wheelUrl")) { + validate124.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.build !== void 0) { + const _errs2 = errors; + if (typeof data.build !== "string") { + validate124.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid0 = _errs2 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.python !== void 0) { + const _errs4 = errors; + if (typeof data.python !== "string") { + validate124.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid0 = _errs4 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.sha256 !== void 0) { + let data2 = data.sha256; + const _errs6 = errors; + if (typeof data2 !== "string" && data2 !== null) { + validate124.errors = [{ instancePath: instancePath + "/sha256", schemaPath: "#/properties/sha256/type", keyword: "type", params: { type: schema138.properties.sha256.type }, message: "must be string,null" }]; + return false; + } + var valid0 = _errs6 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.wheelUrl !== void 0) { + const _errs8 = errors; + if (typeof data.wheelUrl !== "string") { + validate124.errors = [{ instancePath: instancePath + "/wheelUrl", schemaPath: "#/properties/wheelUrl/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid0 = _errs8 === errors; + } else { + var valid0 = true; + } + } + } + } + } + } + } else { + validate124.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate124.errors = vErrors; + return errors === 0; +} +var validateTorchReleaseDriverStatus = validate125; +var schema140 = { "enum": ["available", "unavailable", "not_present", "unknown"], "type": "string" }; +function validate125(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.nvidia === void 0 && (missing0 = "nvidia") || data.amd === void 0 && (missing0 = "amd")) { + validate125.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!(key0 === "amd" || key0 === "nvidia")) { + validate125.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.amd !== void 0) { + let data0 = data.amd; + const _errs2 = errors; + if (typeof data0 !== "string") { + validate125.errors = [{ instancePath: instancePath + "/amd", schemaPath: "#/definitions/TorchDriverAvailability/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data0 === "available" || data0 === "unavailable" || data0 === "not_present" || data0 === "unknown")) { + validate125.errors = [{ instancePath: instancePath + "/amd", schemaPath: "#/definitions/TorchDriverAvailability/enum", keyword: "enum", params: { allowedValues: schema140.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid0 = _errs2 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.nvidia !== void 0) { + let data1 = data.nvidia; + const _errs5 = errors; + if (typeof data1 !== "string") { + validate125.errors = [{ instancePath: instancePath + "/nvidia", schemaPath: "#/definitions/TorchDriverAvailability/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data1 === "available" || data1 === "unavailable" || data1 === "not_present" || data1 === "unknown")) { + validate125.errors = [{ instancePath: instancePath + "/nvidia", schemaPath: "#/definitions/TorchDriverAvailability/enum", keyword: "enum", params: { allowedValues: schema140.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid0 = _errs5 === errors; + } else { + var valid0 = true; + } + } + } + } + } else { + validate125.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate125.errors = vErrors; + return errors === 0; +} +var validateTorchReleaseOptionsOutcome = validate126; +var schema142 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { "TorchDriverAvailability": { "enum": ["available", "unavailable", "not_present", "unknown"], "type": "string" }, "TorchGpuVendor": { "enum": ["nvidia", "amd", "intel"], "type": "string" }, "TorchReleaseCombination": { "additionalProperties": false, "properties": { "build": { "type": "string" }, "python": { "type": "string" }, "sha256": { "type": ["string", "null"] }, "wheelUrl": { "type": "string" } }, "required": ["build", "python", "wheelUrl"], "type": "object" }, "TorchReleaseDriverStatus": { "additionalProperties": false, "properties": { "amd": { "$ref": "#/definitions/TorchDriverAvailability" }, "nvidia": { "$ref": "#/definitions/TorchDriverAvailability" } }, "required": ["nvidia", "amd"], "type": "object" }, "TorchReleaseOptionsStatus": { "enum": ["matches", "none", "inconclusive"], "type": "string" }, "TorchReleaseRecommendation": { "additionalProperties": false, "properties": { "build": { "type": "string" }, "python": { "type": "string" } }, "required": ["build", "python"], "type": "object" } }, "properties": { "checkedChannels": { "items": { "type": "string" }, "type": "array" }, "combinations": { "items": { "$ref": "#/definitions/TorchReleaseCombination" }, "type": "array" }, "completeScan": { "type": "boolean" }, "detectedGpuVendors": { "items": { "$ref": "#/definitions/TorchGpuVendor" }, "type": "array" }, "driverStatus": { "$ref": "#/definitions/TorchReleaseDriverStatus" }, "issues": { "items": { "type": "string" }, "type": "array" }, "recommendationNote": { "type": ["string", "null"] }, "recommended": { "anyOf": [{ "$ref": "#/definitions/TorchReleaseRecommendation" }, { "type": "null" }] }, "status": { "$ref": "#/definitions/TorchReleaseOptionsStatus" }, "tag": { "type": "string" } }, "required": ["tag", "status", "completeScan", "checkedChannels", "combinations", "issues", "detectedGpuVendors", "driverStatus", "recommended", "recommendationNote"], "title": "TorchReleaseOptionsOutcome", "type": "object" }; +var schema143 = { "additionalProperties": false, "properties": { "build": { "type": "string" }, "python": { "type": "string" }, "sha256": { "type": ["string", "null"] }, "wheelUrl": { "type": "string" } }, "required": ["build", "python", "wheelUrl"], "type": "object" }; +var schema144 = { "enum": ["nvidia", "amd", "intel"], "type": "string" }; +var schema149 = { "enum": ["matches", "none", "inconclusive"], "type": "string" }; +var schema146 = { "enum": ["available", "unavailable", "not_present", "unknown"], "type": "string" }; +function validate127(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.nvidia === void 0 && (missing0 = "nvidia") || data.amd === void 0 && (missing0 = "amd")) { + validate127.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!(key0 === "amd" || key0 === "nvidia")) { + validate127.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.amd !== void 0) { + let data0 = data.amd; + const _errs2 = errors; + if (typeof data0 !== "string") { + validate127.errors = [{ instancePath: instancePath + "/amd", schemaPath: "#/definitions/TorchDriverAvailability/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data0 === "available" || data0 === "unavailable" || data0 === "not_present" || data0 === "unknown")) { + validate127.errors = [{ instancePath: instancePath + "/amd", schemaPath: "#/definitions/TorchDriverAvailability/enum", keyword: "enum", params: { allowedValues: schema146.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid0 = _errs2 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.nvidia !== void 0) { + let data1 = data.nvidia; + const _errs5 = errors; + if (typeof data1 !== "string") { + validate127.errors = [{ instancePath: instancePath + "/nvidia", schemaPath: "#/definitions/TorchDriverAvailability/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data1 === "available" || data1 === "unavailable" || data1 === "not_present" || data1 === "unknown")) { + validate127.errors = [{ instancePath: instancePath + "/nvidia", schemaPath: "#/definitions/TorchDriverAvailability/enum", keyword: "enum", params: { allowedValues: schema146.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid0 = _errs5 === errors; + } else { + var valid0 = true; + } + } + } + } + } else { + validate127.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + validate127.errors = vErrors; + return errors === 0; +} +function validate126(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (errors === 0) { + if (data && typeof data == "object" && !Array.isArray(data)) { + let missing0; + if (data.tag === void 0 && (missing0 = "tag") || data.status === void 0 && (missing0 = "status") || data.completeScan === void 0 && (missing0 = "completeScan") || data.checkedChannels === void 0 && (missing0 = "checkedChannels") || data.combinations === void 0 && (missing0 = "combinations") || data.issues === void 0 && (missing0 = "issues") || data.detectedGpuVendors === void 0 && (missing0 = "detectedGpuVendors") || data.driverStatus === void 0 && (missing0 = "driverStatus") || data.recommended === void 0 && (missing0 = "recommended") || data.recommendationNote === void 0 && (missing0 = "recommendationNote")) { + validate126.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + return false; + } else { + const _errs1 = errors; + for (const key0 in data) { + if (!func2.call(schema142.properties, key0)) { + validate126.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs1 === errors) { + if (data.checkedChannels !== void 0) { + let data0 = data.checkedChannels; + const _errs2 = errors; + if (errors === _errs2) { + if (Array.isArray(data0)) { + var valid1 = true; + const len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + const _errs4 = errors; + if (typeof data0[i0] !== "string") { + validate126.errors = [{ instancePath: instancePath + "/checkedChannels/" + i0, schemaPath: "#/properties/checkedChannels/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid1 = _errs4 === errors; + if (!valid1) { + break; + } + } + } else { + validate126.errors = [{ instancePath: instancePath + "/checkedChannels", schemaPath: "#/properties/checkedChannels/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + return false; + } + } + var valid0 = _errs2 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.combinations !== void 0) { + let data2 = data.combinations; + const _errs6 = errors; + if (errors === _errs6) { + if (Array.isArray(data2)) { + var valid2 = true; + const len1 = data2.length; + for (let i1 = 0; i1 < len1; i1++) { + let data3 = data2[i1]; + const _errs8 = errors; + const _errs9 = errors; + if (errors === _errs9) { + if (data3 && typeof data3 == "object" && !Array.isArray(data3)) { + let missing1; + if (data3.build === void 0 && (missing1 = "build") || data3.python === void 0 && (missing1 = "python") || data3.wheelUrl === void 0 && (missing1 = "wheelUrl")) { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1, schemaPath: "#/definitions/TorchReleaseCombination/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + return false; + } else { + const _errs11 = errors; + for (const key1 in data3) { + if (!(key1 === "build" || key1 === "python" || key1 === "sha256" || key1 === "wheelUrl")) { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1, schemaPath: "#/definitions/TorchReleaseCombination/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + return false; + break; + } + } + if (_errs11 === errors) { + if (data3.build !== void 0) { + const _errs12 = errors; + if (typeof data3.build !== "string") { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1 + "/build", schemaPath: "#/definitions/TorchReleaseCombination/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid4 = _errs12 === errors; + } else { + var valid4 = true; + } + if (valid4) { + if (data3.python !== void 0) { + const _errs14 = errors; + if (typeof data3.python !== "string") { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1 + "/python", schemaPath: "#/definitions/TorchReleaseCombination/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid4 = _errs14 === errors; + } else { + var valid4 = true; + } + if (valid4) { + if (data3.sha256 !== void 0) { + let data6 = data3.sha256; + const _errs16 = errors; + if (typeof data6 !== "string" && data6 !== null) { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1 + "/sha256", schemaPath: "#/definitions/TorchReleaseCombination/properties/sha256/type", keyword: "type", params: { type: schema143.properties.sha256.type }, message: "must be string,null" }]; + return false; + } + var valid4 = _errs16 === errors; + } else { + var valid4 = true; + } + if (valid4) { + if (data3.wheelUrl !== void 0) { + const _errs18 = errors; + if (typeof data3.wheelUrl !== "string") { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1 + "/wheelUrl", schemaPath: "#/definitions/TorchReleaseCombination/properties/wheelUrl/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid4 = _errs18 === errors; + } else { + var valid4 = true; + } + } + } + } + } + } + } else { + validate126.errors = [{ instancePath: instancePath + "/combinations/" + i1, schemaPath: "#/definitions/TorchReleaseCombination/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; + } + } + var valid2 = _errs8 === errors; + if (!valid2) { + break; + } + } + } else { + validate126.errors = [{ instancePath: instancePath + "/combinations", schemaPath: "#/properties/combinations/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + return false; + } + } + var valid0 = _errs6 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.completeScan !== void 0) { + const _errs20 = errors; + if (typeof data.completeScan !== "boolean") { + validate126.errors = [{ instancePath: instancePath + "/completeScan", schemaPath: "#/properties/completeScan/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + return false; + } + var valid0 = _errs20 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.detectedGpuVendors !== void 0) { + let data9 = data.detectedGpuVendors; + const _errs22 = errors; + if (errors === _errs22) { + if (Array.isArray(data9)) { + var valid5 = true; + const len2 = data9.length; + for (let i2 = 0; i2 < len2; i2++) { + let data10 = data9[i2]; + const _errs24 = errors; + if (typeof data10 !== "string") { + validate126.errors = [{ instancePath: instancePath + "/detectedGpuVendors/" + i2, schemaPath: "#/definitions/TorchGpuVendor/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data10 === "nvidia" || data10 === "amd" || data10 === "intel")) { + validate126.errors = [{ instancePath: instancePath + "/detectedGpuVendors/" + i2, schemaPath: "#/definitions/TorchGpuVendor/enum", keyword: "enum", params: { allowedValues: schema144.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid5 = _errs24 === errors; + if (!valid5) { + break; + } + } + } else { + validate126.errors = [{ instancePath: instancePath + "/detectedGpuVendors", schemaPath: "#/properties/detectedGpuVendors/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + return false; + } + } + var valid0 = _errs22 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.driverStatus !== void 0) { + const _errs27 = errors; + if (!validate127(data.driverStatus, { instancePath: instancePath + "/driverStatus", parentData: data, parentDataProperty: "driverStatus", rootData })) { + vErrors = vErrors === null ? validate127.errors : vErrors.concat(validate127.errors); + errors = vErrors.length; + } + var valid0 = _errs27 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.issues !== void 0) { + let data12 = data.issues; + const _errs28 = errors; + if (errors === _errs28) { + if (Array.isArray(data12)) { + var valid7 = true; + const len3 = data12.length; + for (let i3 = 0; i3 < len3; i3++) { + const _errs30 = errors; + if (typeof data12[i3] !== "string") { + validate126.errors = [{ instancePath: instancePath + "/issues/" + i3, schemaPath: "#/properties/issues/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid7 = _errs30 === errors; + if (!valid7) { + break; + } + } + } else { + validate126.errors = [{ instancePath: instancePath + "/issues", schemaPath: "#/properties/issues/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + return false; + } + } + var valid0 = _errs28 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.recommendationNote !== void 0) { + let data14 = data.recommendationNote; + const _errs32 = errors; + if (typeof data14 !== "string" && data14 !== null) { + validate126.errors = [{ instancePath: instancePath + "/recommendationNote", schemaPath: "#/properties/recommendationNote/type", keyword: "type", params: { type: schema142.properties.recommendationNote.type }, message: "must be string,null" }]; + return false; + } + var valid0 = _errs32 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.recommended !== void 0) { + let data15 = data.recommended; + const _errs34 = errors; + const _errs35 = errors; + let valid8 = false; + const _errs36 = errors; + const _errs37 = errors; + if (errors === _errs37) { + if (data15 && typeof data15 == "object" && !Array.isArray(data15)) { + let missing2; + if (data15.build === void 0 && (missing2 = "build") || data15.python === void 0 && (missing2 = "python")) { + const err0 = { instancePath: instancePath + "/recommended", schemaPath: "#/definitions/TorchReleaseRecommendation/required", keyword: "required", params: { missingProperty: missing2 }, message: "must have required property '" + missing2 + "'" }; + if (vErrors === null) { + vErrors = [err0]; + } else { + vErrors.push(err0); + } + errors++; + } else { + const _errs39 = errors; + for (const key2 in data15) { + if (!(key2 === "build" || key2 === "python")) { + const err1 = { instancePath: instancePath + "/recommended", schemaPath: "#/definitions/TorchReleaseRecommendation/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key2 }, message: "must NOT have additional properties" }; + if (vErrors === null) { + vErrors = [err1]; + } else { + vErrors.push(err1); + } + errors++; + break; + } + } + if (_errs39 === errors) { + if (data15.build !== void 0) { + const _errs40 = errors; + if (typeof data15.build !== "string") { + const err2 = { instancePath: instancePath + "/recommended/build", schemaPath: "#/definitions/TorchReleaseRecommendation/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err2]; + } else { + vErrors.push(err2); + } + errors++; + } + var valid10 = _errs40 === errors; + } else { + var valid10 = true; + } + if (valid10) { + if (data15.python !== void 0) { + const _errs42 = errors; + if (typeof data15.python !== "string") { + const err3 = { instancePath: instancePath + "/recommended/python", schemaPath: "#/definitions/TorchReleaseRecommendation/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }; + if (vErrors === null) { + vErrors = [err3]; + } else { + vErrors.push(err3); + } + errors++; + } + var valid10 = _errs42 === errors; + } else { + var valid10 = true; + } + } + } + } + } else { + const err4 = { instancePath: instancePath + "/recommended", schemaPath: "#/definitions/TorchReleaseRecommendation/type", keyword: "type", params: { type: "object" }, message: "must be object" }; + if (vErrors === null) { + vErrors = [err4]; + } else { + vErrors.push(err4); + } + errors++; + } + } + var _valid0 = _errs36 === errors; + valid8 = valid8 || _valid0; + if (!valid8) { + const _errs44 = errors; + if (data15 !== null) { + const err5 = { instancePath: instancePath + "/recommended", schemaPath: "#/properties/recommended/anyOf/1/type", keyword: "type", params: { type: "null" }, message: "must be null" }; + if (vErrors === null) { + vErrors = [err5]; + } else { + vErrors.push(err5); + } + errors++; + } + var _valid0 = _errs44 === errors; + valid8 = valid8 || _valid0; + } + if (!valid8) { + const err6 = { instancePath: instancePath + "/recommended", schemaPath: "#/properties/recommended/anyOf", keyword: "anyOf", params: {}, message: "must match a schema in anyOf" }; + if (vErrors === null) { + vErrors = [err6]; + } else { + vErrors.push(err6); + } + errors++; + validate126.errors = vErrors; + return false; + } else { + errors = _errs35; + if (vErrors !== null) { + if (_errs35) { + vErrors.length = _errs35; + } else { + vErrors = null; + } + } + } + var valid0 = _errs34 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.status !== void 0) { + let data18 = data.status; + const _errs46 = errors; + if (typeof data18 !== "string") { + validate126.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/TorchReleaseOptionsStatus/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data18 === "matches" || data18 === "none" || data18 === "inconclusive")) { + validate126.errors = [{ instancePath: instancePath + "/status", schemaPath: "#/definitions/TorchReleaseOptionsStatus/enum", keyword: "enum", params: { allowedValues: schema149.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + var valid0 = _errs46 === errors; + } else { + var valid0 = true; + } + if (valid0) { + if (data.tag !== void 0) { + const _errs49 = errors; + if (typeof data.tag !== "string") { + validate126.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + var valid0 = _errs49 === errors; + } else { + var valid0 = true; + } + } + } + } + } + } + } + } + } + } + } + } + } else { + validate126.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate116.errors = vErrors; + validate126.errors = vErrors; + return errors === 0; +} +var validateTorchReleaseOptionsStatus = validate129; +var schema150 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["matches", "none", "inconclusive"], "title": "TorchReleaseOptionsStatus", "type": "string" }; +function validate129(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { + let vErrors = null; + let errors = 0; + if (typeof data !== "string") { + validate129.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; + } + if (!(data === "matches" || data === "none" || data === "inconclusive")) { + validate129.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema150.enum }, message: "must be equal to one of the allowed values" }]; + return false; + } + validate129.errors = vErrors; return errors === 0; } -var validateSwitchVersionOutcome = validate119; -function validate119(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchReleaseRecommendation = validate130; +function validate130(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; - if (data.success === void 0 && (missing0 = "success")) { - validate119.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + if (data.build === void 0 && (missing0 = "build") || data.python === void 0 && (missing0 = "python")) { + validate130.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { - if (!(key0 === "success")) { - validate119.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + if (!(key0 === "build" || key0 === "python")) { + validate130.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } } if (_errs1 === errors) { - if (data.success !== void 0) { - if (typeof data.success !== "boolean") { - validate119.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + if (data.build !== void 0) { + const _errs2 = errors; + if (typeof data.build !== "string") { + validate130.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } - } - } - } - } else { - validate119.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; - return false; - } - } - validate119.errors = vErrors; - return errors === 0; -} -var validateSwitchVersionParams = validate120; -function validate120(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { - let vErrors = null; - let errors = 0; - const _errs0 = errors; - let valid0 = false; - const _errs1 = errors; - if (errors === _errs1) { - if (data && typeof data == "object" && !Array.isArray(data)) { - let missing0; - if (data.app_id === void 0 && (missing0 = "app_id") || data.tag === void 0 && (missing0 = "tag")) { - const err0 = { instancePath, schemaPath: "#/anyOf/0/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }; - if (vErrors === null) { - vErrors = [err0]; - } else { - vErrors.push(err0); - } - errors++; - } else { - const _errs3 = errors; - for (const key0 in data) { - if (!(key0 === "app_id" || key0 === "tag")) { - const err1 = { instancePath, schemaPath: "#/anyOf/0/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }; - if (vErrors === null) { - vErrors = [err1]; - } else { - vErrors.push(err1); - } - errors++; - break; - } - } - if (_errs3 === errors) { - if (data.app_id !== void 0) { - const _errs4 = errors; - if (typeof data.app_id !== "string") { - const err2 = { instancePath: instancePath + "/app_id", schemaPath: "#/anyOf/0/properties/app_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }; - if (vErrors === null) { - vErrors = [err2]; - } else { - vErrors.push(err2); - } - errors++; - } - var valid1 = _errs4 === errors; - } else { - var valid1 = true; - } - if (valid1) { - if (data.tag !== void 0) { - const _errs6 = errors; - if (typeof data.tag !== "string") { - const err3 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/0/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }; - if (vErrors === null) { - vErrors = [err3]; - } else { - vErrors.push(err3); - } - errors++; - } - var valid1 = _errs6 === errors; - } else { - var valid1 = true; - } - } - } - } - } else { - const err4 = { instancePath, schemaPath: "#/anyOf/0/type", keyword: "type", params: { type: "object" }, message: "must be object" }; - if (vErrors === null) { - vErrors = [err4]; - } else { - vErrors.push(err4); - } - errors++; - } - } - var _valid0 = _errs1 === errors; - valid0 = valid0 || _valid0; - if (!valid0) { - const _errs8 = errors; - if (errors === _errs8) { - if (data && typeof data == "object" && !Array.isArray(data)) { - let missing1; - if (data.appId === void 0 && (missing1 = "appId") || data.tag === void 0 && (missing1 = "tag")) { - const err5 = { instancePath, schemaPath: "#/anyOf/1/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }; - if (vErrors === null) { - vErrors = [err5]; + var valid0 = _errs2 === errors; } else { - vErrors.push(err5); - } - errors++; - } else { - const _errs10 = errors; - for (const key1 in data) { - if (!(key1 === "appId" || key1 === "tag")) { - const err6 = { instancePath, schemaPath: "#/anyOf/1/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }; - if (vErrors === null) { - vErrors = [err6]; - } else { - vErrors.push(err6); - } - errors++; - break; - } + var valid0 = true; } - if (_errs10 === errors) { - if (data.appId !== void 0) { - const _errs11 = errors; - if (typeof data.appId !== "string") { - const err7 = { instancePath: instancePath + "/appId", schemaPath: "#/anyOf/1/properties/appId/type", keyword: "type", params: { type: "string" }, message: "must be string" }; - if (vErrors === null) { - vErrors = [err7]; - } else { - vErrors.push(err7); - } - errors++; + if (valid0) { + if (data.python !== void 0) { + const _errs4 = errors; + if (typeof data.python !== "string") { + validate130.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + return false; } - var valid2 = _errs11 === errors; + var valid0 = _errs4 === errors; } else { - var valid2 = true; - } - if (valid2) { - if (data.tag !== void 0) { - const _errs13 = errors; - if (typeof data.tag !== "string") { - const err8 = { instancePath: instancePath + "/tag", schemaPath: "#/anyOf/1/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }; - if (vErrors === null) { - vErrors = [err8]; - } else { - vErrors.push(err8); - } - errors++; - } - var valid2 = _errs13 === errors; - } else { - var valid2 = true; - } + var valid0 = true; } } } - } else { - const err9 = { instancePath, schemaPath: "#/anyOf/1/type", keyword: "type", params: { type: "object" }, message: "must be object" }; - if (vErrors === null) { - vErrors = [err9]; - } else { - vErrors.push(err9); - } - errors++; } - } - var _valid0 = _errs8 === errors; - valid0 = valid0 || _valid0; - } - if (!valid0) { - const err10 = { instancePath, schemaPath: "#/anyOf", keyword: "anyOf", params: {}, message: "must match a schema in anyOf" }; - if (vErrors === null) { - vErrors = [err10]; } else { - vErrors.push(err10); - } - errors++; - validate120.errors = vErrors; - return false; - } else { - errors = _errs0; - if (vErrors !== null) { - if (_errs0) { - vErrors.length = _errs0; - } else { - vErrors = null; - } + validate130.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + return false; } } - validate120.errors = vErrors; + validate130.errors = vErrors; return errors === 0; } -var validateTorchRuntimePreview = validate121; -var schema137 = { "enum": ["qualified", "unverified"], "type": "string" }; -function validate121(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchRuntimePreview = validate131; +var schema154 = { "enum": ["qualified", "unverified"], "type": "string" }; +function validate131(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.previewId === void 0 && (missing0 = "previewId") || data.tag === void 0 && (missing0 = "tag") || data.build === void 0 && (missing0 = "build") || data.python === void 0 && (missing0 = "python") || data.adapter === void 0 && (missing0 = "adapter") || data.artifacts === void 0 && (missing0 = "artifacts") || data.qualification === void 0 && (missing0 = "qualification") || data.expiresInSeconds === void 0 && (missing0 = "expiresInSeconds")) { - validate121.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate131.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "adapter" || key0 === "artifacts" || key0 === "build" || key0 === "expiresInSeconds" || key0 === "previewId" || key0 === "python" || key0 === "qualification" || key0 === "tag")) { - validate121.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate131.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -15248,7 +15993,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data.adapter !== void 0) { const _errs2 = errors; if (typeof data.adapter !== "string") { - validate121.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -15271,13 +16016,13 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data2 && typeof data2 == "object" && !Array.isArray(data2)) { let missing1; if (data2.name === void 0 && (missing1 = "name") || data2.version === void 0 && (missing1 = "version") || data2.url === void 0 && (missing1 = "url") || data2.sha256 === void 0 && (missing1 = "sha256")) { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs9 = errors; for (const key1 in data2) { if (!(key1 === "name" || key1 === "sha256" || key1 === "url" || key1 === "version")) { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -15286,7 +16031,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data2.name !== void 0) { const _errs10 = errors; if (typeof data2.name !== "string") { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/name", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/name", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs10 === errors; @@ -15297,7 +16042,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data2.sha256 !== void 0) { const _errs12 = errors; if (typeof data2.sha256 !== "string") { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/sha256", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/sha256", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs12 === errors; @@ -15308,7 +16053,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data2.url !== void 0) { const _errs14 = errors; if (typeof data2.url !== "string") { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/url", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/url", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs14 === errors; @@ -15319,7 +16064,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data2.version !== void 0) { const _errs16 = errors; if (typeof data2.version !== "string") { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/version", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/version", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs16 === errors; @@ -15332,7 +16077,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate121.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -15342,7 +16087,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate121.errors = [{ instancePath: instancePath + "/artifacts", schemaPath: "#/properties/artifacts/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate131.errors = [{ instancePath: instancePath + "/artifacts", schemaPath: "#/properties/artifacts/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -15354,7 +16099,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data.build !== void 0) { const _errs18 = errors; if (typeof data.build !== "string") { - validate121.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs18 === errors; @@ -15366,17 +16111,17 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, let data8 = data.expiresInSeconds; const _errs20 = errors; if (!(typeof data8 == "number" && (!(data8 % 1) && !isNaN(data8)) && isFinite(data8))) { - validate121.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate131.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs20) { if (typeof data8 == "number" && isFinite(data8)) { if (data8 > 9007199254740991 || isNaN(data8)) { - validate121.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate131.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data8 < 0 || isNaN(data8)) { - validate121.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate131.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -15390,7 +16135,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data.previewId !== void 0) { const _errs22 = errors; if (typeof data.previewId !== "string") { - validate121.errors = [{ instancePath: instancePath + "/previewId", schemaPath: "#/properties/previewId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/previewId", schemaPath: "#/properties/previewId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs22 === errors; @@ -15401,7 +16146,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data.python !== void 0) { const _errs24 = errors; if (typeof data.python !== "string") { - validate121.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs24 === errors; @@ -15413,11 +16158,11 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, let data11 = data.qualification; const _errs26 = errors; if (typeof data11 !== "string") { - validate121.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data11 === "qualified" || data11 === "unverified")) { - validate121.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/enum", keyword: "enum", params: { allowedValues: schema137.enum }, message: "must be equal to one of the allowed values" }]; + validate131.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/enum", keyword: "enum", params: { allowedValues: schema154.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs26 === errors; @@ -15428,7 +16173,7 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, if (data.tag !== void 0) { const _errs29 = errors; if (typeof data.tag !== "string") { - validate121.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate131.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs29 === errors; @@ -15445,28 +16190,28 @@ function validate121(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate121.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate131.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate121.errors = vErrors; + validate131.errors = vErrors; return errors === 0; } -var validateTorchRuntimePreviewArtifact = validate122; -function validate122(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchRuntimePreviewArtifact = validate132; +function validate132(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.name === void 0 && (missing0 = "name") || data.version === void 0 && (missing0 = "version") || data.url === void 0 && (missing0 = "url") || data.sha256 === void 0 && (missing0 = "sha256")) { - validate122.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate132.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "name" || key0 === "sha256" || key0 === "url" || key0 === "version")) { - validate122.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate132.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -15475,7 +16220,7 @@ function validate122(data, { instancePath = "", parentData, parentDataProperty, if (data.name !== void 0) { const _errs2 = errors; if (typeof data.name !== "string") { - validate122.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate132.errors = [{ instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -15486,7 +16231,7 @@ function validate122(data, { instancePath = "", parentData, parentDataProperty, if (data.sha256 !== void 0) { const _errs4 = errors; if (typeof data.sha256 !== "string") { - validate122.errors = [{ instancePath: instancePath + "/sha256", schemaPath: "#/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate132.errors = [{ instancePath: instancePath + "/sha256", schemaPath: "#/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs4 === errors; @@ -15497,7 +16242,7 @@ function validate122(data, { instancePath = "", parentData, parentDataProperty, if (data.url !== void 0) { const _errs6 = errors; if (typeof data.url !== "string") { - validate122.errors = [{ instancePath: instancePath + "/url", schemaPath: "#/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate132.errors = [{ instancePath: instancePath + "/url", schemaPath: "#/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs6 === errors; @@ -15508,7 +16253,7 @@ function validate122(data, { instancePath = "", parentData, parentDataProperty, if (data.version !== void 0) { const _errs8 = errors; if (typeof data.version !== "string") { - validate122.errors = [{ instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate132.errors = [{ instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs8 === errors; @@ -15521,30 +16266,30 @@ function validate122(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate122.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate132.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate122.errors = vErrors; + validate132.errors = vErrors; return errors === 0; } -var validateTorchRuntimePreviewOutcome = validate123; -var schema143 = { "enum": ["unsupported", "validation_failed", "network_inconclusive", "inconclusive"], "type": "string" }; -var schema142 = { "enum": ["qualified", "unverified"], "type": "string" }; -function validate124(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchRuntimePreviewOutcome = validate133; +var schema160 = { "enum": ["unsupported", "validation_failed", "network_inconclusive", "inconclusive"], "type": "string" }; +var schema159 = { "enum": ["qualified", "unverified"], "type": "string" }; +function validate134(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.previewId === void 0 && (missing0 = "previewId") || data.tag === void 0 && (missing0 = "tag") || data.build === void 0 && (missing0 = "build") || data.python === void 0 && (missing0 = "python") || data.adapter === void 0 && (missing0 = "adapter") || data.artifacts === void 0 && (missing0 = "artifacts") || data.qualification === void 0 && (missing0 = "qualification") || data.expiresInSeconds === void 0 && (missing0 = "expiresInSeconds")) { - validate124.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate134.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "adapter" || key0 === "artifacts" || key0 === "build" || key0 === "expiresInSeconds" || key0 === "previewId" || key0 === "python" || key0 === "qualification" || key0 === "tag")) { - validate124.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate134.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -15553,7 +16298,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data.adapter !== void 0) { const _errs2 = errors; if (typeof data.adapter !== "string") { - validate124.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/adapter", schemaPath: "#/properties/adapter/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -15576,13 +16321,13 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data2 && typeof data2 == "object" && !Array.isArray(data2)) { let missing1; if (data2.name === void 0 && (missing1 = "name") || data2.version === void 0 && (missing1 = "version") || data2.url === void 0 && (missing1 = "url") || data2.sha256 === void 0 && (missing1 = "sha256")) { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs9 = errors; for (const key1 in data2) { if (!(key1 === "name" || key1 === "sha256" || key1 === "url" || key1 === "version")) { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -15591,7 +16336,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data2.name !== void 0) { const _errs10 = errors; if (typeof data2.name !== "string") { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/name", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/name", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/name/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs10 === errors; @@ -15602,7 +16347,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data2.sha256 !== void 0) { const _errs12 = errors; if (typeof data2.sha256 !== "string") { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/sha256", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/sha256", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/sha256/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs12 === errors; @@ -15613,7 +16358,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data2.url !== void 0) { const _errs14 = errors; if (typeof data2.url !== "string") { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/url", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/url", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/url/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs14 === errors; @@ -15624,7 +16369,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data2.version !== void 0) { const _errs16 = errors; if (typeof data2.version !== "string") { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/version", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0 + "/version", schemaPath: "#/definitions/TorchRuntimePreviewArtifact/properties/version/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs16 === errors; @@ -15637,7 +16382,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate124.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts/" + i0, schemaPath: "#/definitions/TorchRuntimePreviewArtifact/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -15647,7 +16392,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate124.errors = [{ instancePath: instancePath + "/artifacts", schemaPath: "#/properties/artifacts/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate134.errors = [{ instancePath: instancePath + "/artifacts", schemaPath: "#/properties/artifacts/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -15659,7 +16404,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data.build !== void 0) { const _errs18 = errors; if (typeof data.build !== "string") { - validate124.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/build", schemaPath: "#/properties/build/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs18 === errors; @@ -15671,17 +16416,17 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, let data8 = data.expiresInSeconds; const _errs20 = errors; if (!(typeof data8 == "number" && (!(data8 % 1) && !isNaN(data8)) && isFinite(data8))) { - validate124.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate134.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs20) { if (typeof data8 == "number" && isFinite(data8)) { if (data8 > 9007199254740991 || isNaN(data8)) { - validate124.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate134.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data8 < 0 || isNaN(data8)) { - validate124.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate134.errors = [{ instancePath: instancePath + "/expiresInSeconds", schemaPath: "#/properties/expiresInSeconds/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -15695,7 +16440,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data.previewId !== void 0) { const _errs22 = errors; if (typeof data.previewId !== "string") { - validate124.errors = [{ instancePath: instancePath + "/previewId", schemaPath: "#/properties/previewId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/previewId", schemaPath: "#/properties/previewId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs22 === errors; @@ -15706,7 +16451,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data.python !== void 0) { const _errs24 = errors; if (typeof data.python !== "string") { - validate124.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/python", schemaPath: "#/properties/python/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs24 === errors; @@ -15718,11 +16463,11 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, let data11 = data.qualification; const _errs26 = errors; if (typeof data11 !== "string") { - validate124.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data11 === "qualified" || data11 === "unverified")) { - validate124.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/enum", keyword: "enum", params: { allowedValues: schema142.enum }, message: "must be equal to one of the allowed values" }]; + validate134.errors = [{ instancePath: instancePath + "/qualification", schemaPath: "#/definitions/TorchRuntimePreviewQualification/enum", keyword: "enum", params: { allowedValues: schema159.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs26 === errors; @@ -15733,7 +16478,7 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, if (data.tag !== void 0) { const _errs29 = errors; if (typeof data.tag !== "string") { - validate124.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate134.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs29 === errors; @@ -15750,14 +16495,14 @@ function validate124(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate124.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate134.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate124.errors = vErrors; + validate134.errors = vErrors; return errors === 0; } -function validate123(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate133(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -15792,8 +16537,8 @@ function validate123(data, { instancePath = "", parentData, parentDataProperty, if (_errs3 === errors) { if (data.preview !== void 0) { const _errs4 = errors; - if (!validate124(data.preview, { instancePath: instancePath + "/preview", parentData: data, parentDataProperty: "preview", rootData })) { - vErrors = vErrors === null ? validate124.errors : vErrors.concat(validate124.errors); + if (!validate134(data.preview, { instancePath: instancePath + "/preview", parentData: data, parentDataProperty: "preview", rootData })) { + vErrors = vErrors === null ? validate134.errors : vErrors.concat(validate134.errors); errors = vErrors.length; } var valid1 = _errs4 === errors; @@ -15900,7 +16645,7 @@ function validate123(data, { instancePath = "", parentData, parentDataProperty, errors++; } if (!(data3 === "unsupported" || data3 === "validation_failed" || data3 === "network_inconclusive" || data3 === "inconclusive")) { - const err9 = { instancePath: instancePath + "/reason", schemaPath: "#/definitions/TorchRuntimePreviewRejectionReason/enum", keyword: "enum", params: { allowedValues: schema143.enum }, message: "must be equal to one of the allowed values" }; + const err9 = { instancePath: instancePath + "/reason", schemaPath: "#/definitions/TorchRuntimePreviewRejectionReason/enum", keyword: "enum", params: { allowedValues: schema160.enum }, message: "must be equal to one of the allowed values" }; if (vErrors === null) { vErrors = [err9]; } else { @@ -15970,7 +16715,7 @@ function validate123(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err13); } errors++; - validate123.errors = vErrors; + validate133.errors = vErrors; return false; } else { errors = _errs0; @@ -15982,57 +16727,57 @@ function validate123(data, { instancePath = "", parentData, parentDataProperty, } } } - validate123.errors = vErrors; + validate133.errors = vErrors; return errors === 0; } -var validateTorchRuntimePreviewQualification = validate126; -var schema144 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["qualified", "unverified"], "title": "TorchRuntimePreviewQualification", "type": "string" }; -function validate126(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchRuntimePreviewQualification = validate136; +var schema161 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["qualified", "unverified"], "title": "TorchRuntimePreviewQualification", "type": "string" }; +function validate136(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (typeof data !== "string") { - validate126.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate136.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data === "qualified" || data === "unverified")) { - validate126.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema144.enum }, message: "must be equal to one of the allowed values" }]; + validate136.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema161.enum }, message: "must be equal to one of the allowed values" }]; return false; } - validate126.errors = vErrors; + validate136.errors = vErrors; return errors === 0; } -var validateTorchRuntimePreviewRejectionReason = validate127; -var schema145 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["unsupported", "validation_failed", "network_inconclusive", "inconclusive"], "title": "TorchRuntimePreviewRejectionReason", "type": "string" }; -function validate127(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTorchRuntimePreviewRejectionReason = validate137; +var schema162 = { "$schema": "http://json-schema.org/draft-07/schema#", "enum": ["unsupported", "validation_failed", "network_inconclusive", "inconclusive"], "title": "TorchRuntimePreviewRejectionReason", "type": "string" }; +function validate137(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (typeof data !== "string") { - validate127.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate137.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data === "unsupported" || data === "validation_failed" || data === "network_inconclusive" || data === "inconclusive")) { - validate127.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema145.enum }, message: "must be equal to one of the allowed values" }]; + validate137.errors = [{ instancePath, schemaPath: "#/enum", keyword: "enum", params: { allowedValues: schema162.enum }, message: "must be equal to one of the allowed values" }]; return false; } - validate127.errors = vErrors; + validate137.errors = vErrors; return errors === 0; } -var validateTrialTorchRuntimeOutcome = validate128; -var schema146 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "capabilities": { "items": { "type": "string" }, "type": "array" }, "cleanup": { "type": "string" }, "error": { "type": ["string", "null"] }, "generation": { "type": ["string", "null"] }, "healthStatus": { "type": "string" }, "profileId": { "type": "string" }, "protocol": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "startedByTrial": { "type": "boolean" }, "startupStatus": { "type": "string" }, "success": { "type": "boolean" }, "tag": { "type": "string" } }, "required": ["success", "tag", "profileId", "startupStatus", "healthStatus", "protocol", "capabilities", "generation", "startedByTrial", "error", "cleanup"], "title": "TrialTorchRuntimeOutcome", "type": "object" }; -function validate128(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTrialTorchRuntimeOutcome = validate138; +var schema163 = { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "capabilities": { "items": { "type": "string" }, "type": "array" }, "cleanup": { "type": "string" }, "error": { "type": ["string", "null"] }, "generation": { "type": ["string", "null"] }, "healthStatus": { "type": "string" }, "profileId": { "type": "string" }, "protocol": { "maximum": 4294967295, "minimum": 0, "type": ["integer", "null"] }, "startedByTrial": { "type": "boolean" }, "startupStatus": { "type": "string" }, "success": { "type": "boolean" }, "tag": { "type": "string" } }, "required": ["success", "tag", "profileId", "startupStatus", "healthStatus", "protocol", "capabilities", "generation", "startedByTrial", "error", "cleanup"], "title": "TrialTorchRuntimeOutcome", "type": "object" }; +function validate138(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.tag === void 0 && (missing0 = "tag") || data.profileId === void 0 && (missing0 = "profileId") || data.startupStatus === void 0 && (missing0 = "startupStatus") || data.healthStatus === void 0 && (missing0 = "healthStatus") || data.protocol === void 0 && (missing0 = "protocol") || data.capabilities === void 0 && (missing0 = "capabilities") || data.generation === void 0 && (missing0 = "generation") || data.startedByTrial === void 0 && (missing0 = "startedByTrial") || data.error === void 0 && (missing0 = "error") || data.cleanup === void 0 && (missing0 = "cleanup")) { - validate128.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate138.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { - if (!func2.call(schema146.properties, key0)) { - validate128.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + if (!func2.call(schema163.properties, key0)) { + validate138.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -16048,7 +16793,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; if (typeof data0[i0] !== "string") { - validate128.errors = [{ instancePath: instancePath + "/capabilities/" + i0, schemaPath: "#/properties/capabilities/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/capabilities/" + i0, schemaPath: "#/properties/capabilities/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid1 = _errs4 === errors; @@ -16057,7 +16802,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate128.errors = [{ instancePath: instancePath + "/capabilities", schemaPath: "#/properties/capabilities/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate138.errors = [{ instancePath: instancePath + "/capabilities", schemaPath: "#/properties/capabilities/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -16069,7 +16814,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.cleanup !== void 0) { const _errs6 = errors; if (typeof data.cleanup !== "string") { - validate128.errors = [{ instancePath: instancePath + "/cleanup", schemaPath: "#/properties/cleanup/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/cleanup", schemaPath: "#/properties/cleanup/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs6 === errors; @@ -16081,7 +16826,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, let data3 = data.error; const _errs8 = errors; if (typeof data3 !== "string" && data3 !== null) { - validate128.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema146.properties.error.type }, message: "must be string,null" }]; + validate138.errors = [{ instancePath: instancePath + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: schema163.properties.error.type }, message: "must be string,null" }]; return false; } var valid0 = _errs8 === errors; @@ -16093,7 +16838,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, let data4 = data.generation; const _errs10 = errors; if (typeof data4 !== "string" && data4 !== null) { - validate128.errors = [{ instancePath: instancePath + "/generation", schemaPath: "#/properties/generation/type", keyword: "type", params: { type: schema146.properties.generation.type }, message: "must be string,null" }]; + validate138.errors = [{ instancePath: instancePath + "/generation", schemaPath: "#/properties/generation/type", keyword: "type", params: { type: schema163.properties.generation.type }, message: "must be string,null" }]; return false; } var valid0 = _errs10 === errors; @@ -16104,7 +16849,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.healthStatus !== void 0) { const _errs12 = errors; if (typeof data.healthStatus !== "string") { - validate128.errors = [{ instancePath: instancePath + "/healthStatus", schemaPath: "#/properties/healthStatus/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/healthStatus", schemaPath: "#/properties/healthStatus/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs12 === errors; @@ -16115,7 +16860,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.profileId !== void 0) { const _errs14 = errors; if (typeof data.profileId !== "string") { - validate128.errors = [{ instancePath: instancePath + "/profileId", schemaPath: "#/properties/profileId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/profileId", schemaPath: "#/properties/profileId/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs14 === errors; @@ -16127,17 +16872,17 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, let data7 = data.protocol; const _errs16 = errors; if (!(typeof data7 == "number" && (!(data7 % 1) && !isNaN(data7)) && isFinite(data7)) && data7 !== null) { - validate128.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/type", keyword: "type", params: { type: schema146.properties.protocol.type }, message: "must be integer,null" }]; + validate138.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/type", keyword: "type", params: { type: schema163.properties.protocol.type }, message: "must be integer,null" }]; return false; } if (errors === _errs16) { if (typeof data7 == "number" && isFinite(data7)) { if (data7 > 4294967295 || isNaN(data7)) { - validate128.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; + validate138.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/maximum", keyword: "maximum", params: { comparison: "<=", limit: 4294967295 }, message: "must be <= 4294967295" }]; return false; } else { if (data7 < 0 || isNaN(data7)) { - validate128.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate138.errors = [{ instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -16151,7 +16896,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.startedByTrial !== void 0) { const _errs18 = errors; if (typeof data.startedByTrial !== "boolean") { - validate128.errors = [{ instancePath: instancePath + "/startedByTrial", schemaPath: "#/properties/startedByTrial/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate138.errors = [{ instancePath: instancePath + "/startedByTrial", schemaPath: "#/properties/startedByTrial/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs18 === errors; @@ -16162,7 +16907,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.startupStatus !== void 0) { const _errs20 = errors; if (typeof data.startupStatus !== "string") { - validate128.errors = [{ instancePath: instancePath + "/startupStatus", schemaPath: "#/properties/startupStatus/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/startupStatus", schemaPath: "#/properties/startupStatus/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs20 === errors; @@ -16173,7 +16918,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.success !== void 0) { const _errs22 = errors; if (typeof data.success !== "boolean") { - validate128.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate138.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs22 === errors; @@ -16184,7 +16929,7 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, if (data.tag !== void 0) { const _errs24 = errors; if (typeof data.tag !== "string") { - validate128.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate138.errors = [{ instancePath: instancePath + "/tag", schemaPath: "#/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs24 === errors; @@ -16204,15 +16949,15 @@ function validate128(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate128.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate138.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate128.errors = vErrors; + validate138.errors = vErrors; return errors === 0; } -var validateTrialTorchRuntimeParams = validate129; -function validate129(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateTrialTorchRuntimeParams = validate139; +function validate139(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -16373,7 +17118,7 @@ function validate129(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err10); } errors++; - validate129.errors = vErrors; + validate139.errors = vErrors; return false; } else { errors = _errs0; @@ -16385,24 +17130,24 @@ function validate129(data, { instancePath = "", parentData, parentDataProperty, } } } - validate129.errors = vErrors; + validate139.errors = vErrors; return errors === 0; } -var validateUpdateInferenceSettingsOutcome = validate130; -function validate130(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateUpdateInferenceSettingsOutcome = validate140; +function validate140(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.model_id === void 0 && (missing0 = "model_id")) { - validate130.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate140.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "model_id" || key0 === "success")) { - validate130.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate140.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -16411,7 +17156,7 @@ function validate130(data, { instancePath = "", parentData, parentDataProperty, if (data.model_id !== void 0) { const _errs2 = errors; if (typeof data.model_id !== "string") { - validate130.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate140.errors = [{ instancePath: instancePath + "/model_id", schemaPath: "#/properties/model_id/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs2 === errors; @@ -16423,11 +17168,11 @@ function validate130(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.success; const _errs4 = errors; if (typeof data1 !== "boolean") { - validate130.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate140.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data1) { - validate130.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate140.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs4 === errors; @@ -16438,19 +17183,19 @@ function validate130(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate130.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate140.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate130.errors = vErrors; + validate140.errors = vErrors; return errors === 0; } -var validateUpdateInferenceSettingsParams = validate131; -var schema150 = { "additionalProperties": false, "properties": { "constraints": { "anyOf": [{ "$ref": "#/definitions/InferenceConstraintsInput" }, { "type": "null" }] }, "default": { "$ref": "#/definitions/DesktopJsonValue" }, "description": { "default": null, "type": ["string", "null"] }, "key": { "type": "string" }, "label": { "type": "string" }, "param_type": { "$ref": "#/definitions/ParamType" } }, "required": ["key", "label", "param_type", "default"], "type": "object" }; -var schema153 = { "description": "Data type for an inference parameter.", "enum": ["Number", "Integer", "String", "Boolean"], "type": "string" }; -var schema151 = { "additionalProperties": false, "properties": { "allowed_values": { "anyOf": [{ "type": "null" }, { "items": { "$ref": "#/definitions/DesktopJsonValue" }, "type": "array" }] }, "max": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "min": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] } }, "type": "object" }; -var wrapper4 = { validate: validate134 }; -function validate134(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateUpdateInferenceSettingsParams = validate141; +var schema167 = { "additionalProperties": false, "properties": { "constraints": { "anyOf": [{ "$ref": "#/definitions/InferenceConstraintsInput" }, { "type": "null" }] }, "default": { "$ref": "#/definitions/DesktopJsonValue" }, "description": { "default": null, "type": ["string", "null"] }, "key": { "type": "string" }, "label": { "type": "string" }, "param_type": { "$ref": "#/definitions/ParamType" } }, "required": ["key", "label", "param_type", "default"], "type": "object" }; +var schema170 = { "description": "Data type for an inference parameter.", "enum": ["Number", "Integer", "String", "Boolean"], "type": "string" }; +var schema168 = { "additionalProperties": false, "properties": { "allowed_values": { "anyOf": [{ "type": "null" }, { "items": { "$ref": "#/definitions/DesktopJsonValue" }, "type": "array" }] }, "max": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] }, "min": { "default": null, "maximum": 17976931348623157e292, "minimum": -17976931348623157e292, "type": ["number", "null"] } }, "type": "object" }; +var wrapper4 = { validate: validate144 }; +function validate144(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -16597,7 +17342,7 @@ function validate134(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err8); } errors++; - validate134.errors = vErrors; + validate144.errors = vErrors; return false; } else { errors = _errs0; @@ -16609,10 +17354,10 @@ function validate134(data, { instancePath = "", parentData, parentDataProperty, } } } - validate134.errors = vErrors; + validate144.errors = vErrors; return errors === 0; } -function validate133(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate143(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { @@ -16620,7 +17365,7 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, const _errs1 = errors; for (const key0 in data) { if (!(key0 === "allowed_values" || key0 === "max" || key0 === "min")) { - validate133.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate143.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -16651,8 +17396,8 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, const len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { const _errs8 = errors; - if (!validate134(data0[i0], { instancePath: instancePath + "/allowed_values/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate134.errors : vErrors.concat(validate134.errors); + if (!validate144(data0[i0], { instancePath: instancePath + "/allowed_values/" + i0, parentData: data0, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate144.errors : vErrors.concat(validate144.errors); errors = vErrors.length; } var valid2 = _errs8 === errors; @@ -16681,7 +17426,7 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err2); } errors++; - validate133.errors = vErrors; + validate143.errors = vErrors; return false; } else { errors = _errs3; @@ -16702,17 +17447,17 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, let data2 = data.max; const _errs9 = errors; if (!(typeof data2 == "number" && isFinite(data2)) && data2 !== null) { - validate133.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/type", keyword: "type", params: { type: schema151.properties.max.type }, message: "must be number,null" }]; + validate143.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/type", keyword: "type", params: { type: schema168.properties.max.type }, message: "must be number,null" }]; return false; } if (errors === _errs9) { if (typeof data2 == "number" && isFinite(data2)) { if (data2 > 17976931348623157e292 || isNaN(data2)) { - validate133.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate143.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data2 < -17976931348623157e292 || isNaN(data2)) { - validate133.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate143.errors = [{ instancePath: instancePath + "/max", schemaPath: "#/properties/max/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -16727,17 +17472,17 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, let data3 = data.min; const _errs11 = errors; if (!(typeof data3 == "number" && isFinite(data3)) && data3 !== null) { - validate133.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/type", keyword: "type", params: { type: schema151.properties.min.type }, message: "must be number,null" }]; + validate143.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/type", keyword: "type", params: { type: schema168.properties.min.type }, message: "must be number,null" }]; return false; } if (errors === _errs11) { if (typeof data3 == "number" && isFinite(data3)) { if (data3 > 17976931348623157e292 || isNaN(data3)) { - validate133.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; + validate143.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/maximum", keyword: "maximum", params: { comparison: "<=", limit: 17976931348623157e292 }, message: "must be <= 1.7976931348623157e+308" }]; return false; } else { if (data3 < -17976931348623157e292 || isNaN(data3)) { - validate133.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; + validate143.errors = [{ instancePath: instancePath + "/min", schemaPath: "#/properties/min/minimum", keyword: "minimum", params: { comparison: ">=", limit: -17976931348623157e292 }, message: "must be >= -1.7976931348623157e+308" }]; return false; } } @@ -16751,27 +17496,27 @@ function validate133(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate133.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate143.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate133.errors = vErrors; + validate143.errors = vErrors; return errors === 0; } -function validate132(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate142(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.key === void 0 && (missing0 = "key") || data.label === void 0 && (missing0 = "label") || data.param_type === void 0 && (missing0 = "param_type") || data.default === void 0 && (missing0 = "default")) { - validate132.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate142.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "constraints" || key0 === "default" || key0 === "description" || key0 === "key" || key0 === "label" || key0 === "param_type")) { - validate132.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate142.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -16783,8 +17528,8 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, const _errs3 = errors; let valid1 = false; const _errs4 = errors; - if (!validate133(data0, { instancePath: instancePath + "/constraints", parentData: data, parentDataProperty: "constraints", rootData })) { - vErrors = vErrors === null ? validate133.errors : vErrors.concat(validate133.errors); + if (!validate143(data0, { instancePath: instancePath + "/constraints", parentData: data, parentDataProperty: "constraints", rootData })) { + vErrors = vErrors === null ? validate143.errors : vErrors.concat(validate143.errors); errors = vErrors.length; } var _valid0 = _errs4 === errors; @@ -16811,7 +17556,7 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err1); } errors++; - validate132.errors = vErrors; + validate142.errors = vErrors; return false; } else { errors = _errs3; @@ -16830,8 +17575,8 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, if (valid0) { if (data.default !== void 0) { const _errs7 = errors; - if (!validate134(data.default, { instancePath: instancePath + "/default", parentData: data, parentDataProperty: "default", rootData })) { - vErrors = vErrors === null ? validate134.errors : vErrors.concat(validate134.errors); + if (!validate144(data.default, { instancePath: instancePath + "/default", parentData: data, parentDataProperty: "default", rootData })) { + vErrors = vErrors === null ? validate144.errors : vErrors.concat(validate144.errors); errors = vErrors.length; } var valid0 = _errs7 === errors; @@ -16843,7 +17588,7 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, let data2 = data.description; const _errs8 = errors; if (typeof data2 !== "string" && data2 !== null) { - validate132.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: schema150.properties.description.type }, message: "must be string,null" }]; + validate142.errors = [{ instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", params: { type: schema167.properties.description.type }, message: "must be string,null" }]; return false; } var valid0 = _errs8 === errors; @@ -16854,7 +17599,7 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, if (data.key !== void 0) { const _errs10 = errors; if (typeof data.key !== "string") { - validate132.errors = [{ instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate142.errors = [{ instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs10 === errors; @@ -16865,7 +17610,7 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, if (data.label !== void 0) { const _errs12 = errors; if (typeof data.label !== "string") { - validate132.errors = [{ instancePath: instancePath + "/label", schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate142.errors = [{ instancePath: instancePath + "/label", schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid0 = _errs12 === errors; @@ -16877,11 +17622,11 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, let data5 = data.param_type; const _errs14 = errors; if (typeof data5 !== "string") { - validate132.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate142.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } if (!(data5 === "Number" || data5 === "Integer" || data5 === "String" || data5 === "Boolean")) { - validate132.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/enum", keyword: "enum", params: { allowedValues: schema153.enum }, message: "must be equal to one of the allowed values" }]; + validate142.errors = [{ instancePath: instancePath + "/param_type", schemaPath: "#/definitions/ParamType/enum", keyword: "enum", params: { allowedValues: schema170.enum }, message: "must be equal to one of the allowed values" }]; return false; } var valid0 = _errs14 === errors; @@ -16896,14 +17641,14 @@ function validate132(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate132.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate142.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate132.errors = vErrors; + validate142.errors = vErrors; return errors === 0; } -function validate131(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate141(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -16960,8 +17705,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len0 = data1.length; for (let i0 = 0; i0 < len0; i0++) { const _errs8 = errors; - if (!validate132(data1[i0], { instancePath: instancePath + "/settings/" + i0, parentData: data1, parentDataProperty: i0, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data1[i0], { instancePath: instancePath + "/settings/" + i0, parentData: data1, parentDataProperty: i0, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid2 = _errs8 === errors; @@ -17035,8 +17780,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len1 = data3.length; for (let i1 = 0; i1 < len1; i1++) { const _errs14 = errors; - if (!validate132(data3[i1], { instancePath: instancePath + "/inference_settings/" + i1, parentData: data3, parentDataProperty: i1, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data3[i1], { instancePath: instancePath + "/inference_settings/" + i1, parentData: data3, parentDataProperty: i1, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid4 = _errs14 === errors; @@ -17126,8 +17871,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len2 = data6.length; for (let i2 = 0; i2 < len2; i2++) { const _errs22 = errors; - if (!validate132(data6[i2], { instancePath: instancePath + "/inferenceSettings/" + i2, parentData: data6, parentDataProperty: i2, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data6[i2], { instancePath: instancePath + "/inferenceSettings/" + i2, parentData: data6, parentDataProperty: i2, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid6 = _errs22 === errors; @@ -17233,8 +17978,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len3 = data10.length; for (let i3 = 0; i3 < len3; i3++) { const _errs32 = errors; - if (!validate132(data10[i3], { instancePath: instancePath + "/settings/" + i3, parentData: data10, parentDataProperty: i3, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data10[i3], { instancePath: instancePath + "/settings/" + i3, parentData: data10, parentDataProperty: i3, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid8 = _errs32 === errors; @@ -17308,8 +18053,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len4 = data12.length; for (let i4 = 0; i4 < len4; i4++) { const _errs38 = errors; - if (!validate132(data12[i4], { instancePath: instancePath + "/inference_settings/" + i4, parentData: data12, parentDataProperty: i4, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data12[i4], { instancePath: instancePath + "/inference_settings/" + i4, parentData: data12, parentDataProperty: i4, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid10 = _errs38 === errors; @@ -17399,8 +18144,8 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, const len5 = data15.length; for (let i5 = 0; i5 < len5; i5++) { const _errs46 = errors; - if (!validate132(data15[i5], { instancePath: instancePath + "/inferenceSettings/" + i5, parentData: data15, parentDataProperty: i5, rootData })) { - vErrors = vErrors === null ? validate132.errors : vErrors.concat(validate132.errors); + if (!validate142(data15[i5], { instancePath: instancePath + "/inferenceSettings/" + i5, parentData: data15, parentDataProperty: i5, rootData })) { + vErrors = vErrors === null ? validate142.errors : vErrors.concat(validate142.errors); errors = vErrors.length; } var valid12 = _errs46 === errors; @@ -17466,7 +18211,7 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err30); } errors++; - validate131.errors = vErrors; + validate141.errors = vErrors; return false; } else { errors = _errs0; @@ -17478,11 +18223,11 @@ function validate131(data, { instancePath = "", parentData, parentDataProperty, } } } - validate131.errors = vErrors; + validate141.errors = vErrors; return errors === 0; } -var validateUpdateModelNotesOutcome = validate144; -function validate144(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateUpdateModelNotesOutcome = validate154; +function validate154(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -17699,7 +18444,7 @@ function validate144(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err14); } errors++; - validate144.errors = vErrors; + validate154.errors = vErrors; return false; } else { errors = _errs0; @@ -17711,12 +18456,12 @@ function validate144(data, { instancePath = "", parentData, parentDataProperty, } } } - validate144.errors = vErrors; + validate154.errors = vErrors; return errors === 0; } -var validateUpdateModelNotesParams = validate145; -var schema157 = { "anyOf": [{ "additionalProperties": false, "properties": { "model_id": { "type": "string" }, "notes": { "default": null, "type": ["string", "null"] } }, "required": ["model_id"], "type": "object" }, { "additionalProperties": false, "properties": { "model_id": { "type": "string" }, "model_notes": { "default": null, "type": ["string", "null"] } }, "required": ["model_id"], "type": "object" }, { "additionalProperties": false, "properties": { "modelId": { "type": "string" }, "notes": { "default": null, "type": ["string", "null"] } }, "required": ["modelId"], "type": "object" }, { "additionalProperties": false, "properties": { "modelId": { "type": "string" }, "model_notes": { "default": null, "type": ["string", "null"] } }, "required": ["modelId"], "type": "object" }] }; -function validate145(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateUpdateModelNotesParams = validate155; +var schema174 = { "anyOf": [{ "additionalProperties": false, "properties": { "model_id": { "type": "string" }, "notes": { "default": null, "type": ["string", "null"] } }, "required": ["model_id"], "type": "object" }, { "additionalProperties": false, "properties": { "model_id": { "type": "string" }, "model_notes": { "default": null, "type": ["string", "null"] } }, "required": ["model_id"], "type": "object" }, { "additionalProperties": false, "properties": { "modelId": { "type": "string" }, "notes": { "default": null, "type": ["string", "null"] } }, "required": ["modelId"], "type": "object" }, { "additionalProperties": false, "properties": { "modelId": { "type": "string" }, "model_notes": { "default": null, "type": ["string", "null"] } }, "required": ["modelId"], "type": "object" }] }; +function validate155(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; const _errs0 = errors; @@ -17768,7 +18513,7 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.notes; const _errs6 = errors; if (typeof data1 !== "string" && data1 !== null) { - const err3 = { instancePath: instancePath + "/notes", schemaPath: "#/anyOf/0/properties/notes/type", keyword: "type", params: { type: schema157.anyOf[0].properties.notes.type }, message: "must be string,null" }; + const err3 = { instancePath: instancePath + "/notes", schemaPath: "#/anyOf/0/properties/notes/type", keyword: "type", params: { type: schema174.anyOf[0].properties.notes.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err3]; } else { @@ -17843,7 +18588,7 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, let data3 = data.model_notes; const _errs13 = errors; if (typeof data3 !== "string" && data3 !== null) { - const err8 = { instancePath: instancePath + "/model_notes", schemaPath: "#/anyOf/1/properties/model_notes/type", keyword: "type", params: { type: schema157.anyOf[1].properties.model_notes.type }, message: "must be string,null" }; + const err8 = { instancePath: instancePath + "/model_notes", schemaPath: "#/anyOf/1/properties/model_notes/type", keyword: "type", params: { type: schema174.anyOf[1].properties.model_notes.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err8]; } else { @@ -17918,7 +18663,7 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, let data5 = data.notes; const _errs20 = errors; if (typeof data5 !== "string" && data5 !== null) { - const err13 = { instancePath: instancePath + "/notes", schemaPath: "#/anyOf/2/properties/notes/type", keyword: "type", params: { type: schema157.anyOf[2].properties.notes.type }, message: "must be string,null" }; + const err13 = { instancePath: instancePath + "/notes", schemaPath: "#/anyOf/2/properties/notes/type", keyword: "type", params: { type: schema174.anyOf[2].properties.notes.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err13]; } else { @@ -17993,7 +18738,7 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, let data7 = data.model_notes; const _errs27 = errors; if (typeof data7 !== "string" && data7 !== null) { - const err18 = { instancePath: instancePath + "/model_notes", schemaPath: "#/anyOf/3/properties/model_notes/type", keyword: "type", params: { type: schema157.anyOf[3].properties.model_notes.type }, message: "must be string,null" }; + const err18 = { instancePath: instancePath + "/model_notes", schemaPath: "#/anyOf/3/properties/model_notes/type", keyword: "type", params: { type: schema174.anyOf[3].properties.model_notes.type }, message: "must be string,null" }; if (vErrors === null) { vErrors = [err18]; } else { @@ -18031,7 +18776,7 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, vErrors.push(err20); } errors++; - validate145.errors = vErrors; + validate155.errors = vErrors; return false; } else { errors = _errs0; @@ -18043,24 +18788,24 @@ function validate145(data, { instancePath = "", parentData, parentDataProperty, } } } - validate145.errors = vErrors; + validate155.errors = vErrors; return errors === 0; } -var validateValidateInstallationsOutcome = validate146; -function validate146(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateValidateInstallationsOutcome = validate156; +function validate156(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.removed_tags === void 0 && (missing0 = "removed_tags") || data.orphaned_dirs === void 0 && (missing0 = "orphaned_dirs") || data.valid_count === void 0 && (missing0 = "valid_count")) { - validate146.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate156.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "orphaned_dirs" || key0 === "removed_tags" || key0 === "valid_count")) { - validate146.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate156.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18076,7 +18821,7 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, for (let i0 = 0; i0 < len0; i0++) { const _errs4 = errors; if (typeof data0[i0] !== "string") { - validate146.errors = [{ instancePath: instancePath + "/orphaned_dirs/" + i0, schemaPath: "#/properties/orphaned_dirs/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate156.errors = [{ instancePath: instancePath + "/orphaned_dirs/" + i0, schemaPath: "#/properties/orphaned_dirs/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid1 = _errs4 === errors; @@ -18085,7 +18830,7 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate146.errors = [{ instancePath: instancePath + "/orphaned_dirs", schemaPath: "#/properties/orphaned_dirs/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate156.errors = [{ instancePath: instancePath + "/orphaned_dirs", schemaPath: "#/properties/orphaned_dirs/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -18104,7 +18849,7 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, for (let i1 = 0; i1 < len1; i1++) { const _errs8 = errors; if (typeof data2[i1] !== "string") { - validate146.errors = [{ instancePath: instancePath + "/removed_tags/" + i1, schemaPath: "#/properties/removed_tags/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate156.errors = [{ instancePath: instancePath + "/removed_tags/" + i1, schemaPath: "#/properties/removed_tags/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid2 = _errs8 === errors; @@ -18113,7 +18858,7 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate146.errors = [{ instancePath: instancePath + "/removed_tags", schemaPath: "#/properties/removed_tags/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate156.errors = [{ instancePath: instancePath + "/removed_tags", schemaPath: "#/properties/removed_tags/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -18126,17 +18871,17 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, let data4 = data.valid_count; const _errs10 = errors; if (!(typeof data4 == "number" && (!(data4 % 1) && !isNaN(data4)) && isFinite(data4))) { - validate146.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate156.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs10) { if (typeof data4 == "number" && isFinite(data4)) { if (data4 > 9007199254740991 || isNaN(data4)) { - validate146.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate156.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data4 < 0 || isNaN(data4)) { - validate146.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate156.errors = [{ instancePath: instancePath + "/valid_count", schemaPath: "#/properties/valid_count/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -18151,28 +18896,28 @@ function validate146(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate146.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate156.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate146.errors = vErrors; + validate156.errors = vErrors; return errors === 0; } -var validateVersionInfoOutcome = validate147; -function validate147(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateVersionInfoOutcome = validate157; +function validate157(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.info === void 0 && (missing0 = "info")) { - validate147.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate157.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "info" || key0 === "success")) { - validate147.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate157.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18186,13 +18931,13 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, if (data0 && typeof data0 == "object" && !Array.isArray(data0)) { let missing1; if (data0.tag === void 0 && (missing1 = "tag") || data0.installed === void 0 && (missing1 = "installed") || data0.size === void 0 && (missing1 = "size")) { - validate147.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate157.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs5 = errors; for (const key1 in data0) { if (!(key1 === "installed" || key1 === "size" || key1 === "tag")) { - validate147.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate157.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18201,7 +18946,7 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, if (data0.installed !== void 0) { const _errs6 = errors; if (typeof data0.installed !== "boolean") { - validate147.errors = [{ instancePath: instancePath + "/info/installed", schemaPath: "#/definitions/RuntimeVersionInfo/properties/installed/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate157.errors = [{ instancePath: instancePath + "/info/installed", schemaPath: "#/definitions/RuntimeVersionInfo/properties/installed/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid2 = _errs6 === errors; @@ -18212,7 +18957,7 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, if (data0.size !== void 0) { const _errs8 = errors; if (data0.size !== null) { - validate147.errors = [{ instancePath: instancePath + "/info/size", schemaPath: "#/definitions/RuntimeVersionInfo/properties/size/type", keyword: "type", params: { type: "null" }, message: "must be null" }]; + validate157.errors = [{ instancePath: instancePath + "/info/size", schemaPath: "#/definitions/RuntimeVersionInfo/properties/size/type", keyword: "type", params: { type: "null" }, message: "must be null" }]; return false; } var valid2 = _errs8 === errors; @@ -18223,7 +18968,7 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, if (data0.tag !== void 0) { const _errs10 = errors; if (typeof data0.tag !== "string") { - validate147.errors = [{ instancePath: instancePath + "/info/tag", schemaPath: "#/definitions/RuntimeVersionInfo/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate157.errors = [{ instancePath: instancePath + "/info/tag", schemaPath: "#/definitions/RuntimeVersionInfo/properties/tag/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid2 = _errs10 === errors; @@ -18235,7 +18980,7 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate147.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate157.errors = [{ instancePath: instancePath + "/info", schemaPath: "#/definitions/RuntimeVersionInfo/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -18248,11 +18993,11 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, let data4 = data.success; const _errs12 = errors; if (typeof data4 !== "boolean") { - validate147.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate157.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data4) { - validate147.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate157.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs12 === errors; @@ -18263,29 +19008,29 @@ function validate147(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate147.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate157.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate147.errors = vErrors; + validate157.errors = vErrors; return errors === 0; } -var validateVersionStatusOutcome = validate148; -var schema162 = { "additionalProperties": false, "properties": { "activeVersion": { "type": ["string", "null"] }, "defaultVersion": { "type": ["string", "null"] }, "installedCount": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "versions": { "additionalProperties": { "$ref": "#/definitions/RuntimeVersionEntry" }, "type": "object" } }, "required": ["installedCount", "activeVersion", "defaultVersion", "versions"], "type": "object" }; -function validate150(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +var validateVersionStatusOutcome = validate158; +var schema179 = { "additionalProperties": false, "properties": { "activeVersion": { "type": ["string", "null"] }, "defaultVersion": { "type": ["string", "null"] }, "installedCount": { "maximum": 9007199254740991, "minimum": 0, "type": "integer" }, "versions": { "additionalProperties": { "$ref": "#/definitions/RuntimeVersionEntry" }, "type": "object" } }, "required": ["installedCount", "activeVersion", "defaultVersion", "versions"], "type": "object" }; +function validate160(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.isActive === void 0 && (missing0 = "isActive") || data.dependencies === void 0 && (missing0 = "dependencies")) { - validate150.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate160.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "dependencies" || key0 === "isActive")) { - validate150.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate160.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18299,13 +19044,13 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, if (data0 && typeof data0 == "object" && !Array.isArray(data0)) { let missing1; if (data0.installed === void 0 && (missing1 = "installed") || data0.missing === void 0 && (missing1 = "missing")) { - validate150.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/required", keyword: "required", params: { missingProperty: missing1 }, message: "must have required property '" + missing1 + "'" }]; return false; } else { const _errs5 = errors; for (const key1 in data0) { if (!(key1 === "installed" || key1 === "missing")) { - validate150.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18321,7 +19066,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, for (let i0 = 0; i0 < len0; i0++) { const _errs8 = errors; if (typeof data1[i0] !== "string") { - validate150.errors = [{ instancePath: instancePath + "/dependencies/installed/" + i0, schemaPath: "#/definitions/RuntimeVersionDependencies/properties/installed/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies/installed/" + i0, schemaPath: "#/definitions/RuntimeVersionDependencies/properties/installed/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid3 = _errs8 === errors; @@ -18330,7 +19075,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate150.errors = [{ instancePath: instancePath + "/dependencies/installed", schemaPath: "#/definitions/RuntimeVersionDependencies/properties/installed/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies/installed", schemaPath: "#/definitions/RuntimeVersionDependencies/properties/installed/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -18349,7 +19094,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, for (let i1 = 0; i1 < len1; i1++) { const _errs12 = errors; if (typeof data3[i1] !== "string") { - validate150.errors = [{ instancePath: instancePath + "/dependencies/missing/" + i1, schemaPath: "#/definitions/RuntimeVersionDependencies/properties/missing/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies/missing/" + i1, schemaPath: "#/definitions/RuntimeVersionDependencies/properties/missing/items/type", keyword: "type", params: { type: "string" }, message: "must be string" }]; return false; } var valid4 = _errs12 === errors; @@ -18358,7 +19103,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate150.errors = [{ instancePath: instancePath + "/dependencies/missing", schemaPath: "#/definitions/RuntimeVersionDependencies/properties/missing/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies/missing", schemaPath: "#/definitions/RuntimeVersionDependencies/properties/missing/type", keyword: "type", params: { type: "array" }, message: "must be array" }]; return false; } } @@ -18370,7 +19115,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate150.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate160.errors = [{ instancePath: instancePath + "/dependencies", schemaPath: "#/definitions/RuntimeVersionDependencies/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -18382,7 +19127,7 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, if (data.isActive !== void 0) { const _errs14 = errors; if (typeof data.isActive !== "boolean") { - validate150.errors = [{ instancePath: instancePath + "/isActive", schemaPath: "#/properties/isActive/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate160.errors = [{ instancePath: instancePath + "/isActive", schemaPath: "#/properties/isActive/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } var valid0 = _errs14 === errors; @@ -18393,27 +19138,27 @@ function validate150(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate150.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate160.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate150.errors = vErrors; + validate160.errors = vErrors; return errors === 0; } -function validate149(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate159(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.installedCount === void 0 && (missing0 = "installedCount") || data.activeVersion === void 0 && (missing0 = "activeVersion") || data.defaultVersion === void 0 && (missing0 = "defaultVersion") || data.versions === void 0 && (missing0 = "versions")) { - validate149.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate159.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "activeVersion" || key0 === "defaultVersion" || key0 === "installedCount" || key0 === "versions")) { - validate149.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate159.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18423,7 +19168,7 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, let data0 = data.activeVersion; const _errs2 = errors; if (typeof data0 !== "string" && data0 !== null) { - validate149.errors = [{ instancePath: instancePath + "/activeVersion", schemaPath: "#/properties/activeVersion/type", keyword: "type", params: { type: schema162.properties.activeVersion.type }, message: "must be string,null" }]; + validate159.errors = [{ instancePath: instancePath + "/activeVersion", schemaPath: "#/properties/activeVersion/type", keyword: "type", params: { type: schema179.properties.activeVersion.type }, message: "must be string,null" }]; return false; } var valid0 = _errs2 === errors; @@ -18435,7 +19180,7 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.defaultVersion; const _errs4 = errors; if (typeof data1 !== "string" && data1 !== null) { - validate149.errors = [{ instancePath: instancePath + "/defaultVersion", schemaPath: "#/properties/defaultVersion/type", keyword: "type", params: { type: schema162.properties.defaultVersion.type }, message: "must be string,null" }]; + validate159.errors = [{ instancePath: instancePath + "/defaultVersion", schemaPath: "#/properties/defaultVersion/type", keyword: "type", params: { type: schema179.properties.defaultVersion.type }, message: "must be string,null" }]; return false; } var valid0 = _errs4 === errors; @@ -18447,17 +19192,17 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, let data2 = data.installedCount; const _errs6 = errors; if (!(typeof data2 == "number" && (!(data2 % 1) && !isNaN(data2)) && isFinite(data2))) { - validate149.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; + validate159.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/type", keyword: "type", params: { type: "integer" }, message: "must be integer" }]; return false; } if (errors === _errs6) { if (typeof data2 == "number" && isFinite(data2)) { if (data2 > 9007199254740991 || isNaN(data2)) { - validate149.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; + validate159.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/maximum", keyword: "maximum", params: { comparison: "<=", limit: 9007199254740991 }, message: "must be <= 9007199254740991" }]; return false; } else { if (data2 < 0 || isNaN(data2)) { - validate149.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; + validate159.errors = [{ instancePath: instancePath + "/installedCount", schemaPath: "#/properties/installedCount/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 }, message: "must be >= 0" }]; return false; } } @@ -18475,8 +19220,8 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, if (data3 && typeof data3 == "object" && !Array.isArray(data3)) { for (const key1 in data3) { const _errs11 = errors; - if (!validate150(data3[key1], { instancePath: instancePath + "/versions/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data3, parentDataProperty: key1, rootData })) { - vErrors = vErrors === null ? validate150.errors : vErrors.concat(validate150.errors); + if (!validate160(data3[key1], { instancePath: instancePath + "/versions/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), parentData: data3, parentDataProperty: key1, rootData })) { + vErrors = vErrors === null ? validate160.errors : vErrors.concat(validate160.errors); errors = vErrors.length; } var valid1 = _errs11 === errors; @@ -18485,7 +19230,7 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate149.errors = [{ instancePath: instancePath + "/versions", schemaPath: "#/properties/versions/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate159.errors = [{ instancePath: instancePath + "/versions", schemaPath: "#/properties/versions/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } @@ -18499,27 +19244,27 @@ function validate149(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate149.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate159.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate149.errors = vErrors; + validate159.errors = vErrors; return errors === 0; } -function validate148(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { +function validate158(data, { instancePath = "", parentData, parentDataProperty, rootData = data } = {}) { let vErrors = null; let errors = 0; if (errors === 0) { if (data && typeof data == "object" && !Array.isArray(data)) { let missing0; if (data.success === void 0 && (missing0 = "success") || data.status === void 0 && (missing0 = "status")) { - validate148.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; + validate158.errors = [{ instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: missing0 }, message: "must have required property '" + missing0 + "'" }]; return false; } else { const _errs1 = errors; for (const key0 in data) { if (!(key0 === "status" || key0 === "success")) { - validate148.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; + validate158.errors = [{ instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" }]; return false; break; } @@ -18527,8 +19272,8 @@ function validate148(data, { instancePath = "", parentData, parentDataProperty, if (_errs1 === errors) { if (data.status !== void 0) { const _errs2 = errors; - if (!validate149(data.status, { instancePath: instancePath + "/status", parentData: data, parentDataProperty: "status", rootData })) { - vErrors = vErrors === null ? validate149.errors : vErrors.concat(validate149.errors); + if (!validate159(data.status, { instancePath: instancePath + "/status", parentData: data, parentDataProperty: "status", rootData })) { + vErrors = vErrors === null ? validate159.errors : vErrors.concat(validate159.errors); errors = vErrors.length; } var valid0 = _errs2 === errors; @@ -18540,11 +19285,11 @@ function validate148(data, { instancePath = "", parentData, parentDataProperty, let data1 = data.success; const _errs3 = errors; if (typeof data1 !== "boolean") { - validate148.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; + validate158.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" }]; return false; } if (true !== data1) { - validate148.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; + validate158.errors = [{ instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", params: { allowedValue: true }, message: "must be equal to constant" }]; return false; } var valid0 = _errs3 === errors; @@ -18555,11 +19300,11 @@ function validate148(data, { instancePath = "", parentData, parentDataProperty, } } } else { - validate148.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; + validate158.errors = [{ instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" }]; return false; } } - validate148.errors = vErrors; + validate158.errors = vErrors; return errors === 0; } export { @@ -18587,6 +19332,7 @@ export { validateGetReleaseDependenciesOutcome, validateGetReleaseDependenciesParams, validateGetTorchPreviewReportParams, + validateGetTorchReleaseOptionsParams, validateGetTorchRuntimeProbeParams, validateGithubCacheStatusOutcome, validateHfDownloadDetailsOutcome, @@ -18623,6 +19369,13 @@ export { validateSupportedQuantTypesOutcome, validateSwitchVersionOutcome, validateSwitchVersionParams, + validateTorchDriverAvailability, + validateTorchGpuVendor, + validateTorchReleaseCombination, + validateTorchReleaseDriverStatus, + validateTorchReleaseOptionsOutcome, + validateTorchReleaseOptionsStatus, + validateTorchReleaseRecommendation, validateTorchRuntimePreview, validateTorchRuntimePreviewArtifact, validateTorchRuntimePreviewOutcome, diff --git a/electron/src/preload.ts b/electron/src/preload.ts index e00241c73..34710c4d4 100644 --- a/electron/src/preload.ts +++ b/electron/src/preload.ts @@ -67,6 +67,8 @@ import { decodeSetDefaultVersionParams, decodeInstallVersionOutcome, decodeInstallVersionParams, + decodeGetTorchReleaseOptionsParams, + decodeTorchReleaseOptionsOutcome, decodePreviewTorchRuntimeParams, decodeTorchRuntimePreviewOutcome, decodeGetTorchRuntimeProbeParams, @@ -644,6 +646,10 @@ const electronAPI = { return validatedApiCall('install_version', decodeInstallVersionOutcome, params); }, get_torch_runtime_options: async () => validateTorchOptions(await apiCall('get_torch_runtime_options')), + get_torch_release_options: (tag: string) => { + const params = requireDecoded(decodeGetTorchReleaseOptionsParams({ tag }), 'get_torch_release_options request'); + return validatedApiCall('get_torch_release_options', decodeTorchReleaseOptionsOutcome, params); + }, preview_torch_runtime: async (request: { tag: string; build: string; python: string; adapter: string }) => { const params = requireDecoded(decodePreviewTorchRuntimeParams(request), 'preview_torch_runtime request'); return validatedApiCall('preview_torch_runtime', decodeTorchRuntimePreviewOutcome, params); diff --git a/frontend/src/components/TorchDesktopProjection.test.tsx b/frontend/src/components/TorchDesktopProjection.test.tsx index 333d950a8..c48b5d34c 100644 --- a/frontend/src/components/TorchDesktopProjection.test.tsx +++ b/frontend/src/components/TorchDesktopProjection.test.tsx @@ -87,6 +87,21 @@ describe('Torch desktop projection', () => { { tag: releaseTag, build: 'cpu', python: 'python3.12', adapter: 'flux2', qualification: 'unverified' }, ], }); + const getReleaseOptions = vi.fn().mockResolvedValue({ + tag: releaseTag, + status: 'matches', + completeScan: true, + checkedChannels: ['cpu', 'cu130'], + combinations: [ + { build: 'cpu', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/whl/cpu/torch.whl' }, + { build: 'cu130', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/whl/cu130/torch.whl' }, + ], + issues: [], + detectedGpuVendors: [], + driverStatus: { nvidia: 'not_present', amd: 'not_present' }, + recommended: { build: 'cpu', python: 'python3.12' }, + recommendationNote: null, + }); const preview = vi.fn().mockResolvedValue({ status: 'resolved', preview: { previewId, expiresInSeconds: 300, tag: releaseTag, build: 'cpu', python: 'python3.12', adapter: 'flux2', @@ -107,6 +122,7 @@ describe('Torch desktop projection', () => { const unconditionalStop = vi.fn(); vi.stubGlobal('electronAPI', { get_torch_runtime_options: getOptions, + get_torch_release_options: getReleaseOptions, preview_torch_runtime: preview, get_torch_runtime_probe: probe, get_runtime_profiles_snapshot: vi.fn().mockResolvedValue({ success: true, snapshot: { profiles: [ @@ -130,7 +146,7 @@ describe('Torch desktop projection', () => { if (!installButton) throw new TypeError('Expected install button'); fireEvent.click(installButton); expect(installVersion).not.toHaveBeenCalled(); - fireEvent.change(await screen.findByRole('combobox', { name: 'Image adapter' }), { target: { value: 'flux2' } }); + expect(await screen.findByText('Pumas image dependencies are selected by Pumas for its FLUX.2 path; they are not part of upstream Torch.')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); await waitFor(() => expect(screen.getByText('SHA-256: abc123')).toBeInTheDocument()); expect(preview).toHaveBeenCalledWith({ tag: releaseTag, build: 'cpu', python: 'python3.12', adapter: 'flux2' }); diff --git a/frontend/src/components/TorchInstallPreview.test.tsx b/frontend/src/components/TorchInstallPreview.test.tsx index 85dea0908..3030c60b4 100644 --- a/frontend/src/components/TorchInstallPreview.test.tsx +++ b/frontend/src/components/TorchInstallPreview.test.tsx @@ -1,180 +1,283 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { TorchInstallPreview } from './TorchInstallPreview'; -import type { TorchAlternativesOutcome, TorchRuntimeOptions, TorchRuntimePreview, TorchRuntimePreviewOutcome, TorchRuntimePreviewRequest } from '../types/torch-install'; +import type { + TorchAlternativesOutcome, + TorchReleaseOptionsOutcome, + TorchRuntimeOptions, + TorchRuntimePreviewOutcome, + TorchRuntimePreviewRequest, +} from '../types/torch-install'; -const getOptions = vi.fn<() => Promise>(); +const getReleaseOptions = vi.fn<(tag: string) => Promise>(); +const getPresetOptions = vi.fn<() => Promise>(); const getPreview = vi.fn<(request: TorchRuntimePreviewRequest) => Promise>(); const getAlternatives = vi.fn<(tag: string, build: string, python: string) => Promise>(); vi.mock('../api/adapter', () => ({ api: { - get_torch_runtime_options: () => getOptions(), + get_torch_release_options: (tag: string) => getReleaseOptions(tag), + get_torch_runtime_options: () => getPresetOptions(), preview_torch_runtime: (request: TorchRuntimePreviewRequest) => getPreview(request), find_torch_alternatives: (tag: string, build: string, python: string) => getAlternatives(tag, build, python), }, })); -const options = { - builds: ['cpu', 'cu130'], - pythons: [{ id: 'python3.12', label: 'Python 3.12 at /usr/bin/python3.12' }], - adapters: ['none', 'flux2'], - installed: [], +const release: TorchReleaseOptionsOutcome = { + tag: 'v2.14.0', status: 'matches', completeScan: true, + checkedChannels: ['cpu', 'cu130'], + combinations: [ + { build: 'cpu', python: 'python3.11', wheelUrl: 'https://download.pytorch.org/whl/cpu/torch.whl' }, + { build: 'cu130', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/whl/cu130/torch.whl' }, + ], + issues: [], detectedGpuVendors: ['nvidia', 'intel'], + driverStatus: { nvidia: 'available', amd: 'not_present' }, + recommended: { build: 'cu130', python: 'python3.12' }, recommendationNote: null, +}; +const preset: TorchRuntimeOptions = { + builds: ['cpu', 'cu130', 'cu134', 'rocm'], + pythons: [{ id: 'python3.12', label: 'Python 3.12' }], + adapters: ['none', 'flux2'], installed: [], preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, }; -const resolved = (preview: TorchRuntimePreview): TorchRuntimePreviewOutcome => ({ status: 'resolved', preview }); +beforeEach(() => { + vi.clearAllMocks(); + getReleaseOptions.mockResolvedValue(release); + getPresetOptions.mockResolvedValue(preset); + getPreview.mockImplementation(async (request) => ({ + status: 'resolved', + preview: { + ...request, previewId: 'review-1', expiresInSeconds: 300, + qualification: request.adapter === 'bundled' ? 'qualified' : 'unverified', + artifacts: [{ name: 'torch', version: '2.14.0', url: 'https://download.pytorch.org/torch.whl', sha256: 'abc123' }], + }, + })); +}); describe('TorchInstallPreview', () => { - it('shows exact artifacts and installs only the reviewed selection', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockResolvedValue(resolved({ - previewId: 'review-1', expiresInSeconds: 300, tag: 'v2.10.0', build: 'cpu', python: 'python3.12', - adapter: 'none', qualification: 'unverified', - artifacts: [{ name: 'torch', version: '2.10.0', url: 'https://download.pytorch.org/whl/cpu/torch.whl', sha256: 'abc123' }], - })); + it('uses the manager recommendation without requiring build, Python, or profile selectors', async () => { const onInstall = vi.fn(); - render(); + render(); - const install = screen.getByRole('button', { name: 'Install reviewed artifacts' }); - expect(install).toBeDisabled(); - expect(screen.getByText(/does not install Python/)).toBeInTheDocument(); - expect(screen.getByText(/Source compilation is a separate unsupported path/)).toBeInTheDocument(); - fireEvent.click(await screen.findByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(screen.getByText('SHA-256: abc123')).toBeInTheDocument()); - expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.10.0', build: 'cpu', python: 'python3.12', adapter: 'none' }); - expect(screen.getByText('URL: https://download.pytorch.org/whl/cpu/torch.whl')).toBeInTheDocument(); - expect(install).toBeEnabled(); + expect(await screen.findByText(/Recommended setup: cu130 · python3.12 · Pumas image dependencies/)).toBeInTheDocument(); + expect(getReleaseOptions).toHaveBeenCalledWith('v2.14.0'); + expect(getPresetOptions).not.toHaveBeenCalled(); + expect(screen.getByText('Advanced setup').closest('details')).not.toHaveAttribute('open'); + expect(screen.getByText(/selected by Pumas for its FLUX.2 path; they are not part of upstream Torch/)).toBeInTheDocument(); - fireEvent.change(screen.getByRole('combobox', { name: 'Torch build' }), { target: { value: 'cu130' } }); - expect(install).toBeDisabled(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(install).toBeDisabled()); - expect(onInstall).not.toHaveBeenCalled(); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'python3.12', adapter: 'flux2' })); + expect(await screen.findByText('SHA-256: abc123')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); + expect(onInstall).toHaveBeenCalledWith('review-1'); }); - it('locks the legacy preset and keeps an inconclusive probe from installing', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockResolvedValue({ status: 'rejected', reason: 'network_inconclusive', message: 'index unreachable' }); - const onInstall = vi.fn(); - render(); + it('limits advanced build and Python overrides to this release’s returned combinations', async () => { + render(); + fireEvent.click(await screen.findByText('Advanced setup')); + const build = screen.getByRole('combobox', { name: 'Torch build' }); + expect(within(build).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose a build', 'cpu', 'cu130']); + expect(within(build).queryByRole('option', { name: /cu134|rocm/i })).not.toBeInTheDocument(); + const python = screen.getByRole('combobox', { name: 'Installed Python' }); + expect(within(python).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose installed Python', 'python3.12']); + fireEvent.change(build, { target: { value: 'cpu' } }); + expect(python).toHaveValue('python3.11'); + expect(within(python).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose installed Python', 'python3.11']); + fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' })); + }); - expect(await screen.findByRole('combobox', { name: 'Torch build' })).toBeDisabled(); + it('requires a deliberate advanced selection when no recommendation exists for a GPU-only release', async () => { + getReleaseOptions.mockResolvedValue({ + ...release, + checkedChannels: ['cu132'], + combinations: [{ build: 'cu132', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/whl/cu132/torch.whl' }], + recommended: null, + recommendationNote: 'No GPU driver could be verified.', + }); + render(); + + expect(await screen.findByText(/No setup was recommended. Open Advanced setup/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Check selected combination' })).toBeDisabled(); + expect(getPreview).not.toHaveBeenCalled(); + fireEvent.click(screen.getByText('Advanced setup')); + const build = screen.getByRole('combobox', { name: 'Torch build' }); + expect(build).toHaveValue(''); expect(screen.getByRole('combobox', { name: 'Installed Python' })).toBeDisabled(); + fireEvent.change(build, { target: { value: 'cu132' } }); + expect(screen.getByRole('combobox', { name: 'Installed Python' })).toHaveValue('python3.12'); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - expect(await screen.findByRole('alert')).toHaveTextContent('Network inconclusive'); - expect(screen.getByRole('button', { name: 'Install fixed preset' })).toBeDisabled(); - expect(screen.queryByRole('button', { name: 'Find compatible alternatives' })).not.toBeInTheDocument(); - expect(onInstall).not.toHaveBeenCalled(); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu132', python: 'python3.12', adapter: 'flux2' })); }); - it('offers the qualified preset and an unverified exact-wheel choice for the same release', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockImplementation(async (request) => resolved({ - previewId: request.adapter === 'bundled' ? 'qualified-preset' : 'upstream-cpu', - expiresInSeconds: 300, - ...request, - qualification: request.adapter === 'bundled' ? 'qualified' : 'unverified', - artifacts: [], - })); + it('shows the manager’s driver caveat alongside a CPU recommendation', async () => { + getReleaseOptions.mockResolvedValue({ + ...release, + driverStatus: { ...release.driverStatus, nvidia: 'unavailable' }, + recommended: { build: 'cpu', python: 'python3.11' }, + recommendationNote: 'NVIDIA hardware was detected, but its driver could not be verified. CPU is recommended.', + }); + render(); + expect(await screen.findByText(/Recommended setup: cpu · python3.11/)).toBeInTheDocument(); + expect(screen.getByText(/driver could not be verified. CPU is recommended/)).toBeInTheDocument(); + }); + + it('makes Core runtime only a deliberate advanced choice', async () => { + render(); + fireEvent.click(await screen.findByText('Advanced setup')); + fireEvent.change(screen.getByRole('combobox', { name: 'Dependency profile' }), { target: { value: 'none' } }); + expect(screen.getByText(/Recommended setup: cu130 · python3.12 · Core runtime only/)).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'python3.12', adapter: 'none' })); + }); + + it('does not imply an incomplete discovery found no wheels', async () => { + getReleaseOptions.mockResolvedValue({ ...release, status: 'inconclusive', completeScan: false, combinations: [], recommended: null, issues: ['index timed out'] }); + render(); + expect(await screen.findByText(/Release discovery was inconclusive. Some official wheels may exist/)).toBeInTheDocument(); + expect(screen.getByText('index timed out')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Check selected combination' })).toBeDisabled(); + expect(screen.queryByText(/No official wheel matches were found/)).not.toBeInTheDocument(); + }); + + it('requires an installed compatible Python before checking release wheels', async () => { + getReleaseOptions.mockResolvedValue({ + ...release, status: 'inconclusive', completeScan: false, + checkedChannels: [], combinations: [], recommended: null, + issues: ['No installed CPython 3.10–3.13 interpreter on Linux x86_64 could inspect official wheels'], + }); + render(); + + expect(await screen.findByText(/No installed CPython 3.10–3.13 interpreter/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Check selected combination' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); + expect(getPreview).not.toHaveBeenCalled(); + }); + + it('keeps the qualified v2.9.1 bundled preset and an unverified upstream choice distinct', async () => { + getReleaseOptions.mockResolvedValue({ ...release, tag: 'v2.9.1' }); const onInstall = vi.fn(); render(); - - const presetChoice = await screen.findByRole('button', { name: 'Qualified fixed preset' }); - expect(presetChoice).toHaveAttribute('aria-pressed', 'true'); - expect(screen.getByRole('combobox', { name: 'Torch build' })).toBeDisabled(); + const qualified = await screen.findByRole('button', { name: 'Qualified fixed preset' }); + expect(qualified).toHaveAttribute('aria-pressed', 'true'); + expect(screen.queryByRole('combobox', { name: 'Torch build' })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ - tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled', - })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' })); fireEvent.click(screen.getByRole('button', { name: 'Install fixed preset' })); - expect(onInstall).toHaveBeenCalledWith('qualified-preset'); + expect(onInstall).toHaveBeenCalledWith('review-1'); fireEvent.click(screen.getByRole('button', { name: 'Other official wheels (unverified)' })); expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); - expect(screen.getByRole('combobox', { name: 'Torch build' })).toBeEnabled(); - expect(screen.getByRole('combobox', { name: 'Image adapter' })).toBeEnabled(); - expect(screen.getByRole('combobox', { name: 'Image adapter' })).toHaveValue('none'); + expect(screen.getByText(/Pumas image dependencies are selected by Pumas/)).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ - tag: 'v2.9.1', build: 'cpu', python: 'python3.12', adapter: 'none', - })); - expect(screen.getByText(/Artifact resolution is unverified/)).toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); - expect(onInstall).toHaveBeenLastCalledWith('upstream-cpu'); + await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'flux2' })); + expect(await screen.findByText(/Artifact resolution is unverified/)).toBeInTheDocument(); }); - it('requires an already installed compatible Python before checking wheels', async () => { - getOptions.mockResolvedValue({ ...options, pythons: [] }); - render(); - expect(await screen.findByText(/Install CPython 3.10–3.13 for Linux x86_64/)).toBeInTheDocument(); + it('leaves the upstream v2.9.1 choice unset when the manager has no recommendation', async () => { + getReleaseOptions.mockResolvedValue({ + ...release, tag: 'v2.9.1', recommended: null, + checkedChannels: ['cu132'], + combinations: [{ build: 'cu132', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/whl/cu132/torch.whl' }], + }); + render(); + fireEvent.click(await screen.findByRole('button', { name: 'Other official wheels (unverified)' })); + expect(await screen.findByText(/No setup was recommended. Open Advanced setup/)).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Check selected combination' })).toBeDisabled(); + expect(getPreview).not.toHaveBeenCalled(); }); - it('reports a failed wheel report validation distinctly without offering alternatives', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockResolvedValue({ status: 'rejected', reason: 'validation_failed', message: 'Unsupported combination: hash missing for torch' }); - render(); - + it('keeps rejected previews from installing and classifies validation by enum', async () => { + getPreview.mockResolvedValue({ status: 'rejected', reason: 'validation_failed', message: 'Unsupported combination: missing hash' }); + render(); fireEvent.click(await screen.findByRole('button', { name: 'Check selected combination' })); - expect(await screen.findByRole('alert')).toHaveTextContent('Resolved wheel report failed validation: Unsupported combination: hash missing for torch'); - expect(screen.getByText(/cpu · python3.12 · none: failed validation/)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); + expect(await screen.findByRole('alert')).toHaveTextContent('Resolved wheel report failed validation: Unsupported combination: missing hash'); expect(screen.queryByRole('button', { name: 'Find compatible alternatives' })).not.toBeInTheDocument(); - expect(screen.queryByText(/Probe inconclusive/)).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); }); - it('treats transport exceptions as inconclusive even when their text resembles an unsupported outcome', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockRejectedValue(new Error('Unsupported combination: transport failed')); - render(); + it('offers official wheel alternatives only after an unsupported preview and rechecks the chosen match', async () => { + getPreview.mockResolvedValueOnce({ status: 'rejected', reason: 'unsupported', message: 'selected dependencies have no matching wheel' }); + getPreview.mockImplementationOnce(async (request) => ({ + status: 'resolved', + preview: { ...request, previewId: 'review-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, + })); + getAlternatives.mockResolvedValue({ + selectedTag: 'v2.14.0', selectedBuild: 'cu130', selectedPython: 'python3.12', + status: 'matches', incomplete: true, dependenciesNotChecked: true, + checkedBuilds: ['cu130', 'cpu'], issues: [], + matches: [{ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', wheelUrl: 'https://download.pytorch.org/whl/cpu/torch.whl', sha256: 'a'.repeat(64) }], + }); + const onInstall = vi.fn(); + render(); fireEvent.click(await screen.findByRole('button', { name: 'Check selected combination' })); - expect(await screen.findByRole('alert')).toHaveTextContent('Probe inconclusive: Unsupported combination: transport failed'); - expect(screen.queryByRole('button', { name: 'Find compatible alternatives' })).not.toBeInTheDocument(); + expect(await screen.findByRole('alert')).toHaveTextContent('Unsupported combination'); expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); + fireEvent.click(screen.getByRole('button', { name: 'Find compatible alternatives' })); + expect(await screen.findByText('Official Torch wheel matches only')).toBeInTheDocument(); + expect(screen.getByText(/Other dependencies have not been checked/)).toBeInTheDocument(); + expect(getAlternatives).toHaveBeenCalledWith('v2.14.0', 'cu130', 'python3.12'); + fireEvent.click(screen.getByRole('button', { name: 'Preview v2.14.0 · cpu · python3.11' })); + await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' })); + expect(await screen.findByText('Exact artifacts resolved')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); + expect(onInstall).toHaveBeenCalledWith('review-cpu'); }); - it('offers official wheel matches only after unsupported preview and rechecks a chosen candidate', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockResolvedValueOnce({ status: 'rejected', reason: 'unsupported', message: 'no matching wheel' }); - getPreview.mockResolvedValueOnce(resolved({ - previewId: 'review-cu130', expiresInSeconds: 300, tag: 'v2.10.0', build: 'cu130', python: 'python3.12', - adapter: 'none', qualification: 'unverified', artifacts: [], + it('shows and previews only host-eligible alternatives from the release combinations', async () => { + getReleaseOptions.mockResolvedValue({ + ...release, + checkedChannels: ['cpu'], + combinations: [{ build: 'cpu', python: 'python3.11', wheelUrl: 'https://download.pytorch.org/whl/cpu/torch.whl' }], + recommended: { build: 'cpu', python: 'python3.11' }, + }); + getPreview.mockResolvedValueOnce({ status: 'rejected', reason: 'unsupported', message: 'dependency unavailable' }); + getPreview.mockImplementationOnce(async (request) => ({ + status: 'resolved', + preview: { ...request, previewId: 'eligible-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, })); getAlternatives.mockResolvedValue({ - selectedTag: 'v2.10.0', selectedBuild: 'cpu', selectedPython: 'python3.12', + selectedTag: 'v2.14.0', selectedBuild: 'cpu', selectedPython: 'python3.11', status: 'matches', incomplete: true, dependenciesNotChecked: true, - checkedBuilds: ['cpu', 'cu130'], issues: [], - matches: [{ tag: 'v2.10.0', build: 'cu130', python: 'python3.12', wheelUrl: 'https://download.pytorch.org/torch.whl', sha256: 'a'.repeat(64) }], + checkedBuilds: ['cpu', 'cu130', 'rocm6.4'], issues: [], + matches: [ + { tag: 'v2.14.0', build: 'cpu', python: 'python3.11' }, + { tag: 'v2.14.0', build: 'cu130', python: 'python3.11' }, + { tag: 'v2.14.0', build: 'rocm6.4', python: 'python3.11' }, + { tag: 'v2.14.0', build: 'cpu', python: 'python3.12' }, + { tag: 'v2.13.0', build: 'cpu', python: 'python3.11' }, + ], }); - render(); + render(); fireEvent.click(await screen.findByRole('button', { name: 'Check selected combination' })); fireEvent.click(await screen.findByRole('button', { name: 'Find compatible alternatives' })); - expect(await screen.findByText('Official Torch wheel matches only')).toBeInTheDocument(); - expect(screen.getByText(/Other dependencies have not been checked/)).toBeInTheDocument(); - expect(getAlternatives).toHaveBeenCalledWith('v2.10.0', 'cpu', 'python3.12'); - fireEvent.click(screen.getByRole('button', { name: 'Preview v2.10.0 · cu130 · python3.12' })); - await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.10.0', build: 'cu130', python: 'python3.12', adapter: 'none' })); - expect(await screen.findByText('Exact artifacts resolved')).toBeInTheDocument(); + expect(await screen.findByText('Checked builds: cpu')).toBeInTheDocument(); + expect(screen.getAllByRole('button', { name: /^Preview v/ })).toHaveLength(1); + expect(screen.queryByRole('button', { name: /Preview .*cu130|Preview .*rocm6\.4|Preview v2\.13\.0/ })).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: 'Preview v2.14.0 · cpu · python3.11' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledTimes(2)); + expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' }); }); it('keeps a long artifact list and its install action inside the scrollable preview region', async () => { - getOptions.mockResolvedValue(options); - getPreview.mockResolvedValue(resolved({ - previewId: 'review-many', expiresInSeconds: 300, tag: 'v2.10.0', build: 'cpu', python: 'python3.12', - adapter: 'none', qualification: 'unverified', - artifacts: Array.from({ length: 30 }, (_, index) => ({ - name: `wheel-${index}`, version: '1.0', - url: `https://download.pytorch.org/whl/cpu/wheel-${index}.whl`, - sha256: 'a'.repeat(64), - })), + getPreview.mockImplementation(async (request) => ({ + status: 'resolved', + preview: { + ...request, previewId: 'review-many', expiresInSeconds: 300, qualification: 'unverified', + artifacts: Array.from({ length: 30 }, (_, index) => ({ + name: `wheel-${index}`, version: '1.0', + url: `https://download.pytorch.org/whl/cu130/wheel-${index}.whl`, + sha256: 'a'.repeat(64), + })), + }, })); - render(); + render(); fireEvent.click(await screen.findByRole('button', { name: 'Check selected combination' })); - const previewRegion = screen.getByRole('region', { name: 'Torch installation preview for v2.10.0' }); + const previewRegion = screen.getByRole('region', { name: 'Torch installation preview for v2.14.0' }); await waitFor(() => expect(screen.getByText('wheel-29 1.0')).toBeInTheDocument()); expect(previewRegion).toHaveClass('flex-1', 'min-h-0', 'overflow-y-auto'); expect(previewRegion).toContainElement(screen.getByRole('button', { name: 'Install reviewed artifacts' })); diff --git a/frontend/src/components/TorchInstallPreview.tsx b/frontend/src/components/TorchInstallPreview.tsx index caa2317db..a07a37201 100644 --- a/frontend/src/components/TorchInstallPreview.tsx +++ b/frontend/src/components/TorchInstallPreview.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { ArrowLeft, Loader2 } from 'lucide-react'; import { api } from '../api/adapter'; -import type { TorchAlternativesOutcome, TorchRuntimeOptions, TorchRuntimePreview, TorchRuntimePreviewOutcome, TorchRuntimePreviewRejectReason } from '../types/torch-install'; +import type { TorchAlternativeMatch, TorchAlternativesOutcome, TorchReleaseOptionsOutcome, TorchRuntimeOptions, TorchRuntimePreview, TorchRuntimePreviewOutcome, TorchRuntimePreviewRejectReason } from '../types/torch-install'; interface TorchInstallPreviewProps { tag: string; @@ -35,10 +35,11 @@ function errorText(error: unknown): string { } export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPreviewProps) { - const [options, setOptions] = useState(null); + const [presetOptions, setPresetOptions] = useState(null); + const [releaseOptions, setReleaseOptions] = useState(null); const [build, setBuild] = useState(''); const [python, setPython] = useState(''); - const [adapter, setAdapter] = useState('none'); + const [adapter, setAdapter] = useState('flux2'); const [selectionMode, setSelectionMode] = useState<'preset' | 'upstream'>('upstream'); const [preview, setPreview] = useState(null); const [loadingOptions, setLoadingOptions] = useState(true); @@ -50,11 +51,13 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev const [alternativesBusy, setAlternativesBusy] = useState(false); const [alternativesError, setAlternativesError] = useState(null); const requestNumber = useRef(0); + const selectionModeRef = useRef<'preset' | 'upstream'>('upstream'); useEffect(() => { let active = true; setLoadingOptions(true); - setOptions(null); + setPresetOptions(null); + setReleaseOptions(null); setPreview(null); setError(null); setRejection(null); @@ -62,19 +65,37 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev setAlternatives(null); setAlternativesError(null); setAlternativesBusy(false); - void api.get_torch_runtime_options().then((result) => { + selectionModeRef.current = tag === 'v2.9.1' ? 'preset' : 'upstream'; + setSelectionMode(selectionModeRef.current); + setBuild(''); + setPython(''); + setAdapter(tag === 'v2.9.1' ? 'bundled' : 'flux2'); + const releaseRequest = api.get_torch_release_options(tag).then((result) => { if (!active) return; - const preset = result.preset.tag === tag ? result.preset : null; - setOptions(result); - setSelectionMode(preset ? 'preset' : 'upstream'); - setBuild(preset?.build ?? result.builds[0] ?? ''); - setPython(preset?.python ?? result.pythons[0]?.id ?? ''); - setAdapter(preset?.adapter ?? result.adapters[0] ?? 'none'); + setReleaseOptions(result); + if (tag === 'v2.9.1' && selectionModeRef.current === 'preset') return; + const choice = result.combinations.find((item) => item.build === result.recommended?.build && item.python === result.recommended.python); + setBuild(choice?.build ?? ''); + setPython(choice?.python ?? ''); }).catch((cause: unknown) => { - if (active) setError(errorText(cause)); + if (active) setError(`Release discovery inconclusive: ${errorText(cause)}`); }).finally(() => { - if (active) setLoadingOptions(false); + if (active && tag !== 'v2.9.1') setLoadingOptions(false); }); + const presetRequest = tag === 'v2.9.1' ? api.get_torch_runtime_options().then((result) => { + if (!active) return; + setPresetOptions(result); + if (selectionModeRef.current === 'preset') { + setBuild(result.preset.build); + setPython(result.preset.python); + } + }).catch((cause: unknown) => { + if (active) setError(`Fixed preset choices unavailable: ${errorText(cause)}`); + }).finally(() => { + if (active) setLoadingOptions(false); + }) : Promise.resolve(); + void releaseRequest; + void presetRequest; return () => { active = false; requestNumber.current += 1; @@ -142,28 +163,33 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev } }; - const isPresetRelease = options?.preset.tag === tag; + const isPresetRelease = tag === 'v2.9.1'; const fixedPreset = isPresetRelease && selectionMode === 'preset'; const chooseMode = (mode: 'preset' | 'upstream') => { - if (!options) return; invalidatePreview(); + selectionModeRef.current = mode; setSelectionMode(mode); if (mode === 'preset') { - setBuild(options.preset.build); - setPython(options.preset.python); - setAdapter(options.preset.adapter); + setBuild(presetOptions?.preset.build ?? ''); + setPython(presetOptions?.preset.python ?? ''); + setAdapter('bundled'); } else { - setBuild(options.builds[0] ?? ''); - setPython(options.pythons[0]?.id ?? ''); - setAdapter(options.adapters[0] ?? 'none'); + const choice = releaseOptions?.combinations.find((item) => item.build === releaseOptions.recommended?.build && item.python === releaseOptions.recommended.python); + setBuild(choice?.build ?? ''); + setPython(choice?.python ?? ''); + setAdapter('flux2'); } }; - const availableBuilds = fixedPreset ? [options.preset.build] : options?.builds ?? []; - const availablePythons = fixedPreset - ? [{ id: options.preset.python, label: `Python ${options.preset.python.replace('python', '')} (fixed preset)` }] - : options?.pythons ?? []; - const availableAdapters = fixedPreset ? [options.preset.adapter] : options?.adapters ?? []; - const hasSelectedPython = options?.pythons.some((choice) => choice.id === python) ?? false; + const availableBuilds = [...new Set(releaseOptions?.combinations.map((choice) => choice.build) ?? [])]; + const availablePythons = [...new Set(releaseOptions?.combinations.filter((choice) => choice.build === build).map((choice) => choice.python) ?? [])]; + const isEligibleAlternative = (match: TorchAlternativeMatch) => match.tag === tag + && releaseOptions?.tag === tag + && releaseOptions.combinations.some((choice) => choice.build === match.build && choice.python === match.python); + const eligibleAlternatives = alternatives?.matches.filter(isEligibleAlternative) ?? []; + const eligibleCheckedBuilds = alternatives?.checkedBuilds.filter((choice) => availableBuilds.includes(choice)) ?? []; + const hasSelectedPython = fixedPreset + ? Boolean(presetOptions?.pythons.some((choice) => choice.id === python)) + : Boolean(releaseOptions?.combinations.some((choice) => choice.build === build && choice.python === python)); const canInstall = preview !== null && Boolean(preview.previewId) && preview.tag === tag && preview.build === build && preview.python === python && preview.adapter === adapter; @@ -191,32 +217,53 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev This flow installs official binary wheels only. Source compilation is a separate unsupported path and is never used as a fallback.

- {loadingOptions ? : options && ( + {loadingOptions ? : (releaseOptions || presetOptions) && ( <> {isPresetRelease &&
} {fixedPreset &&

This release has one qualified Python 3.12 / CUDA 13.0 build.

} -
- - - -
- {options.pythons.length === 0 &&

No supported installed Python interpreter was found. Install CPython 3.10–3.13 for Linux x86_64, then reopen this preview.

} - {fixedPreset && options.pythons.length > 0 && !hasSelectedPython &&

This fixed preset requires installed CPython 3.12. Install it, then reopen this preview.

} + {fixedPreset ?

Recommended setup: {build} · {python} · bundled image dependencies (fixed).

: <> + {build && python &&

{releaseOptions?.recommended?.build === build && releaseOptions.recommended.python === python ? 'Recommended setup' : 'Selected setup'}: {build} · {python} · {adapter === 'flux2' ? 'Pumas image dependencies' : 'Core runtime only'}.

} + {!build && releaseOptions?.combinations.length ?

No setup was recommended. Open Advanced setup and choose a build to continue.

: null} + {releaseOptions?.recommendationNote &&

{releaseOptions.recommendationNote}

} + {adapter === 'flux2' &&

Pumas image dependencies are selected by Pumas for its FLUX.2 path; they are not part of upstream Torch.

} +
+ Advanced setup +
+ + + +
+
+ } + {fixedPreset && presetOptions?.pythons.length === 0 &&

No supported installed Python interpreter was found. Install CPython 3.10–3.13 for Linux x86_64, then reopen this preview.

} + {fixedPreset && Boolean(presetOptions?.pythons.length) && !hasSelectedPython &&

This fixed preset requires installed CPython 3.12. Install it, then reopen this preview.

} + {!fixedPreset && releaseOptions?.status === 'inconclusive' &&

Release discovery was inconclusive. Some official wheels may exist; retry later or review the available combinations.

} + {!fixedPreset && releaseOptions?.status === 'none' &&

No official wheel matches were found in this scan for an installed compatible Python.

} + {!fixedPreset && releaseOptions && !releaseOptions.completeScan && releaseOptions.status !== 'inconclusive' &&

This wheel scan was incomplete. Other combinations may still exist.

} + {!fixedPreset && releaseOptions?.issues.map((issue, index) =>

{issue}

)} @@ -236,13 +283,14 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev {alternatives &&
Official Torch wheel matches only

Other dependencies have not been checked. Choose a match to run the normal exact preview for the full environment.

-

Checked builds: {alternatives.checkedBuilds.join(', ') || 'none'}

+

Checked builds: {eligibleCheckedBuilds.join(', ') || 'none'}

{alternatives.status === 'none' &&

No official Torch wheel matches were found for this release and Python.

} {alternatives.status === 'inconclusive' &&

Alternative search was inconclusive.

} {alternatives.issues.map((issue, index) =>

{issue}

)} - {alternatives.matches.length > 0 &&
    {alternatives.matches.map((match) => ( + {eligibleAlternatives.length > 0 &&
      {eligibleAlternatives.map((match) => (
- {loadingOptions ? : (releaseOptions || presetOptions) && ( + {loadingOptions ? : runtimeOptions && (releaseOptions || fixedPreset) && ( <> {isPresetRelease &&
@@ -251,20 +260,19 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev
} - {fixedPreset && presetOptions?.pythons.length === 0 &&

No supported installed Python interpreter was found. Install CPython 3.10–3.13 for Linux x86_64, then reopen this preview.

} - {fixedPreset && Boolean(presetOptions?.pythons.length) && !hasSelectedPython &&

This fixed preset requires installed CPython 3.12. Install it, then reopen this preview.

} + {fixedPreset && runtimeOptions.pythons.length === 0 &&

No compatible installed CPython interpreter was found. Install host-matched CPython 3.10–3.13, then reopen this preview.

} + {fixedPreset && runtimeOptions.pythons.length > 0 && !hasSelectedPython &&

This fixed preset requires installed CPython 3.12. Install it, then reopen this preview.

} {!fixedPreset && releaseOptions?.status === 'inconclusive' &&

Release discovery was inconclusive. Some official wheels may exist; retry later or review the available combinations.

} {!fixedPreset && releaseOptions?.status === 'none' &&

No official wheel matches were found in this scan for an installed compatible Python.

} {!fixedPreset && releaseOptions && !releaseOptions.completeScan && releaseOptions.status !== 'inconclusive' &&

This wheel scan was incomplete. Other combinations may still exist.

} {!fixedPreset && releaseOptions?.issues.map((issue, index) =>

{issue}

)} - diff --git a/frontend/src/components/TorchInstalledVersionInspect.test.tsx b/frontend/src/components/TorchInstalledVersionInspect.test.tsx index 9008851e4..6ff46080e 100644 --- a/frontend/src/components/TorchInstalledVersionInspect.test.tsx +++ b/frontend/src/components/TorchInstalledVersionInspect.test.tsx @@ -14,6 +14,7 @@ describe('installed Torch version review', () => { vi.stubGlobal('electronAPI', { get_torch_runtime_options: vi.fn().mockResolvedValue({ builds: ['cpu', 'cu130'], pythons: [], adapters: ['none', 'flux2'], + bundledPresetAvailable: false, defaultAdapter: 'flux2', preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, installed: [ { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled', qualification: 'qualified' }, diff --git a/frontend/src/components/TorchRuntimeProbePanel.test.tsx b/frontend/src/components/TorchRuntimeProbePanel.test.tsx index 38b581bec..a4a6e4e9a 100644 --- a/frontend/src/components/TorchRuntimeProbePanel.test.tsx +++ b/frontend/src/components/TorchRuntimeProbePanel.test.tsx @@ -13,6 +13,7 @@ const unconditionalStop = vi.fn<(profileId: string) => Promise>(); const getProfiles = vi.fn<() => Promise>().mockResolvedValue({ success: true, snapshot: { profiles: [] } }); const baseOptions: TorchRuntimeOptions = { builds: ['cpu'], pythons: [], adapters: ['none'], + bundledPresetAvailable: false, defaultAdapter: 'none', preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, installed: [], }; diff --git a/frontend/src/components/app-panels/TorchPanel.test.tsx b/frontend/src/components/app-panels/TorchPanel.test.tsx index 8836a809f..d4ce6db5c 100644 --- a/frontend/src/components/app-panels/TorchPanel.test.tsx +++ b/frontend/src/components/app-panels/TorchPanel.test.tsx @@ -199,12 +199,13 @@ describe('TorchPanel shared version controls', () => { }), get_torch_runtime_options: vi.fn().mockResolvedValue({ builds: ['cpu'], pythons: [{ id: 'python3.12', label: 'Python 3.12' }], - adapters: ['none'], preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, + adapters: ['none'], bundledPresetAvailable: false, defaultAdapter: 'none', + preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, }), preview_torch_runtime: vi.fn().mockResolvedValue({ status: 'resolved', preview: { previewId: 'preview-1', expiresInSeconds: 300, tag: candidateTag, build: 'cpu', python: 'python3.12', - adapter: 'flux2', qualification: 'unverified', artifacts: [], + adapter: 'none', qualification: 'unverified', artifacts: [], }, }), get_torch_runtime_probe: vi.fn().mockResolvedValue({ diff --git a/frontend/src/types/torch-install.ts b/frontend/src/types/torch-install.ts index d7ce0e97f..d455de3c6 100644 --- a/frontend/src/types/torch-install.ts +++ b/frontend/src/types/torch-install.ts @@ -14,6 +14,8 @@ export interface TorchRuntimeOptions { builds: string[]; pythons: Array<{ id: string; label: string }>; adapters: string[]; + bundledPresetAvailable: boolean; + defaultAdapter: 'none' | 'flux2'; preset: { tag: string; build: string; python: string; adapter: string }; installed: TorchInstalledConfig[]; } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4893709a7..5d0080bc2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2302,6 +2302,7 @@ dependencies = [ "chrono", "flate2", "futures", + "getrandom 0.3.4", "nix 0.29.0", "pumas-library", "regex", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1941e3609..e78d663ae 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -120,6 +120,11 @@ rustler = "0.34" nix = { version = "0.29", features = ["signal", "process"] } libc = "0.2" windows-sys = { version = "0.59", features = [ + "Win32_Security", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_JobObjects", + "Win32_NetworkManagement_IpHelper", + "Win32_Networking_WinSock", "Win32_Foundation", "Wdk_Storage_FileSystem", "Win32_System_IO", diff --git a/rust/crates/pumas-app-manager/Cargo.toml b/rust/crates/pumas-app-manager/Cargo.toml index 05096bd6c..a0d7b863d 100644 --- a/rust/crates/pumas-app-manager/Cargo.toml +++ b/rust/crates/pumas-app-manager/Cargo.toml @@ -48,6 +48,7 @@ futures = { workspace = true } # Hashing (for Ollama blob uploads) sha2 = { workspace = true } +getrandom = "=0.3.4" # Byte buffers (for streaming uploads) bytes = { workspace = true } diff --git a/rust/crates/pumas-app-manager/src/version_manager/installer.rs b/rust/crates/pumas-app-manager/src/version_manager/installer.rs index 6631b645e..09a7d2013 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/installer.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/installer.rs @@ -6,6 +6,7 @@ mod torch; #[cfg(all(test, target_os = "linux", target_arch = "x86_64"))] mod torch_tests; pub(crate) use torch::is_torch_runtime_release; +pub(crate) use torch::retry_pending_torch_cleanup; pub(crate) struct TorchInstallPlan { pub(crate) preview: crate::version_manager::TorchPreview, pub(crate) requirements: String, @@ -50,6 +51,7 @@ async fn path_exists(path: &Path) -> Result { pub(crate) struct TorchInstallControl(AtomicU8); type TorchCleanupCompletion = Shared>>>; +type TorchChildReceipt = tokio::sync::watch::Receiver>>>; #[derive(Default)] pub(crate) struct TorchCleanupTasks { @@ -62,9 +64,190 @@ struct TorchCleanupState { tasks: Vec>, failures: Vec, completion: Option, + child_drain_completion: Option, + residual_drain_completion: Option, + child_slots: Vec>, + child_receipts: std::collections::HashMap, +} + +fn start_child_drain( + slots: Vec>, +) -> TorchCleanupCompletion { + let worker = tokio::task::spawn_blocking(move || { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + for slot in slots { + loop { + let remaining = deadline.saturating_duration_since(std::time::Instant::now()); + if remaining.is_zero() && slot.is_active() { + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Torch child cleanup deadline elapsed", + )); + } + match slot.drain(remaining) { + Ok(_) => break, + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(20)); + } + Err(error) => return Err(error), + } + } + } + Ok::<(), std::io::Error>(()) + }); + async move { + worker + .await + .map_err(|error| Arc::new(error.to_string()))? + .map_err(|error| Arc::new(error.to_string())) + } + .boxed() + .shared() } impl TorchCleanupTasks { + pub(crate) fn new_child_slot( + &self, + ) -> Result> { + let mut state = self.state.lock().expect("Torch cleanup lock poisoned"); + if state.closed { + return Err(PumasError::InstallationFailed { + message: "Torch cleanup is closed".into(), + }); + } + state + .child_slots + .retain(|slot| slot.is_active() || Arc::strong_count(slot) > 1); + let retained: std::collections::HashSet = state + .child_slots + .iter() + .map(|slot| Arc::as_ptr(slot) as usize) + .collect(); + state.child_receipts.retain(|key, _| retained.contains(key)); + let slot = pumas_library::platform::managed_child::ManagedChildCustodySlot::new(); + let (completion_tx, completion_rx) = tokio::sync::watch::channel(None); + let supervised = slot.clone(); + state.tasks.push(tokio::spawn(async move { + let result = if supervised.wait_for_park_or_completion().await { + let draining = supervised.clone(); + let outcome = tokio::task::spawn_blocking(move || { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + loop { + let remaining = + deadline.saturating_duration_since(std::time::Instant::now()); + if remaining.is_zero() { + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Torch child cleanup deadline elapsed", + )); + } + match draining.drain(remaining) { + Ok(_) => return Ok(()), + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(20)); + } + Err(error) => return Err(error), + } + } + }) + .await; + match outcome { + Err(error) => Err(Arc::new(error.to_string())), + Ok(Err(error)) => Err(Arc::new(error.to_string())), + Ok(Ok(())) => Ok(()), + } + } else { + Ok(()) + }; + if let Err(error) = &result { + warn!(%error, "Torch child cleanup remains pending"); + } + let _ = completion_tx.send(Some(result)); + })); + state + .child_receipts + .insert(Arc::as_ptr(&slot) as usize, completion_rx); + state.child_slots.push(slot.clone()); + Ok(slot) + } + + pub(crate) async fn drain_child_slot( + &self, + slot: &Arc, + ) -> Result<()> { + let mut receipt = self + .state + .lock() + .expect("Torch cleanup lock poisoned") + .child_receipts + .get(&(Arc::as_ptr(slot) as usize)) + .cloned() + .ok_or_else(|| PumasError::InstallationFailed { + message: "Torch child custody receipt absent".into(), + })?; + loop { + if let Some(result) = receipt.borrow().clone() { + return result.map_err(|_| PumasError::InstallationFailed { + message: "Torch process cleanup incomplete; retry is required".into(), + }); + } + receipt + .changed() + .await + .map_err(|_| PumasError::InstallationFailed { + message: "Torch child cleanup supervisor stopped before receipt".into(), + })?; + } + } + + pub(crate) async fn drain_child_slots(&self) -> Result<()> { + let completion = { + let mut state = self.state.lock().expect("Torch cleanup lock poisoned"); + if let Some(completion) = &state.child_drain_completion { + completion.clone() + } else { + let completion = start_child_drain(state.child_slots.clone()); + state.child_drain_completion = Some(completion.clone()); + completion + } + }; + let result = completion.await; + self.state + .lock() + .expect("Torch cleanup lock poisoned") + .child_drain_completion = None; + result.map_err(|_| PumasError::InstallationFailed { + message: "Torch process cleanup incomplete; retry is required".into(), + }) + } + + pub(crate) async fn drain_residual_child_slots(&self) -> Result<()> { + let completion = { + let mut state = self.state.lock().expect("Torch cleanup lock poisoned"); + if let Some(completion) = &state.residual_drain_completion { + completion.clone() + } else { + let slots = state + .child_slots + .iter() + .filter(|slot| slot.is_cleanup_pending()) + .cloned() + .collect(); + let completion = start_child_drain(slots); + state.residual_drain_completion = Some(completion.clone()); + completion + } + }; + let result = completion.await; + self.state + .lock() + .expect("Torch cleanup lock poisoned") + .residual_drain_completion = None; + result.map_err(|_| PumasError::InstallationFailed { + message: "Torch process cleanup incomplete; retry is required".into(), + }) + } + pub(crate) fn schedule(&self, work: impl FnOnce() + Send + 'static) { let mut state = self.state.lock().expect("Torch cleanup lock poisoned"); if !state.closed { @@ -100,6 +283,10 @@ impl TorchCleanupTasks { } else { let tasks = std::mem::take(&mut state.tasks); let mut failures = std::mem::take(&mut state.failures); + // One-shot cleanup closures cannot be replayed after a panic. + // Preserve that failure receipt; durable stage/publication + // markers are retried at startup, and child slots have their + // own retryable drain completion. // This supervisor owns every handle even if all callers waiting // on the shared receipt are cancelled. let supervisor = tokio::spawn(async move { @@ -252,7 +439,10 @@ impl VersionInstaller { /// before shutting down their Tokio runtime. pub async fn shutdown_torch_cleanup(&self) -> Result<()> { self.torch_cleanup.close(); - self.torch_cleanup.drain().await + let tasks = self.torch_cleanup.drain().await; + let children = self.torch_cleanup.drain_child_slots().await; + tasks?; + children } pub(crate) fn with_torch_control(mut self, control: Arc) -> Self { @@ -1633,7 +1823,7 @@ impl VersionInstaller { .await; // Get Python version - let venv_python = version_dir.join("venv").join("bin").join("python"); + let venv_python = pumas_library::platform::paths::venv_python(version_dir); let python_version = if path_exists(&venv_python).await? { let output = tokio::process::Command::new(&venv_python) .args(["--version"]) diff --git a/rust/crates/pumas-app-manager/src/version_manager/installer/torch.rs b/rust/crates/pumas-app-manager/src/version_manager/installer/torch.rs index 9ad22db8b..992659b49 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/installer/torch.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/installer/torch.rs @@ -2,7 +2,7 @@ use super::*; use crate::torch_client::{SUPPORTED_TORCH_PROTOCOL, TORCH_IMAGE_GENERATION_CAPABILITY}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::process::Stdio; use std::time::Duration; @@ -11,6 +11,303 @@ use tokio::process::Command; const MAX_TORCH_ORPHAN_QUARANTINES: usize = 2; pub(super) const TORCH_PUBLISHING_MARKER: &[u8] = b"metadata pending"; +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +struct TorchDirectoryIdentity { + volume: u64, + file: u64, +} + +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct TorchPendingPublishMarker { + owner: String, + directory: TorchDirectoryIdentity, + source_stage: String, +} + +#[allow(unsafe_code)] +fn torch_directory_identity(path: &Path) -> std::io::Result { + let metadata = std::fs::symlink_metadata(path)?; + if !metadata.is_dir() || metadata.file_type().is_symlink() { + return Err(std::io::Error::other( + "Torch publication directory identity unavailable", + )); + } + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + Ok(TorchDirectoryIdentity { + volume: metadata.dev(), + file: metadata.ino(), + }) + } + #[cfg(windows)] + { + use std::mem::zeroed; + use std::os::windows::fs::OpenOptionsExt; + use std::os::windows::io::AsRawHandle; + #[repr(C)] + #[allow(dead_code)] + struct FileInformation { + attributes: u32, + creation_time: [u32; 2], + access_time: [u32; 2], + write_time: [u32; 2], + volume: u32, + size_high: u32, + size_low: u32, + links: u32, + file_high: u32, + file_low: u32, + } + #[link(name = "kernel32")] + unsafe extern "system" { + #[link_name = "GetFileInformationByHandle"] + fn get_file_information_by_handle( + handle: *mut std::ffi::c_void, + info: *mut FileInformation, + ) -> i32; + } + const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000; + const FILE_SHARE_READ: u32 = 0x0000_0001; + const FILE_SHARE_WRITE: u32 = 0x0000_0002; + const FILE_SHARE_DELETE: u32 = 0x0000_0004; + let directory = std::fs::OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .open(path)?; + let mut info: FileInformation = unsafe { zeroed() }; + // SAFETY: the directory handle remains live and info has the Win32 layout. + if unsafe { get_file_information_by_handle(directory.as_raw_handle(), &mut info) } == 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(TorchDirectoryIdentity { + volume: u64::from(info.volume), + file: (u64::from(info.file_high) << 32) | u64::from(info.file_low), + }) + } +} + +pub(super) fn write_pending_publish_marker(path: &Path, runtime: &Path) -> std::io::Result<()> { + use std::io::Write; + let contents = serde_json::to_vec(&TorchPendingPublishMarker { + owner: "metadata pending".into(), + directory: torch_directory_identity(runtime)?, + source_stage: runtime + .parent() + .and_then(Path::file_name) + .and_then(|name| name.to_str()) + .filter(|name| name.starts_with(".torch-install-")) + .ok_or_else(|| std::io::Error::other("Torch publication source stage absent"))? + .to_owned(), + }) + .map_err(std::io::Error::other)?; + let mut file = std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(path)?; + file.write_all(&contents)?; + file.sync_all()?; + #[cfg(unix)] + std::fs::File::open( + path.parent() + .ok_or_else(|| std::io::Error::other("Marker parent absent"))?, + )? + .sync_all()?; + Ok(()) +} + +/// The sibling marker is created before child admission and survives a failed +/// TempDir removal on Windows, where an open wheel or DLL can lock a stage. +pub(super) struct TorchPendingStage { + directory: tempfile::TempDir, + marker: PathBuf, +} + +impl TorchPendingStage { + fn new(versions_dir: &Path, tag: &str) -> Result { + let directory = tempfile::Builder::new() + .prefix(".torch-install-") + .tempdir_in(versions_dir) + .map_err(PumasError::from)?; + let name = directory + .path() + .file_name() + .ok_or_else(|| failed("Stage name absent"))?; + let marker = + versions_dir.join(format!(".torch-pending-cleanup-{}", name.to_string_lossy())); + std::fs::write(&marker, tag).map_err(PumasError::from)?; + Ok(Self { directory, marker }) + } + + fn path(&self) -> &Path { + self.directory.path() + } +} + +impl Drop for TorchPendingStage { + fn drop(&mut self) { + match std::fs::remove_dir_all(self.path()) { + Ok(()) => { + let _ = std::fs::remove_file(&self.marker); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + let _ = std::fs::remove_file(&self.marker); + } + Err(error) => { + warn!(%error, path = %self.path().display(), "Torch stage retained for pending cleanup") + } + } + } +} + +pub(crate) fn retry_pending_torch_cleanup( + versions_dir: &Path, + metadata_manager: &MetadataManager, +) -> std::io::Result<()> { + let entries = match std::fs::read_dir(versions_dir) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(error) => return Err(error), + }; + for entry in entries { + let entry = entry?; + let marker = entry.path(); + let Some(tag) = entry + .file_name() + .to_str() + .and_then(|name| name.strip_prefix(".torch-pending-publish-")) + .map(str::to_owned) + else { + continue; + }; + if stable_torch_tag(&tag).is_none() || !entry.file_type()?.is_file() { + continue; + } + let marker_bytes = std::fs::read(&marker)?; + let durable_owner = if marker_bytes == TORCH_PUBLISHING_MARKER { + None // Legacy marker: retain the inner marker as ownership proof. + } else { + let Ok(record) = serde_json::from_slice::(&marker_bytes) + else { + continue; + }; + if record.owner != "metadata pending" + || !record.source_stage.starts_with(".torch-install-") + || !record + .source_stage + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.') + { + continue; + } + Some((record.directory, record.source_stage)) + }; + let destination = versions_dir.join(&tag); + let registered = metadata_manager + .get_installed_version(&tag, Some(AppId::Torch)) + .map_err(std::io::Error::other)? + .is_some(); + if registered { + let inner = destination.join(".pumas-publishing"); + match std::fs::remove_file(&inner) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => { + warn!(%error, path = %inner.display(), "Registered Torch marker cleanup remains pending"); + continue; + } + } + std::fs::remove_file(marker)?; + continue; + } + // The original directory still at its source means the rename never + // published it. Release the pending marker before stage cleanup can + // delete the source and allow native file IDs to be reused. + if let Some((expected, source_stage)) = &durable_owner { + let source = versions_dir.join(source_stage).join("runtime"); + if torch_directory_identity(&source).ok() == Some(*expected) { + std::fs::remove_file(marker)?; + continue; + } + } + if destination.exists() { + if let Some((expected, _)) = &durable_owner { + if torch_directory_identity(&destination).ok() != Some(*expected) { + warn!(path = %destination.display(), "Torch pending publication does not own destination identity"); + continue; + } + } + match std::fs::read(destination.join(".pumas-publishing")) { + Ok(contents) if contents == TORCH_PUBLISHING_MARKER => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + if durable_owner.is_none() { + warn!(path = %destination.display(), "Torch publication ownership marker absent; pending cleanup retained"); + continue; + } + } + Ok(_) => continue, + Err(error) => return Err(error), + } + if let Err(error) = std::fs::remove_dir_all(&destination) { + warn!(%error, path = %destination.display(), "Unregistered Torch publication remains pending cleanup"); + continue; + } + } + std::fs::remove_file(marker)?; + } + let entries = match std::fs::read_dir(versions_dir) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(error) => return Err(error), + }; + for entry in entries { + let entry = entry?; + let marker = entry.path(); + let Some(stage_name) = entry + .file_name() + .to_str() + .and_then(|name| name.strip_prefix(".torch-pending-cleanup-")) + .map(str::to_owned) + else { + continue; + }; + if !stage_name.starts_with(".torch-install-") + || !stage_name + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.') + || !entry.file_type()?.is_file() + { + continue; + } + let tag = std::fs::read_to_string(&marker)?; + if stable_torch_tag(&tag).is_none() { + continue; + } + let stage = versions_dir.join(&stage_name); + let quarantine = versions_dir.join(format!(".torch-quarantine-{stage_name}")); + if stage.exists() && !quarantine.exists() { + if let Err(error) = + pumas_library::platform::filesystem::rename_directory_noreplace(&stage, &quarantine) + { + warn!(%error, path = %stage.display(), "Locked Torch stage remains pending cleanup"); + continue; + } + } + if quarantine.exists() { + if let Err(error) = std::fs::remove_dir_all(&quarantine) { + warn!(%error, path = %quarantine.display(), "Torch quarantine remains pending cleanup"); + continue; + } + } + if !stage.exists() && !quarantine.exists() { + std::fs::remove_file(marker)?; + } + } + Ok(()) +} + struct TorchOrphanQuarantine { tag: String, timestamp_ms: u64, @@ -37,6 +334,123 @@ impl TorchPublicationPause { #[path = "torch_upstream_contract_tests.rs"] mod torch_upstream_contract_tests; +#[cfg(all(test, windows))] +mod windows_cleanup_tests { + use super::*; + use std::os::windows::fs::OpenOptionsExt; + + #[test] + fn locked_stage_stays_owned_and_retries_after_unlock() { + let root = tempfile::tempdir().unwrap(); + let versions = root.path(); + let stage = versions.join(".torch-install-owned"); + std::fs::create_dir(&stage).unwrap(); + let payload = stage.join("locked.bin"); + std::fs::write(&payload, b"payload").unwrap(); + let marker = versions.join(".torch-pending-cleanup-.torch-install-owned"); + std::fs::write(&marker, "v2.9.1").unwrap(); + let held = std::fs::OpenOptions::new() + .read(true) + .share_mode(1) + .open(&payload) + .unwrap(); + let metadata = MetadataManager::new(versions); + retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(marker.exists()); + assert!( + stage.exists() + || versions + .join(".torch-quarantine-.torch-install-owned") + .exists() + ); + drop(held); + retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(!stage.exists()); + assert!(!marker.exists()); + } + + #[test] + fn locked_unregistered_publication_retries_after_unlock() { + let root = tempfile::tempdir().unwrap(); + let versions = root.path(); + let stage = versions.join(".torch-install-owned"); + let runtime = stage.join("runtime"); + std::fs::create_dir_all(&runtime).unwrap(); + std::fs::write(runtime.join(".pumas-publishing"), TORCH_PUBLISHING_MARKER).unwrap(); + let payload = runtime.join("locked.bin"); + std::fs::write(&payload, b"payload").unwrap(); + let marker = versions.join(".torch-pending-publish-v2.9.1"); + write_pending_publish_marker(&marker, &runtime).unwrap(); + let destination = versions.join("v2.9.1"); + std::fs::rename(&runtime, &destination).unwrap(); + let held = std::fs::OpenOptions::new() + .read(true) + .share_mode(1) + .open(destination.join("locked.bin")) + .unwrap(); + let metadata = MetadataManager::new(versions); + retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(marker.exists() && destination.exists()); + drop(held); + retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(!marker.exists() && !destination.exists()); + } +} + +#[cfg(all(test, target_os = "linux"))] +mod publication_identity_tests { + use super::*; + + #[test] + fn durable_identity_reclaims_partial_publication_after_inner_marker_loss() { + let root = tempfile::tempdir().unwrap(); + let stage = root.path().join(".torch-install-owned"); + let runtime = stage.join("runtime"); + std::fs::create_dir_all(&runtime).unwrap(); + std::fs::write(runtime.join(".pumas-publishing"), TORCH_PUBLISHING_MARKER).unwrap(); + std::fs::write(runtime.join("payload"), b"owned").unwrap(); + let marker = root.path().join(".torch-pending-publish-v2.9.1"); + write_pending_publish_marker(&marker, &runtime).unwrap(); + let destination = root.path().join("v2.9.1"); + std::fs::rename(&runtime, &destination).unwrap(); + std::fs::remove_file(destination.join(".pumas-publishing")).unwrap(); + let metadata = MetadataManager::new(root.path()); + retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + assert!(!marker.exists() && !destination.exists()); + } + + #[test] + fn pre_rename_marker_cannot_delete_raced_unrelated_destination() { + let root = tempfile::tempdir().unwrap(); + let stage = root.path().join(".torch-install-owned"); + let runtime = stage.join("runtime"); + std::fs::create_dir_all(&runtime).unwrap(); + std::fs::write( + root.path() + .join(".torch-pending-cleanup-.torch-install-owned"), + "v2.9.1", + ) + .unwrap(); + let marker = root.path().join(".torch-pending-publish-v2.9.1"); + write_pending_publish_marker(&marker, &runtime).unwrap(); + let unrelated = root.path().join("v2.9.1"); + std::fs::create_dir(&unrelated).unwrap(); + std::fs::write(unrelated.join("payload"), b"unrelated").unwrap(); + let metadata = MetadataManager::new(root.path()); + retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + assert_eq!( + std::fs::read(unrelated.join("payload")).unwrap(), + b"unrelated" + ); + assert!(!marker.exists()); + assert!(!stage.exists()); + assert!(!root + .path() + .join(".torch-pending-cleanup-.torch-install-owned") + .exists()); + } +} + pub(crate) struct TorchRuntimeRecipe { #[cfg(test)] pub(crate) release_tag: &'static str, @@ -304,11 +718,11 @@ impl VersionInstaller { async fn stage_resolved_torch_runtime( &self, plan: &TorchInstallPlan, - staging: &Path, + staging: &std::sync::Arc, log_path: &Path, progress_tx: &mpsc::Sender, ) -> Result { - let runtime = staging.join("runtime"); + let runtime = staging.path().join("runtime"); let runtime_for_write = runtime.clone(); tokio::task::spawn_blocking(move || write_embedded_torch_runtime(&runtime_for_write)) .await @@ -331,6 +745,7 @@ impl VersionInstaller { log_path, &format!("Creating Python environment with {}", interpreter.display()), progress_tx, + Some(staging.clone()), ) .await?; fs::write(runtime.join("requirements.txt"), &plan.requirements) @@ -358,13 +773,10 @@ impl VersionInstaller { ) .await .map_err(PumasError::from)?; - let python = runtime.join("venv/bin/python"); - let mut install = Command::new(&python); + let python = pumas_library::platform::paths::venv_python(&runtime); + let mut install = Command::new(pumas_library::platform::paths::venv_pip(&runtime)); install .args([ - "-I", - "-m", - "pip", "--isolated", "install", "--no-deps", @@ -381,6 +793,7 @@ impl VersionInstaller { log_path, "Installing resolved wheel artifacts", progress_tx, + Some(staging.clone()), ) .await?; let mut probe = Command::new(&python); @@ -390,6 +803,7 @@ impl VersionInstaller { log_path, "Checking installed Torch identity and CPU operation", progress_tx, + Some(staging.clone()), ) .await?; Ok(runtime) @@ -421,17 +835,26 @@ impl VersionInstaller { progress_tx: mpsc::Sender, plan: Option, ) -> Result<()> { - if !cfg!(all(target_os = "linux", target_arch = "x86_64")) { - return Err(failed("Managed Torch requires Linux x86_64")); + if !cfg!(any( + all(target_os = "linux", target_arch = "x86_64"), + all( + target_os = "windows", + target_arch = "x86_64", + target_env = "msvc" + ), + all(target_os = "macos", target_arch = "aarch64") + )) { + return Err(failed("Managed Torch is unsupported on this platform")); } if tag != release.tag_name || !is_torch_runtime_release(release) { return Err(failed( "Unsupported upstream PyTorch release or mismatched tag", )); } - let recipe = if plan - .as_ref() - .is_none_or(|p| p.preview.qualification == "qualified") + let recipe = if cfg!(all(target_os = "linux", target_arch = "x86_64")) + && plan + .as_ref() + .is_none_or(|p| p.preview.qualification == "qualified") { torch_recipe_for_tag(tag) } else { @@ -450,6 +873,9 @@ impl VersionInstaller { fs::create_dir_all(&versions_dir) .await .map_err(PumasError::from)?; + self.torch_cleanup.drain_residual_child_slots().await?; + retry_pending_torch_cleanup(&versions_dir, &self.metadata_manager) + .map_err(PumasError::from)?; schedule_torch_orphan_prune( &self.torch_cleanup, &versions_dir, @@ -506,10 +932,7 @@ impl VersionInstaller { } // Staging shares the publication filesystem; failed attempts never enter // installed-version state. TempDir removes this attempt on every exit. - let staging = tempfile::Builder::new() - .prefix(".torch-install-") - .tempdir_in(&versions_dir) - .map_err(PumasError::from)?; + let staging = std::sync::Arc::new(TorchPendingStage::new(&versions_dir, tag)?); let logs = self.logs_dir(); fs::create_dir_all(&logs).await.map_err(PumasError::from)?; let log_path = logs.join(format!( @@ -528,7 +951,7 @@ impl VersionInstaller { tag, recipe, plan.as_ref(), - staging.path(), + &staging, &log_path, &progress_tx, ) @@ -567,32 +990,36 @@ impl VersionInstaller { .map_err(|e| failed(format!("Publication pause failed: {e}")))? .forget(); } + let pending = versions_dir.join(format!(".torch-pending-publish-{tag}")); + write_pending_publish_marker(&pending, &runtime).map_err(PumasError::from)?; let publish_to = destination.clone(); - tokio::task::spawn_blocking(move || { + let published = tokio::task::spawn_blocking(move || { pumas_library::platform::filesystem::rename_directory_noreplace( &runtime, &publish_to, ) }) .await - .map_err(|error| failed(format!("Runtime publication task failed: {error}")))? - .map_err(PumasError::from)?; + .map_err(|error| failed(format!("Runtime publication task failed: {error}")))?; + if let Err(error) = published { + fs::remove_file(&pending).await.map_err(PumasError::from)?; + return Err(PumasError::from(error)); + } let result = self .finalize_installation(tag, release, &destination, &progress_tx) .await; if result.is_err() { - // Only our newly published directory is compensated. - fs::remove_dir_all(&destination) - .await - .map_err(PumasError::from)?; + // The durable ownership marker survives a Windows + // file lock and is retried at startup and install. + match fs::remove_dir_all(&destination).await { + Ok(()) => fs::remove_file(&pending).await.map_err(PumasError::from)?, + Err(error) => warn!(%error, path = %destination.display(), "Unregistered Torch publication retained for cleanup"), + } } if result.is_ok() { - if let Err(error) = - fs::remove_file(destination.join(".pumas-publishing")).await - { - warn!( - "Installed Torch publication marker could not be removed: {error}" - ); + match fs::remove_file(destination.join(".pumas-publishing")).await { + Ok(()) => fs::remove_file(&pending).await.map_err(PumasError::from)?, + Err(error) => warn!(%error, "Installed Torch publication marker could not be removed"), } schedule_torch_orphan_prune( &self.torch_cleanup, @@ -621,13 +1048,13 @@ impl VersionInstaller { _tag: &str, recipe_spec: Option<&TorchRuntimeRecipe>, plan: Option<&TorchInstallPlan>, - staging: &Path, + staging: &std::sync::Arc, log_path: &Path, progress_tx: &mpsc::Sender, ) -> Result { #[cfg(test)] if let Some(stage) = &self.torch_stage_override { - return stage(staging); + return stage(staging.path()); } if let Some(plan) = plan.filter(|p| p.preview.qualification != "qualified") { return self @@ -635,7 +1062,7 @@ impl VersionInstaller { .await; } let recipe_spec = recipe_spec.expect("checked above"); - let runtime = staging.join("runtime"); + let runtime = staging.path().join("runtime"); let runtime_for_write = runtime.clone(); tokio::task::spawn_blocking(move || write_embedded_torch_runtime(&runtime_for_write)) .await @@ -688,19 +1115,28 @@ impl VersionInstaller { "-c", "import sys; assert sys.version_info[:2] == (3,12)", ]); - self.run_runtime_command(python_check, log_path, "Checking Python 3.12", progress_tx) - .await?; + self.run_runtime_command( + python_check, + log_path, + "Checking Python 3.12", + progress_tx, + Some(staging.clone()), + ) + .await?; let mut venv = Command::new(interpreter); venv.args(["-I", "-m", "venv"]).arg(runtime.join("venv")); - self.run_runtime_command(venv, log_path, "Creating managed environment", progress_tx) - .await?; - let python = runtime.join("venv/bin/python"); - let mut install = Command::new(&python); + self.run_runtime_command( + venv, + log_path, + "Creating managed environment", + progress_tx, + Some(staging.clone()), + ) + .await?; + let python = pumas_library::platform::paths::venv_python(&runtime); + let mut install = Command::new(pumas_library::platform::paths::venv_pip(&runtime)); install .args([ - "-I", - "-m", - "pip", "--isolated", "install", "--require-hashes", @@ -716,6 +1152,7 @@ impl VersionInstaller { log_path, "Installing locked runtime dependencies", progress_tx, + Some(staging.clone()), ) .await?; let mut validate = Command::new(&python); @@ -729,6 +1166,7 @@ impl VersionInstaller { log_path, "Validating GPU and sidecar protocol", progress_tx, + Some(staging.clone()), ) .await?; let resolution = serde_json::json!({ @@ -751,6 +1189,7 @@ impl VersionInstaller { log_path, "Recording core and adapter probe evidence", progress_tx, + Some(staging.clone()), ) .await?; Ok(runtime) @@ -762,6 +1201,7 @@ impl VersionInstaller { log_path: &Path, stage: &str, progress_tx: &mpsc::Sender, + stage_lease: Option>, ) -> Result<()> { self.check_cancelled()?; self.progress_tracker.write().await.update_stage( @@ -779,84 +1219,49 @@ impl VersionInstaller { .map_err(PumasError::from)?; command .stdout(Stdio::from(log.try_clone().map_err(PumasError::from)?)) - .stderr(Stdio::from(log)) - .kill_on_drop(true); - #[cfg(not(target_os = "linux"))] - return Err(failed("Managed Torch process supervision requires Linux")); - #[cfg(target_os = "linux")] - { - use pumas_library::platform::linux_group; - linux_group::ensure_supported().map_err(PumasError::from)?; - command.process_group(0); - let mut child = command - .spawn() - .map_err(|error| failed(format!("{stage}: {error}")))?; - let pid = child - .id() - .ok_or_else(|| failed("Installer process has no owned PID"))?; - let deadline = tokio::time::Instant::now() + Duration::from_secs(3600); - loop { - // Observe without reaping: the leader pins the numeric PGID until - // every cooperating child (including a GPU health probe) is dead. - let observed = linux_group::observe_exit(pid); - let cancelled = self.cancel_flag.load(Ordering::SeqCst); - let timed_out = tokio::time::Instant::now() >= deadline; - if cancelled || timed_out || !matches!(&observed, Ok(None)) { - finish_process_group(&mut child, pid).await?; - self.check_cancelled()?; - if timed_out { - return Err(failed(format!( - "{stage} exceeded the installation deadline" - ))); - } - let status = observed - .map_err(PumasError::from)? - .ok_or_else(|| failed("Missing terminal installer status"))?; - return if status.success() { - Ok(()) - } else { - Err(failed(format!( - "{stage} failed ({status}); see installation log" - ))) - }; - } - tokio::time::sleep(Duration::from_millis(100)).await; - } + .stderr(Stdio::from(log)); + let custody = self.torch_cleanup.new_child_slot()?; + let mut child = pumas_library::platform::managed_child::ManagedChild::spawn( + command.as_std_mut(), + custody.clone(), + ) + .map_err(|error| failed(format!("{stage}: {error}")))?; + if let Some(stage_lease) = stage_lease { + child.attach_cleanup_lease(stage_lease); } - } -} - -#[cfg(target_os = "linux")] -async fn finish_process_group(child: &mut tokio::process::Child, pid: u32) -> Result<()> { - use pumas_library::platform::linux_group; - let group = i32::try_from(pid).map_err(|_| failed("Invalid installer process ID"))?; - let mut first_failure = None; - loop { - let observation = match linux_group::signal_group(pid) { - Ok(()) => { - tokio::task::spawn_blocking(move || linux_group::group_has_live_members(group)) + let deadline = tokio::time::Instant::now() + Duration::from_secs(3600); + loop { + let observed = child.observe_exit(); + let cancelled = self.cancel_flag.load(Ordering::SeqCst); + let timed_out = tokio::time::Instant::now() >= deadline; + if cancelled || timed_out || !matches!(&observed, Ok(None)) { + drop(child); + self.torch_cleanup + .drain_child_slot(&custody) .await - .map_err(|error| failed(format!("Process-group observation failed: {error}"))) - .and_then(|result| result.map_err(PumasError::from)) - } - Err(error) => Err(PumasError::from(error)), - }; - match observation { - Ok(false) => break, - Ok(true) => {} - Err(error) => { - if first_failure.is_none() { - warn!("Holding runtime staging until process cleanup completes: {error}"); + .map_err(|_| { + failed( + "Torch installer cleanup incomplete; owned process cleanup remains pending", + ) + })?; + self.check_cancelled()?; + if timed_out { + return Err(failed(format!( + "{stage} exceeded the installation deadline" + ))); } - first_failure.get_or_insert(error); + let status = observed + .map_err(PumasError::from)? + .ok_or_else(|| failed("Missing terminal installer status"))?; + return if status.success() { + Ok(()) + } else { + Err(failed(format!( + "{stage} failed ({status}); see installation log" + ))) + }; } + tokio::time::sleep(Duration::from_millis(100)).await; } - tokio::time::sleep(Duration::from_millis(100)).await; - } - // Reaping is deliberately last; no signal may use this PID afterwards. - child.wait().await.map_err(PumasError::from)?; - match first_failure { - Some(error) => Err(error), - None => Ok(()), } } diff --git a/rust/crates/pumas-app-manager/src/version_manager/installer/torch_tests.rs b/rust/crates/pumas-app-manager/src/version_manager/installer/torch_tests.rs index b54a775e1..ca11e9d80 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/installer/torch_tests.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/installer/torch_tests.rs @@ -67,6 +67,92 @@ fn assert_no_staging(installer: &VersionInstaller) { } } +#[test] +fn pending_stage_quarantine_retries_after_obstruction_clears() { + let root = tempfile::tempdir().unwrap(); + let versions = root.path(); + let stage = versions.join(".torch-install-owned"); + let quarantine = versions.join(".torch-quarantine-.torch-install-owned"); + let marker = versions.join(".torch-pending-cleanup-.torch-install-owned"); + std::fs::create_dir(&stage).unwrap(); + std::fs::write(stage.join("payload"), "staged").unwrap(); + std::fs::write(&marker, "v2.9.1").unwrap(); + std::fs::write(&quarantine, "obstruction").unwrap(); + let metadata = MetadataManager::new(versions); + torch::retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(stage.exists() && marker.exists()); + std::fs::remove_file(&quarantine).unwrap(); + torch::retry_pending_torch_cleanup(versions, &metadata).unwrap(); + assert!(!stage.exists() && !quarantine.exists() && !marker.exists()); +} + +#[test] +fn unregistered_publication_retry_removes_owned_directory() { + let root = tempfile::tempdir().unwrap(); + let destination = root.path().join("v2.9.1"); + std::fs::create_dir(&destination).unwrap(); + std::fs::write( + destination.join(".pumas-publishing"), + torch::TORCH_PUBLISHING_MARKER, + ) + .unwrap(); + std::fs::write(destination.join("payload"), "unregistered").unwrap(); + let marker = root.path().join(".torch-pending-publish-v2.9.1"); + std::fs::write(&marker, torch::TORCH_PUBLISHING_MARKER).unwrap(); + let metadata = MetadataManager::new(root.path()); + torch::retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + assert!(!destination.exists() && !marker.exists()); +} + +#[test] +fn registered_publication_recovery_preserves_runtime() { + let root = tempfile::tempdir().unwrap(); + let metadata = MetadataManager::new(root.path()); + metadata.ensure_directories().unwrap(); + let destination = root.path().join("v2.9.1"); + std::fs::create_dir(&destination).unwrap(); + std::fs::write( + destination.join(".pumas-publishing"), + torch::TORCH_PUBLISHING_MARKER, + ) + .unwrap(); + std::fs::write(destination.join("payload"), "registered").unwrap(); + let marker = root.path().join(".torch-pending-publish-v2.9.1"); + std::fs::write(&marker, torch::TORCH_PUBLISHING_MARKER).unwrap(); + metadata + .update_installed_version( + "v2.9.1", + InstalledVersionMetadata { + path: "v2.9.1".into(), + release_tag: "v2.9.1".into(), + ..Default::default() + }, + Some(AppId::Torch), + ) + .unwrap(); + torch::retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + assert_eq!( + std::fs::read_to_string(destination.join("payload")).unwrap(), + "registered" + ); + assert!(!marker.exists()); + assert!(!destination.join(".pumas-publishing").exists()); +} + +#[test] +fn partial_publication_cleanup_preserves_ambiguous_directory() { + let root = tempfile::tempdir().unwrap(); + let metadata = MetadataManager::new(root.path()); + let destination = root.path().join("v2.9.1"); + std::fs::create_dir(&destination).unwrap(); + std::fs::write(destination.join("locked-payload"), "unfinished").unwrap(); + let marker = root.path().join(".torch-pending-publish-v2.9.1"); + std::fs::write(&marker, torch::TORCH_PUBLISHING_MARKER).unwrap(); + torch::retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + torch::retry_pending_torch_cleanup(root.path(), &metadata).unwrap(); + assert!(destination.join("locked-payload").exists() && marker.exists()); +} + #[tokio::test] async fn direct_install_rejects_legacy_custom_tag_without_changing_existing_runtime() { let (installer, _root) = fixture_installer(); @@ -326,6 +412,28 @@ async fn cancelled_cleanup_drain_waiter_can_retry_without_losing_task() { .unwrap(); } +#[tokio::test] +async fn cancelled_child_drain_waiter_retains_slot_for_retry() { + let cleanup = Arc::new(TorchCleanupTasks::default()); + let slot = cleanup.new_child_slot().unwrap(); + let mut command = std::process::Command::new("sh"); + command.args(["-c", "sleep 30"]); + let child = + pumas_library::platform::managed_child::ManagedChild::spawn(&mut command, slot.clone()) + .unwrap(); + let waiting_cleanup = cleanup.clone(); + let waiter = tokio::spawn(async move { waiting_cleanup.drain_child_slots().await }); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + assert!(!waiter.is_finished()); + waiter.abort(); + assert!(waiter.await.unwrap_err().is_cancelled()); + assert!(slot.is_active()); + drop(child); + cleanup.drain_child_slots().await.unwrap(); + assert!(!slot.is_active()); + assert!(!slot.has_parked_child()); +} + #[tokio::test] async fn cancelling_a_runtime_command_reaps_its_process_group() { let (installer, root) = fixture_installer(); @@ -339,7 +447,13 @@ async fn cancelling_a_runtime_command_reaps_its_process_group() { let (tx, _rx) = mpsc::channel(16); let result = tokio::time::timeout( std::time::Duration::from_secs(5), - installer.run_runtime_command(command, &root.path().join("child.log"), "test child", &tx), + installer.run_runtime_command( + command, + &root.path().join("child.log"), + "test child", + &tx, + None, + ), ) .await .expect("cancelled command did not terminate promptly"); diff --git a/rust/crates/pumas-app-manager/src/version_manager/installer/torch_upstream_contract_tests.rs b/rust/crates/pumas-app-manager/src/version_manager/installer/torch_upstream_contract_tests.rs index 20c87b304..a8e0eb9e7 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/installer/torch_upstream_contract_tests.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/installer/torch_upstream_contract_tests.rs @@ -165,7 +165,8 @@ async fn manager_filters_upstream_releases_and_preserves_legacy_runtime_state() let mut installed = HashMap::new(); for tag in legacy_tags { let runtime = root.path().join("torch-versions").join(tag); - std::fs::create_dir_all(runtime.join("venv/bin")).unwrap(); + let python = pumas_library::platform::paths::venv_python(&runtime); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); let recipe = if tag == "0.1.6" { r#"{"recipe_id":"torch-runtime-0.1.6"}"# } else { @@ -174,7 +175,7 @@ async fn manager_filters_upstream_releases_and_preserves_legacy_runtime_state() std::fs::write(runtime.join("runtime.json"), recipe).unwrap(); std::fs::write(runtime.join("serve.py"), "# legacy fixture\n").unwrap(); std::fs::write(runtime.join("requirements.txt"), "# legacy fixture\n").unwrap(); - std::fs::write(runtime.join("venv/bin/python"), "# fixture\n").unwrap(); + std::fs::write(python, "# fixture\n").unwrap(); installed.insert( tag.to_string(), InstalledVersionMetadata { @@ -304,7 +305,8 @@ fn mock_runtime_for_publication( staging: &std::path::Path, ) -> pumas_library::Result { let runtime = staging.join("runtime"); - std::fs::create_dir_all(runtime.join("venv/bin")).map_err(PumasError::from)?; + let python = pumas_library::platform::paths::venv_python(&runtime); + std::fs::create_dir_all(python.parent().unwrap()).map_err(PumasError::from)?; std::fs::write( runtime.join("runtime.json"), r#"{"recipe_id":"torch-upstream-2.9.1-r1"}"#, @@ -317,8 +319,7 @@ fn mock_runtime_for_publication( "# isolated publication fixture\n", ) .map_err(PumasError::from)?; - std::fs::write(runtime.join("venv/bin/python"), "# fake interpreter\n") - .map_err(PumasError::from)?; + std::fs::write(python, "# fake interpreter\n").map_err(PumasError::from)?; Ok(runtime) } diff --git a/rust/crates/pumas-app-manager/src/version_manager/mod.rs b/rust/crates/pumas-app-manager/src/version_manager/mod.rs index 8a8c343c2..e9eda9a34 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/mod.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/mod.rs @@ -587,7 +587,10 @@ impl VersionManager { let _ = self.cancel_installation().await?; let _install_guard = self.install_lock.lock().await; self.torch_cleanup.close(); - self.torch_cleanup.drain().await + let tasks = self.torch_cleanup.drain().await; + let children = self.torch_cleanup.drain_child_slots().await; + tasks?; + children } /// Install a version with progress channel. @@ -1154,26 +1157,26 @@ mod tests { } async fn register_test_version(manager: &VersionManager, tag: &str) { - std::fs::create_dir_all(manager.version_path(tag)).unwrap(); - std::fs::create_dir_all(manager.version_path(tag).join("venv/bin")).unwrap(); - for required in [ - "runtime.json", - "serve.py", - "venv/bin/python", - "requirements.txt", - ] { - std::fs::write(manager.version_path(tag).join(required), b"test fixture").unwrap(); + let runtime = manager.version_path(tag); + std::fs::create_dir_all(&runtime).unwrap(); + let python = pumas_library::platform::paths::venv_python(&runtime); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); + for required in ["runtime.json", "serve.py", "requirements.txt"] { + std::fs::write(runtime.join(required), b"test fixture").unwrap(); } + std::fs::write(&python, b"test fixture").unwrap(); if manager.app_id == AppId::Torch { std::fs::write( - manager.version_path(tag).join("resolution.json"), + runtime.join("resolution.json"), r#"{"torch":"test-fixture"}"#, ) .unwrap(); - let python = manager.version_path(tag).join("venv/bin/python"); std::fs::write(&python, b"#!/bin/sh\necho test-fixture\n").unwrap(); - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(&python, std::fs::Permissions::from_mode(0o755)).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&python, std::fs::Permissions::from_mode(0o755)).unwrap(); + } } let metadata = pumas_library::metadata::InstalledVersionMetadata { path: tag.to_string(), @@ -1218,6 +1221,7 @@ mod tests { assert!(error.to_string().contains("identity manifest")); } + #[cfg(unix)] #[tokio::test] async fn qualified_legacy_torch_activation_uses_trusted_recipe_identity() { let (manager, _root) = create_torch_test_manager().await; @@ -1230,7 +1234,7 @@ mod tests { ) .unwrap(); std::fs::write( - runtime.join("venv/bin/python"), + pumas_library::platform::paths::venv_python(&runtime), b"#!/bin/sh\necho 2.9.1+cu130\n", ) .unwrap(); diff --git a/rust/crates/pumas-app-manager/src/version_manager/state.rs b/rust/crates/pumas-app-manager/src/version_manager/state.rs index e02797839..3f1db0791 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/state.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/state.rs @@ -116,6 +116,18 @@ impl VersionState { /// Initialize state from metadata and filesystem. async fn initialize(&mut self) -> Result<()> { + if self.app_id == AppId::Torch { + let versions_dir = self.launcher_root.join("torch-versions"); + let metadata = self.metadata_manager.clone(); + tokio::task::spawn_blocking(move || { + super::installer::retry_pending_torch_cleanup(&versions_dir, &metadata) + }) + .await + .map_err(|error| { + PumasError::Other(format!("Torch cleanup recovery task failed: {error}")) + })? + .map_err(PumasError::from)?; + } self.normalize_llama_cpp_legacy_sycl_variants().await?; // Load metadata @@ -660,7 +672,7 @@ impl VersionState { AppId::Torch => vec![ version_path.join("runtime.json"), version_path.join("serve.py"), - version_path.join("venv/bin/python"), + pumas_library::platform::paths::venv_python(version_path), version_path.join("requirements.txt"), ], _ => { @@ -782,8 +794,9 @@ mod tests { let version_dir = temp.path().join("torch-versions/v1.0.0"); std::fs::create_dir_all(&version_dir).unwrap(); std::fs::write(version_dir.join("main.py"), "# main").unwrap(); - std::fs::create_dir_all(version_dir.join("venv/bin")).unwrap(); - std::fs::write(version_dir.join("venv/bin/python"), "#!/bin/python").unwrap(); + let python = pumas_library::platform::paths::venv_python(&version_dir); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); + std::fs::write(python, "#!/bin/python").unwrap(); let metadata = InstalledVersionMetadata { path: "v1.0.0".to_string(), @@ -806,8 +819,9 @@ mod tests { let version_dir = temp.path().join("torch-versions/v1.0.0"); std::fs::create_dir_all(&version_dir).unwrap(); std::fs::write(version_dir.join("main.py"), "# main").unwrap(); - std::fs::create_dir_all(version_dir.join("venv/bin")).unwrap(); - std::fs::write(version_dir.join("venv/bin/python"), "#!/bin/python").unwrap(); + let python = pumas_library::platform::paths::venv_python(&version_dir); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); + std::fs::write(python, "#!/bin/python").unwrap(); let metadata = InstalledVersionMetadata { path: "v1.0.0".to_string(), @@ -848,8 +862,9 @@ mod tests { let version_dir = temp.path().join("torch-versions/v1.0.0"); std::fs::create_dir_all(&version_dir).unwrap(); std::fs::write(version_dir.join("main.py"), "# main").unwrap(); - std::fs::create_dir_all(version_dir.join("venv/bin")).unwrap(); - std::fs::write(version_dir.join("venv/bin/python"), "#!/bin/python").unwrap(); + let python = pumas_library::platform::paths::venv_python(&version_dir); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); + std::fs::write(python, "#!/bin/python").unwrap(); let metadata = InstalledVersionMetadata { path: "v1.0.0".to_string(), @@ -876,8 +891,9 @@ mod tests { let version_dir = temp.path().join("torch-versions/v1.0.0"); std::fs::create_dir_all(&version_dir).unwrap(); std::fs::write(version_dir.join("main.py"), "# main").unwrap(); - std::fs::create_dir_all(version_dir.join("venv/bin")).unwrap(); - std::fs::write(version_dir.join("venv/bin/python"), "#!/bin/python").unwrap(); + let python = pumas_library::platform::paths::venv_python(&version_dir); + std::fs::create_dir_all(python.parent().unwrap()).unwrap(); + std::fs::write(python, "#!/bin/python").unwrap(); let metadata = InstalledVersionMetadata { path: "v1.0.0".to_string(), diff --git a/rust/crates/pumas-app-manager/src/version_manager/torch_alternatives.rs b/rust/crates/pumas-app-manager/src/version_manager/torch_alternatives.rs index da92efb75..c069225e2 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/torch_alternatives.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/torch_alternatives.rs @@ -1,7 +1,7 @@ //! Bounded discovery of official Torch wheels for installed Python interpreters. //! A wheel match is only a lead; dependencies and adapters are not resolved. -use super::torch_preview::{valid_torch_channel, BUILDS, PYTHONS}; +use super::torch_preview::{valid_torch_channel, PYTHONS}; use super::*; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; @@ -13,6 +13,35 @@ const PYTHON_CHECK_TIMEOUT: Duration = Duration::from_secs(3); // Leave room for interpreter checks, the bounded index scan, and process startup. const RELEASE_OPTIONS_TIMEOUT: Duration = Duration::from_secs(60); +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum TorchHostTarget { + LinuxX8664, + WindowsX8664, + MacosArm64, +} + +impl TorchHostTarget { + fn current() -> Option { + if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + Some(Self::LinuxX8664) + } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) { + Some(Self::WindowsX8664) + } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + Some(Self::MacosArm64) + } else { + None + } + } + + fn label(self) -> &'static str { + match self { + Self::LinuxX8664 => "Linux x86_64", + Self::WindowsX8664 => "Windows x86_64", + Self::MacosArm64 => "macOS arm64", + } + } +} + #[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "snake_case")] pub enum TorchReleaseOptionsStatus { @@ -221,21 +250,79 @@ fn amd_driver_available(devices_root: &Path) -> TorchReleaseDriverAvailability { TorchReleaseDriverAvailability::Unavailable } -async fn installed_python_paths() -> Vec<(String, PathBuf)> { +fn python_probe_candidates(target: TorchHostTarget, python: &str) -> Vec<(String, Vec)> { + match target { + TorchHostTarget::WindowsX8664 => vec![ + ( + "py".into(), + vec![format!("-{}", python.trim_start_matches("python"))], + ), + (python.into(), Vec::new()), + ("python".into(), Vec::new()), + ], + TorchHostTarget::LinuxX8664 | TorchHostTarget::MacosArm64 => { + vec![ + (python.into(), Vec::new()), + ("python3".into(), Vec::new()), + ("python".into(), Vec::new()), + ] + } + } +} + +#[derive(Deserialize)] +struct PythonIdentityProbe { + path: String, + version: String, + platform: String, + machine: String, + implementation: String, + bits: u8, +} + +fn python_probe_path(output: &[u8], target: TorchHostTarget, python: &str) -> Option { + let identity: PythonIdentityProbe = serde_json::from_slice(output).ok()?; + let native = match target { + TorchHostTarget::LinuxX8664 => { + identity.platform == "linux" && identity.machine.eq_ignore_ascii_case("x86_64") + } + TorchHostTarget::WindowsX8664 => { + identity.platform == "win32" + && (identity.machine.eq_ignore_ascii_case("amd64") + || identity.machine.eq_ignore_ascii_case("x86_64")) + } + TorchHostTarget::MacosArm64 => { + identity.platform == "darwin" && identity.machine.eq_ignore_ascii_case("arm64") + } + }; + if !native + || identity.implementation != "cpython" + || identity.bits != 64 + || identity.version != python.strip_prefix("python")? + { + return None; + } + let path = std::fs::canonicalize(identity.path).ok()?; + path.is_absolute().then_some(path) +} + +async fn installed_python_paths(target: TorchHostTarget) -> Vec<(String, PathBuf)> { + const PROBE: &str = "import json,platform,struct,sys; print(json.dumps({'path':sys.executable,'version':f'{sys.version_info.major}.{sys.version_info.minor}','platform':sys.platform,'machine':platform.machine(),'implementation':sys.implementation.name,'bits':struct.calcsize('P')*8}))"; let mut interpreters = Vec::new(); for python in PYTHONS { - let mut command = Command::new(python); - command.kill_on_drop(true).args([ - "-I", "-c", - "import platform,sys; print(sys.executable if sys.platform == 'linux' and platform.machine() == 'x86_64' and sys.implementation.name == 'cpython' and f'{sys.version_info.major}.{sys.version_info.minor}' == sys.argv[1] else '')", - python.strip_prefix("python").unwrap_or(""), - ]); - if let Ok(Ok(output)) = tokio::time::timeout(PYTHON_CHECK_TIMEOUT, command.output()).await { - if output.status.success() { - let path = String::from_utf8_lossy(&output.stdout); - if let Ok(path) = std::fs::canonicalize(path.trim()) { - if path.is_absolute() { + for (launcher, prefix) in python_probe_candidates(target, python) { + let mut command = Command::new(launcher); + command + .kill_on_drop(true) + .args(prefix) + .args(["-I", "-c", PROBE]); + if let Ok(Ok(output)) = + tokio::time::timeout(PYTHON_CHECK_TIMEOUT, command.output()).await + { + if output.status.success() { + if let Some(path) = python_probe_path(&output.stdout, target, python) { interpreters.push(((*python).to_owned(), path)); + break; } } } @@ -244,11 +331,79 @@ async fn installed_python_paths() -> Vec<(String, PathBuf)> { interpreters } -fn valid_official_wheel(url: &str, build: &str) -> bool { +pub(super) async fn installed_torch_interpreters() -> Vec<(String, PathBuf)> { + match TorchHostTarget::current() { + Some(target) => installed_python_paths(target).await, + None => Vec::new(), + } +} + +fn valid_wheel_platform(platform: &str, target: TorchHostTarget) -> bool { + match target { + TorchHostTarget::LinuxX8664 => { + platform == "linux_x86_64" + || ((platform.starts_with("manylinux") || platform.starts_with("musllinux")) + && platform.ends_with("_x86_64")) + } + TorchHostTarget::WindowsX8664 => platform == "win_amd64", + TorchHostTarget::MacosArm64 => { + let Some(numbers) = platform.strip_prefix("macosx_").and_then(|value| { + value + .strip_suffix("_arm64") + .or_else(|| value.strip_suffix("_universal2")) + }) else { + return false; + }; + let Some((major, minor)) = numbers.split_once('_') else { + return false; + }; + major.parse::().is_ok_and(|value| value >= 11) && minor.parse::().is_ok() + } + } +} + +fn valid_official_wheel( + url: &str, + build: &str, + version: &str, + python: &str, + target: TorchHostTarget, +) -> bool { let Ok(url) = reqwest::Url::parse(url) else { return false; }; - url.scheme() == "https" + let Some(filename) = url.path().rsplit('/').next() else { + return false; + }; + let Some(stem) = filename.strip_suffix(".whl") else { + return false; + }; + let mut fields = stem.rsplitn(4, '-'); + let (Some(platform), Some(abi), Some(python_tag), Some(distribution)) = + (fields.next(), fields.next(), fields.next(), fields.next()) + else { + return false; + }; + let Some(remainder) = distribution.strip_prefix(&format!("torch-{version}")) else { + return false; + }; + let expected_python = python.replace("python", "cp").replace('.', ""); + let exact_version = remainder.is_empty() + || remainder.eq_ignore_ascii_case(&format!("%2B{build}")) + || remainder.eq_ignore_ascii_case(&format!("+{build}")); + let target_build = match target { + TorchHostTarget::MacosArm64 => build == "cpu", + TorchHostTarget::WindowsX8664 => build == "cpu" || build.starts_with("cu"), + TorchHostTarget::LinuxX8664 => valid_torch_channel(build), + }; + target_build + && exact_version + && python_tag.split('.').any(|tag| tag == expected_python) + && !abi.is_empty() + && platform + .split('.') + .any(|tag| valid_wheel_platform(tag, target)) + && url.scheme() == "https" && matches!( url.host_str(), Some("download.pytorch.org" | "download-r2.pytorch.org") @@ -258,12 +413,7 @@ fn valid_official_wheel(url: &str, build: &str) -> bool { && url.password().is_none() && url.query().is_none() && url.fragment().is_none() - && url.path().starts_with(&format!("/whl/{build}/")) - && url - .path() - .rsplit('/') - .next() - .is_some_and(|name| name.starts_with("torch-") && name.ends_with(".whl")) + && url.path().starts_with(&format!("/whl/{build}/torch/")) } fn python_rank(python: &str) -> u8 { @@ -276,16 +426,36 @@ fn python_rank(python: &str) -> u8 { } } +#[cfg(test)] fn visible_combinations( combinations: Vec, vendors: &[String], +) -> Vec { + visible_combinations_for_target(combinations, vendors, TorchHostTarget::LinuxX8664) +} + +fn visible_combinations_for_target( + combinations: Vec, + vendors: &[String], + target: TorchHostTarget, ) -> Vec { combinations .into_iter() - .filter(|item| visible_build(&item.build, vendors)) + .filter(|item| visible_build_for_target(&item.build, vendors, target)) .collect() } +fn visible_build_for_target(build: &str, vendors: &[String], target: TorchHostTarget) -> bool { + match target { + TorchHostTarget::LinuxX8664 => visible_build(build, vendors), + TorchHostTarget::WindowsX8664 => { + build == "cpu" + || (build.starts_with("cu") && vendors.iter().any(|vendor| vendor == "nvidia")) + } + TorchHostTarget::MacosArm64 => build == "cpu", + } +} + fn visible_build(build: &str, vendors: &[String]) -> bool { build == "cpu" || (build.starts_with("cu") && vendors.iter().any(|vendor| vendor == "nvidia")) @@ -402,6 +572,51 @@ fn recommend_release_option( (None, note.to_owned()) } +fn recommend_release_option_for_target( + combinations: &[TorchReleaseCombination], + vendors: &[String], + drivers: &TorchReleaseDriverStatus, + nvidia_version: Option, + complete_scan: bool, + target: TorchHostTarget, +) -> (Option, String) { + if target == TorchHostTarget::LinuxX8664 { + return recommend_release_option( + combinations, + vendors, + drivers, + nvidia_version, + complete_scan, + ); + } + let cpu = best_combination(combinations, "cpu").map(|item| TorchReleaseRecommendation { + build: item.build.clone(), + python: item.python.clone(), + }); + let note = match target { + TorchHostTarget::WindowsX8664 if cpu.is_some() && !complete_scan => { + "The official wheel scan was incomplete; CPU is a provisional choice. Exact CUDA wheels remain advanced choices." + } + TorchHostTarget::WindowsX8664 if cpu.is_some() => { + "CPU is recommended because Windows CUDA driver compatibility has not been established. Exact CUDA wheels remain advanced choices." + } + TorchHostTarget::WindowsX8664 => { + "No exact CPU wheel matched. Windows CUDA wheels require an explicit advanced choice; driver and device compatibility are checked after installation." + } + TorchHostTarget::MacosArm64 if cpu.is_some() && !complete_scan => { + "The official wheel scan was incomplete; CPU is a provisional choice. Apple MPS is a runtime capability, not a wheel channel." + } + TorchHostTarget::MacosArm64 if cpu.is_some() => { + "The official CPU wheel is recommended. Apple MPS is a runtime capability checked after installation, not a wheel channel." + } + TorchHostTarget::MacosArm64 => { + "No exact macOS arm64 CPU wheel matched. Apple MPS is a runtime capability, not a wheel channel." + } + TorchHostTarget::LinuxX8664 => unreachable!(), + }; + (cpu, note.to_owned()) +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct TorchAlternativeMatch { @@ -431,15 +646,16 @@ pub struct TorchAlternativeDiscovery { fn filter_alternatives_for_host( mut result: TorchAlternativeDiscovery, detected: io::Result>, + target: TorchHostTarget, ) -> TorchAlternativeDiscovery { let unknown_host = detected.is_err(); let vendors = detected.unwrap_or_default(); result .checked_builds - .retain(|build| visible_build(build, &vendors)); + .retain(|build| visible_build_for_target(build, &vendors, target)); result .matches - .retain(|item| visible_build(&item.build, &vendors)); + .retain(|item| visible_build_for_target(&item.build, &vendors, target)); if unknown_host { result.status = "inconclusive".into(); result @@ -451,6 +667,39 @@ fn filter_alternatives_for_host( result } +fn validate_legacy_wheel_leads( + mut result: TorchAlternativeDiscovery, + interpreters: &[(String, PathBuf)], + version: &str, + target: TorchHostTarget, +) -> TorchAlternativeDiscovery { + let before = result.matches.len(); + let checked_before = result.checked_builds.len(); + result + .checked_builds + .retain(|build| valid_torch_channel(build)); + result.matches.retain(|candidate| { + candidate.tag == result.selected_tag + && interpreters + .iter() + .any(|(python, _)| python == &candidate.python) + && valid_torch_channel(&candidate.build) + && candidate.wheel_url.as_ref().is_some_and(|url| { + valid_official_wheel(url, &candidate.build, version, &candidate.python, target) + }) + && candidate.sha256.as_ref().is_none_or(|hash| { + hash.len() == 64 && hash.bytes().all(|byte| byte.is_ascii_hexdigit()) + }) + }); + if result.matches.len() != before || result.checked_builds.len() != checked_before { + result.status = "inconclusive".into(); + result + .issues + .push("Some wheel leads could not be verified for this host and were withheld".into()); + } + result +} + fn stable_version(tag: &str) -> Option<&str> { let version = tag.strip_prefix('v').unwrap_or(tag); let parts: Vec<_> = version.split('.').collect(); @@ -475,26 +724,6 @@ fn unsupported_input(message: &str) -> PumasError { } } -async fn installed_pythons() -> Vec<&'static str> { - let mut available = Vec::new(); - for python in PYTHONS { - let mut command = Command::new(python); - command.kill_on_drop(true).args([ - "-I", - "-c", - "import platform,sys; print(f'{sys.version_info.major}.{sys.version_info.minor}' if sys.platform == 'linux' and platform.machine() == 'x86_64' and sys.implementation.name == 'cpython' else '')", - ]); - if let Ok(Ok(output)) = tokio::time::timeout(PYTHON_CHECK_TIMEOUT, command.output()).await { - let expected = python.strip_prefix("python").unwrap_or(""); - if output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == expected - { - available.push(*python); - } - } - } - available -} - impl VersionManager { /// Find a few official binary Torch wheel leads for the selected stable tag. /// This never claims a dependency-compatible install plan. @@ -511,12 +740,17 @@ impl VersionManager { } let version = stable_version(selected_tag) .ok_or_else(|| unsupported_input("Select a stable upstream Torch tag"))?; - if !BUILDS.contains(&selected_build) || !PYTHONS.contains(&selected_python) { + let target = TorchHostTarget::current().ok_or_else(|| { + unsupported_input( + "Torch alternatives require Linux x86_64, Windows x86_64, or macOS arm64", + ) + })?; + if !valid_torch_channel(selected_build) || !PYTHONS.contains(&selected_python) { return Err(unsupported_input( "Select a supported Torch build and Python version", )); } - let interpreters = installed_pythons().await; + let interpreters = installed_torch_interpreters().await; if interpreters.is_empty() { return Ok(TorchAlternativeDiscovery { selected_tag: selected_tag.into(), @@ -527,10 +761,10 @@ impl VersionManager { dependencies_not_checked: true, checked_builds: Vec::new(), matches: Vec::new(), - issues: vec![ - "No installed CPython interpreter on Linux x86_64 can inspect official wheels" - .into(), - ], + issues: vec![format!( + "No installed CPython interpreter on {} can inspect official wheels", + target.label() + )], }); } let workspace = tempfile::tempdir().map_err(PumasError::from)?; @@ -540,7 +774,7 @@ impl VersionManager { include_str!("../../../../../torch-server/resolve_runtime.py"), ) .map_err(PumasError::from)?; - let mut command = Command::new(interpreters[0]); + let mut command = Command::new(&interpreters[0].1); command.kill_on_drop(true).arg("-I").arg(&resolver).args([ "--discover", "--version", @@ -550,7 +784,7 @@ impl VersionManager { "--selected-python", selected_python, ]); - for interpreter in &interpreters { + for (_, interpreter) in &interpreters { command.arg("--interpreter").arg(interpreter); } let output = tokio::time::timeout(DISCOVERY_TIMEOUT, command.output()) @@ -574,22 +808,32 @@ impl VersionManager { || result.selected_tag != format!("v{version}") || result.selected_build != selected_build || result.selected_python != selected_python - || result.matches.iter().any(|candidate| { - candidate.tag != result.selected_tag - || !BUILDS.contains(&candidate.build.as_str()) - || !interpreters.contains(&candidate.python.as_str()) - }) + || !matches!(result.status.as_str(), "matches" | "none" | "inconclusive") + || result.checked_builds.len() > 5 { return Err(unsupported_input( "Torch discovery violated its bounded wheel-only contract", )); } - Ok(filter_alternatives_for_host( - result, - pci_display_vendors(Path::new("/sys/bus/pci/devices"), |path| { - std::fs::read_to_string(path) - }), - )) + let result = validate_legacy_wheel_leads(result, &interpreters, version, target); + let detected = match target { + TorchHostTarget::LinuxX8664 => { + pci_display_vendors(Path::new("/sys/bus/pci/devices"), |path| { + std::fs::read_to_string(path) + }) + } + TorchHostTarget::WindowsX8664 => { + if nvidia_driver_probe().await.availability + == TorchReleaseDriverAvailability::Available + { + Ok(vec!["nvidia".into()]) + } else { + Err(io::Error::from(io::ErrorKind::NotFound)) + } + } + TorchHostTarget::MacosArm64 => Ok(Vec::new()), + }; + Ok(filter_alternatives_for_host(result, detected, target)) } } @@ -607,43 +851,83 @@ impl VersionManager { } let version = stable_release_version(tag) .ok_or_else(|| unsupported_input("Select a stable upstream Torch tag"))?; + let target = TorchHostTarget::current().ok_or_else(|| { + unsupported_input( + "Torch release options require Linux x86_64, Windows x86_64, or macOS arm64", + ) + })?; self.resolve_installable_release(tag).await?; - let detected = pci_display_vendors(Path::new("/sys/bus/pci/devices"), |path| { - std::fs::read_to_string(path) - }); - let (vendors, mut host_issues) = match detected { - Ok(vendors) => (vendors, Vec::new()), - Err(_) => ( - Vec::new(), - vec!["GPU devices could not be inspected".to_owned()], - ), - }; - let unknown_host = !host_issues.is_empty(); - let nvidia_probe = if unknown_host { - NvidiaDriverProbe { - availability: TorchReleaseDriverAvailability::Unknown, - version: None, + let (vendors, mut host_issues, nvidia_probe, driver_status, unknown_host) = match target { + TorchHostTarget::LinuxX8664 => { + let detected = pci_display_vendors(Path::new("/sys/bus/pci/devices"), |path| { + std::fs::read_to_string(path) + }); + let (vendors, issues) = match detected { + Ok(vendors) => (vendors, Vec::new()), + Err(_) => ( + Vec::new(), + vec!["GPU devices could not be inspected".to_owned()], + ), + }; + let unknown = !issues.is_empty(); + let nvidia = if unknown { + NvidiaDriverProbe { + availability: TorchReleaseDriverAvailability::Unknown, + version: None, + } + } else if vendors.iter().any(|vendor| vendor == "nvidia") { + nvidia_driver_probe().await + } else { + NvidiaDriverProbe { + availability: TorchReleaseDriverAvailability::NotPresent, + version: None, + } + }; + let drivers = TorchReleaseDriverStatus { + nvidia: nvidia.availability, + amd: if unknown { + TorchReleaseDriverAvailability::Unknown + } else if vendors.iter().any(|vendor| vendor == "amd") { + amd_driver_available(Path::new("/sys/bus/pci/devices")) + } else { + TorchReleaseDriverAvailability::NotPresent + }, + }; + (vendors, issues, nvidia, drivers, unknown) } - } else if vendors.iter().any(|vendor| vendor == "nvidia") { - nvidia_driver_probe().await - } else { - NvidiaDriverProbe { - availability: TorchReleaseDriverAvailability::NotPresent, - version: None, + TorchHostTarget::WindowsX8664 => { + let nvidia = nvidia_driver_probe().await; + let detected = nvidia.availability == TorchReleaseDriverAvailability::Available; + let vendors = if detected { + vec!["nvidia".into()] + } else { + Vec::new() + }; + let issues = if detected { + Vec::new() + } else { + vec!["NVIDIA GPU could not be positively detected; CUDA wheel choices were withheld".into()] + }; + let drivers = TorchReleaseDriverStatus { + nvidia: nvidia.availability, + amd: TorchReleaseDriverAvailability::NotPresent, + }; + (vendors, issues, nvidia, drivers, !detected) + } + TorchHostTarget::MacosArm64 => { + let nvidia = NvidiaDriverProbe { + availability: TorchReleaseDriverAvailability::NotPresent, + version: None, + }; + let drivers = TorchReleaseDriverStatus { + nvidia: TorchReleaseDriverAvailability::NotPresent, + amd: TorchReleaseDriverAvailability::NotPresent, + }; + (Vec::new(), Vec::new(), nvidia, drivers, false) } }; - let driver_status = TorchReleaseDriverStatus { - nvidia: nvidia_probe.availability, - amd: if unknown_host { - TorchReleaseDriverAvailability::Unknown - } else if vendors.iter().any(|vendor| vendor == "amd") { - amd_driver_available(Path::new("/sys/bus/pci/devices")) - } else { - TorchReleaseDriverAvailability::NotPresent - }, - }; - let interpreters = installed_python_paths().await; + let interpreters = installed_python_paths(target).await; let empty = |issue: &str| ResolverReleaseOptions { tag: tag.to_owned(), status: TorchReleaseOptionsStatus::Inconclusive, @@ -653,7 +937,10 @@ impl VersionManager { issues: vec![issue.to_owned()], }; let mut result = if interpreters.is_empty() { - empty("No installed CPython 3.10–3.13 interpreter on Linux x86_64 could inspect official wheels") + empty(&format!( + "No installed CPython 3.10–3.13 interpreter on {} could inspect official wheels", + target.label() + )) } else { let workspace = tempfile::tempdir().map_err(PumasError::from)?; let resolver = workspace.path().join("resolve_runtime.py"); @@ -699,7 +986,13 @@ impl VersionManager { || result.combinations.iter().any(|item| { !checked.contains(item.build.as_str()) || !available_pythons.contains(item.python.as_str()) - || !valid_official_wheel(&item.wheel_url, &item.build) + || !valid_official_wheel( + &item.wheel_url, + &item.build, + version, + &item.python, + target, + ) || item.sha256.as_ref().is_some_and(|hash| { hash.len() != 64 || !hash.bytes().all(|byte| byte.is_ascii_hexdigit()) }) @@ -718,7 +1011,13 @@ impl VersionManager { "Torch release-options result violated its exact-wheel contract", )); } - result.combinations = visible_combinations(result.combinations, &vendors); + result.combinations = + visible_combinations_for_target(result.combinations, &vendors, target); + if target != TorchHostTarget::LinuxX8664 { + result + .checked_channels + .retain(|channel| visible_build_for_target(channel, &vendors, target)); + } let status = if !result.complete_scan || (unknown_host && result.combinations.is_empty()) { TorchReleaseOptionsStatus::Inconclusive } else if result.combinations.is_empty() { @@ -726,14 +1025,15 @@ impl VersionManager { } else { TorchReleaseOptionsStatus::Matches }; - let (recommended, mut recommendation_note) = recommend_release_option( + let (recommended, mut recommendation_note) = recommend_release_option_for_target( &result.combinations, &vendors, &driver_status, nvidia_probe.version, result.complete_scan, + target, ); - if unknown_host { + if unknown_host && target == TorchHostTarget::LinuxX8664 { recommendation_note = if recommended.is_some() { "GPU devices could not be inspected; CPU is the only recommended choice until hardware is known." } else { @@ -800,21 +1100,224 @@ mod tests { .contains("torch-2.14.0%2Bcu136")); assert!(valid_official_wheel( value["combinations"][0]["wheelUrl"].as_str().unwrap(), - "cu136" + "cu136", + "2.14.0", + "python3.12", + TorchHostTarget::LinuxX8664, )); assert!(value["combinations"][0].get("sha256").is_none()); assert_eq!(value["driverStatus"]["nvidia"], "available"); assert_eq!(value["driverStatus"]["amd"], "not_present"); assert!(valid_official_wheel( - "https://download.pytorch.org/whl/cu136/torch-2.14.0%2Bcu136.whl", - "cu136" + "https://download.pytorch.org/whl/cu136/torch/torch-2.14.0%2Bcu136-cp312-cp312-manylinux_2_17_x86_64.whl", + "cu136", + "2.14.0", + "python3.12", + TorchHostTarget::LinuxX8664, + )); + assert!(!valid_official_wheel( + "https://example.com/whl/cu136/torch/torch-2.14.0%2Bcu136-cp312-cp312-manylinux_2_17_x86_64.whl", + "cu136", + "2.14.0", + "python3.12", + TorchHostTarget::LinuxX8664, + )); + } + + #[test] + fn native_wheel_urls_are_release_and_target_bounded() { + let win_cpu = + "https://download.pytorch.org/whl/cpu/torch/torch-2.14.0-cp312-cp312-win_amd64.whl"; + let win_cuda = "https://download.pytorch.org/whl/cu132/torch/torch-2.14.0%2Bcu132-cp312-cp312-win_amd64.whl"; + let mac_cpu = "https://download.pytorch.org/whl/cpu/torch/torch-2.14.0-cp312-cp312-macosx_11_0_arm64.whl"; + assert!(valid_official_wheel( + win_cpu, + "cpu", + "2.14.0", + "python3.12", + TorchHostTarget::WindowsX8664 + )); + assert!(valid_official_wheel( + win_cuda, + "cu132", + "2.14.0", + "python3.12", + TorchHostTarget::WindowsX8664 + )); + assert!(valid_official_wheel( + mac_cpu, + "cpu", + "2.14.0", + "python3.12", + TorchHostTarget::MacosArm64 + )); + assert!(valid_official_wheel( + "https://download.pytorch.org/whl/cpu/torch/torch-2.14.0-cp312-cp312-macosx_11_0_universal2.whl", + "cpu", + "2.14.0", + "python3.12", + TorchHostTarget::MacosArm64 + )); + for (url, build, target) in [ + (win_cpu, "cpu", TorchHostTarget::LinuxX8664), + (win_cuda, "cu132", TorchHostTarget::MacosArm64), + (mac_cpu, "cpu", TorchHostTarget::WindowsX8664), + ("https://evil.example/whl/cpu/torch/torch-2.14.0-cp312-cp312-win_amd64.whl", "cpu", TorchHostTarget::WindowsX8664), + ("https://download.pytorch.org/whl/cu132/torch/torch-2.14.0%2Bcu132-cp312-cp312-win_arm64.whl", "cu132", TorchHostTarget::WindowsX8664), + ("https://download.pytorch.org/whl/cpu/torch/torch-2.14.0-cp312-cp312-macosx_11_0_x86_64.whl", "cpu", TorchHostTarget::MacosArm64), + ] { + assert!(!valid_official_wheel(url, build, "2.14.0", "python3.12", target), "{url}"); + } + assert!(!valid_official_wheel( + win_cpu, + "cpu", + "2.13.0", + "python3.12", + TorchHostTarget::WindowsX8664 )); assert!(!valid_official_wheel( - "https://example.com/whl/cu136/torch-2.14.0%2Bcu136.whl", - "cu136" + win_cpu, + "cpu", + "2.14.0", + "python3.13", + TorchHostTarget::WindowsX8664 )); } + #[test] + fn native_choices_filter_channels_and_keep_cpu_default() { + let wheels = vec![ + wheel("cpu", "python3.12"), + wheel("cu132", "python3.12"), + wheel("rocm7.2", "python3.12"), + ]; + let no_gpu = + visible_combinations_for_target(wheels.clone(), &[], TorchHostTarget::WindowsX8664); + assert_eq!( + no_gpu + .iter() + .map(|item| item.build.as_str()) + .collect::>(), + ["cpu"] + ); + let nvidia = visible_combinations_for_target( + wheels.clone(), + &["nvidia".into()], + TorchHostTarget::WindowsX8664, + ); + assert_eq!( + nvidia + .iter() + .map(|item| item.build.as_str()) + .collect::>(), + ["cpu", "cu132"] + ); + let mac = visible_combinations_for_target( + wheels, + &["nvidia".into(), "amd".into()], + TorchHostTarget::MacosArm64, + ); + assert_eq!( + mac.iter() + .map(|item| item.build.as_str()) + .collect::>(), + ["cpu"] + ); + let drivers = TorchReleaseDriverStatus { + nvidia: TorchReleaseDriverAvailability::Available, + amd: TorchReleaseDriverAvailability::NotPresent, + }; + let (choice, note) = recommend_release_option_for_target( + &nvidia, + &["nvidia".into()], + &drivers, + Some(NvidiaDriverVersion(700, 0, 0)), + true, + TorchHostTarget::WindowsX8664, + ); + assert_eq!(choice.unwrap().build, "cpu"); + assert!(note.contains("Windows CUDA driver compatibility")); + let (choice, note) = recommend_release_option_for_target( + &mac, + &[], + &drivers, + None, + true, + TorchHostTarget::MacosArm64, + ); + assert_eq!(choice.unwrap().build, "cpu"); + assert!(note.contains("MPS is a runtime capability")); + } + + #[test] + fn windows_cuda_wheels_never_become_automatic_default_without_cpu() { + let vendors = vec!["nvidia".into()]; + let combinations = visible_combinations_for_target( + vec![wheel("cu130", "python3.12"), wheel("cu132", "python3.12")], + &vendors, + TorchHostTarget::WindowsX8664, + ); + assert_eq!(combinations.len(), 2); + let drivers = TorchReleaseDriverStatus { + nvidia: TorchReleaseDriverAvailability::Available, + amd: TorchReleaseDriverAvailability::NotPresent, + }; + for complete_scan in [true, false] { + let (recommended, note) = recommend_release_option_for_target( + &combinations, + &vendors, + &drivers, + Some(NvidiaDriverVersion(700, 0, 0)), + complete_scan, + TorchHostTarget::WindowsX8664, + ); + assert!(recommended.is_none()); + assert!(note.contains("explicit advanced choice")); + } + } + + #[test] + fn native_interpreter_probe_uses_windows_launcher_then_absolute_identity() { + let candidates = python_probe_candidates(TorchHostTarget::WindowsX8664, "python3.12"); + assert_eq!(candidates[0], ("py".into(), vec!["-3.12".into()])); + assert!(candidates.iter().any(|(name, _)| name == "python")); + let mac = python_probe_candidates(TorchHostTarget::MacosArm64, "python3.12"); + assert_eq!(mac[0], ("python3.12".into(), Vec::new())); + assert_eq!(mac[1], ("python3".into(), Vec::new())); + assert_eq!(mac[2], ("python".into(), Vec::new())); + let linux = python_probe_candidates(TorchHostTarget::LinuxX8664, "python3.12"); + assert_eq!(linux, mac); + } + + #[test] + fn generic_python_probe_rejects_wrong_minor_and_foreign_architecture() { + let root = tempfile::tempdir().unwrap(); + let executable = root.path().join("python"); + std::fs::write(&executable, b"test").unwrap(); + let identity = serde_json::json!({ + "path": executable, + "version": "3.11", + "platform": "linux", + "machine": "x86_64", + "implementation": "cpython", + "bits": 64, + }); + let output = serde_json::to_vec(&identity).unwrap(); + assert!(python_probe_path(&output, TorchHostTarget::LinuxX8664, "python3.12").is_none()); + assert_eq!( + python_probe_path(&output, TorchHostTarget::LinuxX8664, "python3.11"), + Some(std::fs::canonicalize(&executable).unwrap()) + ); + assert!(python_probe_path(&output, TorchHostTarget::MacosArm64, "python3.11").is_none()); + + let mut translated_mac = identity; + translated_mac["platform"] = "darwin".into(); + translated_mac["machine"] = "x86_64".into(); + let output = serde_json::to_vec(&translated_mac).unwrap(); + assert!(python_probe_path(&output, TorchHostTarget::MacosArm64, "python3.11").is_none()); + } + + #[cfg(target_os = "linux")] #[test] fn pci_reader_identifies_only_display_gpu_vendors() { let root = tempfile::tempdir().unwrap(); @@ -840,6 +1343,7 @@ mod tests { assert_eq!(vendors, ["amd", "intel", "nvidia"]); } + #[cfg(target_os = "linux")] #[test] fn amd_driver_status_requires_binding_to_display_device() { let root = tempfile::tempdir().unwrap(); @@ -1100,7 +1604,11 @@ mod tests { #[test] fn legacy_alternatives_filter_cpu_only_and_nvidia_intel_hosts() { - let cpu = filter_alternatives_for_host(legacy_alternatives(), Ok(Vec::new())); + let cpu = filter_alternatives_for_host( + legacy_alternatives(), + Ok(Vec::new()), + TorchHostTarget::LinuxX8664, + ); assert_eq!(cpu.checked_builds, ["cpu"]); assert_eq!( cpu.matches @@ -1114,6 +1622,7 @@ mod tests { let nvidia = filter_alternatives_for_host( legacy_alternatives(), Ok(vec!["intel".into(), "nvidia".into()]), + TorchHostTarget::LinuxX8664, ); assert_eq!(nvidia.checked_builds, ["cpu", "cu130"]); assert_eq!( @@ -1132,6 +1641,7 @@ mod tests { let result = filter_alternatives_for_host( legacy_alternatives(), Err(io::Error::from(io::ErrorKind::PermissionDenied)), + TorchHostTarget::LinuxX8664, ); assert_eq!(result.checked_builds, ["cpu"]); assert_eq!( @@ -1153,8 +1663,60 @@ mod tests { fn legacy_alternatives_removed_gpu_only_match_does_not_claim_match() { let mut result = legacy_alternatives(); result.matches.retain(|item| item.build == "cu130"); - let result = filter_alternatives_for_host(result, Ok(Vec::new())); + let result = + filter_alternatives_for_host(result, Ok(Vec::new()), TorchHostTarget::LinuxX8664); assert!(result.matches.is_empty()); assert_eq!(result.status, "none"); } + + #[test] + fn legacy_native_wheel_leads_require_exact_official_target_wheels() { + let interpreters = vec![("python3.12".into(), PathBuf::from("/unused/python"))]; + let mut windows = legacy_alternatives(); + for item in &mut windows.matches { + item.wheel_url = Some(match item.build.as_str() { + "cpu" => "https://download.pytorch.org/whl/cpu/torch/torch-2.10.0-cp312-cp312-win_amd64.whl", + "cu130" => "https://download.pytorch.org/whl/cu130/torch/torch-2.10.0%2Bcu130-cp312-cp312-win_amd64.whl", + _ => "https://download.pytorch.org/whl/rocm7.2/torch/torch-2.10.0%2Brocm7.2-cp312-cp312-manylinux_2_17_x86_64.whl", + }.into()); + } + let windows = validate_legacy_wheel_leads( + windows, + &interpreters, + "2.10.0", + TorchHostTarget::WindowsX8664, + ); + assert_eq!(windows.status, "inconclusive"); + assert_eq!(windows.matches.len(), 2); + let windows = filter_alternatives_for_host( + windows, + Ok(vec!["nvidia".into()]), + TorchHostTarget::WindowsX8664, + ); + assert_eq!( + windows + .matches + .iter() + .map(|item| item.build.as_str()) + .collect::>(), + ["cpu", "cu130"] + ); + assert_eq!(windows.checked_builds, ["cpu", "cu130"]); + + let mut mac = legacy_alternatives(); + mac.matches[0].wheel_url = Some("https://download.pytorch.org/whl/cpu/torch/torch-2.10.0-cp312-cp312-macosx_11_0_arm64.whl".into()); + mac.matches[1].wheel_url = Some("https://download.pytorch.org/whl/cu130/torch/torch-2.10.0%2Bcu130-cp312-cp312-win_amd64.whl".into()); + let mac = + validate_legacy_wheel_leads(mac, &interpreters, "2.10.0", TorchHostTarget::MacosArm64); + let mac = filter_alternatives_for_host(mac, Ok(Vec::new()), TorchHostTarget::MacosArm64); + assert_eq!( + mac.matches + .iter() + .map(|item| item.build.as_str()) + .collect::>(), + ["cpu"] + ); + assert_eq!(mac.checked_builds, ["cpu"]); + assert_eq!(mac.status, "inconclusive"); + } } diff --git a/rust/crates/pumas-app-manager/src/version_manager/torch_preview.rs b/rust/crates/pumas-app-manager/src/version_manager/torch_preview.rs index 4812b28ad..f1a3c2b36 100644 --- a/rust/crates/pumas-app-manager/src/version_manager/torch_preview.rs +++ b/rust/crates/pumas-app-manager/src/version_manager/torch_preview.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::collections::HashMap; -use std::io::Read; use std::time::Instant; use tokio::process::Command; @@ -83,6 +82,18 @@ pub(super) fn valid_torch_channel(build: &str) -> bool { false } +fn supported_torch_build(build: &str) -> bool { + match std::env::consts::OS { + "linux" => cfg!(target_arch = "x86_64") && valid_torch_channel(build), + "windows" => { + cfg!(all(target_arch = "x86_64", target_env = "msvc")) + && (build == "cpu" || build.starts_with("cu") && valid_torch_channel(build)) + } + "macos" => cfg!(target_arch = "aarch64") && build == "cpu", + _ => false, + } +} + fn insert_retained_torch_preview( previews: &mut HashMap, preview_id: String, @@ -326,12 +337,14 @@ mod tests { #[tokio::test] async fn preview_resolver_deadline_kills_process_group() { - let workspace = tempfile::tempdir().unwrap(); + let workspace = std::sync::Arc::new(tempfile::tempdir().unwrap()); let mut command = Command::new("sh"); command.args(["-c", "sleep 30"]); - let outcome = run_preview_resolver(command, workspace.path(), Duration::from_millis(25)) - .await - .unwrap(); + let cleanup = super::installer::TorchCleanupTasks::default(); + let outcome = + run_preview_resolver(command, &workspace, Duration::from_millis(25), &cleanup) + .await + .unwrap(); assert!(matches!(&outcome, PreviewResolverRun::TimedOut)); let rejected = resolver_rejection(outcome).unwrap(); let value = serde_json::to_value(rejected).unwrap(); @@ -341,10 +354,11 @@ mod tests { #[tokio::test] async fn preview_resolver_cleans_child_after_leader_exits() { - let workspace = tempfile::tempdir().unwrap(); + let workspace = std::sync::Arc::new(tempfile::tempdir().unwrap()); let mut command = Command::new("sh"); command.args(["-c", "echo $$; sleep 30 &"]); - let outcome = run_preview_resolver(command, workspace.path(), Duration::from_secs(2)) + let cleanup = super::installer::TorchCleanupTasks::default(); + let outcome = run_preview_resolver(command, &workspace, Duration::from_secs(2), &cleanup) .await .unwrap(); let PreviewResolverRun::Exited(status) = outcome else { @@ -358,6 +372,97 @@ mod tests { .unwrap(); assert!(!pumas_library::platform::linux_group::group_has_live_members(pid).unwrap()); } + + #[tokio::test] + async fn cancelled_resolver_waiter_keeps_workspace_until_owned_drain() { + let workspace = std::sync::Arc::new(tempfile::tempdir().unwrap()); + let path = workspace.path().to_path_buf(); + let ready = path.join("ready"); + let cleanup = std::sync::Arc::new(super::installer::TorchCleanupTasks::default()); + let run_workspace = workspace.clone(); + let run_cleanup = cleanup.clone(); + let task = tokio::spawn(async move { + let mut command = Command::new("sh"); + command + .arg("-c") + .arg("touch \"$1\"; sleep 30") + .arg("sh") + .arg(&ready); + run_preview_resolver( + command, + &run_workspace, + Duration::from_secs(30), + &run_cleanup, + ) + .await + }); + tokio::time::timeout(Duration::from_secs(5), async { + while !path.join("ready").exists() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .unwrap(); + task.abort(); + assert!(task.await.unwrap_err().is_cancelled()); + drop(workspace); + assert!( + path.exists(), + "workspace lease released before descendant drain" + ); + tokio::time::timeout(Duration::from_secs(5), async { + while path.exists() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("registered cleanup supervisor did not drain cancelled resolver"); + cleanup.drain().await.unwrap(); + assert!(!path.exists()); + } + + #[tokio::test] + async fn healthy_long_resolver_does_not_block_another_preview_or_preflight() { + let cleanup = std::sync::Arc::new(super::installer::TorchCleanupTasks::default()); + let long_workspace = std::sync::Arc::new(tempfile::tempdir().unwrap()); + let ready = long_workspace.path().join("ready"); + let long_cleanup = cleanup.clone(); + let long_path = long_workspace.clone(); + let long_task = tokio::spawn(async move { + let mut command = Command::new("sh"); + command + .arg("-c") + .arg("touch \"$1\"; sleep 30") + .arg("sh") + .arg(&ready); + run_preview_resolver(command, &long_path, Duration::from_secs(30), &long_cleanup).await + }); + tokio::time::timeout(Duration::from_secs(5), async { + while !long_workspace.path().join("ready").exists() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .unwrap(); + let short_workspace = std::sync::Arc::new(tempfile::tempdir().unwrap()); + let mut short = Command::new("sh"); + short.args(["-c", "exit 0"]); + let result = tokio::time::timeout( + Duration::from_secs(2), + run_preview_resolver(short, &short_workspace, Duration::from_secs(2), &cleanup), + ) + .await + .expect("healthy resolver blocked another preview") + .unwrap(); + assert!(matches!(result, PreviewResolverRun::Exited(status) if status.success())); + tokio::time::timeout(Duration::from_secs(1), cleanup.drain_residual_child_slots()) + .await + .expect("healthy resolver blocked preflight") + .unwrap(); + long_task.abort(); + assert!(long_task.await.unwrap_err().is_cancelled()); + cleanup.drain().await.unwrap(); + } } #[derive(Clone, Debug, Serialize)] @@ -438,6 +543,7 @@ pub(crate) type TorchPreviews = Arc> #[derive(Deserialize)] struct Resolution { + release: String, torch: String, build: String, python: String, @@ -457,10 +563,8 @@ fn failed(message: impl Into) -> PumasError { fn preview_token() -> Result { let mut bytes = [0u8; 24]; - std::fs::File::open("/dev/urandom") - .map_err(PumasError::from)? - .read_exact(&mut bytes) - .map_err(PumasError::from)?; + getrandom::fill(&mut bytes) + .map_err(|error| failed(format!("Preview ID randomness unavailable: {error}")))?; Ok(bytes.iter().map(|byte| format!("{byte:02x}")).collect()) } @@ -540,9 +644,6 @@ fn safe_torch_tag(tag: &str) -> bool { .all(|c| c.is_ascii_alphanumeric() || b"-._".contains(&c)) } -#[cfg(target_os = "linux")] -struct PreviewGroupGuard(Option); - #[derive(Debug)] enum PreviewResolverRun { Exited(std::process::ExitStatus), @@ -564,63 +665,37 @@ fn resolver_rejection(run: PreviewResolverRun) -> Option { Some(TorchPreviewOutcome::Rejected { reason, message }) } -#[cfg(target_os = "linux")] -impl Drop for PreviewGroupGuard { - fn drop(&mut self) { - if let Some(pid) = self.0.take() { - let _ = pumas_library::platform::linux_group::signal_group(pid); - } - } -} - -#[cfg(target_os = "linux")] async fn run_preview_resolver( mut command: Command, - workspace: &Path, + workspace: &std::sync::Arc, deadline: Duration, + cleanup: &super::installer::TorchCleanupTasks, ) -> Result { - use pumas_library::platform::linux_group; - linux_group::ensure_supported().map_err(PumasError::from)?; - let stdout = - std::fs::File::create(workspace.join("resolver.stdout")).map_err(PumasError::from)?; - let stderr = - std::fs::File::create(workspace.join("resolver.stderr")).map_err(PumasError::from)?; - command - .stdout(stdout) - .stderr(stderr) - .kill_on_drop(true) - .process_group(0); - let mut child = command - .spawn() - .map_err(|e| failed(format!("Torch resolver could not start: {e}")))?; - let pid = child - .id() - .ok_or_else(|| failed("Torch resolver process has no PID"))?; - let mut group = PreviewGroupGuard(Some(pid)); + let stdout = std::fs::File::create(workspace.path().join("resolver.stdout")) + .map_err(PumasError::from)?; + let stderr = std::fs::File::create(workspace.path().join("resolver.stderr")) + .map_err(PumasError::from)?; + command.stdout(stdout).stderr(stderr); + let custody = cleanup.new_child_slot()?; + let mut child = pumas_library::platform::managed_child::ManagedChild::spawn( + command.as_std_mut(), + custody.clone(), + ) + .map_err(|e| failed(format!("Torch resolver could not start: {e}")))?; + child.attach_cleanup_lease(workspace.clone()); let until = tokio::time::Instant::now() + deadline; let (status, timed_out) = loop { - let observed = linux_group::observe_exit(pid).map_err(PumasError::from)?; + let observed = child.observe_exit().map_err(PumasError::from)?; let timed_out = tokio::time::Instant::now() >= until; if observed.is_some() || timed_out { break (observed, timed_out); } tokio::time::sleep(Duration::from_millis(100)).await; }; - let group_id = i32::try_from(pid).map_err(|_| failed("Invalid Torch resolver PID"))?; - loop { - linux_group::signal_group(pid).map_err(PumasError::from)?; - let alive = - tokio::task::spawn_blocking(move || linux_group::group_has_live_members(group_id)) - .await - .map_err(|e| failed(format!("Preview process observation failed: {e}")))? - .map_err(PumasError::from)?; - if !alive { - break; - } - tokio::time::sleep(Duration::from_millis(100)).await; - } - child.wait().await.map_err(PumasError::from)?; - group.0 = None; + drop(child); + cleanup.drain_child_slot(&custody).await.map_err(|_| { + failed("Torch resolver cleanup incomplete; owned process cleanup remains pending") + })?; if timed_out { return Ok(PreviewResolverRun::TimedOut); } @@ -629,15 +704,6 @@ async fn run_preview_resolver( .ok_or_else(|| failed("Torch preview resolver exited without status")) } -#[cfg(not(target_os = "linux"))] -async fn run_preview_resolver( - _command: Command, - _workspace: &Path, - _deadline: Duration, -) -> Result { - Err(failed("Managed Torch previews require Linux x86_64")) -} - impl VersionManager { /// Check that an installed Torch tag is safe to address and its interpreter /// still matches the recorded wheel identity before selection or launch. @@ -681,7 +747,7 @@ impl VersionManager { && fs::try_exists(runtime.join("serve.py")) .await .map_err(PumasError::from)? - && fs::try_exists(runtime.join("venv/bin/python")) + && fs::try_exists(pumas_library::platform::paths::venv_python(&runtime)) .await .map_err(PumasError::from)? { @@ -727,7 +793,7 @@ impl VersionManager { pub(crate) async fn check_torch_identity(&self, tag: &str) -> Result<()> { let runtime = self.versions_dir().join(tag); let expected = self.expected_torch_version(tag).await?; - let mut command = Command::new(runtime.join("venv/bin/python")); + let mut command = Command::new(pumas_library::platform::paths::venv_python(&runtime)); command .kill_on_drop(true) .args(["-I", "-c", "import torch; print(torch.__version__)"]); @@ -751,14 +817,22 @@ impl VersionManager { if self.app_id != AppId::Torch { return Err(failed("Torch manager required")); } - let mut pythons = Vec::new(); - for name in PYTHONS { - if let Ok(output) = Command::new(name).args(["-I", "--version"]).output().await { - if output.status.success() { - pythons.push(serde_json::json!({"id": name, "label": String::from_utf8_lossy(&output.stdout).trim()})); - } - } - } + let interpreters = super::torch_alternatives::installed_torch_interpreters().await; + let pythons: Vec<_> = interpreters + .iter() + .map( + |(name, path)| serde_json::json!({"id": name, "label": path.display().to_string()}), + ) + .collect(); + let linux_x64 = cfg!(all(target_os = "linux", target_arch = "x86_64")); + let bundled_preset_available = + linux_x64 && interpreters.iter().any(|(name, _)| name == "python3.12"); + let adapters: &[&str] = if linux_x64 { ADAPTERS } else { &["none"] }; + let builds: Vec<&str> = BUILDS + .iter() + .copied() + .filter(|build| supported_torch_build(build)) + .collect(); let mut installed = Vec::new(); for tag in self.state.read().await.get_installed_tags() { let recipe_path = self.versions_dir().join(&tag).join("runtime.json"); @@ -775,7 +849,9 @@ impl VersionManager { "qualification": if preset { "qualified" } else { "unverified" } })); } Ok( - serde_json::json!({"builds": BUILDS, "pythons": pythons, "adapters": ADAPTERS, + serde_json::json!({"builds": builds, "pythons": pythons, "adapters": adapters, + "bundledPresetAvailable": bundled_preset_available, + "defaultAdapter": if linux_x64 { "flux2" } else { "none" }, "preset": {"tag":"v2.9.1", "build":"cu130", "python":"python3.12", "adapter":"bundled"}, "installed": installed}), ) @@ -789,14 +865,27 @@ impl VersionManager { adapter: &str, ) -> Result { if self.app_id != AppId::Torch - || !valid_torch_channel(build) + || !supported_torch_build(build) || !PYTHONS.contains(&python) || (!ADAPTERS.contains(&adapter) && adapter != "bundled") { return Err(failed("Invalid Torch preview selection")); } - if !cfg!(all(target_os = "linux", target_arch = "x86_64")) { - return Err(failed("Managed Torch requires Linux x86_64")); + if !cfg!(any( + all(target_os = "linux", target_arch = "x86_64"), + all( + target_os = "windows", + target_arch = "x86_64", + target_env = "msvc" + ), + all(target_os = "macos", target_arch = "aarch64") + )) { + return Err(failed("Managed Torch is unsupported on this platform")); + } + if !cfg!(target_os = "linux") && adapter != "none" { + return Err(failed( + "Image dependency profiles are unavailable on this platform", + )); } let version = tag .strip_prefix('v') @@ -814,7 +903,13 @@ impl VersionManager { )); } self.resolve_installable_release(tag).await?; - let output = Command::new(python) + let interpreters = super::torch_alternatives::installed_torch_interpreters().await; + let interpreter = interpreters + .iter() + .find(|(name, _)| name == python) + .map(|(_, path)| path) + .ok_or_else(|| failed("Selected Python interpreter is unavailable"))?; + let output = Command::new(interpreter) .args([ "-I", "-c", @@ -879,7 +974,7 @@ impl VersionManager { qualification: "qualified".into(), expires_in_seconds: PREVIEW_TTL.as_secs(), }; - let path_output = Command::new(python) + let path_output = Command::new(interpreter) .args(["-I", "-c", "import sys; print(sys.executable)"]) .output() .await @@ -910,7 +1005,7 @@ impl VersionManager { } // This temporary directory is only a resolver workspace. Retained preview // data is held in memory, so restart invalidates every outstanding ID. - let workspace = tempfile::tempdir().map_err(PumasError::from)?; + let workspace = std::sync::Arc::new(tempfile::tempdir().map_err(PumasError::from)?); let resolver = workspace.path().join("resolve_runtime.py"); fs::write( &resolver, @@ -918,7 +1013,7 @@ impl VersionManager { ) .await .map_err(PumasError::from)?; - let mut command = Command::new(python); + let mut command = Command::new(interpreter); command .arg(&resolver) .args([ @@ -931,7 +1026,14 @@ impl VersionManager { "--output", ]) .arg(workspace.path()); - let run = run_preview_resolver(command, workspace.path(), Duration::from_secs(180)).await?; + self.torch_cleanup.drain_residual_child_slots().await?; + let run = run_preview_resolver( + command, + &workspace, + Duration::from_secs(180), + &self.torch_cleanup, + ) + .await?; if let Some(rejection) = resolver_rejection(run) { return Ok(rejection); } @@ -946,14 +1048,22 @@ impl VersionManager { .map_err(PumasError::from)?; let parsed: Resolution = serde_json::from_str(&resolution) .map_err(|e| failed(format!("Invalid resolver manifest: {e}")))?; - if parsed.torch != format!("{version}+{build}") + if parsed.release != version || parsed.build != build || parsed.python != expected_python || parsed.adapter != adapter || parsed.artifacts.is_empty() || parsed.implementation != "cpython" - || parsed.machine != "x86_64" - || !parsed.platform.starts_with("Linux-") + || !match std::env::consts::OS { + "linux" => parsed.machine == "x86_64" && parsed.platform.starts_with("Linux-"), + "windows" => parsed.machine == "AMD64" && parsed.platform.starts_with("Windows-"), + "macos" => parsed.machine == "arm64" && parsed.platform.starts_with("macOS-"), + _ => false, + } + || !parsed + .artifacts + .iter() + .any(|artifact| artifact.name == "torch" && artifact.version == parsed.torch) { return Err(failed( "Resolver manifest does not match requested Torch selection", @@ -975,9 +1085,11 @@ impl VersionManager { } let interpreter_path = std::fs::canonicalize(&parsed.interpreter) .map_err(|e| failed(format!("Cannot identify selected Python executable: {e}")))?; - if !interpreter_path.is_absolute() || !parsed.interpreter.starts_with('/') { + if !Path::new(&parsed.interpreter).is_absolute() + || interpreter_path != std::fs::canonicalize(interpreter).map_err(PumasError::from)? + { return Err(failed( - "Resolver did not identify an absolute Python executable", + "Resolver did not identify the selected absolute Python executable", )); } let interpreter_hash = format!( @@ -1064,7 +1176,8 @@ impl VersionManager { .unwrap_or_default(); let distribution_names = serde_json::to_string(&distribution_names).map_err(|e| failed(e.to_string()))?; - let mut hardware_command = Command::new(runtime.join("venv/bin/python")); + let mut hardware_command = + Command::new(pumas_library::platform::paths::venv_python(&runtime)); hardware_command.kill_on_drop(true).arg("-I").arg("-c").arg(r#"import hashlib,importlib.metadata,json,subprocess,sys from pathlib import Path import torch @@ -1073,6 +1186,8 @@ if torch.cuda.is_available(): for index in range(torch.cuda.device_count()): devices.append({"name":torch.cuda.get_device_name(index),"capability":list(torch.cuda.get_device_capability(index))}) identity={"cuda":torch.version.cuda,"hip":torch.version.hip,"device_count":len(devices),"devices":devices} +mps_available=bool(getattr(getattr(torch.backends,"mps",None),"is_available",lambda:False)()) +identity["mps_available"]=mps_available versions={} for name in json.loads(sys.argv[1]): try: versions[name]=importlib.metadata.version(name) diff --git a/rust/crates/pumas-core/src/platform/managed_child.rs b/rust/crates/pumas-core/src/platform/managed_child.rs new file mode 100644 index 000000000..51ecd876b --- /dev/null +++ b/rust/crates/pumas-core/src/platform/managed_child.rs @@ -0,0 +1,892 @@ +//! Generation-owned child supervision for managed Torch operations. +//! +//! Unix children lead a private process group. Windows children are admitted +//! suspended to a retained kill-on-close Job before their first instruction. + +use crate::models::RuntimeEndpointUrl; +use std::io; +use std::process::{Child, Command, ExitStatus}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +const POLL_INTERVAL: Duration = Duration::from_millis(20); + +pub struct ManagedChild { + child: Option, + drained: bool, + cleanup_lease: Option>, + custody: Option>, + #[cfg(test)] + force_observation_failure: Arc, + #[cfg(windows)] + job: std::sync::Arc, +} + +#[derive(Debug, Default)] +pub struct ManagedChildCustodySlot { + parked: Mutex>, + active: std::sync::atomic::AtomicBool, + cleanup_pending: std::sync::atomic::AtomicBool, + admission_complete: std::sync::atomic::AtomicBool, + changed: tokio::sync::Notify, +} + +impl ManagedChildCustodySlot { + pub fn new() -> Arc { + Arc::new(Self::default()) + } + + pub fn has_parked_child(&self) -> bool { + self.parked + .lock() + .expect("Child custody poisoned") + .is_some() + } + + pub fn is_active(&self) -> bool { + self.active.load(std::sync::atomic::Ordering::Acquire) + } + + pub fn is_cleanup_pending(&self) -> bool { + self.cleanup_pending + .load(std::sync::atomic::Ordering::Acquire) + } + + pub async fn wait_for_park_or_completion(&self) -> bool { + loop { + let notified = self.changed.notified(); + tokio::pin!(notified); + notified.as_mut().enable(); + if self.has_parked_child() { + return true; + } + if self + .admission_complete + .load(std::sync::atomic::Ordering::Acquire) + && !self.is_active() + { + return false; + } + notified.await; + } + } + + /// Retry a bounded process-tree drain. Failure leaves custody in this slot. + pub fn drain(&self, deadline: Duration) -> io::Result { + let Some(mut child) = self + .parked + .lock() + .map_err(|_| io::Error::other("Child custody poisoned"))? + .take() + else { + return if self.is_active() { + Err(io::Error::new( + io::ErrorKind::WouldBlock, + "Owned child has not returned custody", + )) + } else { + Ok(false) + }; + }; + match child.terminate_and_drain(deadline) { + Ok(_) => Ok(true), + Err(error) => { + *self + .parked + .lock() + .map_err(|_| io::Error::other("Child custody poisoned"))? = Some(child); + Err(error) + } + } + } +} + +impl std::fmt::Debug for ManagedChild { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("ManagedChild") + .field("pid", &self.id()) + .field("drained", &self.drained) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct ManagedListenerCustody { + #[cfg(unix)] + pid: u32, + #[cfg(windows)] + job: std::sync::Arc, +} + +impl ManagedListenerCustody { + pub fn owns_listener(&self, endpoint: &RuntimeEndpointUrl) -> io::Result { + #[cfg(target_os = "linux")] + { + super::runtime_listener::owns_listener(self.pid, endpoint) + } + #[cfg(target_os = "macos")] + { + macos_owns_listener(self.pid, endpoint) + } + #[cfg(windows)] + { + windows::job_owns_listener(&self.job, endpoint) + } + } +} + +impl ManagedChild { + pub fn spawn(command: &mut Command, custody: Arc) -> io::Result { + if custody + .active + .swap(true, std::sync::atomic::Ordering::AcqRel) + { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "Custody slot already active", + )); + } + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + let child = match command.spawn() { + Ok(child) => child, + Err(error) => { + custody + .active + .store(false, std::sync::atomic::Ordering::Release); + custody + .admission_complete + .store(true, std::sync::atomic::Ordering::Release); + custody.changed.notify_waiters(); + return Err(error); + } + }; + custody + .admission_complete + .store(true, std::sync::atomic::Ordering::Release); + custody.changed.notify_waiters(); + Ok(Self { + child: Some(child), + drained: false, + cleanup_lease: None, + custody: Some(custody), + #[cfg(test)] + force_observation_failure: Arc::new(std::sync::atomic::AtomicBool::new(false)), + }) + } + #[cfg(windows)] + { + let result = windows::spawn(command, custody.clone()); + custody + .admission_complete + .store(true, std::sync::atomic::Ordering::Release); + if result.is_err() && !custody.is_cleanup_pending() { + custody + .active + .store(false, std::sync::atomic::Ordering::Release); + } + custody.changed.notify_waiters(); + result + } + } + + pub fn id(&self) -> u32 { + self.child.as_ref().expect("managed child retained").id() + } + + pub fn attach_cleanup_lease(&mut self, lease: Arc) { + self.cleanup_lease = Some(lease); + } + + pub fn listener_custody(&self) -> ManagedListenerCustody { + ManagedListenerCustody { + #[cfg(unix)] + pid: self.id(), + #[cfg(windows)] + job: self.job.clone(), + } + } + + /// Observe the leader without releasing its Unix PID/PGID pin. + pub fn observe_exit(&mut self) -> io::Result> { + #[cfg(target_os = "linux")] + { + super::linux_group::observe_exit(self.id()) + } + #[cfg(target_os = "macos")] + { + use std::os::unix::process::ExitStatusExt; + #[allow(unsafe_code)] + let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() }; + // SAFETY: `info` is a valid output buffer; WNOWAIT retains the + // direct child's PID until all members have drained. + #[allow(unsafe_code)] + let result = unsafe { + libc::waitid( + libc::P_PID, + self.id(), + &mut info, + libc::WEXITED | libc::WNOWAIT | libc::WNOHANG, + ) + }; + if result != 0 { + return Err(io::Error::last_os_error()); + } + // SAFETY: waitid initialized siginfo_t on success. + #[allow(unsafe_code)] + if unsafe { info.si_pid() } == 0 { + return Ok(None); + } + #[allow(unsafe_code)] + let status = unsafe { info.si_status() }; + Ok(Some(ExitStatus::from_raw( + if info.si_code == libc::CLD_EXITED { + status << 8 + } else { + status + }, + ))) + } + #[cfg(windows)] + { + self.child + .as_mut() + .expect("managed child retained") + .try_wait() + } + } + + /// Stop the owned generation, prove descendant drain, then reap its leader. + /// An error retains custody so callers can retry rather than publishing or + /// deleting a still-owned staging directory. + pub fn terminate_and_drain(&mut self, deadline: Duration) -> io::Result { + #[cfg(test)] + if self + .force_observation_failure + .load(std::sync::atomic::Ordering::Acquire) + { + return Err(io::Error::other("Injected process observation failure")); + } + let until = Instant::now() + deadline; + loop { + #[cfg(target_os = "linux")] + { + super::linux_group::signal_group(self.id())?; + if !super::linux_group::group_has_live_members( + i32::try_from(self.id()).map_err(|_| io::Error::other("Invalid group ID"))?, + )? { + break; + } + } + #[cfg(target_os = "macos")] + { + let pid = + i32::try_from(self.id()).map_err(|_| io::Error::other("Invalid group ID"))?; + // SAFETY: the unreaped direct child pins its group ID; the + // group was created by this owner at spawn. + #[allow(unsafe_code)] + if unsafe { libc::killpg(pid, libc::SIGKILL) } != 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() != Some(libc::ESRCH) { + return Err(error); + } + } + if !macos_group_has_live_members(pid)? { + break; + } + } + #[cfg(windows)] + { + if !windows::terminate_and_has_members(&self.job)? { + break; + } + } + if Instant::now() >= until { + return Err(io::Error::new( + io::ErrorKind::TimedOut, + "Owned process-tree drain timed out", + )); + } + std::thread::sleep(POLL_INTERVAL); + } + let status = self + .child + .as_mut() + .expect("managed child retained") + .wait()?; + self.drained = true; + if let Some(custody) = &self.custody { + custody + .active + .store(false, std::sync::atomic::Ordering::Release); + custody + .cleanup_pending + .store(false, std::sync::atomic::Ordering::Release); + custody.changed.notify_waiters(); + } + Ok(status) + } +} + +impl Drop for ManagedChild { + fn drop(&mut self) { + if !self.drained { + let Some(child) = self.child.take() else { + return; + }; + let lease = self.cleanup_lease.take(); + let custody = self + .custody + .take() + .expect("Admitted child custody registered"); + #[cfg(windows)] + let job = self.job.clone(); + #[cfg(test)] + let force_observation_failure = self.force_observation_failure.clone(); + let parked = ManagedChild { + child: Some(child), + drained: false, + cleanup_lease: lease, + custody: Some(custody.clone()), + #[cfg(windows)] + job, + #[cfg(test)] + force_observation_failure, + }; + let mut slot = custody + .parked + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + assert!(slot.is_none(), "Child custody slot already occupied"); + custody + .cleanup_pending + .store(true, std::sync::atomic::Ordering::Release); + *slot = Some(parked); + custody.changed.notify_waiters(); + } + } +} + +#[cfg(target_os = "macos")] +fn macos_group_has_live_members(group: i32) -> io::Result { + let output = Command::new("/bin/ps") + .args(["-A", "-o", "pgid=,stat="]) + .output()?; + if !output.status.success() { + return Err(io::Error::other("Could not observe managed process group")); + } + let text = String::from_utf8(output.stdout) + .map_err(|_| io::Error::other("Invalid process-group observation"))?; + for line in text.lines() { + let mut fields = line.split_whitespace(); + let Some(pgid) = fields.next() else { continue }; + if pgid.parse::().ok() == Some(group) { + let state = fields + .next() + .ok_or_else(|| io::Error::other("Incomplete process state"))?; + if !state.starts_with('Z') { + return Ok(true); + } + } + } + Ok(false) +} + +#[cfg(target_os = "macos")] +fn macos_owns_listener(pid: u32, endpoint: &RuntimeEndpointUrl) -> io::Result { + let url = url::Url::parse(endpoint.as_str()).map_err(io::Error::other)?; + let host = url + .host_str() + .ok_or_else(|| io::Error::other("Listener host absent"))?; + if !host + .parse::() + .map_err(io::Error::other)? + .is_loopback() + { + return Err(io::Error::other("Managed listener must be loopback")); + } + let port = url + .port_or_known_default() + .ok_or_else(|| io::Error::other("Listener port absent"))?; + let output = Command::new("/usr/sbin/lsof") + .args([ + "-nP", + "-a", + "-p", + &pid.to_string(), + "-iTCP", + "-sTCP:LISTEN", + "-FfnT", + ]) + .output()?; + if !output.status.success() { + return Ok(false); + } + let text = String::from_utf8(output.stdout) + .map_err(|_| io::Error::other("Invalid listener observation"))?; + Ok(macos_lsof_has_listener(&text, host, port)) +} + +#[cfg(target_os = "macos")] +fn macos_lsof_has_listener(output: &str, host: &str, port: u16) -> bool { + let mut name_matches = false; + let mut listening = false; + for line in output.lines().chain(std::iter::once("f")) { + if line.starts_with('f') { + if name_matches && listening { + return true; + } + name_matches = false; + listening = false; + } else if let Some(name) = line.strip_prefix('n') { + name_matches = name == format!("{host}:{port}") || name == format!("[{host}]:{port}"); + } else if line == "TST=LISTEN" { + listening = true; + } + } + false +} + +#[cfg(all(test, target_os = "macos"))] +#[test] +fn macos_listener_attribution_requires_structured_listen_state() { + let observation = + "p42\nf1\nn127.0.0.1:8080\nTST=LISTEN\nf2\nn127.0.0.1:8081\nTST=ESTABLISHED\n"; + assert!(macos_lsof_has_listener(observation, "127.0.0.1", 8080)); + assert!(!macos_lsof_has_listener(observation, "127.0.0.1", 8081)); + assert!(!macos_lsof_has_listener(observation, "127.0.0.2", 8080)); +} + +#[cfg(windows)] +#[allow(unsafe_code)] +mod windows { + use super::*; + use std::mem::{size_of, zeroed}; + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; + use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; + use windows_sys::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32, + }; + use windows_sys::Win32::System::JobObjects::{ + AssignProcessToJobObject, CreateJobObjectW, JobObjectBasicAccountingInformation, + JobObjectExtendedLimitInformation, QueryInformationJobObject, SetInformationJobObject, + TerminateJobObject, JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, + JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, + }; + use windows_sys::Win32::System::Threading::{ + OpenThread, ResumeThread, CREATE_SUSPENDED, THREAD_SUSPEND_RESUME, + }; + + fn checked_tcp_row_count( + buffer_size: usize, + reported_size: usize, + count: usize, + row_size: usize, + ) -> io::Result { + if reported_size < size_of::() + || reported_size > buffer_size + || row_size == 0 + || count > (reported_size - size_of::()) / row_size + { + return Err(io::Error::other("Invalid TCP owner table")); + } + Ok(count) + } + + pub(super) fn spawn( + command: &mut Command, + custody: Arc, + ) -> io::Result { + use std::os::windows::process::CommandExt; + // The child cannot execute or create descendants before Job admission. + command.creation_flags(CREATE_SUSPENDED); + let raw_job = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) }; + if raw_job.is_null() { + return Err(io::Error::last_os_error()); + } + let job = std::sync::Arc::new(unsafe { OwnedHandle::from_raw_handle(raw_job) }); + let mut limits: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() }; + limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if unsafe { + SetInformationJobObject( + raw_job, + JobObjectExtendedLimitInformation, + (&raw const limits).cast(), + size_of::() as u32, + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + let mut child = command.spawn()?; + if unsafe { AssignProcessToJobObject(raw_job, child.as_raw_handle()) } == 0 { + let error = io::Error::last_os_error(); + // Still suspended: no child code or descendant could have run. + let _ = child.kill(); + let _ = child.wait(); + return Err(error); + } + #[cfg(test)] + let forced_resume_failure = command.get_envs().any(|(key, value)| { + key == "PUMAS_TEST_FORCE_RESUME_FAILURE" && value == Some(std::ffi::OsStr::new("1")) + }); + let resumed = { + #[cfg(test)] + if forced_resume_failure { + Err(io::Error::other("Injected resume failure")) + } else { + resume_initial_thread(child.id()) + } + #[cfg(not(test))] + { + resume_initial_thread(child.id()) + } + }; + if let Err(error) = resumed { + // Still suspended and Job-owned. Park it for the registered + // supervisor; caller failure never closes the Job prematurely. + drop(ManagedChild { + child: Some(child), + job, + drained: false, + cleanup_lease: None, + custody: Some(custody), + #[cfg(test)] + force_observation_failure: Arc::new(std::sync::atomic::AtomicBool::new(false)), + }); + return Err(error); + } + Ok(ManagedChild { + child: Some(child), + job, + drained: false, + cleanup_lease: None, + custody: Some(custody), + #[cfg(test)] + force_observation_failure: Arc::new(std::sync::atomic::AtomicBool::new(false)), + }) + } + + fn resume_initial_thread(pid: u32) -> io::Result<()> { + let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; + if snapshot == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + let snapshot = unsafe { OwnedHandle::from_raw_handle(snapshot) }; + let mut entry: THREADENTRY32 = unsafe { zeroed() }; + entry.dwSize = size_of::() as u32; + let mut found = Vec::new(); + let mut has_entry = unsafe { Thread32First(snapshot.as_raw_handle(), &mut entry) } != 0; + while has_entry { + if entry.th32OwnerProcessID == pid { + found.push(entry.th32ThreadID); + } + has_entry = unsafe { Thread32Next(snapshot.as_raw_handle(), &mut entry) } != 0; + } + if found.len() != 1 { + return Err(io::Error::other( + "Suspended child did not have one initial thread", + )); + } + let thread = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, found[0]) }; + if thread.is_null() { + return Err(io::Error::last_os_error()); + } + let thread = unsafe { OwnedHandle::from_raw_handle(thread) }; + if unsafe { ResumeThread(thread.as_raw_handle()) } == u32::MAX { + return Err(io::Error::last_os_error()); + } + Ok(()) + } + + pub(super) fn terminate_and_has_members(job: &OwnedHandle) -> io::Result { + let raw = job.as_raw_handle() as HANDLE; + if unsafe { TerminateJobObject(raw, 1) } == 0 { + return Err(io::Error::last_os_error()); + } + let mut accounting: JOBOBJECT_BASIC_ACCOUNTING_INFORMATION = unsafe { zeroed() }; + if unsafe { + QueryInformationJobObject( + raw, + JobObjectBasicAccountingInformation, + (&raw mut accounting).cast(), + size_of::() as u32, + std::ptr::null_mut(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + Ok(accounting.ActiveProcesses != 0) + } + + pub(super) fn job_owns_listener( + job: &OwnedHandle, + endpoint: &RuntimeEndpointUrl, + ) -> io::Result { + use std::net::{IpAddr, Ipv4Addr}; + use windows_sys::Win32::NetworkManagement::IpHelper::{ + GetExtendedTcpTable, MIB_TCPROW_OWNER_PID, MIB_TCP_STATE_LISTEN, + TCP_TABLE_OWNER_PID_LISTENER, + }; + use windows_sys::Win32::Networking::WinSock::AF_INET; + use windows_sys::Win32::System::JobObjects::IsProcessInJob; + use windows_sys::Win32::System::Threading::{ + OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, + }; + let url = url::Url::parse(endpoint.as_str()).map_err(io::Error::other)?; + let address: IpAddr = url + .host_str() + .ok_or_else(|| io::Error::other("Listener host absent"))? + .parse() + .map_err(io::Error::other)?; + if !address.is_loopback() { + return Err(io::Error::other("Managed listener must be loopback")); + } + let port = url + .port_or_known_default() + .ok_or_else(|| io::Error::other("Listener port absent"))?; + if !address.is_ipv4() { + return Err(io::Error::other("IPv6 listener attribution unavailable")); + } + let mut size = 0; + unsafe { + GetExtendedTcpTable( + std::ptr::null_mut(), + &mut size, + 0, + u32::from(AF_INET), + TCP_TABLE_OWNER_PID_LISTENER, + 0, + ) + }; + if size < size_of::() as u32 { + return Err(io::Error::other("Invalid TCP owner table size")); + } + let mut table = vec![0u64; (size as usize).div_ceil(8)]; + let code = unsafe { + GetExtendedTcpTable( + table.as_mut_ptr().cast(), + &mut size, + 0, + u32::from(AF_INET), + TCP_TABLE_OWNER_PID_LISTENER, + 0, + ) + }; + if code != 0 { + return Err(io::Error::from_raw_os_error(code as i32)); + } + let reported_size = size as usize; + if reported_size < size_of::() || reported_size > table.len() * size_of::() { + return Err(io::Error::other("Invalid TCP owner table")); + } + let count = unsafe { table.as_ptr().cast::().read_unaligned() } as usize; + let row_size = size_of::(); + for index in 0..checked_tcp_row_count( + table.len() * size_of::(), + reported_size, + count, + row_size, + )? { + let offset = size_of::() + index * row_size; + let row = unsafe { + table + .as_ptr() + .cast::() + .add(offset) + .cast::() + .read_unaligned() + }; + if row.dwState != MIB_TCP_STATE_LISTEN as u32 + || Ipv4Addr::from(row.dwLocalAddr.to_ne_bytes()) != address + || u16::from_be(row.dwLocalPort as u16) != port + { + continue; + } + let process = + unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, row.dwOwningPid) }; + if process.is_null() { + return Err(io::Error::last_os_error()); + } + let process = unsafe { OwnedHandle::from_raw_handle(process) }; + let mut member = 0; + if unsafe { IsProcessInJob(process.as_raw_handle(), job.as_raw_handle(), &mut member) } + == 0 + { + return Err(io::Error::last_os_error()); + } + if member != 0 { + return Ok(true); + } + } + Ok(false) + } + + #[cfg(test)] + #[test] + fn tcp_table_bounds_reject_empty_and_truncated_rows() { + assert!(checked_tcp_row_count(0, 0, 0, 24).is_err()); + assert_eq!(checked_tcp_row_count(4, 4, 0, 24).unwrap(), 0); + assert!(checked_tcp_row_count(4, 4, 1, 24).is_err()); + assert!(checked_tcp_row_count(8, 12, 0, 24).is_err()); + assert_eq!(checked_tcp_row_count(28, 28, 1, 24).unwrap(), 1); + } +} + +#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] +mod unix_tests { + use super::*; + + fn wait_for(path: &std::path::Path) -> u32 { + let deadline = Instant::now() + Duration::from_secs(5); + loop { + if let Ok(contents) = std::fs::read_to_string(path) { + if let Ok(pid) = contents.trim().parse() { + return pid; + } + } + assert!( + Instant::now() < deadline, + "fixture descendant did not start" + ); + std::thread::sleep(POLL_INTERVAL); + } + } + + #[test] + fn managed_child_drains_process_group_descendants() { + let root = tempfile::tempdir().unwrap(); + let marker = root.path().join("descendant.pid"); + let mut command = Command::new("sh"); + command + .arg("-c") + .arg("sleep 30 & echo $! > \"$1\"; wait") + .arg("sh") + .arg(&marker); + let custody = ManagedChildCustodySlot::new(); + let mut child = ManagedChild::spawn(&mut command, custody).unwrap(); + let descendant = wait_for(&marker); + assert!(super::super::process::is_process_alive(descendant)); + child.terminate_and_drain(Duration::from_secs(5)).unwrap(); + #[cfg(target_os = "linux")] + assert!(!super::super::linux_group::group_has_live_members(child.id() as i32).unwrap()); + #[cfg(target_os = "macos")] + assert!(!macos_group_has_live_members(child.id() as i32).unwrap()); + } + + #[test] + fn cancelled_managed_child_drop_drains_group() { + let root = tempfile::tempdir().unwrap(); + let marker = root.path().join("descendant.pid"); + let mut command = Command::new("sh"); + command + .arg("-c") + .arg("sleep 30 & echo $! > \"$1\"; wait") + .arg("sh") + .arg(&marker); + let custody = ManagedChildCustodySlot::new(); + let child = ManagedChild::spawn(&mut command, custody.clone()).unwrap(); + let group = child.id() as i32; + wait_for(&marker); + drop(child); + assert!(custody.has_parked_child()); + custody.drain(Duration::from_secs(5)).unwrap(); + #[cfg(target_os = "linux")] + assert!(!super::super::linux_group::group_has_live_members(group).unwrap()); + #[cfg(target_os = "macos")] + assert!(!macos_group_has_live_members(group).unwrap()); + } + + #[test] + fn failed_observation_retains_lease_without_blocking_drop() { + use std::sync::atomic::{AtomicBool, Ordering}; + struct Lease(Arc); + impl Drop for Lease { + fn drop(&mut self) { + self.0.store(true, Ordering::Release); + } + } + let mut command = Command::new("sh"); + command.args(["-c", "sleep 30"]); + let custody = ManagedChildCustodySlot::new(); + let mut child = ManagedChild::spawn(&mut command, custody.clone()).unwrap(); + let failure = child.force_observation_failure.clone(); + let released = Arc::new(AtomicBool::new(false)); + child.attach_cleanup_lease(Arc::new(Lease(released.clone()))); + failure.store(true, Ordering::Release); + let started = Instant::now(); + drop(child); + assert!(started.elapsed() < Duration::from_secs(1)); + assert!(custody.drain(Duration::from_millis(50)).is_err()); + assert!(!released.load(Ordering::Acquire)); + failure.store(false, Ordering::Release); + custody.drain(Duration::from_secs(5)).unwrap(); + assert!(released.load(Ordering::Acquire)); + } +} + +#[cfg(all(test, windows))] +mod windows_tests { + use super::*; + use std::os::windows::io::AsRawHandle; + use windows_sys::Win32::System::JobObjects::IsProcessInJob; + use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION}; + + #[test] + fn failed_resume_parks_suspended_job_for_bounded_retry() { + let custody = ManagedChildCustodySlot::new(); + let mut command = Command::new("powershell"); + command + .args(["-NoProfile", "-Command", "Start-Sleep 30"]) + .env("PUMAS_TEST_FORCE_RESUME_FAILURE", "1"); + let started = Instant::now(); + assert!(ManagedChild::spawn(&mut command, custody.clone()).is_err()); + assert!(started.elapsed() < Duration::from_secs(2)); + assert!(custody.is_cleanup_pending() && custody.has_parked_child()); + custody.drain(Duration::from_secs(10)).unwrap(); + assert!(!custody.is_active() && !custody.has_parked_child()); + } + + #[test] + #[allow(unsafe_code)] + fn suspended_admission_assigns_descendants_to_job_and_drains_them() { + let root = tempfile::tempdir().unwrap(); + let marker = root.path().join("descendant.pid"); + let script = format!( + "$p=Start-Process powershell -ArgumentList '-NoProfile','-Command','Start-Sleep 30' -PassThru; Set-Content -Path '{}' -Value $p.Id; Start-Sleep 30", + marker.display() + ); + let mut command = Command::new("powershell"); + command.args(["-NoProfile", "-Command", &script]); + let custody = ManagedChildCustodySlot::new(); + let mut child = ManagedChild::spawn(&mut command, custody).unwrap(); + let deadline = Instant::now() + Duration::from_secs(10); + let descendant = loop { + if let Ok(contents) = std::fs::read_to_string(&marker) { + if let Ok(pid) = contents.trim().parse::() { + break pid; + } + } + assert!( + Instant::now() < deadline, + "fixture descendant did not start" + ); + std::thread::sleep(POLL_INTERVAL); + }; + let process = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, descendant) }; + assert!(!process.is_null()); + let mut member = 0; + assert_ne!( + unsafe { IsProcessInJob(process, child.job.as_raw_handle(), &mut member) }, + 0 + ); + assert_ne!(member, 0, "descendant escaped the admitted Job"); + unsafe { windows_sys::Win32::Foundation::CloseHandle(process) }; + child.terminate_and_drain(Duration::from_secs(10)).unwrap(); + assert!(!windows::terminate_and_has_members(&child.job).unwrap()); + } +} diff --git a/rust/crates/pumas-core/src/platform/mod.rs b/rust/crates/pumas-core/src/platform/mod.rs index 64960bf15..014cf9b27 100644 --- a/rust/crates/pumas-core/src/platform/mod.rs +++ b/rust/crates/pumas-core/src/platform/mod.rs @@ -21,6 +21,7 @@ pub(crate) mod capability_fs; pub mod filesystem; #[cfg(target_os = "linux")] pub mod linux_group; +pub mod managed_child; pub mod paths; pub mod permissions; pub mod process; diff --git a/rust/crates/pumas-core/src/platform/process.rs b/rust/crates/pumas-core/src/platform/process.rs index db8f4e94e..b09cbeb24 100644 --- a/rust/crates/pumas-core/src/platform/process.rs +++ b/rust/crates/pumas-core/src/platform/process.rs @@ -9,6 +9,43 @@ use crate::error::{PumasError, Result}; use std::process::Command; use tracing::{debug, warn}; +/// Compare open file identities before deleting owned process metadata. +#[allow(unsafe_code)] +pub(crate) fn same_file_identity( + left: &std::fs::File, + right: &std::fs::File, +) -> std::io::Result { + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + let left = left.metadata()?; + let right = right.metadata()?; + Ok(left.dev() == right.dev() && left.ino() == right.ino()) + } + #[cfg(windows)] + { + use std::mem::zeroed; + use std::os::windows::io::AsRawHandle; + use windows_sys::Win32::Storage::FileSystem::{ + GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, + }; + let mut left_info: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() }; + let mut right_info: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() }; + // SAFETY: both file handles are live; the output buffers have the + // exact Win32 structure layout and remain valid during each call. + if unsafe { GetFileInformationByHandle(left.as_raw_handle(), &mut left_info) } == 0 + || unsafe { GetFileInformationByHandle(right.as_raw_handle(), &mut right_info) } == 0 + { + return Err(std::io::Error::last_os_error()); + } + Ok( + left_info.dwVolumeSerialNumber == right_info.dwVolumeSerialNumber + && left_info.nFileIndexHigh == right_info.nFileIndexHigh + && left_info.nFileIndexLow == right_info.nFileIndexLow, + ) + } +} + /// Check if a process with the given PID is alive. /// /// # Platform Behavior diff --git a/rust/crates/pumas-core/src/process/manager.rs b/rust/crates/pumas-core/src/process/manager.rs index 56d14498f..0054f1659 100644 --- a/rust/crates/pumas-core/src/process/manager.rs +++ b/rust/crates/pumas-core/src/process/manager.rs @@ -1,7 +1,7 @@ //! High-level process management. use super::launcher::{BinaryLaunchConfig, LaunchResult, ProcessLauncher}; -use crate::error::Result; +use crate::error::{PumasError, Result}; use crate::system::{ProcessResources, ResourceTracker}; use std::collections::HashMap; use std::fs; @@ -11,6 +11,13 @@ use std::sync::{Arc, Mutex}; use std::thread; use tracing::{debug, error, info, warn}; +const LEGACY_TORCH_REJECTION: &str = + "Legacy Torch process control is unavailable on this platform; use owned Torch runtime-profile APIs"; + +fn legacy_torch_requires_owned_api() -> bool { + !cfg!(target_os = "linux") +} + #[derive(Debug, Clone, Default)] struct CachedProcessStatus { running: bool, @@ -249,6 +256,17 @@ impl ProcessManager { version_dir: &Path, log_dir: Option<&Path>, ) -> LaunchResult { + if legacy_torch_requires_owned_api() { + let message = LEGACY_TORCH_REJECTION.to_owned(); + *self.last_launch_error.lock().unwrap() = Some(message.clone()); + return LaunchResult { + success: false, + process: None, + log_path: None, + error: Some(message), + ready: false, + }; + } // Clear previous error { let mut error = self.last_launch_error.lock().unwrap(); @@ -307,6 +325,12 @@ impl ProcessManager { /// /// Looks for torch.pid files in the torch-versions directory and stops those processes. pub fn stop_torch(&self) -> Result { + if legacy_torch_requires_owned_api() { + return Err(PumasError::LaunchFailed { + app: "torch".to_owned(), + message: LEGACY_TORCH_REJECTION.to_owned(), + }); + } let timeout_ms = 2000; let mut stopped_any = false; @@ -371,6 +395,9 @@ impl ProcessManager { } fn detect_torch_running(root_dir: &Path) -> bool { + if legacy_torch_requires_owned_api() { + return false; + } // Check for PID files in torch-versions directory let versions_dir = root_dir.join("torch-versions"); if versions_dir.exists() { @@ -552,6 +579,7 @@ mod tests { assert!(manager.is_ollama_running()); } + #[cfg(target_os = "linux")] #[test] fn torch_liveness_read_uses_cache_until_explicit_refresh() { let temp_dir = TempDir::new().unwrap(); @@ -571,6 +599,44 @@ mod tests { assert!(manager.is_torch_running()); } + #[cfg(target_os = "linux")] + #[test] + fn linux_legacy_torch_path_remains_enabled() { + assert!(!legacy_torch_requires_owned_api()); + } + + #[cfg(not(target_os = "linux"))] + #[test] + fn non_linux_legacy_torch_control_rejects_before_pid_or_process_actions() { + assert!(legacy_torch_requires_owned_api()); + let temp_dir = TempDir::new().unwrap(); + let version_dir = temp_dir.path().join("torch-versions").join("test"); + fs::create_dir_all(&version_dir).unwrap(); + let pid_file = version_dir.join("torch.pid"); + fs::write(&pid_file, "4294967295").unwrap(); + let manager = ProcessManager::new(temp_dir.path(), None).unwrap(); + assert!(!manager.is_torch_running()); + assert!(!manager.refresh_torch_running()); + + let launched = manager.launch_torch("test", &version_dir, None); + assert!(!launched.success); + assert!(launched.process.is_none()); + assert!(launched.log_path.is_none()); + assert!(!launched.ready); + assert!(launched + .error + .as_deref() + .unwrap() + .contains("owned Torch runtime-profile APIs")); + assert_eq!(manager.last_launch_error(), launched.error); + + let stopped = manager.stop_torch().unwrap_err(); + assert!(stopped + .to_string() + .contains("owned Torch runtime-profile APIs")); + assert!(pid_file.exists()); + } + #[cfg(unix)] #[test] fn child_exit_observer_clears_matching_liveness_generation() { diff --git a/rust/crates/pumas-core/src/runtime_profiles/process_owner.rs b/rust/crates/pumas-core/src/runtime_profiles/process_owner.rs index 70eb49131..1508ac22c 100644 --- a/rust/crates/pumas-core/src/runtime_profiles/process_owner.rs +++ b/rust/crates/pumas-core/src/runtime_profiles/process_owner.rs @@ -1,6 +1,6 @@ //! Session custody for managed binary profiles. PID files are diagnostics, never -//! authority to adopt or signal a process. Linux leaders remain unreaped until -//! cooperating group cleanup completes; escaped descendants are not contained. +//! authority to adopt or signal a process. Unix leaders remain unreaped until +//! group cleanup completes; Windows generations retain a kill-on-close Job. use super::router_model_operation::{OwnedRouterModelOperation, RouterModelState}; use super::{RuntimeProfileLaunchSpec, RuntimeProfileOperationGuard}; @@ -8,11 +8,12 @@ use crate::models::{ LaunchResponse, RuntimeEndpointUrl, RuntimeLifecycleState, RuntimeProfileId, RuntimeProfileStatus, }; +use crate::platform::managed_child::{ManagedChild, ManagedListenerCustody}; use crate::process::BinaryLaunchConfig; use crate::{PumasError, Result}; +use futures::future::{BoxFuture, FutureExt, Shared}; use std::collections::HashMap; use std::path::PathBuf; -use std::process::Child; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -20,6 +21,14 @@ use tokio::task::JoinHandle; const OBSERVATION_INTERVAL: Duration = Duration::from_millis(20); const STOP_WAIT: Duration = Duration::from_secs(7); +type ChildDrainResult = std::result::Result<(), Arc>; +#[derive(Clone)] +struct ChildDrainCompletion(Shared>); +impl std::fmt::Debug for ChildDrainCompletion { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("ChildDrainCompletion") + } +} /// In-memory identity of a process launched by this API instance. #[derive(Debug, Clone, PartialEq, Eq)] @@ -48,7 +57,6 @@ pub(crate) struct RuntimeProfileProcessOwner { #[derive(Debug, Default)] struct Registry { closed: bool, - #[cfg(target_os = "linux")] generation: u64, sessions: HashMap>, #[cfg(all(test, target_os = "linux"))] @@ -63,6 +71,7 @@ struct Session { context_size: Option, router_models: Option>>, stop: AtomicBool, + child_custody: Arc, state: Mutex, observer_stop: tokio::sync::watch::Sender, observer_terminal: tokio::sync::watch::Sender>, @@ -71,7 +80,6 @@ struct Session { #[derive(Debug)] struct SessionState { status: RuntimeProfileStatus, - #[cfg(target_os = "linux")] launch: Option>, terminal: Option>, worker: Option>, @@ -80,7 +88,9 @@ struct SessionState { observer_error: Option, joined: bool, // A failed cleanup never releases child custody or permits replacement. - residual_child: Option, + residual_child: Option, + custody_drain: Option, + listener: Option, } /// A dropped launch future must not leave its admitted worker running. The @@ -163,17 +173,27 @@ impl RuntimeProfileProcessOwner { guard: RuntimeProfileOperationGuard, #[cfg(target_os = "linux")] observer: Option, ) -> Result { - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] { let _ = (config, spec, model_path, context_size, guard); Err(failure( - "Owned binary runtime profiles are currently supported only on Linux", + "Owned binary runtime profiles are unsupported on this platform", )) } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] { + #[cfg(target_os = "linux")] crate::platform::linux_group::ensure_supported().map_err(|e| failure(e.to_string()))?; + #[cfg(not(target_os = "linux"))] + if spec.provider != crate::models::RuntimeProviderId::Torch { + return Err(failure( + "Only Torch binary profiles are supported on this platform", + )); + } + #[cfg(target_os = "linux")] let router_models = RouterModelState::for_spec(&spec); + #[cfg(not(target_os = "linux"))] + let router_models = None; let session = { let mut registry = self .registry @@ -210,6 +230,7 @@ impl RuntimeProfileProcessOwner { model_path, context_size, stop: AtomicBool::new(false), + child_custody: crate::platform::managed_child::ManagedChildCustodySlot::new(), state: Mutex::new(SessionState { status: RuntimeProfileStatus { profile_id: spec.profile_id.clone(), @@ -230,6 +251,8 @@ impl RuntimeProfileProcessOwner { terminal_observer: None, observer_error: None, residual_child: None, + custody_drain: None, + listener: None, }), spec, }); @@ -243,6 +266,7 @@ impl RuntimeProfileProcessOwner { armed: true, }; // No await between registration, worker retention and gate release. + #[cfg(target_os = "linux")] if let Some(observer) = observer { let mut state = session .state @@ -278,7 +302,7 @@ impl RuntimeProfileProcessOwner { start .send(()) .map_err(|_| failure("Runtime process start gate closed"))?; - #[cfg(test)] + #[cfg(all(test, target_os = "linux"))] { let gate = self.registry.lock().unwrap().launch_reply_gate.clone(); if let Some(gate) = gate { @@ -346,6 +370,15 @@ impl RuntimeProfileProcessOwner { } } } + #[cfg(not(target_os = "linux"))] + if lifecycle == RuntimeLifecycleState::Running + && state + .status + .pid + .is_some_and(|pid| !crate::platform::process::is_process_alive(pid)) + { + lifecycle = RuntimeLifecycleState::Stopping; + } Ok(OwnedRuntimeProfileObservation { generation: session.generation, pid: state.status.pid, @@ -461,10 +494,19 @@ impl RuntimeProfileProcessOwner { } #[cfg(not(target_os = "linux"))] { - let _ = publish; - Err(failure( - "Owned runtime publication unsupported on this platform", - )) + let listener = state + .listener + .as_ref() + .ok_or_else(|| failure("Managed runtime listener custody is absent"))?; + if !listener + .owns_listener(&session.spec.endpoint_url) + .map_err(|error| { + failure(format!("Runtime listener attribution unavailable: {error}")) + })? + { + return Ok(None); + } + Ok(Some(publish(session))) } } @@ -692,6 +734,58 @@ async fn drain_session(session: &Session) -> Result { } state.joined = true; } + let completion = { + let mut state = session + .state + .lock() + .map_err(|_| failure("Runtime process session poisoned"))?; + if let Some(child) = state.residual_child.take() { + // Drop transfers this exact child to the session custody slot. + drop(child); + } + if state.joined + && state.observer.is_none() + && state.terminal_observer.is_none() + && session.child_custody.is_active() + { + if let Some(completion) = &state.custody_drain { + Some(completion.0.clone()) + } else { + let custody = session.child_custody.clone(); + let worker = + tokio::task::spawn_blocking(move || custody.drain(Duration::from_secs(5))); + let completion = async move { + worker + .await + .map_err(|error| Arc::new(error.to_string()))? + .map_err(|error| Arc::new(error.to_string()))?; + Ok(()) + } + .boxed() + .shared(); + state.custody_drain = Some(ChildDrainCompletion(completion.clone())); + Some(completion) + } + } else { + None + } + }; + if let Some(completion) = completion { + let result = completion.await; + let mut state = session + .state + .lock() + .map_err(|_| failure("Runtime process session poisoned"))?; + state.custody_drain = None; + if let Err(error) = result { + return Err(failure(format!( + "Runtime descendant custody remains pending: {error}" + ))); + } + state.listener = None; + state.status.pid = None; + session.observer_terminal.send_replace(Some(true)); + } { let state = session .state @@ -715,14 +809,14 @@ async fn drain_session(session: &Session) -> Result { } } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfileOperationGuard) { let mut child = None; let mut pid_file = None; let execution = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> Result<()> { use std::io::Write; - use std::os::unix::process::CommandExt; use std::process::{Command, Stdio}; + #[cfg(target_os = "linux")] if let Some(models) = &session.router_models { RouterModelState::capture(models)?; } @@ -741,10 +835,17 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi })?; pid_file = Some(( config.pid_file.clone(), - file.metadata() + file.try_clone() .map_err(|e| PumasError::io_with_path(e, &config.pid_file))?, )); - let mut command = Command::new(&config.binary_path); + let binary = if cfg!(not(target_os = "linux")) + && session.spec.provider == crate::models::RuntimeProviderId::Torch + { + crate::platform::paths::venv_python(&config.version_dir) + } else { + config.binary_path.clone() + }; + let mut command = Command::new(binary); if let Some(arg) = &config.command { command.arg(arg); } @@ -752,8 +853,7 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi .args(&config.extra_args) .envs(&config.env_vars) .current_dir(&config.version_dir) - .stdin(Stdio::null()) - .process_group(0); + .stdin(Stdio::null()); if let Some(path) = &config.log_file { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).map_err(|e| PumasError::io_with_path(e, parent))?; @@ -773,8 +873,7 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi return Err(failure("Runtime launch stopped before process creation")); } child = Some( - command - .spawn() + ManagedChild::spawn(&mut command, session.child_custody.clone()) .map_err(|e| failure(format!("Runtime spawn failed: {e}")))?, ); let pid = child.as_ref().expect("spawned child").id(); @@ -785,6 +884,7 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi .lock() .map_err(|_| failure("Runtime process session poisoned"))?; state.status.pid = Some(pid); + state.listener = Some(child.as_ref().expect("owned child").listener_custody()); state.status.state = RuntimeLifecycleState::Running; state.launch = Some(Ok(OwnedRuntimeProfileObservation { generation: session.generation, @@ -797,6 +897,7 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi } drop(guard); loop { + #[cfg(target_os = "linux")] if let Some(models) = &session.router_models { RouterModelState::process_pending(models, |receipt| { if session.stop.load(Ordering::Acquire) @@ -831,7 +932,10 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi if session.stop.load(Ordering::Acquire) { return Ok(()); } - if let Some(status) = crate::platform::linux_group::observe_exit(pid) + if let Some(status) = child + .as_mut() + .expect("owned child") + .observe_exit() .map_err(|e| failure(format!("Runtime exit observation failed: {e}")))? { return Err(failure(format!("Managed runtime exited: {status}"))); @@ -848,12 +952,13 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi state.status.state = RuntimeLifecycleState::Stopping; } session.observer_stop.send_replace(true); + #[cfg(target_os = "linux")] if let Some(models) = &session.router_models { RouterModelState::reject_pending(models); } let spawned = child.is_some(); if let Some(process) = child.as_mut() { - if let Err(e) = cleanup_child(process) { + if let Err(e) = process.terminate_and_drain(Duration::from_secs(5)) { error = Some(format!( "{}; cleanup failed: {e}", error.unwrap_or_default() @@ -863,13 +968,10 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi } } if child.is_none() { - if let Some((path, owned_metadata)) = pid_file { - use std::os::unix::fs::MetadataExt; - let cleanup = std::fs::symlink_metadata(&path).and_then(|current| { - if current.dev() != owned_metadata.dev() || current.ino() != owned_metadata.ino() { - return Err(std::io::Error::other( - "Runtime PID metadata was replaced; preserving unowned replacement", - )); + if let Some((path, owned_file)) = pid_file { + let cleanup = std::fs::File::open(&path).and_then(|current| { + if !crate::platform::process::same_file_identity(&owned_file, ¤t)? { + return Err(std::io::Error::other("Runtime PID metadata was replaced")); } std::fs::remove_file(&path) }); @@ -889,7 +991,10 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi } else { RuntimeLifecycleState::Stopped }; - state.status.pid = child.as_ref().map(Child::id); + state.status.pid = child.as_ref().map(ManagedChild::id); + if child.is_none() { + state.listener = None; + } state.status.last_error = error.clone(); state.residual_child = child; // This receipt reflects completed group cleanup, independently of an @@ -904,29 +1009,6 @@ fn run_worker(session: &Session, config: BinaryLaunchConfig, guard: RuntimeProfi } } -#[cfg(target_os = "linux")] -fn cleanup_child(child: &mut Child) -> Result<()> { - use crate::platform::linux_group; - linux_group::signal_group(child.id()) - .map_err(|e| failure(format!("Owned group signal failed: {e}")))?; - let deadline = std::time::Instant::now() + Duration::from_secs(5); - let group = i32::try_from(child.id()).map_err(|e| failure(e.to_string()))?; - loop { - if !linux_group::group_has_live_members(group) - .map_err(|e| failure(format!("Owned group observation failed: {e}")))? - { - child - .wait() - .map_err(|e| failure(format!("Owned leader reap failed: {e}")))?; - return Ok(()); - } - if std::time::Instant::now() >= deadline { - return Err(failure("Owned group cleanup deadline elapsed")); - } - std::thread::sleep(OBSERVATION_INTERVAL); - } -} - #[cfg(all(test, target_os = "linux", not(target_env = "uclibc")))] mod tests { use super::*; diff --git a/torch-server/probe_runtime.py b/torch-server/probe_runtime.py index 98cb95872..78770c289 100644 --- a/torch-server/probe_runtime.py +++ b/torch-server/probe_runtime.py @@ -237,6 +237,13 @@ def probe(root: Path) -> dict: "hip": hardware["hip"], "device_count": len(hardware["devices"]), "devices": hardware["devices"], + "mps_available": bool( + getattr( + getattr(getattr(torch, "backends", None), "mps", None), + "is_available", + lambda: False, + )() + ), } hardware_fingerprint = hashlib.sha256( json.dumps(hardware_identity, sort_keys=True, separators=(",", ":")).encode() diff --git a/torch-server/resolve_runtime.py b/torch-server/resolve_runtime.py index 85a45cf4c..6282724de 100644 --- a/torch-server/resolve_runtime.py +++ b/torch-server/resolve_runtime.py @@ -252,13 +252,13 @@ def discovery_builds(selected_build: str) -> list[str]: def interpreter_tags(interpreter: str) -> tuple[str, set[str]]: - """Ask an installed interpreter for its own Linux x86_64 wheel tags.""" + """Ask an installed native interpreter for its own binary wheel tags.""" code = ( "import json,platform,sys;" "from pip._vendor.packaging.tags import sys_tags;" "print(json.dumps({'python':f'{sys.version_info.major}.{sys.version_info.minor}'," - "'ok':sys.platform=='linux' and platform.machine()=='x86_64' " - "and sys.implementation.name=='cpython','tags':[str(tag) for tag in sys_tags()]}))" + "'platform':sys.platform,'machine':platform.machine()," + "'implementation':sys.implementation.name,'tags':[str(tag) for tag in sys_tags()]}))" ) completed = subprocess.run( [interpreter, "-I", "-c", code], capture_output=True, text=True, timeout=4, check=False @@ -266,9 +266,31 @@ def interpreter_tags(interpreter: str) -> tuple[str, set[str]]: if completed.returncode: raise ValueError(f"Installed interpreter {interpreter} cannot report wheel tags") details = json.loads(completed.stdout) - if not details["ok"]: - raise ValueError(f"Installed interpreter {interpreter} is not CPython on Linux x86_64") - return details["python"], set(details["tags"]) + target = native_target( + details["platform"], details["machine"], details["python"], details["implementation"] + ) + suffix = { + "linux": re.compile(r"(?:manylinux[^-]*|musllinux[^-]*|linux)_x86_64"), + "windows": re.compile(r"win_amd64"), + "macos": re.compile(r"macosx_\d+_\d+_(?:arm64|universal2)"), + }[target] + tags = {tag for tag in details["tags"] if suffix.fullmatch(tag.rsplit("-", 1)[-1])} + return details["python"], tags + + +def native_target(system: str, machine: str, python: str, implementation: str) -> str: + """Name a supported native target; reject translated or foreign architectures.""" + if implementation != "cpython": + raise ValueError("A native CPython interpreter is required") + if system == "linux" and machine == "x86_64": + return "linux" + if not re.fullmatch(r"3\.(10|11|12|13)", python): + raise ValueError("Windows and macOS require CPython 3.10 through 3.13") + if system == "win32" and machine.lower() in {"amd64", "x86_64"}: + return "windows" + if system == "darwin" and machine == "arm64": + return "macos" + raise ValueError("Unsupported or translated native Torch target") def torch_index_links(build: str) -> list[str]: @@ -299,7 +321,16 @@ def wheel_match(href: str, version: str, build: str, tags: set[str]) -> dict | N return None filename = unquote(parsed.path.rsplit("/", 1)[-1]) match = re.fullmatch(r"torch-([^-]+)-([^-]+)-([^-]+)-([^-]+)\.whl", filename) - if not match or match.group(1) != f"{version}+{build}": + if not match: + return None + distribution_version = match.group(1) + mac_cpu = build == "cpu" and any( + re.fullmatch(r"macosx_\d+_\d+_(?:arm64|universal2)", platform_tag) + for platform_tag in match.group(4).split(".") + ) + if distribution_version != f"{version}+{build}" and not ( + mac_cpu and distribution_version == version + ): return None if not any( f"{python}-{abi}-{system}" in tags @@ -314,7 +345,7 @@ def wheel_match(href: str, version: str, build: str, tags: set[str]) -> dict | N if digest is not None and not re.fullmatch(r"[0-9a-fA-F]{64}", digest): return None clean_url = urlunparse(parsed._replace(fragment="")) - return {"wheelUrl": clean_url, "sha256": digest} + return {"wheelUrl": clean_url, "sha256": digest, "torch": distribution_version} def discover_alternatives( @@ -371,7 +402,7 @@ def discover_alternatives( if len(matches) == 3: break if not installed: - issues.append("No installed CPython interpreter could report Linux x86_64 wheel tags") + issues.append("No installed native CPython interpreter could report compatible wheel tags") network_inconclusive = True issues.append( "Search is bounded to at most five official indexes; dependencies and adapters were not resolved" @@ -432,7 +463,7 @@ def result() -> dict: } if not installed: - issues.append("No installed interpreter could report compatible Linux x86_64 wheel tags") + issues.append("No installed interpreter could report compatible native wheel tags") return result() try: raw_channels = root_loader() @@ -575,9 +606,18 @@ def requirements_from_report( torch_entries = [ item for item in entries if item["metadata"]["name"].lower().replace("_", "-") == "torch" ] - expected = f"{version}+{build}" - if len(torch_entries) != 1 or torch_entries[0]["metadata"]["version"] != expected: + target = native_target( + sys.platform, + platform.machine(), + f"{sys.version_info.major}.{sys.version_info.minor}", + sys.implementation.name, + ) + expected_versions = {f"{version}+{build}"} + if target == "macos" and build == "cpu": + expected_versions.add(version) + if len(torch_entries) != 1 or torch_entries[0]["metadata"]["version"] not in expected_versions: raise ValueError(f"Official {build} artifact for Torch {version} was not resolved") + resolved_torch = torch_entries[0]["metadata"]["version"] lines = [] artifacts = [] seen = set() @@ -593,8 +633,16 @@ def requirements_from_report( raise ValueError(f"No SHA-256 provenance for {name}") if not trusted_wheel_url(url, build, name, adapter): raise ValueError(f"Untrusted or non-binary artifact for {name}: {url}") - if name == "torchvision" and not item["metadata"]["version"].endswith(f"+{build}"): - raise ValueError("Torchvision build differs from the selected Torch build") + if name == "torchvision": + vision_version = item["metadata"]["version"] + local_build = vision_version.endswith(f"+{build}") + plain_mac_cpu = ( + target == "macos" + and build == "cpu" + and re.fullmatch(r"\d+\.\d+\.\d+", vision_version) + ) + if not (local_build or plain_mac_cpu): + raise ValueError("Torchvision build differs from the selected Torch build") if name == "nunchaku" and (adapter != "nunchaku" or digest.lower() != NUNCHAKU_SHA256): raise ValueError("Nunchaku artifact differs from the qualified wheel") lines.append(f"{name} @ {url} --hash=sha256:{digest}") @@ -620,7 +668,8 @@ def requirements_from_report( if missing: raise ValueError(f"Resolution omitted requested packages: {', '.join(sorted(missing))}") return lines, { - "torch": expected, + "release": version, + "torch": resolved_torch, "build": build, "adapter": adapter, "python": f"{sys.version_info.major}.{sys.version_info.minor}", @@ -633,7 +682,11 @@ def requirements_from_report( def resolution_failure( - stderr: str, version: str, build: str, adapter: str = "none" + stderr: str, + version: str, + build: str, + adapter: str = "none", + torch_requirement: str | None = None, ) -> tuple[int, str]: lowered = stderr.lower() if any(marker in lowered for marker in NETWORK_MARKERS): @@ -647,7 +700,7 @@ def resolution_failure( "Package metadata prevented a conclusive resolution. Review pip output or try another Python or build.", ) missing = MISSING_REQUIREMENT.findall(lowered) - if f"torch=={version}+{build}" in missing: + if f"torch=={torch_requirement or f'{version}+{build}'}" in missing: return ( 4, f"No official Torch {version}+{build} wheel matches this interpreter and platform. Choose another build or installed Python.", @@ -725,11 +778,22 @@ def main() -> None: if args.output is None: parser.error("Resolution requires --output") try: + target = native_target( + sys.platform, + platform.machine(), + f"{sys.version_info.major}.{sys.version_info.minor}", + sys.implementation.name, + ) extras = adapter_requirements(args.adapter, args.version, args.build) except ValueError as error: parser.exit(2, f"{error}\n") args.output.mkdir(parents=True, exist_ok=True) report_path = args.output / "pip-resolution.json" + torch_requirement = ( + args.version + if target == "macos" and args.build == "cpu" + else f"{args.version}+{args.build}" + ) command = [ sys.executable, "-I", @@ -746,7 +810,7 @@ def main() -> None: f"https://download.pytorch.org/whl/{args.build}", "--extra-index-url", "https://pypi.org/simple", - f"torch=={args.version}+{args.build}", + f"torch=={torch_requirement}", *CORE, *extras, ] @@ -754,7 +818,9 @@ def main() -> None: print(completed.stdout, end="", flush=True) print(completed.stderr, end="", file=sys.stderr, flush=True) if completed.returncode: - code, message = resolution_failure(completed.stderr, args.version, args.build, args.adapter) + code, message = resolution_failure( + completed.stderr, args.version, args.build, args.adapter, torch_requirement + ) parser.exit(code, f"{message}\n") try: report = json.loads(report_path.read_text()) diff --git a/torch-server/tests/test_probe_runtime.py b/torch-server/tests/test_probe_runtime.py index 20a890b18..1aa9b0af0 100644 --- a/torch-server/tests/test_probe_runtime.py +++ b/torch-server/tests/test_probe_runtime.py @@ -70,6 +70,19 @@ async def health(): self.assertEqual(result["capabilities"]["sidecar_app"]["status"], "passed") self.assertRegex(result["recorded_at"], r"^\d{4}-\d{2}-\d{2}T") self.assertEqual(len(result["context"]["hardware_fingerprint"]), 64) + canonical_hardware = { + "cuda": None, + "hip": None, + "device_count": 0, + "devices": [], + "mps_available": False, + } + self.assertEqual( + result["context"]["hardware_fingerprint"], + hashlib.sha256( + json.dumps(canonical_hardware, sort_keys=True, separators=(",", ":")).encode() + ).hexdigest(), + ) self.assertEqual(len(result["context"]["interpreter_sha256"]), 64) self.assertEqual(len(result["context"]["distributions_sha256"]), 64) self.assertIn("serve.py", result["context"]["runtime_files_sha256"]) @@ -83,7 +96,11 @@ def __matmul__(self, _other): def tolist(self): return [[2.0, 2.0], [2.0, 2.0]] - fake_torch = SimpleNamespace(__version__="2.10.0+cpu", ones=lambda *_: Tensor()) + fake_torch = SimpleNamespace( + __version__="2.10.0+cpu", + ones=lambda *_: Tensor(), + backends=SimpleNamespace(mps=SimpleNamespace(is_available=lambda: True)), + ) async def health(): return {"status": "ok", "protocol": 3} @@ -119,6 +136,17 @@ def import_module(name): self.assertEqual(result["adapter_status"], "unavailable") self.assertEqual(result["status"], "partial") self.assertEqual(result["capabilities"]["flux2_klein"]["status"], "unavailable") + canonical_hardware = { + "cuda": None, + "hip": None, + "device_count": 0, + "devices": [], + "mps_available": True, + } + expected_fingerprint = hashlib.sha256( + json.dumps(canonical_hardware, sort_keys=True, separators=(",", ":")).encode() + ).hexdigest() + self.assertEqual(result["context"]["hardware_fingerprint"], expected_fingerprint) def test_device_failure_is_scoped(self): fake_torch = SimpleNamespace( @@ -147,7 +175,14 @@ def test_runtime_hashes_track_files_and_skip_venv_and_symlinks(self): (root / "venv" / "ignored.py").write_bytes(b"ignored") (root / "venv" / "lib").mkdir() (root / "venv" / "lib" / "package.py").write_bytes(b"package") - (root / "linked.py").symlink_to(root / "serve.py") + try: + (root / "linked.py").symlink_to(root / "serve.py") + except OSError as error: + # Windows hosted runners may lack symlink privilege; keep the + # file-hash assertions useful without weakening link coverage + # on systems that can create the fixture. + if getattr(error, "winerror", None) != 1314: + raise hashes = probe_runtime.runtime_file_hashes(root) self.assertEqual( hashes, diff --git a/torch-server/tests/test_resolve_runtime.py b/torch-server/tests/test_resolve_runtime.py index cc471987b..6163247d8 100644 --- a/torch-server/tests/test_resolve_runtime.py +++ b/torch-server/tests/test_resolve_runtime.py @@ -9,6 +9,7 @@ import unittest from contextlib import redirect_stderr, redirect_stdout from io import StringIO +from types import SimpleNamespace from urllib.request import Request from unittest.mock import patch @@ -60,6 +61,145 @@ def report( class ResolverTests(unittest.TestCase): + def test_native_interpreter_tags_include_windows_and_macos_but_reject_rosetta(self): + cases = ( + ("win32", "AMD64", "cp312-cp312-win_amd64", True), + ("darwin", "arm64", "cp312-cp312-macosx_14_0_arm64", True), + ("darwin", "x86_64", "cp312-cp312-macosx_14_0_x86_64", False), + ("win32", "x86", "cp312-cp312-win32", False), + ("linux", "x86_64", "cp312-cp312-manylinux_2_28_x86_64", True), + ) + for system, machine, wheel_tag, accepted in cases: + with self.subTest(system=system, machine=machine): + completed = type( + "Completed", + (), + { + "returncode": 0, + "stdout": json.dumps( + { + "python": "3.12", + "platform": system, + "machine": machine, + "implementation": "cpython", + "tags": [wheel_tag, "py3-none-any"], + } + ), + }, + )() + with patch.object(resolver.subprocess, "run", return_value=completed): + if accepted: + self.assertEqual(resolver.interpreter_tags("python"), ("3.12", {wheel_tag})) + else: + with self.assertRaises(ValueError): + resolver.interpreter_tags("python") + + def test_native_wheels_match_exact_release_and_architecture(self): + windows = {"cp312-cp312-win_amd64"} + mac = {"cp312-cp312-macosx_14_0_arm64"} + self.assertIsNotNone( + resolver.wheel_match( + "torch-2.14.0%2Bcpu-cp312-cp312-win_amd64.whl", "2.14.0", "cpu", windows + ) + ) + self.assertIsNotNone( + resolver.wheel_match( + "torch-2.14.0%2Bcu130-cp312-cp312-win_amd64.whl", "2.14.0", "cu130", windows + ) + ) + plain = resolver.wheel_match( + "torch-2.14.0-cp312-cp312-macosx_14_0_arm64.whl", "2.14.0", "cpu", mac + ) + self.assertEqual(plain["torch"], "2.14.0") + for href, build, tags in ( + ("torch-2.14.0-cp312-cp312-win_amd64.whl", "cpu", windows), + ("torch-2.14.0-cp312-cp312-macosx_14_0_x86_64.whl", "cpu", mac), + ("torch-2.14.1-cp312-cp312-macosx_14_0_arm64.whl", "cpu", mac), + ("https://evil.test/torch-2.14.0-cp312-cp312-macosx_14_0_arm64.whl", "cpu", mac), + ): + with self.subTest(href=href): + self.assertIsNone(resolver.wheel_match(href, "2.14.0", build, tags)) + + def test_macos_cpu_report_records_plain_torch_distribution_version(self): + fixture = report( + version="2.14.0", + url="https://download.pytorch.org/whl/cpu/torch-2.14.0-cp312-cp312-macosx_14_0_arm64.whl", + ) + with ( + patch.object(resolver.sys, "platform", "darwin"), + patch.object(resolver.platform, "machine", return_value="arm64"), + patch.object(resolver.sys, "version_info", SimpleNamespace(major=3, minor=12)), + ): + _, resolution = resolver.requirements_from_report(fixture, "2.14.0", "cpu") + self.assertEqual(resolution["release"], "2.14.0") + self.assertEqual(resolution["torch"], "2.14.0") + self.assertEqual(resolution["build"], "cpu") + with ( + patch.object(resolver.sys, "platform", "linux"), + patch.object(resolver.platform, "machine", return_value="x86_64"), + ): + with self.assertRaises(ValueError): + resolver.requirements_from_report(fixture, "2.14.0", "cpu") + + def test_macos_cpu_cli_requests_plain_version_and_classifies_missing_wheel(self): + failed = type( + "Completed", + (), + { + "returncode": 1, + "stdout": "", + "stderr": "No matching distribution found for torch==2.14.0", + }, + )() + with tempfile.TemporaryDirectory() as directory: + with ( + patch.object( + resolver.sys, + "argv", + [ + "resolve_runtime.py", + "--version", + "2.14.0", + "--build", + "cpu", + "--output", + directory, + ], + ), + patch.object(resolver.sys, "platform", "darwin"), + patch.object(resolver.platform, "machine", return_value="arm64"), + patch.object(resolver.sys, "version_info", SimpleNamespace(major=3, minor=12)), + patch.object(resolver.subprocess, "run", return_value=failed) as run, + redirect_stderr(StringIO()), + ): + with self.assertRaises(SystemExit) as exit_result: + resolver.main() + self.assertEqual(exit_result.exception.code, 4) + self.assertIn("torch==2.14.0", run.call_args.args[0]) + self.assertNotIn("torch==2.14.0+cpu", run.call_args.args[0]) + + def test_release_options_match_native_windows_and_macos_wheels(self): + fixtures = ( + ("cp312-cp312-win_amd64", "torch-2.14.0%2Bcu130-cp312-cp312-win_amd64.whl", "cu130"), + ( + "cp312-cp312-macosx_14_0_arm64", + "torch-2.14.0-cp312-cp312-macosx_14_0_arm64.whl", + "cpu", + ), + ) + for tag, wheel, build in fixtures: + with self.subTest(tag=tag): + result = resolver.discover_release_options( + "2.14.0", + ["python"], + root_loader=lambda: [build], + index_loader=lambda _: [f"https://download.pytorch.org/whl/{build}/{wheel}"], + tag_loader=lambda _: ("3.12", {tag}), + ) + self.assertEqual(result["status"], "matches") + self.assertEqual(len(result["combinations"]), 1) + self.assertEqual(result["combinations"][0]["build"], build) + def test_release_channels_keep_only_canonical_official_cpu_cuda_and_rocm(self): links = [ "cpu/", From 9585bad481f6558a7fe57436560ef9e6bcfa50a6 Mon Sep 17 00:00:00 2001 From: MrScripty Date: Thu, 24 Sep 2026 22:06:04 -0700 Subject: [PATCH 03/32] chore(release): refresh attribution inventory --- docs/release-attribution/0.7.0/inventory.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release-attribution/0.7.0/inventory.json b/docs/release-attribution/0.7.0/inventory.json index 860253b92..6b1d7c121 100644 --- a/docs/release-attribution/0.7.0/inventory.json +++ b/docs/release-attribution/0.7.0/inventory.json @@ -6069,11 +6069,11 @@ } ], "input_sha256": { - "rust/Cargo.lock": "8a6a244244d3bc5fbc91bfd5425ce24d1d329b356367733a6f402d90c413c11e", - "rust/Cargo.toml": "1f8782df439d528763c3b9780d585d62d8accf0cc2b7b5c3752d5000ed000287", + "rust/Cargo.lock": "e17473bc7f1cb23583e64db07ed6e83f2cf7b6ddf97d1a2f1c397a92a7ac632c", + "rust/Cargo.toml": "c81fc6cbbe7b8a371f7492cb08b8715cf567ba9ec5321f19ffc1a1b8df50fd28", "rust/crates/pumas-core/Cargo.toml": "920dfa71dc7160c14c753b656d16f65ad2b3fcdc7cbe1c56525e195588897022", "rust/crates/pumas-rpc/Cargo.toml": "b51fc102e08bf9ba72cbec544cd8b933171031078b1ba273b289a6f4c5cf981a", - "rust/crates/pumas-app-manager/Cargo.toml": "d5c9c33758a64d5db0d379abba14db300788977e5bae1957d44ff7ea32702c70", + "rust/crates/pumas-app-manager/Cargo.toml": "4bda7dd13149c65161e24eb1f281a19c4c7c50537340d3c1e754cf827a1e621f", "pnpm-lock.yaml": "8ef64f92ef3734992ce825b3253a6edef6586cf87b0a22494096bb12b9c86304", "electron/package.json": "1f2b68e5819cbea84a2d8f25bbdda5d8288b3c1bf90a52162dc414c5938d72d8", "frontend/package.json": "0f3d0d26b1155cdabd7a5b1f93d2b99aff64b70105760284343adeb1afb6ca2e" From 6a726eac9cf3d79c6bf9774c015cccc170183582 Mon Sep 17 00:00:00 2001 From: MrScripty Date: Fri, 25 Sep 2026 10:36:56 -0700 Subject: [PATCH 04/32] fix(torch): register release options RPC method --- electron/src/rpc-method-registry.ts | 1 + electron/tests/ipc-validation.test.mjs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/electron/src/rpc-method-registry.ts b/electron/src/rpc-method-registry.ts index bed94c136..87151f8dd 100644 --- a/electron/src/rpc-method-registry.ts +++ b/electron/src/rpc-method-registry.ts @@ -44,6 +44,7 @@ export const RPC_METHOD_REGISTRY = { 'switch_version', 'install_version', 'get_torch_runtime_options', + 'get_torch_release_options', 'preview_torch_runtime', 'get_torch_preview_report', 'get_torch_runtime_probe', diff --git a/electron/tests/ipc-validation.test.mjs b/electron/tests/ipc-validation.test.mjs index f117090d4..e85490511 100644 --- a/electron/tests/ipc-validation.test.mjs +++ b/electron/tests/ipc-validation.test.mjs @@ -19,6 +19,14 @@ test('RPC method registry has stable unique method names', () => { assert.ok(ALLOWED_RPC_METHODS.includes('torch_configure')); }); +test('Torch release options request reaches the registered RPC method', () => { + assert.ok(ALLOWED_RPC_METHODS.includes('get_torch_release_options')); + assert.deepEqual(validateApiCallPayload('get_torch_release_options', { tag: 'v2.14.0' }), { + method: 'get_torch_release_options', + params: { tag: 'v2.14.0' }, + }); +}); + test('backend setup IPC validates exact backend and retry identity before forwarding', () => { const token = '2e038924-e0e3-4266-95ef-f7a02997b7b6'; for (const backend of ['python_conversion', 'llama_cpp', 'nvfp4', 'sherry']) { From 12e8b2ab41e95b1b24cd1500c85b58c540055d35 Mon Sep 17 00:00:00 2001 From: MrScripty Date: Fri, 25 Sep 2026 12:17:34 -0700 Subject: [PATCH 05/32] feat(torch): provision managed Python for upstream installs --- .../execution-ledger.md | 35 + .../plan.md | 255 ++- .../reports/managed-python-provider.md | 86 + .../upstream-version-manager-progress.md | 115 +- .../torch-upstream-version-management/plan.md | 85 +- electron/src/python-bridge.ts | 10 +- electron/tests/python-bridge.test.mjs | 14 +- .../components/TorchInstallPreview.test.tsx | 88 +- .../src/components/TorchInstallPreview.tsx | 59 +- .../src/version_manager/installer.rs | 2 + .../src/version_manager/installer/torch.rs | 127 +- .../src/version_manager/managed_python.rs | 1411 +++++++++++++++++ .../src/version_manager/mod.rs | 32 +- .../src/version_manager/torch_alternatives.rs | 500 +++--- .../src/version_manager/torch_preview.rs | 324 +++- torch-server/README.md | 25 +- torch-server/resolve_runtime.py | 129 +- torch-server/tests/test_resolve_runtime.py | 239 +++ 18 files changed, 3011 insertions(+), 525 deletions(-) create mode 100644 docs/plans/torch-cross-platform-runtime-management/reports/managed-python-provider.md create mode 100644 rust/crates/pumas-app-manager/src/version_manager/managed_python.rs diff --git a/docs/plans/torch-cross-platform-runtime-management/execution-ledger.md b/docs/plans/torch-cross-platform-runtime-management/execution-ledger.md index 0dcf6cf52..e72c43248 100644 --- a/docs/plans/torch-cross-platform-runtime-management/execution-ledger.md +++ b/docs/plans/torch-cross-platform-runtime-management/execution-ledger.md @@ -80,3 +80,38 @@ pending: the native QA matrix has not run, and exact CPU-wheel install/identity, RPC health/generation-owned stop, and packaged desktop smoke have not been exercised on Windows x64 or macOS arm64. + +## 2026-09-25 — Managed CPython and install-preview repair + +- Implemented the authorized Pumas-managed CPython provider for Linux x86_64, + Windows x86_64 MSVC, and macOS arm64. It bootstraps pinned uv without host + Python, provisions a private stable CPython 3.10+ runtime, and tries the + newest candidates first when an exact official Torch wheel and the complete + selected dependency profile are available. It has no user Python selector or + upper minor allowlist. Clean-host download, license, and native-platform + acceptance remain pending. +- Traced the desktop error `Unknown API method: get_torch_release_options` to + Electron's method allowlist, which rejects the request before Rust dispatch. + Commit `6a726eac` registers the method and adds the payload regression test. + The toolbar-linked package still needs a published update; this work creates + a local candidate only. +- Core Torch (`none`) is now the default on all targets. On Linux, FLUX.2 and + the qualified v2.9.1 bundled recipe require explicit user selection. The + preview explains that Pumas provisions its own Python runtime. +- Linux verification: app-manager tests (178), app-manager Clippy, Electron + tests (12 files), Torch preview tests (14), frontend typecheck/lint, resolver + tests (98), Ruff, Rust formatting, and `git diff --check` pass. The exact + v2.14.0 remote artifact scan/install could not run because artifact hosts are + unreachable in this session; Windows/macOS native acceptance remains open. +- Built local `v0.7.0` release artifacts: + `electron/release/Pumas.Library-0.7.0.AppImage` and + `electron/release/pumas-library-electron_0.7.0_amd64.deb`. The release builder + completed without publishing. The artifact checker verified both; the + headless release smoke and package smoke each reported RPC health passed for + the standalone backend, AppImage, and deb. Inspection confirmed the packaged + Electron bundle contains `get_torch_release_options` and the managed-Python + UI copy. +- Next: use the candidate on a host with PyTorch/uv artifact access to run exact + v2.14.0 preview/install and sidecar lifecycle acceptance. Complete clean-host + provider/download/license and native Windows/macOS package acceptance before + claiming those platforms are supported. diff --git a/docs/plans/torch-cross-platform-runtime-management/plan.md b/docs/plans/torch-cross-platform-runtime-management/plan.md index 4b34db9cf..88e8c640e 100644 --- a/docs/plans/torch-cross-platform-runtime-management/plan.md +++ b/docs/plans/torch-cross-platform-runtime-management/plan.md @@ -5,40 +5,49 @@ **Owner:** Pumas runtime integration **Authorization:** The repository owner authorized Windows and macOS Torch -runtime management on 2026-09-24, retaining the existing Linux support. - -**Current acceptance:** `pending` — the Windows and macOS implementation is not -yet accepted. Native install, RPC lifecycle, and packaged desktop evidence is -still required. - -**Current phase:** M2 native acceptance. M1 implementation and repair are -complete in the candidate branch. Linux regressions and Windows GNU test-target -compilation pass; macOS test-target compilation and Windows/macOS native test -execution have not run in this Linux session. - -**Blockers:** No known code blocker remains from the architecture or independent -reviews. Native Windows and macOS acceptance evidence is unavailable here: -real wheel preview/install/identity, RPC health and generation-owned stop, and -packaged desktop smoke must run on their target systems. The new native QA -matrix is committed as a gate but has not run for this candidate. - -**Next slice:** On native Windows x64 and macOS arm64 runners, run the candidate's -Torch QA matrix, then exercise exact official CPU-wheel preview/install/identity, -RPC start/health/owned-stop, and packaged desktop smoke. Record each runner, -environment, result, and artifact before changing M2 acceptance. +runtime management on 2026-09-24, retaining the existing Linux support. On +2026-09-25, the owner authorized Pumas-managed CPython provisioning and removed +the requirement for a host-installed Python interpreter or a user Python choice. + +**Current acceptance:** `pending` — a local Linux 0.7.0 AppImage and deb were +built from the candidate source. Both packages pass artifact validation and +their bundled RPC health smoke. The packed Electron bundle contains the +`get_torch_release_options` registration. A live Torch 2.14.0 resolution and +managed-CPython install still cannot run in this session because artifact hosts +are unreachable. Windows and macOS still need native install, RPC lifecycle, and +packaged desktop evidence. + +**Current phase:** M1b managed interpreter provisioning and Linux packaging are +implemented and reviewed. Commit `6a726eac` fixes the missing Electron allowlist +entry for `get_torch_release_options`; the local candidate contains it. The +desktop package linked from the toolbar has not been replaced or published. +Clean-host Python provisioning and native Windows/macOS packaging remain +outstanding. + +**Blockers:** No design blocker remains. This Linux session cannot resolve the +uv, Python, or PyTorch artifact hosts, so provider-byte verification and a live +no-host-Python install cannot run. Native Windows and macOS execution is not +available here and remains a separate acceptance gate. + +**Next slice:** On a host with artifact access, install Torch v2.14.0 Core +runtime from the local candidate and record the selected managed CPython, +resolved wheel set, installed identity, and sidecar lifecycle. Run provider +download/integrity/license and exact-wheel install acceptance on native Linux, +Windows x64, and macOS arm64 runners before changing platform acceptance. ## Objective and scope Extend the existing Torch release manager so users can discover a stable -upstream Torch release, let Pumas select an exact official wheel matching the -host and an installed supported Python, preview the complete hash-locked -dependencies, install and inspect it, explicitly select it, and start/stop its -managed sidecar through the existing RPC and desktop flows. +upstream Torch release, let Pumas provision the newest stable CPython that +matches an exact official wheel and resolves the complete selected dependency +profile, install and inspect it, explicitly select it, and start/stop its +managed sidecar through the existing RPC and desktop flows. The host does not +need Python on `PATH`, and the desktop does not require a Python selection. -“Any release” continues to mean an upstream stable tag for which the exact host, -Python, official wheel, and complete dependency resolution exist. It does not -promise a wheel for every release or every supported host. Python provisioning, -source builds, and unqualified adapters are outside this objective. +“Any release” continues to mean an upstream stable tag for which an exact host, +stable standard-CPython wheel, and complete dependency resolution exist. It does +not promise a wheel for every release or every supported host. Source builds and +unqualified adapters remain outside this objective. The current accepted Linux contract remains the regression baseline in the [upstream version-management plan](../torch-upstream-version-management/plan.md) @@ -57,15 +66,42 @@ recorded exact tuples. | macOS arm64 | `aarch64-apple-darwin`; arm64 DMG | Discover exact official native CPU wheels; inspect MPS after installation as an optional runtime capability, not a separate Torch build channel | The interpreter and wheel must be native arm64. Intel Mac and Rosetta/x86_64 Torch runtimes are unsupported. No CUDA/ROCm claim | The target triples and artifacts match the current release contract in -[`artifact-plan.json`](../../../scripts/release/artifact-plan.json#L58). Use an -already installed CPython 3.10–3.13 interpreter whose own supported wheel tags -match the Pumas process architecture, selected official Torch wheel, and all -resolved dependencies. Discover and rank eligible interpreters automatically; -keep interpreter/build overrides advanced and constrained to exact matches. -Do not provision Python or infer compatibility from a display name. +[`artifact-plan.json`](../../../scripts/release/artifact-plan.json#L58). Pumas +owns a private CPython installation depot. Candidate stable CPython minor +versions come from the exact official Torch wheel tags and the pinned Python +distribution catalog in the pinned uv release; do not maintain a Pumas upper +version allowlist or treat prerelease/free-threaded tags as standard CPython. +CPython 3.10 is Pumas' runtime minimum; there is no configured upper minor cap. +Rank candidates numerically from newest to oldest, validate each installed +interpreter's observed target and `sys_tags()`, and retain the first complete +dependency resolution. A proven missing wheel or dependency incompatibility can +advance to the next candidate. Network failure, timeout, provisioning failure, +integrity failure, or incomplete scans are inconclusive and cannot justify a +lower Python version. The user never has to install or choose Python. + +The app-manager downloads and bootstraps uv 0.12.19 for the shipped Linux x86_64 GNU, Windows +x86_64 MSVC, and macOS arm64 targets from versioned official archives. It +verifies each archive against the exact SHA-256 in the provider report before +extracting or executing it. uv's Python distribution catalog is embedded and +frozen per uv release; its exact binary pin therefore pins that catalog too. +The manager enumerates that catalog on the native host, filters to stable +standard CPython candidates, and sorts them newest-first. One managed +interpreter acts as a bootstrap for native wheel discovery; the resolver +synthesizes standard CPython tags for the catalog candidates, so discovery does +not install every Python minor. Preview provisions real candidates in bounded +newest-first order and runs complete dependency resolution under each candidate. +An exact absent wheel or definite dependency incompatibility permits trying the +next candidate. Network, provider, timeout, integrity, or incomplete-scan +outcomes stop as inconclusive. uv +provisions Python Build Standalone CPython releases; Pumas must retain the +selected distribution URL, version, target/build, pinned uv/catalog identity, +and a fingerprint of the installed interpreter. uv verifies the CPython archive +using the checksum in its pinned embedded catalog. The CPython build source must +be described accurately and not as a Python.org installer. The release manager retains the exact upstream wheel URL, hash, distribution -version, build identity, interpreter fingerprint, and dependency lock. Validate +version, build identity, managed interpreter distribution identity and +fingerprint, and dependency lock. Validate the observed installed Torch identity against that retained resolution. The Torch distribution version is not always `release+build`: for example, the official Torch 2.14.0 CPU index publishes Windows wheels named @@ -75,7 +111,7 @@ upstream distribution version. Do not manufacture a `+cpu` local version for the macOS artifact. Exact package availability is determined from official indexes and the -installed interpreter's `packaging.tags.sys_tags()`, not a Pumas release +provisioned interpreter's `packaging.tags.sys_tags()`, not a Pumas Torch release allowlist or a guessed OS floor. Full preview remains required before install; an exact wheel match alone never claims dependency compatibility. On macOS, MPS availability is probed from the installed Torch runtime and host. MPS does @@ -87,22 +123,38 @@ Stable upstream tag discovery remains global and independent of this support matrix; install choices are limited to exact compatible host tuples. The bundled v2.9.1 Linux preset is Linux-only. The manager will expose an explicit `bundledPresetAvailable` host capability, true only for Linux x86_64 when its -required CPython 3.12 interpreter is present; the desktop must use that result -instead of the release tag alone. Windows and macOS use the retained dynamic -preview and wheel lock. - -On Windows and macOS, Core Torch runtime (`none`) is the default dependency -profile and the only offered adapter until a platform-specific image adapter -has its own accepted plan. Linux retains its current default/profile behavior. -The desktop displays this manager-provided default rather than guessing from -the OS. This enables portable Torch install/select/start without suggesting -FLUX.2 or Nunchaku inference has been qualified there. +managed CPython 3.12 provider artifact is available; the desktop must use that +result instead of the release tag alone. Windows and macOS use the retained +dynamic preview and wheel lock. + +On every supported target, Core Torch runtime (`none`) is the default dependency +profile. Linux may offer Pumas' FLUX.2 profile as an explicit advanced choice; +Windows and macOS offer only core Torch until a platform-specific image adapter +has its own accepted plan. The desktop displays the manager-provided default +rather than guessing from the OS. This enables portable Torch install/select/ +start without suggesting FLUX.2 or Nunchaku inference has been qualified on +every release or platform. ## Ownership and design -- `pumas-app-manager` continues to own release choices, automatic interpreter - selection, retained previews, staged installation, identity, selection, and - cleanup admission. +- `pumas-app-manager` owns release choices, CPython candidate ranking and + provisioning, retained interpreter leases/previews, staged Torch installation, + identity, selection, and cleanup admission. Python artifacts live in an + immutable Pumas-managed depot; uv's disposable download cache is separate. +- The app-manager invokes only the exact-hash-pinned uv binary that it has + downloaded into its private bootstrap directory and verified. It clears + ambient uv configuration, forces manager-owned Python, uses private + install/cache directories, disables PATH shims and (on Windows) registry + writes, and never runs a shell installer or administrator operation. +- A selected Python distribution's source catalog identity, exact CPython + version, native target/build, download URL, uv/catalog identity, executable + fingerprint, and observed `sys_tags()` remain attached to its retained + preview and installed runtime record. uv verifies the Python archive with the + digest from its embedded catalog. + Existing installations keep their original base interpreter; managed-runtime + cleanup may not delete an interpreter referenced by a preview, install, or + installed runtime. Automatic pruning is out of scope until reference-safe + removal is implemented. - `torch-server/resolve_runtime.py` continues to own pip dry-run resolution, wheel-tag matching, official wheel provenance, and hash reporting. Its protocol must carry the upstream distribution version and exact build as @@ -113,45 +165,51 @@ FLUX.2 or Nunchaku inference has been qualified there. belong in the existing platform process API and, if the lifecycle interface needs its own depth, one `pumas-core/src/platform/managed_child.rs` module; installer and resolver operations remain their manager owners. -- RPC and generated contracts remain unchanged unless a required capability - fact cannot be expressed by existing release-options, preview, install, - selection, and startup results. Any contract change is serial and owns its - generated artifacts. +- RPC and generated contracts remain unchanged unless the request must allow an + automatic Python choice or the response must report provisioning progress and + selected interpreter identity. Any contract change is serial and updates its + generator, Rust source, generated artifacts, preload, and consumers together. - The desktop remains a presentation of manager-returned exact choices. It must not infer OS support, invent builds, select a mismatched interpreter, or - interpret MPS as an image adapter. It uses manager-provided + interpret MPS as an image adapter. It never requires a Python selector and + reports the manager-selected interpreter after resolution. It uses manager-provided `bundledPresetAvailable` and `defaultAdapter` capabilities rather than enabling the Linux bundled preset merely because the selected tag is v2.9.1. ### Composed-design review -**Applicability:** `applicable` — the work changes target selection and -process-lifecycle seams across resolver, manager, and platform runtime owners. +**Applicability:** `applicable` — the work changes target selection, dependency +provisioning, Python runtime persistence, and process-lifecycle seams across the +resolver, manager, desktop, and platform runtime owners. 1. **Independent concerns:** upstream wheel identity and interpreter tags - (Python resolver); host hardware facts and recommendations (manager); staged - version identity/publication (installer); native process supervision - (platform lifecycle); choice presentation (existing RPC/desktop). + (Python resolver); managed CPython artifacts and leases (interpreter owner); + host hardware facts and recommendations (manager); staged version + identity/publication (installer); native process supervision (platform + lifecycle); choice presentation (RPC/desktop). 2. **Interleavings:** release/build/distribution version; target/architecture/ - Python ABI and wheel tags; hardware/driver evidence; wheel URLs and hashes; + Python ABI and wheel tags; Python provider version/catalog/artifact and + retained interpreter; hardware/driver evidence; wheel URLs and hashes; staged directory and cleanup; process generation and cancellation. These remain explicit fields/outcomes at the manager boundaries. 3. **Caller knowledge:** RPC/desktop clients consume manager choices, retained - preview identifiers, and lifecycle status. They do not parse platform paths, - wheel filenames, drivers, or process IDs to decide support. + preview identifiers, interpreter identity, and lifecycle status. They do not + parse platform paths, wheel filenames, drivers, or process IDs to decide + support. 4. **Representative changes:** a new supported host must update target facts, exact wheel fixtures/resolution, manager host/interpreter detection, native - lifecycle evidence, and its native CI acceptance. A new upstream wheel tag - remains local to official-tag resolution and its tests. A new image adapter - does not change this runtime-management contract. + lifecycle evidence, provisioned Python target, and its native CI acceptance. + A new upstream wheel tag remains local to official-tag resolution and its + tests. A new image adapter does not change this runtime-management contract. 5. **Stable interfaces:** exact artifact and host facts cross the resolver to manager; the retained resolution crosses preview to installer; process owners expose generation-scoped lifecycle results. OS path strings, display text, or ambient command lookup do not act as artifact identity. 6. **Independent verification:** resolver tags can be fixture-tested; host - detection and venv paths are tested natively; process-group/job ownership, - cleanup, and sidecar lifecycle require native target execution. Integration - remains in the manager/API and desktop composition. + detection, Python provisioning, and venv paths require clean-host native + execution; process-group/job ownership, cleanup, and sidecar lifecycle also + require native target execution. Integration remains in the manager/API and + desktop composition. 7. **Independent evolution and deletion test:** Linux/macOS process-group mechanics and Windows Job ownership may fail and evolve independently behind one generation-lifecycle contract. The Windows child must be created @@ -162,21 +220,26 @@ process-lifecycle seams across resolver, manager, and platform runtime owners. Strategy/Factory or one-file-per-platform mirror. Any new process primitive must remove duplicate Linux-only process-tree logic from its callers and enforce generation ownership; otherwise delete it. -8. **Necessary complexity:** exact wheel-tag compatibility and native - process-tree ownership are required complexity, contained in resolver and - process-lifecycle owners. No new public API or package dependency is admitted - unless implementation evidence shows the current owners cannot express the - contract. +8. **Necessary complexity:** exact wheel-tag compatibility, private interpreter + provisioning, and native process-tree ownership are required complexity, + contained in the resolver, manager-owned interpreter lifecycle, and process + lifecycle. uv is selected over local archive installation because it owns + Python distribution selection, integrity checks, and platform installation; + Pumas retains policy, artifact identity, private paths, leases, and cleanup. + The target uv binary and its embedded interpreter catalog are pinned + together. Do not use ambient uv or runtime `latest` URLs. ## Acceptance claims | ID | Observable criterion | Evidence required | Status | | --- | --- | --- | --- | | X1 | Linux behavior and existing accepted v2.9.0 install/v2.10 Tuldok evidence remain accurately scoped and pass relevant regression gates | Existing Linux manager/resolver/RPC/frontend suites; retained historical evidence references | pending | -| X2 | Windows x64 and macOS arm64 discovery returns only exact official wheels whose tags match an installed supported native CPython; CPU/MPS/CUDA choices follow this contract | Native wheel fixtures, wrong-OS/architecture/interpreter rejection, live official-index scan on both native runners | pending | -| X3 | Preview retains the exact distribution version, release/build, official wheel URL/hash, interpreter identity, and complete dependencies; install stages and verifies that exact identity before publication | Resolver/Rust contract fixtures plus real official CPU wheel preview, install, identity and cleanup on Windows/macOS | pending | -| X4 | Installed versions can be inspected, explicitly selected, started with health/protocol checks, and stopped by their owned generation; cancellation, timeout, RPC shutdown, and failed cleanup do not leak a resolver, installer, sidecar, or unregistered runtime | Native lifecycle tests and real RPC start/health/owned-stop integration on Windows/macOS; Linux regression | pending | -| X5 | The same manager choices and lifecycle are reachable through packaged desktop controls and the existing RPC API; the desktop consumes manager-provided preset/default-adapter capability and offers no unsupported adapter | Preload boundary validation, composed desktop tests, native RPC integration, packaged desktop smoke on all declared targets | pending | +| X2 | Each shipped target provisions a private stable standard CPython 3.10 or newer without a host Python dependency or global PATH/registry changes; artifact integrity, target identity, cache separation, cancellation, and retention are verified | Clean-host native provisioning and tamper/cancel/retry tests on Linux, Windows, and macOS; downloaded uv digest and license checks | pending | +| X3 | Candidate versions come from exact official standard-CPython wheel tags and the pinned stable provider catalog; the manager tries highest to lowest, selects the newest complete compatible resolution, and never treats network/provisioning failure as a reason to downgrade | CPython 3.14 preference when fully resolved; definite 3.14 dependency incompatibility falls to 3.13; prerelease/free-threaded/wrong-target and pre-3.10 artifact rejection; incomplete scan/provider failures remain typed inconclusive | pending | +| X4 | Windows x64 and macOS arm64 discovery returns only exact official wheels whose tags match the provisioned native CPython; CPU/MPS/CUDA choices follow this contract | Native wheel fixtures, wrong-OS/architecture/interpreter rejection, live official-index scan on both native runners | pending | +| X5 | Preview retains the exact distribution version, release/build, official wheel URL/hash, Python provider artifact identity, interpreter fingerprint, and complete dependencies; install stages and verifies that exact identity before publication | Resolver/Rust contract fixtures plus real official CPU-wheel preview, install, identity, and cleanup on every declared target | pending | +| X6 | Installed versions can be inspected, explicitly selected, started with health/protocol checks, and stopped by their owned generation; cancellation, timeout, RPC shutdown, and failed cleanup do not leak a resolver, installer, sidecar, interpreter provisioner, or unregistered runtime | Native lifecycle tests and real RPC health/generation-owned stop on Windows/macOS; Linux regression | pending | +| X7 | The same manager choices and lifecycle are reachable through packaged desktop controls and the existing RPC API; the user never has to choose Python, and the desktop offers no unsupported adapter | Preload boundary validation, composed desktop tests, native RPC integration, packaged desktop smoke with no Python on PATH on all declared targets | pending | Plan acceptance is `pending` until every required row is satisfied on its declared native environment. Cross-compilation, Linux simulation, packaging, @@ -233,19 +296,40 @@ and unit fixtures do not substitute for the Windows/macOS runtime claims. - **Verification:** Focused Python and Rust suites; Rust format/Clippy; native Windows/macOS cargo and Python QA; exact release preview and lifecycle smoke. - **Re-plan trigger:** A native target requires a contract/schema change, - process primitive outside the existing ownership boundary, Python version - expansion, or a non-CPU runtime claim unsupported by official evidence. + process primitive outside the existing ownership boundary, or a non-CPU + runtime claim unsupported by official evidence. - **State:** Implemented; target-native acceptance pending. +### M1b — Managed stable CPython + +- **Goal:** Bootstrap a pinned uv helper without host Python, enumerate its + target-native catalog, use it to discover native candidate wheel tags without + provisioning every Python minor, then provision newest-first Python + candidates in Pumas-owned storage, retain exact provider/interpreter identity, + resolve dependencies against the + observed wheel tags, and keep the user-facing flow automatic. +- **Write sets:** App-manager Python provider/lease and Torch manager/resolver + files; Torch install UI and focused tests; Electron bridge timeout policy and + tests; uv pin and attribution files; Linux/Windows/macOS native provisioning + QA. The existing RPC string request already accepts `python: "auto"`, so this + change does not require generated contract or preload changes. Root owns + plan/inventory, native workflow composition, final review, and commit. +- **Verification:** Pinned uv artifact digest/version checks; stable catalog + selection; managed Python 3.14 with full resolution; definite-incompatibility + fallback to 3.13; inconclusive failures do not downgrade; clean host PATH, + concurrency/cancellation, immutable retention, tamper, and safe cleanup tests. +- **State:** Manager/provider/resolver/UI implementation is present; target + native acceptance and clean-host provisioning are pending. + ### M2 — Native acceptance and inventory - **Goal:** Collect target-native resolver/install/lifecycle/packaged desktop - evidence, update the existing Torch inventory, and report any model-support - limit without broadening Tuldok claims. + evidence for managed Python and update the existing Torch inventory without + broadening Tuldok claims. - **Write set:** New plan evidence/report; relevant sections of `docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md`; execution ledger. Existing historical model reports remain unchanged. -- **Verification:** Every X1–X5 claim has an evidence environment, execution +- **Verification:** Every X1–X7 claim has an evidence environment, execution mode, result, and link. Reuse read-only review for any lifecycle/security repair. - **State:** Active; native acceptance pending. @@ -291,9 +375,10 @@ and unit fixtures do not substitute for the Windows/macOS runtime claims. - [Execution ledger](execution-ledger.md) - [Issues and dispositions](issues.md) - [Official target and wheel research](reports/target-wheel-research.md) +- [Managed Python provider admission](reports/managed-python-provider.md) - Coding Standards MCP policies applied: `topic.cross-platform`, `profile.language.rust.cross-platform`, `topic.architecture`, `profile.language.rust.security`, `topic.security.filesystem-containment`, `topic.contracts`, `topic.dependencies`, `workflow.planning`, and - `workflow.verification.platforms` from snapshot - `snapshot:v1:305ccfba-eed5-46b7-bfa2-02868fe804b3`. + `topic.licensing`, `workflow.verification.platforms` from snapshot + `snapshot:v1:588a50a0-e97e-444d-9c6b-a383baa4dfcf`. diff --git a/docs/plans/torch-cross-platform-runtime-management/reports/managed-python-provider.md b/docs/plans/torch-cross-platform-runtime-management/reports/managed-python-provider.md new file mode 100644 index 000000000..a172a8d68 --- /dev/null +++ b/docs/plans/torch-cross-platform-runtime-management/reports/managed-python-provider.md @@ -0,0 +1,86 @@ +# Managed Python Provider Admission + +**Status:** Candidate pin recorded; target-native download, byte verification, +package execution, clean-host provisioning, and license-material acceptance are +still pending. + +**Decision:** Use the app-manager's exact-hash-pinned uv release to provision +stable, standard CPython distributions from uv's embedded Python Build Standalone +catalog. Pumas requires CPython 3.10 or newer for the sidecar and has no upper +minor-version cap. Do not use a host Python, ambient `uv`, runtime `latest` URL, +shell installer, system package manager, PATH shim, or Windows registry +registration. + +## uv target artifacts + +The official uv `0.12.19` release is immutable, published 2026-09-24, and points +to commit `bea138450f0e620a4ce5765b0e38cff7b9f0799f`. The three shipped target +archives and published SHA-256 checksums are: + +| Pumas target | Official asset URL | Published SHA-256 | +| --- | --- | --- | +| Linux x86_64 GNU | [`uv-x86_64-unknown-linux-gnu.tar.gz`](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-x86_64-unknown-linux-gnu.tar.gz) | `23bf5552d220e0842b65c862097b2ebaeba0064b74eda5e565e77fd25969d8c8` | +| Windows x86_64 MSVC | [`uv-x86_64-pc-windows-msvc.zip`](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-x86_64-pc-windows-msvc.zip) | `6dbb02d79e419522f1c500f0adb1cddcff0cda7d59b0d66ea7f5e3b4a1b2f5f0` | +| macOS arm64 | [`uv-aarch64-apple-darwin.tar.gz`](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-aarch64-apple-darwin.tar.gz) | `a9a8df1eedeb192f2e47e40e2faabfb387db4b850209118786d42f89dde3e0ba` | + +The digest values come from the release's official `.sha256` assets for [Linux](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-x86_64-unknown-linux-gnu.tar.gz.sha256), [Windows](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-x86_64-pc-windows-msvc.zip.sha256), and [macOS](https://releases.astral.sh/github/uv/releases/download/0.12.19/uv-aarch64-apple-darwin.tar.gz.sha256). The upstream [release page](https://github.com/astral-sh/uv/releases/tag/0.12.19) states that the release is immutable and publishes GitHub artifact attestations. These are published checksum values; Pumas has not yet downloaded the archives in this environment, so local verification of downloaded bytes and executable fingerprints remains an acceptance gate. + +The app-manager must download the exact target URL into a private temporary file, +enforce a finite size/time budget, verify SHA-256 before extraction, extract only +the expected executable, and atomically publish it under the private managed +Python root. A failed download, digest, or extraction leaves no executable +eligible for invocation. This is runtime bootstrapping from versioned assets, +not a package-time bundled binary. + +## Interpreter catalog and provenance + +uv documents that available Python versions are frozen for each uv release and +that its managed CPython distributions come from +[python-build-standalone](https://docs.astral.sh/uv/concepts/python-versions/). +The exact uv binary pin therefore pins the catalog implementation and data +compiled into that release; Pumas must not fetch catalog metadata from `main` or +maintain a second Python-version allowlist. + +At runtime, the manager asks the pinned uv binary to enumerate downloadable +interpreters for the current native target, validates the returned structured +records, filters to stable standard CPython 3.10+, and sorts by numeric version +descending. It selects the newest stable version within each minor and installs +that exact version; it does not keep an upper minor allowlist. For each selected +interpreter the retained runtime record stores the full version, catalog key, +source URL, target triple, pinned uv version and archive SHA-256, canonical +executable path, and executable fingerprint. uv performs the Python archive +integrity check against the checksum carried by its embedded metadata; provider +output alone is not treated as proof of the installed interpreter's identity. +The manager re-probes the installed interpreter's implementation, version, +native architecture, and wheel tags before use. + +The public Python catalog page identifies the current tier-one standard CPython +range through 3.14 and states that 3.15 is pre-release support. Candidate +stability is decided from structured version metadata, not from a hard-coded +upper limit or an assumption based on a Torch wheel filename. A stable Python +minor absent from the pinned catalog remains unavailable until a reviewed uv +provider update adds its artifact and native evidence. + +The provider implementation runs `uv python list --managed-python +--only-downloads --show-urls --output-format json`. A Linux local-uv control +returned structured URL-bearing records, and provider fixtures cover native +filtering, stable-version ordering, the 3.10 floor, exact-version install +selection, and malformed catalogs. Those checks do not substitute for executing +the pinned uv 0.12.19 asset on a clean host. + +## Licensing and remaining admission gates + +The uv release uses the [MIT/Apache-2.0 license policy](https://docs.astral.sh/uv/reference/policies/license/). +The Python Build Standalone documentation says distribution archives include +license texts and that individual bundled dependencies carry their own terms +([distribution runtime and licensing](https://github.com/astral-sh/python-build-standalone/blob/main/docs/running.rst)). +Before release acceptance, retain uv notices and the applicable license files +for the actual selected CPython archives in Pumas' attribution inventory. Do not +infer the complete archive license set from the repository's top-level license. + +Admission remains pending until all target artifacts are downloaded and checked +on native Linux x86_64, Windows x86_64 MSVC, and macOS arm64 hosts; clean-host +provisioning succeeds without Python on `PATH`; stable catalog filtering excludes +pre-releases, debug, free-threaded, and foreign-target artifacts; timeout, +cancellation, tamper, retry, and durable retention behavior pass; and license +notices are recorded. diff --git a/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md b/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md index c115864f5..937bf7578 100644 --- a/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md +++ b/docs/plans/torch-diffusion-serving/reports/upstream-version-manager-progress.md @@ -1,6 +1,6 @@ # Upstream Torch version manager progress -Date: 2026-09-24 (America/Vancouver). This is an implementation and evidence +Date: 2026-09-25 (America/Vancouver). This is an implementation and evidence inventory, not a completed Pumas desktop acceptance. The prior [A1 packaged acceptance](a1-packaged-acceptance.md) applies only to its original `v2.9.1` / CPython 3.12 / CUDA 13.0 / Linux x86_64 combination. The Tuldok image @@ -16,14 +16,18 @@ independently of Pumas qualification when GitHub pagination completes within the an error without a partial list presented as complete. Installation is offered for an exact official CPU, CUDA, or ROCm binary wheel plus fully resolved dependencies on Linux x86_64 with an already installed CPython 3.10–3.13. This records the -original Linux-only scope; the later Windows/macOS authorization and its exact -candidate contract are below. Pumas does not provision Python or compile Torch -from source; XPU remains outside this provider contract. A release with no -compatible wheel for a supported interpreter is discoverable but correctly -rejected for that tuple. +original Linux-only scope and is superseded for the in-progress install flow by +the 2026-09-25 managed-CPython authorization below. The current implementation +provisions stable native CPython 3.10+ from a pinned provider and chooses the +newest candidate whose exact official wheel and complete dependencies resolve. +Clean-host and native provisioning acceptance remain pending. Pumas does not +compile Torch from source; XPU remains outside this provider contract. A release +with no compatible wheel is discoverable but correctly rejected for that tuple. The fixed qualified `v2.9.1` / CUDA 13.0 / Python 3.12 bundled preset remains a -separate recipe. Other exact combinations, including alternate `v2.9.1` +separate recipe. The current manager path provisions its Python 3.12 base +interpreter from the managed depot; the previous host-interpreter condition is +historical. Other exact combinations, including alternate `v2.9.1` combinations, use dynamic retained previews and do not inherit the preset's qualification. The serialized follow-up and acceptance contract live in the [Torch upstream version management plan](../../torch-upstream-version-management/plan.md); @@ -57,6 +61,44 @@ No new FLUX.2, Nunchaku, or Tuldok image-generation support is implied. The current image result stays limited to its recorded Linux tuple until a separate target/model acceptance proves otherwise. +## 2026-09-25 — Managed Python and desktop discovery repair + +The install flow no longer requires host-installed Python or a Python choice. +PyTorch wheels do not declare one Python version for an entire release: each +official wheel declares its compatible CPython ABI and platform tags. Pumas +uses its pinned provider catalog to provision stable native CPython candidates +from newest to oldest, then retains the first interpreter whose exact selected +Torch wheel and complete dependency profile resolve. CPython 3.10 is the +minimum; no upper minor cap is configured. A newer candidate is skipped only +after a definite wheel or dependency incompatibility. Provider, network, and +incomplete-scan failures remain inconclusive. See the +[managed Python provider report](../../torch-cross-platform-runtime-management/reports/managed-python-provider.md). + +The manager now defaults every target to Core Torch (`none`). Linux exposes +Pumas' FLUX.2 dependencies only as an explicit advanced profile, and the +qualified v2.9.1 bundled recipe is also opt-in. Windows and macOS expose Core +only until a platform-specific adapter is accepted. UI copy states that Pumas +installs a private Python runtime and does not require Python on the host. + +The reported `Unknown API method: get_torch_release_options` is emitted by the +Electron `api:call` allowlist before a request reaches Rust. The registration +fix is in commit `6a726eac`, with a regression test covering allowlist admission +and payload validation. The local Linux 0.7.0 candidate contains the registration +in its Electron bundle. Its AppImage and deb pass the artifact checker and +bundled RPC health smoke. A package built before that commit still shows the +reported error; the toolbar-linked package has not been replaced or published +by this local build. + +Current host verification: `pumas-app-manager` passed 178 tests and Clippy; +`pumas-rpc` passed 161 unit tests, 13 integration tests, and 2 intent tests; +Electron passed 12 test files including the RPC registration regression; the +Torch preview passed 14 tests with frontend typecheck/lint; the Torch resolver +passed 98 tests with Ruff. Release build, headless startup smoke, artifact +validation, and AppImage/deb bundled RPC health smoke passed. The exact live +v2.14.0 artifact preview and clean managed-Python install remain unverified +because this environment cannot reach the artifact hosts. Native Windows/macOS +runtime and packaged acceptance also remain pending. + ## Current branch behavior - Stable upstream `vMAJOR.MINOR.PATCH` releases are discovered independently of @@ -72,15 +114,18 @@ target/model acceptance proves otherwise. gates are specified in the linked cross-platform plan. Installed local releases remain visible when upstream discovery fails. - The qualified 2.9.1 recipe remains a fixed CUDA 13.0 / CPython 3.12 bundled - preset. Other stable releases can use an installed CPython 3.10–3.13 - interpreter and an official CPU/CUDA/ROCm wheel index. Pumas does not provision - interpreters. One installed build/interpreter/adapter combination is supported - per upstream tag; the UI must disclose the installed combination before a - different combination is attempted. + preset. The updated manager provisions its CPython 3.12 base from the pinned + provider catalog. Other stable releases try stable native CPython candidates + from newest to oldest (3.10 minimum, no upper minor cap) against exact official + CPU/CUDA/ROCm wheels and complete dependencies. The UI has no Python selector + and shows the Pumas-provisioned interpreter after preview. Provider/network failures remain + inconclusive and cannot trigger a downgrade. Clean-host provisioning is not + accepted yet. One installed build/interpreter/adapter combination is supported + per upstream tag; a different combination requires a fresh preview. - On the accepted Linux x86_64 target, dynamic install choices come from a bounded scan of the official PyTorch wheel directory and exact per-channel indexes for the selected release and - installed CPython tags. A release/build/Python pair is offered only when that + managed CPython candidate tags. A release/build/Python pair is offered only when that exact Torch wheel exists for the target host and interpreter tags. Partial scans remain inconclusive; they do not turn an unobserved wheel into a confirmed absence. The desktop uses @@ -96,12 +141,13 @@ target/model acceptance proves otherwise. An incomplete scan recommends an exact CPU wheel provisionally when present. - The desktop asks the manager for available choices and a retained preview before installation. Dynamic previews show every resolved wheel URL, version, - and SHA-256. The default `flux2` selection means Pumas' FLUX.2 image dependency - profile; it is not an upstream Torch component. Core runtime only is available - as an explicit advanced profile. Neither profile implies qualification for - image generation on every release. - The manager retains the exact pip resolution and a fingerprint of the chosen - interpreter; installation consumes that retained lock without resolving again. + and SHA-256. Core runtime (`none`) is the default dependency profile. Linux + may offer Pumas' FLUX.2 image dependencies as an explicit advanced profile; + they are not an upstream Torch component. Neither profile implies + qualification for image generation on every release. + The manager retains the exact pip resolution, Python distribution source and + version, uv pin, target, and interpreter fingerprint; installation consumes + that retained identity without resolving again. Previews expire after 30 minutes and the in-memory store retains at most 32; expired reports are discarded when read. Resolver results use a typed outcome across the RPC and desktop boundary: @@ -110,8 +156,8 @@ target/model acceptance proves otherwise. is validated across the generated contract. The install request contract accepts both `app_id`/`appId` and optional `preview_id`/`previewId` forms. After a definite unsupported preview, the desktop can search a bounded set of - official Torch indexes for - wheel matches for installed interpreters. + official Torch indexes for wheel matches across the pinned managed CPython + catalog. These discovery results are independent of host-installed Python. These leads are explicitly incomplete: dependencies and adapters are unchecked, and choosing one starts a fresh exact preview. Nunchaku's known wheel is confined to the fixed 2.9.1 preset. @@ -162,12 +208,14 @@ communicate with the NVIDIA driver, so its automatic choice is CPU where an exac CPU wheel exists. This is not a claim about the driver's availability to the unsandboxed desktop process. -The flow selects Pumas' FLUX.2 image dependency profile by default. Exact package -resolution still determines whether that profile can be installed for a given -Torch release, and a passing package preview does not prove device use, image -generation, or Tuldok compatibility. The existing real Tuldok evidence remains -limited to Torch 2.10 / CUDA 13.0 / Python 3.12 / FLUX.2; no v2.14.0 image result -or all-release inference claim is added by this implementation. +At that time the flow selected Pumas' FLUX.2 image dependency profile by +default. The current flow defaults to core Torch (`none`) and makes FLUX.2 an +explicit advanced profile on Linux. Exact package resolution still determines +whether FLUX.2 can be installed for a given Torch release, and a passing +package preview does not prove device use, image generation, or Tuldok +compatibility. The existing real Tuldok evidence remains limited to Torch +2.10 / CUDA 13.0 / Python 3.12 / FLUX.2; no v2.14.0 image result or all-release +inference claim is added by this implementation. ## Earlier evidence retained from the previous report @@ -252,8 +300,11 @@ desktop control test, matching the user's approved “desktop or API” scope. T actual desktop-driven FLUX.2/Tuldok repeat below remains a separate desktop product claim; it is not required to prove the RPC installation path. -U1–U4 are accepted for the scope in the plan. On Linux x86_64, the current Pumas -RPC flow discovered 63 stable tags, previewed `v2.9.0` / CPU / CPython 3.12 as +U1–U4 were accepted for the earlier Linux x86_64 flow using a host-installed +CPython interpreter. Managed-Python provisioning supersedes that setup, so this +record is historical evidence and does not satisfy the current clean-host +acceptance gate. On Linux x86_64, the Pumas RPC flow discovered 63 stable tags, +previewed `v2.9.0` / CPU / CPython 3.12 as 25 exact wheel artifacts with URLs and SHA-256 hashes, installed it from the retained resolution, inspected a passing core probe, and explicitly selected it. A managed Torch profile then passed startup, health, and protocol 3 checks as @@ -264,12 +315,12 @@ wheel was `2.9.0+cpu` with SHA-256 The adapter was `none`; no image generation was attempted. The probe result was passed, non-stale, with core status passed and adapter status not selected. -The accepted RPC lifecycle summary and complete 25-artifact manifest are now +The historical RPC lifecycle summary and complete 25-artifact manifest are retained in the [acceptance evidence JSON](../../torch-upstream-version-management/reports/v2.9.0-cpu-rpc-acceptance.json). The original isolated launcher root and temporary acceptance file were under `/tmp/pumas-torch-upstream-e2e-20260924-accepted`. This closes PRG-I17's runtime -management acceptance for the authorized Linux/Python scope. It does not claim a -real interactive Pumas desktop run. +management acceptance for the earlier Linux/Python scope. It does not claim +clean-host managed-Python provisioning or a real interactive Pumas desktop run. ## Historical pre-U6 branch gates diff --git a/docs/plans/torch-upstream-version-management/plan.md b/docs/plans/torch-upstream-version-management/plan.md index 96998f389..a7e02cc55 100644 --- a/docs/plans/torch-upstream-version-management/plan.md +++ b/docs/plans/torch-upstream-version-management/plan.md @@ -5,28 +5,31 @@ **Owner:** Pumas runtime integration **Authorization:** The repository owner selected this scope on 2026-09-24 after -reviewing the active Torch deployment inventory. +reviewing the active Torch deployment inventory. On 2026-09-25, the owner +authorized Pumas-managed stable CPython provisioning with no host-Python or +user-Python-selection requirement. ## Execution state -**Current phase:** U1–U5 remain accepted for the approved Linux x86_64 / installed -CPython scope. U6 implementation and acceptance gates are complete: the live -release-specific `v2.14.0` wheel scan, host filtering and driver policy, -generated RPC contract, manager tests, and desktop tests pass. A full -`v2.14.0` FLUX.2 dependency preview and image-generation run remain a separate -exact-tuple validation. A real interactive Pumas desktop image run and image -generation across other Torch tuples are not claimed by this plan. - -**Blocker:** No source implementation blocker. The current sandbox cannot -communicate with the NVIDIA driver, and the full `v2.14.0` FLUX.2 dependency -preview has not completed. CUDA recommendation and Tuldok image generation for -that tuple still need host evidence. - -**Next slice:** Resolve and retain the exact `v2.14.0` / `cu132` / automatically -selected installed Python / Pumas FLUX.2 dependency preview. If it resolves, -install, select, start, and generate through Tuldok on the GPU host; record any -upstream dependency incompatibility explicitly. Keep this exact-tuple result -separate from any all-release claim. +**Current phase:** The managed-CPython provider, automatic-choice UI, resolver +candidate scan, retained interpreter provenance, and Core-default profile have +passed code review and targeted Linux checks. Local Linux 0.7.0 AppImage and deb +packages contain the commit `6a726eac` RPC registration and pass artifact and +bundled RPC health smoke. The toolbar-linked package has not been replaced or +published. Image evidence remains limited to its exact previously recorded +tuples. + +**Blockers:** The current sandbox cannot reach the uv, Python, or PyTorch +artifact hosts, so clean-host provisioning, downloaded-byte verification, and +the exact `v2.14.0` preview cannot run here. Native Windows and macOS acceptance +also remains pending. + +**Next slice:** Once artifact-host access is available, install `v2.14.0` Core +runtime (`none`) from the candidate using the newest stable managed CPython +whose official wheel and complete dependencies resolve. Record the installed +identity and sidecar lifecycle. Keep this Torch install path separate from +FLUX.2, image generation, and Tuldok claims, which require their own exact +runtime evidence. ## Product contract @@ -34,20 +37,22 @@ Every stable upstream PyTorch release tag (`vMAJOR.MINOR.PATCH`) is discoverable without a Pumas qualification allowlist when the complete GitHub listing fits the 20-page / 120-second request budget. If a page fails or the budget is exceeded, discovery returns an error and never presents a partial listing as -complete. A release is installable when the user can select an official CPU, -CUDA, or ROCm binary wheel that matches Linux x86_64 and an already installed -CPython 3.10–3.13 interpreter, and the full sidecar dependency set resolves to -binary artifacts. For dynamic choices, the retained preview records the exact -version, build, interpreter, adapter, wheel URLs, and hashes consumed by -installation. +complete. A release is installable when an official CPU, CUDA, or ROCm binary +wheel matches the native host and a stable standard CPython provisioned by Pumas, +and the full sidecar dependency set resolves to binary artifacts. CPython 3.10 +is the sidecar runtime minimum, with no configured upper minor cap. Candidate +Python versions come from wheel compatibility tags and the pinned managed +provider catalog; the manager selects the newest stable candidate whose complete +resolution succeeds. For dynamic choices, the retained preview records the exact +version, build, interpreter and provider identity, adapter, wheel URLs, and +hashes consumed by installation. "Any release" describes discovery and exact-tuple resolution, not a promise that every old tag has a wheel for every interpreter or build. Unsupported -combinations and network-inconclusive checks remain distinct. Pumas does not -provision Python or compile Torch from source in this plan. XPU and operating -systems other than Linux x86_64 remain outside this plan's accepted -provider/runtime contract. A separate Windows/macOS expansion is authorized but -not yet accepted under the +combinations and network-inconclusive checks remain distinct. Pumas provisions +its own Python runtime and does not compile Torch from source in this plan. XPU +remains outside this plan's accepted provider/runtime contract. Windows and +macOS support is authorized but not yet accepted under the [cross-platform Torch runtime plan](../torch-cross-platform-runtime-management/plan.md). The fixed `v2.9.1` CUDA 13.0 / CPython 3.12 preset remains available as a separately qualified, previously accepted exception backed by the embedded @@ -57,15 +62,17 @@ does not claim the dynamic path's full per-wheel URL preview. Every other tuple for `v2.9.1` uses the normal retained preview path and does not inherit the preset's qualification. -Normal installation does not require the user to pick a build, Python -interpreter, or dependency profile. The manager discovers exact official wheels -for the selected release and installed CPython 3.10–3.13 interpreters, filters +Normal installation does not require the user to pick a Python interpreter. +The manager provisions its pinned Python provider, discovers exact official +wheels for the selected release and provider-catalog candidates, filters GPU channels by detected display-device vendor, and returns a recommendation -when its bounded scan and host checks support one. Advanced controls can select -only a returned exact wheel/interpreter pair. Every install still requires the -existing full dependency preview and retained hash lock. The default image -profile is Pumas-owned FLUX.2 dependencies, not an upstream Torch feature; Core -runtime only remains an explicit advanced choice. +when its bounded scan and host checks support one. The manager selects Python +automatically; advanced controls can select only a returned build/profile +combination. Every install still requires the +existing full dependency preview and retained hash lock. Core Torch runtime is +the default. On Linux, Pumas-owned FLUX.2 dependencies remain an explicit +advanced profile; they are not part of an upstream Torch release. Windows and +macOS offer only the core profile until a platform-specific adapter is accepted. Installing a release does not activate it or make it the default. Selection, default selection, sidecar startup, and stopping remain explicit version/profile @@ -93,7 +100,7 @@ updated PRG-I17 disposition. | U3 | A non-preset release installs into staging from the retained lock, passes identity/core probes, and publishes only after validation. Failure/cancellation preserves installed, active, and default state. | Managed installation tests and an isolated real v2.9.0+cpu / CPython 3.12 installation on Linux x86_64; see the [RPC acceptance summary and artifact manifest](reports/v2.9.0-cpu-rpc-acceptance.json). | | U4 | The installed runtime can be inspected, explicitly selected, started by a managed Torch profile, health/protocol checked, and stopped by its owned generation through Pumas RPC or the desktop. | Composed desktop test plus a real older-release RPC lifecycle on Linux x86_64; see the [RPC acceptance summary](reports/v2.9.0-cpu-rpc-acceptance.json). | | U5 | The current real Tuldok image result remains accurately scoped to its exact Torch 2.10 CUDA 13.0 / FLUX.2 tuple; no general model/plugin or all-release image-generation support is inferred. | Reuse [2.10 GPU and Tuldok evidence](../torch-diffusion-serving/reports/upstream-v210-cu130-flux2-e2e.md); update the runtime inventory in [the progress report](../torch-diffusion-serving/reports/upstream-version-manager-progress.md). | -| U6 | For each selected stable release, the manager discovers that release's exact official CPU/CUDA/ROCm wheel matches for installed CPython interpreters, filters GPU families against detected display devices, and supplies a host-aware default when compatibility is established. Build, Python, and Pumas dependency profile are optional advanced settings. Partial discovery stays inconclusive; a wheel match still requires the full retained dependency preview. | Dynamic official-index scan fixtures and live `v2.14.0` scan; NVIDIA driver-floor/unknown/partial-scan manager tests; typed RPC and generated-contract checks; desktop preview tests for automatic defaults, release-bounded overrides, null recommendations, and Pumas profile labeling. No all-release image qualification is inferred. | +| U6 | For each selected stable release, the manager discovers that release's exact official CPU/CUDA/ROCm wheel matches for Pumas-managed CPython candidates, filters GPU families against detected display devices, and supplies a host-aware default when compatibility is established. Build and Pumas dependency profile remain optional advanced settings; Python selection is automatic. Partial discovery stays inconclusive; a wheel match still requires the full retained dependency preview. | Dynamic official-index scan fixtures and live `v2.14.0` scan; managed-provider candidate/fallback tests; NVIDIA driver-floor/unknown/partial-scan manager tests; typed RPC and generated-contract checks; desktop preview tests for automatic defaults, release-bounded overrides, null recommendations, and Pumas profile labeling. No all-release image qualification is inferred. | ## Ownership and verification diff --git a/electron/src/python-bridge.ts b/electron/src/python-bridge.ts index b3ede04f3..8f71097c3 100644 --- a/electron/src/python-bridge.ts +++ b/electron/src/python-bridge.ts @@ -52,9 +52,15 @@ interface RPCResponse { } export function rpcRequestTimeoutMs(method: string): number { - if (method === 'preview_torch_runtime') return 195_000; + // Managed Python may need a bounded uv bootstrap, interpreter install, index + // scan, and several full wheel previews before the RPC returns any bytes. + // Let those backend stage deadlines own these requests instead of imposing + // an earlier HTTP socket inactivity timeout. + if (method === 'get_torch_runtime_options' + || method === 'get_torch_release_options' + || method === 'preview_torch_runtime' + || method === 'find_torch_alternatives') return 0; if (method === 'trial_torch_runtime') return 90_000; - if (method === 'find_torch_alternatives') return 75_000; return 60_000; } diff --git a/electron/tests/python-bridge.test.mjs b/electron/tests/python-bridge.test.mjs index 57747684b..76875a4c9 100644 --- a/electron/tests/python-bridge.test.mjs +++ b/electron/tests/python-bridge.test.mjs @@ -14,15 +14,16 @@ import { parseStatusTelemetryUpdateSseChunk, } from '../dist/python-bridge.js'; -test('Torch artifact preview has time to complete while other RPC calls keep the normal timeout', () => { - assert.equal(rpcRequestTimeoutMs('preview_torch_runtime'), 195_000); +test('managed-Python Torch operations rely on bounded backend stages instead of old bridge timeouts', () => { + assert.equal(rpcRequestTimeoutMs('get_torch_release_options'), 0); + assert.equal(rpcRequestTimeoutMs('preview_torch_runtime'), 0); assert.equal(rpcRequestTimeoutMs('trial_torch_runtime'), 90_000); - assert.equal(rpcRequestTimeoutMs('find_torch_alternatives'), 75_000); + assert.equal(rpcRequestTimeoutMs('find_torch_alternatives'), 0); assert.equal(rpcRequestTimeoutMs('install_version'), 60_000); - assert.equal(rpcRequestTimeoutMs('get_torch_runtime_options'), 60_000); + assert.equal(rpcRequestTimeoutMs('get_torch_runtime_options'), 0); }); -test('bridge applies the longer timeout only to Torch preview HTTP requests', async () => { +test('bridge does not apply the old 60s/195s socket cutoff to provisioned-Python requests', async () => { const bridgeUrl = new URL('../dist/python-bridge.js', import.meta.url); const requireBridge = createRequire(bridgeUrl); const exports = {}; @@ -57,11 +58,12 @@ test('bridge applies the longer timeout only to Torch preview HTTP requests', as }); bridge.process = {}; + await bridge.call('get_torch_release_options', { tag: 'v2.10.0' }); await bridge.call('preview_torch_runtime', { tag: 'v2.10.0' }); await bridge.call('trial_torch_runtime', { tag: 'v2.10.0', profileId: 'torch-profile' }); await bridge.call('find_torch_alternatives', { tag: 'v2.10.0', build: 'cpu', python: 'python3.12' }); await bridge.call('get_torch_runtime_options', {}); - assert.deepEqual(seen.map((request) => request.timeout), [195_000, 90_000, 75_000, 60_000]); + assert.deepEqual(seen.map((request) => request.timeout), [0, 0, 90_000, 0, 0]); }); class FakeTimerController { diff --git a/frontend/src/components/TorchInstallPreview.test.tsx b/frontend/src/components/TorchInstallPreview.test.tsx index 42d21e3c4..b5cd9a716 100644 --- a/frontend/src/components/TorchInstallPreview.test.tsx +++ b/frontend/src/components/TorchInstallPreview.test.tsx @@ -38,7 +38,7 @@ const preset: TorchRuntimeOptions = { builds: ['cpu', 'cu130', 'cu134', 'rocm'], pythons: [{ id: 'python3.12', label: 'Python 3.12' }], adapters: ['none', 'flux2'], installed: [], - bundledPresetAvailable: true, defaultAdapter: 'flux2', + bundledPresetAvailable: true, defaultAdapter: 'none', preset: { tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' }, }; @@ -50,6 +50,7 @@ beforeEach(() => { status: 'resolved', preview: { ...request, previewId: 'review-1', expiresInSeconds: 300, + python: request.python === 'auto' ? (request.build === 'cpu' ? 'python3.11' : 'python3.12') : request.python, qualification: request.adapter === 'bundled' ? 'qualified' : 'unverified', artifacts: [{ name: 'torch', version: '2.14.0', url: 'https://download.pytorch.org/torch.whl', sha256: 'abc123' }], }, @@ -57,36 +58,37 @@ beforeEach(() => { }); describe('TorchInstallPreview', () => { - it('uses the manager recommendation without requiring build, Python, or profile selectors', async () => { + it('uses the manager recommendation with core Torch and no required profile selectors', async () => { const onInstall = vi.fn(); render(); - expect(await screen.findByText(/Recommended setup: cu130 · python3.12 · Pumas image dependencies/)).toBeInTheDocument(); + expect(await screen.findByText(/Recommended setup: cu130 · Python selected automatically · Core runtime only/)).toBeInTheDocument(); expect(getReleaseOptions).toHaveBeenCalledWith('v2.14.0'); expect(getPresetOptions).toHaveBeenCalledOnce(); expect(screen.getByText('Advanced setup').closest('details')).not.toHaveAttribute('open'); - expect(screen.getByText(/selected by Pumas for its FLUX.2 path; they are not part of upstream Torch/)).toBeInTheDocument(); + expect(screen.getByText(/Pumas installs a private Python version that matches the selected Torch release and its dependencies/i)).toBeInTheDocument(); + expect(screen.queryByRole('combobox', { name: 'Installed Python' })).not.toBeInTheDocument(); + expect(screen.queryByText(/install Python 3\.10–3\.13/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/selected by Pumas for its FLUX.2 path/)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'python3.12', adapter: 'flux2' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'auto', adapter: 'none' })); expect(await screen.findByText('SHA-256: abc123')).toBeInTheDocument(); + expect(screen.getByText('Selected Python: python3.12')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); expect(onInstall).toHaveBeenCalledWith('review-1'); }); - it('limits advanced build and Python overrides to this release’s returned combinations', async () => { + it('limits advanced build overrides to this release’s returned combinations', async () => { render(); fireEvent.click(await screen.findByText('Advanced setup')); const build = screen.getByRole('combobox', { name: 'Torch build' }); expect(within(build).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose a build', 'cpu', 'cu130']); expect(within(build).queryByRole('option', { name: /cu134|rocm/i })).not.toBeInTheDocument(); - const python = screen.getByRole('combobox', { name: 'Installed Python' }); - expect(within(python).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose installed Python', 'python3.12']); + expect(screen.queryByRole('combobox', { name: 'Installed Python' })).not.toBeInTheDocument(); fireEvent.change(build, { target: { value: 'cpu' } }); - expect(python).toHaveValue('python3.11'); - expect(within(python).getAllByRole('option').map((option) => option.textContent)).toEqual(['Choose installed Python', 'python3.11']); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'auto', adapter: 'none' })); }); it('requires a deliberate advanced selection when no recommendation exists for a GPU-only release', async () => { @@ -105,11 +107,9 @@ describe('TorchInstallPreview', () => { fireEvent.click(screen.getByText('Advanced setup')); const build = screen.getByRole('combobox', { name: 'Torch build' }); expect(build).toHaveValue(''); - expect(screen.getByRole('combobox', { name: 'Installed Python' })).toBeDisabled(); fireEvent.change(build, { target: { value: 'cu132' } }); - expect(screen.getByRole('combobox', { name: 'Installed Python' })).toHaveValue('python3.12'); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu132', python: 'python3.12', adapter: 'flux2' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu132', python: 'auto', adapter: 'none' })); }); it('shows the manager’s driver caveat alongside a CPU recommendation', async () => { @@ -120,17 +120,18 @@ describe('TorchInstallPreview', () => { recommendationNote: 'NVIDIA hardware was detected, but its driver could not be verified. CPU is recommended.', }); render(); - expect(await screen.findByText(/Recommended setup: cpu · python3.11/)).toBeInTheDocument(); + expect(await screen.findByText(/Recommended setup: cpu · Python selected automatically/)).toBeInTheDocument(); expect(screen.getByText(/driver could not be verified. CPU is recommended/)).toBeInTheDocument(); }); - it('makes Core runtime only a deliberate advanced choice', async () => { + it('keeps Pumas FLUX.2 dependencies as an explicit advanced choice', async () => { render(); fireEvent.click(await screen.findByText('Advanced setup')); - fireEvent.change(screen.getByRole('combobox', { name: 'Dependency profile' }), { target: { value: 'none' } }); - expect(screen.getByText(/Recommended setup: cu130 · python3.12 · Core runtime only/)).toBeInTheDocument(); + fireEvent.change(screen.getByRole('combobox', { name: 'Dependency profile' }), { target: { value: 'flux2' } }); + expect(screen.getByText(/Recommended setup: cu130 · Python selected automatically · Pumas image dependencies/)).toBeInTheDocument(); + expect(screen.getByText(/selected by Pumas for its FLUX.2 path; they are not part of upstream Torch/)).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'python3.12', adapter: 'none' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'auto', adapter: 'flux2' })); }); it('does not imply an incomplete discovery found no wheels', async () => { @@ -142,18 +143,17 @@ describe('TorchInstallPreview', () => { expect(screen.queryByText(/No official wheel matches were found/)).not.toBeInTheDocument(); }); - it('requires an installed compatible Python before checking release wheels', async () => { - getReleaseOptions.mockResolvedValue({ - ...release, status: 'inconclusive', completeScan: false, - checkedChannels: [], combinations: [], recommended: null, - issues: ['No installed host-matched CPython 3.10–3.13 interpreter could inspect official wheels'], - }); + it('does not require a host Python selection before checking wheels', async () => { + getPresetOptions.mockResolvedValue({ ...preset, pythons: [], bundledPresetAvailable: false }); render(); - expect(await screen.findByText(/No installed host-matched CPython 3.10–3.13 interpreter/)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Check selected combination' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); - expect(getPreview).not.toHaveBeenCalled(); + const check = await screen.findByRole('button', { name: 'Check selected combination' }); + expect(check).toBeEnabled(); + fireEvent.click(screen.getByText('Advanced setup')); + expect(screen.queryByRole('combobox', { name: 'Installed Python' })).not.toBeInTheDocument(); + expect(screen.queryByText(/Install.*CPython 3\.10–3\.13/i)).not.toBeInTheDocument(); + fireEvent.click(check); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.14.0', build: 'cu130', python: 'auto', adapter: 'none' })); }); it('keeps the qualified v2.9.1 bundled preset and an unverified upstream choice distinct', async () => { @@ -161,18 +161,25 @@ describe('TorchInstallPreview', () => { const onInstall = vi.fn(); render(); const qualified = await screen.findByRole('button', { name: 'Qualified fixed preset' }); + expect(qualified).toHaveAttribute('aria-pressed', 'false'); + expect(screen.getByRole('button', { name: 'Other official wheels (unverified)' })).toHaveAttribute('aria-pressed', 'true'); + expect(screen.getByText('Advanced setup').closest('details')).not.toHaveAttribute('open'); + fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'auto', adapter: 'none' })); + expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeEnabled(); + + fireEvent.click(qualified); expect(qualified).toHaveAttribute('aria-pressed', 'true'); - expect(screen.queryByRole('combobox', { name: 'Torch build' })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' })); + await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'bundled' })); fireEvent.click(screen.getByRole('button', { name: 'Install fixed preset' })); expect(onInstall).toHaveBeenCalledWith('review-1'); fireEvent.click(screen.getByRole('button', { name: 'Other official wheels (unverified)' })); expect(screen.getByRole('button', { name: 'Install reviewed artifacts' })).toBeDisabled(); - expect(screen.getByText(/Pumas image dependencies are selected by Pumas/)).toBeInTheDocument(); + expect(screen.queryByText(/Pumas image dependencies are selected by Pumas/)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'python3.12', adapter: 'flux2' })); + await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.9.1', build: 'cu130', python: 'auto', adapter: 'none' })); expect(await screen.findByText(/Artifact resolution is unverified/)).toBeInTheDocument(); }); @@ -186,16 +193,16 @@ describe('TorchInstallPreview', () => { const onInstall = vi.fn(); render(); - expect(await screen.findByText(/Recommended setup: cpu · python3.12 · Core runtime only/)).toBeInTheDocument(); + expect(await screen.findByText(/Recommended setup: cpu · Python selected automatically · Core runtime only/)).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Qualified fixed preset' })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Other official wheels (unverified)' })).not.toBeInTheDocument(); expect(screen.queryByText(/bundled image dependencies \(fixed\)/)).not.toBeInTheDocument(); - expect(screen.getByText(/matching this host’s operating system and architecture/)).toBeInTheDocument(); + expect(screen.queryByText(/install Python 3\.10–3\.13/i)).not.toBeInTheDocument(); fireEvent.click(screen.getByText('Advanced setup')); const profile = screen.getByRole('combobox', { name: 'Dependency profile' }); expect(within(profile).getAllByRole('option').map((option) => option.textContent)).toEqual(['Core runtime only']); fireEvent.click(screen.getByRole('button', { name: 'Check selected combination' })); - await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.9.1', build: 'cpu', python: 'python3.12', adapter: 'none' })); + await waitFor(() => expect(getPreview).toHaveBeenCalledWith({ tag: 'v2.9.1', build: 'cpu', python: 'auto', adapter: 'none' })); fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); expect(onInstall).toHaveBeenCalledWith('review-1'); }); @@ -226,7 +233,7 @@ describe('TorchInstallPreview', () => { getPreview.mockResolvedValueOnce({ status: 'rejected', reason: 'unsupported', message: 'selected dependencies have no matching wheel' }); getPreview.mockImplementationOnce(async (request) => ({ status: 'resolved', - preview: { ...request, previewId: 'review-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, + preview: { ...request, python: 'python3.11', previewId: 'review-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, })); getAlternatives.mockResolvedValue({ selectedTag: 'v2.14.0', selectedBuild: 'cu130', selectedPython: 'python3.12', @@ -245,7 +252,7 @@ describe('TorchInstallPreview', () => { expect(screen.getByText(/Other dependencies have not been checked/)).toBeInTheDocument(); expect(getAlternatives).toHaveBeenCalledWith('v2.14.0', 'cu130', 'python3.12'); fireEvent.click(screen.getByRole('button', { name: 'Preview v2.14.0 · cpu · python3.11' })); - await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' })); + await waitFor(() => expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'auto', adapter: 'none' })); expect(await screen.findByText('Exact artifacts resolved')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Install reviewed artifacts' })); expect(onInstall).toHaveBeenCalledWith('review-cpu'); @@ -261,7 +268,7 @@ describe('TorchInstallPreview', () => { getPreview.mockResolvedValueOnce({ status: 'rejected', reason: 'unsupported', message: 'dependency unavailable' }); getPreview.mockImplementationOnce(async (request) => ({ status: 'resolved', - preview: { ...request, previewId: 'eligible-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, + preview: { ...request, python: 'python3.11', previewId: 'eligible-cpu', expiresInSeconds: 300, qualification: 'unverified', artifacts: [] }, })); getAlternatives.mockResolvedValue({ selectedTag: 'v2.14.0', selectedBuild: 'cpu', selectedPython: 'python3.11', @@ -284,7 +291,7 @@ describe('TorchInstallPreview', () => { expect(screen.queryByRole('button', { name: /Preview .*cu130|Preview .*rocm6\.4|Preview v2\.13\.0/ })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Preview v2.14.0 · cpu · python3.11' })); await waitFor(() => expect(getPreview).toHaveBeenCalledTimes(2)); - expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'python3.11', adapter: 'flux2' }); + expect(getPreview).toHaveBeenLastCalledWith({ tag: 'v2.14.0', build: 'cpu', python: 'auto', adapter: 'none' }); }); it('keeps a long artifact list and its install action inside the scrollable preview region', async () => { @@ -292,6 +299,7 @@ describe('TorchInstallPreview', () => { status: 'resolved', preview: { ...request, previewId: 'review-many', expiresInSeconds: 300, qualification: 'unverified', + python: 'python3.12', artifacts: Array.from({ length: 30 }, (_, index) => ({ name: `wheel-${index}`, version: '1.0', url: `https://download.pytorch.org/whl/cu130/wheel-${index}.whl`, diff --git a/frontend/src/components/TorchInstallPreview.tsx b/frontend/src/components/TorchInstallPreview.tsx index 7895261b7..601947c7f 100644 --- a/frontend/src/components/TorchInstallPreview.tsx +++ b/frontend/src/components/TorchInstallPreview.tsx @@ -38,7 +38,6 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev const [runtimeOptions, setRuntimeOptions] = useState(null); const [releaseOptions, setReleaseOptions] = useState(null); const [build, setBuild] = useState(''); - const [python, setPython] = useState(''); const [adapter, setAdapter] = useState(''); const [selectionMode, setSelectionMode] = useState<'preset' | 'upstream'>('upstream'); const [preview, setPreview] = useState(null); @@ -70,7 +69,6 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev selectionModeRef.current = 'upstream'; setSelectionMode(selectionModeRef.current); setBuild(''); - setPython(''); setAdapter(''); const releaseRequest = api.get_torch_release_options(tag).then((result) => { if (!active) return; @@ -78,7 +76,6 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev if (selectionModeRef.current === 'preset') return; const choice = result.combinations.find((item) => item.build === result.recommended?.build && item.python === result.recommended.python); setBuild(choice?.build ?? ''); - setPython(choice?.python ?? ''); }).catch((cause: unknown) => { if (active) setError(`Release discovery inconclusive: ${errorText(cause)}`); }).finally(() => { @@ -87,15 +84,7 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev const runtimeRequest = api.get_torch_runtime_options().then((result) => { if (!active) return; setRuntimeOptions(result); - if (tag === result.preset.tag && result.bundledPresetAvailable) { - selectionModeRef.current = 'preset'; - setSelectionMode('preset'); - setBuild(result.preset.build); - setPython(result.preset.python); - setAdapter(result.preset.adapter); - } else { - setAdapter(result.defaultAdapter); - } + setAdapter(result.defaultAdapter); }).catch((cause: unknown) => { if (active) setError(`Torch runtime choices unavailable: ${errorText(cause)}`); }).finally(() => { @@ -120,11 +109,12 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev setAlternativesBusy(false); }; - const probe = async (selection = { build, python, adapter }) => { + const selectedPython = selectionMode === 'preset' ? runtimeOptions?.preset.python ?? '' : 'auto'; + const probe = async (selection = { build, python: selectedPython, adapter }) => { if (!selection.build || !selection.python || probing) return; const currentRequest = ++requestNumber.current; const key = `${selection.build}|${selection.python}|${selection.adapter}`; - const label = `${selection.build} · ${selection.python} · ${selection.adapter}`; + const label = `${selection.build} · ${selection.python === 'auto' ? 'Python selected automatically' : selection.python} · ${selection.adapter}`; const recordCheck = (status: CheckedCombination['status']) => { setChecks((previous) => [...previous.filter((item) => item.key !== key), { key, label, status }]); }; @@ -161,7 +151,9 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev setAlternatives(null); setAlternativesError(null); try { - const result = await api.find_torch_alternatives(tag, build, python); + const discoveryPython = releaseOptions?.combinations.find((choice) => choice.build === build)?.python; + if (!discoveryPython) return; + const result = await api.find_torch_alternatives(tag, build, discoveryPython); if (requestNumber.current === currentRequest) setAlternatives(result); } catch (cause) { if (requestNumber.current === currentRequest) setAlternativesError(errorText(cause)); @@ -178,30 +170,26 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev setSelectionMode(mode); if (mode === 'preset') { setBuild(runtimeOptions?.preset.build ?? ''); - setPython(runtimeOptions?.preset.python ?? ''); setAdapter(runtimeOptions?.preset.adapter ?? ''); } else { const choice = releaseOptions?.combinations.find((item) => item.build === releaseOptions.recommended?.build && item.python === releaseOptions.recommended.python); setBuild(choice?.build ?? ''); - setPython(choice?.python ?? ''); setAdapter(runtimeOptions?.defaultAdapter ?? ''); } }; const availableBuilds = [...new Set(releaseOptions?.combinations.map((choice) => choice.build) ?? [])]; - const availablePythons = [...new Set(releaseOptions?.combinations.filter((choice) => choice.build === build).map((choice) => choice.python) ?? [])]; const availableAdapters = runtimeOptions?.adapters.filter((choice) => choice === 'none' || choice === 'flux2') ?? []; const isEligibleAlternative = (match: TorchAlternativeMatch) => match.tag === tag && releaseOptions?.tag === tag && releaseOptions.combinations.some((choice) => choice.build === match.build && choice.python === match.python); const eligibleAlternatives = alternatives?.matches.filter(isEligibleAlternative) ?? []; const eligibleCheckedBuilds = alternatives?.checkedBuilds.filter((choice) => availableBuilds.includes(choice)) ?? []; - const hasSelectedPython = fixedPreset - ? Boolean(runtimeOptions.pythons.some((choice) => choice.id === python)) - : Boolean(releaseOptions?.combinations.some((choice) => choice.build === build && choice.python === python)); const loadingOptions = loadingRuntimeOptions || (!fixedPreset && loadingReleaseOptions); const canInstall = preview !== null && Boolean(preview.previewId) && preview.tag === tag && preview.build === build - && preview.python === python && preview.adapter === adapter; + && Boolean(preview.python) && preview.python !== 'auto' + && (!fixedPreset || preview.python === runtimeOptions.preset.python) + && preview.adapter === adapter; return (
@@ -220,10 +208,10 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev This check resolves package files and hashes. Device use, image generation, and socket startup need later runtime checks.

- Managed Torch requires an already installed CPython 3.10–3.13 interpreter matching this host’s operating system and architecture; Pumas does not install Python. + This flow installs official binary wheels only. Source compilation is a separate unsupported path and is never used as a fallback.

- This flow installs official binary wheels only. Source compilation is a separate unsupported path and is never used as a fallback. + Pumas installs a private Python version that matches the selected Torch release and its dependencies. Python does not need to be installed on this computer.

{loadingOptions ? : runtimeOptions && (releaseOptions || fixedPreset) && ( @@ -233,31 +221,24 @@ export function TorchInstallPreview({ tag, onBack, onInstall }: TorchInstallPrev } {fixedPreset &&

This release has one qualified Python 3.12 / CUDA 13.0 build.

} - {fixedPreset ?

Recommended setup: {build} · {python} · bundled image dependencies (fixed).

: <> - {build && python &&

{releaseOptions?.recommended?.build === build && releaseOptions.recommended.python === python ? 'Recommended setup' : 'Selected setup'}: {build} · {python} · {adapter === 'flux2' ? 'Pumas image dependencies' : 'Core runtime only'}.

} + {fixedPreset ?

Recommended setup: {build} · {runtimeOptions.preset.python} · bundled image dependencies (fixed).

: <> + {build &&

{releaseOptions?.recommended?.build === build ? 'Recommended setup' : 'Selected setup'}: {build} · Python selected automatically · {adapter === 'flux2' ? 'Pumas image dependencies' : 'Core runtime only'}.

} {!build && releaseOptions?.combinations.length ?

No setup was recommended. Open Advanced setup and choose a build to continue.

: null} {releaseOptions?.recommendationNote &&

{releaseOptions.recommendationNote}

} {adapter === 'flux2' &&

Pumas image dependencies are selected by Pumas for its FLUX.2 path; they are not part of upstream Torch.

}
Advanced setup -
+
-