Skip to content

feat: v0.8.0 consumer API, desktop contract validation and release version - #76

Merged
MrScripty merged 49 commits into
mainfrom
feat/v080-consumer-api
Nov 3, 2026
Merged

MrScripty merged 49 commits into
mainfrom
feat/v080-consumer-api

Conversation

@MrScripty

@MrScripty MrScripty commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

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-validation work (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)

  • Every rejection carries a human-readable message naming the field, limit, model or unavailable reason. The caller's request_id is echoed whenever it can be read, and the HTTP status matches the error code. Provider failure reasons are forwarded with credential-shaped text redacted.
  • One request grammar. capability, semantic_task and options are optional selectors, and every request accepted before is still accepted.
  • Responses may grow (new fields, capabilities and enum values) within contract version 1. Consumers must ignore what they don't use.
  • GET /v1/served-models lists each model and profile once, with capabilities and context size. /v1/models no longer repeats IDs.
  • No flat limits on the typed path. max_tokens follows the served context, and ONNX batch limits apply only to ONNX. Image decode-safety limits stay.
  • New decision capability: llama.cpp decision models (Kev, OpenJev) through /v1/systemone, offered only when llama.cpp itself reports the model as a decision model.
  • New multimodal_embedding capability: image and mixed text/image items for projector-backed embedders (EmbeddingGemma 2).
  • Vision-language models (Gemma 3/4, Qwen VL) serve chat and image-to-text instead of reporting model_task_mismatch.

Desktop

  • RPC and outcome errors carry their failure reason (bounded and redacted) instead of timing out or reporting a generic failure.
  • The remaining hand-written preload validators are replaced by validators generated from the Rust contract or the Electron-owned schemas:
    • library-upgrade confirmation and model-import selection;
    • the Torch install options, saved probe, startup trial and alternative-wheel responses;
    • the five update streams (model library, downloads, runtime profiles, serving status, status telemetry).
  • An update a subscriber can't accept is reported through a required 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-validation

Download 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, and 20160752 updates the release test that still required the rehash.

Serving

  • llama.cpp embedding and reranking sessions size their micro-batch from the profile's served context, capped at the model's trained context from its GGUF header; otherwise an 8,192-token startup default applies. The trained context alone can't be the default: EmbeddingGemma 2 and Kev record 262,144 tokens, and a batch that size made llama.cpp try to allocate 202 GB.

Native QA fixes

  • The staged pip install test pins a Linux target, because macOS CPU wheels have no +cpu suffix.
  • The Windows flush test matches the checksum policy: the first verification of a read-only file is denied, and reusing its receipt needs read access only.

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. rand 0.10 renames the range helpers to RngExt; the two imports are updated, since #74 did not compile. Both 0.8.0-rc.1 attribution sets are regenerated.

Verification

  • Rust workspace (excluding pumas_rustler): 3,167 tests pass, and fmt and clippy (all features) are clean.
    • owned_audio_route_refuses_unsupported_requests_and_stale_selection_without_replay can exceed its fixed 30 s budget when the whole workspace runs on a loaded machine. It passes on its own and in a full pumas-rpc run (429/429).
  • Frontend and Electron: lint, types, tests and build pass, along with desktop-contract generation, the generator test, conformance, and the launcher tests.
  • Release: dependency ownership, release versions, attribution (base and S3), dependency features, and the release script tests (37/37) pass.
  • Live checks:
    • Kev through Pumas against llama.cpp b11429: decision discovered, and choice, score and yes/no answers returned with normalized probabilities.
    • EmbeddingGemma 2 has not run live. b11429 cannot load its gemma-embedding2 architecture; b11454 is the first published build that can, and the release notes now say so.
  • Not run here: native Windows/macOS packaging, pretrained ONNX/vision models, and the Python sidecar tests that need numpy and uvicorn.

Consumers

Matching branches named feat/pumas-v080-api exist 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

MrScripty and others added 30 commits October 10, 2026 17:36
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>
MrScripty and others added 4 commits November 2, 2026 16:27
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>
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ee9a50ea-5897-4ff9-b1fa-970eaa635035
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 2/5

[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

  1. P1 Shortened embedding batches fail ▶
  2. P1 Security Bearer credentials can escape redaction ▶
  3. P2 Download updates can move backward ▶

Summary

Adds typed consumer operations, served-model discovery, decision and mixed text/image embeddings. Replaces desktop validators and adds retained-download recovery.

  • Model callers can discover served models and request more kinds of work.
  • Desktop updates report rejected messages and reload the latest view data.
  • Users can resolve some failed downloads while keeping matching local files.
  • Acquired files follow one checksum policy through installation and later use.
  • Desktop screens show download and deletion failures with clearer recovery details.
  • The release uses the updated dependency versions and matching attribution records.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Consumer request] --> B[Resolve model and capability]
  B --> C[Provider]
  C -->|Success| D[Check and shape result]
  C -->|Failure| E[Redact failure reason]
  D --> F[Typed response]
  E --> F
Loading

Reviews (1) · Last reviewed commit: "docs(release): state the llama.cpp build..." · Reviewed by Greptile

Comment on lines +501 to 507
let vector = match request.options {
OperationOptions::Embeddings {
dimensions: Some(d),
} => truncate_embedding(vector, d).ok_or(invalid)?,
_ => vector,
};
width = Some(vector.len());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Comment on lines +38 to +45
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") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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.

Comment on lines +125 to +126
void api.list_model_downloads().then((result) => {
if (!cancelled) applyDownloadSnapshot(result.downloads.map(projectDownloadProgress));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

MrScripty and others added 3 commits November 2, 2026 20:54
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>
@MrScripty MrScripty changed the title feat: v0.8.0 consumer API, desktop contract validation and dependency updates feat: v0.8.0 consumer API, desktop contract validation and release version Nov 3, 2026
MrScripty and others added 10 commits November 2, 2026 21:44
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>
@MrScripty
MrScripty merged commit 5dab1af into main Nov 3, 2026
51 of 52 checks passed
@MrScripty
MrScripty deleted the feat/v080-consumer-api branch November 3, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant