From c24573bd9f2f2d7bac4621e1d49e4634c69df600 Mon Sep 17 00:00:00 2001 From: Mathias Date: Sat, 3 Oct 2026 21:32:14 +0200 Subject: [PATCH] docs: state what the save timeout and a racing delete actually guarantee (#51, #37) The tracker comment said 'never a stalled job'; each transition can wait up to the timeout. The delete refusal is judged at the moment of the attempt. Found by the PR #58 review. Co-Authored-By: Claude Sonnet 5.5 --- DECISIONS.md | 4 +++- internal/progress/tracker.go | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DECISIONS.md b/DECISIONS.md index 01f0dc4..69786f3 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1556,7 +1556,9 @@ deferred. The scope cut — chat first, no rich analysis, no video — is what k mechanism), confirm-then-delete on the recording's page. A recording that is **queued or transcribing is refused (409) until it ends**, because the worker would otherwise write to a row that is gone; a job whose service was lost stays undeletable until the next start's sweep - marks it failed. The home list now links to `/uploads` when uploads are mounted. + marks it failed. The refusal reflects the row's state when the delete was attempted: a + recording that finishes in the same instant still gets a 409 once, and a retry deletes it. + The home list now links to `/uploads` when uploads are mounted. **Reversibility.** Additive: one table (down-migration drops it), one adapter, one upload surface, one image change. Removing the upload route removes the feature; `transcripts`, `videos` diff --git a/internal/progress/tracker.go b/internal/progress/tracker.go index c365d58..deaca50 100644 --- a/internal/progress/tracker.go +++ b/internal/progress/tracker.go @@ -57,8 +57,11 @@ func WithClock(now func() time.Time) Option { return func(t *Tracker) { t.now = func WithMinSave(d time.Duration) Option { return func(t *Tracker) { t.minSave = d } } // WithSaveTimeout bounds one save or heartbeat write. Progress is cosmetic, so a database -// that stops answering costs a dropped write, never a stalled job: saves run under the -// tracker's lock, which Begin and Advance (called by the engines) need (#51). +// that stops answering costs a dropped write rather than an unbounded stall. It is still a +// delay: saves run under the tracker's lock, which Begin and Advance (called by the engines) +// need, so each transition can wait up to this long, and a hung database slows a job by +// that much per call, out of the job's own time budget (#51). A dropped final save is not +// retried; the row keeps its last saved step. func WithSaveTimeout(d time.Duration) Option { return func(t *Tracker) { t.saveTimeout = d } } // Tracker implements Reporter for one job.