Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3e75999
[codex/vc-workflow] fix(stt): refuse quantized Whisper runtime models
m-szymanska Aug 21, 2026
2bf97be
[codex/vc-workflow] fix(stt): satisfy newer Clippy PCM chunk lint
m-szymanska Aug 21, 2026
be6b6c9
[codex/vc-workflow] fix(stt): remove dead q8 dequantization path (PR …
m-szymanska Aug 21, 2026
f377548
[codex/vc-workflow] fix(stt): make fp16 bundles repairable
m-szymanska Aug 21, 2026
4eba1ae
[codex/vc-workflow] docs(stt): document validated fp16 bundles
m-szymanska Aug 21, 2026
38fb2de
[codex/vc-workflow] test(stt): satisfy rust 1.98 clippy
m-szymanska Aug 21, 2026
e0063b7
[codex/vc-workflow] fix(stt): select a valid weights alternative (PR …
m-szymanska Aug 21, 2026
e8f0bf0
[codex/vc-workflow] fix(stt): skip invalid cached snapshots (PR #81 r…
m-szymanska Aug 21, 2026
f064d5f
[codex/vc-workflow] fix(stt): validate safetensors metadata (PR #81 r…
m-szymanska Aug 21, 2026
1f2d0b3
[codex/vc-workflow] fix(stt): reject empty model tensors
m-szymanska Aug 21, 2026
189b51f
[codex/vc-workflow] docs(stt): align final-pass default (PR #81 review)
m-szymanska Aug 21, 2026
6de8128
[codex/vc-workflow] fix(stt): validate release model bundles (PR #81 …
m-szymanska Aug 21, 2026
65df3fd
[codex/vc-workflow] fix(stt): fail closed on invalid model overrides
m-szymanska Aug 21, 2026
7b85f71
[codex/vc-workflow] test(stt): reuse canonical model discovery
m-szymanska Aug 21, 2026
2d34de5
[codex/vc-workflow] fix(stt): expand tilde model overrides (PR #81 re…
m-szymanska Aug 21, 2026
d3b8285
[codex/vc-workflow] fix(stt): validate fat-build weight selection (PR…
m-szymanska Aug 21, 2026
18a04a5
[codex/vc-workflow] fix(stt): expand build-time tilde model paths (PR…
m-szymanska Aug 21, 2026
677d9f1
[codex/vc-workflow] fix(stt): validate benchmark model discovery (PR …
m-szymanska Aug 21, 2026
ba4887d
[codex/vc-workflow] fix(stt): stage model bundle before promotion (PR…
m-szymanska Aug 21, 2026
671785f
[codex/vc-workflow] fix(stt): validate complete fat-build bundles (PR…
m-szymanska Aug 21, 2026
5142c3a
[codex/vc-workflow] test(stt): validate E2E model discovery (PR #81 r…
m-szymanska Aug 21, 2026
94554f5
[codex/vc-workflow] chore(stt): preserve validator trust boundary
m-szymanska Aug 21, 2026
6b15066
[codex/vc-workflow] test(stt): describe E2E bundle validation (PR #81…
m-szymanska Aug 21, 2026
fa89b3c
[codex/vc-workflow] fix(release): quiet invalid cache probes (PR #81 …
m-szymanska Aug 21, 2026
1647a22
[codex/vc-workflow] fix(stt): require Whisper control tokens (PR #81 …
m-szymanska Aug 21, 2026
f780561
[codex/vc-workflow] fix(stt): repair default model as paired assets (…
m-szymanska Aug 21, 2026
40eefa4
[codex/vc-workflow] fix(stt): exclude alignment metadata from model w…
m-szymanska Aug 21, 2026
3723f96
[codex/vc-workflow] fix(release): remove shadowed Whisper weights
m-szymanska Aug 21, 2026
77e18bf
[codex/vc-workflow] fix(msrv): declare Rust 1.88 support floor
m-szymanska Aug 21, 2026
dfa9ec2
[codex/vc-workflow] fix(stt): align model discovery across build and …
m-szymanska Aug 21, 2026
c956250
[codex/vc-workflow] docs(stt): align FP16 compatibility contract
m-szymanska Aug 21, 2026
e368904
[codex/vc-workflow] test(release): cover alternate Whisper weight pro…
m-szymanska Aug 21, 2026
8f41abd
[codex/vc-workflow] test(release): gate Whisper bundle promotion
m-szymanska Aug 21, 2026
66b9ae7
[codex/vc-workflow] fix(stt): expand models root home prefix
m-szymanska Aug 21, 2026
4f2db14
[codex/vc-workflow] test(release): make Whisper fixture hermetic
m-szymanska Aug 21, 2026
976b9ed
[codex/vc-workflow] fix(stt): validate model architecture and cache r…
m-szymanska Aug 21, 2026
12a870b
[codex/vc-workflow] fix(stt): validate complete model compatibility
m-szymanska Aug 21, 2026
3392871
[codex/vc-workflow] fix(stt): harden tokenizer compatibility
m-szymanska Aug 21, 2026
04ff5f0
[codex/vc-workflow] fix(stt): align bundle validation with runtime
m-szymanska Aug 22, 2026
fbf447f
[codex/vc-workflow] fix(stt): close model resource boundaries
m-szymanska Aug 22, 2026
6dad4df
[codex/vc-workflow] fix(stt): enforce runtime window bounds
m-szymanska Aug 22, 2026
cd8cbc9
[codex/vc-workflow] fix(stt): validate decode-time model contracts
m-szymanska Aug 22, 2026
83d13cc
[codex/vc-workflow] fix(stt): bound model width and align e2e discovery
m-szymanska Aug 22, 2026
d95c1e7
[codex/vc-workflow] fix(stt): bound metadata and reuse runtime resolu…
m-szymanska Aug 22, 2026
13174b8
[codex/vc-workflow] docs(setup): align Rust MSRV
m-szymanska Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.debug.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
# CODESCRIBE_TOGGLE_FINAL_PASS=1 # Default: 1 — Use saved-WAV final-pass adjudication when stopping toggle dictation (0 restores preview-only stop path)
# CODESCRIBE_WHISPER_IDLE_UNLOAD_SECS=300 # Default: 300 — Unload idle Whisper engine after this many seconds (0 = explicit keep-warm)
# CODESCRIBE_WHISPER_INITIAL_PROMPT= # Default: unset — Initial prompt hint for Whisper decoding (ignored by ONNX adapter)
# LOCAL_MODEL=whisper-large-v3-turbo-mlx-q8 # Default: whisper-large-v3-turbo-mlx-q8 — Local Whisper model id (HF cache / embedded lookup)
# LOCAL_MODEL=whisper-large-v3-turbo # Default fp16 local Whisper model id
# STT_API_KEY=<your-key> # Default: unset — Cloud STT API key; prefer Settings / macOS Keychain
# STT_ENDPOINT= # Default: unset — Cloud STT API endpoint (when USE_LOCAL_STT=0)
# USE_LOCAL_STT=1 # Default: 1 — Use local Whisper model (vs cloud)
Expand Down
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
# CODESCRIBE_TOGGLE_FINAL_PASS=1 # Default: 1 — Use saved-WAV final-pass adjudication when stopping toggle dictation (0 restores preview-only stop path)
# CODESCRIBE_WHISPER_IDLE_UNLOAD_SECS=300 # Default: 300 — Unload idle Whisper engine after this many seconds (0 = explicit keep-warm)
# CODESCRIBE_WHISPER_INITIAL_PROMPT= # Default: unset — Initial prompt hint for Whisper decoding (ignored by ONNX adapter)
# LOCAL_MODEL=whisper-large-v3-turbo-mlx-q8 # Default: whisper-large-v3-turbo-mlx-q8 — Local Whisper model id (HF cache / embedded lookup)
# LOCAL_MODEL=whisper-large-v3-turbo # Default fp16 local Whisper model id
# STT_API_KEY=<your-key> # Default: unset — Cloud STT API key; prefer Settings / macOS Keychain
# STT_ENDPOINT= # Default: unset — Cloud STT API endpoint (when USE_LOCAL_STT=0)
# USE_LOCAL_STT=1 # Default: 1 — Use local Whisper model (vs cloud)
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ env:
CARGO_TERM_COLOR: always

jobs:
msrv:
name: Rust 1.88 MSRV
runs-on: macos-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Ensure MSRV toolchain on PATH
run: |
rustup toolchain install 1.88.0 --profile minimal --no-self-update
dirname "$(rustup which --toolchain 1.88.0 rustc)" >> "$GITHUB_PATH"

- name: cargo check at MSRV
run: cargo check --workspace --all-targets

format:
name: Format Check
runs-on: macos-latest
Expand Down
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Local Whisper is an explicitly validated FP16/F32 bundle.** Runtime,
Settings download, release scripts, E2E discovery, and the optional fat build
share the same architecture, tokenizer-vocabulary/language, pinned-mel, and
required tensor-name/shape contract. The loader rejects incomplete bundles
before cold model construction; prompt/control token IDs, automatic-language
candidates, layer/context resource bounds, and mapped tensor-name collisions
are validated by the same runtime-owned helpers. Tokenizers cannot emit IDs
without embedding rows; audio context must match the supported 30-second
window; matching state widths are capped at the official Whisper maximum of
1280; decoder context is capped at the supported 448 positions; timestamp
token ranges are validated end to end; mel verification is size-bounded before
hashing; config/tokenizer JSON and vocabulary size are bounded before parsing
or allocation; and surplus tensors are refused before allocation. Quantized payloads and the
legacy Q8 fallback are refused; the old public Q8 identifiers remain
deprecated source-compatibility constants only. Building from source now
declares Rust 1.88 as the minimum supported toolchain.
Warm-cache tokenizer repair now returns immediately when it completes the
installed bundle instead of falling through to redundant network downloads,
and it preserves a valid installed config/weights pair without creating a
weights-sized temporary copy. STT benchmarks now use the production model
resolver, including validated Hugging Face cache snapshots.

- **Supervisor findings own transcript-quality categories.** Engine catalog
`codescribe-supervisor-findings/v1` (`core/quality/supervisor.rs`) names
every issue class the tree already had — contract forbiddens, clock-lie,
Expand All @@ -77,7 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Voice Lab three-judge emits those findings. WER stays a footnote of
proposal agreement, not accuracy.


- **Layer 1 applies aligned same-utterance wording.** When live Apple and
the Whisper window share most words, Layer 1 now substitutes those
spans instead of discarding the repair at the 50% change cap. Unrelated
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [".", "core", "bridge"]
[workspace.package]
version = "0.14.1"
edition = "2024"
rust-version = "1.88"
authors = ["Vetcoders <hello@vetcoders.io>"]

[workspace.dependencies]
Expand All @@ -20,6 +21,7 @@ tracing = "0.1"
name = "codescribe"
version = "0.14.1"
edition = "2024"
rust-version.workspace = true
description = "Speech-to-text for macOS — SwiftUI front-end over a Rust engine (UniFFI bridge)"
authors = ["Vetcoders <hello@vetcoders.io>"]
license = "FSL-1.1-ALv2"
Expand Down Expand Up @@ -141,6 +143,7 @@ tempfile = "3"
mockito = "1"
serial_test = "3"
hound = "3.5"
tokenizers = "0.22"

[lints.rust]
# Allow unexpected_cfgs from objc crate's msg_send! macro (uses cargo-clippy cfg internally)
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ bump-major:
# gate: check class=static ci=no -- cargo fmt, prettier, clippy, semgrep, validate-envs, validate-gates; executes ZERO tests
# gate: lint class=static ci=no -- cargo fmt --check + clippy on the workspace + verify-swift-format; no tests
# gate: semgrep class=static ci=no -- semgrep scan --config auto (semgrep.yml runs semgrep directly, not this target)
# gate: verify class=hermetic ci=yes -- the workspace test set + doctests + env registry + this ledger; the command rust.yml runs
# gate: verify class=hermetic ci=yes -- workspace tests, doctests, model-promotion regression, env registry + this ledger; rust.yml runs it
# gate: verify-canaries class=hermetic ci=no -- claim-vs-execution canaries that read repo files only (scripts/canaries.sh); each row is born from a named incident
# gate: verify-swift-format class=static ci=no -- swift-format lint --strict over macos/Codescribe + macos/CodescribeTests; skips the generated UniFFI binding; no Swift tests (that is test-swift)
# gate: smoke-canaries class=operator ci=no -- verify-canaries + host rows: dist inputs, appcast feed, live-store purity, Sparkle key parity, keychain domain cleanliness (scripts/canaries.sh --host)
Expand Down Expand Up @@ -1119,6 +1119,8 @@ verify:
echo "=== Verify (hermetic: doctests) ==="; \
CODESCRIBE_NO_EMBED=1 CODESCRIBE_DISABLE_KEYCHAIN=1 \
cargo test --workspace --doc; \
echo "=== Verify (Whisper model promotion) ==="; \
bash scripts/tests/download-model-test.sh; \
echo "=== Verify (env registry) ==="; \
bash scripts/validate-envs.sh; \
echo "=== Verify (gate ledger) ==="; \
Expand Down
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Codescribe can load custom MCP servers from `~/.codescribe/mcp.json`. That keeps

- **macOS 14+** (Sonoma or later)
- **Apple Silicon** (M1, M2, M3, or later)
- **Rust Toolchain** (1.85+ with edition 2024 support)
- **Rust Toolchain** (1.88+; the workspace declares this MSRV)
Comment thread
m-szymanska marked this conversation as resolved.

### Install from Source

Expand Down Expand Up @@ -327,8 +327,8 @@ qube-daemon --help
Codescribe uses **whisper-large-v3-turbo** (mlx-community, fp16):

- 4-layer turbo architecture (vs 32 layers in full model)
- fp16 weights (~1.6 GB): loads without q8→F32 dequantization, roughly
halving cold start; the legacy q8 model stays supported as a fallback
- fp16 weights (~1.6 GB): load without q8→F32 dequantization; quantized Whisper
payloads are rejected before engine load
- ~10x faster than whisper-large-v3
- Metal GPU acceleration

Expand All @@ -342,13 +342,43 @@ Runtime resolution when Whisper is not embedded:

1. `CODESCRIBE_MODEL_PATH` environment variable
2. `~/.codescribe/models/whisper-large-v3-turbo/` (fp16 default)
3. Hugging Face cache snapshots for `mlx-community/whisper-large-v3-turbo`
4. Legacy fallback: `~/.codescribe/models/whisper-large-v3-turbo-mlx-q8/` or
`LibraxisAI/whisper-large-v3-turbo-mlx-q8` snapshots
3. A complete Hugging Face snapshot configured by repo id, followed by the
default `mlx-community/whisper-large-v3-turbo` snapshot
Comment on lines +345 to +346

The mlx-community repo ships only `config.json` + `weights.safetensors`;
the download paths compose `tokenizer.json` + `mel_filters.npz` from the
legacy repo (both files are quantization-independent).
the download paths compose `tokenizer.json` from the matching official OpenAI
Transformers repo and `mel_filters.npz` from a checksum-pinned OpenAI Whisper
asset. The resulting directory is validated as loader-compatible fp16/fp32
before resolution.
The shared bundle validator parses the config, applies bounded architecture
resource limits, requires every runtime prompt/control token to fit the
configured vocabulary, and uses the same automatic-language candidate logic as
the decoder. It verifies the pinned mel SHA-256 and validates every
required Whisper tensor name and shape plus the complete safetensors tensor
table, exact consumed tensor set, bounded alignment metadata, dtype allowlist,
mapped-name uniqueness, offsets, and file length. The
disk loader applies this complete gate before mmap or model construction.
Config and tokenizer JSON are size-bounded before parsing, and vocabulary size
is capped at the largest supported official Whisper vocabulary.
Downloads and warm-cache
copies are written to `.partial` files and promoted only after per-file
validation; an invalid destination is repaired on the next Download action
instead of being accepted as complete. Config validation requires the complete
MLX Whisper architecture
used by the loader (including matching audio/text state widths and compatible
attention heads, a decode context that leaves room for output, and broad layer
count safety fences). Matching audio/text state widths are bounded to the
official Whisper range `4..=1280` before quadratic model allocations. Decoder context is bounded to the supported `5..=448`
range before its quadratic causal mask is allocated. Audio context must equal the 1500 positions consumed by
the supported 30-second Whisper window; shorter contexts would silently truncate
audio. The pinned mel filterbank is size-checked and hashed through a bounded
stream before use; missing dimensions are never replaced
with runtime defaults.
Warm-cache repair checks older snapshots when the newest config, weights, or
tokenizer is invalid, and returns as soon as the composed destination validates,
preserving an already-valid installed model pair when only a smaller artifact
needs repair and avoiding a weights-sized temporary copy. Optional timestamp
tokens are accepted only as a complete contiguous 20 ms range from 0.00 to 30.00 seconds.

`CODESCRIBE_EMBED_EMBEDDER=1` is an explicit fat/debug path that compiles MiniLM into Rust artifacts. Normal builds resolve MiniLM from the signed app resource or HF cache. `CODESCRIBE_NO_EMBED=1` disables every optional binary embed; Silero remains embedded.

Expand Down
1 change: 1 addition & 0 deletions bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "codescribe-ffi"
version.workspace = true
edition = "2024"
rust-version.workspace = true
authors = ["Vetcoders <hello@vetcoders.io>"]
description = "UniFFI bridge exposing the codescribe engine (agent streaming, STT, config) to Swift"

Expand Down
8 changes: 8 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "codescribe-core"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors = ["Vetcoders <hello@vetcoders.io>"]
license = "FSL-1.1-ALv2"
description = "Core library for Codescribe (audio, STT, quality pipeline)"
Expand All @@ -18,6 +19,10 @@ path = "lib.rs"
name = "codescribe-stt-sidecar"
path = "bin/codescribe-stt-sidecar.rs"

[[bin]]
name = "codescribe-whisper-validate"
path = "bin/codescribe-whisper-validate.rs"

[features]
default = []
offline_eval = []
Expand Down Expand Up @@ -102,8 +107,11 @@ mockito = "1"
serial_test = "3"

[build-dependencies]
anyhow = "1"
dirs = "6"
serde_json = "1"
sha2 = "0.10"
tokenizers = "0.22"

[lints.rust]
# Allow unexpected_cfgs from objc crate's msg_send! macro (uses cargo-clippy cfg internally)
Expand Down
28 changes: 28 additions & 0 deletions core/bin/codescribe-whisper-validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Validate a composed Whisper model with the runtime's canonical contract.

use anyhow::{Context, Result, anyhow};
use codescribe_core::config::models::{
resolve_runtime_whisper_model_path, validate_whisper_model_bundle,
};
use std::path::PathBuf;

fn main() -> Result<()> {
let mut args = std::env::args_os().skip(1);
let command = args
.next()
.ok_or_else(|| anyhow!("usage: codescribe-whisper-validate <model-directory>|--resolve"))?;
if args.next().is_some() {
return Err(anyhow!(
"usage: codescribe-whisper-validate <model-directory>|--resolve"
));
}

if command == "--resolve" {
println!("{}", resolve_runtime_whisper_model_path(None)?.display());
return Ok(());
}

let path = PathBuf::from(command);
validate_whisper_model_bundle(&path)
.with_context(|| format!("invalid Whisper model bundle: {}", path.display()))
}
Loading
Loading