feat: full-platform STT API (v2.3 consensus plan) #1

Open
lukehemmin wants to merge 21 commits from feat/full-platform into main
21 Commits
Author SHA1 Message Date
lukehemmin 7616b87f4c refine: dashboard review fixes - echo removal, model escape, RMS gate, fd leak, mic cleanup
Reviewer feedback: remove AudioContext destination echo, escape model names, add RMS gate to skip silence, close mkstemp fd, clean up mic on WS close. Tests updated for the RMS gate.
2026-08-12 21:44:26 +09:00
lukehemmin 3dfa660503 feat: dashboard UI + key API + realtime decode
Full HTML dashboard (dark theme, vanilla JS, no external deps) served at / and /dashboard with five tabs: system status (admin), file upload-to-transcribe with progress/result/downloads, job history with cancel/result modal, realtime mic demo over the existing WebSocket, and API key create/list.

Backend: POST/GET /v1/keys (admin; raw key returned once, digest-only storage); KeyStore.list_keys(); EngineOwner.emit_hypothesis is now a real implementation (PCM16 chunk -> WAV -> realtime-lane decode with the single GPU lock) instead of a stub.

Notebook: tunnel cell links /dashboard and smoke-checks the HTML.

+ 6 tests (dashboard public HTML, key create/list/scope, WAV header, emit_hypothesis cleanup). 153 tests pass, ruff clean, JS syntax verified with node --check.
2026-08-12 21:41:42 +09:00
lukehemmin 440e947d47 refine: glossary - reject empty keys, guard non-dict, schema field
Reviewer feedback: (1) CLI --glossary rejects empty key/value so an empty pattern cannot corrupt text; (2) worker guards job.options glossary/post_correction with isinstance dict; (3) TranscribeOptions gains a dedicated glossary dict field; (4) rules.py documents the word-boundary/Hangul adjacency limitation.
2026-08-12 21:07:56 +09:00
lukehemmin 6bb89eb51f fix: solve vLLM->BLM misrecognition via glossary postprocessing
The bench and CLI kept emitting BLM for vLLM. Three layers:

1. rules.py: add default rule BLM -> vLLM (word-boundary, case-insensitive) so the default post_mode=rules path restores it everywhere.

2. benchmark/runner.py: the bench never ran postprocessing - it measured raw engine text, so entity retention (66.7%) never reflected rules/glossary. _transcribe_clip now builds Segments and runs run_postprocess(settings, glossary) before metrics; manifest top-level glossary {pattern: replacement} is supported and recorded in run_config (post_mode/glossary).

3. glossary plumbing: CLI transcribe gains --glossary KEY=VALUE (repeatable, parsed + validated); BatchPipeline.run accepts glossary= and passes it to run_postprocess; the in-proc worker forwards job.options.glossary or post_correction.

Notebook: transcribe cells pass --glossary BLM=vLLM, bench manifest carries glossary, postprocess section documents rules/glossary/hotword.

+ 9 unit tests (BLM rule, boundary/case behavior, bench postprocess + glossary entity retention). 147 tests pass, ruff clean.
2026-08-12 21:06:42 +09:00
lukehemmin f171ce4992 refine: tunnel verify - tighter retries, stderr-first on 000, announce DoH fallback
Reviewer feedback: cut worst-case stall from ~200s to ~60s (urllib 2x, DoH 4x with max-time 10); when curl reports 000 (connect failure) prefer stderr for the diagnostic; print an explicit note when falling back to DoH after a resolved-DNS urllib failure.
2026-08-12 20:58:44 +09:00
lukehemmin e34322cc73 feat: colab notebook - DNS diagnosis + DoH-bypass tunnel verification
Colab VM DNS fails to resolve fresh trycloudflare hostnames (Name or service not known) while the tunnel itself works from any real browser. Verification now: 1) diagnoses VM DNS via socket.gethostbyname, 2) uses urllib when DNS resolves, 3) falls back to curl --doh-url (Cloudflare DNS over HTTPS) to bypass the VM resolver, 4) otherwise prints the docs URL with an explicit note that a minted URL means the tunnel is already connected. Troubleshooting section documents the DNS limitation.
2026-08-12 20:57:37 +09:00
lukehemmin 96b53f889c refine: notebook - tolerant import verify, keep last err in tunnel check
Reviewer feedback: wrap the install-cell import verification in try/except so a walk-up failure cannot crash the cell (previously always-succeeding), and keep the last exception detail in the tunnel external-verification failure message.
2026-08-12 20:49:36 +09:00
lukehemmin 5b8576e2a3 fix: colab notebook - register src on kernel sys.path
Colab run 5 failed at cell 12 (import luke_scribe.config) because hatchling editable installs write a .pth pointing at src/ that the interpreter only reads at startup; a long-running Colab kernel started before pip install -e never sees it. Subprocesses (CLI) work, but kernel-side imports fail.

