Skip to content

Robustness fixes to convergence; v0.3.0 CHANGELOG#22

Merged
REPPL merged 3 commits into
mainfrom
bughunt-6
Jul 22, 2026
Merged

Robustness fixes to convergence; v0.3.0 CHANGELOG#22
REPPL merged 3 commits into
mainfrom
bughunt-6

Conversation

@REPPL

@REPPL REPPL commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Three commits, reviewable separately. This is the branch that takes the hunt to genuine convergence and prepares the v0.3.0 release.

What's here

  1. Fix 11 defects as classes, sweeping for siblings (85586c1) — a re-hunt over the previous tree confirmed 7 distinct defects, five of them the same class as a fix from the prior round, left on a sibling path. Each is fixed here and the repo swept for further instances. Includes the round's one major: analyze findings had no t presence check, so a finding omitting its time filed silently at [00:00].
  2. Wire the t0 anchor guard to its callers; guard the read-side FIFO (c40ba64) — a further hunt found a major left by the previous round: session.Manifest.T0() was added with a doc comment naming its mandatory callers, then wired to none of them. timeline.Merge and transcribe.Run still read the raw field, so a manifest with an absent or negative anchor silently corrupted the timeline / transcript and reported success. Both now route through the accessor. Also closes the read-side counterpart of the FIFO guard (ReadJSONL, LoadManifest, analyze.Load, holdsVerdicts).
  3. CHANGELOG entry for v0.3.0 (2155ec3).

Convergence

This branch was driven to a genuinely clean review, not a declared one. The full history across this effort:

Round Non-nitpick defects confirmed
prior hunt (PR #19 "noise floor, stop") declared done
hunt over that tree 13 (2 major)
re-hunt over the fixes 7 (1 major) — 5 were siblings of the previous round's own fixes
hunt over those fixes 1 major (the unwired T0() guard)
hunt over this branch 0 — two full rounds of 11 reviewers each, plus a completeness sweep, all empty

The final hunt added a dedicated unwired-guard audit — enumerating every guard, accessor and validator in the repo and confirming each is actually called by every site it protects — precisely because the T0() bug was a guard that existed but wasn't wired. It found nothing further.

Every fix carries a regression test verified failing against the pre-fix code. gofmt, go vet, go test -race ./... and the sample pipeline all pass; the sample session's merged output is byte-identical.

Note on process

The recurring pattern — each round finding real bugs the previous round's "done" missed, five of them siblings of just-applied fixes — is why the later rounds mandate a documented sibling sweep at every fix, and why the final round audits guards for their callers rather than their existence. A fix is not done when the reported line is patched; it is done when its class has been swept.

Assisted-by: Claude:claude-opus-4-8

REPPL added 3 commits July 20, 2026 14:47
A re-hunt over the previous round's tree confirmed 9 findings (7 distinct).
Five were the same defect class as a fix from that round, left on a sibling
path — the previous round patched reported sites rather than classes. This
round fixes each defect and sweeps the repo for its siblings.

Reported defects:
- analyze: reject a finding whose required t is absent. It decoded to the
  zero value, passed the range check, and filed at [00:00] — tens of seconds
  from the utterance it quoted. Decoded through a pointer-typed raw shape,
  mirroring timeline.rawInteraction, because t = 0 is a legitimate anchor.
- analyze: bound a finding's serialised length, not just its evidence count,
  so ingest cannot write a findings.jsonl line no reader can take back.
- session: enforce MaxJSONLLine inside WriteJSONL, the invariant its own doc
  comment declared every writer must respect. Closes timeline.jsonl,
  findings.jsonl and transcript.jsonl at once.
- record: stop the demo server through the bounded demo.Shutdown at both
  call sites, so record -demo gets the deadline the previous round added.
- report: render negative session-relative times signed instead of clamping
  them to zero. Pre-t0 entries are legitimate and were misreported as 00:00.
- transcribe: refuse any non-regular ffmpeg input or output path, not just a
  symlink, so a planted FIFO cannot block open(2) for ever.
- review: roll back a partial verdict write, so a short write cannot leave a
  newline-less fragment that breaks findings.jsonl for every reader.

Siblings the sweeps found, fixed here:
- review: the same negative-time clamp, byte-identical to report's.
- timeline: utterances were value-typed while interactions in the same file
  were already guarded, so a transcript omitting t0 planted speech at the
  session start. A missing t1 now defaults to t0, matching SpeechEnd, rather
  than inverting the join window.
- transcribe: whisperx and whisper.cpp segment times were value-typed while
  the word-level times beside them were already pointers.

Sweeps also confirmed the remaining matches are unaffected, with reasons
recorded in the code: signal-wait contexts that must not carry a deadline,
sort and window arithmetic that handles negatives correctly, and manifest
t0 which timeline.Merge already guards.

gofmt, vet, go test -race, and the sample pipeline all pass; the sample
session's merged output is byte-identical.

Assisted-by: Claude:claude-opus-4-8
A third hunt (truncated by a usage limit, but its first round complete)
found a major left by the previous round: session.Manifest.T0() was added
with a doc comment naming timeline.BuildEntries and transcribe's offset
derivation as mandatory callers, then wired to neither. A guard with a doc
comment and no callers is worse than none — it reads as protection that is
not there.

- timeline.Merge: resolve the anchor through man.T0() instead of an inline
  `== 0` test. The old test was narrower than T0()'s `<= 0`, so a negative
  t0_epoch_ms slipped through and BuildEntries shifted every interaction by
  +|t0|, writing a silently corrupt timeline and exiting 0. The transcript-
  only exemption (no interactions, no anchor needed) is preserved.
- transcribe.Run: derive the audio offset through man.T0(), failing the run
  when an external recording's manifest carries no usable anchor. Pre-fix a
  missing t0 made deriveOffset return roughly the whole Unix epoch, writing a
  transcript ~56 years off and reporting success. The session-audio and
  explicit -offset paths never consult t0 and are unaffected.

Read-side FIFO guard (same class as the write-side fix already shipped):
- session: OpenFileNoFollow's symlink+regular-file check is now shared with a
  new OpenFileNoFollowRead via one openNoFollow core (no third copy). ReadJSONL
  and LoadManifest route through it, so a FIFO planted at a session artefact is
  refused rather than blocking open(2) for ever. Write-side messages unchanged.
- analyze.Load and analyze.holdsVerdicts: the two sibling os.Open sites the
  session sweep flagged, now routed through OpenFileNoFollowRead as well.

gofmt, vet, go test -race and the sample pipeline all pass; sample output
byte-identical.

Assisted-by: Claude:claude-opus-4-8
A robustness release: no new commands, but the pipeline no longer accepts
malformed or hostile session input in silence. Groups the ~35 fixes since
v0.2.0 by theme — evidence-record integrity, robustness against exchanged
sessions, and resource lifecycle — and records under Changed that boundary
validation is now stricter, refusing schema-violating input that earlier
versions accepted and turned into a silently wrong artefact.

Assisted-by: Claude:claude-opus-4-8
@REPPL
REPPL merged commit d965f4c into main Jul 22, 2026
6 checks passed
@REPPL
REPPL deleted the bughunt-6 branch July 22, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant