Fast-path decoders must accept exactly what the full decoder accepts
Bug pattern found in a compressed-file reader. A point query used a streaming partial decode that stopped as soon as it had the requested row. The full-block decoder decompressed and validated the whole payload. So a block with corruption past the answer, such as a damaged compressed tail or a bad later row, was rejected by the full path but served by the fast path. The same file then gave an answer or an error depending on which API was called, and corruption went undetected on the fast path.
Fix: make the fast path a restriction of the full decoder. Decompress and check the whole unit, then build values only for the rows you return. That keeps most of the speed benefit, since the work saved was in building values, not in validation.
Test: a hostile-input suite that runs every mutated input through both paths and fails on disagreement in either direction, with queries early, in the middle and at the end of the unit. In our case strict validation cost ~25-35 us per point read (21 -> 55 us) and was accepted for correctness.
Second data point from a different consumer of the same reader: its own streaming point-read path had the same flaw. A suite with valid chunk checksums and three edits (trailing byte, a junk byte between the time section and the value section, a last time just past the block end), queried early, mid-block and at the last row across 5 row kinds, found 22 of 45 reads answered where the full decode refuses. The junk-byte cases returned wrong values, not just accepted ones. Same fix: decompress and check the whole chunk, build values only up to the query. Measured cost per point read on a 16384-row chunk: 7 -> 53 us for 8-bit rows, 90 -> 323 us for 256-bit rows. Gotcha in the test harness: if a mode env var is read only at open and only applied when set, deleting it does not restore the default, so pin it explicitly in each phase.
Follow-up from applying this fix: the usual fix (decompress the whole payload and run the full decoder, keeping only rows <= the query) closes the parity gap. But if the decoder builds every kept row's value and the caller then trims to the last one, a string column with back-references copies each referenced string into the output. In one case a single point read late in a chunk went from about 50 MB to about 300 MB of RSS, and from 1 to 22 ms. Validate every row, but build values only for the rows you return; for dropped rows, keep literals as offsets into the decompressed buffer. The cost of strictness was 3-5x on early-row reads (wide rows worst), which was fine for correctness. Also, for a differential fuzz that sends random payloads to both paths, test the harness on the old code first: there it showed hundreds of disagreements, so a clean run on the new code means something.