From 9aa01468cef3948c35721b04cacc8d7b009111b6 Mon Sep 17 00:00:00 2001 From: Mathias Date: Sat, 3 Oct 2026 21:30:44 +0200 Subject: [PATCH] fix(web): judge each alias of a reconciled endpoint, so an all-local transcript is not called cloud-served (#52) The two-engine path writes ' + , reconciled by '. 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 --- internal/web/uploads.go | 24 +++++++++++++++++--- internal/web/uploads_cloud_note_test.go | 29 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/internal/web/uploads.go b/internal/web/uploads.go index f4638f6..8ef6a43 100644 --- a/internal/web/uploads.go +++ b/internal/web/uploads.go @@ -273,12 +273,30 @@ func titleFromFilename(name string) string { // cloudProvider names the cloud service that produced a transcript, or false when it was // served locally (or by an unknown endpoint). "Local" is config.IsLocalModel: the same // definition the start-up validation uses, not a second string match here (#52). +// +// The endpoint is one alias, except for the two-engine path (ADR-034), which writes +// " + , reconciled by ". Every alias in it is judged on its own, so a +// composite of local models is local and one naming a cloud alias names that provider. func cloudProvider(endpoint string) (string, bool) { - if endpoint == "" || config.IsLocalModel(endpoint) { + if strings.TrimSpace(endpoint) == "" { return "", false } - provider, _, _ := strings.Cut(endpoint, "/") - return provider, true + aliases := []string{} + for _, tok := range strings.Fields(endpoint) { + if tok = strings.Trim(tok, ","); strings.Contains(tok, "/") { + aliases = append(aliases, tok) + } + } + if len(aliases) == 0 { + aliases = append(aliases, strings.TrimSpace(endpoint)) + } + for _, a := range aliases { + if !config.IsLocalModel(a) { + provider, _, _ := strings.Cut(a, "/") + return provider, true + } + } + return "", false } // hasNote reports whether any phase carries a note. A note is the only warning for a diff --git a/internal/web/uploads_cloud_note_test.go b/internal/web/uploads_cloud_note_test.go index f6ebf76..7e68187 100644 --- a/internal/web/uploads_cloud_note_test.go +++ b/internal/web/uploads_cloud_note_test.go @@ -53,3 +53,32 @@ func TestUploadStatus_AnEndpointThatOnlyLooksLocalIsStillNamedAsCloud(t *testing assert.Contains(t, b, "left the homelab") } + +// The two-engine path (ADR-034) writes one composite endpoint string. Every name in it is +// a local model, so the audio never left the homelab and the note must not say it did. +func TestUploadStatus_AReconciledAllLocalEndpointCarriesNoCloudNote(t *testing.T) { + for _, endpoint := range []string{ + "iguana/kb-whisper + koala/whisper, reconciled by iguana/gemma4-26b", + "iguana/kb-whisper + koala/whisper, reconciled by iguana/gemma4-26b\n", + } { + t.Run(endpoint, func(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.") + setEndpoint(t, id, endpoint) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.NotContains(t, b, "left the homelab") + }) + } +} + +func TestUploadStatus_AReconciledEndpointNamingACloudEngineStillNamesTheCloudProvider(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.") + setEndpoint(t, id, "iguana/kb-whisper + berget/kb-whisper, reconciled by iguana/gemma4-26b") + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "cloud service (berget)") +}