Entry 20
One plus-minus sign crashed the scanner
Filescrates/cairns-core/src/entry.rs
thread 'main' panicked at crates/cairns-core/src/entry.rs:490:32:
start byte index 1 is not a char boundary; it is inside '±' (bytes 0..2 of string)
The reference scanner from 19 walks each line looking for [[, and where
a character is not one it advances with at += 1. That is a byte, not a
character. ± is two bytes, so the next line[at..] started inside it and
Rust refused - correctly, and fatally.
It steps by ch.len_utf8() now. Hellbender's log contains eight ±, amber's
contains eleven em dashes, and both were enough; hellbender's is the log that
found it.
What the tests missed and why
There were six tests on this scanner, covering references, labels, fenced
code, inline code and dangling numbers. Every one of them was written in
ASCII, because I wrote them, and I was thinking about the syntax rather than
about the text it sits in. A worklog is prose about measurements - ±, —,
µs, ° - and the one thing the scanner is guaranteed to meet is a character
I did not type into a test.
The new tests run the scanner over ±, an em dash, an emoji, ±[[3]]±, and a
fence containing ±, and assert the surrounding text survives the rewrite
intact rather than merely that nothing panicked.
It is also worth noticing what caught it: not check, not the tests, not the
build. Someone ran it on a real log. That is the fourth time in two days that
the thing which found the fault was the tool meeting real content rather than
anything I ran against content I had invented.
The rest of the codebase, checked
The same habit of mind wrote the front matter splitter, the slug derivation, the summary extractor and the trailer parser, so all of them were read again. They are safe, and for a reason worth recording rather than by luck:
- every one of them slices at an index found by searching for an ASCII
delimiter -
\n,---,.,/,#,**Still unknown:**- and a byte index found that way is always on a character boundary slugifytruncates its own output, which is ASCII by construction: it emits onlya-z,0-9and-- the one other
at += 1in the tree walks aVec<Event>, not a string
So the scanner was the only place advancing a byte at a time through text it had not built. That is the distinction to watch for: searching for ASCII and slicing there is fine, and stepping through arbitrary text is not.