5.6 KiB
Sprint: audio upload follow-ups (Sweep Sprint, 2026-10)
Method: Sweep Sprint (phase 1 parallel read-only research per issue: done; phase 2 sequential execution, one issue per commit: not started, waits for the owner's go). Closing oath below.
Milestones: v0.31.1 - security and robustness, v0.32.0 - audio upload follow-ups.
Context: v0.31.0 shipped audio upload (ADR-033/034/035) and is live in production behind auth at
/uploads, with the Berget cloud fallback on for audio (infra#522). A real 63.7 min upload
transcribed in 2m53s with no errors. The phase-1 investigation found two pre-existing or new
weaknesses an authenticated user can reach, so the first release is a hotfix.
Oath (closing gates, every clause must hold; blocked = descoped and tracked, never "done")
| # | Clause | Evidence |
|---|---|---|
| SG1 | Every issue in the sprint is closed with a linked commit, or moved out with a stated reason | issue list of both milestones |
| SG2 | Each fix has a test that was seen red on main first, named after the mechanism it observes | red/green output in the issue |
| SG3 | task check AND golangci-lint run ./... exit 0 (checked by $?, not by grep); task check does not gate lint until #41 |
gate output |
| SG4 | Control code (#48, #49, the upload cap in #47) has a recorded adversarial review verified by execution, verdict in the issue | gatekeeper verdict comment |
| SG5 | Docs current in the same commit: ADR-033/035 text, feature file, use-case coverage test | diff |
| SG6 | Each release tagged (v0.31.1, v0.32.0), pushed, deployed, and observed live (pod running the new image, no restart loop, a real upload still works) | kubectl/log + one upload |
| SG7 | Learnings written to the brain; deferred work filed as issues; decisions that are the owner's recorded as DECISION NEEDED | brain ids + issue numbers |
Work order
v0.31.1 (security and robustness; hotfix, do first)
- #41 lint gate.
task checkswallows lint findings (|| echo). Make the gate fail on findings, fix what it surfaces. First, so every later gate means something. (Decision D1.) - #48 cross-origin POST protection.
http.NewCrossOriginProtection()aroundRouter(). Red: foreign-origin POST to/account/delete,/uploads,/uploads/{id}/chatis refused. - #49 ffprobe/ffmpeg limits.
-max_streams 32+ narrower probe output; probe stdout cap; probe deadline. Three commits. - #47 concurrent-upload cap (remaining item). Bound the spool: reservation per accepted upload against the 6Gi emptyDir so concurrent uploads cannot evict the pod.
- #50 SIGTERM interim. Handle SIGTERM; record the interrupted job at once; fix the false comment. Full drain recorded as a limitation. (Decision D2.)
Release v0.31.1: one adversarial review round over 2-5, then tag, push, observe.
v0.32.0 (follow-ups)
- #52 label a cloud-served transcript in plain words; document the empty-transcript trigger.
- #53 bound the LLM client's response body read.
- #51 tracker: save timeout, then app-clock heartbeat.
- #37 nav link to uploads and per-recording delete (queued/transcribing: refuse until it ends). (Decision D3.)
- #36 chat default model after measurement. (Decision D4.)
- #38 measurements (Berget length limit, large models on production iguana). (D5, D6.)
- #46 remainder of the hardening list after the items above are split out.
- #45 evaluate Berget as the reconciler: a question, not work. (D8.)
DECISION NEEDED (owner's call; recommendation first)
| # | Question | Recommendation | Blocks |
|---|---|---|---|
| D1 | #41: should any linter be allowed to opt out of the gate? | No opt-out; fix findings | #41 |
| D2 | #50: full SIGTERM drain, or record as a limitation and ship the interim fix? | Limitation + interim; a drain costs 4-6 h and downtime on every deploy at current volume | #50 |
| D3 | #37: delete of a queued/transcribing recording | Refuse until it ends | #37 |
| D4 | #36: make a 31B model the chat default? | Only after measurement, conditional | #36 |
| D5 | #38: Berget length measurement needs a throwaway key | Yes, throwaway key | #38 |
| D6 | #38: load large models on production iguana | Yes, in a quiet window with the owner present | #38 |
| D7 | #46: where the ffmpeg tarball lives | Do nothing now; file an issue | none |
| D8 | #45: evaluate Berget as the reconciler | No; stay local-only | #45 |
| D9 | Who judges ADR-034's second blind check | The owner; do not reuse the one-off Opus authorisation | ADR-034 |
| D10 | When to enable reconcile in production | Needs iguana/pianissimo in the key pin and env vars; owner present |
ADR-034 |
Dropped or reduced, with reasons
- #46 item 1 (
alis/drefexternal reference): checked, not a problem. Under the application's flags ffmpeg skips the external track and writes a 78-byte WAV. - #46 item 2 / #47 item 2: merged into #49.
- #47 item 3 (full drain): reduced to #50 plus a recorded limitation.
- #46
Beattaking the tracker mutex: incorrect in #46; onlylogStilldoes. Corrected in #51.
Human-present items (do not run unattended)
- Production iguana model loads (#38, D6).
- Enabling reconcile in production (D10).
- Any key change or
applyin infra (infra#523, infra#524 are open and not part of this sprint).
Release plan
SemVer: v0.31.1 patch (fixes, no new behaviour); v0.32.0 minor (nav link, delete, cloud labelling). Every push to tapir main redeploys the pod and fails any job in flight: push when no upload is running (check the pod log first).