bathy-interpret: the pure interpretation layer.
This crate turns the raw bytes a [bathy_types::ProbeCapture] recorded
into zero or more [Interpretation]s -- structured claims about what
service, product, and version a peer's response is evidence for. It is
the reason findings in this project are explainable and replayable:
- Explainable. Every [
Interpretation] names the exactrule_idthat produced it and the exact byte range of the response that justified it (matched_span). [explain] resolves any rule id this crate can produce back to human-readable documentation and its provenance. M5'sfingerprint.explaintool is built directly on this. - Replayable. [
interpret] is a pure function: no I/O, no clock, no randomness, no async runtime. Feed it the same bytes years later and it makes the same claim, because nothing except those bytes and this crate's own source code ever fed the decision. M4 Task 4's replay corpus depends on this holding exactly, with the network interface down; M7's fuzz target depends on it never panicking regardless.
Purity is enforced structurally, not just by convention
bathy-interpret depends on exactly two crates: bathy-types (for
[bathy_types::ProbeCapture], [bathy_types::event::Observation], and
[bathy_types::confidence::Confidence] -- the shapes this crate
consumes and produces) and regex (for matching text-shaped protocol
banners). This crate's own code never touches tokio, the filesystem, a
clock, or a random-number generator -- cargo tree -p bathy-interpret --edges normal is asserted in CI to show only bathy-types, regex,
and their own transitive dependencies (AC-4.10), and no matcher in
rules.rs calls anything from either crate except regex::Regex
itself and plain byte/string operations.
(Narrowed claim, M4 Task 3 review round 1: an earlier version of this
paragraph said "no randomness anywhere in ... its dependency graph,"
which overstates what AC-4.10 actually checks. bathy-types itself
depends on ulid, which depends on rand/getrandom -- getrandom
is present in this crate's dependency tree; bathy_types::clock is
the sanctioned, sole call site for it in this workspace, and this
crate's own code never calls it. That inheritance is unavoidable and
judged acceptable: it is linkable, not callable, from here, and
removing it would not actually shrink the built artifact -- Cargo's
feature unification means every other crate in the workspace that
depends on bathy-types already pulls the same dependency in, so it is
compiled into any binary that links this crate either way.)
This crate also sits below bathy-probe in this workspace's layer
order (xtask's LAYERS), specifically so ProbeCapture fixtures can
be built by hand in a test or fuzz target with no socket anywhere in
the dependency graph at all.
No panics on the byte path (Global Constraint), and how it is scoped
The #![cfg_attr(not(test), deny(...))] above is the executable form of
the overview's "No panics in parsing paths" constraint. That constraint
said unwrap()/expect()/indexing-slice panics were "denied by lint" in
this crate and in bathy-probe from M1, and no such lint existed
anywhere in the tree until the M7 verification round. It was an
aspiration written in the indicative mood for six milestones, in the one
crate whose entire input is bytes a scanned peer chose.
It found real hits here, and they are not stylistic: utf8_lines sliced
bytes[start..i], u16_at indexed a two-byte window it had just
get-checked, mysql_handshake_v10 and dns_bind_version sliced with
offsets built by unchecked +, tls_server_hello indexed a header, and
every text-shaped rule computed its matched_span as a bare `line_start
- m.start()
. That last expression is the one thefrom_utf8_lossydefect corrupted and the one seven span mutants attacked across three review rounds; it now lives in exactly one place,rules::absolute_span, and it is checked. Seerules.rs`'s own "Byte safety" note.
How test code is exempt. cfg_attr(not(test), ...), not a bare
deny. Under cargo clippy --all-targets the library is compiled twice:
once as the lib, where cfg(test) is off and the deny is live over every
line of production code, and once as the unit-test harness, where
cfg(test) is on and the deny is absent -- so #[cfg(test)] mod tests
keeps unwrap(). tests/replay.rs, tests/span_edge_corpus.rs and
benches/interpret.rs are separate crates that never see the attribute.
A crate-level #![allow] of any of these would reproduce the exact
defect being closed, so the two exceptions in this crate
(Specificity::confidence and rules::static_regex, both over
compile-time-constant inputs) are site-level, carry a reason, and are
each backed by a test that fails if the reasoning stops being true.
cargo run -p xtask -- check-panics enforces that shape, and
additionally holds the overview's constraint text to the set of crates
that actually carry the attribute.
Never guess (AC-4.13)
[interpret] returns an empty vector when nothing in its rule set
recognizes the input. A scanner that invents a service from bytes that
do not structurally support the claim is worse than one that reports
nothing -- see interpret::tests::unrecognized_bytes_yield_no_observation_rather_than_a_guess.