Skip to content

fix: reject smart cleanup output that drops the spoken transcript - #15

Merged
Kuberwastaken merged 1 commit into
Kuberwastaken:mainfrom
KadenThomp36:fix/preserve-transcript-in-smart-cleanup
Jul 24, 2026
Merged

fix: reject smart cleanup output that drops the spoken transcript#15
Kuberwastaken merged 1 commit into
Kuberwastaken:mainfrom
KadenThomp36:fix/preserve-transcript-in-smart-cleanup

Conversation

@KadenThomp36

Copy link
Copy Markdown
Contributor

Problem

validate() only guards against the model adding text:

if !allowsExpansion && output.count > max(sourceCount * 2, sourceCount + 200) {
    throw SmartCleanupError.invalidOutput("unexpectedly expanded the transcript")
}

Nothing guards against it removing text. When the on-device model declines to reproduce part of a transcript, the truncated result passes validation and is reported as a success, so the user silently gets words they never said — or loses words they did.

Real entries from PipelineHistory on 1.1.8 (macOS 26, Apple silicon), all logged as Smart on-device cleanup succeeded:

Spoken Pasted
What the fuck? What
What the fuck? I think we should ship this.
It actually seems really good, but can you check... same text wrapped in a ``` fence

The first two are the notable ones: SystemLanguageModel is already constructed with guardrails: .permissiveContentTransformations, but the model still truncates at profanity or paraphrases around it, despite the system prompt's explicit instruction to Preserve language, names, technical identifiers, paths, flags, URLs, and profanity. Because the output is shorter than the input, nothing caught it.

Change

Three additional checks in validate(), all gated on !allowsExpansion so selection transforms (shorten/reword) are unaffected:

  1. Truncation — reject when the output loses more than half the transcript.
  2. Must-preserve terms — reject when profanity present in the source is absent from the output, enforcing the contract the system prompt already states.
  3. Block markdown — reject a leading ```, #, or > that the source did not have, unless the speaker asked for a code block/heading/quote.

Bullets and numbered lists are deliberately not rejected — auto-list formatting seems desirable, and the prompt already permits it when requested.

A rejection is not a failure: it falls through to deterministicCleanup, which reproduces the speaker's words verbatim. On the samples above, that path already produced correct output.

validate changed from private static to static so tests can reach it, matching commandPrompt and friends.

Tests

New Tests/SmartCleanupValidationTests.swift, registered in the runner and the Makefile, covering the logged regressions plus the cases that must keep passing: filler removal, self-corrections, punctuation fixes, auto bullets/numbered lists, preserved profanity, and allowsExpansion transforms.

make test passes.

Notes

  • The threshold for check 1 is deliberately loose (50%) to avoid rejecting legitimate filler removal and self-corrections. It therefore does not catch smaller losses — e.g. This seems really fast. I think I might switch to this.- I think I might switch to this still passes. Tightening it safely probably needs filler-aware content-word retention rather than a character ratio; happy to follow up if you want that.
  • The term list in mustPreserveTerms is English-only and intentionally short. Easy to extend, though a broader list raises the cost of a false rejection (a needlessly basic-cleaned transcript).

@Kuberwastaken

Copy link
Copy Markdown
Owner

Reviewed this and built + ran the suite on macOS 26.5.2 (Apple silicon) — make test passes, including the new SmartCleanupValidationTests.

Verdict: good to merge. The approach is right: the three new checks are all gated on !allowsExpansion, and a rejection isn't a hard failure — it falls through to deterministicCleanup, so the worst case is a basic-cleaned transcript with the speaker's exact words rather than a silently truncated or censored one. That's the correct trade for a silent-data-loss bug. Tests cover both the regressions and the must-keep-passing cases (filler removal, self-corrections, auto lists, preserved profanity, selection transforms).

A few notes, none blocking:

  1. This partially overlaps Output is including JSON and tags like "cleaned_text". #14, worth being explicit about it. The new markdown-prefix check also rejects a leading ``` fence, which means the fenced-JSON variant of Output is including JSON and tags like "cleaned_text". #14 (```json\n{"cleaned_text": …}\n```) now gets rejected → falls back to basic. I confirmed that empirically. But the other two Output is including JSON and tags like "cleaned_text". #14 variants still slip through validation untouched:

    model output on this branch
    ```json\n{"cleaned_text":"…"}\n``` rejected → basic fallback ✅
    {"cleaned_text":"…"} (no fence) accepted, leaks
    <cleaned_text>…</cleaned_text> accepted, leaks

    So Output is including JSON and tags like "cleaned_text". #14 needs a complementary fix at the normalizeCommandOutput layer (I've got one prepped — see the issue). The two compose cleanly: normalize runs before validate, so once the wrapper is unwrapped the fenced-JSON case yields the smart result and passes validation instead of falling back. No conflict with this PR beyond a trivial Makefile/runner line.

  2. # prefix is broad. The heading check keys on a bare #, so a transcript the model renders as #YOLO (hashtag, not heading) would also be rejected. Fine given the safe fallback, just noting it's not strictly "heading."

  3. English-only must-preserve list — you already called this out. Agree it's the right scope for now; a broader list raises the cost of a false rejection.

Nice catch on the guardrails: .permissiveContentTransformations interaction — the model truncating at profanity despite that setting is exactly the kind of thing that's invisible without reading PipelineHistory.

@Kuberwastaken
Kuberwastaken merged commit 23447db into Kuberwastaken:main Jul 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants