Repository navigation
Keep the advert pass off the listeners' thread, and make it skip what it has already done - #15
Merged
Merged
Conversation
… it has already done Audit tier 3 (P1-P6) and X2. P1: reading, hashing and decoding an episode — to fingerprint it or to hand a window of it to the recogniser — now runs in the audio worker that already held the corpus searches. The main thread hands over a gated path and writes down what comes back; typed arrays are transferred, not copied, and the fingerprint is byte for byte what the main thread produced. Measured on an hour of audio: a 10 ms timer fired 10,046 ms late inline and 1 ms late with the worker. A worker that crashes or wedges past its timeout fails that episode loudly and is restarted. X2: fingerprinting, listening and cutting all go through one gate that resolves the path inside the show folder, stats it, and refuses a link out of the folder, anything that is not an ordinary file, and anything over 768 MB — before a byte is read, and again on the open descriptor. A refusal is a banner naming the file and one line in the pass's activity entry. P2: the detectors run only when their inputs or the catalogue content changed since the last pass (a per-show key the pipeline keeps in memory, plus a counter every owner action bumps). The jingle is located only in episodes whose fingerprint is newer than their hit row; fingerprints are read lazily, a header only where timing is all that is needed; the held-episode count is one GROUP BY. A taught boundary links to the jingle within the pass instead of the next one. P3: the two sample routes read only the frames they play, through a per-file frame table built by a chunked walk proven frame-for-frame equal to readFrames; the trimmer hashes and streams the cut's parts rather than concatenating a copy. P4: @fastify/compress 9.2.1 for HTML, CSS, JS and JSON, in the web and API contexts only; audio and the pre-compressed feed are untouched, and request bodies are never inflated. P5/P6: session not rolling; PRAGMA optimize on close and daily; journal_size_limit; hoisted prepares; migration 012 indexing the advert tables' foreign keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Tier 3 of the October audit (
doc/audit-2026-10-09.md, section 3, plus X2 from section 2). The advert pass decoded audio on the HTTP thread, re-did all of its work every five minutes, and read whatever was on the share without the containment check the serving routes use. Stacked on #14.What changed
src/lib/audio-tasks.js(fingerprint a file, profile a file, decode one transcription window to WAV) runs in the existing worker with transferred typed arrays; the main thread only ever sees results. A per-task timeout terminates a wedged worker and fails that episode rather than hanging the pass. Measured with a 10 ms interval during a 1-hour fingerprint: inline 10,046 ms late, worker 1 ms late. On a NAS this was the difference between/healthanswering and the container being restarted mid-pass.src/lib/pipeline-file.js:resolveContained→stat→ regular file only → 768 MB cap, re-checked on the open descriptor withO_NONBLOCKso a FIFO is refused instead of blocking. Used by the fingerprinter, transcriber, trimmer and the sample routes. A refusal names the file once in the activity log and raises a banner.locateAnchorruns only for (anchor, episode) pairs without a fresh hit row (checked_atis now read); fingerprints load lazily;settleHoldsis oneGROUP BYinstead of a query per episode.src/lib/mp3-range.jswalks frame headers in chunks and reads only the byte range a sample needs, with a small cache keyed on (path, size, mtime); the trimmer streams cut parts instead of holding two copies.@fastify/compressfor HTML, CSS, JS and JSON only, registered inside encapsulated contexts so it can never touch/mediaor/feeds; explicit text types, no request-body decompression.rolling: falsesessions (12 hours from sign-in, matching "Stay signed in for");PRAGMA optimizeon close and daily;journal_size_limit; hoisted prepared statements; migration 012 adds the four missing FK indexes on the advert tables.How it was verified
npm test: 1376 pass, 0 fail (stacked on Count only what was actually served: no HEADs, no 304s, no abandoned downloads #12, Refuse giant images, hide shows from strangers, and sign other sessions out on a password change #13, Answer every press, keep what is being typed, and fit a phone #14). 40 new tests: worker results byte-identical to the inline path, loop-lateness bound (and a control proving the inline path blocks), FIFO/symlink/oversized refusals with banners and single activity warnings, incremental pass (unchanged → zero work twice; new episode → one locate; decision/teach/catalogue edit reopen detection), frame-walk equivalence on ten MP3 shapes, compression on HTML/JSON and never on audio or the feed, migration indexes present and used../test/acceptance/run.sh selfpod:tier3: 63 passed, 0 failed against a built image.Known limits: the "last detected" key is in memory, so every show is looked at once after a restart; a decode failure in the worker during transcription counts toward the existing whisper breaker.
🤖 Generated with Claude Code