A body that stopped arriving held one of the receive slots (#47) and a
spool file indefinitely. Every read of the upload body now pushes the
connection read deadline UploadIdleTimeout (default 60 s) ahead, so a
stalled body fails its read, Accept returns and the slot is released,
while a slow but moving body is never cut (ReadTimeout would cut it).
Two things found by running it over a socket:
- metrics.statusWriter had no Unwrap, so http.ResponseController could
not reach the connection and the bound silently did not apply in the
production handler stack. It now unwraps; a writer that still cannot
set a deadline logs "upload idle bound unavailable".
- clearing the deadline on any read error left net/http's pre-reply
drain of the unread remainder unbounded, so the connection hung. The
deadline is now cleared only on a clean io.EOF.
ADR-033 limitation amended. A 1-byte-per-59-s drip is still unbounded.
Closes#56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping 'by', 'reconciled' and '+' as words everywhere let an alias with one of those
names pass as local. The two-engine shape is now split on its own separators and each alias
judged whole; a reconciler that is not local is pinned too. Hardening suggested by the third
PR #58 review.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The previous version kept only words containing '/' and dropped the rest, so a bare
TAPIR_TRANSCRIBE_MODEL alias in the composite produced a false 'all local' result. Found
by the PR #58 re-review; red tests for a bare alias, an alias with a space and a split
'berget / x' first.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The two-engine path writes '<base> + <alt>, reconciled by <model>'. IsLocalModel is a
single-alias match, so the composite was 'not local' and the note claimed the audio left
the homelab, naming a local host as the provider. Found by the PR #58 review.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upload a recording (audio, or video with the camera off), get a private Swedish transcript, and chat about it.
- ADR-033: RLS-scoped private transcript store; ffprobe/ffmpeg intake; gateway transcription (iguana kb-whisper, berget fallback); in-process single-replica worker; chat over the transcript (local models only).
- ADR-034: optional two-engine mode (pianissimo + kb-whisper) reconciled by a LOCAL model; off unless TAPIR_RECONCILE_BASE_MODEL is set.
- ADR-035: real progress (phases, running/quiet/lost), phase logging, every judged spot stored, merged segments.
- Hardening from four adversarial reviews: errors logged and stored as kinds never text; per-engine deadlines; converted-length check; bounded queue; client hang-ups are not errors; strict local-alias check.
- Dockerfile: static ffmpeg/ffprobe 7.0.2 pinned by sha256 (#34). Built and inspected by hand; CI does not build the image on a PR, so its first CI build is this merge.
Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set in the deployment.
Closes#33, #34, #42, #43, #44.
Co-authored-by: Mathias <mthbqv@gmail.com>