Carry the engine's speech evidence to where it can be read, and put the leak's guard where its comment already was - #364
Open
lilseyi wants to merge 2 commits into
Conversation
The silence refusal cut invented words from 1.84/s to 0.64/s on the owner's Mac and did not stop them, and nobody could tell whether the threshold was wrong or the signal was: `no_speech_prob`, `avg_logprob` and `duration_after_vad` were read inside the transcription Worker, used for control flow, and dropped. The segment that reaches the bucket carries one number, `confidence`, whose distinct set of values across every segment ever produced is `[null]` — because the deployed model does not emit that field and this repository refuses to invent one. Established first, since "the provider omits them" would have been the finding: it does not. The published output schema of the deployed model carries both per-segment fields and both `transcription_info` durations, and they demonstrably arrive — the VAD rule cannot fire on this deployment at all (`vad_filter` defaults to false), so the measured reduction was produced entirely by the rule that needs `no_speech_prob` and `avg_logprob` together. So the Worker now answers with a per-chunk `evidence` summary beside `refused`: counts of what the engine stated, the extremes over the kept and the refused populations on each axis, and the two durations as stated. The gateway carries it as `speechEvidence` the way it already carries `refusedSegments` — rebuilt key by key, judging nothing — and the desktop writes one structured line per answered chunk, in the process the person diagnosing a recording already has. Not on the segment, because a segment is rendered at a person and written into their note; not in the Worker's log, because reaching that needs an account the person diagnosing does not have, which is exactly what blocked the diagnosis. `null` is never filled in with `0` on any hop and prints as `absent`: "it did not say" and "it measured silence" must stay different answers. Nothing branches on any of it, nothing renders it, and every value is a number — there is no field here that can hold a word anybody said. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz
Two reports against the signed build — the eight parked outbox rows did not clear, and every segment reached the page once per meeting in the run — are one fact. That build is workflow run 34181715875, `head_sha` `fd0081e`, started 02:55:14Z; `dropMisaddressed` and the controller's detach both landed in `7bc99ca` (#353), merged 03:00:52Z. Neither fix was in the binary. Reproduced against `fd0081e` — three meetings deliver meeting N's segments N times and each record ends holding every later meeting's words — and absent on `main`, where the same run delivers each once and leaves no subscription behind. Any build cut from `7bc99ca` or later drops the parked rows on the first read of the queue and logs `meeting_segments_misaddressed_dropped rows=<n>`; nothing is lost, because every one of those rows also went out under its own meeting and was acked. The recorder-to-bridge attachment named in the third report is correct and was. What was missing is the check: its own docblock states the hazard — "a recorder is created per configuration and `stop()` must genuinely detach, or a second meeting is fed by two subscriptions" — and the only test on it ran one meeting, the single length at which a per-meeting leak is invisible. So the guard now sits where the comment does: three meetings in one run with each segment delivered exactly once, the recorder's subscription count asserted back at its baseline after every stop (a fix that halved a leak passes the first and fails the second), the whole subscription set released rather than the segment half, and the overlapping-start race — the one path where `attach()`'s opening `detach?.()` is reachable, and deleting that line failed nothing in the app's whole suite until now. Sabotage, whole mobile suite, reverted: `stop()` not detaching 4; `attach()` not detaching first 1; only the segment subscription released 4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three defects were reported from hardware last night. One is real and is fixed here. The other two are the same fact: the build they were measured on predates the fixes for them. That is established from the workflow run rather than argued.
1 — The evidence that decides the invented-speech fix now reaches the client
The silence refusal works and does not finish the job:
166 words / 90sbecame29 words / 45s, the meeting was still filed, andhasNothingCapturedis still unreachable. What decides the next move is whether the survivors sit just past the thresholds (move the threshold — cheap) or far from them (the engine decoded silence confidently — no threshold reaches it). Nobody could answer it, becauseno_speech_prob,avg_logprobandduration_after_vadwere read inside the transcription Worker, used for control flow, and dropped.First finding, established before anything was built: the provider does return them. The alternative — that the fields are absent — would have made plumbing them a waste, so it was checked two ways:
segments[].{avg_logprob, no_speech_prob, …}andtranscription_info.{duration, duration_after_vad}. It carries noconfidence, which is why the distinct set ofconfidencevalues across every segment in the bucket is[null]and always will be on this model.vad_filterdefaults to false), so the measured threefold reduction was produced entirely by the rule that requires both per-segment fields to be present. A rule that cannot fire without two fields, firing repeatedly on real audio, is those two fields arriving.Where it now lives: a per-chunk summary, on the wire, to the recorder that posted the audio. The Worker answers with
evidencebesiderefused; the gateway carries it asspeechEvidenceexactly the way it already carriesrefusedSegments— rebuilt key by key, judging nothing; the desktop writes one structured line per answered chunk,meeting_speech_evidence chunk=… kept=… refused=… kept_no_speech_max=… ….Against the constraints, each one load-bearing:
CAPTURE_NOTICES.silent).null; the only string is the client's ownchunkId. Both the gateway and the desktop rebuild the object from keys they declare, so a field the far end grows does not ride along (checked with a plantedtranscriptkey at both hops).nullis never filled in with0on any hop, and prints asabsent.0is a legalno_speech_prob; substituting it would publish a confident measurement of silence that nobody made.Nothing branches on any of it. The Convex path is deliberately not plumbed — nothing on the phone can read a log line, so it would land in Convex's logs, the same inaccessible place one provider along, and a field nobody reads rots. Argued in
docs/decisions/meetings.md.2 and 3 — The duplicate deliveries and the parked rows are one build
The installed build is workflow run
34181715875:head_shafd0081e(#351), started 02:55:14Z.dropMisaddressedand the controller's detach both landed in7bc99ca(#353), merged 03:00:52Z — five minutes and thirty-eight seconds after that build started. Neither fix was in the binary. The dispatch message named #353 because it was sent after the merge was requested and before it landed.Both reports are correct observations of the old code, reproduced and then shown absent:
fd0081e, three meetings driven through the realMeetingsControllerdeliver meeting N's segments N times — the ratio-equals-index shape reported from hardware — and leave meeting 1's record holding all three meetings' words.main, the same run delivers each segment exactly once and leaves the recorder's subscription count back at zero between meetings.What clears the eight parked rows: any build cut from
7bc99caor later. They are dropped once, on the first read of the queue, withmeeting_segments_misaddressed_dropped rows=<n>logged. Nothing is lost — every one of those rows also went out under the meeting that produced it and was acked there.The recorder-to-bridge attachment is correct, and was. What was missing is the check. Its own
attach()docblock states the hazard in the exact words the report used — "a recorder is created per configuration andstop()must genuinely detach, or a second meeting is fed by two subscriptions and every segment is emitted twice" — and the only test standing on it ran one meeting, the single length at which a per-meeting leak is invisible. A comment describing the failure it sits above, with no test at that length, is how one shape gets reported three times. So the guard is now where the comment is:stop()— the stricter half, because a fix that merely halved a leak passes a delivery count and fails a baseline;start()race, which is the only path whereattach()'s openingdetach?.()is reachable at all — deleting that line failed nothing in the app's whole suite until this test.Every other consumer of the same channel, checked:
capture/level.ts(onLevel) attaches in its own effect and detaches in that effect's cleanup;ThisMachineCardholdsconnection.onChangeandonPendingApprovalthe same way; the shell side emits once per segment from one call site and the preload'ssubscriberemoves the same listener reference it added. The controller-to-recorder layer is #353's, with three-meeting coverage already. No other leaked subscription was found.Three instances of one shape — controller-to-recorder (#353), the phone's equivalent (#358), and this comment's hazard — suggest a structural answer: one attach/detach helper owning a subscription set, or a check that every subscription taken in this subsystem is released. That is larger than this pull request, so it is proposed in the decision doc rather than built.
Sabotage
Every count is failing tests/checks, run whole-suite and reverted.
infra/transcribe-workerevidencedropped from the answer entirelynullreading filled in with0statedNoSpeechcounted over kept segments rather than all of themduration_after_vadreported only when the rule would act on itapps/mcpspeechEvidencefrom the answerraw.evidenceinstead of rebuilding it0rather thannullapps/mobile(capture/desktop.ts, whole mobile suite)stop()does not detach, so one subscription leaks per meetingattach()does not detach the previous subscriptions firstapps/desktop(main/transcribe.ts, recorded in the test docblock):speechEvidencedropped 2;body.speechEvidenceforwarded whole 1; an unreadable reading defaulted to01.Suites
pnpm install, thenturbo run typecheck(12/12) andturbo run test --force(12/12), rebased ontoe47a770:infra/transcribe-worker125 (was 119) — vitestapps/mcpALL PASSapps/desktopALL PASSapps/mobile4101 passed, 1 todo, 220 suitespackages/meetingsALL PASSapps/convexpassNo pre-existing failures on
origin/main; the mobile editor test that flakes under parallel load did not flake in any run here.Compatibility, both directions
Additive on every hop. An old Worker gives the gateway no
evidence→speechEvidence: null; an old gateway gives the desktop no field →null; an old desktop ignores the new key. Each is checked, and none of them is a row of zeros.What still needs a Mac
meeting_speech_evidencelines from a quiet-room recording. That build must be cut from this merge or later — cut it after merging, not frommainas of last night.7bc99caor later and check the log line.--no-sandbox); the desktop bundle builds and the per-PR gate runs it.🤖 Generated with Claude Code
https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz
Generated by Claude Code