test(assets): a truncated RLE8 stream must be refused, not painted (red)

1 of 3 new guards RED, on an ASSERT about behaviour: `DID NOT RAISE
ExtractionError` on all four cuts. The other two are the section's own
known-positives and pass by describing what is already true -- where each
cut lands in the fixture's opcode stream, and that the UNCOMPRESSED BMP path
already refuses the same shape with `asset_samples_invalid`.

Measured by PM 2026-09-19 on a real R761 asset (352x548 = 192 896 pixels): a
stream cut to 90 % was carried with 13 923 pixels wrong, to 50 % with 95 890,
to 10 % with 166 525 -- no code, no row, a partly blank PNG standing under a
content address that says it holds the source's pixels.

The test brings its OWN permissive RLE8 decoder, so the pixel cost of each cut
is a number this file computes (8, 11, 0 and 0 of 32) rather than the package
agreeing with itself; two of the four cuts lose no pixel at all, which is the
whole reason the rule has to be the terminator and not a pixel count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 09:19:50 +02:00
commit c2080c4b34
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -503,3 +503,112 @@ def test_the_refused_image_leaves_a_row_the_reader_can_see(tmp_path: Path) -> No
assert "skjema.tiff" in text
log = (bundle / corpus.LOG_NAME).read_text("utf-8")
assert "image" in log.lower()
# --- 7. a truncated RLE8 stream is refused, never painted -------------------
#: The code the UNCOMPRESSED path already raises for a body that stops short
#: ("refusing to pad, because a short buffer means the header was read wrong").
#: Written out here rather than imported, like the two codes above.
CODE_SAMPLES_INVALID = "asset_samples_invalid"
def rle8_indices(stream: bytes, width: int, height: int) -> bytes:
"""A PERMISSIVE RLE8 decoder, written in this file, top-down.
It paints what the stream holds and leaves the rest at index 0. That is
what the format says about a SKIPPED pixel and what every decoder does
with a stream that simply stops -- which is exactly why a truncation is
invisible without a check: no decoder disagrees, they all agree on the
wrong picture. Its job here is to MEASURE what each cut costs, so the
guard below is pinned to a pixel count this file computed rather than to
the package's opinion of itself.
"""
rows = [bytearray(width) for _ in range(height)]
position, end, x, y = 0, len(stream), 0, 0
while position + 1 < end:
count, value = stream[position], stream[position + 1]
position += 2
if count:
if 0 <= y < height and x < width:
stop = min(x + count, width)
rows[y][x:stop] = bytes((value,)) * (stop - x)
x += count
continue
if value == 0:
x, y = 0, y + 1
elif value == 1:
break
elif value == 2:
if position + 2 > end:
break
x += stream[position]
y += stream[position + 1]
position += 2
else:
run = stream[position : position + value]
position += value + (value & 1)
if 0 <= y < height and x < width:
stop = min(x + len(run), width)
rows[y][x:stop] = run[: stop - x]
x += value
rows.reverse()
return b"".join(bytes(row) for row in rows)
def test_the_cuts_below_land_on_the_opcodes_this_file_names() -> None:
"""The fixture's own known-positive: a cut that no longer truncates
anything would make every guard below pass over nothing."""
assert len(RLE8_STREAM) == 36
assert RLE8_STREAM[32:34] == bytes((0x08, 0x01)), "the last encoded run"
assert RLE8_STREAM[20:22] == bytes((0x00, 0x08)), "the absolute block"
assert RLE8_STREAM[34:36] == bytes((0x00, 0x01)), "end-of-bitmap"
assert rle8_indices(RLE8_STREAM, 8, 4) == bytes(
index for row in EXPECTED_INDICES for index in row
)
def test_a_truncated_rle8_stream_is_refused_with_a_published_code() -> None:
"""Eval point 6 again, on the one case that was silent.
Measured by PM 2026-09-19 on a real R761 asset (352x548 = 192 896 pixels):
a stream cut to 90 % carried with 13 923 pixels wrong, to 50 % with 95 890,
to 10 % with 166 525 -- no code, no row, a partly blank PNG under a name
that says it holds the source's pixels. The uncompressed path already
refuses the same shape. NO PIXEL MAY BE GUESSED: either every one of them
is decoded from the stream, or the picture is refused.
THE RULE IS THE TERMINATOR, and it is read off the corpus rather than
chosen: over the 19 real RLE8 assets of the frozen R761 delivery, 19 of 19
end at an explicit end-of-bitmap escape, that escape is the stream's LAST
two bytes on 19 of 19, and `biSizeImage` equals the available bytes on
19 of 19. So a stream that runs out before its terminator is refused, and
so is one that is whole but never states it is finished -- the two are the
same bytes from a reader's side, and the measurement says no real writer
here produces the second.
"""
full = rle8_indices(RLE8_STREAM, 8, 4)
cuts = {
"cut in the middle of an encoded run": (RLE8_STREAM[:33], 8),
"cut in the middle of an absolute block": (RLE8_STREAM[:26], 11),
"cut immediately before end-of-bitmap": (RLE8_STREAM[:34], 0),
"ends with end-of-line instead of end-of-bitmap": (RLE8_STREAM[:34] + bytes((0, 0)), 0),
}
for label, (stream, lost) in cuts.items():
differ = sum(a != b for a, b in zip(full, rle8_indices(stream, 8, 4)))
assert differ == lost, f"{label}: {differ} pixels differ, this file says {lost}"
with pytest.raises(ExtractionError) as caught:
read_image(bmp_rle8(8, 4, PALETTE, stream), name="kuttet.bmp")
assert caught.value.code == CODE_SAMPLES_INVALID, f"{label}: {caught.value.code}"
# The control: the refusal is about the cut, not about the fixture.
whole = read_image(bmp_rle8(8, 4, PALETTE, RLE8_STREAM), name="hel.bmp")
assert whole.media_type == "image/png"
def test_the_uncompressed_path_refuses_the_same_shape() -> None:
"""The twin this rule was aligned to, so the two BMP paths cannot drift."""
short = bmp_24(RGB_ROWS)[:-30]
with pytest.raises(ExtractionError) as caught:
read_image(short, name="kort.bmp")
assert caught.value.code == CODE_SAMPLES_INVALID, caught.value.code