Interim fix for #61: stop forcing language=sv on every upload.
What changes
Upload form: a Language field, Swedish (preselected) or Other (detected by the engine), placed before the file part. An upload that names none is Swedish. A value outside the set is a 400 before anything is spooled or stored.
domain.Language (sv | auto), carried on the Job and on the worker's context to the engines; migration 021 adds uploaded_transcripts.language (default sv for history, CHECK constraint on the closed set).
Gateway: sv sends language=sv, auto omits the field, anything else is refused before a request is made.
Ensemble: an auto upload never reaches the Swedish-only base engine and is not reconciled; the other engine transcribes alone, then the cloud fallback if it fails and the job is live. A cancelled job never reaches the fallback.
Status page shows the language. Feature scenarios, coverage map, ADR-033/034 amended.
What it does not do (still #61): automatic detection, a general-purpose engine for other languages, per-language fallback. auto goes to the same configured engine (kb-whisper in production), which is not tuned for it. Production runs the single-engine chain (reconcile off), so there the whole effect is the field and the omitted language.
Verification
Red tests first for each step; mutation checks: worker context, intake job/row language, CHECK constraint, handler field, refusal, preselect, parser, cancelled-job fallback all go red when broken; one redundant length check found by a surviving mutant was removed.
task check exit 0, golangci-lint run ./... exit 0, go test -race of the touched packages ok.
Not yet verified: adversarial review (running), CI on the head, a live upload with Other.
risk/medium (intake, worker, adapter, migration, decides which engine receives which audio): PR with a recorded adversarial review before merge.
Interim fix for #61: stop forcing `language=sv` on every upload.
**What changes**
- Upload form: a Language field, Swedish (preselected) or Other (detected by the engine), placed before the file part. An upload that names none is Swedish. A value outside the set is a 400 before anything is spooled or stored.
- `domain.Language` (`sv` | `auto`), carried on the `Job` and on the worker's context to the engines; migration 021 adds `uploaded_transcripts.language` (default `sv` for history, CHECK constraint on the closed set).
- Gateway: `sv` sends `language=sv`, `auto` omits the field, anything else is refused before a request is made.
- Ensemble: an `auto` upload never reaches the Swedish-only base engine and is not reconciled; the other engine transcribes alone, then the cloud fallback if it fails and the job is live. A cancelled job never reaches the fallback.
- Status page shows the language. Feature scenarios, coverage map, ADR-033/034 amended.
**What it does not do (still #61):** automatic detection, a general-purpose engine for other languages, per-language fallback. `auto` goes to the same configured engine (kb-whisper in production), which is not tuned for it. Production runs the single-engine chain (reconcile off), so there the whole effect is the field and the omitted `language`.
**Verification**
- Red tests first for each step; mutation checks: worker context, intake job/row language, CHECK constraint, handler field, refusal, preselect, parser, cancelled-job fallback all go red when broken; one redundant length check found by a surviving mutant was removed.
- `task check` exit 0, `golangci-lint run ./...` exit 0, `go test -race` of the touched packages ok.
- Not yet verified: adversarial review (running), CI on the head, a live upload with Other.
risk/medium (intake, worker, adapter, migration, decides which engine receives which audio): PR with a recorded adversarial review before merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Created via git-mcp on behalf of @mathias_
sv and auto; an empty form value is sv, anything else is refused. LanguageFrom defaults to
sv so a caller that sets nothing behaves as before.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The request used to hard-code language=sv for every upload, so a recording in another
language was decoded as Swedish. The language now comes from the context; no language in
the context is still sv.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The other engine transcribes alone, then the cloud fallback if it fails and the job is
still live. A cancelled job never reaches the fallback (mutation-checked).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Form field (Swedish preselected, Other = auto) read before the file part and refused
before anything is spooled if outside the closed set; stored on the row (migration 021,
CHECK constraint, default sv for history); carried on the Job and the worker's context;
shown on the status page. Feature scenarios and the coverage map are updated, and
ADR-033/034 amended.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The read is capped at 17 bytes and no valid language is longer than 4, so the parse
refuses anything the length check would; the mutant that removed it survived.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The wrapper sits in front of both local engines; a fresh context there would send an auto
upload as language=sv and un-cancel the job, and no test noticed. Found by the PR #62 review.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found by the PR #62 review: the message could be replaced with 'x' and the test stayed green.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PlanFor(lang) on the Ensemble and a LanguagePlanner the worker prefers: the page lists
the other engine (and the fallback if configured), not the base engine, the comparison and
the decisions as coming. Found by the PR #62 review.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The label moves from the done fragment to the page header, so queued, transcribing and
failed recordings show it too. The scenario no longer implies a general engine exists. Found
by the PR #62 review.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adversarial review, round 1 (agent, by execution): BLOCK (narrow) on head cc6fe70. No bad language value could reach an engine (30 raw multipart bodies, 28 mutants), auto never reached the base engine in Ensemble, chain or fallback paths, the cancel guard held, migration 021 was executed up/down on a populated table (history rows became sv, CHECK refused AUTO and empty, RLS and FORCE intact, the app role works), and the templ output matches the committed file apart from the usual import style. Gates: task check 0, golangci-lint 0, go test -race of the four packages ok.
Findings and what I did (head below, all test-first):
MEDIUM, fixed.WithTimeout (in front of both local engines in production) could drop the context, sending an auto upload as language=sv, and no test noticed. New test TestWithTimeout_TheEngineStillReceivesTheLanguageOfTheJob (auto and sv); the mutant now goes red.
LOW, fixed. The refusal test asserted Contains(body, "language"), which the re-rendered form always contains. It now asserts the message; replacing the message with "x" goes red.
LOW, fixed. The progress page listed the base engine, the comparison and the decisions as coming for an auto job. Ensemble.PlanFor(lang) and a LanguagePlanner the worker prefers; three mutants red.
LOW, fixed (wording and placement). The feature said "no Swedish-only engine", but in production Other goes to kb-whisper, itself a Swedish fine-tune; it now says the Swedish-only base engine and reconcile are not used and the engine that runs is not tuned for other languages. The "Language:" label moved to the page header so queued, transcribing and failed recordings show it too (tested for all four states).
INFO, accepted. A language part after the file is never read (documented and pinned: the upload is Swedish); with repeated language parts the last wins and an invalid one before the file is a 400; an empty part resets to Swedish. The result is always a constant.
**Adversarial review, round 1 (agent, by execution): BLOCK (narrow)** on head cc6fe70. No bad language value could reach an engine (30 raw multipart bodies, 28 mutants), `auto` never reached the base engine in Ensemble, chain or fallback paths, the cancel guard held, migration 021 was executed up/down on a populated table (history rows became `sv`, CHECK refused `AUTO` and empty, RLS and FORCE intact, the `app` role works), and the templ output matches the committed file apart from the usual import style. Gates: `task check` 0, `golangci-lint` 0, `go test -race` of the four packages ok.
Findings and what I did (head below, all test-first):
1. **MEDIUM, fixed.** `WithTimeout` (in front of both local engines in production) could drop the context, sending an `auto` upload as `language=sv`, and no test noticed. New test `TestWithTimeout_TheEngineStillReceivesTheLanguageOfTheJob` (auto and sv); the mutant now goes red.
2. **LOW, fixed.** The refusal test asserted `Contains(body, "language")`, which the re-rendered form always contains. It now asserts the message; replacing the message with "x" goes red.
3. **LOW, fixed.** The progress page listed the base engine, the comparison and the decisions as coming for an `auto` job. `Ensemble.PlanFor(lang)` and a `LanguagePlanner` the worker prefers; three mutants red.
4. **LOW, fixed (wording and placement).** The feature said "no Swedish-only engine", but in production Other goes to kb-whisper, itself a Swedish fine-tune; it now says the Swedish-only *base* engine and reconcile are not used and the engine that runs is not tuned for other languages. The "Language:" label moved to the page header so queued, transcribing and failed recordings show it too (tested for all four states).
5. **INFO, accepted.** A language part after the file is never read (documented and pinned: the upload is Swedish); with repeated `language` parts the last wins and an invalid one before the file is a 400; an empty part resets to Swedish. The result is always a constant.
Re-review of the delta is next.
---
_Created via git-mcp on behalf of @mathias_
Low findings of the PR #62 re-review: the WithTimeout test's comment claimed cancellation
travels on the context but only the language was asserted (the WithoutCancel mutant survived),
and PlanFor('') and PlanFor(unknown) were untested.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adversarial review, round 2 (delta cc6fe70..e3288d0, agent, by execution): APPROVE.
Verified by flip tests (all compiling mutants): the WithTimeout language test goes red for a wrapper that uses context.Background(), forces auto or forces sv; the refusal test goes red when the message changes; PlanFor/LanguagePlanner mutants (always-full plan, wrong auto plan, Plan() returning the auto plan, fallback always on, LanguagePlanner branch off, Planner-only branch off) all red; the page label is red when removed or shown only when done (8 state/language subtests). A scratch matrix of Ensemble.PlanFor against the real Ensemble.Transcribe events (languages "", sv, auto, xx, SV, Auto, " sv" x fallback on/off x engine success/failure) found no phase that ran without being planned; through the real Worker an auto job saves received, audio, engine_b, save, and an empty-Language job gets the full 7-step Swedish plan. The Ensemble reaches the worker unwrapped in production, so the LanguagePlanner is visible there. templ output equals the committed file apart from import style. Gates: task check 0, golangci-lint 0, go test -race ok.
Low findings, handled (head f704258): (1) the test comment claimed cancellation travels on the context but only the language was asserted (a WithoutCancel mutant survived): new TestWithTimeout_ACancelledJobIsNotRestartedByTheWrapper, mutant now red; (3) PlanFor("") and PlanFor(unknown) pinned, mutant red. (2) accepted: the worker's ""-to-Swedish normalisation has equivalent mutants today (LanguageFrom already maps "" to sv and the Ensemble and fake do too); mapping the default to auto does go red (4 failures), so the Swedish default itself is pinned.
Not verified by any reviewer: a live upload and real engine behaviour on non-Swedish audio, a browser rendering of the header, a rolling deploy with the old binary against the migrated table (the old INSERT and explicit column lists are unaffected by an added defaulted column; not run). CI run 9179 on cc6fe70 was green; the head moved by tests only, run on f704258 is queued.
**Adversarial review, round 2 (delta cc6fe70..e3288d0, agent, by execution): APPROVE.**
Verified by flip tests (all compiling mutants): the `WithTimeout` language test goes red for a wrapper that uses `context.Background()`, forces `auto` or forces `sv`; the refusal test goes red when the message changes; PlanFor/LanguagePlanner mutants (always-full plan, wrong auto plan, `Plan()` returning the auto plan, fallback always on, LanguagePlanner branch off, Planner-only branch off) all red; the page label is red when removed or shown only when done (8 state/language subtests). A scratch matrix of `Ensemble.PlanFor` against the real `Ensemble.Transcribe` events (languages `""`, sv, auto, xx, SV, Auto, " sv" x fallback on/off x engine success/failure) found no phase that ran without being planned; through the real Worker an `auto` job saves received, audio, engine_b, save, and an empty-Language job gets the full 7-step Swedish plan. The Ensemble reaches the worker unwrapped in production, so the LanguagePlanner is visible there. templ output equals the committed file apart from import style. Gates: `task check` 0, `golangci-lint` 0, `go test -race` ok.
Low findings, handled (head f704258): (1) the test comment claimed cancellation travels on the context but only the language was asserted (a `WithoutCancel` mutant survived): new `TestWithTimeout_ACancelledJobIsNotRestartedByTheWrapper`, mutant now red; (3) `PlanFor("")` and `PlanFor(unknown)` pinned, mutant red. (2) accepted: the worker's `""`-to-Swedish normalisation has equivalent mutants today (`LanguageFrom` already maps `""` to sv and the Ensemble and fake do too); mapping the default to `auto` does go red (4 failures), so the Swedish default itself is pinned.
Not verified by any reviewer: a live upload and real engine behaviour on non-Swedish audio, a browser rendering of the header, a rolling deploy with the old binary against the migrated table (the old INSERT and explicit column lists are unaffected by an added defaulted column; not run). CI run 9179 on cc6fe70 was green; the head moved by tests only, run on f704258 is queued.
---
_Created via git-mcp on behalf of @mathias_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Interim fix for #61: stop forcing
language=svon every upload.What changes
domain.Language(sv|auto), carried on theJoband on the worker's context to the engines; migration 021 addsuploaded_transcripts.language(defaultsvfor history, CHECK constraint on the closed set).svsendslanguage=sv,autoomits the field, anything else is refused before a request is made.autoupload never reaches the Swedish-only base engine and is not reconciled; the other engine transcribes alone, then the cloud fallback if it fails and the job is live. A cancelled job never reaches the fallback.What it does not do (still #61): automatic detection, a general-purpose engine for other languages, per-language fallback.
autogoes to the same configured engine (kb-whisper in production), which is not tuned for it. Production runs the single-engine chain (reconcile off), so there the whole effect is the field and the omittedlanguage.Verification
task checkexit 0,golangci-lint run ./...exit 0,go test -raceof the touched packages ok.risk/medium (intake, worker, adapter, migration, decides which engine receives which audio): PR with a recorded adversarial review before merge.
🤖 Generated with Claude Code
Created via git-mcp on behalf of @mathias
Adversarial review, round 1 (agent, by execution): BLOCK (narrow) on head
cc6fe70. No bad language value could reach an engine (30 raw multipart bodies, 28 mutants),autonever reached the base engine in Ensemble, chain or fallback paths, the cancel guard held, migration 021 was executed up/down on a populated table (history rows becamesv, CHECK refusedAUTOand empty, RLS and FORCE intact, theapprole works), and the templ output matches the committed file apart from the usual import style. Gates:task check0,golangci-lint0,go test -raceof the four packages ok.Findings and what I did (head below, all test-first):
WithTimeout(in front of both local engines in production) could drop the context, sending anautoupload aslanguage=sv, and no test noticed. New testTestWithTimeout_TheEngineStillReceivesTheLanguageOfTheJob(auto and sv); the mutant now goes red.Contains(body, "language"), which the re-rendered form always contains. It now asserts the message; replacing the message with "x" goes red.autojob.Ensemble.PlanFor(lang)and aLanguagePlannerthe worker prefers; three mutants red.languageparts the last wins and an invalid one before the file is a 400; an empty part resets to Swedish. The result is always a constant.Re-review of the delta is next.
Created via git-mcp on behalf of @mathias
Adversarial review, round 2 (delta cc6fe70..e3288d0, agent, by execution): APPROVE.
Verified by flip tests (all compiling mutants): the
WithTimeoutlanguage test goes red for a wrapper that usescontext.Background(), forcesautoor forcessv; the refusal test goes red when the message changes; PlanFor/LanguagePlanner mutants (always-full plan, wrong auto plan,Plan()returning the auto plan, fallback always on, LanguagePlanner branch off, Planner-only branch off) all red; the page label is red when removed or shown only when done (8 state/language subtests). A scratch matrix ofEnsemble.PlanForagainst the realEnsemble.Transcribeevents (languages"", sv, auto, xx, SV, Auto, " sv" x fallback on/off x engine success/failure) found no phase that ran without being planned; through the real Worker anautojob saves received, audio, engine_b, save, and an empty-Language job gets the full 7-step Swedish plan. The Ensemble reaches the worker unwrapped in production, so the LanguagePlanner is visible there. templ output equals the committed file apart from import style. Gates:task check0,golangci-lint0,go test -raceok.Low findings, handled (head
f704258): (1) the test comment claimed cancellation travels on the context but only the language was asserted (aWithoutCancelmutant survived): newTestWithTimeout_ACancelledJobIsNotRestartedByTheWrapper, mutant now red; (3)PlanFor("")andPlanFor(unknown)pinned, mutant red. (2) accepted: the worker's""-to-Swedish normalisation has equivalent mutants today (LanguageFromalready maps""to sv and the Ensemble and fake do too); mapping the default toautodoes go red (4 failures), so the Swedish default itself is pinned.Not verified by any reviewer: a live upload and real engine behaviour on non-Swedish audio, a browser rendering of the header, a rolling deploy with the old binary against the migrated table (the old INSERT and explicit column lists are unaffected by an added defaulted column; not run). CI run 9179 on
cc6fe70was green; the head moved by tests only, run onf704258is queued.Created via git-mcp on behalf of @mathias