Fuzz the substitution surface, the soak tier's first target
subst/parser.rs is row 6 of astra-soak-overview and the cheapest target in
the tree, which is why it goes first: it proves the loop before eight more
harnesses exist.
The target is substitute() rather than parse_expr(), which is pub(crate)
and half the surface. One call runs code-span detection, the marker regex,
path lookup, the filter chain and the formatter.
Three oracles beyond not-panicking, because a target that only asserts no
panic reports clean forever while returning wrong answers: code-span ranges
must be usable as byte ranges into the input, text with no marker must come
back unchanged, and substitution must reach a fixed point.
That last one caught its own first draft. The table held a value whose text
was itself a marker, so a second pass legitimately differed and the fuzzer
reported it in under a minute. The untrusted input on this path is the
template, not the table, so the value came out and the reasoning is in the
target. Whether hostile values should also be inert is a real question, a
different one, and wants its own target rather than a weakened oracle.
822,005 runs clean at 6,793 exec/s after the fix.
seeds/ is committed and corpus/ is not. Seeds are human intent, reviewable
in a diff, and name the grammar shapes worth reaching in the first second.
The working corpus is machine output that belongs on astra until it
represents real soak hours.
Author: Max Johnson <me@maxj.phd> · 2026-08-11 23:19 UTC