Fix: install cell now walks up to the repo root and inserts src/ into the kernel sys.path, verifying with import luke_scribe.config. Cell 12 has a defensive guard of the same kind. Tunnel external verification now retries up to 6x2s for DNS propagation. Troubleshooting section documents the issue.
2026-08-12 20:48:08 +09:00
lukehemmin 44b7211653 refine: colab tunnel cell - skip poll when cloudflared missing, softer verify message
Reviewer feedback: guard the 120s URL poll with shutil.which(cloudflared) so the failure case returns immediately; merge import re into the cell top import; note Cloudflare browser-check interstitials can fail urllib verification even when the link works in a real browser.
2026-08-12 19:42:14 +09:00
lukehemmin 0ed2ea6160 feat: colab notebook - cloudflare tunnel for external dashboard access
Server cell sets LUKESCRIBE_TUNNEL=cloudflare so the app built-in CloudflareTunnel (trycloudflare quick tunnel) starts alongside the server. After health+auth checks the cell polls server.log for the trycloudflare URL, prints it with /docs dashboard and /health links, and verifies external reachability.

cloudflared binary is downloaded in the install cell (graceful skip on failure). Troubleshooting section documents the temporary/public nature of the link.
2026-08-12 19:41:16 +09:00
lukehemmin 457c67df2c fix: colab bench manifest — entities must be dicts with char spans
Bench clip failed in Colab because entity_retention calls ent.get() but
the manifest used plain strings -> AttributeError -> clip counted as
failure (failure_rate 1.0). Notebook now builds entities as
{canonical, surface, start_char, end_char} from the reference text and
writes a reference file for meaningful metrics.
2026-08-12 17:40:08 +09:00
lukehemmin be5f505410 fix: normalize faster-whisper TranscriptionInfo to dict
Colab run 4 (A100): same namedtuple bug as segments — faster-whisper
returns TranscriptionInfo (namedtuple) but batch.py reads
outcome['info'].get('language') -> AttributeError 'TranscriptionInfo'
object has no attribute 'get' on every real transcription, failing the
CLI, API auto-worker job, worker drain, and bench clip alike.

FasterWhisperEngine now normalizes info to a dict at the boundary
(_to_dict_info: _asdict -> dataclasses.asdict -> known-field fallback).

+ 2 unit tests (namedtuple/dict info); 138 tests pass, ruff clean.
2026-08-12 17:39:15 +09:00
lukehemmin 20777386fe fix: normalize faster-whisper segments to dicts + in-proc auto-worker
Colab run 3 (A100) surfaced three GPU/API-path bugs mocks couldn't catch:

1. faster-whisper yields namedtuple Segments, but batch/bench consume
   them as dicts (.get) -> AttributeError 'Segment' has no attribute
   'get' on every real transcription. Engine now normalizes segments
   to dicts (_to_dict_segments) at the boundary.

2. API TranscribeOptions carries engine-irrelevant keys (formats,
   timestamps, glossary_id, post_correction, diarize); worker's
   TranscriptionOptions(**job.options) crashed with TypeError. Worker
   now filters job.options to TranscriptionOptions.__slots__.

3. in-proc server never consumed its own queue (jobs stayed queued
   forever). Added opt-in Settings.auto_worker (default off): lifespan
   starts a daemon Worker thread for inproc backend, stopped on
   shutdown. Notebook enables it via LUKESCRIBE_AUTO_WORKER=true so the
   API upload -> completed flow works end to end.

Notebook: bench manifest now uses clips schema (audio_path/duration_sec/
entities); cell 22 reads error_message/error_code; upload poll window
raised to 4min (first-run model download).

