Repository navigation
feat: v0.8.0 consumer API, desktop contract validation and release version - #76
Conversation
A valid reindex can restore index-only damage between synchronous and awaited refusal checks. Drain background work before fault injection and retain a successful post-drain resolution as a positive control. Test live recovery separately while keeping invalid payloads and receipts refused.
Verify supplied SHA evidence during acquisition and retain it for explicit integrity checks. Keep receipt, path, file identity and required-member checks without rehashing installed runtimes, published models or copied read sources. Derive all audio dependency constraints from the existing candidate recipe and require exact download artifact identities before staged publication. Use layout metadata for the coordinated Rust/Python model correlation ID. Persisted checksum fields and RPC shapes are unchanged. Direct Rust consumers must handle optional RuntimeReadFile checksum evidence and the removed fixed installed-byte helper when adopting this prerelease candidate.
Record the committed source and local Debian/AppImage artifact identities, release build, packaged resource startup and isolated install/remove results. Keep native-platform and real-inference qualification limits explicit.
A catalog row merged with live download activity was still displaying the missing-ticket warning intended for inactive partial files. Use the existing download ownership state to select that diagnostic and preserve exact live pause/resume routing and current-ticket requirements for inactive recovery.
Library-root exclusion can refuse deletion while another artifact downloads. The action hook previously logged that refusal without informing the user. Own pending and failed deletion state by exact model ID, retain distinct weight identities, and expose bounded guidance for busy-library RPC errors. Keep existing acquisition custody and mutation exclusion unchanged.
Project managed download errors through their exact presentation keys onto associated catalog rows and unassociated activity rows. Keep failed transfers separate from confirmed catalog completion and preserve exact retry identity.
Resume and retry must retain transferred bytes when workspace or checkpoint ownership is reconstructed. Continue HTTP/S3 under the selected supplied digest, preserve protocol and final acquisition verification, and retain partial files on refusal. Fresh remote writes cannot truncate a prefix. Keep continuation evidence ephemeral and preserve existing store and receipt shapes. Expose bounded continuation guidance through the progress contract.
Record the committed backend producer, accepted test evidence and exact Debian/AppImage candidates. Both extracted backends pass health/shutdown checks; isolated Debian installation, alternatives and removal pass. Preserve qualification limits and packaging warnings. Host installation, running app and model library remain untouched for maintainer review.
The managed_without_session case once refused readiness correctly but observed an unexplained backend request. Capture requests before the capability query and include both observations in the existing assertion. Retain strict availability and zero-request assertions. Record the initial failure and passing focused/cohort/final runs without claiming a cause or changing the production validator.
Deletion acquired a separate exclusive root grant even when the same app owned it for another destination. Share the HF owner grant through a private deletion lease and retain exact effects/admission exclusion until the existing library custody and intent claim settle. Serialize deletion against durable admissions without blocking ordinary queueing. Preserve native exclusion for independent owners and reject retained target custody. Pre-write refusals cannot poison owner shutdown. Cover primary API, local IPC and real HTTP download deletion/completion. Record final core/RPC suites, headless regressions and strict lint evidence.
Sharing the HF root grant allows independent deletions to overlap. Link registry saves captured snapshots before asynchronous atomic writes, so older saves could publish after newer mutations. Serialize cache changes, load and publication across registry clones, and retain the turn in the actual worker after a save waiter disappears. Keep transfer execution independent and preserve the existing link data format. Cover overlapping cascade removals and abandoned save waiters with canonical reload. Record passing complete core/RPC suites, headless tests and lints.
Record the final optimized producer, accepted checks and exact Linux installers. Verify the newer Debian revision and a distinctly named copy so the fixed candidate can be identified during installation. Include package resource/RPC smoke, isolated install/remove and optimized exact-target deletion evidence. Preserve warnings and the unresolved vision request-count diagnostic without claiming inference qualification.
Classify only completed read-only update responses on single-request connections. Give desktop observation streams separate connections so ordinary close/reopen does not poison the instance shutdown receipt. Preserve custody and failure reporting for pending requests, finite responses, inference streams and handler panics.
Offer exact failed-download inspection and explicit local acceptance in the running desktop. Preserve the original Error and file observations, release only its durable and live admission, and fence stale resume without changing model files or fabricating HF completion. Scope the opt-in format upgrade to the HF partition and warn older direct readers before confirmation.
Record the optimized workspace checks and actual Debian recovery, installation and close/reopen evidence. Clarify the independently scoped HF partition format and retain the remaining native and composed qualification limits.
…odels Typed model operations now report why a request was rejected. Every error carries a human-readable message, the caller's request_id is echoed whenever the body is JSON with a valid ID, and HTTP status always matches the error code. Provider failure reasons (for example llama.cpp's "increase the physical batch size") reach the caller with credential-shaped text redacted, on both the typed path and the OpenAI-compatible routes. /v1/model-operations has one request grammar. capability, semantic_task and options are optional selectors over the modality-first shape, and the image_messages input kind is accepted, so every request accepted before remains valid. GET /v1/served-models lists every served (model, profile) pair once with provider, load state, context size and capability descriptors. /v1/models lists each ID once with its profiles. Flat, model-agnostic limits are removed from the typed path: text and vision max_tokens are bounded by the served context, and the ONNX batch, length and dimension limits apply only to the ONNX adapter. The contract now states the compatibility rule: responses may grow within contract_version 1 and consumers ignore what they do not use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vision-language models published as image-text-to-text (Gemma 3/4, Qwen VL and similar) now advertise chat_generation, text_generation and image_to_text instead of model_task_mismatch. EmbeddingGemma 2 and other GGUF embedding models serve correctly: - primary model file selection never picks a larger mmproj projector, which previously made Pumas launch the projector as the model and made router catalog generation fail for every model; - llama.cpp serving mode is derived once from the model type and applied to router presets and dedicated launches (--embeddings or --reranking, with an 8K micro-batch so whole non-causal inputs fit); - Ollama embedding models load through /api/embed, since Ollama refuses /api/generate for them; - requested embedding dimensions are applied by Pumas (Matryoshka truncation and renormalization) when a provider ignores them. A new multimodal_embedding capability embeds an image or a content_batch of mixed text/image items through llama.cpp's multimodal embedding input, available when a dedicated session has its projector loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native decision models such as Kev and OpenJev answer typed questions (choice, score, noul) about a state with probabilities through llama.cpp's /v1/systemone API. Pumas now exposes them as the `decision` capability: - a `decision` input carries the state, the questions and optional images; question shapes are validated with reasons, while option counts and score levels stay runtime limits; - answers are validated against the asked question types and returned as typed `decisions` results with input token usage; - a model is offered `decision` only when llama.cpp reports the `decisions` output modality, and such models no longer advertise text generation (OpenJev is published as image-text-to-text); - decision images are decoded off the async runtime with the existing bounded image validation. The model-operations contract and the pumas-rpc README now describe served-model discovery, every capability, runtime-derived limits, content_batch and decision inputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A field the download store does not know (for example one written by a newer Pumas) made restore fail, which aborted the whole backend: the desktop waited for readiness and timed out. Download state is one subsystem, so the backend now checks it with a plain read before the custody-owned restore. If it cannot be read, the library, models and inference start; download changes are refused so the saved state is never rewritten; and the reason is reported in status `notices`, which the desktop shows as a banner. Restore failures inside an admitted restore keep their fatal custody semantics, so a clean shutdown retains no failed owner claim and the next start works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sons The launcher-root startup state, selection result and library-upgrade result were declared by hand four times (Electron main, preload validator, frontend types) with exact-key checks, so adding a field meant editing every layer. They are now declared once in electron/contracts and the desktop contract generator merges them with the Rust export, giving main, preload and the frontend one generated type and validator. A declaration owned by both sources is rejected. The backend's startup diagnostic is now a typed, exported StartupFailureFrame written for every fatal startup error, with a bounded `detail`. Electron may run a backend from another build, so the frame is read tolerantly (unknown fields ignored, unknown reasons treated as backend-unavailable). The detail reaches the recovery screen, which shows it instead of guessing that another app is using the library. The generated startup-state type also enforces that an explicit launch input always maps to correcting that input, which the old projection did not prove. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sum policy Brings in the 21 commits made after PR #69 that were never merged into main, which the installed 0.8.0-rc.1 desktop build was produced from: moved-download reconciliation (and its HF partition field), resume and restart partial preservation, deletion during unrelated HF downloads, link-registry publication serialization, observation-stream shutdown, the acquisition checksum policy, and their desktop and release records. Conflict resolution: - Audio runtime recipe: follow the acquisition checksum policy the owner selected on 2026-10-10 (docs/plans/acquisition-checksum-policy.md), which supersedes automatic installed-byte validation for the audio dependency pin. The branch's constraint-based recipe is kept; main's 38ec86f (frozen wheel link page plus interpreter and installed-member rehashing) is backed out from the recipe, installer, preview, resolver and their tests, leaving no unused paths. - acquired_gguf_vision: keep the branch test, which asserts file bindings rather than unchanged payload bytes under the same policy. - CHANGELOG: keep both sides' entries. - Generated desktop contract and release attribution: regenerated from the merged sources. - The download-state regression test now uses a field no Pumas version knows, since the merged store understands hf_model_partition_version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSON-RPC errors and outcome `error` fields replaced every library failure
with one of seven fixed sentences ("A required operation is currently
unavailable."), so callers could not tell why something failed, for
example why downloads were refused. They now carry the actual reason,
prepared once by the new `public_text` module: bounded to 1,024
characters, single-line, credential-shaped words, secret key=value pairs,
URL user info and URL query strings redacted, and the home directory
shown as `~`. Callers still branch on the stable code and class; protocol
errors keep their fixed sentences.
Conversion setup, conversion progress and download progress errors carry
their reasons too, and their desktop contract schemas are now bounded
strings instead of a single allowed sentence. The OpenAI gateway and the
startup frame use the same module, replacing two copies of the
redaction code. docs/SECURITY.md records the disclosure decision.
Tests that asserted the generic sentences now assert the specific reasons
while still requiring credentials to stay out of responses and logs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preload checked each update stream by hand and dropped any payload that failed, silently, which could leave a view stale with no explanation. The frontend re-checked model-library and runtime-profile payloads a third time and kept its own copies of every stream type. - pumas-core derives contract schemas for the model-library, runtime profile, serving status and status telemetry payloads; pumas-rpc exports them with the typed download push (ModelDownloadUpdateOutcome). - Conformance fixtures are built from the producer types and serialized by production serde, without opening a library. - Preload decodes every stream with the generated validators. Each subscription takes a required onError: a payload that fails its contract reports why, and the renderer reloads from its RPC snapshot. The serving-stream error payload is declared in electron/contracts. - Generated decoders name the failed rule and location (never a field's value) instead of a fixed "Invalid desktop contract payload.". - Frontend stream, status and resource types alias the generated ones; remove the duplicate validators and their test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The installed-runtime checksum policy verifies the managed interpreter's hash once, at installation; ada3258 removed the later rehash from the controlled session step but left this test requiring it. The test now asserts the step reuses the recorded interpreter path without hashing it again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply #74 and #75 on the release branch with the exact resolved versions Dependabot proposed: Electron 43.8.1, Vite 6.4.5, undici 6.29.1/7.30.1, zip 2.6.1, rustls 0.23.46, bytes 1.12.1 and rand 0.10. rand 0.10 moves the range helpers to `RngExt`, so the retry jitter and the metadata audit example import that trait. Regenerate both 0.8.0-rc.1 attribution sets, which the Dependabot branches left stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
llama.cpp b11429, the Kev minimum, cannot load EmbeddingGemma 2 (unknown model architecture 'gemma-embedding2'). Support arrived in ggml-org/llama.cpp#30054; b11454 is the first published release that contains it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
[High impact] The PR changes model-serving contracts and local model-library workflows across the desktop and backend. Fix batched embedding shortening and bearer credential redaction before merging. Findings
SummaryAdds typed consumer operations, served-model discovery, decision and mixed text/image embeddings. Replaces desktop validators and adds retained-download recovery.
|
| let vector = match request.options { | ||
| OperationOptions::Embeddings { | ||
| dimensions: Some(d), | ||
| } => truncate_embedding(vector, d).ok_or(invalid)?, | ||
| _ => vector, | ||
| }; | ||
| width = Some(vector.len()); |
There was a problem hiding this comment.
Shortened embedding batches fail
When a provider ignores dimensions, the first vector is shortened and its new length is stored in width. The next full-width vector is checked against that shortened length before it is shortened, so a valid batch returns InvalidProviderResult.
Compare widths before shortening, or compare all vectors afterward. Add a test with multiple full-width vectors and smaller requested dimensions.
| text.split(' ') | ||
| .map(|word| { | ||
| if std::mem::take(&mut redact_next) && !word.is_empty() { | ||
| return "[redacted]".to_owned(); | ||
| } | ||
| let bare = | ||
| word.trim_matches(|c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '-'); | ||
| if bare.eq_ignore_ascii_case("bearer") { |
There was a problem hiding this comment.
Bearer credentials can escape redaction
The new redactor splits only on literal spaces and clears redact_next even for an empty word. A forwarded provider error containing Bearer abc.def leaves abc.def unchanged. Bearer\tabc.def also bypasses the check because the whole word never matches Bearer.
Normalize whitespace before redacting, and keep the pending redaction until a nonempty word is consumed. Add tests through the provider-error response path.
How this was verified: Provider error messages pass through public_reason, whose literal-space split leaves these bearer values intact before the gateway returns the message.
| void api.list_model_downloads().then((result) => { | ||
| if (!cancelled) applyDownloadSnapshot(result.downloads.map(projectDownloadProgress)); |
There was a problem hiding this comment.
Download updates can move backward
The new error-recovery reply can overwrite a newer pushed update. If list_model_downloads() captures a downloading snapshot, a completion push arrives, and the RPC reply arrives last, this callback restores the old downloading state. It can remain wrong until another update or reload.
Apply the same snapshotGenerationRef check used by refreshDownloads so recovery cannot undo a newer update.
The staged pip install test assumed a Linux host: macOS CPU wheels have no `+cpu` local version, so pin the resolver's native target as the neighbouring tests already do. The Windows flush test still expected reusing a receipt to need GENERIC_WRITE. Since ada3258 only first acquisition verification flushes; reuse is read-only. The test now proves both: the first verification of a read-only file retains ERROR_ACCESS_DENIED, and reusing its receipt succeeds with read access alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Embedding and reranking sessions evaluate a whole input in one micro-batch. The batch now follows the profile's served context when one is set, never exceeds the model's trained context read from its GGUF header, and otherwise uses an 8,192-token startup default. A 512-token embedder no longer reserves an 8K batch. The trained context cannot be the default: EmbeddingGemma 2 and Kev record 262,144 tokens, and a micro-batch that size made llama.cpp try to allocate 202 GB at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Promote the root, frontend and Electron manifests and the Rust workspace package from 0.8.0-rc.1 to 0.8.0, updating only the five workspace entries in Cargo.lock. Generate the 0.8.0 and 0.8.0-s3 attribution sets (identical notices apart from the version) and point the packaged notices, RELEASING, the CHANGELOG and the release notes at them. The rc.1 attribution stays for the historical records that cite it. No tag or release is created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single-model reconcile checks that the model directory exists, then indexes and reclassifies it. An owned migration or reclassification can move the model in between, so reclassify reported ModelNotFound, the owned task failed and instance shutdown reported incomplete work (observed in the headless CI run of acquired_gguf_vision). When the directory is gone by the time the error returns, the scope is now handled exactly like one whose directory was already absent: its stale index row is dropped and nothing else changes. ModelNotFound for a model that still exists remains an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…liation Reclassification and artifact relocation already tell the watcher about the metadata at their destination, so their own move is not reconciled as an external change. Checkpointed migration and merge did not: the watcher reconciled the moved model and could reclassify it again while its mover, or a caller, still used the new location. That is the race the headless CI run of acquired_gguf_vision hit (metadata.json missing after a migration, and a dry-run report losing its recovery anchor). Both now notify the destination after the rename, like the other owned moves. A migration unit test records the notifications and fails without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The candidate build's platform-warnings step (cargo check --all-targets with -D warnings) failed on macOS: test helpers for the catalog custody, Torch read-source and native-recovery tests were compiled for every test build while their only callers are Linux tests. Gate each helper to its callers' platforms. The managed child's private stdio is used by Unix tests only, and Windows venvs have no per-minor directory, so the Windows build no longer reports those as unused either. Verified with RUSTFLAGS=-D warnings cargo check --workspace --all-targets on Linux and x86_64-pc-windows-gnu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows inference candidate failed its own verification ("inner
checksum inventory mismatch"): SHA256SUMS was written from sorted Path
objects, which Windows orders case-insensitively, in text mode (CRLF),
while verification sorted names as strings. Writer and verifier now
share one function that orders names by code point and emits LF-only
UTF-8 bytes, and verification compares bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS candidate's workspace tests failed in six places, most from /var being a symlink to /private/var: - Registry lookups by path fell back to the unresolved path when the library no longer existed, so a removed library's row (keyed by its canonical path) could not be found, unregistered or touched. Lookups now resolve the deepest surviving ancestor. A Unix regression test reproduces this with a symlinked parent on any platform. - Tests compared canonical owner roots, or keyed raw SQL, with the unresolved temporary path; they now use the canonical path. - macOS also needs write permission on a directory being renamed; the audio disposal test grants it before replacing a copied directory. - Apple's TLS stack refuses server certificates valid for more than 825 days, so the long-lived loopback TLS fixture cannot authenticate there. The downgrade-refusal test, which is platform-independent, now runs on Linux and Windows only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests The Windows candidate's workspace tests failed where the owner's canonical root (\\?\C:\Users\runneradmin\...) met the unresolved temporary path (C:\Users\RUNNER~1\...). The already-running refusal now prints the root through platform_display_path, so users see no verbatim-path prefix, and the tests that match that message, or the owner/read-only roots, compare against the canonical root. The other Windows failures share their macOS fixes in 0fdf223. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows inference candidate's lifecycle check imported its fixture
the moment the packaged RPC printed its port. Startup reconciliation of
that library was still running, so Pumas refused the import with its
retryable conflict ("Finish or cancel it, then try again"). The
verifier now retries conflict refusals for up to two minutes, as a
consumer should; a conflict is refused before any effect, and every
other error still fails immediately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aunch rules With the unit suites passing, the candidate's integration tests reached two more platform assumptions: - macOS and Windows launch only Torch binary profiles, so a llama.cpp launch is refused with "Only Torch binary profiles are supported on this platform"; the API tests still expected an older message. - Execution descriptors report paths in display form, while library roots are canonical (\\?\ on Windows); the GGUF vision tests compare descriptor paths in display form, and the root test uses the canonical temporary path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…im path The Windows inference candidate's lifecycle check reported that the packaged ONNX Runtime was not loaded. Pumas loads it through its canonical directory, which Windows reports as \\?\D:\..., while the expected path has no verbatim prefix, so the two never compared equal. Module paths now drop the prefix before comparison, and both refusals name the expected and observed module paths so a genuinely ambient runtime (such as System32's onnxruntime.dll) is identifiable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With pumas-library passing on macOS, the candidate's pumas-rpc tests reached the same assumption in their fixtures: hand-written download snapshots, registry rows and resolved package paths used the unresolved temporary path, while the owner canonicalizes its root (/var is /private/var on macOS; Windows adds \\?\). Download recovery refused the fixture's dest_dir as outside its authority, so shutdown failed to drain, raw-SQL fixture edits matched no row, and the FP8 package check saw its config outside the package. Fixtures now use the canonical root, which is what Pumas itself persists. Shutdown-drain and fixture-request assertions report the underlying error or request, and package facts are compared in display form on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.8.0 consumer API: typed model operations that explain every rejection, decision and multimodal-embedding capabilities, served-model discovery, and a desktop contract validated end to end from the backend's declaration. It also merges the previously local
fix/main-test-validationwork (download recovery, deletion and the installed-file checksum policy) and applies the Dependabot updates from #74 and #75.Sets the source version to
0.8.0(manifests, workspace lock entries, 0.8.0 attribution). No tag or release is created; the pre-tag release QA in RELEASING.md follows the merge.HTTP API (
/v1/model-operations,/v1/capabilities)messagenaming the field, limit, model or unavailable reason. The caller'srequest_idis echoed whenever it can be read, and the HTTP status matches the error code. Provider failure reasons are forwarded with credential-shaped text redacted.capability,semantic_taskandoptionsare optional selectors, and every request accepted before is still accepted.GET /v1/served-modelslists each model and profile once, with capabilities and context size./v1/modelsno longer repeats IDs.max_tokensfollows the served context, and ONNX batch limits apply only to ONNX. Image decode-safety limits stay.decisioncapability: llama.cpp decision models (Kev, OpenJev) through/v1/systemone, offered only when llama.cpp itself reports the model as a decision model.multimodal_embeddingcapability: image and mixed text/image items for projector-backed embedders (EmbeddingGemma 2).model_task_mismatch.Desktop
onError, and the view reloads its RPC snapshot instead of silently dropping the update. Rejections name the failed field and rule, never a field's value.Merged from
fix/main-test-validationDownload recovery, deletion during unrelated downloads, and partial preservation on resume. Also the checksum policy: supplied hashes are verified once at acquisition, and installed runtimes are not rehashed.
38ec86f9's installed-byte rehash for audio is backed out to follow that policy, and20160752updates the release test that still required the rehash.Serving
Native QA fixes
+cpusuffix.Dependencies
Electron 43.8.1, Vite 6.4.5, undici 6.29.1/7.30.1, zip 2.6.1, rustls 0.23.46 and rand 0.10, at the exact versions proposed in #74 and #75.
rand0.10 renames the range helpers toRngExt; the two imports are updated, since #74 did not compile. Both 0.8.0-rc.1 attribution sets are regenerated.Verification
pumas_rustler): 3,167 tests pass, and fmt and clippy (all features) are clean.owned_audio_route_refuses_unsupported_requests_and_stale_selection_without_replaycan exceed its fixed 30 s budget when the whole workspace runs on a loaded machine. It passes on its own and in a fullpumas-rpcrun (429/429).gemma-embedding2architecture; b11454 is the first published build that can, and the release notes now say so.numpyanduvicorn.Consumers
Matching branches named
feat/pumas-v080-apiexist in phased-memory, Tuldok, Lanternwake and Pumas-Playground. They read responses tolerantly, use served-model discovery, and show error messages.Closes #74, closes #75.
🤖 Generated with Claude Code