Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -739,3 +739,40 @@ Architecture-shaping decisions graduate to an ADR under
drafts (round 20); the intents-README "always they/them"
persona-quote rule read against `03-personas.md`'s gendered narrative
pronouns (round 24).
- 2026-08-06 — Bug-hunt round 30: `transcribe.mapSegments`'s word-level
emptiness check now decides presence on `session.SafeText`'s rendered form,
matching the segment-level guard five lines above it — a word that is
entirely invisible-only Unicode (e.g. ZWSP U+200B) was non-empty raw and
survived `strings.TrimSpace`, so it reached `transcript.jsonl`,
`timeline.jsonl`, and the analysis request as a timestamped word with no
visible content. `install.sh`'s `install_ffmpeg_local` error-handling
comment no longer claims the EXIT trap "covers only `install_binary`'s
`$tmp`" — that trap has swept `$tmp2` since round 21's fix; the comment was
stale from the moment it landed in the same commit that widened the trap.
Refuted: an `analyze.indexTimeline` map write keying `idx.uttText` under an
unguarded raw id (the read path is gated behind `idx.ids`, which is itself
gated, so the empty key is write-only dead data with no observable effect);
`session.WriteFileAtomicNoFollow` refusing only symlinks, not other
non-regular files, at its target path (the function never opens the
pre-existing file — it renames a temp file into place, and `rename(2)`
neither opens, blocks on, nor writes through a FIFO/device/socket, so the
hazard the sibling `openNoFollow`'s stricter check exists to prevent is
absent by construction); `cli.md`'s `-offset` table cell compressing the
"external audio vs the session's own `audio.wav`" split as "with/without
`-audio`" (the surrounding prose states the exception twice within ten
lines, and the table already compresses two other branches the same way);
`ci.yml`'s cross-compile step comment ("linux/amd64 already covered by
Build above") read as a claim about `CGO_ENABLED`, which it never makes —
the `CGO_ENABLED=0` clause and the coverage clause are two independent
statements joined by a semicolon, and the coverage claim holds regardless;
`analyse-a-session.md`'s "four steps" intro read against its five numbered
headings (the fifth, re-rendering the report, is grammatically set off by
an em dash as a follow-on outside the enumerated list, and invokes a
different pipeline command, `report`, from the four analysis-layer steps
proper); `session-directory.md`'s abbreviated `utt-003` example text read
against the fuller fixture sentence (the page's other examples are
established as illustrative reductions too — the manifest example already
drops three fixture fields — and no `findings.jsonl` quote cites `utt-003`,
so nothing depends on the byte-exact form). Also excluded before
verification, as a precedent duplicate: `AGENTS.md` claiming CI runs plain
`go test ./...` (round 13).
9 changes: 5 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,11 @@ install_ffmpeg_local() {
# available, and refuse on a bad or wrong-key signature.
# Every fetch/unpack below is guarded with the err-skip-return
# convention the parse failure already uses: ffmpeg is an OPTIONAL
# dependency, and under `set -eu` an unguarded failure aborted the
# whole installer with the child's raw exit code — skipping the ASR
# step and the closing guidance, and leaking $tmp2 (the EXIT trap
# covers only install_binary's $tmp).
# dependency, and under `set -eu` an unguarded failure would abort
# the whole installer with the child's raw exit code, skipping the
# ASR step and the closing guidance (install_binary's EXIT trap
# still sweeps $tmp2, so nothing leaks — but the abort itself is
# still the wrong outcome for an optional dependency).
say "Fetching static ffmpeg build (evermeet.cx) ..."
fetch "https://evermeet.cx/ffmpeg/info/ffmpeg/release" "$tmp2/info.json" \
|| { err "could not reach evermeet.cx; skipping ffmpeg"; rm -rf "$tmp2"; return; }
Expand Down
6 changes: 5 additions & 1 deletion internal/transcribe/transcribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,11 @@ func mapSegments(segs []segment, offset float64) ([]timeline.Utterance, error) {
}
for _, w := range s.words {
word := strings.TrimSpace(w.W)
if word == "" {
// Presence is decided on the rendered form, matching the segment
// guard above: an invisible-only word (Cf) is non-empty raw and
// survives TrimSpace, so it would otherwise reach transcript.jsonl
// and the analysis request as a timestamped word with no content.
if strings.TrimSpace(session.SafeText(word)) == "" {
continue
}
// An implausible word time is dropped, not refused: the same policy
Expand Down
30 changes: 30 additions & 0 deletions internal/transcribe/transcribe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,36 @@ func TestMapSegmentsDropsInvisibleOnlyText(t *testing.T) {
}
}

// TestMapSegmentsDropsInvisibleOnlyWord mirrors
// TestMapSegmentsDropsInvisibleOnlyText at word level: the word-level
// emptiness check tested only strings.TrimSpace(w.W), the raw form, unlike
// the segment-level guard immediately above it, which decides presence on
// session.SafeText's rendered form. A word that is entirely invisible-only
// Unicode (ZWSP U+200B) is non-empty raw and survives TrimSpace, so it
// reached transcript.jsonl, timeline.jsonl, and the analysis request as a
// timestamped word with no visible content. An ordinary whitespace-only word
// must still be dropped the same way.
func TestMapSegmentsDropsInvisibleOnlyWord(t *testing.T) {
zeroWidthSpace := string(rune(0x200B))
utts, err := mapSegments([]segment{
{start: 0, end: 1, text: "real words", words: []timeline.Word{
{W: "real", T: 0},
{W: zeroWidthSpace, T: 0.5},
{W: " ", T: 0.7},
{W: "words", T: 1},
}},
}, 0)
if err != nil {
t.Fatalf("mapSegments: %v", err)
}
if len(utts) != 1 {
t.Fatalf("want 1 utterance, got %d", len(utts))
}
if len(utts[0].Words) != 2 || utts[0].Words[0].W != "real" || utts[0].Words[1].W != "words" {
t.Fatalf("want only the two visible words kept, got %+v", utts[0].Words)
}
}

// TestCheckOffset pins the explicit-flag bound: the derived and sidecar paths
// refuse a non-finite or over-magnitude offset where the bad value enters, and
// the flag path must apply the same rule. Every genuine offset (including the
Expand Down