+ 5 tests (namedtuple/dict segments, API-style options, auto_worker
on/off); 136 tests pass, ruff clean.
2026-08-12 17:32:18 +09:00
lukehemmin 741bce9fc6 fix: split 'cuda:N' into device+device_index for CTranslate2
Colab A100 run exposed a GPU-only bug: DeviceManager returns
selected_device='cuda:0' and the engine passed it verbatim to
faster-whisper, but CTranslate2 only accepts device='cuda' with a
separate device_index arg -> 'unsupported device cuda:0' on every GPU
transcription. Fix: FasterWhisperEngine._split_device() splits
'cuda:N' -> ('cuda', N) and passes device_index to WhisperModel.

Notebook: server cell now pkills stale servers (old process holds
port 8000 and 401s on new keys since KeyStore loads at startup),
sets LUKESCRIBE_API_KEY_FILE explicitly, and verifies auth with
RAW_KEY before proceeding; worker cell guards read_result() is None.

+ 4 unit tests (device split contract), 131 tests pass, ruff clean.
2026-08-12 17:20:12 +09:00
lukehemmin 236ca0acf4 fix: colab notebook — locate cuDNN/cuBLAS .so via find
nvidia-cudnn-cu12/cublas-cu12 are namespace packages with no
__file__, so os.path.dirname() raised TypeError and the install cell
aborted. Locate libcublas.so/libcudnn.so under site-packages with
find and build LD_LIBRARY_PATH from those. Also add a CTranslate2
GPU check cell (ctranslate2.get_cuda_device_count) right after
install so GPU usability is confirmed before transcription.
2026-08-12 17:11:14 +09:00
lukehemmin fb936e1953 fix: colab notebook — CUDA 13 runtime + raw key capture
Colab now ships CUDA 13.0 (driver 580); CTranslate2 wheels are CUDA 12
so GPU init fails with 'unsupported device cuda:0'. Install
nvidia-cublas-cu12/nvidia-cudnn-cu12 and set LD_LIBRARY_PATH in the
install cell; transcription cell reports failures cleanly.

API smoke: api_keys.json stores only digests (raw key shown once), so
the notebook now captures RAW_KEY at creation and uses it for upload
instead of reading the digest file (fixes 401).
2026-08-12 17:07:23 +09:00
lukehemmin f6e5ae662f fix: colab notebook — idempotent clone (pull if repo exists)
Cell 1 now detects an existing /content/luke_scribe/.git and runs
git fetch + checkout feat/full-platform + pull --ff-only instead of
rm -rf + clone, so re-running the notebook updates instead of wiping
local changes.
2026-08-12 17:02:53 +09:00
lukehemmin f385734630 fix: colab notebook — use system pip instead of venv
Colab 'python3 -m venv' fails with ensurepip error (no .venv created,
so every subsequent cell hit 'command not found'). Switch to system pip
(Colab standard) and run the API server via nohup background with log
fallback diagnostics.
2026-08-12 16:55:21 +09:00
lukehemmin 0d90845ac6 docs: add Colab GPU test notebook for real-model verification
CPU-only dev env verified via mocks; the notebook runs the full real
pipeline on Colab Pro T4: clone → ffmpeg/venv install → detect (GPU
capability tier) → 127 unit/integration tests → sample TTS (KO+EN
tech terms) → real faster-whisper transcription → hotword/postprocess
→ API smoke → benchmark.
2026-08-12 16:39:25 +09:00
lukehemmin 7327145d7a feat: implement full-platform STT API (v2.3 consensus plan)
Batch+realtime transcription API: faster-whisper engine w/ EngineOwner
(single GPU owner, OOM downgrade chain, persisted attempted_profiles),
hardware-adaptive device manager (T0-T3 VRAM tiers), Redis/in-proc job
queue w/ leases + crash recovery, postprocess (glossary/rules/LLM w/
egress guard), privacy-first result store (UUID keys, source deleted
after transcribe), retention sweeper, API-key auth (HMAC digests,
scopes, job ownership), WebSocket realtime lane (LocalAgreement),
CLI (detect/transcribe/bench/serve/key), Docker, benchmark runner.

127 mock-based tests pass; ruff clean. Includes verification checklist
and autoplan review notes.
2026-08-12 16:01:21 +09:00
lukehemmin b7c30f8b71 WIP: define Luke Scribe v0.1 design
[gstack-context]
Decisions: Validate the transcription core and benchmark gate before API, queue, and realtime work; preserve five expansion contracts.
Remaining: Build the benchmark dataset and implement detect/transcribe/bench.
Skill: /office-hours
[/gstack-context]
2026-08-10 17:40:33 +09:00