[upstream-sync] Merge block/buzz 39ce3dfc3..db7e84d4f (7 commits) - #10
Conversation
## Context Pocket TTS currently offers bundled reference voices. People also need a local, private way to add a voice without sending audio to a cloud service. ## Summary Add a Pocket voice import flow to Voice settings. Buzz opens the native file picker, decodes common audio formats in the reusable `buzz-voice` crate, canonicalizes the selected audio, stores it under a content-derived identity in app data, selects it, and lets the user delete it later. ## Changes - Accept WAV, M4A, MP3, FLAC, OGG, and AIFF files between 2 and 30 seconds, including multichannel sources. - Decode and downmix accepted audio to canonical mono 32 kHz PCM16 WAV before hashing and storage. - Store imported voices behind stable `pocket:imported:<sha256>` identities and content-addressed files. - Keep absolute file paths inside the native process and expose only voice metadata to React. - Include imported voices in Pocket preview and live huddle playback. - Add Add voice and delete controls while preserving the bundled Pocket voice catalog. - Fall back to Mary when the selected imported voice is deleted. - Keep durable import, selection, and deletion successful when a live TTS worker acknowledgement is delayed. - Preserve bundled voices when optional import metadata is unreadable and keep failed deletion retryable. ## Related issue None found. ## Testing Production decoding was exercised with WAV, M4A with AAC, MP3, FLAC, OGG Vorbis, and AIFF fixtures. Each format canonicalized to mono 32 kHz PCM16 WAV. Manual validation in the combined daily-driver build covered native-picker import, Preview, live-huddle playback, deletion, and Mary fallback. ## Screenshots The Voice settings card preserves the bundled Pocket catalog and adds the local Add voice action.  ## Reviewer-reproducible examples Create common-format fixtures and run them through the production importer: ```bash . ./bin/activate-hermit fixtures="$(mktemp -d)" ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=3" -ac 2 -ar 44100 "$fixtures/voice.wav" ffmpeg -hide_banner -loglevel error -i "$fixtures/voice.wav" -c:a aac "$fixtures/voice.m4a" ffmpeg -hide_banner -loglevel error -i "$fixtures/voice.wav" "$fixtures/voice.mp3" ffmpeg -hide_banner -loglevel error -i "$fixtures/voice.wav" "$fixtures/voice.flac" ffmpeg -hide_banner -loglevel error -i "$fixtures/voice.wav" -c:a libvorbis "$fixtures/voice.ogg" ffmpeg -hide_banner -loglevel error -i "$fixtures/voice.wav" -c:a pcm_s16be "$fixtures/voice.aiff" BUZZ_VOICE_IMPORT_TEST_DIR="$fixtures" \ cargo test -p buzz-voice imports_common_audio_format_fixtures -- --ignored --nocapture ``` Exercise import persistence, synthesis, deletion, and bundled-voice fallback with an installed Pocket model: ```bash BUZZ_POCKET_MODEL_DIR=/path/to/pocket-model-bundle \ cargo test -p buzz-voice --test pocket_import_audio \ objective_import_synthesis_delete_and_mary_fallback \ -- --ignored --nocapture ``` Exercise the native-picker boundary, selection, preview dispatch, deletion, cancellation, and invalid-file states: ```bash cd desktop pnpm build:e2e pnpm exec playwright test tests/e2e/voice-settings.spec.ts --project=smoke ``` --------- Signed-off-by: John Tennant <jtennant@block.xyz> Signed-off-by: John Tennant <johnmatthewtennant@gmail.com> Signed-off-by: John Tennant <jtennant@squareup.com> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: John Tennant <jtennant@block.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
## Summary - document `Prepare Desktop Release` as the canonical desktop release entry point - describe the frozen candidate, exact-head approval, and true merge-commit contract - document all platform outputs and complete release App/signing configuration - link the release runbook from the README - allow stable reruns to repair the rolling updater manifest after the versioned release has already published ## Release blocker The live repository cannot currently complete this flow: repository settings disable merge commits and the `main` ruleset allows only squash, while `scripts/verify-desktop-release-merge.sh` requires a two-parent merge whose second parent is the approved candidate. Those settings must allow merge commits before a desktop release PR is merged. ## Validation - `bash scripts/test-desktop-release-candidate.sh` - `bash scripts/test-release-ref-contract.sh` - `git diff --check` - verified live repository merge settings, `main` ruleset, release tag ruleset, Actions variable names, and secret names with GitHub API - independent review by Princess Donut; incorporated all findings, including the rolling-manifest retry gap and unsigned Windows labeling Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
chore(release): release Buzz Desktop version 0.5.3
…rification model to NIP-RS (block#2864) ## Summary Amends `docs/nips/NIP-RS.md` with the manual mark-as-unread override layer and includes `docs/formal/nip-rs-unread/`, the bounded exhaustive verification model that preceded and informed the spec. All `ov_*` override state lives in exactly one coordinate per installation. That single constraint is what makes the rest of the amendment small: override state never moves between coordinates, so there is no slot lifecycle to make crash-safe, and the only durability obligation is carry-forward on `client_id` rotation. ## Spec changes (`docs/nips/NIP-RS.md`) - **Non-Goals:** drop the stale line stating mark-as-unread is out of scope; state the `ov_*` durability exception to the best-effort/time-horizon model. - **Reserved Namespace:** `ov_` stem and `esc:` escape marker reserved. Escape on publish (prepend `esc:` to raw IDs beginning with `ov_` or `esc:`), unescape on receive (strip exactly one `esc:`). Bijection, with the pre-amendment backward-compat residual documented as a stated limitation. - **Content Validation:** override entries are collected and validated as a complete logical group *before* any decoding, zero-filling, merging, or canonicalizing. Only two wire shapes are accepted — a complete live three-key group, or an `ov_c:`-only tombstone floor. Any other shape rejects the whole group while retaining the frontier entry; applying the generic per-entry discard rule first is prohibited. - **`d` Tag:** `<slot-id>` is exactly 32 lowercase hexadecimal characters, replacing "a random opaque string" of 1–64 ASCII characters. The fixed shape lets a relay recognize a read-state coordinate structurally from the `d` tag alone, without decrypting anything, and apply per-coordinate protections to it — under the old wording a conforming client could pick a shape that silently forfeits them. Recognizable coordinates are also what let a relay replace superseded versions outright rather than accumulating one retained row per publish, which keeps the coordinate count a full-state load must enumerate near one per installation. Every client designates one **primary** coordinate with a stable `<slot-id>` for the installation's lifetime. All `ov_*` entries, and the frontier entries of the contexts they belong to, MUST live in the primary. Additional coordinates remain legal for frontier volume but MUST NOT carry `ov_*`, which keeps them freely rewritable and freely deletable. - **`t` Tag:** described as a discoverability marker rather than a guarantee of relay-side selectivity. A relay MAY apply tag constraints after its result cap, and `kind:30078` is shared with unrelated application data, so clients MUST apply the tag as a correctness filter locally, MUST NOT infer completeness from a short result, and MUST omit the tag entirely when performing a full-state load. - **Fetching / Full-State Load:** clients implementing the override layer MUST NOT apply a finite `since` filter — an encrypted payload means a relay filter cannot select for override-bearing events, so any event-level window can exclude the only coordinate holding a tombstone floor. Removing `since` is not sufficient: relays MAY cap historical results, MAY cap below the requested `limit`, and emit end-of-stored-events after the capped query, so neither EOSE nor a short page proves completeness. No test against the client's requested `limit` can detect truncation either: the effective cap belongs to the relay, a relay MAY cap below what was requested, and an advertised maximum limit is not necessarily the limit enforced. A full-state load is therefore enumerated on `{"kinds": [30078], "authors": [<pubkey>], "limit": <n>}` with **no tag constraint**. A relay MAY apply tag constraints only after its result cap and withhold the events that fail them, so under a tag-constrained filter the delivered count is not the count the cap selected — a delivered page can be empty while older coordinates still exist below it, and `kind:30078` is arbitrary application data whose `d` tag namespace is open to every application that has written under the user's key. Omitting the tag makes delivery observable; read-state selection moves client-side, where the validation rules already place it. Completeness is then established by enumeration on a strictly decreasing cursor: collect a page, descend on the lowest `created_at` across all delivered events, exhaust that second with a window pinned to it, continue below it, and treat only an empty delivery as complete. Every query carries the same explicit `limit` `n` with `n >= L`. Per-second exhaustion is discharged by comparing the pinned window's delivery against the largest delivery the relay has already demonstrated in the same load, floored at `L = 2` so that the ordinary single-coordinate installation can reach *complete* at all. The comparison fails safe: an inconclusive window reports *cannot prove complete* rather than *complete*, and that verdict is terminal for the load. Because these are addressable events, a coordinate republished mid-load moves *above* the descending cursor while its previous version stops existing, so neither is reachable by any later query. A full-state load is therefore fenced by a live subscription on the same tag-free filter, established — defined as receipt of end-of-stored-events — before the first enumeration query and held unbroken on the same connection for the load's duration. Fence deliveries are collected like enumerated events but do not contribute to the cursor or to the demonstrated-delivery bound. Collection deduplicates coordinates on the full NIP-01 addressable ordering — greatest `created_at`, lowest event id on ties — because an equal-timestamp replacement is legal and is the version the relay retains. A lapsed or reconnected fence makes the load potentially incomplete, and a client MUST NOT publish to its own coordinates during its own load. Five relay behaviours the *complete* verdict rests on are stated as normative conformance preconditions rather than assumptions, because none is verifiable from the responses a client receives: newest-first prefix delivery with lowest-id tie-breaking (what NIP-01 already specifies for `limit`), a non-decreasing effective cap within a load, the floor `L`, push delivery on an open subscription, and a delivery barrier ordering accepted matching events ahead of a query's end-of-stored-events on the same connection. Conditioning *complete* on positive proof of these instead would withdraw the override layer from every client rather than from the non-conforming relays. A client MUST NOT load against a relay it has evidence violates them, and MUST treat any such load as potentially incomplete. A load that is potentially incomplete, or that failed on any relay the client publishes to, MUST NOT authorize canonical compaction, publishing a canonicalized override blob, deleting or abandoning a coordinate, or reporting a mark-read as successful; the client falls back to local state. - **Client-ID Rotation / Orphaned Blob Deletion:** rotation is the only event that changes an override-bearing coordinate. Before deleting or abandoning its previous primary, a client MUST republish the componentwise `max()` of every register that primary holds — every tombstone ceiling included — under its new primary, and MUST confirm acceptance on **every relay** from which the old primary will be deleted or allowed to lapse. Acceptance on one relay does not authorize deletion on another. Frontier-only orphans are deletable unconditionally; an unknown same-`client_id` coordinate is treated as a live carrier until merged. - **Live Subscription and Convergence:** the re-publish trigger and its suppression are evaluated on canonicalized state, so a retained live peer blob the client has already tombstoned cannot trigger an identical write on every replay. - **Manual-Unread Override Layer** (new section): - **Wire encoding:** `ov_s:<ctx>`, `ov_c:<ctx>`, `ov_b:<ctx>` as uint32 siblings in the existing `contexts` map. - **Merge rule:** componentwise `max()` per counter — no new wire merge logic. - **Liveness predicate:** `S > 0 AND F <= B AND S > C`, transcribed from `model.py::override_set_b`. - **Actions:** mark-unread bumps S and captures the effective frontier as B; mark-read bumps C; a natural frontier advance past B deactivates a stale set with no counter update. Every action requires a complete full-state load. At the uint32 ceiling, wrapping and resetting are prohibited: mark-unread is refused, and mark-read completes only if the resulting state has `override_active == false` — otherwise it fails visibly rather than reporting success over a still-live override. - **Tombstone floor:** a dead ever-active register compacts to `RegB(0, max(S,C), 0)` — a single `ov_c:` key. A virgin register is omitted entirely. This blocks counter reuse and the resulting resurrection. - **Mandatory canonical publication:** a protocol requirement, not an optimization. Publishing raw dead registers lets two independently-dead registers from different devices produce a live join. - **Override group co-location rule:** a context's frontier entry and all its `ov_*` siblings MUST travel in the same event, and that event MUST be the primary coordinate. An override-bearing context therefore has exactly one legal destination for its whole group; only frontier-only groups may be distributed across additional coordinates. Grouping is per logical context, never per key. - **Unescape-before-group rule:** the frontier wire key MUST be unescaped to its raw logical context ID before use as group identity. Equal normative weight to atomic grouping. - **Tie policy:** clear-wins is MUST. The tie verdict is not encoded on the wire, so a selectable policy makes two conforming clients diverge permanently on both the unread verdict and the canonical wire form. - **Override State Durability:** `ov_*` entries are exempt from age pruning and budget eviction permanently, and durability is defined over retrievable logical state — the containing event must stay reachable and the load must establish completeness, not merely retain keys. There is no safe finite GC horizon. - **Bounds and budget:** byte/key analysis at both small-counter and uint32-maximum values. Confining `ov_*` to one blob makes its plaintext budget a hard lifetime ceiling on ever-overridden contexts — roughly 600 tombstones at the worst-case ~54 bytes against 32 KiB, ~730 at the common ~45 bytes, ~199 simultaneously live overrides at ~164 bytes. At the ceiling a client MUST refuse mark-unread and MUST NOT split override state, drop floors, or publish a truncated override set. Same policy shape as counter exhaustion: visible failure, never silent degradation. - **Verification artifact:** `docs/formal/nip-rs-unread/`. The model is a broader predecessor of this NIP: its `split_blob_into_slots` permits override groups in any slot, so verified atomicity covers every arrangement this NIP allows, but the converse does not follow. The model does not verify the single-primary rule, the completeness procedure, the relay conformance requirements or the mutation fence, or carry-forward; malformed-group wire validation is likewise normative but outside verified scope. - **Abstract / Non-Goals / Backwards Compatibility:** the absolute "no relay-side logic" and "no relay behavior changes" claims are narrowed to what remains true — no new event kind, no new wire message, no relay-stored read-state logic — with the override layer's relay conformance contract named as the exception. Frontier sync and clients that skip the override layer are unaffected on any relay. ## Verification model (`docs/formal/nip-rs-unread/`) Four Python files constituting a bounded exhaustive verification model for the override layer's register algebra. **What it does:** constructs a toy universe — 2–3 devices, 2 channels, every action that can happen (mark-unread, mark-read, late/duplicate syncs, app reinstall, storage compaction) — and brute-forces every reachable ordering (14,258 BFS states; 672-point deep-history parameter cube; 9-mutant harness over ~45,000 merge pairs). After each world-state it asks: did all devices converge? Did any unread flag get resurrected after being cleared, or vanish while live? **What it found and fixed:** 1. **Killed candidate A.** The model produced a concrete kill sequence: an old client that doesn't know about the new field rewrites its read-state blob and silently erases unread flags. That witness is why the spec uses candidate B (two counters that only count up, plus a snapshot) instead. 2. **Candidate B passes everything.** All delivery orders converge; the frontier high-water mark never regresses; duplicated/replayed syncs are harmless; old clients can't destroy it; compaction never resurrects a dead unread or drops a live one, including cleanup-followed-by-weeks-late-stale-sync and tombstone-landing-on-unrelated-live-state corner cases. 3. **Caught a second real bug late.** Two devices each publishing "this unread is cleared" could, on merge, reactivate it. The fix (canonicalize before publishing) is a mandatory rule in the spec; the model re-checks it across ~45,000 merge pairs. **Scope and caveats:** bounded to 2–3 devices and 2 channels. Can't prove the infinite case. `NOTE.md` documents the exact verification scope and the gap between the model's `split_blob_into_slots` generality and the single-primary rule the spec adds on top. **Why it's in the repo:** the spec asserts "verified by bounded exhaustive model checking." Keeping the artifact in-repo means anyone who later amends the merge/compaction rules can `python3 exhaustive.py && python3 mutation.py` (deterministic, exit 0) and confirm the guarantees hold. Without it the spec claims a proof nobody can check. ## Diff scope `docs/nips/NIP-RS.md` — spec amendment, zero product code. `docs/formal/nip-rs-unread/{NOTE.md,model.py,exhaustive.py,mutation.py}` — bounded exhaustive verification model, zero product code. `.gitignore` — `__pycache__/` and `*.pyc` entries for the model directory. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary - validate desktop release candidates before merge and keep the repository squash-only - tag the squash commit only after proving frozen-base parent and complete-tree identity with the validated PR head - accept either an exact-head approval or the durable Default-ruleset bypass record as release authorization - remove the unusable App-backed preparation workflow; retain `just release-desktop` ## Ruleset follow-up After this PR merges, update Default ruleset `13596885` to: - enable strict required status checks - dismiss stale reviews on push and require approval after the last push - require the integration-bound `Desktop Release Candidate` check The next desktop release should be cut only after that settings update. ## Verification At commit `d8c254db427eedbcffac1a6e078e90d1d0f5e151` with a clean worktree: - `scripts/test-release-ref-contract.sh` - `scripts/test-desktop-release-candidate.sh` - `bash -n scripts/verify-desktop-release-merge.sh scripts/prepare-desktop-release.sh scripts/test-release-ref-contract.sh` - `git diff --check` The bypass test fixture is the captured rule-suite shape from real squash merge PR block#2864 / suite `3520068134`. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Range 39ce3df..db7e84d. Clean merge — no conflicts. Upstream content: - feat(desktop): import local Pocket voices (block#3259) — new buzz-voice `imported` module, Tauri `import_pocket_voice`/`delete_pocket_voice` commands, VoiceSettingsCard UI. - fix(release): immutable desktop release operability (block#3943, block#3965) — prepare-desktop-release.yml replaced by desktop-release-candidate.yml, squash-safe tagging, new bypass-authorization jq filter and fixture. - Revert of the 0.5.3 desktop version bump (block#3960, block#3944) — desktop is back at 0.5.2. - docs(nips): NIP-RS manual-unread override layer plus a formal model under docs/formal/nip-rs-unread/ (block#2864). Fork-local patch sites upstream touched, both benign: - release.yml — one-line `if:` relaxation on the assemble-manifest latest.json upload, away from every fork marker. All 15 FORK-LOCAL hunks intact, including the job-result asserts that make the fork's auto-update manifest publishable. - desktop/src-tauri/src/lib.rs — two command registrations, nowhere near the fork's `mod relay_allowlist;`. Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>
…atch Upstream's 0.5.3 range bumped the root Cargo.lock's mesh-llm pin to tag=v0.74.0 (e60b2fe4), which is what desktop/src-tauri/Cargo.lock already pinned. The two locks now name the same rev, so ci.yml's fork-local "resolve the rev from the desktop lock, not the root one" patch resolves identically either way and is currently a no-op. That is exactly the shape AGENTS.md warns about — a patch that merges cleanly and quietly stops doing anything — so record it rather than let a future sync read the stale v0.73.1/f455d493 revs, fail to find them, and conclude the patch is obsolete. Keep the patch. Desktop is excluded from the root workspace, so the two locks resolve independently; the convergence is a coincidence of this one bump, not upstream adopting the fix. The next bump that moves one lock and not the other re-breaks the root-lock version, and the build step still fetches the desktop manifest. Comments and the AGENTS.md patch-table row only — no behaviour change. Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>
Not auto-merged — tripwire 3The daily sync merges itself when every check is green and no tripwire fires.
I changed the The decision a human is being asked to confirm: upstream's mesh-llm bump made The alternative reading is that a no-op patch is dead weight and should go. I do Steps 6 and 8 of the runbook are consistent here rather than in tension: step 6 Everything else about this sync is routine: clean merge, both parents intact, no Merge with a merge commit, not squash. |
There was a problem hiding this comment.
Pull request overview
Daily upstream sync merging block/buzz range 39ce3dfc3..db7e84d4f into the fork, bringing in upstream desktop Pocket voice import support, updated desktop release plumbing (squash-merge based candidate verification + ruleset-bypass authorization), and new formal docs for NIP-RS unread modeling.
Changes:
- Added Pocket voice import/delete support end-to-end (Rust Tauri commands + desktop UI + E2E mocks/tests) and introduced
buzz-voiceimported-voice library support. - Updated desktop release verification and contract tests to support squash-merge release candidates and authorized ruleset bypasses.
- Added a formal Python model + mutation harness for NIP-RS manual-unread semantics, and minor repo hygiene/docs updates.
Reviewed changes
Copilot reviewed 32 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/verify-desktop-release-merge.sh | Updates desktop release merge verification for squash merges; adds ruleset-bypass authorization path. |
| scripts/test-release-ref-contract.sh | Extends release contract assertions and adds bypass-filter fixture + mutation tests. |
| scripts/prepare-desktop-release.sh | Updates release PR instructions to require squash merge and candidate check. |
| scripts/fixtures/desktop-release-rule-suite-bypass.json | Adds fixture for GitHub ruleset bypass suite validation. |
| scripts/desktop-release-bypass-authorized.jq | Adds jq predicate to validate a bypass came from the Default ruleset pull_request rule. |
| RELEASING.md | Updates desktop release process documentation to match new candidate + squash-merge flow. |
| README.md | Adds link to releasing documentation. |
| docs/formal/nip-rs-unread/mutation.py | Adds mutation harness for candidate B unread model. |
| docs/formal/nip-rs-unread/model.py | Adds bounded exhaustive model for two candidate unread encodings (A and B). |
| desktop/tests/helpers/bridge.ts | Extends mock bridge options for Pocket voice import test outcomes. |
| desktop/tests/e2e/voice-settings.spec.ts | Adds E2E coverage for importing/selecting/deleting Pocket voices and error/cancel handling. |
| desktop/src/testing/e2eBridge.ts | Implements E2E mock handling for listing/importing/deleting Pocket voices. |
| desktop/src/features/settings/ui/VoiceSettingsCard.tsx | Adds “Add voice” and delete flow UI for Pocket imported voices (with confirmation dialog). |
| desktop/src-tauri/src/lib.rs | Registers new Tauri commands for importing and deleting Pocket voices. |
| desktop/src-tauri/src/huddle/tts_voice_transition.rs | Updates voice-path resolution to support imported voices via absolute paths. |
| desktop/src-tauri/src/huddle/tts_voice_import.rs | Adds native file picker adapter and library wiring for imported Pocket voices. |
| desktop/src-tauri/src/huddle/tts_settings.rs | Extends voice registry to include imported voices; resolves imported voice file paths for runtime. |
| desktop/src-tauri/src/huddle/pipeline.rs | Avoids repeated imported-voice resolution during hot-start polling; uses new voice reference resolver. |
| desktop/src-tauri/src/huddle/mod.rs | Registers the new tts_voice_import module. |
| desktop/src-tauri/Cargo.lock | Updates desktop lockfile for new buzz-voice dependencies (symphonia, sha2, etc.). |
| crates/buzz-voice/tests/pocket_import_audio.rs | Adds ignored-on-by-default integration test covering import/synthesis/delete + fallback behavior. |
| crates/buzz-voice/src/lib.rs | Exposes new imported module. |
| crates/buzz-voice/src/imported.rs | Implements imported voice validation/canonicalization/storage (hashing, decoding, registry). |
| crates/buzz-voice/Cargo.toml | Adds dependencies needed for import pipeline (atomic write, symphonia codecs, hashing, tempfile). |
| Cargo.lock | Updates workspace lockfile for new buzz-voice deps. |
| AGENTS.md | Updates fork-local patch note for ci.yml mesh-llm pin explanation (comment-only). |
| .gitignore | Ignores Python cache files introduced by new formal model docs. |
| .github/workflows/release.yml | Adjusts rolling latest.json upload gating logic. |
| .github/workflows/prepare-desktop-release.yml | Removes deprecated workflow in favor of new desktop release candidate validation. |
| .github/workflows/desktop-release-candidate.yml | Adds PR check that validates desktop release candidates on version-bump/* branches. |
| .github/workflows/ci.yml | Updates fork-local comment explaining the mesh-llm rev resolution behavior. |
| .github/workflows/auto-tag-on-release-pr-merge.yml | Updates tagging workflow inputs to use merge commit SHA and pass merger identity. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def mutant_m5(): | ||
| """M5: values outside uint32 range must fail legacy sanitization.""" | ||
| blob = {"v": 1, "client_id": "x", "contexts": { | ||
| "ov_s:c0": 4294967296, | ||
| "ov_c:c0": 0, | ||
| "ov_b:c0": 10, | ||
| }} | ||
| sanitized = legacy_sanitize_blob(blob) | ||
| if "ov_s:c0" in sanitized["contexts"]: | ||
| return [] | ||
| return [("overflow-rejected", blob["contexts"]["ov_s:c0"], | ||
| sanitized["contexts"])] |
| fn atomic_write_restricted(path: &Path, payload: &[u8]) -> Result<(), String> { | ||
| let resolved = fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()); | ||
| let mut file = AtomicWriteFile::open(&resolved) | ||
| .map_err(|error| format!("open {} for atomic write: {error}", resolved.display()))?; | ||
| #[cfg(unix)] | ||
| { | ||
| use std::os::unix::fs::PermissionsExt; | ||
| file.set_permissions(fs::Permissions::from_mode(0o600)) | ||
| .map_err(|error| format!("set {} permissions: {error}", resolved.display()))?; | ||
| } | ||
| file.write_all(payload) | ||
| .map_err(|error| format!("write {}: {error}", resolved.display()))?; | ||
| file.commit() | ||
| .map_err(|error| format!("commit {}: {error}", resolved.display())) | ||
| } |
|
✅ CI is green — all 35 checks passed. Safe to review and merge. Merge with a merge commit, not squash — the sync relies on upstream's individual commits staying in history so the next run can tell what is already merged (see |
CI green — ready for a human to merge32 pass, 3 skipping, 0 fail. Longest legs: Windows Rust 27m18s, Mobile 25m45s, Desktop E2E Relay 22m39s. Skipped are the expected ones: Notably This PR is blocked only on the tripwire above, not on anything red. When you merge:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 35 changed files in this pull request and generated no new comments.
Suppressed comments (2)
docs/formal/nip-rs-unread/mutation.py:269
mutant_m5is intended to fail only when legacy sanitization incorrectly accepts a uint32 overflow. The current condition is inverted and reports a violation when the overflow is correctly rejected.
def mutant_m5():
"""M5: values outside uint32 range must fail legacy sanitization."""
blob = {"v": 1, "client_id": "x", "contexts": {
"ov_s:c0": 4294967296,
"ov_c:c0": 0,
"ov_b:c0": 10,
}}
sanitized = legacy_sanitize_blob(blob)
if "ov_s:c0" in sanitized["contexts"]:
return []
return [("overflow-rejected", blob["contexts"]["ov_s:c0"],
sanitized["contexts"])]
docs/formal/nip-rs-unread/mutation.py:182
mutant_m3returns a violation when the model correctly distinguishes SET vs CLEAR ties (S==C). The check is inverted: violations should be returned only when the two tie policies produce the same verdict.
def mutant_m3():
"""M3: tie policy is load-bearing — S==C must produce different verdicts.
Not a DeviceB mutation; tests the model function directly."""
reg = RegB(s=1, c=1, b=10)
frontier = 10
v_clear = override_set_b(reg, frontier, CLEAR)
v_set = override_set_b(reg, frontier, SET)
if v_clear == v_set:
return []
return [("tie-distinguishable", v_clear, v_set, reg, frontier)]
Daily upstream sync. Merge with a merge commit — not squash. A squash drops
the second parent, leaves the merge base stale, and every later sync re-resolves
the same conflicts from it. This fork has already been repaired by hand once for
exactly that (
3ce7c8adc).Range
39ce3dfc3..db7e84d4f— 7 commits. Clean merge, no conflicts.Merge commit has 2 parents (
b2a21fdc2+db7e84d4f);git rev-list --count upstream/main ^HEADis 0.What changed upstream
Desktop — Pocket voice import (block#3259). New
buzz-voiceimportedmodule(730 lines) plus an audio test, Tauri
import_pocket_voice/delete_pocket_voicecommands,tts_voice_import.rs, and VoiceSettingsCard UIwith a new e2e spec.
Release plumbing (block#3943, block#3965).
prepare-desktop-release.ymldeleted andreplaced by
desktop-release-candidate.yml; desktop tagging made squash-safe(
target_shanowmerge_commit_sha, nothead.sha); newdesktop-release-bypass-authorized.jqfilter with a fixture and mutation tests.Version revert (block#3960, block#3944). The 0.5.3 desktop bump was reverted — the
desktop crate is back at 0.5.2. Not a fork action; nothing to reconcile.
Docs (block#2864). NIP-RS manual-unread override layer, plus a formal model under
docs/formal/nip-rs-unread/(~3200 lines of Python model/mutation/exhaustivecheckers).
Conflicts
None — clean merge.
Per
AGENTS.md, a clean merge is not a correct merge, so I read the diff atevery fork-local patch site. Upstream touched exactly two of them:
release.ymlassemble-manifest"Upload latest.json to rolling release last" step dropped itsenv.already_published != 'true'gateFORK-LOCALhunks intact —RELEASE_REPOguards,BASE/BUZZ_UPDATER_ENDPOINTderiving fromgithub.repository, and the job-result asserts that replaced the unreachable-ge 3count. Verified the fork's mutual-exclusion assert between the signed and unsigned macOS lanes still reads correctly against upstream's new surroundingsdesktop/src-tauri/src/lib.rsinvoke_handlermod relay_allowlist;(line 32)Spot-checked the untouched patches too:
tauri.conf.jsonproductNameis stillBitcoinMarkets(version correctly follows upstream back to0.5.2), theInfo.plist/ xcconfig / gradle brand strings survive,KIND_STARKNET_WALLET_BINDINGis still30900with itsis_parameterized_replaceableassert, and both relay allowlists are in place.migrations/is unchanged — still 28 files, matching themigrations.len()assertion. No new migration in this sync.
One fork patch went quietly no-op
Upstream's bump moved the root
Cargo.lockmesh-llm pin totag=v0.74.0(
e60b2fe4) — which is whatdesktop/src-tauri/Cargo.lockalready pinned. Thetwo locks now name the same rev, so
ci.yml's fork-local "resolve the rev fromthe desktop lock, not the root one" patch resolves identically either way.
This is the failure mode
AGENTS.mdwarns about: a patch that merges cleanly andstops doing anything, whose comment now cites revs (
v0.73.1/f455d493) thatno longer appear in either lockfile — so the next sync could read it, fail to
find them, and delete the patch as obsolete.
Kept the patch, corrected the record (
a43a4deb2, comments + theAGENTS.mdrow only, no behaviour change). Desktop is excluded from the root workspace, so
the locks still resolve independently; the convergence is a coincidence of this
one bump, not upstream adopting the fix. The next bump moving one lock and not
the other re-breaks the root-lock version.
Verification
Run locally on macOS at
a43a4deb2:cargo fmt --all --checkcargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all --checkcargo clippy --workspace --all-targets -- -D warningscargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warningscargo metadata --lockedscripts/test-release-ref-contract.shscripts/test-mobile-worktree-overrides.shjust test-unitpnpm check:px-textpnpm exec tsc --noEmitTwo extra gates beyond the standard list, because upstream landed ~400 lines of
desktop TS: the px-text guard and the typecheck. Both clean.
test-release-ref-contract.shis worth calling out — upstream tightened it inthis range. It now asserts the rolling-updater step is not gated by
already_published, and adds five mutation tests against the new bypass filter.The fork's patched
release.ymlsatisfies the new assertions unchanged.Not run:
flutter analyze— the Hermit Dart is older thanmobile/pubspec.yaml's constraint and fails to resolve, as documented. Relyingon CI. Low risk here: upstream touched no mobile files in this range.
Needs a human look
Nothing blocking. Three things worth knowing:
desktop-release-candidate.ymlruns on every PR tomain,including this one. Its only real step is gated on a
version-bump/head-refprefix, so it no-ops here — but it is a new always-present check.
auto-tag-on-release-pr-merge.ymlchanged upstream (head.sha→merge_commit_sha). That workflow is disabled in this fork by reposetting, so no effect. Flagging only because the sync moved it.
Signed-off-by(bb34bc4d9,6e02e0a90).They are upstream's own and cannot be signed without rewriting their history,
which would destroy the merge base. No DCO app is installed on this fork — the
check listed in
AGENTS.mdbelongs toblock/buzz— and PR [upstream-sync] Sync upstream block/buzz → 2026-07-31 (23 commits) #9 merged thesame way. Both fork-authored commits here are signed.
No wire-format changes. No kind renumbers. No migrations. No fork patch deleted.